In this tutorial, we will learn some of the most important data annotations that are used when modeling a simple class in ASP.NET. Some of these annotations pertain to validations, others pertain to database related schemas and constraints, and yet others pertain to data formatting. Although all these concepts work for both ASP.NET MVC and Razor Pages, we will be using Razor Pages in this tutorial.
Source Code: https://github.com/medhatelmasry/AnnotationsDemo
Companion Video: https://www.youtube.com/watch?v=6_twITOH-Tc
Assumptions
It is assumed that you have the following installed on your computer:
- .NET 7.0
- “dotnet-ef” tool
- “aspnet-codegenerator” tool
- Visual Studio code
Getting Started
In a terminal window, run the following command to creates an ASP.NET Razor Pages application that user the SQLite database with individual authentication:
dotnet new razor -f net7.0 --auth individual -o AnnotationsDemo
Change directory to where the app was created with:
cd AnnotationsDemo
Add the following packages to the application:
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
In a /Models folder, create a Student class and add to it the following code:
Class Level Annotations
Annotation | What it does . . . |
---|---|
[Table("PublicSchoolStudent")] | The database will be named PublicSchoolStudent |
[Index(nameof(School))] | The School column in the database will be indexed |
Column Level Annotations
Annotation | What it does . . . |
---|---|
[Key] | This ensures that the property is made a Primary Key |
[Key] [Column(Order = 1)] public int StudentNumber { get; set; } [Key] [Column(Order = 2)] public string? School { get; set; } |
This defines a composite key comprising StudentNumber and School NOTE: You must add this code to the OnModelCreating() method in the database context class for this to work: builder.Entity table.PassportNumber, table.IssuingCountry }); |
[MaxLength(60), MinLength(5)] public string? School { get; set; } |
The maximum and minimum length of the School property is 60 and 5 respectively. Only Maxength affects the database schema. |
[Required(ErrorMessage = "{0} is required.")] public string? FirstName { get; set; } |
This ensures that the FirstName property must have a value. ErrorMessage is optional. |
[StringLength(30, ErrorMessage = "{0} must be between {2} & {1} characters."), MinLength(5)] public string? FirstName { get; set; } |
StringLength allows for the MaxLength and MinLength to be combines into one annotation such that the error message can describe both constraints. |
[Display(Name = "First Name")] public string? FirstName { get; set; } |
Instead of FirstName, the display name will be “First Name”. |
[RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$", ErrorMessage = "Only letters allowed.")] public string? LastName { get; set; } |
The regular expression for LastName matches any string that contains letters or spaces. |
[NotMapped] public string FullName { get { return $"{FirstName} {LastName}"; } } |
This property will not be mapped into the database schema because it is a calculated property in the application. |
[Column("Note", TypeName = "NTEXT")] public String? Comment { get; set; } |
The Comment property in the application will be mapped as a Note column in the database of type NTEXT. |
[DatabaseGenerated(DatabaseGeneratedOption.Computed)] public DateTime DateCreated { get; set; } |
The DateCreated column will be generated by the database engine. Note: You must add the proprietary function for generating the current date in the OnModelCreating() method in the database context class. In the case of SQLite it would look like this: builder.Entity .Property(s => s.DateCreated) .HasDefaultValueSql("DATE('now')"); In the case of SQL Server, it would look like this: builder.Entity .Property(s => s.DateCreated) .HasDefaultValueSql("GETDATE()"); |
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")] public DateTime DateCreated { get; set; } |
The display format for DateCreated will be MM/dd/yyyy. Example: 10/29/2022 |
[Range(10, 1000, ErrorMessage = "Value for {0} must be between {1} and {2}.")] public int Weight; |
The Weight column will have a range from 10 to 1000. Both are inclusive. |
[DataType(DataType.EmailAddress)] public string? Email { get; set; } |
The data type for the Email property is specified as EmailAddress |
public string? Email { get; set; } [Compare("Email")] public string? EmailConfirm { get; set; } |
The Compare annotation makes sure that EmailConfirm is equal to Email |
[ScaffoldColumn(false)] public string? StudentPhotoFileName; |
The code generator will not scaffold the StudentPhotoFileName column |
protected override void OnModelCreating(ModelBuilder builder) {base.OnModelCreating(builder);builder.Entity<Student>().HasKey(table => new{table.StudentNumber,table.School});// GETDATE() in SQL Serverbuilder.Entity<Student>().Property(s => s.DateCreated).HasDefaultValueSql("DATE('now')");}public DbSet<Student>? Students { get; set; }
- The name of the table is PublicSchoolStudent
- All the model property MaxLength values are being applied to the database schema
- The Comment property in the Student model is called Note in the database and is set of type NTEXT
- The DateCreated column in the database has a default value generated with the DATE('now') SQLite function
- The primary key is a composite key of StudentNumber & School
- An index will be created on the School column
<td>
@Html.ActionLink("Edit", "Edit", new { /* id=item.PrimaryKey */ }) |@Html.ActionLink("Details", "Details", new { /* id=item.PrimaryKey */ }) |@Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })</td>
<td><a asp-page="./Edit" asp-route-id="@item.StudentNumber" asp-route-school="@item.School">Edit</a> |<a asp-page="./Details" asp-route-id="@item.StudentNumber" asp-route-school="@item.School">Details</a> |<a asp-page="./Delete" asp-route-id="@item.StudentNumber" asp-route-school="@item.School">Delete</a></td>
No comments:
Post a Comment