Core MVC - Model Binding Part 2
Core MVC - Model Binding Part 2
Less Type Safety: Errors related to type mismatches or missing fields might
only be detected at runtime.
No Model Validation: We need to handle validation and error messages
manually.
Security: Directly accessing form values using FormCollection can cause
security issues like SQL injection or XSS attacks.
Using Model Binding in ASP.NET Core MVC
Let’s see how we can rewrite the previous example using Model binding. First, let’s create a
model to hold the UserName and UserEmail. So, create a class file with the
name User.cs within the Models folder and copy and paste the following code.
using System.ComponentModel.DataAnnotations;
namespace ModelBindingDemo.Models
{
public class User
{
[Required]
public string UserName { get; set; }
[Required]
public string UserEmail { get; set; }
}
}
Next, modify the Index.cshtml view as follows. As you can see, the User is now the model
for the following view.
@model User
@{
ViewData["Title"] = "Home Page";
}
<form method="post" asp-controller="Home" asp-action="SubmitForm">
<label>Name:</label>
<input asp-for="UserName" />
<br />
<label>Email:</label>
<input asp-for="UserEmail" />
<br />
<button type="submit">Submit</button>
</form>
4
manually extract data from the request; the framework handles it for us based on the
parameters of our action methods.
How Does Model Binding Work in ASP.NET Core?
When a request is made to an MVC controller, the model binding system:
Retrieves Data: It looks at the source of the incoming HTTP request (like
form values, route data, query string parameters, request body, and HTTP
headers).
Converts Data: It then attempts to convert these data values into .NET types
that match the parameters of the controller action being called.
Validation: Optionally, model binding also works with data annotation
attributes to validate data as it’s converted into types.
Note: The model binder looks for data in the request that matches the names of the action
method’s parameters. Depending on the source attribute (e.g., [FromQuery], [FromBody],
[FromRoute]), it knows where to look, such as in the query string, the request body, or the
route data.
Different Model Binding Techniques in ASP.NET Core MVC:
You can configure model binding using various attributes that control how data is extracted
from the request. Some common attributes include:
[FromBody]: To bind data from the request body.
[FromForm]: To bind data from form fields.
[FromQuery]: To bind data from the query string.
[FromRoute]: To bind data from route data.
[FromHeader]: To bind data from headers.
Note: We will discuss all these model-binding techniques in detail in our next article. This
article will help us understand the basics of these model binding techniques and how we
can use them in our controller action methods.
FromBody:
This attribute tells ASP.NET Core to bind data from the request body to the parameter of an
action method. It’s commonly used when complex types like objects or collections, typically
in JSON or XML format, are expected in the body of a POST request. It is commonly used
in Restful Services.
[HttpPost]
public IActionResult Create([FromBody] Product product)
{
//...
}
FromForm:
The [FromForm] attribute binds data from HTML form fields to action method parameters.
It’s suitable for handling form submissions via HTTP POST requests and is commonly used
in ASP.NET Core MVC Applications.
[HttpPost]
public IActionResult Register([FromForm] UserRegistrationModel user)
{
6
//...
}
FromQuery:
The [FromQuery] attribute binds data from the URL’s query string to action method
parameters. It’s often used for GET requests where data is passed in the URL.
public IActionResult Search([FromQuery] string query)
{
//...
}
FromRoute:
The [FromRoute] attribute is used to bind data from route parameters to action method
parameters. Route parameters are placeholders in the URL defined in your route templates.
[HttpGet("{id}")]
public IActionResult Get([FromRoute]int id)
{
//...
}
FromHeader:
The [FromHeader] attribute binds data from HTTP headers to action method parameters.
It’s useful for extracting custom headers or standard headers like Authorization, User-Agent,
etc.
public IActionResult CheckUserAgent([FromHeader(Name = "User-Agent")] string
userAgent)
{
//...
}
Why Model Binding in ASP.NET Core?
There are several reasons why model binding is important. They are as follows:
Automatic Mapping: Model binding automatically maps data from HTTP
requests to action method parameters. Each parameter’s data source might
include the request body, query string, headers, cookies, or route data.
Support for Complex Types: It supports binding simple types like integers
and strings, as well as complex types like custom classes and collections. For
complex types, model binding looks for matching keys in the incoming data
and assigns values to properties or fields in the object.
Customization and Extensibility: Developers can customize the binding
process by using attributes like [FromBody], [FromQuery], [FromRoute],
[FromHeader], [FromForm], and [FromServices]. These attributes specify the
source of the data for a particular action method parameter.
7
Validation: Model binding works closely with model validation. Once data is
bound to parameters, it can be validated using data annotations and custom
validation rules. Invalid model states can be handled within the action
methods.
Built-in Model Binders in ASP.NET Core MVC:
In ASP.NET Core MVC, model binding involves a sequence of providers, each designed to
handle different types of input data. The built-in model binders are integrated into the
framework and configured to work in a specific sequence that is generally suitable for most
use cases.
The default order of these providers is designed to handle most typical scenarios effectively.
This order determines how the framework attempts to bind incoming data to action method
parameters or properties of complex types. To see the same, please add the following code
to the Program.cs class file. It will display the default order of all the registered Model
Binders in the ASP.NET Core Application. Once you run the application, check the Console
Window.
builder.Services.AddControllersWithViews(options =>
{
foreach (var provider in options.ModelBinderProviders)
{
Console.WriteLine(provider.GetType().ToString());
}
});
If you want to control the “priority” or decide which source to use, then you need to specify
the appropriate attribute explicitly on each action method parameter. For example, if you
have an action method that could receive a userId either from the query string or as a JSON
payload, then you can decorate the parameter using either [FromQuery] or [FromBody] as
follows:
public IActionResult GetUser([FromQuery] int userId)
{
// Uses userId from the query string
}
// OR
public IActionResult UpdateUser([FromBody] User user)
{
// User object is expected as a JSON body which includes userId
}
8
such as ID, Name, Email, Password, and Mobile. So, first, create a class file
named User.cs within the Models folder and then copy and paste the following code into it.
namespace ModelBindingDemo.Models
}
Create an Action Method:
Next, in our controller, we need to define an action method to handle the form submission.
Use the FromForm attribute to specify that the action method should accept data from the
form. So, create a UsersController controller within the Controllers folder and copy and
paste the following code. The Create action method, decorated with the HttpPost attribute,
will handle the form POST request.
using Microsoft.AspNetCore.Mvc;
using ModelBindingDemo.Models;
namespace ModelBindingDemo.Controllers
[HttpGet("users/create")]
{
10
return View();
[HttpPost("users/create")]
if (!ModelState.IsValid)
// Return the form view with the posted data and validation messages
return View(user);
return RedirectToAction("SuccessAction");
}
In the above example, when the form is submitted, the data from the form body is
automatically bound to the User object of the Create action method (Create method, which
is decorated with HttpPost Attribute) using the FromForm attribute. The FromForm attribute
maps the posted form data to the User object.
Create a Form in a View:
11
Next, we need to create a view, and inside the view, we need to create a form using which
the user submits his data. The name attributes of the form inputs must match the properties
of the model. So, create the Create.cshtml view within the Views/Users directory. Once
you create the Create.cshtml view, copy and paste the following code.
@model User
@{
ViewData["Title"] = "Create";
<div class="mb-3">
</div>
<div class="mb-3">
</div>
<div class="mb-3">
</div>
<div class="mb-3">
</div>
</form>
Now, run the application and create a user by visiting the /users/create URL. You will see
the respective view to create the user, as expected. Provide the details in the view and click
the Submit button, as shown in the image below.
namespace ModelBindingDemo.Controllers
13
[HttpGet("users/create")]
return View();
[HttpPost("users/create")]
return RedirectToAction("SuccessAction");
}
14
namespace ModelBindingDemo.Controllers
[HttpGet("users/create")]
return View();
[HttpPost("users/create")]
return RedirectToAction("SuccessAction");
}
Now, with the above changes in place, run the application, and it should work as expected.
When Should We Use FromForm Attribute in ASP.NET Core MVC?
In ASP.NET Core MVC, the FromForm attribute indicates that a parameter should be bound
from the form data in the request body. The following are some scenarios where we can
use the FromForm attribute:
Traditional HTML Forms:
One of the most common use cases for FromForm is when dealing with traditional HTML
forms.
<form method="post" action="/submit">
</form>
When the form is submitted, the data is sent in the request body. The FromForm attribute
binds this data to the action method’s parameters.
File Uploads:
When handling file uploads with a form using enctype=”multipart/form-data”, the
FromForm attribute is used to bind uploaded files and any additional form fields.
<form method="post" action="/upload" enctype="multipart/form-data">
</form>
In the corresponding action method, you might have:
[HttpPost("/upload")]
}
When Not to Use FromForm Attribute?
If we are building an API, i.e., Restful Services, that will be consumed by other clients, such
as mobile apps or frontend frameworks like React, Angular, or Vue.js, where we generally
work with JSON payloads rather than form data, we should use FromBody instead of
FromForm Attribute.
In ASP.NET Core MVC, model binding allows you to map data from various sources (such
as query strings, form fields, and JSON bodies) to the parameters of your action methods.
The FromQuery attribute specifically tells the ASP.NET Core MVC framework to bind data
from the query string of the request to parameters in your action method. The query string is
part of a URL after the “?” Character and is composed of key-value pairs separated
by & characters.
When you decorate an action method parameter with the [FromQuery] attribute, ASP.NET
Core MVC automatically binds data from the query string to that parameter based on its
name. If you go to the definition of FromQueryAttribute, you will see the following signature.
}
Next, create another class file named User.cs within the Models folder and copy and paste
the following code. This is a simple model that represents a user.
namespace ModelBindingDemo.Models
}
Next, create an Empty MVC Controller named UsersController within the Controllers and
copy and paste the following code. In the following example, we are binding query
parameters to a model, but you can also bind them directly to action method primitive
parameters. Please ensure the null values are handled, or default values are provided if
some query parameters are optional. In the example below, int? Age means age can be
null.
using Microsoft.AspNetCore.Mvc;
using ModelBindingDemo.Models;
namespace ModelBindingDemo.Controllers
public UsersController()
19
};
[HttpGet("users/search")]
if (criteria != null)
else if(!string.IsNullOrEmpty(criteria.Name))
{
20
return Ok(FilteredUsers);
}
Understanding the Search Action Method:
The method is decorated with the [HttpGet] attribute, indicating that it will respond to HTTP
GET requests only. The route template “users/search” specifies the endpoint URL for
accessing this method. The method takes a parameter criteria of type UserSearchCriteria,
decorated with [FromQuery] attribute. This means that the method expects the values for
criteria to be provided in the query string of the request URL.
The Logic of the Search Method:
The method initializes an empty list called FilteredUsers, which will store the
users that match the search criteria. Then, it checks if the criteria object is not
null. If the criteria is not null, it checks whether the Name property or the Age
property (or both) of the criteria object has been provided.
Depending on the provided criteria, it filters the _users list accordingly. If both
Name and Age are provided, it filters users whose names start with the
provided name (case-insensitive) or whose age is greater than the provided
age. If only the Name is provided, it filters users whose names start with the
provided name (case-insensitive). If only Age is provided, it filters users
whose age is greater than the provided age.
Finally, it returns an HTTP response with status code 200 (OK) and the
filtered list of users.
Return Value:
The method returns an IActionResult, which is a common base type for all
action results in ASP.NET Core MVC. If the filtering logic executes
successfully, it returns an HTTP 200 OK response and the filtered list of users
(FilteredUsers).
21
using ModelBindingDemo.Models;
namespace ModelBindingDemo.Controllers
public UsersController()
};
22
[HttpGet("users/search")]
else if (!string.IsNullOrEmpty(Name))
return Ok(FilteredUsers);
}
23
If the parameter names and query string keys match, you don’t strictly need the FromQuery
attribute for binding to work. For a better understanding, please modify
the UsersController as follows:
using Microsoft.AspNetCore.Mvc;
using ModelBindingDemo.Models;
namespace ModelBindingDemo.Controllers
public UsersController()
};
[HttpGet("users/search")]
else if (!string.IsNullOrEmpty(Name))
return Ok(FilteredUsers);
}
Note: Using the attribute explicitly clarifies your intention and makes the code more
maintainable and understandable.
How Does Model Binding Work with FromQuery Attribute in ASP.NET
Core MVC?
When a request is made to an MVC action that has parameters decorated with the
FromQuery attribute, the model binding system kicks in with the following steps:
Identify the Source: The model binder identifies the query string as the
source of the binding data due to the FromQuery attribute.
Match Parameters: The model binder matches keys in the query string with
the names of the action method parameters. If a Name property is specified
in the FromQuery attribute, it uses this custom name to match the keys in the
query string.
25
Conversion and Binding: Once matching keys are found, the model binder
converts the query string values from strings to the types of the respective
action method parameters. ASP.NET Core has built-in type converters that
handle most primitive types and can bind to complex types by matching the
query string keys to property names of the complex types.
Handling Errors: If there are binding errors, such as a mismatch in type
conversion, ASP.NET Core handles these, typically assigning default values
or nulls depending on the scenario and configuration. Errors can also be
handled or logged by checking the model state (ModelState.IsValid).
Using Name Property of FromQuery Attribute:
The Name property of the FromQueryAttribute is used when the name of the parameter in
the action method does not match the key in the query string. It provides a way to map the
query string key directly to a method parameter that might have a different name.
Suppose you have a query string named EmpAge, but in your method, you want to bind it
to a parameter named Age. Again, you want to map the EmpName query string with
the Name parameter of our action method. In this case, we need to use the Name property
to map these correctly. For a better understanding, please modify the UsersController as
follows:
using Microsoft.AspNetCore.Mvc;
using ModelBindingDemo.Models;
namespace ModelBindingDemo.Controllers
public UsersController()
};
[HttpGet("users/search")]
else if (!string.IsNullOrEmpty(Name))
return Ok(FilteredUsers);
}
27
}
Now, while calling the above action method, you can provide search criteria through the
query string as follows:
/users/search?EmpName=pr
/users/search?EmpAge=30
/users/search?EmpName=Pr&EmpAge=30
When Should We Use FromQuery Attribute in ASP.NET Core?
The FromQuery Attribute in ASP.NET Core can be useful for handling HTTP GET requests
where you want to pass information through the URL. The following are a few scenarios
where you might use the FromQuery attribute:
Filtering Data: It’s common to use query parameters to filter data in APIs. For
instance, if you have an endpoint that retrieves a list of products, you can use
FromQuery to allow clients to filter products based on criteria like category,
price range, etc.
Pagination: For endpoints that return a collection of items, query parameters
can be used to implement pagination. Using FromQuery, you can specify
parameters such as page and pageSize to control how many items to return
and which data page to retrieve.
Search Parameters: If you have a search endpoint, you can use FromQuery
to capture search criteria sent by the client, such as search keywords, date
ranges, or other filters.
Sorting: When returning a list of items, you might want to allow the user to
specify sorting criteria through the query string, such as sorting by date,
name, or price.
When Not to Use:
Sensitive Data: Never pass sensitive data in the query string, like passwords
or security tokens. They can be logged in server logs, browser history, or
referrer headers, posing a security risk.
Very Large Data Sets: Browsers and servers have URL length limits. If the
query string becomes too long, it’s better to use a different method, like
sending a JSON payload in the POST request body.
In ASP.NET Core MVC, the BindingSource and the Name property of the FromRoute
attribute are essential for controlling how data is parsed and bound to action method
parameters.
Ad
1/2
00:19
}
Next, create a new Empty MVC controller named UsersController within the Controllers
folder and then copy and paste the following code.
using Microsoft.AspNetCore.Mvc;
using ModelBindingDemo.Models;
namespace ModelBindingDemo.Controllers
public UsersController()
};
[HttpGet]
[Route("users/{Id}/getdetails")]
// Here, you can use the 'id' to fetch user details from a database or other data sources.
if (user == null)
return NotFound();
return Ok(user);
}
When you want to fetch a user with ID 2, you will access the URL: users/2/getdetails. The
2 in the URL would be bound to the Id parameter in the GetUserById action, which is
possible because of the FromRoute attribute.
Using Name Property of FromRoute Attribute:
The Name property of the FromRoute Attribute is used when the name of the route
parameter defined in the route template is different from the name of the action method
parameter to which it needs to be bound. So, it provides a way to map the route data
directly to a method parameter with a different name.
Suppose you have a Route Token Named ID, but in your method, you want to bind it to a
parameter named UserId. In this case, you need to use the Name property to map these
correctly. For a better understanding, please modify the UsersController as follows:
using Microsoft.AspNetCore.Mvc;
using ModelBindingDemo.Models;
namespace ModelBindingDemo.Controllers
public UsersController()
};
[HttpGet]
[Route("users/{Id}/getdetails")]
// Here, you can use the 'id' to fetch user details from a database or other data sources.
if (user == null)
return NotFound();
return Ok(user);
}
32
}
How Does Model Binding Work with FromRoute Attribute in ASP.NET
Core?
The FromRoute attribute is used to bind action method parameters to parameters from the
route data in an HTTP request. The process of model binding with FromRoute involves
several steps:
Route Template Matching: When a request is received, ASP.NET Core
matches the URL to the route templates defined in your controllers or
program configuration. Each segment of the URL defined as a parameter
within curly braces ({ }) is parsed as route data.
Identify the Source: The model binder identifies the route parameters as the
data source because the FromRoute attribute applied to the action method
parameters makes this possible.
Match and Bind Parameters: The model binder matches the names of the
parameters in the route data with the names of the parameters in your action
method. If they match, it binds the values from the route data to those
parameters. If a Name property is specified in the FromRoute attribute, it
overrides the default matching to use this specified name instead.
Conversion: The values extracted from the route are converted from strings
(as all route data is captured as strings) to the types of the respective
parameters in the action method. ASP.NET Core uses built-in type converters
for most primitive types.
Validation: Once the values are bound, any validation attributes applied to
the action method parameters are processed. If the validation fails, the errors
are added to ModelState.
When Should We Use FromRoute Attribute in ASP.NET Core MVC?
The FromRoute attribute is useful when you want to retrieve data directly from the request
URL. The following are some of the scenarios where you can use FromRoute Attribute:
RESTful APIs: In RESTful API design, resource identifiers are often part of
the URL. For example, if you have an API endpoint that fetches a specific
user by their ID, the user ID might be part of the route. Using FromRoute lets
you directly bind this user ID from the route to a method parameter in your
controller.
Avoiding Query Strings for Mandatory Data: If certain data is essential for
the operation of a specific endpoint (like an entity ID in CRUD operations),
embedding it in the URL (and using FromRoute to bind it) can be more
appropriate than relying on query strings, which are better suited for optional
parameters.
Hierarchy in Data: When you are dealing with hierarchical resources where
one resource is a subset of another, using route data is more semantically
correct. For example, accessing comments on a blog post might look like
/posts/123/comments/456, where both 123 and 456 can be bound using
FromRoute.
When Not to Use FromRoute?
Query-Specific Data: If the data represents filters, sorts, or other query-
specific data, it might be better suited for the query string (i.e., use
FromQuery).
33
Large or Complex Data: Route data should be kept simple and limited to
identifiers or other small data bits. If you are passing complex objects, those
should likely be sent in the request body (i.e., use FromBody).
Sensitive Data: Don’t send sensitive information in the URL, even if it’s in the
route. URLs can be logged, cached, or shared, which could expose sensitive
information.
namespace ModelBindingDemo.Controllers
[Route("users/data")]
[HttpGet]
if (string.IsNullOrEmpty(apiVersion))
if (apiVersion == "1.0")
else
}
35
}
Request with Headers:
Clients can request the /users/data endpoint and set the X-Api-Version header to 1.0 or
2.0 to fetch version-specific data.
Points to Remember:
The Name property in the FromHeader attribute is used to specify the
header’s name from which the data should be bound. If the parameter name
matches the header name, the Name property isn’t strictly necessary, but it’s
a good practice to include it for clarity.
Always validate incoming header data. Clients can manipulate headers, so
don’t trust them implicitly. In this example, we perform basic validation by
checking for null, empty, and known versions.
When to use FromHeader Attribute in ASP.NET Core MVC?
In ASP.NET Core MVC, the FromHeader attribute binds action method parameters to
specific HTTP header values. Here are scenarios in which you might use the FromHeader
attribute:
Authentication & Authorization:
The Authorization header is often used to pass tokens (like JWTs) or credentials to the
server.
[HttpGet]
}
Content Negotiation:
Clients can use the Accept header to specify the expected media type (e.g.,
application/json, text/xml). The server can then use this header to determine how to format
the response. Similarly, the Content-Type header can inform the server about the data
format in the request body.
Caching Control:
Headers like If-Modified-Since or If-None-Match can be used for conditional requests. The
server can check these headers and decide whether to send a full response or a 304 Not
Modified status.
Localization:
The Accept-Language header can specify the client’s language preferences. Based on this,
the server can return localized content.
[HttpGet]
36
}
Custom Headers for Business Logic:
Sometimes, apps define custom headers for specific business requirements. For instance,
an X-User-Tier header could denote the subscription tier of the user, affecting the kind of
data or features available to them.
Rate Limiting:
If you’re implementing rate limiting, headers like X-RateLimit-Limit and X-RateLimit-
Remaining can be utilized to communicate rate limit details back to the client.
Debugging or Diagnostics:
Some applications use headers for diagnostics or tracing. For example, a header might
contain a trace ID propagated through various microservices to track a request.
When Not to Use:
Regular Payload Data: Headers are not meant for standard request payload
data. Use FromBody, FromQuery, or FromRoute for that.
Sensitive Data: It’s best to avoid sending sensitive data in headers,
especially if unnecessary. While headers are part of the encrypted content in
HTTPS traffic, they can be logged in server logs or exposed in other parts of
the processing pipeline.
So, FromHeader provides a way to access specific HTTP headers and can be vital for
certain scenarios, especially around authentication, content negotiation, and other HTTP-
specific functionalities. However, it’s important to use it judiciously and avoid overloading
headers with unnecessary data.
The FromBodyAttribute specifies that a parameter or property should be bound using the
request body.
Ad
1/2
00:19
}
Create a controller action and use the FromBody attribute to bind the incoming request
body to your model. Create a controller named UsersController and then copy and paste
the following code into it.
using Microsoft.AspNetCore.Mvc;
using ModelBindingDemo.Models;
namespace ModelBindingDemo.Controllers
38
[Route("users/create")]
[HttpPost]
if (!ModelState.IsValid)
return BadRequest(ModelState);
// Handle the user data: save it to a database, perform some operations, etc.
// Often, you might return the created entity or just a success message.
return Ok(user);
}
Making a Request:
Clients can then send a POST request with a JSON body to the /users/create endpoint:
{
"Id": 10,
"Name": "Kumar",
"Age": 30,
"Mobile":"1234567890"
}
39
The FromBody attribute will take this JSON data, attempt to deserialize it into a User object
and provide it to your action method as the user parameter.
Points to Remember:
Ensure you have a suitable JSON serializer configured for your application.
For many applications in ASP.NET Core, the default JSON serializer (like
Newtonsoft.Json in older versions or System.Text.Json in newer versions) is
used.
If deserialization fails (e.g., the provided JSON doesn’t match the User
structure or contains invalid data), the model binding will mark the model as
invalid. This is why you check ModelState.IsValid in your action method. If it’s
false, it means something went wrong with the binding.
You can return a bad request or any other appropriate response when the
model state is invalid. The ModelState will contain error messages about what
went wrong, which can be useful for debugging or informing clients about the
nature of the error.
In ASP.NET Core 3.x and later, you no longer need to specify [FromBody] for
complex types; it’s inferred by default. However, it’s still a good practice to
include it for clarity.
When to Use FromBody Attribute in ASP.NET Core?
In ASP.NET Core, the FromBody attribute binds action method parameters from the request
body. It’s especially useful when dealing with HTTP methods like POST, PUT, and PATCH,
where significant data is sent in the request body. Here’s when we would typically use the
FromBody attribute:
RESTful APIs:
When creating (POST) or updating (PUT, PATCH) resources, you often send the entire
resource or changes in the request body. The FromBody attribute allows the automatic
deserialization of the incoming request body to a .NET object.
[HttpPost]
}
Complex Data Types:
For sending complex objects, arrays, or other structured data. Query strings (with
FromQuery) are better suited for simple values or filtering criteria, while the request body
can accommodate more structured and comprehensive data.
JSON or XML Payloads:
Modern web APIs often communicate using JSON or XML payloads. Clients send this data
in the request body, and the FromBody attribute tells the ASP.NET Core framework to use
input formatters to deserialize this data into the corresponding .NET types.
Avoiding Length Limitations:
URLs have length limits. When you have a large amount of data to send, sending it in the
body is more suitable than as a query string or as part of the URL.
40
using Microsoft.AspNetCore.Mvc.ModelBinding;
using ModelBindingDemo.Models;
41
namespace ModelBindingDemo.Models
//ModelBindingContext: A context that contains operating information for model binding and
validation.
if (string.IsNullOrEmpty(Ids))
return Task.CompletedTask;
bindingContext.Result = ModelBindingResult.Success(values);
return Task.CompletedTask;
}
42
}
Create a Model Binder Provider:
A provider allows more granular control, determining when your binder should be used
based on the context. So, create a class file
named CommaSeparatedModelBinderProvider.cs and copy and paste the following
code. In this case, we’re checking if the model type is a list of integers.
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace ModelBindingDemo.Models
if (context.Metadata.ModelType == typeof(List<int>))
return null;
}
Registering the Custom Model Binder:
Register your custom model binder in the Startup.cs or Program.cs depending on your
ASP.NET Core version. As I am developing the application using the .NET 6 version, I will
register the custom model binder within the Main method of the Program class using the
code below.
builder.Services.AddControllersWithViews(options =>
43
// Add the provider at the top of the list to ensure it runs before default providers
});
Using Custom Binder:
Now that everything’s set up, you can use your custom binder in your action methods. So,
modify the HomeController as follows to use Custom Model Binder.
using Microsoft.AspNetCore.Mvc;
using ModelBindingDemo.Models;
namespace ModelBindingDemo.Controllers
//[HttpGet("home/getdetails")]
//{
// return Ok(Ids);
//}
[HttpGet("home/getdetails")]
// Your logic...
return Ok(Ids);
44
}
When you access this action with a URL like /home/getdetails?Ids=1,2,3. The IDS
parameter will be a list containing the integers 1, 2, and 3.
So, custom model binding in ASP.NET Core provides flexibility in dealing with unique
request data scenarios. It allows you to seamlessly integrate custom parsing logic into the
framework’s model-binding process.
Custom Model Binding RealTime Example in ASP.NET Core MVC
Consider a real-time scenario for custom model binding in ASP.NET Core MVC: converting
a date range string in the format “startDate-endDate” into a custom DateRange object. This
is useful in reporting scenarios where users might provide a date range as part of their
query parameters.
Step 1: Define the DateRange Class
namespace ModelBindingDemo.Models
}
Step 2: Implement the Custom Model Binder
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.Globalization;
namespace ModelBindingDemo.Models
if (string.IsNullOrEmpty(DateRangeQueryString))
return Task.CompletedTask;
if (dateValues.Length != 2)
return Task.CompletedTask;
bindingContext.Result = ModelBindingResult.Success(dateRange);
return Task.CompletedTask;
else
return Task.CompletedTask;
}
Step3: Create a Model Binder Provider:
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace ModelBindingDemo.Models
if (context.Metadata.ModelType == typeof(DateRange))
return null;
}
Step 3: Register the Custom Model Binder
builder.Services.AddControllersWithViews(options =>
// Add the provider at the top of the list to ensure it runs before default providers
});
Step 4: Use the Custom Binder in an Action
using Microsoft.AspNetCore.Mvc;
using ModelBindingDemo.Models;
namespace ModelBindingDemo.Controllers
//[HttpGet("home/getdata")]
//{
//}
48
[HttpGet("home/getdata")]
}
When you access this action with a URL like /home/getdata?range=01/01/2023-
12/31/2023, the range parameter will be populated with the correct start and end dates. If
the format is incorrect, a model state error is added.
This real-time example showcases how custom model binding can deal with specific
formats or data types that the default model binders might not handle.
Complex Object Creation using Custom Model Binder in ASP.NET Core
MVC
Creating complex objects using custom model binding in ASP.NET Core MVC can be
beneficial in scenarios where the default model binders aren’t sufficient to generate an
object based on a combination of different parts of an HTTP request (e.g., headers, query
parameters, route values, and body data).
Suppose we have an object, ComplexUser, that we want to create based on data from the
request header, route, and query string.
1. Define the ComplexUser Class:
namespace ModelBindingDemo.Models
}
Where:
Username comes from a header named “X-Username”.
Age is derived from the query string.
Country is part of the route.
ReferenceId is also from the query string.
2. Implement the Custom Model Binder:
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace ModelBindingDemo.Models
Username = headers["X-Username"].ToString(),
Country = routeData["country"].ToString(),
ReferenceId = query["refId"].ToString()
};
bindingContext.Result = ModelBindingResult.Success(user);
return Task.CompletedTask;
50
}
Step3: Create a Model Binder Provider:
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace ModelBindingDemo.Models
if (context.Metadata.ModelType == typeof(ComplexUser))
return null;
}
3. Register the Custom Model Binder:
In the Startup.cs or Program.cs (depending on your ASP.NET Core version):
builder.Services.AddControllersWithViews(options =>
// Add the provider at the top of the list to ensure it runs before default providers
51
});
4. Use the Custom Binder in an Action:
using Microsoft.AspNetCore.Mvc;
using ModelBindingDemo.Models;
namespace ModelBindingDemo.Controllers
[HttpGet("data/{country}")]
public IActionResult
GetComplexUserData([ModelBinder(typeof(ComplexUserModelBinder))] ComplexUser
user)
// Your logic...
return Ok(user);
}
With this setup, if you send a request to /data/India?age=25&refId=12345 with the header
X-Username: Pranaya, the ComplexUser parameter in the action will be populated based
on the custom binding logic.
Single Property Binding in ASP.NET Core MVC
We can also achieve the previous example using Single Property Binding in ASP.NET Core
MVC. In ASP.NET Core MVC, single property binding refers to the capability to bind a
52
specific property of an object differently from other properties. This can be achieved by
using model binding attributes on individual properties.
We can use the built-in attributes like [FromRoute], [FromQuery], [FromBody], [FromForm],
and [FromHeader] on individual properties to specify where the property value should come
from. So, modify the ComplexUser class as follows.
using Microsoft.AspNetCore.Mvc;
namespace ModelBindingDemo.Models
[FromHeader(Name = "X-Username")]
[FromQuery(Name = "age")]
[FromRoute(Name = "country")]
[FromQuery(Name = "refid")]
}
With the above changes in place, modify the Home Controller as follows:
using Microsoft.AspNetCore.Mvc;
using ModelBindingDemo.Models;
namespace ModelBindingDemo.Controllers
{
53
[HttpGet("data/{country}")]
// Your logic...
return Ok(user);
[DisplayFormat] attribute can specify how date fields are displayed in your UI, such as
showing only the date part of a DateTime object.
[DisplayFormat(DataFormatString = “{0:F2}”)]: Defines the display format
for a property, useful for formatting dates, numbers, etc.
Client-Side Validation Enhancement:
While data annotations primarily enforce server-side validation, they can also automatically
integrate with client-side validation when using ASP.NET Core MVC. Data Annotations
Attributes like [Required], [StringLength(maxLength)], [Range(minimum, maximum)],
[Compare(otherPropertyName)], [RegularExpression(pattern)], [EmailAddress], [Phone],
[Url], [CreditCard] translate directly to HTML5 data val attributes that work with jQuery
Validation plugins.
Database Schema Configuration:
The Data Annotations Attributes can also specify how the model properties map to
database columns when using the Entity Framework Core Code First Approach. The
following are a few of such Attributes.
[Key]: Used to denote a property as the primary key in a model.
[ForeignKey(“RelatedEntity”)]: Used in Entity Framework to indicate that a
property is a foreign key.
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]: Specifies how
the database generates values for a property.
[Column(“ColumnNameInDb”)]: Specifies a column name in the database
that is different from the property name in the model.
[Index]: Attributes like [Index] can be used to define indexing behavior on
specific columns to improve database performance.
Model Binding Attributes:
Data Annotation Attributes in ASP.NET Core can also manage how model binding should
treat certain properties, such as excluding a property from model binding and specifying that
a Property should be present to perform Model Binding. The following two Attributes fall into
this category:
[BindNever]: It indicates that a model property should not be bound by
model binding. This can be useful for properties that should not be updated
directly from the data in the request, such as IDs, timestamps, or user-
sensitive data that needs to be protected or generated by the system.
[BindRequired]: It indicates that a model property should be present in the
incoming data for the model binding to succeed. If the field is missing during
model binding, a model state error is added.
Limitations of Data Annotations in ASP.NET Core:
There are scenarios where data annotations might not be the best choice:
Complex Validation Rules: For validation logic that is too complex to be
expressed in data annotations (e.g., cross-field validation, dependency on
external systems), Fluent Validation or custom validation methods should be
considered.
Separation of Concerns: If you prefer a strict separation of concerns where
your domain model does not contain any hints about presentation or
persistence, you might opt for a separate configuration class or use other
validation frameworks.
56
We want to make the Name, Email, and Department fields required. If the required values
are not provided and the form is submitted, we want to display the required validation
errors, as shown in the image below.
60
If we enter an Invalid Email, it should display an Invalid Email Format validation error, as
shown below.
Let us proceed and see how we can implement this using Data Annotation Attributes in
ASP.NET Core MVC Application.
Creating the Model with Data Annotation Attributes:
Create a class file named Department.cs, then copy and paste the following code. As you
can see, this will be an Enum, and these Enum constants will be the data source for our
department field.
namespace DataAnnotationsDemo.Models
None,
HR,
Payroll,
IT
61
}
Next, create another class file named Employee.cs and copy and paste the following code.
As you can see in the code below, we have marked the Name, Email, and Department
properties with the Required Attribute, which makes these properties required while
performing the model validation. The Required Data Annotation Attribute belongs to
the System.ComponentModel.DataAnnotations namespace.
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
[Required]
[Required]
[Required]
}
Creating Employee Controller:
Create a new Empty – MVC Controller with the name EmployeeController and then copy
and paste the following code into it.
using DataAnnotationsDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace DataAnnotationsDemo.Controllers
{
62
return View();
[HttpPost]
if (ModelState.IsValid)
return RedirectToAction("Successful");
return View();
}
63
}
Explanation:
In our example, the Create() action method, decorated with HTTP Post
Attribute, will be executed when the form is submitted.
The model for the Create Employee Form is the Employee class. When the
form is submitted, model binding maps the posted form values to the
respective properties of the Employee class.
With the Required attribute on the Name, Email, and Department properties
of the Employee class, if a value for these properties is not present, the
model validation fails.
We need to use ModelState.IsValid property to check if validation has failed
or succeeded. If validation has failed, we return the same view so the user
can provide the required data and resubmit the form. If there is no validation
error, we often save the data into the database and then redirect to a different
view as per the business requirement.
What is ModelState.IsValid in ASP.NET Core MVC?
ModelState.IsValid in ASP.NET Core MVC is a property that indicates whether the data
posted to a server is valid according to the data annotations and validation rules set on the
server-side models. This property in controller actions ensures that the data sent by the
client is valid. Here is how ModelState.IsValid works:
Data Annotation Validation: ASP.NET Core models can be decorated with
data annotations that define validation rules. For example, you can use
annotations like [Required], [StringLength], [Range], etc to provide the
Validation Rules for a Model property.
Automatic Validation Check: When a request is made to an ASP.NET Core
controller action, the framework automatically binds incoming request data to
action parameters and checks these against the model’s validation rules. This
process populates the ModelState.
ModelState Dictionary: The ModelState is a property of the controller base
class, represented as a dictionary containing the model’s state and the
validation errors. It includes information about each field (like whether there
were any errors during conversion, formatting, or validation based on the
rules).
Checking ModelState.IsValid: In the controller actions, we need to check
the ModelState.IsValid property to determine if the data passed to the action
meets all the validation criteria defined by the model. If ModelState.IsValid
returns false, which means that at least one of the validation rules was not
satisfied.
Handling Validation Errors: If ModelState.IsValid is false. We typically
need to return to the same view in which the user entered data and display
the validation error messages stored in the ModelState. This helps the user
correct their input data and resubmit the form.
Displaying Model Validation Error in ASP.NET Core MVC Razor Views:
We need to use asp-validation-for and asp-validation-summary tag helpers to display
model validation errors in Views. The asp-validation-for tag helper displays a validation
message for a single property of our model class. On the other hand, the asp-validation-
summary tag helper summarizes all validation errors that occurred on a model.
64
For example, to display the validation error associated with the Name property of the
Employee class, we need to use asp-validation-for tag helper on a <span> element, as
shown below.
<div class="form-group row">
<div class="col-sm-10">
<span asp-validation-for="Name"></span>
</div>
</div>
On the other hand, to display a summary of all validation errors, we need to use the asp-
validation-summary tag helper on a <div> element, as shown below.
<div asp-validation-summary="All" class="text-danger"></div>
The value for the asp-validation-summary tag helper can be any of the following.
All: When you set the asp-validation-summary attribute to All, it displays all
validation messages. This includes messages that are associated with model
properties as well as messages that are added to the ModelState without a
key (often used for summary error messages that aren’t tied to a specific
field).
ModelOnly: Setting the asp-validation-summary attribute to ModelOnly will
display only those validation messages that are directly associated with the
model properties. This setting excludes any error messages that are added to
the ModelState without a specific key.
None: When the asp-validation-summary is set to None, no validation
summary is displayed. This option effectively disables the validation summary
display.
Let us use All. By default, it will show the error message in black color. If you want to
display the error message in red, use the bootstrap class=”text-danger” as follows.
<div asp-validation-summary="All" class="text-danger"></div>
<div class="col-sm-10">
</div>
</div>
Creating ASP.NET Core Razor Views to Display Error Messages:
Next, create a view for the Create action method of Employee Controller. Once you add
the Create.cshtml view file, please copy and paste the following code.
@model DataAnnotationsDemo.Models.Employee
@{
ViewData["Title"] = "Create";
<h1>Create Employee</h1>
<div class="row">
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
asp-items="Html.GetEnumSelectList<Department>()">
</select>
</div>
</div>
<div class="col-sm-10">
</div>
</div>
</form>
</div>
Now, run the application, navigate to /Employee/Create URL, and submit the form without
filling in any data, and you will see the following error messages.
67
namespace DataAnnotationsDemo.Models
{
68
}
With the above model changes, run the application, navigate to
the /Employee/Create URL, and submit the form without filling in any data. You will see the
following custom error messages as expected.
Along with the Required Validation, we want to apply the Invalid Format error message for
the Email ID field. To achieve this, we must use either the EmailAddress Attribute
or RegularExpression attribute along with the Required Attribute. So, modify the
Employee class as follows.
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
[RegularExpression(@"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",
}
With the above changes in place, run the application, navigate to
the /Employee/Create URL, and submit the form by entering an invalid email address. You
should get the appropriate error message, as shown in the below image.
71
Currently, Validation is performed on the server side, not on the client side. The Validation
attributes in ASP.NET Core MVC Framework provide both client-side and server-side
validation. Let us proceed to understand how to enable client-side validation in ASP.NET
Core MVC Application.
Enabling Client-Side Validation in ASP.NET Core MVC:
Client-Side Validation in ASP.NET Core MVC Application is a feature that allows us to
perform validation on the client browser without making a round trip to the server. This
provides a more responsive experience for the user. ASP.NET Core MVC uses jQuery
Validation and jQuery Unobtrusive Validation for client-side validation. Here’s how you can
enable client-side validation in an ASP.NET Core MVC application:
You must include jQuery, jQuery Validation, and jQuery Unobtrusive Validation in your
project. These can be added via CDNs or local files. For example, include them via CDNs in
your _Layout.cshtml:
<!-- jQuery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.5/
jquery.validate.min.js"></script>
72
!-- jQuery Unobtrusive Validation (to bridge ASP.NET Core MVC validation with jQuery
Validate) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.13/
jquery.validate.unobtrusive.min.js">
Please add the following files in your _Layout.cshtml to enable client-side validation using
local files. The order is also important, and please include them in the following order.
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></
script>
The order in which the script files are referenced is also important. This is
because jquery.validate is dependent on jquery, and jquery.validate.unobtrusive is
dependent on jquery.validate. So, they should be referenced in the above order.
Otherwise, client-side validation will not work as expected. In short, JavaScript is parsed
“top-down”, so all dependencies must be referenced before the dependent reference.
With these changes in place, the client-side validation should be active in your ASP.NET
Core MVC application. Whenever a user attempts to submit the form, jQuery Validation will
check input values against the data annotations before the form is submitted. If validation
fails, appropriate error messages will be displayed next to the form fields.
ASP.NET Core MVC uses these libraries to enhance and streamline client-side scripting
and validation:
jQuery: It provides core functionality for DOM manipulation and event
handling, making it simpler to write complex client-side scripts that enhance
user experience.
jQuery Validation: Integrated with ASP.NET Core MVC’s server-side
validation, this plugin ensures that validation rules are enforced on the client
side before the form data is sent to the server. This reduces the server load
and provides immediate feedback to users.
jQuery Unobtrusive Validation: Works with the jQuery Validation plugin and
ASP.NET Core MVC’s validation annotations. It uses data-* attributes
generated from model annotations to control the behavior of client-side
validation without additional scripting. This helps keep your JavaScript code
minimal and separated from HTML content.
Best Practices to Use Validation Related JavaScript files:
We should not include the following three JavaScript files in our Layout File to enable
Client-Side Validation using Data Annotation.
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
73
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></
script>
You need to remember that we are not going to create forms or perform client-side
validations on every web page of our application. If we include the above JavaScript files
inside our Layout files, they will be downloaded on every page where we use the layout file,
even if we are not performing client-side validations.
Creating a Partial View:
So, to overcome this problem, let us create a partial view with the
name _ValidationScriptsPartial.cshtml within our application’s Views/Shared folder and
then copy and paste the following code into it. We are not including the jquery.js file
because the jQuery.js file is required in all the pages, and hence, it is better to put that file
inside the layout file.
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></
script>
Next, we must add a render section inside the layout file to include the partial view above.
So, add the following code just before the end of the body tag of the layout file.
@await RenderSectionAsync("Scripts", required: false)
This is where we need to add the validation script. Suppose our Create.cshtml view wants
client-side validation; then, we need to add the following code just before the end of the
view.
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
With these changes in place, run the application, and you will see that the client-side
validation is working as expected.
Using HTML Helper to Provide Validation Message:
Instead of using asp-validation-summary and asp-validation-for tag helpers, we can also
use Html.ValidationSummary and Html.ValidationMessageFor html helper methods to
achieve the same. So, modify the Create.cshtml file as shown below to use HTML helper
methods to display the error message.
@model DataAnnotationsDemo.Models.Employee
@{
74
ViewData["Title"] = "Create";
<h1>Create Employee</h1>
<div class="row">
<div class="text-danger">
@Html.ValidationSummary()
</div>
<div class="col-sm-10">
<span class="text-danger">
</span>
</div>
</div>
<div class="col-sm-10">
<span class="text-danger">
</span>
</div>
</div>
<div class="col-sm-10">
asp-items="Html.GetEnumSelectList<Department>()">
</select>
<span class="text-danger">
</span>
</div>
</div>
<div class="col-sm-10">
</div>
</div>
</form>
76
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
Which Data Annotation Attributes support Client Side Validations in
ASP.NET Core MVC?
All Data Annotation Attributes are not supported by client-side validation. The following are
the data annotation attributes that support client-side validation in ASP.NET Core MVC:
[Required]
[StringLength(maxLength)]
[Range(minimum, maximum)]
[Compare(otherPropertyName)]
[RegularExpression(pattern)]
[EmailAddress]
[Phone]
[Url]
[CreditCard]
Best Practices to Implement Validation in a Website:
Combine with Client-side Validation: Use server-side and client-side
validation for better security and user experience.
Consistent Validation Across Client and Server: Ensure that client-side
and server-side validations follow the same rules for consistency and
redundancy.
User Feedback: Provide clear, helpful error messages for the end users
when their inputs fail server-side validation.
namespace DataAnnotationsDemo.Models
[Required]
[Required]
}
Using the Model Inside a Controller:
Let us use the Employee model inside a Controller. So, modify the Employee Controller as
follows. The GET version of the Create action method will render the view where it will ask
the user to submit the form with First and Last Name values. Once the form is submitted, it
will submit the form data to the Post version of the Create action method. Here, we are
using ModelState.IsValid property to check whether the Model state is valid or not. If valid,
we process the request and navigate to the Successful page, and if not, then we stay in the
same view by displaying the Model Validation error messages.
using DataAnnotationsDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace DataAnnotationsDemo.Controllers
return View();
[HttpPost]
if (ModelState.IsValid)
return RedirectToAction("Successful");
return View();
}
80
@{
ViewData["Title"] = "Create";
<h1>Create Employee</h1>
<div class="row">
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
81
<div class="col-sm-10">
</div>
</div>
</form>
</div>
The Required attribute raises a validation error if the property value is null or empty. When
we submit the page without providing an employee’s first and last name, we will get the
error message as shown in the image below.
With the Required Data Annotation attributes in place, if someone tries to submit the page
without providing the FirstName and LastName values, it will give us the default error, as
shown in the above image. But suppose you want to provide some user-defined error
message when validation fails. In that case, you can use the other overloaded version of
the Required attribute, which accepts ErrorMessage as an input parameter. For a better
understanding, please modify the Employee class as follows:
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
{
82
}
With the above changes, if someone tries to submit the page without providing the
FirstName and LastName values, it will give the user-defined error message, as shown in
the image below.
namespace DataAnnotationsDemo.Models
{
83
[StringLength(30)]
}
With the above changes in place, you cannot enter more than 30 characters in the Last
Name text box, as shown in the image below.
The [StringLength] attribute verifies that a string is of a certain length but does not enforce
that the property is REQUIRED. If you want to enforce the property as required, use
the [Required] attribute and the [StringLength] attribute. That means it is also possible to
apply multiple validation attributes on a single property.
The MinimumLength is an optional named parameter used to specify a string’s minimum
length. We can also specify the user-defined error message. Here, we specify the minimum
length as 4 and the ErrorMessage as the Last name should be between 4 and 30
characters.
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
}
In the above example, we have decorated the LastName property with
the StringLength attribute and then specified the Minimum and Maximum length of the
model properties. We also used the [Required] attribute. So, at this point, the LastName
property is required and should be between 4 and 30 characters. Now run the application
and check everything is working as expected, as shown below.
namespace DataAnnotationsDemo.Models
{
85
[RegularExpression(@"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",
}
Next, modify the Create.cshtml view as follows.
@model DataAnnotationsDemo.Models.Employee
@{
ViewData["Title"] = "Create";
<h1>Create Employee</h1>
<div class="row">
<div class="col-sm-10">
</div>
</div>
86
<div class="col-sm-10">
</div>
</div>
</form>
</div>
In the above example, we are applying both Required and RegularExpression attributes to
the EmailID model property, which ensures that the Email ID field is a required field and will
validate whether the field value is a proper email ID format or not, as shown below. When a
user tries to enter an invalid Email ID and submit the page, he will get the validation error
message, as shown below.
namespace DataAnnotationsDemo.Models
{
87
[RegularExpression(@"^(([A-za-z]+[\s]{1}[A-za-z]+)|([A-Za-z]+))$",
ErrorMessage = "UserName can contain the first and last names with a single space. " +
"The last name is optional. Only upper and lower case alphabets are allowed")]
[RegularExpression(@"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",
}
Notice that we are passing a regular expression string to the Attribute constructor. The
Regular Expression Attribute is great for pattern matching and ensures that the value for the
UserName property is in the format we want. Also, we use a verbatim literal (@ symbol)
string, as we don’t want escape sequences to be processed. Next, modify
the Create.cshtml file as follows.
@model DataAnnotationsDemo.Models.Employee
@{
ViewData["Title"] = "Create";
<h1>Create Employee</h1>
<div class="row">
<div class="col-sm-10">
88
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
</form>
</div>
Run the application and see the UserName field is working as expected.
89
Understanding and writing regular expressions is beyond the scope of this article. If you are
interested in learning to write regular expressions, here is a link from MSDN
http://msdn.microsoft.com/en-us/library/az24scfc.aspx
Range Data Annotation Attribute in ASP.NET Core MVC:
Suppose we have the Age field in the Employee model. If we don’t provide any validations,
we can enter any value, such as 5000 as the age and click on the Submit button, and then
the data will also be saved.
As we know, an employee who is 5000 years old is not possible. So, let’s validate the Age
field and force users to enter a value between 18 and 60. We can achieve this easily using
the RangeAttribute in an ASP.NET Core MVC Application. The Range attributes specify a
numerical number’s minimum and maximum constraints, as shown below.
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
}
90
In the above example, we set the employee’s age to be between 18 and 60 years to pass
the validation. Here, the first parameter of the attribute is the minimum value, the second
parameter is the maximum value, and the third parameter is the error message we want to
show when the validation fails. Next, modify the Create.cshtml view as follows:
@model DataAnnotationsDemo.Models.Employee
@{
ViewData["Title"] = "Create";
<h1>Create Employee</h1>
<div class="row">
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
</form>
</div>
91
When a user tries to enter the age which is not between 18 and 60 and clicks on the submit
button, then he will get the validation error message as shown below:
At this point, we should not be able to enter any values outside the range of 18 and 60 for
the Age field.
Note: The Range attribute in ASP.NET Core MVC does not support the validation of the
DateTime fields.
MinLength and MaxLength Attribute in ASP.NET Core MVC:
These two attributes specify the Minimum and Maximum Length of a property. For example,
suppose we want to restrict the Employee Address as 5 as the minimum length and 25 as
the maximum length. In that case, we can decorate the address property with the
MinLength and Maxlength attributes, as shown below.
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
}
Next, modify the Create.cshtml file as follows.
@model DataAnnotationsDemo.Models.Employee
@{
ViewData["Title"] = "Create";
92
<h1>Create Employee</h1>
<div class="row">
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
</form>
</div>
Submitting the page by entering less than 5 characters will give the error as shown below.
The DataType Attribute in the ASP.NET Core MVC Framework enables us to provide
runtime information about the specific purpose of the properties. For example, a property of
type string can have various scenarios, as it might hold an Email address, URL, Phone
Number, Credit Card, Password, etc. If you go to the definition of DataType Enum, you will
see the following named constants that we can specify using the DataType Attribute.
Let’s see an example of using the DataType attribute in the ASP.NET Core MVC
Application. Please modify the Employee model class as follows. Here, we are using the
DataType attribute to validate the Postal Code, Website URL, Password, Mobile Number,
Email Address, and Salary of an employee.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace DataAnnotationsDemo.Models
{
94
[DataType(DataType.Currency)]
}
Next, modify the Create.cshtml view as follows.
@model DataAnnotationsDemo.Models.Employee
@{
ViewData["Title"] = "Create";
<h1>Create Employee</h1>
<div class="row">
95
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
96
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
</form>
</div>
Compare Data Annotation Attribute in ASP.NET MVC Application:
The Compare Data Annotation Attribute in ASP.NET Core MVC Framework compares 2
model properties with the same value. The Compare attribute is commonly used to compare
credit card numbers and passwords. Let’s understand using the Compare attribute with an
example. For example, to ensure that the user has typed the correct password, we must
use the Password and ConfirmPassword of the employee model, as shown below.
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
[DataType(DataType.Password)]
[DataType(DataType.Password)]
}
Next, modify the Create.cshtml view as follows.
98
@model DataAnnotationsDemo.Models.Employee
@{
ViewData["Title"] = "Create";
<h1>Create Employee</h1>
<div class="row">
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
</form>
</div>
Now, if both Password and Confirm Password are not the same, the user will get the model
validation error, as shown below:
Real-Time Example:
Let us see one real-time example of understanding the data annotation attributes in an
ASP.NET core MVC application. We will be creating an Employee form to create a new
employee with proper validation both on the client and server sides. So, first, create a class
file named Gender.cs and then copy and paste the following code. This is going to be an
enum containing the gender-named constants.
namespace DataAnnotationsDemo.Models
Male,
Female
}
100
}
Employee Model:
Next, modify the Employee.cs class file as follows.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace DataAnnotationsDemo.Models
[RegularExpression(@"^(([A-za-z]+[\s]{1}[A-za-z]+)|([A-Za-z]+))$",
ErrorMessage = "UserName can contain the first and last names with a single space. " +
"The last name is optional. Only upper and lower case alphabets are allowed")]
[DataType(DataType.Currency)]
[DataType(DataType.Password)]
[DataType(DataType.Password)]
}
Employee Controller:
Next, modify the Employee Controller as follows:
using DataAnnotationsDemo.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace DataAnnotationsDemo.Controllers
PrepareEmployeeViewModel();
return View();
[HttpPost]
if (ModelState.IsValid)
103
return RedirectToAction("Successful");
PrepareEmployeeViewModel();
return View(employee);
ViewBag.AllGenders = Enum.GetValues(typeof(Gender)).Cast<Gender>().ToList();
ViewBag.Departments = GetDepartments();
//You can get the data from the database and populate the SelectListItem
};
}
Employee Create View:
Next, modify the Create.cshtml view as follows:
@model DataAnnotationsDemo.Models.Employee
@{
ViewData["Title"] = "Create";
<h1>Create Employee</h1>
<div class="row">
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
asp-items="ViewBag.Departments">
</select>
</div>
106
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
107
<label class="radio-inline">
</label>
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
108
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
<style>
.form-check {
display: inline-block;
margin-right: 20px;
109
</style>
@{
int counter = 0;
<div class="form-check">
</div>
</div>
</div>
</div>
<div class="col-sm-10">
</div>
110
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
</form>
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
public ValidJoiningDateAttribute()
if (value is DateTime)
{
112
return ValidationResult.Success;
}
Code Explanation:
The ValidJoiningDateAttribute class extends the ValidationAttribute class provided by
the .NET framework. ValidationAttribute is a base class used to create custom validation
attributes that can be applied to model properties.
Constructor:
The constructor initializes the new attribute and sets the ErrorMessage property inherited
from ValidationAttribute. This message is displayed when the validation fails.
IsValid Method:
This method overrides the IsValid method of the base ValidationAttribute class. It is called
automatically when the framework performs model validation. The parameters are:
value: This is the value of the property to which the attribute is applied.
validationContext: This provides context about the validation operation,
such as the object being validated.
Validation Logic:
The validation logic first checks if the value being validated is of type
DateTime. If it is, the code casts value to DateTime and assigns it to
joiningDate.
Next, it checks if joiningDate is greater than DateTime.Now (i.e., it checks if
the date is in the future). If this condition is true, the method returns a new
ValidationResult containing the error message specified in the constructor.
This indicates that the validation has failed.
Successful Validation:
If the value is not a DateTime or the joiningDate is not in the future, the
method returns ValidationResult.Success indicates that the validation passed
successfully.
Apply the Attribute to a Model Property
Now that you have created the custom validation attribute, you can apply it to properties in
your model classes. So, create a class file named Employee.cs and copy and paste the
113
following code. Here, you can see we have applied the Custom ValidJoiningDate Attribute
to the DateOfJoining property.
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
}
Modify the Employee Controller:
Next, modify the Employee Controller as follows. Please ensure the model state is checked
for validity in your action methods before processing the data.
using DataAnnotationsDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace DataAnnotationsDemo.Controllers
return View();
}
114
[HttpPost]
if (ModelState.IsValid)
return RedirectToAction("Successful");
return View(employee);
}
Create.cshtml View:
Next, add Create.cshtml view and copy and paste the following code. Ensure that you use
HTML helpers like Html.ValidationMessageFor() or Tag Helper like asp-validation-for to
display validation messages.
@model DataAnnotationsDemo.Models.Employee
@{
ViewData["Title"] = "Create";
<h1>Create Employee</h1>
115
<div class="row">
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
</form>
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
Test Your Application
Finally, test your application to ensure the custom validation attribute works as expected.
Try submitting a form with a future date for the “Date of Joining” field and verify that the
error message is displayed, as shown in the image below.
116
By following these steps, we have successfully created and applied a custom data
annotation attribute in ASP.NET Core MVC, specifically for validating a “Date of Joining”
property. This not only ensures data integrity but also enhances your application’s
robustness. The above validation attribute only works on the server side, not on the client
side. Let us proceed and see how to enable client-side validation.
How Client-Side Validation Works in ASP.NET Core MVC?
Before understanding how to enable Client-Side Validation in ASP.NET Core MVC for
Custom Data Annotation Attributes, let’s first understand how client-side validation works in
ASP.NET Core MVC when using Data Annotation.
When we use Razor syntax to create form fields linked to model properties decorated with
data annotation attributes, the ASP.NET Core MVC Framework automatically generates
HTML elements with data attributes that describe the validation rules. The jQuery Validate
Unobtrusive library reads these attributes.
When we add a data annotation to a model property, the ASP.NET Core MVC tag helpers
will automatically generate the necessary data-val-* attributes to represent the validation
rules for that property. Let us take the following class. Here, we have applied the Required
Data Annotation attribute with the DateOfJoining model property.
public class Employee
}
We can then use ASP.NET Core MVC tag helpers in our Razor view to generate the HTML
as follows:
<input asp-for="DateOfJoining" class="form-control">
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
namespace DataAnnotationsDemo.Models
public ValidJoiningDateAttribute()
if (value is DateTime)
return ValidationResult.Success;
if (context == null)
}
119
if (!attributes.ContainsKey(key))
attributes.Add(key, value);
return true;
return false;
}
Code Explanation:
IsValid Method: Enable Server-Side Validation. We have already explained this method in
our previous example.
AddValidation Method:
Parameter Check: Initially, the method checks if the context is null, throwing
an exception if it is. This check ensures the method has the necessary
context to operate.
Adding Validation Attributes: The method uses the MergeAttribute to add
HTML5 data attributes to the form input element that will be used for client-
side validation:
data-val=”true” signals to the jQuery validation framework that the field has
validation rules.
data-val-ValidJoiningDate=”ErrorMessage” adds a custom attribute with
the validation error message. The key data-val-ValidJoiningDate is a unique
identifier for this particular validation rule.
MergeAttribute Method:
This helper method checks if an attribute already exists in the dictionary. If it doesn’t, the
attribute is added with the specified key and value. The method returns true if the attribute
120
was added and false if it was already present. This functionality prevents overwriting of any
existing attributes that may have been set elsewhere.
Client-Side Validation Logic
The client-side part of the validation logic is not explicitly detailed in this code but generally
involves:
JavaScript Integration: You would need to write custom JavaScript or
jQuery that uses the data attributes (data-val-ValidJoiningDate) to perform
validation on the client side.
Validation Framework: ASP.NET Core typically uses jQuery Unobtrusive
Validation for client-side validation. Your JavaScript would integrate with this
framework, ensuring that the client validates that the joining date is not in the
future before the form data is submitted to the server.
Add Client-Side JavaScript
Using jQuery and the jQuery Validation library, we can implement the client-side validation
logic. Create a JavaScript file that will extend jQuery validation to handle your custom
attribute. Add a new JavaScript file named JoiningDateValidate.js into the wwwroot folder
as shown below:
Then, copy and paste the following code into the JoiningDateValidate.js file. The following
code is self-explained, so please go through the comment lines for a better understanding.
// Define a custom method for jQuery validation to check the "Date of Joining".
// Directly return true for empty values, which allows the field to be optional.
if (!value) {
return true;
}
121
// Compare input date with the current date, ignoring time of day.
jQuery.validator.unobtrusive.adapters.addBool('ValidJoiningDate');
Include the JavaScript File in Your View
Make sure to reference the JavaScript file you created in the views that use the validation.
So, modify the Create.cshtml file as follows:
@model DataAnnotationsDemo.Models.Employee
@{
ViewData["Title"] = "Create";
<script>
</script>
<h1>Create Employee</h1>
<div class="row">
<div class="col-sm-10">
122
</div>
</div>
<div class="col-sm-10">
</div>
</div>
</form>
</div>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></
script>
<script src="~/js/joiningdatevalidate.js"></script>
Applying Custom Data Annotation Attribute:
Please make sure to apply the Custom Data Annotation Attribute with DateOfJoining
Property of Employee Model as follows:
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
}
Test the Validation
Now that both client-side and server-side validations are in place, test the application, and it
should work as expected:
Client-side: Try entering a future date in your form and see if the form
prevents submission without needing to go to the server.
Server-side: Disable JavaScript in your browser and try the same to ensure
the server-side validation is still working as expected.
Now, if you view the page source code, then you will see the following HTML code:
<input class="form-control" type="date" data-val="true" data-val-required="Date of Joining
is required" data-val-ValidJoiningDate="Date of Joining Cannot be Future Date"
id="DateOfJoining" name="DateOfJoining" value="">
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
namespace DataAnnotationsDemo.Models
{
125
_minimumAge = minimumAge;
if (value == null)
if (value is DateTime)
else
126
return ValidationResult.Success;
if (context == null)
MergeAttribute(context.Attributes, "data-val-minimumage-minage",
_minimumAge.ToString());
if (!attributes.ContainsKey(key))
attributes.Add(key, value);
return true;
return false;
}
127
}
Create a JavaScript File for Client-Side Validation
Next, we need to write JavaScript that works with the jQuery validation library to add a
method for handling your custom MinimumAge validation. So, create a JavaScript file
named minimum-age-validation.js within the wwwroot/js folder and copy and paste the
following code.
// minimum-age-validation.js
if (!value) {
return true; // Not validating emptiness here, required attribute should handle this
minimumAgeDate.setFullYear(minimumAgeDate.getFullYear() - parseInt(params));
return $(element).data('val-minimumage');
});
options.rules["minimumage"] = options.params.minage;
options.messages["minimumage"] = options.message;
});
Apply the Attribute to Your Model
Next, apply this attribute to the DOB property in your model. So, let us create a class file
named Employee.cs and then copy and paste the following code:
128
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
}
Use the Model in Your MVC Controller
Ensure your MVC controller is set up to use model validation. So, modify the Employee
Controller as follows:
using DataAnnotationsDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace DataAnnotationsDemo.Controllers
return View();
[HttpPost]
129
if (ModelState.IsValid)
return RedirectToAction("Successful");
return View(employee);
}
Update the View to Display Validation Messages
In your view, ensure you display validation messages related to the DOB field. So, modify
the Create.cshtml view as follows:
@model DataAnnotationsDemo.Models.Employee
@{
ViewData["Title"] = "Create";
<h1>Create Employee</h1>
<div class="row">
130
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
</form>
</div>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></
script>
<script src="~/js/minimum-age-validation.js"></script>
Testing:
Now, run the application and try to enter an Age that is less than 18 years, and then you will
get the required error message as shown in the image below:
131
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
namespace DataAnnotationsDemo.Models
public PasswordStrengthAttribute()
ErrorMessage = "Password must contain at least one uppercase letter, one lowercase letter,
one number, one special character (@,#,$,&) and be at least 8 characters long.";
if (!regex.IsMatch(password))
return ValidationResult.Success;
if (context == null)
context.Attributes.Add("data-val", "true");
context.Attributes.Add("data-val-passwordstrength", ErrorMessage);
}
Explanation
Regular Expression: The Regex pattern checks for:
133
if (!value) {
}, 'Password must contain at least one uppercase letter, one lowercase letter, one number, one
special character (@,#,$,&) and be at least 8 characters long.');
options.rules['passwordstrength'] = {};
options.messages['passwordstrength'] = options.message;
});
Usage in a Model
Apply the custom validation attribute to a property in your model. Please modify the
Employee model as follows:
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
{
134
[DataType(DataType.Password)]
[PasswordStrength]
}
Update the View to Display Validation Messages
In your view, ensure you display validation messages related to the Password field. So,
modify the Create.cshtml view as follows:
@model DataAnnotationsDemo.Models.Employee
@{
ViewData["Title"] = "Create";
<h1>Create Employee</h1>
<div class="row">
<div class="col-sm-10">
</div>
</div>
135
<div class="col-sm-10">
</div>
</div>
</form>
</div>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></
script>
<script src="~/js/minimum-age-validation.js"></script>
Testing:
Now, run the application and try to enter an invalid password and then you should get the
following error message as shown in the below image:
from the ValidationAttribute class for server-side validation and implementing the
IClientModelValidator interface for client-side validation. So, create a class file
named DateRangeValidationAttribute.cs and then copy and paste the following code:
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
namespace DataAnnotationsDemo.Models
OtherPropertyName = otherPropertyName;
if (otherPropertyInfo == null)
return ValidationResult.Success;
}
138
if (!attributes.ContainsKey(key))
attributes.Add(key, value);
}
Usage in a Model
Next, apply this attribute to your model. This example assumes that your model has
properties FromDate and ToDate. Let us modify the Employee.cs class file as follows. We
want to find the employees who joined the organization between these dates.
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
}
139
}
Add Client-Side Validation Script
Next, you need to write custom JavaScript that works with the jQuery validation library to
add a method for handling your custom Date Rangle validation. So, create a JavaScript file
named Date-Range-Validation.js within the wwwroot/js folder and copy and paste the
following code.
$.validator.addMethod('daterange', function (value, element, params) {
} else {
return true;
return $(element).data('valDaterange');
});
options.rules['daterange'] = true;
options.messages['daterange'] = options.message;
});
140
@{
ViewData["Title"] = "Create";
<h1>Create Employee</h1>
<div class="row">
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
141
<div class="col-sm-10">
</div>
</div>
</form>
</div>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></
script>
<script src="~/js/date-range-validation.js"></script>
Testing:
Now, run the application and try to enter an invalid date range, and then you should get the
error message as shown in the below image:
So, we need to do the following For Email and User Name fields:
Email: It must be Unique in the Database and should be a Proper Valid Email
Address. If the email address has already been taken by someone else, it
should give alternative suggestions for the email using the proper email
format. It is very similar to the Gmail Account Registration.
User Name: It Must be Unique in the Database, and the User Name should
Contain both Lower-Case and Upper-Case Alphabets, digits from 0 to 9, with
some special characters such as @, #, and $, and it should be at least 8
characters. Also, if the username has already been taken by someone else,
then we need to give some alternative suggestions.
To implement the above validation for the email and user name in an ASP.NET Core MVC
Application, we need a combination of client-side and server-side validations. Let’s proceed
and see how we can implement this step by step.
Creating a New ASP.NET Core Project:
143
First, create a new ASP.NET Core Project using the Model View Controller Template and
give the project name RemoteValidationDemo. Let’s create the project using the latest .NET
Core Version so that we can also use the latest EF Core Packages.
Once the Project is created, as we are going to work with SQL Server Database using
Entity Framework Core Code First Approach, so, we need to install the following two NuGet
Packages:
Microsoft.EntityFrameworkCore.Tools
Microsoft.EntityFrameworkCore.SqlServer
You can install the above two packages using NuGet Package Manager for the solution or
using Package Manager Console. As we are working with the latest version of .NET Core,
open Package Manager Console and then execute the following command.
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools
Creating Model:
Please add the following User.cs class file to the Models folder. EF Core will use this
Model to create the database table.
namespace RemoteValidationDemo.Models
}
Configure the Database Connection
Next, we are going to store the connection string in the appsettings.json file. So, add your
database connection string in the appsettings.json file as follows:
{
"Logging": {
"LogLevel": {
"Default": "Information",
144
"Microsoft.AspNetCore": "Warning"
},
"AllowedHosts": "*",
"ConnectionStrings": {
"EFCoreDBConnection": "Server=LAPTOP-6P5NK25R\\
SQLSERVER2022DEV;Database=EFCoreMVCDB;Trusted_Connection=True;TrustServerC
ertificate=True;"
}
Note: It will use the EFCoreMVCDB data if it exists in the SQL Server; otherwise, it will
create and use the EFCoreMVCDB.
Configure DbContext
Create a DbContext class for the application. Add a class
named EFCoreDBContext.cs within the Models folder, and then copy and paste the
following code.
using Microsoft.EntityFrameworkCore;
namespace RemoteValidationDemo.Models
: base(options)
}
145
}
Registering the Connection String and DbContext Class:
Next, we need to configure the connection string and register the context class to use the
Connection string in the Program class. So, please add the following to the Program class:
//Configure the ConnectionString and DbContext class
builder.Services.AddDbContext<EFCoreDBContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("EFCoreDBConnection"))
;
});
Create Database Migration
Next, we need to generate and execute the Migration so that it will create and update the
database schema. So, open the Package Manager Console and Execute the add-migration
and update-database commands as follows. You can give your migration any name. Here, I
am giving it EFCoreDBMig1. The name that you are giving it should not be given earlier.
With this, our Database with Users database table should be created as shown in the image
below:
146
Before proceeding with the Remote Validations, let us insert some data into the Users
database table by executing the following INSERT SQL statements, which we will use while
performing the Remote Validation checks.
INSERT INTO Users (Email, UserName, Password) VALUES
using Microsoft.EntityFrameworkCore;
namespace RemoteValidationDemo.Models
147
_context = context;
string suggestion;
do
suggestions.Add(suggestion);
}
148
string suggestion;
do
suggestions.Add(suggestion);
}
GenerateUniqueEmailsAsync Method
To generate a list of unique email addresses based on the provided base email, ensuring
that none of the suggested emails are already registered in the database or duplicated in
the list of suggestions.
Parameters:
baseUsername: The initial username input from which to derive the
suggestions.
149
using Microsoft.EntityFrameworkCore;
using RemoteValidationDemo.Models;
namespace RemoteValidationDemo.Controllers
_context = context;
_generateSuggestions = generateSuggestions;
[AcceptVerbs("GET", "POST")]
return Json(true);
//The second parameter will decide the number of unique suggestions to be generated
[AcceptVerbs("GET", "POST")]
return Json(true);
//The second parameter will decide the number of unique suggestions to be generated
}
IsEmailAvailable Method
This method asynchronously checks if an email address is already registered in the
database and, if so, provides alternative suggestions. The method uses the AnyAsync
method to query the database asynchronously and determine if the given email already
exists in the Users table.
Provide Feedback:
Email Available: If the email does not exist in the database, the method
returns a JSON object with a true value, indicating the email is available.
Email Not Available: If the email exists, it triggers the
GenerateUniqueEmailsAsync method from the GenerateSuggestions class to
generate three alternative email suggestions. It then returns a JSON object
containing a message that the email is in use, along with the suggestions.
IsUsernameAvailable Method
Similar to the IsEmailAvailable method, but focused on checking the availability of
usernames. This method also employs AnyAsync to check asynchronously if the username
already exists in the database.
Provide Feedback:
Username Available: If no existing user with the username is found, the
method returns a JSON object that is true, signaling that the username can
be used.
152
Username Not Available: If the username is taken, the method utilizes the
GenerateUniqueUsernamesAsync to generate three alternative usernames. It
then returns a JSON response indicating the username is taken and provides
alternative suggestions.
How Do We Implement Remote Validation in ASP.NET Core MVC?
We need to use the Remote attribute in ASP.NET Core MVC to enable server-side
validation as part of the client-side validation process. This attribute makes an AJAX call to
a specified server method to validate the data entered by the user, providing an
asynchronous way to check for uniqueness or other custom conditions that cannot be easily
validated using standard client-side validation techniques. Here is how it works in ASP.NET
Core MVC.
AJAX Call: When a user interacts with a form field (typically on losing focus),
the Remote attribute triggers an AJAX call to a specific server-side method.
Server-Side Method: The server method specified by the attribute checks
the input against business rules or database constraints. It then returns a
validation response (true if valid, false if not).
Feedback: If the response is false, an error message specified in the Remote
attribute is displayed to the user without the user needing to submit the form.
This instant feedback enhances the user experience by preventing form
submissions that would otherwise fail server-side validation.
Add Model with Validation Attributes
Next, we need to create a model with the necessary validation attributes and Apply the
Remote attribute to the property you want to validate. Please add the
following RegisterViewModel.cs class file within the Models folder and then copy and
paste the following code.
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
namespace RemoteValidationDemo.Models
[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@#$])[A-Za-z\d@#$]{8,}$",
ErrorMessage = "Username must be at least 8 characters long and include lower case, upper
case, a digit, and a special character.")]
}
If you look at the above View Model, then you will see we have applied the Remote Attribute
with the Email and UserName property:
Email Property:
Remote(action: “IsEmailAvailable”, controller: “RemoteValidation”, ErrorMessage =
“Email is already in use. Please use a different email address.”)]
Action: “IsEmailAvailable” – This is the server-side action method of the
RemoteValidation controller that the AJAX call will invoke.
Controller: “RemoteValidation” – Specifies the controller where the
IsEmailAvailable action method is defined.
ErrorMessage: If the IsEmailAvailable method returns false (indicating the
email is already in use), this error message is displayed: “Email is already in
use. Please use a different email address.”
UserName Property:
[Remote(action: “IsUsernameAvailable”, controller: “RemoteValidation”,
ErrorMessage = “Username is already taken. Please use a different username.”)]
Action: “IsUsernameAvailable” – Similar to the email check, this specifies
the server-side method that checks if the username is available.
Controller: “RemoteValidation” – This is the controller where the
IsUsernameAvailable action method resides.
ErrorMessage: If the method returns false (indicating the username is
taken), this error message is shown: “Username is already taken. Please use
a different username.”
Create the Controller for Handling Form Submission
Next, we need to create a controller to handle the form submission. So, create an Empty
MVC Controller named UserController within the Controllers folder and copy and paste
the following code.
using Microsoft.AspNetCore.Mvc;
154
using RemoteValidationDemo.Models;
namespace RemoteValidationDemo.Controllers
_context = context;
_generateSuggestions = generateSuggestions;
[HttpGet]
return View();
[HttpPost]
if (ModelState.IsValid)
//The second parameter will decide the number of unique suggestions to be generated
//The second parameter will decide the number of unique suggestions to be generated
if (ModelState.IsValid)
// Proceed with saving the new user or any other business logic
Email = model.Email,
UserName = model.UserName,
Password = model.Password
156
};
await _context.Users.AddAsync(user);
await _context.SaveChangesAsync();
return RedirectToAction("Successful");
return View(model);
}
In the above code, the GET version of the Create action method renders the view, which
will ask the user to enter the Email and User Name and the POST version of the Create
action method to handle the form submission. It might be possible that the client may
disable the JavaScript on his browser. In that case, the Remote Validation will not work. So,
for the safer side, we are also handling the User Name and Email Validation manually
inside the POST version of the Create action method.
Creating the View:
Next, add a view named Create.cshtml within the Views/User folder and copy and paste
the following. This view will ask the user to enter the User Name, Email, and Password.
@model RemoteValidationDemo.Models.RegisterViewModel
@{
}
157
<h1>Create User</h1>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
</div>
</div>
</form>
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
Testing the Application:
Now, run the application, navigate to the User/Create URL, and try to enter the Email and
User Name that exists in the database. You should see the Error Message immediately, as
shown in the below image:
159
namespace RemoteValidationDemo.Models
//The second parameter will decide the number of unique suggestions to be generated
return ValidationResult.Success;
}
User Name Validation Attribute
So, create a class file named UniqueUsernameAttribute.cs within the Models folder and
copy and paste the following code. As you can see, the class is inheriting from the
ValidationAttribute class and overriding the IsValid method.
using System.ComponentModel.DataAnnotations;
namespace RemoteValidationDemo.Models
{
161
//The second parameter will decide the number of unique suggestions to be generated
var suggestedUsernames =
generateSuggestions?.GenerateUniqueUsernamesAsync(userName, 3).Result;
return ValidationResult.Success;
}
Apply Custom Validation Attributes to Your Model
Now, apply these custom attributes to your model properties and other built-in and remote
attributes. So, please modify the RegisterViewModel.cs class file as follows.
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
namespace RemoteValidationDemo.Models
162
[UniqueEmail]
[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@#$])[A-Za-z\d@#$]{8,}$",
ErrorMessage = "Username must be at least 8 characters long and include lower case, upper
case, a digit, and a special character.")]
[UniqueUsername]
}
Modifying the Controller:
With the Custom Validation Attribute applied to Model Properties which will handle the
Server-Side Validation, now, we can safely remove the Validation Logic from the Controller
action method. So, please modify the User Controller as follows:
using Microsoft.AspNetCore.Mvc;
163
using RemoteValidationDemo.Models;
namespace RemoteValidationDemo.Controllers
_context = context;
[HttpGet]
return View();
[HttpPost]
if (ModelState.IsValid)
if (ModelState.IsValid)
// Proceed with saving the new user or any other business logic
164
Email = model.Email,
UserName = model.UserName,
Password = model.Password
};
await _context.Users.AddAsync(user);
await _context.SaveChangesAsync();
return RedirectToAction("Successful");
return View(model);
}
Testing the Application:
Before testing the Application to check whether the server-side validations are working fine
or not, first, disable the Browser JavaScript and then provide the value that exists in the
database. This time, you will see that client-side validation is not working, as shown in the
below image:
165
Now, with the above-existing Email and User Name, submit the data to the server. On the
server side, the validation will check, and then it will give you the error as shown in the
below image:
using Microsoft.EntityFrameworkCore;
using RemoteValidationDemo.Models;
166
namespace RemoteValidationDemo.Controllers
_context = context;
[AcceptVerbs("GET", "POST")]
return Json(true);
[AcceptVerbs("GET", "POST")]
return Json(true);
167
}
Then modify the UniqueEmailAttribute as follows:
using System.ComponentModel.DataAnnotations;
namespace RemoteValidationDemo.Models
return ValidationResult.Success;
}
168
}
Finally, modify the UniqueUsernameAttribute as follows:
using System.ComponentModel.DataAnnotations;
namespace RemoteValidationDemo.Models
return ValidationResult.Success;
}
169
With the above changes in place, run the application, and you should see the following error
message. You can test by enabling and disabling JavaScript.
Blacklist and Whitelist Checks using Data Annotation in ASP.NET Core MVC
In this article, I will discuss Blacklist and Whitelist Checks using Data Annotation in
ASP.NET Core MVC Applications. Please read our previous article discussing Remote
Validations in ASP.NET Core MVC.
Blacklist and Whitelist Checks using Data Annotation in ASP.NET Core
MVC
In ASP.NET Core MVC, implementing blacklist and whitelist checks is one of the security
measures to control access to resources and to filter inputs based on defined lists of
allowed (whitelist) or disallowed (blacklist) entities. This can help prevent unauthorized
access, SQL injection, cross-site scripting (XSS), and other malicious activities.
Whitelist: Only allows items that appear on the list. It’s generally considered
more secure than a blacklist because it explicitly permits only known safe
entities.
170
Blacklist: Blocks items that appear on the list. It can be harder to manage
because it requires anticipating all possible harmful inputs.
Both blacklisting and whitelisting are methods used to filter and validate data. Blacklisting
involves blocking specific values, while whitelisting permits only specific values.
Create Custom Data Annotations
ASP.NET Core does not provide built-in annotations for blacklist and whitelist checks, so we
need to create Custom Data Annotation Attributes. Let’s create custom data annotation
attributes in ASP.NET Core MVC for Black and White Lists.
Whitelist Attribute:
This attribute will only allow specific values from a predefined list. So, first, create a custom
data annotation attribute named WhitelistAttribute.cs within the Models folder and then
copy and paste the following code into it.
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
return ValidationResult.Success;
}
171
}
Blacklist Attribute:
This attribute will block specific values from a predefined list. So, create a custom data
annotation attribute named BlackListAttribute.cs within the Models folder and copy and
paste the following code into it.
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
if (string.IsNullOrEmpty(email))
return ValidationResult.Success;
return ValidationResult.Success;
172
}
Define Your Model
Now, we need to create a model class to restrict certain inputs by applying the above
WhitelistAttribute and BlackListAttribute. So, create a class file named UserInput.cs and
copy and paste the following code. Apply the custom annotations to the appropriate
properties in your model.
namespace DataAnnotationsDemo.Models
}
Handling Validation in the Controller
In your controller, you can check the validation status of your model as part of your action
methods. Next, modify the Home Controller as follows:
using DataAnnotationsDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace DataAnnotationsDemo.Controllers
{
173
return View();
[HttpPost]
if (ModelState.IsValid)
return RedirectToAction("Successful");
return View(model);
}
Create a View:
174
Ensure your views appropriately display validation messages. Next, create a view
named Create.cshtml within the Views/Home folder and then copy and paste the following
code:
@model DataAnnotationsDemo.Models.UserInput
@{
ViewData["Title"] = "Create";
<div class="row">
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
</form>
</div>
Now, run the application and see if everything is working as expected. If you provide the
wrong value, then you will get an error message, as shown in the image below:
namespace DataAnnotationsDemo.Models
{
176
if (commentText.Contains(word, StringComparison.OrdinalIgnoreCase))
foundWords.Add(word);
// If any offensive words are found, return a ValidationResult with all offending words
if (foundWords.Any())
return ValidationResult.Success;
177
}
Explanation:
List of Found Words: The foundWords list collects all the offensive words
that appear in the comment.
Check Each Word: The loop checks each word in the DisallowedWords
array to see if it appears in the comment.
Record Offenses: If a word is found, it’s added to the foundWords list.
Validation Result: If any offensive words are found, a ValidationResult is
returned that lists all of these words. If none are found,
ValidationResult.Success is returned
Create the Model
Next, we will define the BlogComment model, which represents a user’s comment on a blog
post. This model will include the comment text and a custom validation attribute to check for
offensive words. So, create a class file named BlogComment.cs and then copy and paste
the following code:
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
[Required]
[EmailAddress]
[Required]
[Required]
}
Create the MVC Controller
Now, let’s implement a controller with actions to create and post comments. The Create
GET action displays the form, and the Create POST action handles the form submission.
So, create an MVC Empty Controller named BlogCommentController within the
Controllers folder and then copy and paste the following code:
using DataAnnotationsDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace DataAnnotationsDemo.Controllers
[HttpGet]
return View();
[HttpPost]
if (ModelState.IsValid)
return RedirectToAction("Successful");
179
return View(blogComment);
}
Create the View
Next, create a view named Create.cshtml within the Views/BlogComment folder, and then
copy and paste the following code:
@model DataAnnotationsDemo.Models.BlogComment
@{
<div class="row">
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
</form>
</div>
181
With the above changes in place, run the application and navigate
to BlogComment/Create URL. Fill out the form with offensive words in the comment
section, and then click on submit. The application will then process the comment on the
server side and return it with the error message shown in the image below.
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
182
[DisplayName("Email Address")]
}
DisplayFor HTML Helper Method in ASP.NET Core MVC
The DisplayFor helper method formatted the value of a model property, taking into account
any formatting rules or display templates defined for that property. If the property has
display attributes like DisplayFormat, DisplayFor respects these settings when rendering the
property’s value.
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
}
Example to Understand DisplayFor, DisplayNameFor in ASP.NET Core
MVC:
Let us see one real-time application to understand the need and use of DisplayFor and
DisplayNameFor HTML Helper methods in ASP.NET Core MVC.
Creating Model:
First, create a class file named Employee.cs and copy and paste the following code into it.
This will be our model class, which will hold the employee data we want to display to the
user.
namespace DataAnnotationsDemo.Models
}
Creating Employee Controller:
Create an MVC Empty controller named EmployeeController within the Controllers folder
and copy and paste the following code. Here, we have hardcoded the employee details. But
in real time, you will get the details from the database.
using DataAnnotationsDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace DataAnnotationsDemo.Controllers
Id = 1,
Gender = "Male",
Age = 29,
185
EmailAddress = "info@dotnettutorials.net",
Salary = 55000,
PersonalWebSite = "https://dotnettutorials.net/",
JobDescription = string.Empty,
PerformanceRating = 4.5,
LastDateOfWorking = null
};
return View(employee);
}
Creating Details View:
Add a view named Details.cshtml within the Views/Employee folder and copy and paste
the following code into it. For the following view, Employee is the model, and then using the
DisplayNameFor and DisplayFor HTML helper methods, we display the Employee details.
@model DataAnnotationsDemo.Models.Employee
@{
<div>
<h3>Employee Details</h3>
<hr />
<dl class="row">
</dt>
</dd>
</dt>
</dd>
</dt>
</dd>
</dt>
</dd>
</dt>
</dd>
<dt class="col-sm-2">
</dt>
<dd class="col-sm-10">
</dd>
</dt>
</dd>
</dt>
</dd>
</dt>
</dd>
</dt>
</dd>
</dt>
</dd>
</dt>
</dd>
</dl>
189
</div>
With the above changes in place, run the application and visit the Employee/Details URL.
You should see the employee details in the image below.
Notice that the output is not that pretty. We can control the display of data in a view using
display attributes that are present in
the System.ComponentModel and System.ComponentModel.DataAnnotations namesp
ace. So, modify the Employee.cs class file as follows:
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
[DisplayName("Full Name")]
[DisplayName("Email Address")]
[DataType(DataType.EmailAddress)]
[DisplayName("Personal WebSite")]
[DataType(DataType.Url)]
[DisplayFormat(NullDisplayText = "N/A")]
}
Explanation of Each Property:
Each attribute affects how the property will be displayed or edited in the user interface (UI).
Let us understand what we applied on each property of the above Employee model one by
one, and they are going to be displayed in the UI:
Id
Property Type: int
Display/Format Attributes: None applied.
UI Display: Shows as a simple integer. No specific formatting or label
customization.
FullName
Property Type: string
Display Attributes: [DisplayName(“Full Name”)]
UI Display: This attribute changes the property’s display name in the UI to
“Full Name” instead of the default property name, “FullName.”
Gender
Property Type: string
Display Attributes: [DisplayFormat(NullDisplayText = “Gender Not
Specified”)]
UI Display: If Gender is null, it will display “Gender Not Specified”. Otherwise,
it displays the actual value of the Gender property.
Age
Property Type: int
Display/Format Attributes: None applied.
UI Display: Displays as a simple integer.
DateOFJoining
Property Type: DateTime
Display Attributes: [DisplayFormat(DataFormatString = “{0:dd/MM/yyyy}”,
ApplyFormatInEditMode = true)]
UI Display: Formats the date as “dd/MM/yyyy” (e.g., “23/04/2024”). This
format is also applied when editing the date in the UI.
EmailAddress
Property Type: string
Display Attributes: [DisplayName(“Email Address”)],
[DataType(DataType.EmailAddress)]
UI Display: The label will read “Email Address” instead of “EmailAddress”.
The DataType attribute informs the UI that this string should be treated as an
email address, which can affect the rendering (e.g., may use <a href=”mailto:
…”>).
Salary
Property Type: decimal
Display Attributes: [DisplayFormat(DataFormatString = “{0:C}”,
ApplyFormatInEditMode = true)]
UI Display: Formats the salary as a currency value (e.g., “$10,000.00”). This
currency formatting is applied in both display and edit modes.
192
PersonalWebSite
Property Type: string?
Display Attributes: [DisplayName(“Personal WebSite”)],
[DataType(DataType.Url)]
UI Display: The label changes to “Personal WebSite”. The DataType attribute
specifies that this string should be formatted as a URL, potentially rendering it
as a clickable hyperlink.
PerformanceRating
Property Type: double
Display Attributes: [DisplayFormat(DataFormatString = “{0:N2}”,
ApplyFormatInEditMode = true)]
UI Display: Formats the number to two decimal places (e.g., “85.75”). This
formatting is consistent in both display and edit modes.
DateOfBirth
Property Type: DateTime
Display Attributes: [DisplayFormat(DataFormatString = “{0:yyyy-MM-dd}”,
ApplyFormatInEditMode = true)]
UI Display: Formats the date as “yyyy-MM-dd” (e.g., “1990-05-21”). This
setting is also applied in edit mode.
JobDescription
Property Type: string?
Display Attributes: [DisplayFormat(NullDisplayText = “N/A”)]
UI Display: If JobDescription is null, the UI will display “N/A”. Otherwise, it
displays the actual value.
LastDateOfWorking
Property Type: DateTime?
Display Attributes: [DisplayFormat(DataFormatString = “{0:MMM dd, yyyy}”,
NullDisplayText = “N/A”, ApplyFormatInEditMode = true)]
UI Display: For non-null dates, it formats them as “MMM dd, yyyy” (e.g., “Apr
23, 2024”). If null, it displays “N/A”. This formatting is used both for display
and edit modes.
Modifying the Program Class:
We need to set some settings in the program class to display the currency symbol specific
to culture. By default, as I am from India, the currency will be the Indian rupee. However, we
can also set the currency we want to display, such as the US dollar, the Great British
Pound, etc. To do so, please modify the Program class as shown below.
using Microsoft.AspNetCore.Localization;
namespace DataAnnotationsDemo
builder.Services.AddControllersWithViews();
builder.Services.Configure<RequestLocalizationOptions>(options =>
});
app.UseRequestLocalization();
if (!app.Environment.IsDevelopment())
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios,
see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
app.UseHttpsRedirection();
194
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Employee}/{action=Details}/{id?}");
app.Run();
}
After making the above changes, run the application, and you should get the expected
output, as shown in the image below.
195
196
[BindNever]
public string CreatedBy { get; set; } // This property will not be bound to user input
}
In this example, the CreatedBy property will not receive any data from an incoming request,
regardless of whether it appears in the request. This helps protect critical fields that should
not be altered directly by external users.
Ad
1/2
00:14
[BindRequired]
[BindRequired]
}
In this example, both Username and Email must be present in the request data; otherwise,
the model binding will fail, making this method effective for enforcing mandatory fields.
When to use BindRequired:
Validation: Ensure the user provides essential fields during form submissions
or API calls.
Data Integrity: To prevent the application from processing incomplete or
corrupt data.
User Feedback: To generate automatic validation responses that inform the
user of required data that was not submitted.
Example of BindNever and BindRequired Attribute in ASP.NET
Core MVC:
Suppose you build an e-commerce application using ASP.NET Core MVC with an Order
model. The Order model includes sensitive information that should not be updated directly
from form data, such as OrderTotal and PaymentProcessed. Other fields like
CustomerName, OrderDate, and ShippingAddress should also be updated. So, create a
class file named Order.cs and copy and paste the following code. Inside this file, we are
creating all our models.
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace DataAnnotationsDemo.Models
public decimal Price { get; set; } // This can be the selling price which might include
discounts etc.
[BindNever]
[BindRequired]
[BindRequired]
[BindNever]
[BindRequired]
199
[BindNever]
}
In the Order model:
ShippingAddress is a required field as it’s crucial for processing the delivery.
PhoneNumber is optional but can be useful for contacting the customer
regarding their order.
PaymentProcessed is marked with BindNever to ensure it’s not modified
directly by form submission, reflecting whether payment has been securely
processed on the server side.
Items represent a list of OrderItem, detailing the items included in the order.
Add a Controller for the Order
Create a new Empty MVC Controller in the Controllers folder
called OrderController.cs, and then copy and paste the following code.
using DataAnnotationsDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace DataAnnotationsDemo.Controllers
public OrderController()
{
200
};
[HttpGet]
OrderDate = DateTime.Today,
ProductId = p.ProductId,
Price = p.Price,
}).ToList()
};
return View(order);
[HttpPost]
{
201
if (ModelState.IsValid)
if (product != null)
item.Price = product.Price;
item.Product = product;
order.OrderTotal = CalculateOrderTotal(order);
order.PaymentProcessed = ProcessPayment(order);
return RedirectToAction("Success");
return View(order);
return total;
return true;
return View();
}
Create the View
Create a new View named Create.cshtml in the Views/Order folder, and then copy and
paste the following code.
@model DataAnnotationsDemo.Models.Order
@{
<h2>Create Order</h2>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<h3>Order Items</h3>
<colgroup>
</colgroup>
<thead>
<tr>
<th>Item Name</th>
<th>Quantity</th>
<th>Unit Price</th>
<th>Total Price</th>
</tr>
</thead>
205
<tbody>
<tr>
<td>
<span>@Model.Items[i].Product?.Name</span>
</td>
<td>
</td>
<td class="text-right">
<span>@Model.Items[i].Price.ToString("C")</span>
</td>
<td class="text-right">
<span class="total-price">@((Model.Items[i].Price *
Model.Items[i].Quantity).ToString("C"))</span>
</td>
</tr>
</tbody>
</table>
206
</div>
</div>
</div>
</form>
@section Scripts {
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$(document).ready(function () {
$('.quantity').change(function () {
var total = 0;
$('tbody tr').each(function () {
var price = parseFloat($row.find('.unit-price').val()); // Get the unit price from the hidden
input
$row.find('.total-price').text('$' + totalPrice.toFixed(2));
total += totalPrice;
});
$('#totalAmount').text('$' + total.toFixed(2));
});
});
</script>
}
Create another View named Success.cshtml in the Views/Order folder and then copy and
paste the following code.
@{
<h2>Success</h2>
Once you click the Submit Order button, the order should be processed, and you should get
the following success message.
Points to Remember:
Security: Using BindNever can be part of a strategy to prevent over-posting
attacks, in which users might attempt to set properties they should not be
able to set.
Validation: BindRequired does not replace validation attributes like
[Required]. While BindRequired ensures the presence of a property in the
incoming data, [Required] validates that a property is not null after binding
has occurred.
[BindNever] is used to protect specific properties from being affected by
model binding, usually for security reasons
[BindRequired] ensures that certain fields are included in the request, which
is crucial for data completeness and validation.
Differences Between Required and BindRequired in ASP.NET
Core MVC
In ASP.NET Core MVC, the Required and BindRequired attributes are often confused
because they imply some form of “required-ness” for model properties. However, they serve
209
different purposes and are used in different contexts. Understanding the Differences
between them is important for correctly implementing data validation and model-binding
behavior in your applications.
Required Attribute
Context: Data Annotation for Validation
Purpose: The Required attribute is part of the
System.ComponentModel.DataAnnotations namespace and is used primarily
for model validation. It ensures that a property has a value; it is mainly used
to enforce that a field should not be null or empty during model validation.
Behavior: When a model is validated (usually after model binding has
occurred), if a property marked with the Required attribute does not have a
value, the model state is considered invalid. This attribute affects the server-
side validation process and is also used to generate client-side validation
rules.
Example of Required Attribute:
using System.ComponentModel.DataAnnotations;
[Required]
}
BindRequired Attribute
Context: Model Binding
Purpose: The BindRequired attribute, from the
Microsoft.AspNetCore.Mvc.ModelBinding namespace handles model binding
behavior. It is used to indicate that a property must be present in the incoming
data for binding to succeed. If the property is not present in the incoming
request, the model state becomes invalid.
Behavior: Unlike Required, BindRequired does not concern itself with
validating the property’s content (whether empty or null) but rather with its
presence in the request. This is useful for ensuring that key parts of the model
are not omitted in the request.
Example of BindRequired attribute usage:
using Microsoft.AspNetCore.Mvc.ModelBinding;
[BindRequired]
210
}
Key Differences Between Required and BindRequired in ASP.NET Core
Validation vs. Binding: Required validates that a property has a value after
binding has occurred, whereas BindRequired ensures that the property is
included in the incoming data before or during binding.
Effect on ModelState: Both can make ModelState invalid for different
reasons: Required if the value is null or empty, and BindRequired if the key is
missing in the request data.
Use Case: Use Required Attribute when you need to ensure that a property is
not empty or null (common for forms and data entry scenarios). Use
BindRequired when you need to ensure that certain data elements are
actually present in the incoming request, typically important in APIs and data
update scenarios.
[Required]
211
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at most {1} characters
long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
[DataType(DataType.Password)]
}
Product Listing in an E-Commerce Application:
In an e-commerce application, you’d want to have constraints and proper display settings
for your product model when listing products.
Ad
1/2
00:46
[Key]
[Required]
[StringLength(255)]
[Required]
212
[DataType(DataType.Currency)]
[StringLength(2000)]
[DataType(DataType.Date)]
}
Employee Management System:
For an application that manages employees, you’d likely want to enforce validation and
structure to your data.
public class Employee
[Key]
[Required]
[StringLength(100)]
[Required]
[StringLength(100)]
[Required]
[Required]
[EmailAddress]
[Phone]
[DataType(DataType.Date)]
}
Feedback Form:
You’d want to gather accurate and relevant information for a feedback or contact form on a
website.
public class FeedbackViewModel
[Required]
[StringLength(100)]
[Required]
[EmailAddress]
[Required]
[DataType(DataType.MultilineText)]
}
Hotel Booking System:
Data integrity for date ranges and room types is essential in a hotel booking application.
public class RoomBookingModel
[Key]
[Required]
[Required]
[DataType(DataType.Date)]
[Required]
[DataType(DataType.Date)]
[Required]
[EnumDataType(typeof(RoomType))]
Single,
Double,
Suite
}
Library Management System:
Ensuring the right format for ISBN numbers and proper categorization of books is vital for a
library system.
public class BookModel
[Key]
[Required]
[Required]
[Required]
[Display(Name = "Author")]
216
[DataType(DataType.Date)]
[EnumDataType(typeof(Genre))]
Fiction,
NonFiction,
Biography,
Children,
Mystery,
Fantasy
}
Employee Portal:
Ensuring accurate data for roles, contact information, and employment dates is critical for a
company’s employee portal.
public class EmployeeModel
[Key]
[Required]
217
[Required]
[Required]
[EmailAddress]
[Required]
[Phone]
[Required]
[EnumDataType(typeof(Role))]
[DataType(DataType.Date)]
Admin,
Manager,
218
Developer,
Designer,
Sales
}
Online Shopping Portal:
When users want to register on an e-commerce site, data annotations can be used to
ensure valid and consistent data entry.
public class CustomerRegistrationViewModel
[Required]
[Required]
[EmailAddress]
[Required]
[DataType(DataType.Password)]
[DataType(DataType.Password)]
[Required]
[Phone]
[Required]
}
Health Clinic Appointment Booking:
When patients book an appointment, ensure the date and time are specified and gather
valid contact information.
public class AppointmentViewModel
[Required]
[DataType(DataType.Date)]
[Required]
[DataType(DataType.Time)]
[Required]
[Required]
[Phone]
220
}
Blog Management System:
In a system where users can submit blog posts, data annotations can enforce character
limits, ensure necessary fields are filled, and handle other constraints.
public class BlogPostViewModel
[Required]
[StringLength(200)]
[Required]
[StringLength(5000)]
[DataType(DataType.Date)]
[EmailAddress]
}
Job Application Portal:
Imagine a scenario where you’re building a job application portal, and applicants need to fill
out their details.
public class JobApplicationModel
{
221
[Required]
[Required]
[Phone]
[Required]
[DataType(DataType.Upload)]
}
Real Estate Listing:
For a real estate application, agents need to list properties with specific attributes:
public class PropertyListingModel
[Required]
222
[Required]
[Required]
[Required]
[DataType(DataType.Currency)]
}
Booking a Workshop or Seminar:
For booking a workshop, attendees might need to provide some details:
public class WorkshopBookingModel
[Required]
[Required]
[EmailAddress]
[Required]
[DataType(DataType.Date)]
[Required]
}
Product Review on an E-Commerce Site:
Customers might want to leave reviews for products they’ve purchased:
public class ProductReviewModel
[Required]
[Required]
[Range(1, 5)]
[Required]
[Required]
}
Event Registration Form:
224
[Required]
[Required]
[Required]
}
Order Placement in an E-commerce Platform:
When placing an order, users need to provide shipping details.
public class ShippingDetailsModel
[Required]
[Required]
[Required]
[Required]
[Phone]
}
Gym Membership Form:
For a gym membership registration, data annotations ensure valid user data.
public class MembershipModel
[Required]
[Required]
[DataType(DataType.Date)]
[Required]
[EmailAddress]
[Required]
}
Feedback for a Restaurant:
After dining in a restaurant, customers might want to leave feedback and rate their
experience.
public class RestaurantFeedbackModel
[Required]
[Required]
approaches, i.e., Data Annotation and Fluent API Validations. We have already discussed
Model Validation using Data Annotation. In this article and in our few upcoming articles, we
are going to discuss Model Validations using Fluent API.
What is Fluent API Validation in ASP.NET Core MVC?
Fluent API validation in ASP.NET Core MVC is an approach to model validation that uses a
fluent interface provided by the FluentValidation library. This approach constructs validation
rules in a clear, flexible, understandable, and readable manner, which is an alternative to
the traditional Data Annotation approach.
So, instead of directly applying data annotation attributes to model properties, we can define
validation rules in a separate class with Fluent Validation. This “fluent” approach is based
on the Fluent Interface design pattern, which uses method chaining to create code that
reads like natural language.
Fluent API Validation Methods in ASP.NET Core
In ASP.NET Core, when we refer to Fluent API validation, we often talk about the library
FluentValidation. The FluentValidation provides a rich set of methods to define validation
rules fluently. Here are some of the common validation methods provided by
FluentValidation:
Traveling across Quang Binh to stunning cinematic locations
Basic Validators:
NotNull() / NotEmpty(): Check the property is not null or empty.
Equal(): Checks if the property equals another value or property.
NotEqual(): Checks the property is not equal to another value or property.
Length(): Checks the length of a string property that falls within the given min
and max range.
Matches(): Checks the property matches a given regex pattern.
Number Validators:
InclusiveBetween(): Checks the property value falls between or equals to
the specified limits. This can also be used for DateTime ranges.
ExclusiveBetween(): Checks the property value falls between but not equal
to the specified limits. This can also be used for DateTime values.
LessThan() / LessThanOrEqualTo(): For comparison with another value.
This can also be used for DateTime values.
GreaterThan() / GreaterThanOrEqualTo(): Same as above, but for greater
values. This can also be used for DateTime values.
String Validators:
EmailAddress(): Checks the property is a valid email address.
CreditCard(): Checks the property is a valid credit card number.
Conditionally Applying Rules:
When(): Apply rules conditionally based on properties or external factors.
Unless(): The opposite of When(), the rule is applied unless the condition is
true.
Collections:
Must(): This allows for more complex custom logic. For instance, you could
validate that a list has unique elements. It can also be used to write custom
logic for validation.
ForEach(): Apply a rule to each element in a collection.
Custom Validators:
228
}
Creating a Fluent API Validator:
A validator class in Fluent Validation is where you define the rules for each property of the
model you want to validate. To implement Fluent API Validation to Validate the Model
Properties, we need to create a Fluent API Validator class, and for each model class, we
need to create a separate Validator class. For example, for our Registration Model, we need
to create a Validator Class, and as part of the Validator class, we need to write the logic to
validate the properties, as per our business requirement.
So, create a class file named RegistrationValidator.cs within the Models folder and copy
and paste the following code. Here, we use the built-in Fluent API validation methods to
validate the RegistrationModel properties.
using FluentValidation;
namespace FluentAPIDemo.Models
public RegistrationValidator()
.NotEmpty().WithMessage("Username is Required.")
.NotEmpty().WithMessage("Email is Required.")
.NotEmpty().WithMessage("Password is Required.")
}
Understanding the above Code:
Namespace and Usings: The code uses the FluentValidation library. The using
FluentValidation; statement allows access to the FluentValidation classes and methods.
Class Definition: The RegistrationValidator class is defined, which inherits
from AbstractValidator<T> generic class. This generic base class is provided by Fluent
Validation, where T represents the type of object to validate, in this
case, RegistrationModel. So, it is the base class that we need to extend to define our
validation rules for a specific model.
Validator Constructor: The constructor public RegistrationValidator() sets up rules for the
properties of RegistrationModel. The Validation Rules are as follows:
Username Validation:
RuleFor(x => x.Username): Defines a validation rule for the Username
property of RegistrationModel.
.NotEmpty(): Specifies that the Username must not be empty. If it is empty,
the message “Username is Required.” will be shown.
.Length(5, 30): Ensures that the length of the Username must be between 5
and 30 characters. If not, the message “Username must be between 5 and 30
characters.” is displayed.
Email Validation:
RuleFor(x => x.Email): Sets a rule for the Email property.
.NotEmpty(): The Email cannot be empty, and if it is, “Email is Required.” will
be the error message.
.EmailAddress(): Validates that the input is a valid email address format. If it
fails, “Valid Email Address is Required.” is shown as the error message.
Password Validation:
RuleFor(x => x.Password): Defines a rule for the Password property.
.NotEmpty(): Checks that the Password is not empty, displaying “Password
is Required.” if it is.
.Length(6, 100): Ensures the Password length is between 6 and 100
characters. If the length criteria are not met, “Password must be between 6
and 100 characters.” will be shown.
Points to Remember:
231
using FluentValidation;
using FluentValidation.AspNetCore;
using System.Drawing;
namespace FluentAPIDemo
builder.Services.AddControllersWithViews();
builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddFluentValidationClientsideAdapters();
//Registering Model and Validator to show the error message on client side
builder.Services.AddTransient<IValidator<RegistrationModel>, RegistrationValidator>();
if (!app.Environment.IsDevelopment())
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios,
see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
233
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
}
The FluentValidation will automatically validate controller action method parameters,
populating the ModelState with validation errors. You can then check ModelState.IsValid in
your action methods to determine if the received input is valid.
Validating in Controllers:
Now, we need to Validate the RegistrationModel within the Controller action method. You
need to remember that FluentValidation will automatically validate the model and populate
the ModelState. So, modify the Home Controller class as follows:
using FluentAPIDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FluentAPIDemo.Controllers
return View();
[HttpPost]
if (!ModelState.IsValid)
{
234
return View(model);
return RedirectToAction("Success");
}
Display Errors in Views:
We can display errors using the validation tag helpers in our Razor views. So, add
the Register.cshtml view and copy and paste the following code.
@model FluentAPIDemo.Models.RegistrationModel
@{
ViewData["Title"] = "Register";
<h1>Register User</h1>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Register">
</div>
</div>
</div>
</div>
</form>
</div>
</div>
Now, run the application, navigate to Home/Register, and check whether everything works
as expected.
236
Here, you can observe that the Client-Side validation is not working. To ensure the client-
side validation is working, we must include jQuery, jQuery Validation, and jQuery
Unobtrusive Validation in our project. These can be added via CDNs or local files. For
example, include them via CDNs in your _Layout.cshtml:
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.2/
jquery.validate.min.js"></script>
<!-- jQuery Unobtrusive Validation (to bridge ASP.NET Core MVC validation with jQuery
Validate) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/
jquery.validate.unobtrusive.min.js"></script>
237
To enable client-side validation using local files, you can add the following files to
your _Layout.cshtml. The order is also important; please include them in the following
order.
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></
script>
However, as we already discussed, the above files are already added within
the _ValidationScriptsPartial partial view. In the _Layout file, one optional section is
provided named Scripts. So, we can include a section called Scripts and call the
_ValidationScriptsPartial partial view inside that section. Please modify
the Register.cshtml view as follows. We can add the Script section and call the
_ValidationScriptsPartial Partial View wherever the client-side validation is required.
@model FluentAPIDemo.Models.RegistrationModel
@{
ViewData["Title"] = "Register";
<h1>Register User</h1>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Register">
</div>
</div>
</div>
</div>
</form>
</div>
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
With the above changes, you can observe that the client-side validation is also working as
expected.
Validating Manually:
Instead of using the ModelState.IsValid to check the Validation automatically, we can also
check the validation manually by creating an instance of the Validator class and then
invoking the Validate method. For a better understanding, please look at the following code.
using FluentAPIDemo.Models;
239
using Microsoft.AspNetCore.Mvc;
namespace FluentAPIDemo.Controllers
return View();
[HttpPost]
if (!validationResult.IsValid)
return View(model);
//if (!ModelState.IsValid)
//{
// return View(model);
//}
return RedirectToAction("Success");
}
With the above changes, run the application and see the expected behavior. Note that not
all rules defined in FluentValidation will work with ASP.NET’s client-side validation. For
example, rules defined using a condition (with When/Unless), Custom Validators, or Calls to
Must will not run on the client side. The client-side validation supports the following
Validator Methods in ASP.NET Core:
NotNull/NotEmpty
Matches (regex)
InclusiveBetween (range)
CreditCard
Email
EqualTo (cross-property equality comparison)
MaxLength
MinLength
Length
What happens when using Fluent Validation and Data Annotations in
ASP.NET Core MVC with the Same Model Properties?
When Fluent Validation and Data Annotations are used in an ASP.NET Core MVC
application to validate the same model properties, both sets of validation rules are applied.
Data Annotations: ASP.NET Core Processes Data Annotations validation
automatically when model binding occurs. This means that when the
framework attempts to bind incoming data to a model, it checks the Data
Annotations rules.
Fluent Validation: If you have configured Fluent Validation to integrate with
ASP.NET Core’s automatic validation pipeline, it generally runs after Data
Annotations during the model binding process. If you invoke Fluent Validation
manually (e.g., inside a controller action), you control when it runs relative to
Data Annotations.
Example:
Let’s understand this with an example. To include the Data Annotation Validations, let’s
modify the RegistrationModel.cs class file as follows. You can see we have decorated the
Model Properties with Data Annotation Attributes.
241
using System.ComponentModel.DataAnnotations;
namespace FluentAPIDemo.Models
}
Next, modify the Home Controller as follows. The ASP.NET Core framework will
automatically handle Data annotation and Fluent validations at the time of Model Binding as
we have enabled the Auto Validations for Fluent API.
using FluentAPIDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FluentAPIDemo.Controllers
{
242
return View();
[HttpPost]
if (!ModelState.IsValid)
return View(model);
return RedirectToAction("Success");
}
To see the effect, please modify the Register.cshtml file as follows. Here, we are disabling
the client-side validation by removing the script section that invokes the Partial View.
@model FluentAPIDemo.Models.RegistrationModel
@{
ViewData["Title"] = "Register";
}
243
<h1>Register User</h1>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Register">
</div>
</div>
</div>
</div>
244
</form>
</div>
</div>
@* @section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
} *@
Now, run the application and click on the Create button without providing any data, and you
will get the following error. In each individual span element where the model-specific error is
displayed, you will not see both error messages as they always overwrite the previous error
message with the latest one. However, if you observe the validation summary, it shows all
the error messages, as shown in the image below, which proves that both Validations are
working.
245
make the code more clear and self-documenting. For example, a validation
rule might look like this: .RuleFor(model =>
model.Property).NotEmpty().WithMessage(“Property cannot be empty”);
Centralized Validation Rules: This allows you to define all validation rules
for a model in one place. This centralization helps avoid duplication of
validation logic across different parts of an application, such as controllers,
views, services, etc., making the code cleaner and reducing maintenance
overhead. This also makes the rules easier to manage and modify.
Customization and Flexibility: Fluent API validators are highly
customizable, allowing developers to define complex validation rules that are
not easily implemented using data annotations. You can also create and use
custom validation methods in the validation chain.
Customizable Error Messages: Fluent API allows users to customize error
messages easily and dynamically based on validation contexts. This flexibility
makes error responses more meaningful for the end user.
Reusability: Since validation rules are defined in separate classes (usually
one per model), you can reuse these validators across different application
parts, ensuring consistent validation logic throughout.
Integration with ASP.NET Core Features: These validators work
seamlessly with ASP.NET Core MVC features like model binding, automatic
HTTP 400 responses on validation failures, and integration with ModelState.
This means you can easily tie your custom validation logic into the standard
ASP.NET Core pipeline and handle errors effectively.
Support for Asynchronous Validation: Fluent API supports asynchronous
validation rules, which are essential for non-blocking I/O operations, such as
database or network calls, improving the performance of your web
application.
Interdependent Validations: Fluent API can handle complex scenarios
where the validity of one field may depend on the state or value of another
field within the model. This interdependency is more awkward to implement
with traditional attribute-based validation.
Performance Differences Between Data Annotation and Fluent API
Validation in ASP.NET Core MVC:
Data Annotation: Data Annotations may lead to slightly faster performance because they
are less complex and integrated directly into the model properties. However, the difference
is often negligible in most applications.
Fluent API Validation: In runtime performance, there might be a negligible performance
overhead with Fluent Validation because it involves compiling and executing more complex
validation rules. However, this difference is generally small and can be offset by the benefits
of more powerful validation capabilities.
Fluent Validation on both the Client and Server Sides. At the end of this article, you will
understand examples using the following Validators.
1. Fluent API Basic Validators Examples
2. Fluent API Number Validators Examples
3. String Validators in ASP.NET Core MVC
4. Fluent API Date and Time Validators
5. Conditional Validation using Fluent API in ASP.NET Core MVC
6. Fluent API Collections Validator
7. Fluent API Other Validators in ASP.NET Core MVC
Basic Fluent API Validators Example in ASP.NET Core MVC:
FluentValidation in ASP.NET Core MVC offers many built-in validators. The following are
some of the most common basic Fluent API validators.
NotNull() / NotEmpty(): Check the property is not null or empty.
Equal(): Checks if the property equals another value or property.
NotEqual(): Checks the property is not equal to another value or property.
Length(): Checks the length of a string property that falls within the given min
and max range.
Matches(): Checks the property matches a given regex pattern.
Let us see one real-time example to understand Fluent API NotNull(), NotEmpty(), Equal(),
NotEqual(), Length(), and Matches() methods in ASP.NET Core MVC Application.
Define the Model:
Let’s assume we have the Registration model.
namespace FluentAPIDemo.Models
}
248
namespace FluentAPIDemo.Models
public RegistrationValidator()
.NotNull().WithMessage("Username is Required")
.NotNull().WithMessage("Password is Required")
.Matches("(?!^[0-9]*$)(?!^[a-zA-Z]*$)^([a-zA-Z0-9]{2,})$").WithMessage("Password can
only contain alphanumeric characters");
.NotEmpty().WithMessage("Email is required")
}
Registering the Validator:
Add the following code to the Program class to register Fluent API Validation, the Model,
and the corresponding validator.
//Enables integration between FluentValidation and ASP.NET MVC's automatic validation
pipeline.
builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddFluentValidationClientsideAdapters();
builder.Services.AddTransient<IValidator<RegistrationModel>, RegistrationValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FluentAPIDemo.Controllers
return View();
[HttpPost]
250
if (!ModelState.IsValid)
return View(model);
return RedirectToAction("Success");
}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add Register.cshtml view and copy
and paste the following code.
@model FluentAPIDemo.Models.RegistrationModel
@{
ViewData["Title"] = "Register";
<h1>Register User</h1>
<hr />
251
<div class="row">
<div class="col-md-4">
<form asp-action="Register">
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
With the above changes, run the application, and you should get the validation error
message as expected, as shown in the image below.
253
public decimal Discount { get; set; } // Let's assume this is a percentage value.
}
Set Up the Validator:
Now, let’s define a validator for this model:
using FluentValidation;
namespace FluentAPIDemo.Models
public ProductValidator()
}
In this Validator:
GreaterThan, LessThan, GreaterThanOrEqualTo, and LessThanOrEqualTo
set specific bounds on numeric values.
InclusiveBetween ensures the value lies within (and including) the provided
range.
ExclusiveBetween would ensure the value lies within the range but excluding
the boundaries (this method is not used in the above example, but you could
replace InclusiveBetween with it for a different behavior).
Registering the Validator:
Add the following code in the Program class to register the Model and corresponding
validator.
//Registering Model and Validator to show the error message on the client side
builder.Services.AddTransient<IValidator<Product>, ProductValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FluentAPIDemo.Controllers
return View();
}
256
[HttpPost]
if (!ModelState.IsValid)
return View(product);
return RedirectToAction("Success");
}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add AddProduct.cshtml view and
copy and paste the following code.
@{
<h1>Add Product</h1>
<hr />
257
<div class="row">
<div class="col-md-4">
<form asp-action="AddProduct">
</div>
</div>
</div>
</div>
</form>
</div>
</div>
258
@* @section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
} *@
Now, run the application, and you should see the proper validation messages in the image
below.
Let’s assume we have the following PaymentInfo model, which has properties like
CardHolderName, CardNumber, BillingEmail, and Amount. We will use this model to
capture a user’s payment details.
namespace FluentAPIDemo.Models
}
Set Up the Validator:
Now, let’s define a validator for this model:
using FluentValidation;
namespace FluentAPIDemo.Models
public PaymentInfoValidator()
.CreditCard()
//PrecisionScale: Checks the number of allowable decimal places and the overall precision of
a number.
.PrecisionScale(8, 2, true)
}
Registering the Validator:
Add the following code in the Program class to register the Model and corresponding
validator.
//Registering Model and Validator to show the error message on the client side
builder.Services.AddTransient<IValidator<PaymentInfo>, PaymentInfoValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FluentAPIDemo.Controllers
return View();
[HttpPost]
if (!ModelState.IsValid)
return RedirectToAction("Success");
}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add ProcessPayment.cshtml view
and copy and paste the following code.
@model FluentAPIDemo.Models.PaymentInfo
262
@{
<h1>Process Payment</h1>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="ProcessPayment">
</div>
</div>
</div>
263
</div>
</div>
</form>
</div>
</div>
@* @section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
} *@
Now, run the application, and you should see the proper validation messages as expected,
as shown in the below image.
Fluent API DateTime Validators in ASP.NET Core MVC:
Let’s understand Fluent API DateTime Validations in the ASP.NET Core MVC application
context. We can use the following validator methods to validate the date and time.
Must: For more complex scenarios. For instance, to ensure the date is not a
weekend
InclusiveBetween(): This can also be used for DateTime ranges.
LessThan() / LessThanOrEqualTo(): This can also be used for DateTime
values.
GreaterThan() / GreaterThanOrEqualTo(): Same as above, but for future
DateTime values.
Define the Model:
Let’s say we have an event scheduling application. Our model Event will have EventDate,
as shown below.
264
namespace FluentAPIDemo.Models
}
Set Up the Validator:
Now, let’s define a validator for this model:
using FluentValidation;
namespace FluentAPIDemo.Models
public EventValidator()
// For demonstration, let's assume we want events only within the next 30 days
// .InclusiveBetween(DateTime.Now, DateTime.Now.AddMonths(1))
}
Registering the Validator:
Add the following code in the Program class to register the Model and corresponding
validator.
//Registering Model and Validator to show the error message on the client side
builder.Services.AddTransient<IValidator<Event>, EventValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FluentAPIDemo.Controllers
return View();
266
[HttpPost]
if (!ModelState.IsValid)
return RedirectToAction("Success");
}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add ScheduleEvent.cshtml view
and copy and paste the following code.
@model FluentAPIDemo.Models.Event
@{
ViewData["Title"] = "ScheduleEvent";
<h1>Schedule Event</h1>
267
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="ScheduleEvent">
<div class="form-group">
</div>
<div class="form-group">
</div>
</form>
</div>
</div>
Now, run the application, and you should see the proper validation messages.
Conditional Validation using Fluent API in ASP.NET Core MVC:
Conditional Validation is one of FluentValidation’s powerful features. It allows us to perform
validation rules based on certain conditions. This can be achieved using
the When() and Unless() methods. The When and Unless methods in FluentValidation offer
conditional validation. They allow you to specify under which conditions a particular
validation rule should be executed.
When(): Apply rules conditionally based on properties or external factors.
Unless(): The opposite of When(), the rule is applied unless the condition is
true.
Imagine an e-commerce application where users can register and, while creating an
account, provide valid phone numbers to receive promotional offers.
Define the Model:
Here’s the Registration model:
namespace FluentAPIDemo.Models
268
}
Set Up the Validator:
Now, let’s define a validator for this model. We want the Phone Number only mandatory if
the WantsPromotions is “true.”
using FluentValidation;
namespace FluentAPIDemo.Models
public RegistrationValidator()
.WithMessage("Phone number should start with '+' and be longer than 10 digits.")
}
In this example:
The NotEmpty() validation for PhoneNumber only applies when
WantsPromotions is true.
The Must() validation for PhoneNumber applies unless the phone number is
null or empty.
Registering the Validator:
Add the following code in the Program class to register the Model and corresponding
validator.
//Registering Model and Validator to show the error message on the client side
builder.Services.AddTransient<IValidator<Registration>, RegistrationValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FluentAPIDemo.Controllers
return View();
[HttpPost]
if (!ModelState.IsValid)
270
return RedirectToAction("Success");
}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add Register.cshtml view and copy
and paste the following code.
@model FluentAPIDemo.Models.Registration
@{
ViewData["Title"] = "Register";
<h1>Register</h1>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Register">
<div class="form-group">
</div>
<div class="form-group">
</div>
<div class="form-group">
</div>
</form>
</div>
</div>
Now, run the application, and you should see the proper validation messages.
Fluent API Collections Validator in ASP.NET Core MVC:
Fluent API Provides Must and ForEach methods to validate collections and their individual
items in ASP.NET Core MVC. The Must method in FluentValidation offers custom validation
logic, while ForEach can be employed to validate individual items within a collection
Must(): Allows for more complex custom logic.
ForEach(): Apply a rule to each element in a collection.
Let’s say we have a Cart model representing a shopping cart containing a list of Product
items:
Define the Model:
Let’s define a model for Book and Order:
namespace FluentAPIDemo.Models
}
Set Up the Validators:
Product Validator:
Firstly, define a validator for individual Product items:
using FluentValidation;
namespace FluentAPIDemo.Models
public ProductValidator()
.GreaterThan(0)
}
273
}
Cart Validator:
Now, define a validator for the Cart, applying the ProductValidator to each item in the
Products collection and ensuring the cart contains at least one product:
using FluentValidation;
namespace FluentAPIDemo.Models
public CartValidator()
.NotEmpty()
.Must(ContainUniqueProducts)
if (!uniqueProductNames.Add(product.Name))
274
return false;
return true;
}
In this CartValidator, we have a Must validator that ensures all products in the cart are
unique, along with ForEach applying a ProductValidator to each product in the cart.
Registering the Validator:
Add the following code with the Program class to register Fluent API Validation and the
Model and corresponding validator.
//Registering Model and Validator to show the error message on the client side
builder.Services.AddTransient<IValidator<Cart>, CartValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FluentAPIDemo.Controllers
};
cart.Products = products;
return View(cart);
[HttpPost]
if (!ModelState.IsValid)
return RedirectToAction("Success");
}
Displaying Errors in Views:
276
You can use the tag helpers to bind and display errors. Add Checkout.cshtml view and
copy and paste the following code.
@model FluentAPIDemo.Models.Cart
@{
ViewData["Title"] = "Checkout";
<h1>Checkout</h1>
<label asp-for="@Model.Products[i].Quantity">Quantity</label>
<br/>
</form>
Now, run the application, and you should see the proper validation messages.
Fluent API Other Validators in ASP.NET Core MVC:
Let us understand the following Fluent API Validator Methods.
277
}
Set Up the Validator:
Now, let’s define a validator for this model:
using FluentValidation;
namespace FluentAPIDemo.Models
public UserRegistrationValidator()
{
278
//CascadeMode Determines how the validator should continue its validation process
//then the EmailAddress validation won't even be checked because of the CascadeMode.Stop.
ClassLevelCascadeMode = CascadeMode.Stop;
//WithMessage(): Allows customizing the error message for a specific validation rule.
.NotNull()
//the error message will refer to the property as "User Email Address".
.NotNull()
.EmailAddress()
//In the following rule, the validator checks if ConfirmPassword is equal to Password.
//If it is, the dependent rule for Password being not empty is checked.
.DependentRules(() =>
{
279
});
}
Registering the Validator:
Add the following code with the Program class to register Fluent API Validation and the
Model and corresponding validator.
//Registering Model and Validator to show the error message on the client side
builder.Services.AddTransient<IValidator<UserRegistration>, UserRegistrationValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FluentAPIDemo.Controllers
return View();
[HttpPost]
{
280
if (!ModelState.IsValid)
return RedirectToAction("Success");
}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add Register.cshtml view and copy
and paste the following code.
@model FluentAPIDemo.Models.UserRegistration
@{
ViewData["Title"] = "Register";
<h1>User Register</h1>
<hr />
<div class="row">
<div class="col-md-4">
<div class="form-group">
</div>
<div class="form-group">
</div>
<div class="form-group">
</div>
<div class="form-group">
</div>
</form>
</div>
</div>
282
namespace FluentAPIDemo.Models
}
Service
We need to create a service to Check Whether the Email ID Exists in the Database. So,
create a class file named UserService.cs and copy and paste the following code. As you
can see in the following code, the EmailExistsAsync method is used to check if an email is
registered.
namespace FluentAPIDemo.Models
};
if (emails.Contains(email))
return Task.FromResult(true);
else
284
return Task.FromResult(false);
}
Registering the UserService
Please add the following code to the Program.cs class file.
builder.Services.AddTransient<UserService>();
Set Up the Validator:
Now, let’s define a validator for the UserRegistration model. So, create a class file
named UserRegistrationValidator.cs and copy and paste the following code. As you can
see in the following code, we have used the MustAsync and CustomAsync validator
methods to write the custom logic, invoking the EmailExistsAsync method to validate
whether the Email is already registered.
using FluentValidation;
namespace FluentAPIDemo.Models
// Synchronous validation
.NotEmpty()
.WithMessage("Password is required.");
// Asynchronous validation
// MustAsync
285
// .NotEmpty()
// .EmailAddress()
// !await userService.EmailExistsAsync(email))
// Asynchronous validation
// CustomAsync
if (isUnique)
});
}
Explanation:
MustAsync: This method is also used for asynchronous operations. The
provided predicate should return true if validation succeeds and false if it fails.
In this case, it invokes
the userService.EmailExistsAsync(email) asynchronous method (this
method must be asynchronous) and should return true or false.
286
//builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddFluentValidationClientsideAdapters();
//Registering Model and Validator to show the error message on client side
builder.Services.AddTransient<IValidator<UserRegistration>, UserRegistrationValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows. Since we’re using asynchronous validation, ensure
that the ASP.NET Core MVC Controller action method is also asynchronous. With the
async validator in place, Auto Validation will not work, so we need to create an instance
of UserRegistrationValidator and invoke the ValidateAsync method by passing
the UserRegistration object as a parameter that needs to be validated.
using FluentAPIDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FluentAPIDemo.Controllers
{
287
_userService = userService;
return View();
[HttpPost]
if (!validationResult.IsValid)
ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
return RedirectToAction("Success");
{
288
}
Displaying Errors in Views:
Now, we need to display the validator error inside a view. We can use the tag helpers to
bind and display model validation errors here. So, add Register.cshtml view and copy and
paste the following code.
@model FluentAPIDemo.Models.UserRegistration
@{
ViewData["Title"] = "Register";
<h1>User Register</h1>
<hr />
<div class="row">
<div class="col-md-4">
</div>
</div>
</div>
</form>
</div>
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
Now, run the application, and you should see the Validation Messages as expected, as
shown in the below image:
290
The most important point is to use the MustAsync and CustomAsync Fluent API Validators
only when necessary. Performing too many I/O-bound operations during validation can slow
down your application. Secondly, with MustAsync and CustomAsync Fluent API Validators,
ASP.NET Core MVC Auto Validation will not work, and in that case, we need to do the
validation manually by calling the ValidateAsync method.
Differences Between Fluent API Sync vs. Async Validators in
ASP.NET Core MVC
Fluent API provides options for both synchronous and asynchronous validation. Here are
the key differences between using Fluent API’s synchronous and asynchronous validators
in ASP.NET Core:
Fluent API Synchronous Validators
Synchronous validators are the traditional type of validators that execute on the server in a
blocking manner as part of the request processing pipeline. This means the thread handling
the request will wait until the validation is complete before continuing.
Execution: Runs on the current thread and blocks it until the validation is
complete.
Usage Scenario: This is ideal for quick validations that do not involve I/O
operations, like checking string lengths, numeric ranges, or complex rules
that don’t require database or network calls.
API Methods: Typically, methods like Validate() are used.
Fluent API Asynchronous Validators
Asynchronous validators, on the other hand, allow the validation process to be non-
blocking. They are designed to perform I/O-bound tasks, such as database lookups or calls
to external services, without blocking the executing thread.
Execution: Runs without blocking the thread. It allows the thread to be used
for other tasks while waiting for the validation logic that involves I/O
operations to complete.
Usage Scenario: Best suited for validations that involve external data
sources, like database constraint checks, API calls, or any operations that
can be delayed without impacting the thread’s ability to serve other requests.
API Methods: Methods like ValidateAsync() are used.
Choosing Between Sync and Async Validators
The choice between synchronous and asynchronous validators typically depends on the
nature of the validation task:
Performance Considerations: Synchronous validators are generally
sufficient and simpler to implement for non-I/O-bound validations.
Asynchronous validators should be considered when the validation involves
I/O operations, which could potentially slow down the request processing if
done synchronously.
Resource Utilization: Asynchronous validators help better utilize server
resources, especially under load, as they free up the server thread to handle
other requests while waiting for I/O operations to complete.
291
First, define the registration form model. We will use the following RegistrationModel to
understand Fluent API Custom Validators in an ASP.NET Core MVC Application. Create a
class file named RegistrationModel.cs within the Models folder and copy and paste the
following code. As you can see, this model class contains three properties.
namespace FluentAPIDemo.Models
}
Create Custom Validator
You need to create a custom validator class to access the database and check for
Username and Email uniqueness. In the following class, we have hard-coded the data
source, but you must check it against the database table in real-time. So, create a class file
named UserValidator.cs within the Models folder and copy and paste the following code.
namespace FluentAPIDemo.Models
};
}
Explanation
BeUniqueUsername Method: This method uses LINQ’s Any() to determine if
any user in the list has the same username as the one provided. It returns
false if such a user exists, else false. The
StringComparison.OrdinalIgnoreCase makes the comparison case-
insensitive.
BeUniqueEmailAsync Method: Similarly, this method checks for the
uniqueness of the email. It is marked with async; to fulfill the method’s
signature as async, we return Task.FromResult(…).
294
Registering UserValidator:
Add the following code to the Program class to register the UserValidator with the built-in
dependency injection container.
builder.Services.AddTransient<UserValidator>();
Next, using FluentValidation, we need to apply these custom methods to validate the
registration form.
Creating Custom Validator Using Custom and CustomAsync Method in
ASP.NET Core:
Let us see how to implement the Custom Validation Logic using
the Custom and CustomAsync Validator method. So, create a class file
named RegistrationModelValidator.cs and copy and paste the following code. As you can
see, this class is inherited from the AbstractValidator<RegistrationModel> class. As you
can see, the UserValidator is injected into the RegistrationModelValidator, and using this
instance, we are calling the BeUniqueUsername and BeUniqueEmailAsync method using
the Custom and CustomAsync Validator method. The following code is self-explained, so
please go through the comment lines.
using FluentValidation;
namespace FluentAPIDemo.Models
.NotEmpty().WithMessage("Username is required.")
//Adds a custom validation rule where the uniqueness of the username is checked.
if (isExists)
});
//Ensures the email field is not empty, with a corresponding error message if the check fails
.NotEmpty().WithMessage("Email is required.")
//Validates that the input is in a proper email format, with a custom message if the format is
incorrect.
if (isExists)
});
}
Registering the Validator:
Add the following code to the Program class to register Fluent API Validation, the Model,
and the corresponding validator. As we are using Asynchronous Validation, we should
disable Auto validation.
//Enables integration between FluentValidation and ASP.NET MVC's automatic validation
pipeline.
//builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddFluentValidationClientsideAdapters();
//Registering Model and Validator to show the error message on client side
builder.Services.AddTransient<IValidator<RegistrationModel>,
RegistrationModelValidator>();
Use in Controller:
Finally, use the RegistrationModelValidator in your controller to handle user registration.
Modify the Home Controller as follows. Now that we have disabled Auto Validation, we need
to create an instance of lValidator and call the ValidateAsync method by passing
the RegistrationModel object to check whether the model validation is successful, as
shown in the code below.
using FluentAPIDemo.Models;
using FluentValidation;
using Microsoft.AspNetCore.Mvc;
namespace FluentAPIDemo.Controllers
297
_validator = validator;
return View();
[HttpPost]
if (!validationResult.IsValid)
ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
return View(model);
}
298
return RedirectToAction("Success");
}
Displaying Errors in Views:
We must bind the model properties in the Razor View and display validation messages
using ASP.NET Core’s built-in tag helpers. Add a view named Register.cshtml within the
Views/Home folder and copy and paste the following code.
@model FluentAPIDemo.Models.RegistrationModel
@{
ViewData["Title"] = "Register";
<h1>User Register</h1>
<hr />
<div class="row">
<div class="col-md-4">
</div>
</div>
</div>
</div>
</form>
</div>
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
300
With the above changes in place, run the application, provide the existing User Name and
Email, and click on the Create button to see if everything is working as expected, as shown
in the image below.
So, by using the Custom and CustomAsync methods, we can create validation rules
according to our business requirements, which are not provided by the built-in Fluent API
Validator.
Custom Validation using Must and MustAsync Fluent API Methods in
ASP.NET Core MVC
You can also write custom validation logic in an ASP.NET Core MVC Application using
Must and MustAsync Methods. These methods enable custom validation logic for specific
properties synchronously or asynchronously, respectively. These methods are useful when
the built-in Fluent API validators do not meet our business needs.
Let us understand the Use of Must and MustAsync with a Real-time Example. Let us create
an application for Venue Reservations. We will validate the Booking model for our venue
reservation system. The validation will ensure the booking date and time are valid, check for
venue availability, and use dependency injection to access external services.
Define the Model:
Let’s consider the following Booking model to understand Custom Validation using Must
and MustAsync Fluent API Methods. So, add a class file named Booking.cs within the
Models folder and then copy and paste the following code.
namespace FluentAPIDemo.Models
301
}
Booking Service Validator:
You need to create a custom service class to access the database, check if a venue is
available on a given date, and verify if a customer is registered and active. For simplicity, I
am providing basic in-memory implementations in the following class. So, create a class file
named BookingService.cs within the Models folder and copy and paste the following code.
using System;
using System.Collections.Generic;
using System.Linq;
namespace FluentAPIDemo.Models
};
302
{ "User1@Example.com", true },
};
VenueId = venueId;
Date = date;
303
}
The above BookingService class uses data encapsulated within lists and dictionaries to
manage and check states related to bookings and customer activity. The IsVenueAvailable
method ensures that venues cannot be double-booked for the same date, while the
IsCustomerActive method allows the system to verify the active status of a customer before
allowing them to make a booking.
Registering UserValidator:
Add the following code to the Program class to register the UserValidator with the built-in
dependency injection container.
builder.Services.AddTransient<BookingService>();
Next, using FluentValidation, we need to apply these custom methods to validate the
registration form.
Creating Custom Validator with Must and MustAsync Methods:
Let us see how to implement the Custom Validation Logic using the Must and MustAsync
Validator method. So, create a class file named BookingValidator.cs and copy and paste
the following code. As you can see, this class is inherited from the
AbstractValidator<Booking> class. As you can see, the BookingService is injected into the
BookingValidator, and using this instance, we are calling the IsVenueAvailable and
IsCustomerActive method using the Custom and CustomAsync Validator method. The
following code is self-explained, so please go through the comment lines.
using FluentValidation;
namespace FluentAPIDemo.Models
//Ensures the email field is not empty, with a corresponding error message if the check fails
.NotEmpty().WithMessage("Email is required.")
//Validates that the input is in a proper email format, with a custom message if the format is
incorrect.
await bookingService.IsCustomerActive(email))
}
Explanation
Must Method: The Must method is used for simple, CPU-bound checks like
date comparisons, which don’t require IO operations.
MustAsync Method: The MustAsync method is ideally used for IO-bound
operations such as database calls or API requests.
Registering the Validator:
305
Add the following code to the Program class to register the Model and corresponding
validator.
builder.Services.AddTransient<IValidator<Booking>, BookingValidator>();
Using Booking Validator in ASP.NET Core MVC Actions:
Let’s create an ASP.NET Core MVC controller that uses the Booking model and the
BookingValidator. Modify the Home Controller as follows. Now that we have disabled Auto
Validation, we need to create an instance of lValidator and call the ValidateAsync method
by passing the BookingModel object to check whether the model validation is successful,
as shown in the code below.
using FluentAPIDemo.Models;
using FluentValidation;
using Microsoft.AspNetCore.Mvc;
namespace FluentAPIDemo.Controllers
_validator = validator;
return View();
[HttpPost]
{
306
if (!validationResult.IsValid)
ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
return View(model);
return RedirectToAction("Success");
}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Modify the Register.cshtml within
the Views/Home folder as follows. The following view uses tag helpers to bind model
properties to form fields, automatically handles validation messages, and uses a partial view
to include client-side validation scripts.
@model FluentAPIDemo.Models.Booking
@{
ViewData["Title"] = "Booking";
307
<h1>User Register</h1>
<hr />
<div class="row">
<div class="col-md-4">
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
With the above changes in place, run the application, provide an inactive User Email,
provide the wrong Event Date, and click the Booking button to see if everything is working
as expected, as shown in the image below.
309
Reusable Validation Logic: When you have validation rules that are used
across different models or parts of your application, Fluent API can help you
centralize and reuse this logic without duplicating code. For instance, if
several models require a tax ID number to be validated in a specific format,
you can create a custom validator for this purpose.
Integration with ASP.NET Core’s Validation Pipeline: Custom Validators
created with Fluent API can easily integrate with ASP.NET Core’s model
validation system. This means they work well with the existing framework
features like model binding, producing validation results that automatically
translate into HTTP responses.
Custom Validation Responses: If you need to return detailed validation
responses or customize the format of the validation error messages, Fluent
API allows more flexibility compared to Data Annotations. This can be
particularly useful for APIs where you might want to provide more detailed
error information to the client.
When Should We Use Fluent API Custom, CustomAsync, Must, and
MustAsync Methods in ASP.NET Core MVC?
When deciding between using Custom, CustomAsync, Must, and MustAsync methods in
Fluent Validation for ASP.NET Core MVC Application, the choice largely depends on the
complexity of the validation requirement, whether the validation involves asynchronous
operations, and the level of control needed over the validation process.
Must:
This is a synchronous method that takes a predicate function. The function takes the
property value as a parameter and returns a boolean indicating whether the property meets
a specific condition.
Simple Condition: The validation rule is straightforward and can be
expressed as a simple condition or predicate.
Synchronous Operation: No asynchronous operation is needed (e.g., no
database or external service checks).
Example Scenarios: Validating that a number falls within a specific range;
checking if a string meets certain criteria (e.g., format or contains specific
characters).
MustAsync:
This is the asynchronous version of Must. It is useful when the validation rule needs to
perform asynchronous operations, such as database lookups or API calls, to determine
validity.
Asynchronous Condition: The validation involves asynchronous operations
such as database checks or calls to external APIs.
Non-blocking Requirement: It’s important to keep the operation non-
blocking, especially in web applications, to maintain responsiveness.
Example Scenarios: Checking if a username is available by querying a
database asynchronously. Verifying if a record exists or meets certain criteria
in an external API.
Custom:
This method is synchronous and allows for custom validation logic. You can define a
function that takes the property value and a validation context and then returns a
ValidationResult. This method is useful when the validation depends on multiple properties
of the model.
311
Let us understand How we can validate the order processing in an e-commerce application
using Fluent API Validation.
Define the Model:
Let’s consider the following Order model.
namespace FluentAPIDemo.Models
}
Set Up the Validator:
Following are the Validation Rules that we need to check while processing the order:
Validate a collection, ensuring each OrderItem has valid data and additional
custom rules.
Implementing Complex Validators:
using FluentValidation;
namespace FluentAPIDemo.Models
public OrderValidator()
.Matches("^ORD-\\d{6}$")
// Ensure that when a CouponCode is provided, the Amount exceeds a certain value (e.g.,
$100)
.GreaterThan(100)
});
.SetValidator(new OrderItemValidator());
314
public OrderItemValidator()
.NotEmpty()
.GreaterThan(0)
// Ensure that if UnitPrice is 0, Quantity is not more than 3 (e.g., max 3 free items per order)
.GreaterThan(0)
}
Registering the Validator:
Add the following code with the Program class to register Fluent API Validation and the
Model and corresponding validator.
//Enables integration between FluentValidation and ASP.NET MVC's automatic validation
pipeline.
315
builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddFluentValidationClientsideAdapters();
//Registering Model and Validator to show the error message on the client side
builder.Services.AddTransient<IValidator<Order>, OrderValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FluentAPIDemo.Controllers
};
decimal TotalAmount = 0;
{
316
OrderDate = DateTime.Now,
OrderId = "ORD-123456",
Amount = TotalAmount,
OrderItems = products
};
return View(order);
[HttpPost]
if (!ModelState.IsValid)
return RedirectToAction("Success");
}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add CreateOrder.cshtml view and
copy and paste the following code.
@model FluentAPIDemo.Models.Order
@{
ViewData["Title"] = "CreateOrder";
<h1>CreateOrder</h1>
<div class="row">
<div class="col-md-4">
<div class="form-group">
</div>
<div class="form-group">
</div>
318
<div class="form-group">
</div>
<div class="form-group">
</div>
<div class="form-group">
<span>
<b>@Model.OrderItems[i].ProductName</b>
</span>
<label asp-for="@Model.OrderItems[i].Quantity">Quantity:</label>
<span>
<b>@Model.OrderItems[i].Quantity</b>
319
</span>
<label asp-for="@Model.OrderItems[i].UnitPrice">UnitPrice:</label>
<span><b>@Model.OrderItems[i].UnitPrice</b></span>
</div>
<br />
</form>
</div>
</div>
Real-Time Example of Fluent API Validations in ASP.NET Core MVC:
Website for a Library
Let’s go through a real-world example. Imagine you’re building a website for a library, and
you need to validate data for adding a new book.
Define the Model:
We’ll start with a Book model:
namespace FluentAPIDemo.Models
}
Set Up the Validator:
Now, we’ll set up validation:
The ISBN should match a certain pattern.
The title should be mandatory and have a maximum length.
The author should also be mandatory.
The publish date shouldn’t be in the future.
The price should be positive.
Here’s the validator:
using FluentValidation;
namespace FluentAPIDemo.Models
public BookValidator()
.NotEmpty().WithMessage("ISBN is required.")
.NotEmpty().WithMessage("Title is required.")
.NotEmpty().WithMessage("Author is required.");
}
Understanding Matches(“^(97(8|9))?\\d{9}(\\d|X)$”):
The regular expression ^(97(8|9))?\d{9}(\d|X)$ is a pattern for validating ISBN-10 and ISBN-
13 numbers. Let’s break it down:
^: Matches the start of a string.
(97(8|9))?: This matches the prefixes ‘978’ or ‘979’ common for ISBN-13. The trailing “?”
means this part is optional, so it can also validate ISBN-10.
(97: Matches the string ’97’.
(8|9): Matches an ‘8’ or a ‘9’.
): Closes the group.
?: Specifies that the preceding element (in this case, the whole 97(8|9) group)
is optional.
\d{9}: Matches 9 digits. \d is a shorthand character class that matches any digit from 0 to 9.
(\d|X): Matches either a digit or the letter ‘X’. The ‘X’ is used in ISBN-10 numbers as a
checksum character.
$: Matches the end of a string.
So, the regex can match ISBN-13 numbers that start with ‘978’ or ‘979’, followed by 9 digits,
and ending with either a digit or ‘X’. It can also match ISBN-10 numbers, 9 digits followed by
a digit or ‘X’. Examples of valid matches would be:
9781234567890 (an example of ISBN-13)
123456789X (an example of ISBN-10 where ‘X’ is the checksum character)
This regex is versatile in matching both formats of the ISBN numbers, making it useful for
validation purposes.
Registering the Validator:
Add the following code with the Program class to register Fluent API Validation and the
Model and corresponding validator.
//Enables integration between FluentValidation and ASP.NET MVC's automatic validation
pipeline.
builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddFluentValidationClientsideAdapters();
//Registering Model and Validator to show the error message on the client side
322
builder.Services.AddTransient<IValidator<Book>, BookValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FluentAPIDemo.Controllers
return View();
[HttpPost]
if (!ModelState.IsValid)
return RedirectToAction("Success");
{
323
}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add AddBook.cshtml view and
copy and paste the following code.
@model FluentAPIDemo.Models.Book
@{
ViewData["Title"] = "AddBook";
<h1>AddBook</h1>
<hr />
<div class="row">
<div class="col-md-4">
<div class="form-group">
</div>
<div class="form-group">
</div>
<div class="form-group">
</div>
<div class="form-group">
</div>
<div class="form-group">
</div>
<div class="form-group">
</div>
</form>
</div>
</div>
325
This application for adding a new book to a library system shows how to use
FluentValidation to handle real-world validation scenarios in ASP.NET Core MVC, providing
a clean and organized way to enforce data integrity.
Real-Time Example of Fluent API Validations in ASP.NET Core MVC:
Workshop Registration
Let’s understand another real-world example using FluentValidation in ASP.NET Core
MVC. In this example, we’ll consider an online registration system for a training workshop.
Define the Model:
We will use the following WorkshopRegistration model:
namespace FluentAPIDemo.Models
}
Set Up the Validator:
For our WorkshopRegistration model, we want the following Validations.
FullName should be mandatory and have a specific length range.
Email should be a valid email format.
PhoneNumber should match a pattern.
DateOfBirth should ensure the registrant is above a certain age.
WorkshopTopic should be selected from a predefined list.
The following is our validator:
using FluentValidation;
namespace FluentAPIDemo.Models
public WorkshopRegistrationValidator()
.NotEmpty().WithMessage("Email is required.")
.Must(topic => new List<string> { "Web Development", "Data Science", "AI", "Design"
}.Contains(topic))
age--;
}
Registering the Validator:
Add the following code with the Program class to register Fluent API Validation and the
Model and corresponding validator.
//Enables integration between FluentValidation and ASP.NET MVC's automatic validation
pipeline.
builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddFluentValidationClientsideAdapters();
//Registering Model and Validator to show the error message on client side
builder.Services.AddTransient<IValidator<WorkshopRegistration>,
WorkshopRegistrationValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FluentAPIDemo.Controllers
return View();
[HttpPost]
if (!ModelState.IsValid)
return RedirectToAction("Success");
}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add Register.cshtml view and copy
and paste the following code.
@model FluentAPIDemo.Models.WorkshopRegistration
@{
ViewData["Title"] = "Register";
329
<h1>User Register</h1>
<hr />
<div class="row">
<div class="col-md-4">
<div class="form-group">
</div>
<div class="form-group">
</div>
<div class="form-group">
</div>
<div class="form-group">
</div>
<div class="form-group">
<select asp-for="WorkshopTopic">
<option value="AI">AI</option>
<option value="Design">Design</option>
<option value="Invalid">Invalid</option>
</select>
<span asp-validation-for="WorkshopTopic"></span>
</div>
<div class="form-group">
</div>
</form>
</div>
</div>
This example demonstrates how FluentValidation can handle various validation needs for
an online workshop registration system in an ASP.NET Core MVC application. It provides a
systematic and organized approach, ensuring a robust registration process.
Real-Time Example of Fluent API Validations in ASP.NET Core MVC:
Hotel Reservation
Let us understand another real-world example using FluentValidation in ASP.NET Core
MVC. Now, we will consider a scenario where a user can reserve a room at a hotel.
331
}
Set Up the Validator:
For our HotelReservation model:
GuestName should be mandatory and have a specific length.
Email should be a valid email format.
CheckInDate shouldn’t be in the past.
NumberOfNights should be positive and below a certain limit (e.g., a
maximum of 30 nights).
RoomType should be selected from a predefined list.
Here’s the validator:
using FluentValidation;
namespace FluentAPIDemo.Models
public HotelReservationValidator()
{
332
.NotEmpty().WithMessage("Email is required.")
}
Registering the Validator:
Add the following code with the Program class to register Fluent API Validation and the
Model and corresponding validator.
//Enables integration between FluentValidation and ASP.NET MVC's automatic validation
pipeline.
builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddFluentValidationClientsideAdapters();
//Registering Model and Validator to show the error message on the client side
builder.Services.AddTransient<IValidator<HotelReservation>,
HotelReservationValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FluentAPIDemo.Controllers
return View();
[HttpPost]
if (!ModelState.IsValid)
return RedirectToAction("Success");
334
}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add BookRoom.cshtml view and
copy and paste the following code.
@model FluentAPIDemo.Models.HotelReservation
@{
ViewData["Title"] = "BookRoom";
<h1>BookRoom</h1>
<hr />
<div class="row">
<div class="col-md-4">
<div class="form-group">
</div>
335
<div class="form-group">
</div>
<div class="form-group">
</div>
<div class="form-group">
</div>
<div class="form-group">
<select asp-for="RoomType">
<option value="Single">Single</option>
<option value="Double">Double</option>
<option value="Suite">Suite</option>
</select>
<span asp-validation-for="RoomType"></span>
</div>
336
<div class="form-group">
</div>
</form>
</div>
</div>
This example demonstrates how Fluent API Validation can effectively handle different
validation scenarios for a hotel room booking system in an ASP.NET Core MVC application.
It provides a structured and organized approach, ensuring a user-friendly booking
experience.
[Required]
[Range(0, 150)]
}
Fluent API (FluentValidation): Uses a chained method approach.
338
public PersonValidator()
}
Expressiveness:
Data Annotations: Limited expressiveness and can be less useful for
complex rules.
Fluent API (FluentValidation): Highly expressive, allowing more complex
validation rules in a more readable manner.
Extensibility:
Data Annotations: To create custom validation, you’d typically need to create
a new attribute class.
Fluent API (FluentValidation): Easily supports custom validation functions
without needing to create new classes or attributes.
Integration:
Data Annotations: Integrated out-of-the-box with ASP.NET Core model
binding.
Fluent API (FluentValidation): Requires setting up integration with ASP.NET
Core, but the FluentValidation library provides integration packages and
extensions to make this process easier.
Performance:
Data Annotations: Slightly faster for basic scenarios since there’s no
additional overhead.
Fluent API (FluentValidation): It might have a minor performance overhead
due to its more dynamic nature, but this isn’t very important in most real-world
scenarios.
Dependency:
Data Annotations: No external libraries are required; it’s built into .NET.
Fluent API (FluentValidation): Requires adding the FluentValidation NuGet
package.
Maintenance and Readability:
Data Annotations: The code can become cluttered for models with
numerous attributes, impacting readability.
Fluent API (FluentValidation): Centralizes validation rules, making it
potentially easier to read and manage, especially when complex rules exist.
339
While both mechanisms offer ways to ensure model validity, the right choice depends on
the specific requirements of your application. For simple validation needs, Data Annotations
might be better. However, if you need more complex validation logic, especially cross-
property validations, or prefer a fluent API approach, FluentValidation might be the better
choice.
When to use Data Annotations for Validations in ASP.NET Core?
Data Annotations in ASP.NET Core provide a straightforward way to define validation rules
on model properties using attributes. While they’re useful and convenient, they may not
always be the best choice for every scenario. Here’s when you might consider using Data
Annotations for validations:
Simplicity and Rapid Development: If you have simple validation
requirements and want to quickly add validations without setting up additional
libraries or configurations, Data Annotations are ideal.
Integrated with .NET: Since Data Annotations are part of the .NET platform,
there’s no need to introduce third-party libraries, which can be an advantage
in environments where adding external dependencies is discouraged.
Clear Visual Indication: By looking at the model properties, developers can
easily identify the validation rules applied due to the presence of attributes.
This can help in quickly understanding the expected format or constraints on
data.
Client-Side Validation: When combined with ASP.NET Core MVC tag
helpers in Razor views, certain Data Annotations can automatically generate
client-side validation. For example, if you have an [Required] attribute on a
model property, the generated form field will have client-side validation,
ensuring the field isn’t left empty.
Integrated with Entity Framework Core (EF Core): If you’re using EF Core
for data access, some Data Annotations (like [Required], [StringLength]) will
also influence the database schema during migrations or database creation.
Built-in Validations: For common validation scenarios like checking for
required fields, maximum string length, patterns (using [RegularExpression]),
or range validations, Data Annotations offer out-of-the-box attributes, so
there’s no need to write custom logic.
When NOT to use Data Annotations?
Complex Validation Rules: If you have intricate validation scenarios,
especially those that involve multiple properties (cross-property validation),
Data Annotations can become cumbersome. FluentValidation or other
libraries might be more appropriate in this case.
Separation of Concerns: If you prefer to keep your validation logic separate
from your model or entity classes, using a library like FluentValidation, which
defines validation in separate classes, can be beneficial.
Customization and Extensibility: While you can create custom validation
attributes with Data Annotations, doing so might feel less natural or more
verbose than library customization options like FluentValidation.
Reusability: If you reuse certain complex validation logic across different
models or parts of the application, external validation libraries might provide a
more modular approach.
So, Data Annotations are a great choice for straightforward validation scenarios, especially
when rapid development and simplicity are priorities. However, considering external
340
libraries like FluentValidation might be beneficial for more advanced or complex validation
needs.
When to use Fluent API for Validations in ASP.NET Core?
When referring to the “Fluent API” in the context of validations in ASP.NET Core, it’s often
associated with the FluentValidation library. FluentValidation offers a powerful, expressive,
and chainable API for defining validation rules. Here’s when you might consider using
FluentValidation (Fluent API) for validations:
Complex Validation Scenarios: FluentValidation excels in scenarios
requiring intricate validation logic. It easily supports cross-property
validations, conditional validations, and collection validations, among others.
Separation of Concerns: With FluentValidation, validation rules are defined
in separate validator classes. This ensures that the model or entity classes
remain clean and validation logic is decoupled, promoting better separation of
concerns.
Reusability and Modularity: With FluentValidation, you can define reusable
validation rules (as methods or extensions) and apply them across multiple
models or properties.
Custom Validation Logic: Creating custom validators in FluentValidation is
straightforward, and the fluent API makes it intuitive to apply custom logic.
Chainable Validation Rules: FluentValidation allows you to fluently chain
multiple rules for a single property, making the rules more readable and
expressive.
Dynamic Error Messages: FluentValidation allows the dynamic generation
of error messages based on property values, making it flexible for scenarios
where static error messages (as in Data Annotations) are insufficient.
Integration with External Data Sources: If your validation requires
integration with external data sources (like databases or APIs),
FluentValidation provides asynchronous validators and the ability to inject
dependencies into validators, which can be highly beneficial.
Testing and Maintenance: Since FluentValidation rules are defined in
separate classes, it becomes easier to write unit tests for these validators,
ensuring that validation logic is correct and making maintenance easier.
Granular Control Over Validation Process: FluentValidation provides more
control over the validation process, like choosing when to stop rule execution
after the first failure, defining the severity of failures, and more.
When NOT to use FluentValidation?
Simplicity Needed: For very basic validation scenarios where you want to
check if a field is required or if a string length is within a certain range,
introducing FluentValidation might be overkill. Data Annotations could be
sufficient in such cases.
Avoiding External Dependencies: If you’re trying to minimize external
libraries or dependencies, then introducing FluentValidation might not be
suitable.
Tight Integration with Entity Framework Core (EF Core): While Data
Annotations used for validation can also impact EF Core configurations (like
database schema generation), FluentValidation is purely for validation and
doesn’t affect EF Core configurations.
341
So, FluentValidation (Fluent API) is a powerful tool for validations in ASP.NET Core,
especially when dealing with complex scenarios, seeking a clear separation of concerns, or
when more control over the validation process is needed. However, built-in mechanisms like
Data Annotations might be more appropriate for simple validations.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:17 / 03:1710 Sec
Cookies work as follows:
First, the client (Web Browser) will send the HTTP Request to the Server.
342
Once the server receives the request, it will process the request and generate
the response. Along with the generated response, it will create the cookie and
send it to the client within the response header. You can also set multiple
cookies as per your requirement. For each cookie, it will add one Set-Cookie
in the response header.
Once the client (web browser) receives the response, from the next request
onwards (as long as the cookie is not expired and not deleted), the client will
send the HTTP Request along with the cookie in the request header. The
client will send one Cookie in the request header containing all cookies data.
Once the server receives the request, it will check the cookie and accordingly
process the request.
Types of Cookies:
There are two types of Cookies. They are as follows:
Persistent Cookies: Persistent cookies are long-term cookies that can be
stored across multiple sessions. They retain information such as login
credentials, allowing users to have a personalized experience when they
revisit a website.
Non-Persistent Cookies: Non-Persistent Cookies are temporary cookies
stored on the user’s computer while browsing a website. They are typically
used to maintain a session state and are destroyed when the client closes
their browser or navigates away from the web page.
Note: In this article, I will show the example using Persistent Cookies. In our next article, I
will discuss the example using Non-Persistent Cookies and then discuss the differences
between them.
How do you Write, Read, and Delete Cookies in ASP.NET Core MVC?
ASP.NET Core MVC provides built-in support for working with cookies to enable various
scenarios, such as authentication, session management, and tracking user preferences.
Writing a Cookie in ASP.NET Core MVC:
To create a cookie in ASP.NET Core MVC, create an instance of the CookieOptions class,
set the expiry date using the Expires property, and then add the cookie
to Response.Cookies collection using the Append method with name, value, and
CookieOptions object as parameters as follows.
CookieOptions options = new CookieOptions();
options.Expires = DateTime.Now.AddDays(7);
Response.Cookies.Append(“UserId”, “1234567”, options);
Response.Cookies.Append(“UserName”, “pranaya@dotnettutotials.net”, options);
The CookieOptions class provides additional properties that can be set when creating a
cookie, including:
1. Domain: Gets or sets the domain to associate the cookie with.
2. Expiration time: Gets or sets the expiration date and time for the cookie.
3. Path: Gets or sets the cookie path. Creates a default cookie with a path of ‘/.’
4. Secure: Gets or sets a value indicating whether to transmit the cookie using
Secure Sockets Layer (SSL) over HTTPS.
5. HttpOnly: Gets or sets a value that indicates whether a cookie is accessible
by client-side script. If its value is set to true, then it can’t be accessed from
JavaScript.
6. MaxAge: Gets or sets the maximum age for the cookie.
343
namespace SampleMVCWeb.Controllers
//Let us assume the User is logged in and we need to store the user information in the cookie
options.Expires = DateTime.Now.AddDays(7);
return View();
344
return Message;
return View();
Response.Cookies.Delete(CookieUserId);
Response.Cookies.Delete(CookieUserName);
}
How do you Access the Cookie Object in a View?
To use Cookies inside a View, we need to inject the IHttpContextAccessor service into our
view and use it to get the HttpContext and Request objects from that. For this, we need to
345
add the following two statements in our view. The first statement adds the namespace and
the second statement injects the IHttpContextAccessor. Using this HttpContextAccessor,
we can access the HttpContext and Session object.
@using Microsoft.AspNetCore.Http;
@inject IHttpContextAccessor HttpContextAccessor
Modifying the Index.cshtml view:
@using Microsoft.AspNetCore.Http;
@{
<div class="text-left">
<br />
</div>
Modifying the Privacy.cshtml view:
@using Microsoft.AspNetCore.Http;
@{
<h1>@ViewData["Title"]</h1>
<div class="text-left">
<br />
</div>
If you run the application with the above changes, you will get the following exception: We
use the HttpContextAccessor service but have not yet configured it into the built-in
IOC container.
Now, we need to configure the HttpContextAccessor service within the Program class. The
following code does the same.
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
So, modify the Main method of the Program class as follows:
namespace SampleMVCWeb
builder.Services.AddControllersWithViews();
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
if (!app.Environment.IsDevelopment())
{
347
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios,
see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
}
Now, run the application, and by default, it will execute the Index Action Method of the
Home controller. Then, it will render the Index view where you can see the cookie data, as
shown in the image below.
348
If you visit the Privacy page, it will render the cookie data, as shown in the image below.
349
Now, if you go to the Network tab and inspect the request using the browser developer tool,
you will see that along with the request, the web browser is now sending the cookie in the
request header, as shown in the image below. This will happen for each and every request
sent from the client to the server until the cookie is not expired or deleted.
If you visit the About page, it will also render the session data, as shown in the image
below.
350
If you visit the DeleteCookie page, it will delete the cookies, and you will get the following
message, as shown in the image below.
Now, if you verify the response header, you will see that the cookie values are being
deleted, as shown in the image below.
If you visit the About and Privacy page, you will not see the cookie data shown in the image
below.
351
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:12 / 03:1710 Sec
builder.Services.AddDataProtection();
How Do We Encrypt Custom Cookies in ASP.NET Core MVC?
If you are creating custom cookies and want to ensure they are encrypted, you need to use
the Data Protection API manually. Let us understand this with an example. Let us first
create a class file named MyCookieService.cs within the Models folder and then copy and
paste the following code:
using Microsoft.AspNetCore.DataProtection;
namespace SampleMVCWeb.Models
//MyCookieProtector must be a unique string value for each Data Protection Provider
_protector = dataProtectionProvider.CreateProtector("MyCookieProtector");
return _protector.Protect(cookieValue);
{
354
return _protector.Unprotect(protectedCookieValue);
}
In ASP.NET Core, the Data Protection API provides CreateProtector, Protect, and
Unprotect methods, which we can use to protect the cookies. The use of each method is as
follows:
CreateProtector Method: The CreateProtector method creates an instance
of IDataProtector, which is then used to encrypt and decrypt data. You need
to specify a purpose string when calling CreateProtector, which acts as a way
to isolate protectors from each other.
Protect Method: The Protect method is used to encrypt data. You need to
pass the plaintext data to this method, which returns the encrypted data.
Unprotect Method: The Unprotect method decrypts encrypted data. It will
throw an exception if the data cannot be decrypted (e.g., if it has been
tampered with or the protector was created for a different purpose).
Storing Cookies
When storing cookies in the response header, use the encrypted value. First, encrypt the
value using DataProtector and then store the encrypted value in the Cookies. The syntax to
encrypt the cookie is given below:
//Encrypt the Value using Data Protection API
Next, we need to configure the MyCookieService within the built-in dependency injection
container. So, please add the following code to the Program class. Here, we need to pass
the concrete class name to the AddSingleton method.
builder.Services.AddSingleton<MyCookieService>();
Modifying Home Controller:
Next, modify the Home Controller as follows. First, we inject the MyCookieService instance
through the constructor, and then, using this MyCookieService instance, we encrypt and
decrypt the cookies. Further, we have stored the retrieved cookie values in the ViewBag,
which will be displayed in the UI. This is because we don’t want to write the logic to fetch
the encrypted data from the cookie and decrypt the value in the View file.
using Microsoft.AspNetCore.Mvc;
using SampleMVCWeb.Models;
namespace SampleMVCWeb.Controllers
MyCookieService _myCookieService;
_myCookieService = myCookieService;
//Let us assume the User is logged in and we need to store the user information in the cookie
HttpOnly = true,
356
Secure = true,
SameSite = SameSiteMode.Strict,
Expires = DateTime.Now.AddDays(7)
};
//it will also fetch the data from the Cookies Response Header
if (encryptedUserNameValue != null)
ViewBag.UserName = _myCookieService.Unprotect(encryptedUserNameValue);
};
if (encryptedUserIdValue != null)
ViewBag.UserId = Convert.ToInt32(_myCookieService.Unprotect(encryptedUserIdValue));
}
357
return View();
try
if (encryptedUserNameValue != null)
UserName = _myCookieService.Unprotect(encryptedUserNameValue);
};
if (encryptedUserIdValue != null)
UserId = Convert.ToInt32(_myCookieService.Unprotect(encryptedUserIdValue));
return Message;
358
if (encryptedUserNameValue != null)
//Store the Decrypted Value in the ViewBag which we will display in the UI
ViewBag.UserName = _myCookieService.Unprotect(encryptedUserNameValue);
};
if (encryptedUserIdValue != null)
//Store the Decrypted Value in the ViewBag which we will display in the UI
ViewBag.UserId = Convert.ToInt32(_myCookieService.Unprotect(encryptedUserIdValue));
return View();
}
359
// Delete the Cookie From the Response Header, i.e., from the Browser.
Response.Cookies.Delete(CookieUserId);
Response.Cookies.Delete(CookieUserName);
}
CookieOptions Class in ASP.NET Core MVC
HttpOnly: The HttpOnly property specifies whether the cookie should be
accessible only by the server. When set to true, the cookie cannot be
accessed through client-side scripts. This is an important security feature that
helps prevent cross-site scripting (XSS) attacks by not allowing client-side
script access to the cookie. Default Value: false
Secure: The Secure property indicates that the cookie should be sent only
over HTTPS. This security measure helps prevent attackers from intercepting
the cookie in a man-in-the-middle attack. Default Value: false
SameSite: The SameSite property controls the same-site and cross-site
cookie-sending behavior. It helps to protect against cross-site request forgery
(CSRF) attacks. Default Value: SameSiteMode.Lax (Note: This can vary
based on the version of ASP.NET Core and browser behavior.)
Expires: The Expires property sets the expiration date and time for the
cookie. After this time, the cookie will no longer be sent to the server. This
property is essential for controlling the lifecycle of your cookie. Default Value:
null. By default, cookies are session cookies, which means they last only as
long as the browser session.
Modifying Index.cshtml View
@{
<div class="text-left">
<br />
</div>
Modifying Privacy.cshtml View
@{
<h1>@ViewData["Title"]</h1>
<div class="text-left">
<br />
</div>
With the above changes in place, run the application, and now you should see the cookies
are transmitted over the network in encrypted format, as shown in the below image:
{
361
// Cookie settings
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.Expiration = TimeSpan.FromDays(1);
});
Now, you need to use the other version of the Append method to set the cookie, which does
not take the cookie option object. So, modify the Index action method of the Home
Controller class as follows:
public IActionResult Index()
//Let us assume the User is logged in and we need to store the user information in the cookie
//{
// HttpOnly = true,
// Secure = true,
// SameSite = SameSiteMode.Strict,
// Expires = DateTime.Now.AddDays(7)
//};
Response.Cookies.Append(CookieUserId, encryptedUserId);
Response.Cookies.Append(CookieUserName, encryptedUserName);
//it will also fetch the data from the Cookies Response Header
if (encryptedUserNameValue != null)
ViewBag.UserName = _myCookieService.Unprotect(encryptedUserNameValue);
};
if (encryptedUserIdValue != null)
ViewBag.UserId = Convert.ToInt32(_myCookieService.Unprotect(encryptedUserIdValue));
return View();
}
After making the above changes, run the application, and you should see that it works as
expected.
When Should We Use Encrypted Cookies in ASP.NET Core MVC?
Encrypted cookies in ASP.NET Core MVC are useful in scenarios where you need to store
sensitive information in cookies while ensuring that it remains secure and protected from
tampering or unauthorized access. The following are some of the specific scenarios when
you might want to use encrypted cookies:
Storing Sensitive Information: Encrypting cookies adds an extra layer of
security if you need to store sensitive data, such as personal identifiers or
preferences, that should not be exposed or manipulated by the client.
363
Preventing Tampering: Encrypted cookies help ensure that the data stored
within them cannot be tampered with. Since the cookie content is encrypted,
any modifications made by the client would invalidate the cookie.
Enhancing Application Security: Encrypted cookies are part of a broader
approach to securing web applications. They should be used alongside other
security practices like HTTPS, secure cookie attributes, and proper session
management.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem TV
Definition:
Persistent Cookies: Persistent cookies, also known as permanent cookies,
remain stored on the client’s device even after the browser is closed. They
have an explicit expiration date set, and they persist until that date is reached
or until they are manually deleted.
Non-Persistent Cookies: Non-persistent cookies, also known as session
cookies, are temporary and are deleted when the browser session ends. They
do not have a specific expiration date set.
Lifespan and Expiration
Persistent Cookies: Have a specific expiration date set. Remain on the
user’s device after the browser is closed until it expires or is deleted.
364
};
Non-persistent cookies, also known as session cookies, are deleted when the user closes
the browser. They are ideal for:
Session Management: Maintaining user state within a single session, like
user authentication in a secure environment. The session ends once the user
closes the browser, and the cookie is discarded, which can be a security
benefit.
Sensitive Data: Temporarily storing sensitive information that should not
persist beyond the current session. This reduces the risk of unauthorized
access to sensitive data.
State Management for Single Visit: Used to store temporary information like
form data during a multi-step process within a single visit.
SecurePolicy: Determines the policy for transmitting the cookie over secure channels like
HTTPS, and you can set the value of this property as follows:
options.Cookie.SecurePolicy = CookieSecurePolicy.Always; (cookie sent
only over HTTPS).
options.Cookie.SecurePolicy = CookieSecurePolicy.None; (cookie sent
over HTTP or HTTPS).
options.Cookie.SecurePolicy =
CookieSecurePolicy.SameAsRequest; (cookie sent using the same
protocol as the request).
So, modify the Main method of the Program class as follows:
namespace SampleMVCWeb
builder.Services.AddControllersWithViews();
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession(options =>
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.IOTimeout = TimeSpan.FromSeconds(10);
options.Cookie.Name = ".MySampleMVCWeb.Session";
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
options.Cookie.Path = "/";
370
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
if (!app.Environment.IsDevelopment())
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios,
see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
//It should and must be configured after UseRouting and before MapControllerRoute
app.UseSession();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
}
371
}
Important Note: If two requests simultaneously attempt to modify the contents of a session,
the last request overrides the first. When two requests seek to modify different session
values, the last request may override session changes made by the first.
Set and Get Session Values in ASP.NET Core MVC
In ASP.NET Core, the ISession interface provides methods to work with session data in a
key-value pair format. These methods allow us to get and set session values easily. These
methods are implemented as extension methods provided for common data types like string
and int. The following is the list of ISession extension methods:
Get(ISession, String): Retrieves a value from the session as a byte array. If
the key does not exist, it returns null.
GetInt32(ISession, String): This method tries to read an integer value from
the session. If the key is not found or the value cannot be converted to an int,
it returns null.
GetString(ISession, String): This method retrieves a string value from the
session. If the key does not exist, it returns null.
SetInt32(ISession, String, Int32): Sets an integer value in the session.
SetString(ISession, String, String): Sets a string value in the session.
When using these methods, keep in mind:
Session Start: The session must be started before you can set or get values.
This is typically done automatically in ASP.NET Core MVC applications.
Serialization: The SetInt32 and SetString methods handle the serialization of
the given values into the session. On the other hand, GetInt32 and GetString
handle deserialization.
Types: These methods are convenient for working with simple data types like
int and string. For more complex types, you would need to handle
serialization and deserialization manually.
Lifetime: Session data is temporary and should not be used for long-term
storage. It’s typically cleared at the end of the session or when the browser is
closed.
Note: Don’t store sensitive data in the session state. The user might not close the browser
and clear the session cookie. Some browsers maintain valid session cookies across
browser windows. A session might not be restricted to a single user. The next user might
continue to browse the app with the same session cookie.
Modifying the Home Controller:
Next, modify the HomeController as follows. As you can see, we have created two constant
variables to store the session keys. We store user data in the session object within the
Index Action method. Further, if you notice, we are accessing the session data from the
About action method.
using Microsoft.AspNetCore.Mvc;
namespace SampleMVCWeb.Controllers
//Let assume the User is logged in and we need to store the user information in the session
HttpContext.Session.SetString(SessionUserName, "pranaya@dotnettutotials.net");
HttpContext.Session.SetInt32(SessionUserId, 1234567);
return View();
return Message;
return View();
}
373
}
Note: After 10 seconds, the cookie expires, and you will not get the data once the cookie
expires.
How do you Access the Session Object in a View?
To use the Session inside a View, we need to inject the IHttpContextAccessor object into
our view, and then we need to use the IHttpContextAccessor object to get the HttpContext
and Session. For this, we need to add the following two statements in our view. The first
statement adds the namespace and the second statement injects the
IHttpContextAccessor, and using this HttpContextAccessor; we can access the HttpContext
and Session object.
@using Microsoft.AspNetCore.Http;
@inject IHttpContextAccessor HttpContextAccessor
Modifying the Index.cshtml view:
@using Microsoft.AspNetCore.Http;
@{
<div class="text-left">
<b>User Name:</b>
@HttpContextAccessor?.HttpContext?.Session.GetString("_UserName")
<br />
</div>
Modifying the Privacy.cshtml view:
@using Microsoft.AspNetCore.Http;
@{
}
374
<h1>@ViewData["Title"]</h1>
<div class="text-left">
<b>User Name:</b>
@HttpContextAccessor?.HttpContext?.Session.GetString("_UserName")
<br />
</div>
You will get the following exception if you run the application with the above changes. This
is because we use the HttpContextAccessor service but have not yet configured this service
into the built-in IOC container.
Now, we need to configure the HttpContextAccessor service within the Main method of the
Program class. The following piece of code does the same.
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
So, modify the Main method of the Program class as follows:
namespace SampleMVCWeb
builder.Services.AddControllersWithViews();
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession(options =>
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.IOTimeout = TimeSpan.FromSeconds(10);
options.Cookie.Name = ".MySampleMVCWeb.Session";
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
options.Cookie.Path = "/";
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
if (!app.Environment.IsDevelopment())
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios,
see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
376
app.UseAuthorization();
//It should and must be configured after UseRouting and before MapControllerRoute
app.UseSession();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
}
Now, run the application, and by default, it will execute the Index Action Method of the
Home Controller and set the session values. Then, it will render the Index view where you
can see the session data, as shown in the image below.
When it returns the response to the client (Web Browser), the Web Server also includes the
generated SessionId in the response header using the Set-Cookie. To confirm this, open a
different web browser, open the developer tool, and then issue a request to the Index action
method. Then, go to the Network tab, inspect the request, and go to the Response header.
You will see the Set-Cookie property where the web server sends the SessionId and its
unique value. This SessionId value is going to be unique per session.
377
From the next HTTP Request onwards, the Client (Browser) will send the SessionId in the
request header using Cookie. Within 10 seconds, if you visit the Privacy page, it will also
render the session data, as shown in the image below.
If you check the Network tab and inspect the request, you will see that one cookie is being
sent from the client to the web server using the Request header, as shown in the image
below. This happens in each and every request from the client to the server as long as the
session has not expired.
378
Again, within 10 seconds, if you visit the About page, it will also render the session data, as
shown in the image below.
Now, if you visit the Privacy page (Not the Index page where we store the session data)
after 10 seconds, you will not get the session data as shown in the image below.
In this case, it also sends the cookie in the HTTP request header, which is expired. If you
want, access the Index Action method, reset the Session timeout, and then the same
SessionId will be used.
379
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:13 / 03:1710 Sec
Implementing in-memory or in-proc (in-process) sessions in ASP.NET Core MVC involves a
few key steps. The in-memory session storage is suitable for single-server scenarios where
you don’t need to persist data across server restarts.
Configure Services for Session:
In your Program.cs file (since ASP.NET Core 6 no longer uses Startup.cs by default),
configure the session services. This includes adding a distributed memory cache and
session services.
// Add Session services to the container.
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession(options =>
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
Use Session Middleware
In the same Program.cs file, ensure the session middleware is added to the application’s
request pipeline. This should be done before routing and endpoints middleware.
382
app.UseRouting();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Using Sessions in Controllers
In your controllers, you can access the session via the HttpContext.
using Microsoft.AspNetCore.Mvc;
namespace SampleMVCWeb.Controllers
HttpContext.Session.SetString("MySessionKey", "MySessionValue");
return View();
ViewBag.SessionValue = value;
return View();
}
383
}
Implementing Distributed (Out-Proc) Sessions in ASP.NET Core
MVC:
Distributed sessions are suitable for applications running on multiple servers. The session
state is stored in an external storage like Redis or SQL Server.
Implementing distributed (out-of-process) sessions in ASP.NET Core MVC to store session
data in SQL Server involves several steps. This approach is useful when you have a web
farm scenario (multiple servers hosting your application), and you want to ensure session
data is consistent across servers.
Add Required Packages
First, add the necessary NuGet packages to your project:
Microsoft.EntityFrameworkCore.SqlServer for Entity Framework Core with
SQL Server.
Microsoft.Extensions.Caching.SqlServer for SQL Server distributed
caching.
Configure SQL Server for Session Storage
First, create a database in the SQL Server database with the SessionDB, where we will
store the session data.
Before proceeding, ensure you have a SQL Server database available. You’ll need to
create a table to store session data. The dotnet sql-cache command is part of the .NET
Core CLI, specifically designed for working with distributed SQL server cache. If you’re
unable to find this command, it’s likely that the necessary tool is not installed on your
system. To install it, you should run the following command:
dotnet tool install –global dotnet-sql-cache
The sql-cache create tool can be used to create this table. Run the following command in
your terminal, replacing the placeholders with your database details. The following
command creates a table named MySessions in the specified database to store session
data.
dotnet sql-cache create “Data Source=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Initial Catalog=SessionDB;Integrated
Security=True;TrustServerCertificate=True” dbo MySessions
Once the above command is executed successfully, the MySessions table must be created
in the SessionDB database with the following structure:
384
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
},
"AllowedHosts": "*",
"ConnectionStrings": {
"EFCoreDBConnection": "Server=LAPTOP-6P5NK25R\\
SQLSERVER2022DEV;Database=SessionDB;Trusted_Connection=True;TrustServerCertific
ate=True;"
}
Step 4: Configure DbContext
Create a DbContext class for the application. So, add a class
named EFCoreDBContext.cs within the Models folder and then copy and paste the
following code.
using Microsoft.EntityFrameworkCore;
namespace SampleMVCWeb.Models
}
386
}
Configure Services for Distributed Cache
In your Program.cs file, configure Entity Framework Core, and the distributed SQL Server
cache. In this configuration, replace EFCoreDbContext and EFCoreDBConnection with your
specific Entity Framework Core context and connection string.
// Add Session Services to the Container.
builder.Services.AddDbContext<EFCoreDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("EFCoreDBConnection"))
);
builder.Services.AddDistributedSqlServerCache(options =>
options.ConnectionString =
builder.Configuration.GetConnectionString("EFCoreDBConnection");
options.SchemaName = "dbo";
options.TableName = "MySessions";
});
builder.Services.AddSession(options =>
options.IdleTimeout = TimeSpan.FromMinutes(30);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
387
});
Note: The AddDistributedSqlServerCache method in ASP.NET Core is used to set up a
distributed cache that is backed by a SQL Server database. This method is part of the
Microsoft.Extensions.DependencyInjection namespace. Distributed caching is a method of
caching where the data is stored across multiple servers, which can help in maintaining
consistency and performance in web applications, especially those deployed in a load-
balanced, multi-server environment.
Configure Session Middleware
In the same Program.cs file, add the session middleware to the application’s request
pipeline.
app.UseRouting();
app.UseAuthorization();
//It should and must be configured after UseRouting and before MapControllerRoute
app.UseSession();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Using Sessions in Controllers
You can now use sessions in your controllers just like you would with in-memory sessions:
using Microsoft.AspNetCore.Mvc;
namespace SampleMVCWeb.Controllers
HttpContext.Session.SetInt32("UserId", 123456);
HttpContext.Session.SetString("UserName", "info@dotnettutorials.net");
388
return View();
ViewBag.UserName = sessionUserName;
ViewBag.UserId = sessionUserId;
return View();
}
Next, modify the Index.cshtml view as follows:
@{
<div class="text-left">
<h2>Index Page</h2>
</div>
Next, modify the Privacy.cshtml view as follows:
@{
<h1>@ViewData["Title"]</h1>
<div class="text-left">
389
<b>User Name:</b>@ViewBag.UserName
<br />
</div>
Now, run the application and visit the Index action method where we are saving the session
data. Now, once you access the Home Page, go to the database and check the MySession
data, and you should see the following:
Note: The AbsoluteExpiration column is not used by the default SQL Server distributed
cache implementation in ASP.NET Core. This column is part of the schema for compatibility
with other potential caching implementations that might use absolute expiration. However,
in the default setup with ASP.NET Core and SQL Server, this column is left null and does
not play a role in the expiration mechanism.
How do you delete the distributed session data from the SQL Server
database?
Managing and deleting expired session data from the SQL Server database in an ASP.NET
Core MVC application, especially when using distributed sessions, is important for
maintaining performance and efficient data management.
SQL Server doesn’t automatically clean up expired sessions. Therefore, you need to
implement a mechanism to remove old session data periodically. You can create an SQL
Server Agent Job or a scheduled task that runs an SQL script to delete expired sessions.
Here is an example SQL script that you could run:
DELETE FROM [YourSessionTable] WHERE ExpiresAt < GETUTCDATE();
Replace [YourSessionTable] with the name of your session table. This script deletes all
sessions that have expired according to the ExpiresAt column.
Key Differences Between In-Proc and Out-Proc Sessions:
Location of Data: In-Proc stores data in server memory, and Out-Proc stores
data in a separate system.
Data Persistence: Out-Proc can maintain data persistence across server
restarts, whereas In-Proc cannot.
Resource Utilization: In-Proc uses server memory, which can be a
constraint. Out-Proc uses external resources, which adds to network
overhead but frees up server memory.
Complexity: Implementing and maintaining a distributed session state is
more complex than in-memory sessions.
390
Cookies: Limited in size (typically 4KB per cookie). Not suitable for storing
large amounts of data.
Sessions: Can handle larger amounts of data as they are stored on the
server. The limit depends on the server’s memory and configuration.
Use Cases
Cookies: Cookies are commonly used to store user preferences,
authentication tokens, and other small pieces of data that need to persist
across requests.
Sessions: Sessions are used to store data such as user profiles, shopping
cart contents, or any other data that needs to persist across multiple requests
but is too large or sensitive to store in a cookie.
Comparison Between Cookies and Sessions
Storage Location: Cookies are stored on the client side, while sessions use
server-side storage.
Data Security: Sessions are generally more secure as the data is stored on
the server.
Capacity: Sessions can handle more data as they are not limited by browser
restrictions on cookie size.
Persistence: Cookies can persist across browser sessions if configured, while
session data is typically tied to a single browser session.
Scalability: Cookies are more scalable as they do not put the load on the
server for data storage. Sessions can be more challenging to scale in load-
balanced environments unless a distributed session management system is
used.
When to Use Each
Use cookies for small pieces of data that need to persist across browser
sessions and are not sensitive.
Use sessions for larger or sensitive data that should not be stored on the
client side and for data that only needs to persist during a single browser
session.
Considerations for Choosing
Data Sensitivity: Use sessions for sensitive data to avoid client-side
tampering.
Data Size: For large data, prefer sessions. Cookies are suitable for small bits
of data.
Persistence Need: If you need data to persist beyond a user session, cookies
are a better option.
Performance Impact: Cookies are sent with every HTTP request, which can
impact performance, especially when dealing with large cookies or many
small cookies.
Scalability Requirements: For applications that scale horizontally across
multiple servers, managing sessions can be more complex.
Statelessness: In a truly RESTful application, statelessness is key, and
sessions (which are stateful) might be avoided in favor of stateless
authentication mechanisms like tokens in cookies.
392
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:13 / 03:1710 Sec
393
But what will you do if you want to execute some code or logic before or after the action
method is executed, as shown in the image below?
If that is your requirement, then you need to use Filters in your ASP.NET Core application.
Filters in ASP.NET Core Applications are the Attributes that allow us to inject logic or code
before or after an action method is executed.
Why are Filters in ASP.NET Core MVC?
In ASP.NET Core MVC, Filters are used to Perform cross-cutting concerns. They are as
follows:
Caching
Logging
Error Handling
Modifying the Result
Authentication and Authorization, etc.
What are the Differences Between Filters and Middlewares in ASP.NET
Core?
Before proceeding further, we need to understand the differences between Filters and
Middleware Components in ASP.NET Core applications. The following are the Key
differences between Filters and Middleware Components:
Scope of Application (Where they Applied):
Filters: Filters are primarily applied to individual controller actions or
controllers. They are used to add specific behaviors or concerns to
processing a single action or a group of actions within a controller.
Middlewares: Middlewares are applied to the entire application’s request
processing pipeline. They can handle requests and responses globally,
regardless of the specific controller or action being invoked.
Execution Point (When they Execute):
Filters: Filters execute within the ASP.NET Core Framework’s pipeline and
are part of the controller/action execution process. They are triggered before
or after the execution of a specific action method.
Middlewares: Middlewares execute earlier in the Request Processing
Pipeline, typically before the request reaches the ASP.NET Core MVC
Controller action method. They can intercept requests and responses and
perform tasks at various stages of the request processing pipeline, such as
Routing, Authentication, Response Formatting, etc.
Purpose and Concerns (Why they are Used):
394
Filters: Filters are designed to handle concerns specific to the ASP.NET Core
MVC framework, such as Logging, Authentication, Authorization, Exception
Handling, Caching, Custom Logic, etc.
Middlewares: Middlewares are more general-purpose and can handle many
concerns, including Routing, Authentication, Request/Response Logging,
Compression, Security, etc.
Configuration (How they Configured):
Filters: Filters are typically configured using attributes (e.g., [Authorize],
[AllowAnonymous], etc.) on controllers or action methods. You can also
register global filters in the Program Class.
Middlewares: Middlewares are configured and ordered in the Program class
(e.g., UseHttpsRedirection(), UseAuthorization(), MapControllers(), etc).
Execution Order (What is the Execution Order):
Filters: The execution order for filters is decided based on the type of filters
you are applying to the controllers and action methods. So, the order of
Filters is not important.
Middlewares: The execution order for middleware components is determined
by the order in which they are added to the IApplicationBuilder pipeline, i.e.,
to the Request Processing Pipeline. So, the order of Middleware Components
is important.
So, Filters and Middleware Components serve different purposes and have different scopes
within an ASP.NET Core MVC application. Filters are more tightly integrated with the
ASP.NET Core MVC framework and are applied at the controller/action level. On the other
hand, Middleware Components are applied globally to the entire application.
Types of Filters in ASP.NET Core MVC
In ASP.NET Core MVC, there are several types of filters available that you can use to
modify the behavior in the request and response processing pipeline. The following are the
commonly used Filters in ASP.NET Core MVC Applications:
Authorization Filters in ASP.NET Core:
The Authorization Filter is used to perform Authentication and Authorization checks before
an action method is executed. Examples include AuthorizeAttribute for role-based or
policy-based authorization and AllowAnonymousAttribute to allow unauthenticated users
to access an action.
[Authorize]: This Built-in Filter restricts access to actions or controllers for
UnAuthenticated Users. You can also specify Roles, Policies, or Claims
based on which it can also decide who can access specific resources.
[AllowAnonymous]: This Built-in Filter allows unauthenticated users to
access actions or controllers.
Custom Authentication: You can also create Custom Authentication. To do
so, we need to create a class implementing the IAuthorizationFilter interface
and provide implementations for the OnAuthorization method, where we
need to write the custom authentication logic according to our business
requirements.
Action Filters in ASP.NET Core:
The Action Filters in the ASP.NET Core MVC Application are executed before and after an
action method is executed. They perform tasks like Logging, Modifying the Action’s
Arguments, or Altering the Action’s Result.
395
In ASP.NET Core, you can create a Custom Action Filter in two ways: First, by creating a
class implementing the IActionFilter interface and providing implementations
for [OnActionExecuting] and [OnActionExecuted] methods. Second, by creating a class
inherited from the ActionFilterAttribute class and overriding the [OnActionExecuting]
and [OnActionExecuted] methods.
[OnActionExecuting] and [OnActionExecuted]: These are the two methods within a
Custom Action Filter to execute logic before and after an action method is called. The
OnActionExecuting method executes before the action method is invoked, and the
OnActionExecuted method executes after the action method is invoked.
How Do We Apply the Custom Filter to Controllers and Action Methods in ASP.NET
Core?
If the custom class is created by overriding the ActionFilterAttribute class, then it is an
Attribute, and we can directly apply that Custom Filter to the Action methods or Controllers.
However, you cannot apply Custom Action Filters (if the custom class is created by
implementing the ActionFilter interface) directly to the controllers and action methods. For
this, we need to use TypeFilter and ServiceFilter as built-in attributes.
TypeFilter and ServiceFilter are built-in attributes in ASP.NET Core that apply Custom
Action Filters as Attributes to controller actions or controllers. Later, as we progress, we will
discuss the differences between TypeFilter and ServiceFilter Attributes and how to use
them.
Result Filters in ASP.NET Core:
The Result Filters in ASP.NET Core MVC Application runs after the action method has been
executed but before the result is processed and sent to the client. This means you can
modify the view or the result data before it gets rendered to the output stream. They are
used for tasks such as Adding Headers to the response, Modifying the Result, etc.
In ASP.NET Core, you can create a Custom Result Filter in two ways: First, by creating a
class implementing the IResultFilter interface and providing implementations
for [OnResultExecuting] and [OnResultExecuted] methods. Second, by creating a class
inherited from the ResultFilterAttribute class and overriding the [OnResultExecuted] and
[OnResultExecuted] methods.
[OnResultExecuting] and [OnResultExecuted]: These are the two methods within a
Custom Result Filter to execute logic before and after the Result is generated.
The OnResultExecuting method executes before the result is generated, and
the OnResultExecuted method executes after the result is generated.
Exception Filters in ASP.NET Core:
The Exception Filters are executed when an unhandled exception occurs during the
execution of an action method. They are used for Logging, Error Handling, and Displaying
Different Error Pages Based on the Error Status Codes.
In ASP.NET Core, you can create a Custom Exception Filter in two ways: First, you can
create a class implementing the IExceptionFilter interface and provide implementations for
the [OnException] method. Second, by creating a class inherited from
the ExceptionFilterAttribute class and overriding the [OnException] method.
Where Can We Configure Filters in ASP.NET Core MVC?
In the ASP.NET Core MVC Application, Filters can be configured in the following three
places:
Configuring Filters at Controller Level:
396
We can apply filters at the controller level by decorating the controller with the Filter
attribute, as shown in the below code. When we apply the Filter at the controller level, it will
apply to all the actions of that controller.
[Authorize] // Authorization filter applied at the controller level
// Action logic
}
Configuring Filters at Globally:
We can configure filters globally in the Program class. By adding filters as services, we can
ensure they are applied globally to all controllers and actions in our application. Following is
an example of configuring a global filter in the Program.cs class:
builder.Services.AddControllersWithViews(options =>
options.Filters.Add(new MyGlobalFilter());
});
Action Method Level:
We can also apply filters directly to individual action methods within our controller by using
the Filter Attribute, as shown in the below code. This allows us to apply specific filters only
to specific action methods.
public class MyController : Controller
// Action logic
397
}
Default Filters Execution Order in ASP.NET Core MVC
In ASP.NET Core MVC, filters are executed in a specific order known as the “Default
Execution Order“. The default execution order ensures filters are applied properly
throughout the request processing pipeline. The default execution order, from the earliest to
the latest in the pipeline, is as follows:
Authorization Filters: Authorization filters are executed first. They are
responsible for checking whether the current user can access the requested
resource or action. If authorization fails, the request will be short-circuited,
and the action method will not be executed.
Action Filters (Before Action Execution): Action filters with “Before Action
Execution” logic are executed before the action method is invoked. These
filters can perform tasks like logging, input validation, or pre-processing data.
Model Binding: Model binding occurs at this stage. It binds incoming data to
action method parameters and executes model validation.
Action Execution: The action method itself is executed.
Action Filters (After Action Execution): Action filters with “After Action
Execution” logic are executed after the action method completes its
execution. These filters can perform tasks like logging, post-processing data,
etc.
Result Filters (Before Result Execution): Result filters with “Before Result
Execution” logic are executed before the action result is executed. These
filters can modify the result or perform additional processing.
Action Result Execution: The action result, which can be a view or any
other result type, is executed.
Result Filters (After Result Execution): Result filters with “After Result
Execution” logic are executed after the action result has been executed.
These filters can perform tasks like logging or post-processing of the result.
Exception Filters (If an Exception Occurs): Exception filters are executed if
an unhandled exception occurs during the request’s processing. These filters
can handle the exception, log it, and return an appropriate error response.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:18 / 03:1710 Sec
However, we can also create a Custom Exception Filter to handle unhandled exceptions
globally. In ASP.NET Core, we can create the Custom Exception Filter in two ways: First,
we can create a class implementing the IExceptionFilter interface and
provide implementations for the [OnException] method. Second, by creating a class
inherited from the ExceptionFilterAttribute class and overriding
the [OnException] method.
Example to understand UseExceptionHandler Middleware in ASP.NET
Core
Let us first see an example of how the UseExceptionHandler middleware component
handles unhandled exceptions globally. Then, we will see how to create a Custom
Exception Filter.
The UseExceptionHandler Middleware allows us to specify an error-handling path that will
be executed when an unhandled exception is thrown within our application. We can
configure this using the Main method of our Program class.
To configure UseExceptionHandler middleware for global Exception Handling, please add
the following code to the program class. With the following code in place, if an exception
occurs and if the environment is not Development, then the user will be redirected to the
“/Home/Error” URL. Here, Home is the Controller name, and Error is the action method
within the Home Controller. If the environment is Development, it will display the complete
error details using the UseDeveloperExceptionPage middleware component.
if (app.Environment.IsDevelopment())
app.UseDeveloperExceptionPage();
}
399
else
// This will handle exceptions and redirect to the specified error page.
app.UseExceptionHandler("/Home/Error");
}
Modify the Home Controller:
Next, modify the Home Controller as follows. As you can see in the code below, within the
Index action method, we have written the logic in a way that it will throw runtime exceptions
while executing it. Further, if you notice, we have not handled that exception within the
Index action method. Again, we have added the Error action method, which should be
executed when there is an unhandled exception and if the environment is not set to be
Development.
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
int x = 10;
int y = 0;
int z = x / y;
return View();
return View();
400
}
Next, add the Error.cshtml view of the Home Controller. Once you add the Error view, copy
and paste the following code. The following view will be executed when your application has
an unhandled exception.
@{
Layout = null;
<!DOCTYPE html>
<html>
<head>
<title>Error</title>
</head>
<body>
<hgroup>
<h2 style="color:red">An unknown error has occurred. We are working on it. Please try
after some time</h2>
</hgroup>
</body>
</html>
Now, set the environment to Production or Staging and run the application, and you should
see the following generic error message.
401
namespace FiltersDemo.Models
File.AppendAllText(filePath, message);
}
Applying the Custom Exception Filter:
We can apply the Custom Exception Filter at three levels: Globally, At the Action Method
Level, and At the Controller Level. We have created the Custom Exception Filter inheriting
from the ExceptionFilterAttribute, which means our Custom Exception Filter is also
an Attribute. As it is an Attribute, we can directly apply it to the Controller or Action
Methods. Let us modify the Home Controller as follows to apply the Custom Exception Filter
on the Index Action method.
using FiltersDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
[CustomExceptionFilter]
int x = 10;
int y = 0;
int z = x / y;
return View();
}
403
options.Filters.Add(new CustomExceptionFilter());
});
Creating Custom Exception Filter by Implementing IExceptionFilter
Interface:
Let us see how we can implement the IExceptionFilter Interface and provide
implementations for the OnException method to create a Custom Exception Filter. Let us
modify the CustomExceptionFilter class as follows, which inherits from the IExceptionFilter
Interface and implements the OnException method. As part of the OnException method, we
have written our custom exception logic, which will log the unhandled exception details to a
text file:
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersDemo.Models
File.AppendAllText(filePath, message);
}
Note: You need to remember that the above CustomExceptionFilter class is no longer an
Attribute. Hence, we cannot apply this CustomExceptionFilter as an Attribute in our
Controller or Action Method.
Register the Custom Exception Filter Globally
You can register the custom exception filter globally in the Program.cs file as follows. This
is for Global registration:
// Global registration
builder.Services.AddControllersWithViews(options =>
options.Filters.Add(new CustomExceptionFilter());
});
OR
builder.Services.AddControllersWithViews(options =>
options.Filters.Add(typeof(CustomExceptionFilter));
});
You can apply the custom exception filter to specific controllers or actions
using ServiceFilter or TypeFilter attributes. So, modify the Home Controller as follows:
using FiltersDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
{
405
[ServiceFilter(typeof(CustomExceptionFilter))]
int x = 10;
int y = 0;
int z = x / y;
return View();
}
So, by following these steps, we can create and apply a Custom Exception Filter in our
ASP.NET Core MVC Application.
Redirecting to an Error View
Let’s see an example of how the Exception Filter redirects the user to a Custom Error view
when an unhandled exception occurs while processing the request. This is similar to the
example we created with the UseExceptionHandler middleware component. Let’s create the
error view within the Views/Shared folder named Error.cshtml and then copy and paste
the following code.
@{
Layout = null;
<!DOCTYPE html>
<html>
<head>
<title>Error</title>
406
</head>
<body>
<hgroup>
<h2 style="color:red">An unknown error has occurred. We are working on it. Please try
after some time</h2>
</hgroup>
</body>
</html>
Creating the Custom Exception Filter Attribute:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersDemo.Models
ViewName = "Error"
};
context.ExceptionHandled = true;
}
407
}
Next, modify the Home Controller as follows:
using FiltersDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
[RedirectToErrorViewFilter]
}
When Should We Use Exception Filter in ASP.NET Core MVC?
The Custom Exception filters are useful in the following scenarios:
Centralized Exception Handling: If you want a single, centralized location to
handle all the unhandled exceptions that occur throughout your application,
exception filters are a good choice.
Error Logging: Exception filters provide a convenient mechanism for logging
exceptions or collecting error information. You can log exceptions to various
targets (e.g., files, databases, or external services).
Custom Error Responses: Exception filters are helpful when you need to
customize the HTTP response sent to the client in case of an exception. You
can set specific HTTP status codes, return custom error messages, and
format responses according to your application’s requirements.
Difference Between Exception Handler and Exception Filter in
ASP.NET Core
408
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:18 / 03:1710 Sec
However, we can also create a Custom Exception Filter to handle unhandled exceptions
globally. In ASP.NET Core, we can create the Custom Exception Filter in two ways: First,
we can create a class implementing the IExceptionFilter interface and
provide implementations for the [OnException] method. Second, by creating a class
inherited from the ExceptionFilterAttribute class and overriding
the [OnException] method.
Example to understand UseExceptionHandler Middleware in ASP.NET
Core
Let us first see an example of how the UseExceptionHandler middleware component
handles unhandled exceptions globally. Then, we will see how to create a Custom
Exception Filter.
The UseExceptionHandler Middleware allows us to specify an error-handling path that will
be executed when an unhandled exception is thrown within our application. We can
configure this using the Main method of our Program class.
To configure UseExceptionHandler middleware for global Exception Handling, please add
the following code to the program class. With the following code in place, if an exception
occurs and if the environment is not Development, then the user will be redirected to the
“/Home/Error” URL. Here, Home is the Controller name, and Error is the action method
within the Home Controller. If the environment is Development, it will display the complete
error details using the UseDeveloperExceptionPage middleware component.
if (app.Environment.IsDevelopment())
app.UseDeveloperExceptionPage();
}
410
else
// This will handle exceptions and redirect to the specified error page.
app.UseExceptionHandler("/Home/Error");
}
Modify the Home Controller:
Next, modify the Home Controller as follows. As you can see in the code below, within the
Index action method, we have written the logic in a way that it will throw runtime exceptions
while executing it. Further, if you notice, we have not handled that exception within the
Index action method. Again, we have added the Error action method, which should be
executed when there is an unhandled exception and if the environment is not set to be
Development.
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
int x = 10;
int y = 0;
int z = x / y;
return View();
return View();
411
}
Next, add the Error.cshtml view of the Home Controller. Once you add the Error view, copy
and paste the following code. The following view will be executed when your application has
an unhandled exception.
@{
Layout = null;
<!DOCTYPE html>
<html>
<head>
<title>Error</title>
</head>
<body>
<hgroup>
<h2 style="color:red">An unknown error has occurred. We are working on it. Please try
after some time</h2>
</hgroup>
</body>
</html>
Now, set the environment to Production or Staging and run the application, and you should
see the following generic error message.
412
namespace FiltersDemo.Models
File.AppendAllText(filePath, message);
}
Applying the Custom Exception Filter:
We can apply the Custom Exception Filter at three levels: Globally, At the Action Method
Level, and At the Controller Level. We have created the Custom Exception Filter inheriting
from the ExceptionFilterAttribute, which means our Custom Exception Filter is also
an Attribute. As it is an Attribute, we can directly apply it to the Controller or Action
Methods. Let us modify the Home Controller as follows to apply the Custom Exception Filter
on the Index Action method.
using FiltersDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
[CustomExceptionFilter]
int x = 10;
int y = 0;
int z = x / y;
return View();
}
414
options.Filters.Add(new CustomExceptionFilter());
});
Creating Custom Exception Filter by Implementing IExceptionFilter
Interface:
Let us see how we can implement the IExceptionFilter Interface and provide
implementations for the OnException method to create a Custom Exception Filter. Let us
modify the CustomExceptionFilter class as follows, which inherits from the IExceptionFilter
Interface and implements the OnException method. As part of the OnException method, we
have written our custom exception logic, which will log the unhandled exception details to a
text file:
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersDemo.Models
File.AppendAllText(filePath, message);
}
Note: You need to remember that the above CustomExceptionFilter class is no longer an
Attribute. Hence, we cannot apply this CustomExceptionFilter as an Attribute in our
Controller or Action Method.
Register the Custom Exception Filter Globally
You can register the custom exception filter globally in the Program.cs file as follows. This
is for Global registration:
// Global registration
builder.Services.AddControllersWithViews(options =>
options.Filters.Add(new CustomExceptionFilter());
});
OR
builder.Services.AddControllersWithViews(options =>
options.Filters.Add(typeof(CustomExceptionFilter));
});
You can apply the custom exception filter to specific controllers or actions
using ServiceFilter or TypeFilter attributes. So, modify the Home Controller as follows:
using FiltersDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
{
416
[ServiceFilter(typeof(CustomExceptionFilter))]
int x = 10;
int y = 0;
int z = x / y;
return View();
}
So, by following these steps, we can create and apply a Custom Exception Filter in our
ASP.NET Core MVC Application.
Redirecting to an Error View
Let’s see an example of how the Exception Filter redirects the user to a Custom Error view
when an unhandled exception occurs while processing the request. This is similar to the
example we created with the UseExceptionHandler middleware component. Let’s create the
error view within the Views/Shared folder named Error.cshtml and then copy and paste
the following code.
@{
Layout = null;
<!DOCTYPE html>
<html>
<head>
<title>Error</title>
417
</head>
<body>
<hgroup>
<h2 style="color:red">An unknown error has occurred. We are working on it. Please try
after some time</h2>
</hgroup>
</body>
</html>
Creating the Custom Exception Filter Attribute:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersDemo.Models
ViewName = "Error"
};
context.ExceptionHandled = true;
}
418
}
Next, modify the Home Controller as follows:
using FiltersDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
[RedirectToErrorViewFilter]
}
When Should We Use Exception Filter in ASP.NET Core MVC?
The Custom Exception filters are useful in the following scenarios:
Centralized Exception Handling: If you want a single, centralized location to
handle all the unhandled exceptions that occur throughout your application,
exception filters are a good choice.
Error Logging: Exception filters provide a convenient mechanism for logging
exceptions or collecting error information. You can log exceptions to various
targets (e.g., files, databases, or external services).
Custom Error Responses: Exception filters are helpful when you need to
customize the HTTP response sent to the client in case of an exception. You
can set specific HTTP status codes, return custom error messages, and
format responses according to your application’s requirements.
Difference Between Exception Handler and Exception Filter in
ASP.NET Core
419
codes to provide meaningful responses to the client. The following are some of the standard
non-success HTTP status codes in ASP.NET Core MVC:
400 Bad Request: This status code is used when the server cannot
understand the request due to invalid syntax or client-side errors.
401 Unauthorized: This status code indicates the request lacks valid
authentication credentials.
403 Forbidden: This status code is used when the server understands the
request but refuses to fulfill it, often due to insufficient permissions.
404 Not Found: This status code indicates that the requested resource could
not be found on the server.
500 Internal Server Error: This status code is a generic error message
indicating that something has gone wrong on the server.
503 Service Unavailable: This status code is used when the server is
temporarily unavailable to handle the request due to being overloaded or
undergoing maintenance.
Error Pages Based on Status Code in ASP.NET Core MVC
In ASP.NET Core MVC, we can create custom error pages based on Non-Success HTTP
status codes to provide a more user-friendly message when errors occur. Let us proceed
and see how to create and return Custom Error Pages based on Non-Success HTTP Status
Codes like 401, 404, 500, etc., in an ASP.NET Core MVC application.
As we already discussed in our previous article, we can configure the Error Handling
Middleware
(UseStatusCodePagesWithReExecute or UseStatusCodePagesWithRedirects middlew
are in the Program.cs file) to handle the Non-Success HTTP status code and render the
appropriate custom error page.
app.UseStatusCodePagesWithRedirects(“/Error/{0}”);
This configuration will redirect the user to the Error Route with the corresponding status
code in the URL. Let us proceed and implement this example step by step:
Creating Error Controller:
Create a controller with the name ErrorController within the Controllers folders. Once you
create the Error Controller, please copy and paste the following code. When an error status
code is encountered, the Index action in the ErrorController is executed with the status code
as a parameter, and based on the status code, a specific error view is returned.
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
[Route("Error/{statusCode}")]
Response.Clear();
Response.StatusCode = statusCode;
switch (statusCode)
case 401:
return View("UnauthorizedError");
case 404:
return View("PageNotFoundError");
case 500:
return View("InternalServerError");
default:
return View("GenericError");
422
}
Creating Error Views:
Next, add the following views to the Views/Shared folder.
PageNotFoundError.cshtml
@{
Layout = null;
<!DOCTYPE html>
<html>
<head>
<title>PageNotFound Error</title>
</head>
<body>
<hgroup>
<h2 style ="color:red">The Page you are trying to access is no longer available. Kindly
check and submit the URL again</h2>
</hgroup>
</body>
</html>
UnauthorizedError.cshtml
@{
423
Layout = null;
<!DOCTYPE html>
<html>
<head>
<title>Unauthorized Error</title>
</head>
<body>
<hgroup>
<h2 style="color:red">You donot have the permission to access this page. Kindly contact
with your admin.</h2>
</hgroup>
</body>
</html>
InternalServerError.cshtml
@{
Layout = null;
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<hgroup>
<h2 style="color:red">Some Internal Server error Occurred while processing your request.
Kindly try after some time.</h2>
</hgroup>
</body>
</html>
GenericError.cshtml
@{
Layout = null;
<!DOCTYPE html>
<html>
<head>
<title>Generic Error</title>
</head>
<body>
<hgroup>
<h2 style="color:red">An unknown error has occurred. We are working on it. Please try
after some time</h2>
425
</hgroup>
</body>
</html>
Configuring Middleware in Program.cs:
namespace FiltersDemo
builder.Services.AddControllersWithViews();
if (app.Environment.IsDevelopment())
app.UseDeveloperExceptionPage();
else
app.UseStatusCodePagesWithReExecute("/Error/{0}");
app.UseHttpsRedirection();
app.UseStaticFiles();
426
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
}
The following line of code configures the Middleware:
app.UseStatusCodePagesWithReExecute(“/Error/{0}”);
Modify Home Controller:
Next, modify the Home Controller as follows. Here, you can see that we have created
different action methods to demonstrate the different types of Non-Success HTTP Status
Codes. SomeAction1 will throw a 400 Bad Request HTTP Status Code. SomeAction2 will
throw a 401 Unauthorized HTTP Status Code. SomeAction3 will throw a 403 Forbidden
HTTP Status Code. SomeAction4 will throw a 404 Not Found HTTP Status Code.
SomeAction5 will throw a 500 Internal Server Error HTTP Status Code. SomeAction6 will
throw a 503 Service Unavailable Error HTTP Status Code.
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
return View();
if (someConditionIsNotMet)
// Other logic
return View();
//401 Unauthorized
if (!IsAuthenticated)
// Other logic
return View();
//403 Forbidden
{
428
if (!UserHasPermissionToAccessResource)
// Other logic
return View();
if (requestedResourceNotFound)
return NotFound();
// Other logic
return View();
try
{
429
// ...
if (isServiceUnavailable)
// Other logic
return View();
}
430
Now, run the application and access the Home Controller Action Methods. You should see
the respective error pages based on the Status Code.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:13 / 03:1710 Sec
certain pages and modify the HTTP response based on the user role. For example, we are
going to dynamically modify the view based on a query string (admin=true changes the
result to “AdminView”).
Define Result Filter:
First, create a custom result filter that measures execution time and appends a custom
header if the execution time exceeds a predefined threshold. So, create a class file
named CustomResultFilterAttribute.cs within the Models folder and copy and paste the
following code.
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
namespace FiltersDemo.Models
_timer = Stopwatch.StartNew();
ViewName = "AdminView",
ViewData = viewResult.ViewData,
TempData = viewResult.TempData
};
base.OnResultExecuting(context);
_timer.Stop();
base.OnResultExecuted(context);
}
OnResultExecuting Method
The OnResultExecuting method is called just before the action result is executed, i.e.,
before the framework writes the response. Here’s what happens in this method:
Timer Initialization: A Stopwatch instance is created and started to measure
the duration of the result’s execution. This is useful for performance
monitoring.
433
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
[CustomResultFilter]
return View();
}
434
}
Index.cshtml
Next, modify the Index.cshtml view as follows:
@{
ViewData["Title"] = "AdminView";
<h1>Admin Panel</h1>
Running and Testing the Application
Access /Home/Index normally to see the “Index View”. I
In both requests, if you check the response headers, then you will see the custom header,
which we set using the Result Filter as shown in the below image:
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
namespace FiltersDemo.Models
_timer = Stopwatch.StartNew();
ViewName = "AdminView",
ViewData = viewResult.ViewData,
TempData = viewResult.TempData
};
_timer.Stop();
}
Code Explanation:
Initialization of Stopwatch: A Stopwatch is instantiated and started to
measure the time taken to execute the action result. This is useful for
performance monitoring.
Setting Custom Header: Before the action result is executed, a custom
header (X-Pre-Execute) is added to the HTTP response. This could be used
for various purposes, such as providing metadata about the response or for
debugging purposes.
Conditional Result Modification: The method checks if the HTTP request
contains a specific query parameter (admin). If this condition is met, and the
current result is a ViewResult (which typically renders a view), it modifies the
result to change the view that will be rendered.
Execution of Result: The await next() call is very important. This line hands
over control to the next filter in the pipeline, or if there are no further filters, it
executes the action result. This is an asynchronous operation and the method
awaits its completion before proceeding. After await next() completes, it
returns a ResultExecutedContext, which contains details about the executed
action result.
Stopwatch Stopping and Logging: Once the action result has been
executed and the control returns to the filter, the stopwatch is stopped. The
elapsed time (how long the result took to execute), the name of the executed
action, and the type of result that was executed are logged using
Debug.WriteLine().
Modifying the Home Controller:
Next, modify the Home Controller as follows. As the CustomResultFilter is not an attribute,
we cannot directly apply it to the Controller or Action method level. Here, we are using
TypeFilter to apply the Custom Result filter at the controller level.
using FiltersDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
[TypeFilter(typeof(CustomResultFilter))]
{
438
return View();
}
With these changes, run the application, and it should work as expected.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:13 / 03:1710 Sec
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersDemo.Models
{
440
File.AppendAllText(filePath, message);
}
Applying the Custom Result Filter to an Action Method:
Let us modify the Home Controller as follows to apply the Custom Result Filter on the Index
Action method:
using FiltersDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
[CustomResultFilterAttribute]
return View();
}
With the above code in place, run the application, navigate to the Index action method, and
verify the Log.txt file, which should be generated within the Log folder.
Registering the Filter Globally
We can also register the filter globally to apply to all actions in the application. This can be
done in the Program.cs class file as follows:
builder.Services.AddControllersWithViews(options =>
441
options.Filters.Add(new CustomResultFilterAttribute());
});
Approach 2: Implementing IResultFilter (Synchronous Result Filter)
Let’s say we want to create a result filter that logs the type of ActionResult returned by our
action methods. So, create a Custom Result Filter by implementing
the IResultFilter interface. You need to implement two
methods: OnResultExecuting and OnResultExecuted. These methods are called before
and after the result is executed. So, modify the CustomResultFilter.cs as follows.
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersDemo.Models
File.AppendAllText(filePath, message);
}
Register the CustomResultFilter as a service in your application’s Program.cs file. You can
specify the filter’s scope (e.g., Transient, Scoped, Singleton) based on your requirements.
builder.Services.AddScoped<CustomResultFilter>();
Apply the CustomResultFilter to your controller action methods using the [ServiceFilter]
attribute or add it globally in the Program.cs file. For example, apply on to the Action
method as follows:
using FiltersDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
[ServiceFilter(typeof(CustomResultFilter))]
return View();
}
443
}
Adding Globally:
Adding the following code in the Program.cs class file:
//Adding Custom Result Filter Globally
builder.Services.AddControllersWithViews(options =>
options.Filters.Add(new CustomResultFilterAttribute());
});
builder.Services.AddScoped<CustomResultFilter>();
Approach 3: Implementing IAsyncResultFilter (Asynchronous Result
Filter)
Assume you want to compress the response’s content asynchronously in certain conditions.
You would implement an asynchronous result filter to compress the response stream.
using Microsoft.AspNetCore.Mvc.Filters;
using System.IO.Compression;
namespace FiltersDemo.Models
if (shouldCompress)
context.HttpContext.Response.Body = compressionStream;
await next();
await compressionStream.FlushAsync();
compressedStream.Seek(0, SeekOrigin.Begin);
await compressedStream.CopyToAsync(originalBodyStream);
context.HttpContext.Response.Body = originalBodyStream;
context.HttpContext.Response.Headers.Add("Content-Encoding", "gzip");
else
// If we don't want to compress, just call the next delegate/middleware in the pipeline
await next();
}
445
return true; // For this example, we're just compressing every response
}
In this asynchronous result filter example, the filter checks if the response should be
compressed. If so, it sets up a new response body stream that compresses the content.
After executing the action result (with await next();), it copies the compressed content to the
original response stream and sets the appropriate HTTP header.
Then, apply this filter:
using FiltersDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
[CompressResultFilter]
return View();
}
446
Enchanted by the Beautiful City near Cambodia Border - Nếm TV00:10 / 02:4810 Sec
stored response for future requests without regenerating the response again by doing all the
processing. This is called Response Caching in ASP.NET Core.
How Response Caching Works in ASP.NET Core?
Please look at the following diagram to understand how response caching works in the
ASP.NET Core Application.
Here’s how response caching works in a web context, particularly in ASP.NET Core Web
Application:
Initial Request:
A client makes a request to the server for a specific resource.
The server processes the request, generates the response, and returns the
response to the client who initially made the request.
Along with the response, the server includes a Cache-Control HTTP
header to indicate that the response can be cached and to specify the
caching behavior.
Subsequent Requests:
When the client (i.e., browser) needs the same resource again, it first checks
the cache.
Suppose the cached response is available and fresh (i.e., within the period
specified by the Cache-Control header). In that case, the client serves it
directly from the cache instead of requesting it from the server again.
What is Proxy Cache in ASP.NET Core?
In the context of ASP.NET Core, Proxy Caches refer to a caching mechanism where a
Caching Server (the Proxy Server) is placed between the Client (usually a Web Browser)
and the Web Server hosting the ASP.NET Core application. This Proxy Server caches
responses from the Web Server, which can then be served quickly to subsequent requests
for the same resource. This can significantly improve the performance of web applications
by reducing the load on the web server and decreasing response times for end-users.
Server-Side:
In ASP.NET Core, you can use the [ResponseCache] attribute to specify
caching behavior for controller actions. This attribute allows you to set various
448
builder.Services.AddControllers();
builder.Services.AddResponseCaching();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
if (app.Environment.IsDevelopment())
app.UseSwagger();
app.UseSwaggerUI();
app.UseHttpsRedirection();
app.UseResponseCaching();
app.UseAuthorization();
450
app.MapControllers();
app.Run();
}
Example to Understand Basic Response Caching
Next, modify the Home Controller as follows. Here, we have applied the Index action
method with the [ResponseCache(Duration = 60)] Attribute, which will cache the response
for 60 seconds. This means that once a response is generated, it will be reused for
subsequent requests for the next 60 seconds.
using Microsoft.AspNetCore.Mvc;
namespace ResponseCachingDemo.Controllers
[Route("api/[controller]/[action]")]
[ApiController]
[HttpGet]
[ResponseCache(Duration = 60)]
}
451
Now, run the application and access the endpoint /api/home/index using Postman, and
you will see the following output. If you access the same endpoint within 60 seconds, you
will also see the same output.
Further, if you check the Response headers, you will see the Cache-Control header whose
value is set to public,max-age=60, as shown in the image below.
Cache-Control: public, max-age=60: These settings instruct all caches (like Browser,
Postman, Fiddler, Swagger, or Proxy Server) to store and reuse the response for 60
seconds without revalidating it. After 60 seconds, the cached response is considered
invalid, and a new copy needs to be fetched from the original server.
Example: No Caching
In the following example, we have set the NoStore property of
the ResponseCache Attribute to true, which tells the browser not to cache the response.
using Microsoft.AspNetCore.Mvc;
452
namespace ResponseCachingDemo.Controllers
[Route("api/[controller]/[action]")]
[ApiController]
[HttpGet]
}
Now, run the application and access the endpoint /api/home/index using Postman, and you
will see the caching is not working. Every time you hit the endpoint, you will get a different
response even though we have set the cache duration to 60 seconds. This is because we
have set the NoStore Attribute value to true, which means caching is disabled. Now, if you
check the Response headers, you will see the Cache-Control header whose value is set
to no-store, as shown in the image below.
453
Cache-Control: no-store: This setting instructs all caches (like Browser, Postman, Fiddler,
Swagger, or Proxy Server) not to store any part of the response in the cache. The no-store
directive ensures that no copy of the response is saved in any cache. Each time the
resource is needed, it must be fetched directly from the server. This guarantees that the
information is retrieved in its current state without the risk of serving outdated or potentially
compromised data from a cache.
Example to Understand Response Caching with VaryByHeader
In ASP.NET Core, the VaryByHeader Parameter in Response Caching specifies a set of
Request Headers (User-Agent, Accept-Language, or any Custom Header) that should
trigger the cache to store multiple responses for the same URL based on their values. This
is useful when responses vary not only by URL but also by request headers.
When we configure the Response Cache with the VaryByHeader parameter, the caching
mechanism considers the URL and the specified header values to determine the cache key.
That means different responses are cached and retrieved based on variations in the
specified headers. For a better understanding, please modify the Home Controller as
follows. Here, we are using the User-Agent header.
using Microsoft.AspNetCore.Mvc;
namespace ResponseCachingDemo.Controllers
[Route("api/[controller]/[action]")]
[ApiController]
{
454
[HttpGet]
}
Now, run the application and access the endpoint /api/home/index using Postman and
Swagger. You will get a different response for each client. This is because the User-Agenet
header for Swagger and Postman is going to be different. This is also true in the case of
Browsers. Each Web Browser has a different User Agent. When the User-Agent is
changed, it will fetch the data from the server. Now, if you check the Response header, then
you will see along with the Cache-Control header, the Server is also adding the Vary header
in the Response, as shown in the below image:
To store the cache on the browse only, we need to use the Client Option.
The ResponseCacheLocation.Client specifies that the response should be cached only
on the client side, and in this case, it will set the Cache-Control header to Private. So,
modify the Home Controller as follows:
using Microsoft.AspNetCore.Mvc;
namespace ResponseCachingDemo.Controllers
[Route("api/[controller]/[action]")]
[ApiController]
[HttpGet]
}
456
}
Now, you can test the functionality using Swagger, and you should see the following output.
It will not work on Postman.
Please check the following Key Points of the behavior of Swagger and Postman:
Swagger Behavior:
When testing APIs, Swagger UI generally respects the HTTP caching headers sent by the
server. This means that if your ASP.NET Core application sends a response with Cache-
Control set to private, max-age=300, Swagger UI will cache the response as instructed for
the duration specified. Subsequent requests to the same endpoint (within the cache
duration) might not hit the server if the cached response is deemed valid.
Postman Behavior:
Postman, on the other hand, does not automatically cache responses like a typical web
browser would, despite receiving the same caching headers. In Postman, every time you hit
the “Send” button, a new request is made to the server, ignoring any previous response
cache headers. This is intentional, as Postman is designed for testing and development,
where you often need to see live server responses without caching interference.
Cache-Control: Private: The Cache-Control header with the value private is used in
Response Caching to indicate that the response is specific to a single user and should not
457
be stored by shared caches, such as proxy servers. It can only be stored in the private
cache of the user’s browser.
Example to Understand Response Caching with VaryByQueryKeys
In ASP.NET Core, the VaryByQueryKeys Property in Response Caching is used to specify
that the response cache should vary based on the values of specified query string
parameters. This is useful when a URL serves different data based on query string
parameters. For example, if you have a URL that can return different results based on a
query string like ?page=1 or ?page=2, you can use VaryByQueryKeys to cache these
responses separately. For a better understanding, please modify the Home Controller as
follows:
using Microsoft.AspNetCore.Mvc;
namespace ResponseCachingDemo.Controllers
[Route("api/[controller]/[action]")]
[ApiController]
[HttpGet]
}
In this case, the cached output of the Index action method would differ depending on the
value of the page query string parameter. This means if a user requests Index?page=1 and
another user requests Index?page=2, they will each get a different cached response
specific to the page they requested.
Example to Understand Custom Cache Profile with Response Caching
In ASP.NET Core, Custom Cache Profiles allows us to define Reusable Caching Settings
that can be applied across multiple actions or controllers. These profiles are defined in the
458
application’s configuration, typically in the Program.cs class file, and can then be
referenced by its name in controllers. Let us modify the Program class as follows.
using Microsoft.AspNetCore.Mvc;
namespace ResponseCachingDemo
builder.Services.AddControllers(options =>
Duration = 60,
Location = ResponseCacheLocation.Any
});
Location = ResponseCacheLocation.None,
NoStore = true
});
});
459
builder.Services.AddResponseCaching();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
if (app.Environment.IsDevelopment())
app.UseSwagger();
app.UseSwaggerUI();
app.UseHttpsRedirection();
app.UseResponseCaching();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
Here, we have configured two cache profiles (Default60 and NoCache), as shown in
the below image:
460
namespace ResponseCachingDemo.Controllers
[Route("api/[controller]/[action]")]
[ApiController]
[HttpGet]
[ResponseCache(CacheProfileName = "Default60")]
{
461
[HttpGet]
[ResponseCache(CacheProfileName = "NoCache")]
}
Now, run the application and access the above endpoints using either Swagger or Postman.
You will see that Response Caching works with the Index action method while it is disabled
with the Privacy Action method.
Benefits of Response Caching in ASP.NET Core MVC
The following are some of the key benefits of using Response Caching in ASP.NET Core
MVC:
Improved Performance: By storing the output of actions, Response Caching
reduces the time and resources needed to generate responses on
subsequent requests. This can lead to faster page load times for users, as the
server can serve cached content instead of regenerating it each time.
Reduced Server Load: Caching can dramatically reduce the workload on the
server. When content is served from the cache, fewer resources are
consumed, which means your server can handle more users and requests
with the same hardware.
Enhanced User Experience: Faster response times generally lead to a
better user experience.
Customizable Caching Strategies: ASP.NET Core offers a variety of
caching strategies, such as caching by duration, parameters, headers, etc.
This flexibility allows developers to optimize caching based on the
application’s needs and behaviors.
ASP.NET Core Applications. As part of this article, we will discuss the following pointers in
detail.
1. What is the Authorization Filter in ASP.NET Core MVC?
2. What is Authentication?
3. What is Authorization?
4. Examples to Understand Authorization Filter in ASP.NET Core MVC
5. Custom Authorization Filter in ASP.NET Core MVC
6. When Should We Use Authorization Filter in ASP.NET Core MVC?
What is the Authorization Filter in ASP.NET Core MVC?
In ASP.NET Core MVC, the Authorization Filter allows us to apply authorization rules to
controllers and actions within our application. Authorization Filters in ASP.NET Core are
responsible for checking whether a user is allowed to perform an action or access a
resource. These filters run before the action method is executed, ensuring the user has
permission to access the method.
Authorization filters are executed after the routing but before model binding and other action
filters. If the authorization fails (e.g., the user does not have the required permissions), the
filter short-circuits the request, and the action method does not execute.
What is Authentication?
Authentication is a process that ensures and confirms a user’s identity. In other words, we
can say that it is a process to validate someone against some data source. Let us
understand Authentication from a layman’s point of view. For this, please have a look at the
following diagram.
The above image shows the different sections of an IT Company, such as Reception, HR
Section, Accounts Section, Server Room, etc. At the gate, we have biometrics to verify the
employee. Suppose one employee comes. This biometrics checks the employee credentials
against some data source, and if it is found that the employee is valid, it only allows entering
into the campus. This is nothing but Authentication.
What is Authorization?
Authorization is a mechanism that determines whether users can access a particular
resource. The most important point that you need to remember is that authentication
happens first and then only authorization. For a better understanding, please have a look at
the following image.
463
As shown in the above image, once the employee is authenticated, he enters the Campus.
Then, Authorization comes into the picture. Within the campus, which section he is allowed
to enter is determined by the Authorization process. The Role of the Employee does this. If
the Employee has list privileges, he may not allow each section. On the other hand, if the
Employee has the highest privileges, he may be allowed to enter each section.
Types of Authorization Filters in ASP.NET Core MVC:
By default, in the ASP.NET Core MVC applications, all the action methods of all controllers
can be accessed by both authenticated and anonymous users. However, if you want the
action methods to be available only for authenticated and authorized users, you need to use
the Authorization Filter in ASP.NET Core MVC. ASP.NET Core provides two built-in
attributes, [Authorize] and [AllowAnonymous], that can be used as filters.
Authorize Attribute: The [Authorize] Attribute specifies that only
authenticated users or users with certain roles or policies can access a
particular action method or controller. The [Authorize] attribute specifies that
the associated action method or controller requires the user to be
authenticated.
AllowAnonymous Attribute: The [AllowAnonymous] attribute specifies that
an action method or controller should allow anonymous access, even when
an [Authorize] attribute is applied at the controller or action level. In other
words, it permits unauthenticated users to access the decorated resource,
bypassing any authentication and authorization checks that might be in place
at higher levels.
Examples to Understand Authorization Filter in ASP.NET Core MVC:
Let us understand the Authorize and AllowAnonymous Filters with an example. First, create
a new ASP.NET Core Application using the Model-View-Controller Project Template. Once
you create the Project, then modify the Home Controller as follows. As you can see, we
created the HomeController with three action methods,
i.e., NonSecureMethod, SecureMethod, and Login. We want the SecureMethod to be
accessed by authenticated users while the NonSecureMethod and Login methods to be
accessed by anyone. Whenever an unauthenticated user wants to access the
SecureMethod, we must redirect that user to the Login action method.
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
{
464
}
At this point, authenticated and anonymous users can access the SecureMethod and the
NonSecureMethod methods using the following two URLs.
/Home/SecureMethod
/Home/NonSecureMethod
If you want the Secure Method to be accessed only by authenticated and authorized users,
then you need to decorate this method with the Authorize attribute, as shown below.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
}
Now run the application and navigate to /Home/SecureMethod. You will see the following
Internal Server Error.
466
Instead of displaying the above error page, we need to redirect the user to the Login Page.
In the later part of this article, I will show you how to create Custom Authentication Filters to
achieve the same in ASP.NET Core MVC Applications.
Can we apply the Authorize Attribute at the controller level?
Applying the Authorize attribute at the controller level applies to all the action methods
present within that controller. For a better understanding, please modify the Home
Controller as follows. The Home Controller Action methods are now protected with the
Authorize Attribute. So, only the authenticated users can now access the SecureMethod(),
NonSecureMethod(), and Login action methods.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
}
467
}
Now, suppose you want to allow anonymous access to the NonSecureMethod and Login
method of the Home Controller. In that case, you need to decorate the NonSecureMethod
and Login method with the AllowAnonymous attribute, as shown below. The
AllowAnonymous attribute in ASP.NET Core MVC is used to skip the authorization enforced
by the Authorization Filter.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
}
468
[AllowAnonymous]
[AllowAnonymous]
}
Note: If both Authorize and AllowAnonymous Attributes are applied to an action method,
then the AllowAnonymous attribute will take priority and be accessed by any user.
Custom Authorization Filter in ASP.NET Core:
We can also create Custom Authentication filters. To create Custom Authentication in
ASP.NET Core MVC, we need to create a class implementing
the IAuthorizationFilter or IAsyncAuthorizationFilter interface and provide
implementations for the OnAuthorization method, where you need to write the custom
authentication logic as per our business requirements.
Example to Understand Custom Authorization Filter in ASP.NET Core
In ASP.NET Core, we can create a Custom Authorization Filter to redirect the User to the
login page when the user is not authenticated. Let us see how we can create a custom
authorization filter to achieve this:
Create a class file named CustomAuthorizationFilterAttribute.cs, and then copy and
paste the following code. As you can see, the following class is inherited from the Attribute
and IAuthorizationFilter classes so that we can use this class as an Attribute. This class
implements the IAuthorizationFilter interface and provides the implementation for
the OnAuthorization method, where we have written our custom authorization logic.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Security.Claims;
469
namespace FiltersDemo.Models
if (!IsAuthorized(context.HttpContext.User))
});
}
Now, we need to decorate the controller or action method where we need to implement our
custom authorization logic. Let us decorate the SecureMethod with our Custom
Authorization Filter Attribute. So, modify the Home Controller as follows:
using FiltersDemo.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
[AllowAnonymous]
[AllowAnonymous]
471
}
With the above changes in place, visit the SecureMethod, and you will see that it will
redirect to the Login Page.
Creating a Custom Authorization Filter
In the previous example, the CustomAuthorizationFilterAttribute class is inherited from
the Attribute class, and that’s why we apply
the CustomAuthorizationFilterAttribute directly to the action methods and controller. Let
us remove the inherited Attribute class from the CustomAuthorizationFilterAttribute class
definition, and let’s inherit from the IAuthorizationFilter interface. So, modify the
CustomAuthorizationFilterAttribute class as follows:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Security.Claims;
namespace FiltersDemo.Models
if (!IsAuthorized(context.HttpContext.User))
});
}
Register the Filter Globally, on a Controller, or an Action:
Now, we can register the Custom Authorization Filter at 3 different places. We can register
the filter globally, which means it will be applied to all controllers and actions in your
application. To do so, you need to modify the MVC Service registration in your Program.cs
class file as follows.
builder.Services.AddControllersWithViews(options =>
options.Filters.Add(new CustomAuthorizationFilter());
});
473
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
[TypeFilter(typeof(CustomAuthorizationFilter))]
}
Custom Asynchronous Authorization Filter in ASP.NET Core MVC
It is also possible to create the Custom Authorization Filter asynchronous. For this, our
Custom Authorization Filter class needs to implement the IAsyncAuthorizationFilter
interface and provide the implementation for the OnAuthorizationAsync method. This type of
filter is suitable for performing asynchronous operations, such as database calls or any I/O-
bound work, within your authorization logic.
Create the Custom Asynchronous Authorization Filter:
So, create a class file named CustomAsyncAuthorizationFilter.cs and copy and paste the
following code. Here, you can see the CustomAsyncAuthorizationFilter class implement the
IAsyncAuthorizationFilter interface and provide implementations for the
OnAuthorizationAsync method where we have written the custom Authorization Logic:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersDemo.Models
474
if (!isAuthorized)
});
// For example, you can check user permissions, roles, etc., using async calls
return false;
}
Register the Filter:
You can register the filter globally or on specific controllers or actions:
Globally:
builder.Services.AddControllersWithViews(options =>
options.Filters.Add(new CustomAsyncAuthorizationFilter());
});
On a Controller or Action:
using FiltersDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
[TypeFilter(typeof(CustomAsyncAuthorizationFilter))]
[TypeFilter(typeof(CustomAsyncAuthorizationFilter))]
{
476
return View();
}
Note: Once we discuss the ASP.NET Core Identity, you will see the real implementation
with proper roles, claims, etc.
When Should We Use Authorization Filter in ASP.NET Core MVC?
Authorization filters in ASP.NET Core MVC should be used when you need to control
access to specific controllers or actions within your web application based on authentication
and authorization rules. The following are some common scenarios in which you should use
authorization filters:
Securing Sensitive Data or Functionality: Use authorization filters to
protect endpoints that access sensitive data or perform sensitive operations,
ensuring that only authorized users can access them.
Role-Based Access Control: When you have different user roles in your
application (like admin, user, and moderator) and want to restrict access to
certain actions or controllers based on these roles.
Error Handling: Authorization filters can also handle unauthorized access by
redirecting users to login pages, displaying access-denied messages, or
taking other appropriate actions.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:16 / 03:1710 Sec
}
We will see how to Validate the above Model using a Custom Action Filter as well as we will
see how to modify the above model data using a Custom Action Filter while returning the
data to the client.
Creating a Logger Service:
Let us define our service interface and implementation for the logging. This Logger service
is going to be used by our Custom Action Filters. So, create an interface
named ILoggerService.cs within the Models folder and then copy and paste the following
code:
namespace FiltersDemo.Models
}
Next, create another class file named LoggerService.cs within the Models folder and copy
and paste the following code. This class implements the ILoggerService interface and
implements the Log method, where we have written the logic to store the log message in a
text file.
namespace FiltersDemo.Models
//saving the data in a text file called Log.txt within the Log folder which must be
File.AppendAllText(filePath, message);
}
Creating the Log Folder:
Next, create a folder called Log within the Project root directory where the Log.txt file is
going to be generated by the application.
Registering the Logger Service:
Next, we need to register the Logger Service into the built-in dependency injection
container. This is because we want to use the Logger service through our application,
including the Custom Action Filter, and we want the Framework to inject the logger service
through the constructor. So, add the following code to the Program.cs class file:
builder.Services.AddSingleton<ILoggerService, LoggerService>();
Creating a Custom Action Filter for Logging in ASP.NET Core MVC:
Create a class file named LoggingFilterAttribute.cs within the Models folder, and then
copy and paste the following code. This filter logs details about the action method calls,
including parameters, execution times, etc.
using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json;
using System.Diagnostics;
namespace FiltersDemo.Models
{
480
_LoggerService = LoggerService;
_timer = Stopwatch.StartNew();
_LoggerService.Log(message);
base.OnActionExecuting(context);
_timer?.Stop();
_LoggerService.Log(message);
base.OnActionExecuted(context);
}
481
}
The above LoggingFilterAttribute is a Custom Filter that inherits from ActionFilterAttribute.
This class is used to log information about MVC actions, specifically when they start and
when they finish. It uses two methods, OnActionExecuting and OnActionExecuted, to
achieve this.
OnActionExecuting Method
The OnActionExecuting method is called before the action method is executed. This
method is used for the following purposes:
Start a Timer: It initializes and starts the Stopwatch to measure the duration
of the action’s execution. This is important for logging the action’s execution
time.
Preparing the Log Message: It constructs a message that includes the
controller’s name, the action’s name, and the action’s serialized arguments.
Log the Start of the Action: The constructed message (indicating the start
of action execution) is logged using the _LoggerService. This helps in
tracking when an action begins its execution.
OnActionExecuted Method
The OnActionExecuted method is invoked after the action method has been executed. This
method is used for the following purposes:
Stop the Timer: It stops the Stopwatch that was started in
OnActionExecuting.
Preparing the Log Message: Similar to OnActionExecuting, it constructs a
message that includes the name of the controller, the action, and the time
taken (in milliseconds) to execute the action.
Logging the End of the Action: This message (indicating the completion of
the action and its duration) is then logged using the same logging service.
Note: Both methods call base.OnActionExecuting(context) and
base.OnActionExecuted(context) at the end of their implementations. These calls ensure
that any additional processing defined in the base class (ActionFilterAttribute) is also
executed.
Creating a Custom Action Filter for Data Transformation in ASP.NET
Core MVC:
Create a class file named DataTransformationFilterAttribute.cs within the Models folder,
and then copy and paste the following code. This filter modifies the data returned from an
action method. It assumes you are returning a specific model that could be transformed.
Here, the following class is inherited from the ActionFilterAttribute and overrides the
OnActionExecuted method.
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Models
model.TransformData();
base.OnActionExecuted(context);
}
Understanding the OnActionExecuted method:
Check the Result Type: The method first checks if the action method’s result
(context.Result) is of type ViewResult. ViewResult is a type of action result
that renders a view as the response to the request.
Access and Modify the Model: If the result type is ViewResult, the method
then checks if the model associated with this view result is of type
MyCustomModel. If it is, it accesses this model.
Transform the Model Data: Once it has access to the model, the method
calls the TransformData() function on the model. This function is assumed to
manipulate or transform the data within the model. Depending on the
implementation of TransformData(), this could involve changing values,
formatting data, calculating fields, etc.
Call Base Method: Finally, the method calls
base.OnActionExecuted(context), which ensures that any logic implemented
in the base class’s OnActionExecuted method is also executed.
Creating a Custom Action Filter for Validation in ASP.NET Core MVC:
483
Create a class file named CustomValidationFilter.cs within the Models folder, and then
copy and paste the following code. This filter performs custom validation of action
parameters. Here, we created the Custom Action Filter, implementing
the IActionFilter interface and providing implementations for
the OnActionExecuting and OnActionExecuted methods. Actually, we are not doing
anything on the OnActionExecuted methods.
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace FiltersDemo.Models
// Validate Name
if (string.IsNullOrWhiteSpace(model.Name))
// Validate Address
if (string.IsNullOrWhiteSpace(model.Address))
484
if (!context.ModelState.IsValid)
// Assuming the controller action expects a return of the same view with the model
};
// For this filter, we do nothing here, but you could add post-processing logic if needed.
}
485
}
Understanding OnActionExecuting Method
Argument Validation: The method starts by attempting to retrieve an
argument named “model” from the context’s ActionArguments. It then checks
if this argument is of type MyCustomModel.
Name Validation: If the model’s Name property is null or whitespace, a
model error stating that “Name cannot be empty or whitespace” is added.
Address Validation: Similarly, it checks the Address property and adds a
model error if it’s null or whitespace.
Invalid Model Handling: If the model state is invalid, it prevents the
execution of the action method by setting the context’s Result property.
Creating and Returning ViewResult: A new ViewResult is created to return
the same view with the invalid model data. This ViewResult includes the
name of the action (retrieved from context.RouteData) to indicate which view
to return. A new ViewDataDictionary is initialized with the model state and
model, allowing the view to display validation errors.
Creating a Custom Action Filter for Error Handling in ASP.NET Core
MVC:
Create a class file named ErrorHandlerFilterAttribute.cs within the Models folder, and
then copy and paste the following code. This filter implements custom error-handling logic
for actions. The Custom Action Filter is inherited from the ExceptionFilterAttribute class and
overrides the OnException method.
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace FiltersDemo.Models
{
486
_LoggerService = LoggerService;
_LoggerService.Log(message);
};
}
Understanding OnException Method:
Logging the Exception: When an exception occurs in any action method to
which this filter is applied, the OnException method captures the exception
and logs a detailed message. The message includes the name of the action
where the exception occurred and the exception details itself. This is done by
the Logger Service, which is injected through the constructor.
487
Setting the Response: After logging the exception, the method proceeds to
alter the user’s experience by redirecting them to a generic error page. This is
done by setting context.Result to a new ViewResult:
View Name: It specifies the path to the error view
(~/Views/Shared/Error.cshtml). This ensures that whenever an exception is
handled by this filter, the user is redirected to a standard error page,
maintaining a consistent error-handling strategy across the application.
ViewData: The ViewDataDictionary is populated with the exception data
using the EmptyModelMetadataProvider and the current ModelState. This
allows the error view to access details of the exception, if necessary, which
can be useful for displaying error messages or diagnostic information on the
error page.
Marking the Exception as Handled: By setting context.ExceptionHandled to
true, the filter communicates to the ASP.NET Core framework that the
exception has been handled. This prevents the exception from propagating
further, which means it won’t trigger other exception handlers or result in the
framework’s default error-handling mechanisms taking over (such as showing
the developer exception page).
Creating the Error View:
Next, create a view named Error.cshtml within the Views/Shared folder and then copy and
paste the following code. This is the view which is going to be rendered by the above
Custom Error Handler:
@{
Layout = "~/Views/Shared/_Layout.cshtml";
ViewData["Title"] = "Error";
<p>We're having trouble processing your request. Please try again later.</p>
Creating a Custom Action Filter for Caching in ASP.NET Core MVC:
Create a class file named AsyncCachingFilter.cs within the Models folder, and then copy
and paste the following code. The following Custom Action Filter implements the
IAsyncActionFilter and provides an implementation for the OnActionExecutionAsync
method.
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
namespace FiltersDemo.Models
488
_cache = cache;
_expirationTimeSpan = TimeSpan.FromSeconds(secondsToCache);
else
}
Understanding OnActionExecutionAsync Method:
Initialization: The Constructor uses an injected IMemoryCache instance
(_cache) to store and retrieve cached data. The cache’s expiration is set via a
TimeSpan (_expirationTimeSpan), which is determined by the number of
seconds passed into the constructor.
Caching Logic: The cache key is generated from the request path
(context.HttpContext.Request.Path). This ensures that each unique URL has
its own cache entry.
Cache Lookup: The method first checks if there is a cached result available
for the generated key using _cache.TryGetValue(key, out IActionResult
cachedResult). If a cached result exists (cachedResult), it is immediately
assigned to context.Result. This tells the framework to skip executing the
action method and return the cached result directly to the client.
Action Execution: If no cached result is found, the method proceeds to
execute the action by calling await next(). This ActionExecutionDelegate
(next) represents the next delegate in the action filter pipeline, which typically
leads to the execution of the action method itself. After the action executes,
the resulting context (executedContext) is returned, which includes the result
of the action method.
Caching the Result: The result of the action method
(executedContext.Result) is checked to see if it’s an instance of ActionResult.
This check is important because only action results should be cached. If it is
an ActionResult, it is added to the cache with the earlier generated key. The
cache entry is set to expire based on _expirationTimeSpan.
Modifying the Home Controller:
Let’s modify the Home Controller class code as follows to demonstrate the use of all
created Custom Action Filters.
using FiltersDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
490
[TypeFilter(typeof(LoggingFilterAttribute))]
[TypeFilter(typeof(AsyncCachingFilter))]
ViewBag.CurrentTime = DateTime.Now;
return View();
[DataTransformationFilter]
};
//return Ok(model);
return View(model);
[HttpGet]
return View();
[TypeFilter(typeof(CustomValidationFilter))]
[HttpPost]
if (ModelState.IsValid)
return RedirectToAction(nameof(Index));
return View(model);
[TypeFilter(typeof(ErrorHandlerFilterAttribute))]
}
Creating and Modifying the Views:
Next, we need to modify and create the Views as per our requirements:
Index.cshtml View:
Modify the Index.cshtml View as follows:
@{
492
@{
<h1 class="mb-3">Details</h1>
<div class="card">
<div class="card-body">
<h5 class="card-title">Name</h5>
<p class="card-text">@Model.Name</p>
<h5 class="card-title">Address</h5>
<p class="card-text">@Model.Address</p>
</div>
</div>
</div>
Create.cshtml View
Add Create.cshtml View within the Views/Home folder and then copy and paste the
following code:
@model FiltersDemo.Models.MyCustomModel
@{
493
<h1>Create Model</h1>
<div class="form-group">
<label for="Name">Name</label>
</div>
<div class="form-group">
<label for="Address">Address</label>
</div>
</form>
</div>
Testing the Application:
Now, run the application and test each action method. We have applied the Logging
Custom Filter at the Controller level, meaning it will be applied to all Action methods of the
Home Controller.
Index Action Method:
Now, access the Home/Index URL, and you should see the following. Now, within 60
seconds, if you access the same page, then you will see that the Date is not going to be
changed. This is because we have applied the Custom Cache filter on the Index Action
method:
494
In ASP.NET Core, Filters execute custom pre- and post-processing logic before and after
the action method execution. They can be used for various purposes, such as
Authentication, Authorization, Caching, Exception Handling, Result Modification, etc. There
are different kinds of filters (like Authorization, Action, Exception, and Result filters). We can
also apply the filters to controllers and actions in different ways, such as applying at the
action method level, at the controller level, or globally.
In ASP.NET Core, TypeFilter and ServiceFilter are built-in attributes that apply custom
filters to controller actions or controllers. However, they have different purposes and are
used in different scenarios. Now, let us proceed and try to understand the TypeFilter and
ServiceFilter, what are the differences between them, and when to use which one:
TypeFilter Attribute in ASP.NET Core MVC:
TypeFilter is a Built-in Attribute in ASP.NET Core that allows us to apply a Custom Filter to
a single action or controller. We must provide the filter type (Custom Filter Class name) as a
parameter to the TypeFilter attribute. It is useful when we want to apply a Custom Filter
Class to a specific action or controller and don’t need to configure it globally or inject
additional services.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:08 / 03:1710 Sec
File.AppendAllText(filePath, message);
}
Creating Custom Action Filter:
Next, create a class file named CustomLoggingFilter.cs and then copy and paste the
following code. As you can see, this class implements the IActionFilter interface and
provides implementations for the OnActionExecuting and OnActionExecuted methods.
Further, we have injected the LoggerService instance through constructor dependency
injection.
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersDemo.Models
_LoggerService = LoggerService;
string message = " Controller:" + controllerName + " Action:" + actionName + " Date: "
+ DateTime.Now.ToString() + Environment.NewLine;
_LoggerService.Log("OnActionExecuting", message);
string message = " Controller:" + controllerName + " Action:" + actionName + " Date: "
+ DateTime.Now.ToString() + Environment.NewLine;
_LoggerService.Log("OnActionExecuted", message);
}
Registering the Services to the Dependency Injection Container:
Next, we must register the Services to the Dependency Injection Container within the
Program class so that the MVC Framework injects the service. So, add the following line to
the Program class. Here, it is not required to register the Custom Filter into the dependency
injection container.
builder.Services.AddScoped<ILoggerService, LoggerService>();
Apply Filter as a Service:
To apply the filter as a service, we can use TypeFilter Attribute. A TypeFilterAttribute is
used to create an instance of a Custom Filter Class using dependency injection. You need
to pass the Custom Filter Type as an argument to its constructor. The Type Filter
instantiates the filter type each time the filter is applied. So, modify the Home Controller as
follows:
using FiltersDemo.Models;
using Microsoft.AspNetCore.Mvc;
499
namespace FiltersDemo.Controllers
[TypeFilter(typeof(CustomLoggingFilter))]
return View();
[TypeFilter(typeof(CustomLoggingFilter))]
return View();
}
Modify Index.cshtml File
@{
<h2>Index Page</h2>
Add Details.cshtml File
@{
}
500
<h2>Details Page</h2>
Testing the Application:
Each time a request is made to either the Index or Details action methods, a new instance
of CustomLoggingFilter is created. This is because the [TypeFilter] attribute creates an
instance of the specified filter type per request for each action method it decorates.
Therefore, if we make a request to the Index method and another request to the Details
method, two separate instances of CustomLoggingFilter will be created, i.e., one for each
request. Each subsequent request to these methods will create new instances accordingly.
Now, run the application, access both Index and Details action methods, and then check
the Log.txt file; you should see that the Instance of the CustomLoggingFilter was created
twice, as shown in the image below.
using Microsoft.AspNetCore.Mvc;
namespace FiltersDemo.Controllers
[ServiceFilter(typeof(CustomLoggingFilter))]
{
501
return View();
[ServiceFilter(typeof(CustomLoggingFilter))]
return View();
}
At this point, if you run the application, then you will get the following 500 HTTP Error. This
is because we are applying the CustomLoggingFilter using the ServiceFilter attribute, but
we have not yet registered the CustomLoggingFilter as a service into the built-in
dependency injection container.
discussed, will create only one instance of the registered service throughout the
application’s life and rescue that instance.
method, which will process the data, update the PIN number, and then redirect to the
PinChangeSuccess page.
using Microsoft.AspNetCore.Mvc;
namespace BankingApplication.Controllers
[HttpGet]
return View();
[HttpPost]
return RedirectToAction("PinChangeSuccess");
return View();
}
504
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem TV
@{
<div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
</form>
</div>
Creating the PinChangeSuccess View:
Next, create a view named PinChangeSuccess.cshtml within the Views/Home folder and
then copy and paste the following code. We are simply displaying the PIN Change Success
message.
@{
<div class="container">
<div class="col-md-8">
<h4 class="alert-heading">Success!</h4>
<p>@TempData["Message"]</p>
<hr>
<p class="mb-0">If you need to make any further changes, please return to your account
settings.</p>
</div>
</div>
</div>
</div>
506
Now, run the application, visit the Home/ChangePin, enter the Account Number and PIN,
and then click on the PIN Change button as shown in the below image:
Once you click on the PIN Change button, the PIN has been changed for the account
number, and you should see the following success message:
As you can see, the application is working as expected. Please observe the
URL https://localhost:7199/Home/ChangePin. Now, let’s see how a hacker can use the
above URL to launch a Cross-Site Request Forgery (CSRF or XSRF) attack.
Hackers Application:
Let us create a new ASP.NET Core Application using the Model View Controller Project
template and give the project name HackerApplication. Once you create the
HackerApplication, then modify the Home Controller as follows:
using Microsoft.AspNetCore.Mvc;
507
namespace HackerApplication.Controllers
return View();
}
Next, modify the Index.cshtml view of the Home Controller of HackerApplication as follows.
Here, you can see we are posting the form data to the Banking Application. We have a text
field to store the Account Number and a hidden field to store some hard-coded PIN. Once
the user clicks on the Claim My Gift Card button, the request will be submitted to the
Banking Application, where it will update the Account Number and PIN.
@{
<div class="container">
<div class="col-md-6">
<div class="text-center">
</div>
</div>
</form>
</div>
</div>
</div>
</div>
Now, run the application, enter your account number, and click the Claim Your Gift button,
as shown in the image below.
Once you click on the Claim Your Gift button, your account number pin is updated in the
Banking Application as shown in the below image:
509
This is nothing but a Cross-Site Request Forgery (CSRF or XSRF) attack on a website.
How can we prevent Cross-Site Request Forgery (CSRF or XSRF) Attack
in ASP.NET Core MVC?
To prevent Cross-Site Request Forgery (CSRF or XSRF) in ASP.NET Core MVC Web
Applications, we need to use AntiForgery Tokens. ASP.NET Core MVC uses AntiForgery
Tokens, also known as request verification tokens, to prevent CSRF attacks. These tokens
are unique to each user session and confirm that the user submitting a request is the one
who originally requested the page.
How Does It Work in ASP.NET Core?
When a form is rendered, ASP.NET Core MVC injects a hidden form field with
the anti-forgery token.
The user’s browser submits this token along with the form data.
Upon receiving the request, the server validates the token to ensure that the
request is legitimate.
How to Use AntiForgery Tokens in ASP.NET Core MVC?
To generate the anti-forgery token, we need to use the @Html.AntiForgeryToken() helper
method within the <form> tag. Then, we need to decorate the action method that handles
the posted form data with the [ValidateAntiForgeryToken] attribute. The
[ValidateAntiForgeryToken] attribute will ensure that the action method processes the
request only if it comes with a valid anti-forgery token.
From ASP.NET Core 2.0 onwards, the framework automatically generates anti-forgery
tokens for all forms by default, so we don’t need to use
the @Html.AntiForgeryToken() helper method. The framework validates the token
automatically when the action method is decorated with
the [ValidateAntiForgeryToken] attribute. If the token is missing or invalid, it rejects the
request. So, let us proceed and implement this in our Banking Application:
Modify the ChangePin.cshtml view of the Home Controller of our banking application as
follows. Here, we are adding the @Html.AntiForgeryToken() helper method within the
form body. The @Html.AntiForgeryToken() method generates a hidden input field
containing the anti-forgery token.
@{
510
<div>
@Html.AntiForgeryToken()
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
<div class="col-sm-10">
</div>
</div>
</form>
</div>
511
Next, modify the Home Controller of our Banking Application as follows. Here, we are
decorating the Post ChangePin action method with the ValidateAntiForgeryToken attribute.
The [ValidateAntiForgeryToken] attribute ensures the request includes a valid anti-forgery
or request verification token.
using Microsoft.AspNetCore.Mvc;
namespace BankingApplication.Controllers
[HttpGet]
return View();
[HttpPost]
[ValidateAntiForgeryToken]
return RedirectToAction("PinChangeSuccess");
return View();
}
512
}
With the above changes, run the Banking Application; you will see that the account number
pin updation functionality is working as expected. Now, run the Hacker Application and click
on the Claim My Gift Card button by entering the Account, and you should see the
following error page:
The anti-forgery token filed name is __RequestVerificationToken, and if you want, you
can also capture this field value in your application code. For example, modify the Home
Controller of Banking Application as follows:
using Microsoft.AspNetCore.Mvc;
namespace BankingApplication.Controllers
[HttpGet]
{
513
return View();
[HttpPost]
[ValidateAntiForgeryToken]
return RedirectToAction("PinChangeSuccess");
return View();
}
How AntiForgeryToken Generated and Validated in ASP.NET Core MVC?
ASP.NET Core generates a unique token called the AntiForgeryToken when a form is
rendered. This token is created using the @Html.AntiForgeryToken() helper method in
Razor views. The generated token consists of two parts: a hidden field in the form and a
cookie. The hidden field ensures the token is sent along with the form data during a POST
request while the cookie is stored in the user’s browser. The token is encrypted and
securely signed to ensure that it cannot be tampered with by an attacker. So, when we send
the request to the server, the token is sent using the hidden form field as well as through the
cookie header.
In ASP.NET Core MVC, when a form is submitted, the framework automatically validates
the AntiForgeryToken if the [ValidateAntiForgeryToken] attribute is applied to an action
method. During validation, the framework compares the token from the hidden field (sent in
the POST request) with the token coming from the request header cookie. If the tokens do
514
not match or are missing, ASP.NET Core will throw an exception, preventing the action
method from executing, thereby stopping the potential CSRF attack.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:09 / 03:1710 Sec
What is ORM?
The term ORM stands for Object-Relational Mapper, and it automatically creates classes
based on database tables and vice versa is also true. It can also generate the necessary
SQL to create the database based on the classes.
As a developer, we mostly work with data-driven applications, and the ORM Framework
generates the necessary SQL (to perform the CRUD operation) that the underlying
database can understand. So, in simple words, we can say that the ORM Framework
eliminates the need for most of the data access code that, as a developer, we generally
write.
Why Do We Need to Use An ORM Framework?
Let us understand why we need to use the ORM Framework with an example. Suppose we
want to develop an application to manage the students of a college. To do this, we may
need to create classes such as Student, Department, Address, etc. Technically, we called
these classes Domain classes or business objects.
Without using an ORM Framework like Entity Framework (.NET Framework) or EF Core
(.NET Core), we have to write lots of data access code to perform the CRUD operations,
i.e., store and retrieve the Student, Department, and Address data from the underlying
database tables.
For example, to perform CRUD operations, i.e., read, insert, update, or delete from a
database table, we generally need to write custom code in our application to generate the
required SQL statements, which the underlying database can understand. Again, when we
want to read the data from the database into our application, we also have to write some
custom code to map the data to our model classes like Student, Department, Address, etc.
This is a very common task as a developer for us that we do almost in every application.
An ORM Framework like Entity Framework or EF Core can do all of the above for us and
saves a lot of time if we provide the required information to the ORM Framework. The ORM
Framework sits between our application code and the Database. It eliminates the need for
most custom data-access codes we usually write without an ORM.
516
In the Code-First Approach, the EF Core creates the database and tables using Code-First
Migration based on the default conventions and configuration. You can also change the
default conventions used to create the database and its related tables. This approach is
useful in Domain-Driven Design (DDD), i.e., creating the database tables based on the
Domain classes.
EF Core Database First Approach:
If you have an existing database and database tables are already there, you must use the
EF Core Database First Approach. In the database-first approach, the EF Core creates the
DBContext and Domain Classes based on the existing database schema using EF Core
Command.
The EF Core database provider usually contains the functionality specific to the database it
supports.
1. Functionality common to all the databases is in the EF Core Component.
2. Functionality Specific to a Database, for example, Microsoft SQL Server-
Specific Functionality, is within the SQL Server Provider for EF Core.
Different Entity Framework Core Database providers are available for the different
databases. Some of them are as follows:
Azure SQL and SQL Server 2012 onwards: Microsoft.EntityFrameworkCore.SqlServer
SQLite 3.7 onwards: Microsoft.EntityFrameworkCore.Sqlite
EF Core in-memory database: Microsoft.EntityFrameworkCore.InMemory
PostgreSQL: Npgsql.EntityFrameworkCore.PostgreSQL
MySQL: MySql.EntityFrameworkCore
Oracle DB 11.2 onwards: Oracle.EntityFrameworkCore
For the complete list of EF Core Database Providers, please visit the following URL.
https://learn.microsoft.com/en-us/ef/core/providers/
Why We Need to Use Entity Framework Core over EF 6.x?
The EF 6.x is a stable and fully tested ORM technology in many .NET Framework
applications. EF Core also provides an experience similar to EF 6.x. However, it is built
upon a new set of components. The application targets .NET Core, e.g., ASP.NET Core
Applications. Entity Framework Core is the new and improved version of the Entity
Framework for .NET Core applications. EF Core continues to support the same features
and concepts as EF 6.
1. DbContext and DbSet
2. Data Model
3. Querying using Linq-to-Entities
4. Change Tracking
5. SaveChanges
6. Migrations
EF Core will include most of the features of EF 6 gradually. However, some features of EF 6
are not supported in EF Core, such as:
1. EDMX/ Graphical Visualization of Model
2. Entity Data Model Wizard (for DB-First approach)
3. ObjectContext API
4. Querying using Entity SQL.
5. Inheritance: Table per type (TPT)
6. Inheritance: Table per concrete class (TPC)
519
7. Entity Splitting
8. Spatial Data
9. Stored Procedure mapping with DbContext for CUD operation
10. Seed Data
EF Core includes the following new features which are not supported in EF 6.x:
1. Easy Relationship Configuration
2. Batch INSERT, UPDATE, and DELETE operations
3. In-memory provider for testing
4. Support for IoC (Inversion of Control)
5. Unique Constraints
6. Shadow Properties
7. Alternate Keys
8. Global Query Filter
9. Field Mapping
10. DbContext Pooling
11. Better Patterns for Handling Disconnected Entity Graphs
The Entity Framework Core is an updated and enhanced version of the Entity Framework
designed for .NET Core applications. While the EF Core is relatively new and not as fully
developed as the EF 6, it still supports its predecessor’s features and concepts. EF Core
offers new features that won’t be implemented in EF6. However, not all EF6 features are
currently implemented in EF Core. Please check the following link for more detailed
information.
https://learn.microsoft.com/en-us/ef/efcore-and-ef6/
Differences Between EF Core and EF6:
Entity Framework (EF) has been a popular ORM (Object-Relational Mapper) in the .NET
ecosystem. It has gone through two major iterations: EF6 and EF Core. Here are the
primary differences between the two:
Development Platform:
EF6: EF6 is a Windows-only framework within .NET, not compatible with
other platforms.
EF Core: EF Core is part of the .NET Core Ecosystem, which means it is
cross-platform and can be used in applications that run on Windows, Linux,
and macOS.
Performance:
EF6: Although EF6 is feature-rich and robust, it has been criticized for its
performance in certain scenarios, particularly when performing batch
operations.
EF Core: EF Core is a performance-focused framework, built from scratch.
It’s generally considered to be faster and more efficient than EF6.
Features:
1. EF6: EF6 is an ORM Framework with advanced features. Initially, some
features like Lazy Loading and Entity Graphs were not supported in EF, but
they were added later.
2. EF Core: EF Core started with a smaller feature set of features but then it
introduced many new features such as Global Query Filters and Shadow
Properties, etc.
520
Database Providers:
EF6: Although EF6 supports multiple database providers, integrating third-
party providers can be a challenge.
EF Core: EF Core has been designed to support various databases,
including NoSQL databases, with a more flexible provider model. You can
easily integrate third-party providers.
Configuration:
EF6: In Entity Framework 6, configuration can be done using either a code-
based or XML-based (in .config files) approach.
EF Core: EF Core uses code-based configuration instead of XML-based
configurations. The .NET Core philosophy is to move away from XML-based
configurations.
Community and Future:
EF6: Although EF6 is still maintained and receives updates, the primary focus
of the EF team and community is on EF Core.
EF Core: EF Core is the future of Entity Framework. It receives regular
updates, new features, and performance improvements.
If you’re working on an existing project that already using EF6 or one that is linked to
the .NET Framework, it’s still a good option to use EF 6. However, if you are developing
new projects, particularly projects targeting .NET Core or .NET 5/6+, EF Core is the best
choice. EF Core’s flexibility, performance improvements, and cross-platform capabilities
make it the perfect selection for modern application development.
Prerequisites to Learn Entity Framework Core:
To take full advantage of this Entity Framework Core Tutorials, you should have the basic
knowledge of C# and any database such as SQL Server, Oracle, or MySQL to gain more
knowledge of these tutorials. Having .NET Core, Visual Studio, and SQL Server installed on
your computer is good.
The Entity Framework Core is not a part of the .NET Core and standard .NET framework. It
is available as a NuGet package. We need to install the following two NuGet packages to
use EF Core in our application:
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem TV
1. EF Core DB Provider
2. EF Core Tools
Let’s install the above two NuGet Packages in the .NET Core Console application using
Visual Studio 2022. First, open Visual Studio 2022 and click “Create a new Project”, as
shown in the image below, to create a new .NET Console Application.
Then select the Console App, which is targeting the .NET Core, and then click on the Next
button, as shown in the below image. As I am using Windows OS, I have filtered the
language as C#, OS as Windows, and Application type as Console.
522
Once you click on the Next button, it will open the following Configure Your New Project
window. Here, you need to provide the Project Name (I am providing the project name
as EFCoreCodeFirstDemo), the location (I am creating in D:\EFCoreProjects) where you
want to create the project, and the solution name (I am keeping the same as the project
name) and then click on the Next button as shown in the below image.
Once you click on the Next button, it will open the Additional Information window. Here,
select the Target .NET Framework. I am selecting .NET 6, and I don’t want to use the top-
level statements, so I am checking the Do not use top-level statement checkbox and finally
clicking on the Create button, as shown in the image below.
Once you click the Create button, it will create the Console Application using .NET 6 with
the following structure.
523
As you can see, Entity Framework Core is not installed by default. So, let us proceed and
try to understand How to Install Entity Framework Core in .NET Core Application.
Installing Entity Framework Core in .NET Core Application:
Entity Framework Core is not part of the .NET Core Framework. So, we need to install
Entity Framework Core using NuGet packages to use Entity Framework. We need to install
the following two NuGet packages to use EF Core in our .NET Core Application:
1. EF Core DB Provider
2. EF Core Tools
Installing EF Core DB Provider:
As discussed in our previous article, Entity Framework Core allows us to access databases
via the provider model. Different Entity Framework Core Database providers are available
for the different databases. Some of them are as follows:
Azure SQL and SQL Server 2012 onwards: Microsoft.EntityFrameworkCore.SqlServer
SQLite 3.7 onwards: Microsoft.EntityFrameworkCore.Sqlite
EF Core in-memory database: Microsoft.EntityFrameworkCore.InMemory
PostgreSQL: Npgsql.EntityFrameworkCore.PostgreSQL
MySQL: MySql.EntityFrameworkCore
Oracle DB 11.2 onwards: Oracle.EntityFrameworkCore
For the complete list of EF Core Database Providers, please visit the following URL.
https://learn.microsoft.com/en-us/ef/core/providers/
These providers are available as NuGet packages. So, we need to install the NuGet
Package for the database provider we want to access. I will use Microsoft SQL Server as
the backend database in this course. So, we need to
install Microsoft.EntityFrameworkCore.SqlServer package from NuGet.
To Install Entity Framework Core using NuGet packages, Right click on the Project and then
click on the Manage NuGet Packages option from the context menu, as shown in the image
below. Alternatively, select Tools -> NuGet Package Manager -> Manage NuGet
Packages for Solution from the Visual Studio menus.
524
Once you select Manage NuGet Packages, it will open the NuGet Package Manager UI, as
shown in the image below. Select the Browse Tab, then search
for Microsoft.EntityFrameworkCore.SqlServer and then
select Microsoft.EntityFrameworkCore.SqlServer Package and select the Framework
Version that you want to Install. By default, the latest version will be selected, and currently,
the latest version of Microsoft.EntityFrameworkCore.SqlServer package is 7.0.9 (it might
be changed while you read this article). finally, click on the Install button as shown in the
below image.
Once you click on the Install Button, the Preview Changes window will pop up, showing the
list of packages it will install in your application. Review the changes and click the OK
button, as shown in the image below.
525
Once you click on the OK button, it will open the License Acceptance pop-up. So, finally,
accept the license terms associated with the packages that will be installed by clicking on
the “I Accept” button, as shown in the image below.
526
Alternatively, you can install the provider’s NuGet Package using the Package Manager
Console. Go to Tools -> NuGet Package Manager -> Package Manager Console and
527
then execute the following command to install the Entity Framework Core SQL Server
Provider package:
PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer
Note: If you want to use a different database with your application, install that provider-
specific NuGet Package instead of Microsoft.EntityFrameworkCore.SqlServer database
provider package.
For example, if you want to use MySQL as your database, install
MySql.EntityFrameworkCore database provider package. Along the same lines, if you want
to use PostgreSQL as your database, use NDevart.Data.PostgreSql.EFCore database
provider package. For the complete list of EF Core Database Providers, please visit the
following URL.
https://learn.microsoft.com/en-us/ef/core/providers/
What is the Role of EF Core DB Provider?
When you work with Entity Framework Core (EF Core), the database provider acts as a
bridge between the EF Core API and a particular database management system (DBMS).
The primary role of the DB provider is to convert the operations you define in EF Core into
commands that the underlying DBMS can understand because each DBMS has its own set
of features, rules, syntax, and configurations. The following tasks are going to be performed
by EF Core DB Provider.
Query Translation: When you execute a LINQ query using EF Core, it
doesn’t run as a LINQ query on objects in memory. Instead, EF Core converts
the query to SQL (or another suitable query language for the selected
DBMS). The DB provider is responsible for translating the query to ensure
that the generated SQL is going to be understood and executed by the
corresponding DBMS.
Change Tracking: EF Core monitors entity changes to entities retrieved from
the database and then generates appropriate SQL commands such as
INSERT, UPDATE, and DELETE for persistence based on the Entity State. If
the entity stated is Added, it will generate an INSERT SQL Statement, if the
entity state is Modified, it will generate an UPDATE SQL Statement and if the
entity state is Deleted, then it will generate the DELETE SQL Statement.
Connection Management: The DB Provider also manages tasks involving
database connections, including opening and closing connections as well as
managing transactions.
Migrations: With the Migrations feature of EF Core, we can easily manage
and version our database schema. The database provider will translate the
migration commands into SQL to modify the database schema ensuring that
the generated SQL is suitable for the target database. If you are using the
SQL Server Database Provider, then it will generate SQL Statements which
are going to be understood by the SQL Server Database. Similarly, If you are
using the MySQL Database Provider, then it will generate SQL Statements
that are going to be understood by the MySQL Database.
Database-Specific Features: Some databases have some features that
aren’t common across all DBMSs. For example, the SQL Statement we write
to get the TOP recorded in the SQL Server database is not the same as in the
MySQL Database. So, to guarantee smooth integration with the wider EF
Core API, the DB provider can give access to these exceptional features to
EF Core.
528
Once you click on the Install Button, the Preview Changes window will pop up, showing the
list of packages that will be installed. Simply click on the OK button as shown in the below
image.
529
Once you click on the OK button, it will open the License Acceptance pop-up, and you need
to click on the “I Accept” button, as shown in the image below.
530
Alternatively, you can install the EF Core Tool Package using the Package Manager
Console. Go to Tools -> NuGet Package Manager -> Package Manager Console and
then execute the following command to install the Entity Framework Core Tool package:
PM> Install-Package Microsoft.EntityFrameworkCore.Tools
What is the Role of EF Core Tools?
Entity Framework Core (EF Core) Tools provide a set of command-line tools and .NET CLI
tools that assist developers in tasks related to the Entity Framework. These tools simplify
tasks like creating migrations, updating the database with migrations, querying the
database, and more. The primary roles of EF Core Tools are as follows:
Database Migrations:
Creating Migrations: Generate code files that represent the model changes,
which can be applied to the database schema.
Updating the Database: Apply migrations to update the database schema to
the latest version of the migration.
Removing Migrations: Remove the latest migration, allowing you to make
additional changes before regenerating the migration.
Generating SQL Scripts: Produce SQL scripts from migrations and this is
useful when direct database updates are not feasible (e.g., in strict production
environments).
Database Scaffolding: EF Core Tools can create code-first model files from an existing
database. This is useful when we have an existing database and want to represent it as a
code-first model in our application.
View DbContext Model: EF Core Tools enable creating a visual representation (DGML file)
of DbContext to understand entity relationships and structure.
EF Core Tools are available as PowerShell commands (in the Visual Studio Package
Manager Console) and as .NET CLI commands. Some commonly used commands are as
follows:
PowerShell Commands (Package Manager Console in Visual Studio):
1. Add-Migration
2. Update-Database
3. Remove-Migration
4. Scaffold-DbContext
.NET CLI Commands:
1. dotnet ef migrations add
2. dotnet ef database update
532
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:07 / 03:1710 Sec
Object Set Representation: DbContext represents a session with the
database that allows querying and saving of entity classes via
DbSet<TEntity> properties for each entity type in the model.
Change Tracking: When retrieving data from the database using the
DbContext, the changes made to that data are tracked by the DbContext. If
any modifications, the entity’s properties are updated, DbContext keeps track
of those changes. This enables EF Core to generate appropriate SQL
statements to reflect the database changes accurately.
Database Operations: Using methods on DbContext, such as
SaveChanges(), we can insert, update, or delete records in the database.
That means the changes we make to our entities will be updated in the
database when we call the SaveChanges() method of the DbContext object.
Configuration: The DbContext allows for the configuration of the database
connection. It also tells how models are mapped to database schemas,
caching policies, etc. We need to do this by overriding the OnConfiguring or
OnModelCreating methods in our DbContext class (A class that is inherited
from the DbContext class).
533
using System.ComponentModel.DataAnnotations.Schema;
namespace EFCoreCodeFirstDemo.Entities
{
534
[Column(TypeName = "decimal(18,4)")]
[Column(TypeName = "decimal(18,4)")]
}
Next, create another class file within the Entities folder named Standard.cs, and then copy
and paste the following code. As you can see, here, we are creating the Standard class with
few scalar properties and one collection Navigation property called Students, which makes
the relationship between Standard and Student entities one-to-many.
namespace EFCoreCodeFirstDemo.Entities
}
535
Now, we are done with the initial domain classes for our application. Later, as we progress,
we will add more domain classes to this example.
How to Create a DbContext Class in Entity Framework Core?
The main class that coordinates Entity Framework Core functionality for a given data model
is the database context class, which allows to query and save data. The Entity Framework
Core Code-First Approach requires a user-defined context class, which should be derived
from the DbContext class.
To use the DbContext class in our .NET Application, we must create a class derived from
the DbContext class. The DbContext class is present
in Microsoft.EntityFrameworkCore namespace. The Entity Framework
Core DbContext class includes a property, i.e., DbSet<TEntity>, for each entity in your
application.
So, create a class file within the Entities folder named EFCoreDbContext.cs and then copy
and paste the following code into it. You can give any name for your context. But the class
should and must be derived from the DbContext class and exposes DbSet properties for the
types you want to be part of the model. For example, Student and Standard domain classes
in this case. The DbSet is a collection of entity classes (i.e., entity set). Here, we have given
the property name in the plural form of the entity name, like Students and Standards, as
Microsoft recommends. The DbContext class belongs to Microsoft.EntityFrameworkCore
namespace.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
}
536
//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API
}
As you can see in the above code, the EFCoreDbContext class is derived from the
DbContext class and contains the DbSet<TEntity> properties of Student and Standard type.
The parameterless constructor of our context class is called the base DbContext Class
constructor. It also overrides the OnConfiguring and OnModelCreating methods. We must
create an instance of the EFCoreDbContext class to connect to the database and save or
retrieve Student or Standard data.
The OnConfiguring() method allows us to select and configure the data source to be used
with a context using DbContextOptionsBuilder. In our upcoming article, we will discuss more
about this method.
The OnModelCreating() method allows us to configure the model using ModelBuilder Fluent
API. Again, in our upcoming article, we will discuss more about this method.
Note: we have included two model classes as DbSet properties, and the entity framework
core will create two database tables for the above two model classes with the required
relationships.
DbContext Methods in Entity Framework Core:
1. Add: Adds a new entity to DbContext with an Added state and starts tracking
it. This new entity data will be inserted into the database when
SaveChanges() is called.
2. AddAsync: Asynchronous method for adding a new entity to DbContext with
Added state and starts tracking it. This new entity data will be inserted into the
database when SaveChangesAsync() is called.
3. AddRange: Adds a collection of new entities to DbContext with Added state
and starts tracking it. This new entity data will be inserted into the database
when SaveChanges() is called.
4. AddRangeAsync: Asynchronous method for adding a collection of new
entities, which will be saved on SaveChangesAsync().
537
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:16 / 03:1710 Sec
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
public EFCoreDbContext()
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB1;Trusted_Connection=True;TrustServerCertif
icate=True;");
//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API
}
As you can see, the context class includes two DbSet<TEntity> properties for Student and
Standard, which will be mapped to the Students and Standards tables in the underlying
SQL Server database. In the OnConfiguring() method, an instance of
DbContextOptionsBuilder is used to specify which database to use. We have installed MS
SQL Server provider, which has added the extension method UseSqlServer on
DbContextOptionsBuilder.
The connection string “Server=LAPTOP-6P5NK25R\SQLSERVER2022DEV;
Database=EFCoreDB1; Trusted_Connection=True; TrustServerCertificate=True;” in
the UseSqlServer method provides database information:
1. Server= specifies the DB Server to use,
2. Database= specifies the name of the database to create
3. Trusted_Connection=True specifies the Windows authentication mode.
4. TrustServerCertificate=True specifies the transport layer will use SSL to
encrypt the channel and bypass walking the certificate chain to validate trust.
540
EF Core will use this connection string to create a database when we run the migration.
After Creating the Context Class and Entity Classes and specifying the Database
Connection String, it’s time to add the migration to create a database.
Adding Entity Framework Core Migration:
Entity Framework Core Includes different migration commands to create or update the
database based on the model. At this point, there is no EFCoreDB1 database in SQL
Server. So, we need to create the database from the model (Entities and Context) by
adding a migration.
We can execute the migration command using the NuGet Package Manager Console and
the dotnet CLI (command line interface) command.
In Visual Studio, open the NuGet Package Manager Console. To launch Package Manager
Console, select Tools => NuGet Package Manager => Package Manager Console from
the menu below.
This will open the Package Manager Console. Now, type the add-migration
CreateEFCoreDB1 command, select the project where your Context class is, and press the
enter button, as shown in the image below.
541
Once the above code is executed successfully, it will create a new folder
named Migrations in the project and create the ModelSnapshot files, as shown below. In
our upcoming article, we will discuss Migration files in detail.
Note: This was the first migration to create a database. The most important point is that
whenever we add or update domain classes or configurations, we need to sync the
database with the model using add-migration and update-database commands.
542
namespace EFCoreCodeFirstDemo
try
FirstName = "Pranaya",
LastName = "Rout",
Height = 5.10M,
Weight = 50
};
//Add the Student into context object using DbSet Property and Add method
context.Students.Add(student);
//Call the SaveChanges method to make the changes Permanent into the Database
543
context.SaveChanges();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Once you run the above code, you should get the following output.
Now, if you go and verify the database, you should see the newly added student inside the
Students database table, as shown in the image below.
Note: Hardcoding the application code’s connection string is a bad programming practice.
In real-time applications, we need to store the connection string in the appsettings.json file,
and from the configuration file, we need to read the connection string. But by default, the
appsettings.json file is unavailable in the Console Application. So, let us proceed and
understand the steps required to use the appsettings.json file in the Console Application,
How to use AppSettings.json files in .NET Core Console Application?
For the .NET Framework Console Application, we always use the app.config file to store our
configuration values for the application, such as Connection Strings, Application Level
Global Variables, etc. In .NET Core, instead of App.Config file: we need to use the
appsettings.json file. But, by default, this appsettings.json file is available in ASP.NET Core
Framework Applications. But, by default, not available for the Console Application. Let us
544
proceed and see how we can create and use the appsettings.json file in the .NET Core
Console Application.
To use the appsettings.json file in the Console Application, we need to install the following
package from NuGet either by using NuGet Package Manager UI or Package Manager
Console.
Microsoft.Extensions.Configuration.Json
So, open NuGet Package Manager UI by selecting Tools => NuGet Package Manager =>
Manage NuGet Packages for Solution from the Visual Studio Menu, which will open the
following window. Select the Search tab from this window, then search for
the Microsoft.Extensions.Configuration.Json package, select the package, and click the
Install button, as shown in the image below.
Once you click the Install button, it will open the Preview Changes window to tell what
packages (including the dependency packages) will be installed. Review the changes and
click the OK button in the image below.
545
Once you click on the OK button, it will install the package, and you can verify the package
inside the Dependencies => Packages folder of your project, as shown in the image below.
Once you add the appsettings.json file, please open it and copy and paste the following
code. Here, we are adding the database connection string.
{
"ConnectionStrings": {
"SQLServerConnection": "Server=LAPTOP-6P5NK25R\\
SQLSERVER2022DEV;Database=EFCoreDB1;Trusted_Connection=True;TrustServerCertif
icate=True;"
}
How to Fetch the Connection String from the appsettings.json file?
We must follow the steps to get the Connection String from the appsettings.json file.
Step 1: Import the following namespace.
using Microsoft.Extensions.Configuration;
Step 2: Load the Configuration File.
var configBuilder = new
ConfigurationBuilder().AddJsonFile(“appsettings.json”).Build();
Step 3: Get the Section to Read from the Configuration File
var configSection = configBuilder.GetSection(“ConnectionStrings”);
Step 4: Get the Configuration Values based on the Config key.
var connectionString = configSection[“SQLServerConnection”] ?? null;
So, modify the EFCoreDbContext class to read the connection string from the
appsettings.json file. The following code is self-explained, so please go through the
comment lines for a better understanding.
547
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(connectionString);
//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API
}
Next, modify the Main method of the Program class as shown below.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
FirstName = "Pranaya",
LastName = "Rout",
Height = 5.10M,
Weight = 50
};
//Add the Student into context object using DbSet Property and Add method
context.Students.Add(student);
//Call the SaveChanges method to make the changes Permanent into the Database
context.SaveChanges();
Console.WriteLine($"Error: {ex.Message}"); ;
}
With the above changes in place, now run the application, and you should get the following
Runtime Exception.
The above exception clearly says that the appsettings.json file was not found inside the
project bin=>debug=>.net6.0 folder. That means we need to ensure that once we build the
project, the appsettings.json should be stored inside the above location. To do so, Right-
click the appsettings.json file to show its Properties window. Then, set its “Copy to Output
Directory” property to “Copy always.” This ensures the appsettings.json file is copied to the
Debug or Release folder every time the project is built.
So, with the above changes in place, build the project and run the application, and you
should get the output as expected, as shown in the below image.
method is called on the DbContext instance. The following diagram shows the connected
scenario’s CUD (Create, Update, Delete) operations.
Note: In the connected scenario, the same DbContext instance keeps track of all the
entities, and when an entity is created, modified, or deleted, it automatically sets the
appropriate State for the entity.
Enabling EF Core Logging:
We often need to see or log the generated SQL Script and Change Tracking information for
debugging purposes. Entity Framework Core logging automatically integrates with the
logging mechanisms of .NET Core.
We can use the simple Entity Framework Core (EF Core) logging mechanism to log the
generated SQL while developing and debugging applications. This form of simple logging
requires minimal configuration and no additional NuGet packages.
EF Core Logs can be accessed using the LogTo method, and we need to configure this
method within the OnConfiguring method of the DbContext class. So, using
the DbContextOptionsBuilder instance, we need to call the LogTo method and tell where
to show the generated logs (Console.Write in our example) and what kind of logs
(Information, Trace, Debug, Error, Warning, etc.) you want to display. So, please modify the
EFCoreDbContext class as shown below to display the EF Core Log Information on the
Console window.
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB1;Trusted_Connection=True;TrustServerCertif
icate=True;");
//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API
}
553
}
Inside the OnConfiguring method, we have added the following statement. The following
statement will log the generated SQL Script on the Console window.
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
Create Operation using Entity Framework Core:
Create Operation means we need to add a new object. Adding a new object using Entity
Framework Core is very simple. First, you need to create an instance of the Entity you want
to add to the database. Once you created the instance of the entity, then you need to call
the Add Method of the DbSet or DbContext class and pass the entity. Finally, call
the SaveChanges method on the DbContext object, which will insert the new record into
the database by generating the INSERT SQL Statement.
Note: The DbSet.Add and DbContext.Add methods add a new entity to a context
(instance of DbContext), which will insert a new record in the database when you call the
SaveChanges() method.
For a better understanding, modify the Main method of the Program class as follows. In the
below example, context.Students.Add(newStudent) adds the newly created student entity
to the context object with Added Entity State. EF Core introduced the
new DbContext.Add method, which does the same thing as the DbSet.Add method. Most
often, we are going to use the DbContext.Add by omitting the type parameter because the
compiler will infer the type from the argument passed into the method. I have shown you
three ways to add the student entity to the context object here.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
FirstName = "Pranaya",
554
LastName = "Rout",
Height = 5.10m,
Weight = 72
};
context.Students.Add(newStudent);
//Method2: Add Student Entity Using DbContext.Add Method with Type Parameter
//context.Add<Student>(newStudent);
//Method2: Add Student Entity Using DbContext.Add Method without Type Parameter
//context.Add(newStudent);
context.SaveChanges();
//Now the Entity State will change from Added State to Unchanged State
Console.WriteLine($"Error: {ex.Message}"); ;
Console.ReadKey();
}
With the above changes, run the application and get the following output.
If you verify the SQL Server database, you will see that the above entity is inserted into the
Students database table, as shown in the image below.
namespace EFCoreCodeFirstDemo
{
556
try
if(student != null)
student.FirstName = "Prateek";
student.LastName = "Sahu";
context.SaveChanges();
//Now the Entity State will change from Modified State to Unchanged State
else
Console.WriteLine($"Error: {ex.Message}"); ;
Console.ReadKey();
}
In the above example, first, we retrieve the student from the database using the DbSet
Find method, which generates a SELECT SQL query. When we modify the FirstName and
LastName, the context object sets its Entity State to Modified. When we call the
SaveChanges() method, it builds and executes the Update SQL statement in the database.
Run the above code, and you will get the following output.
Note: The context object in Entity Framework Core keeps track of the columns of an entity
that are modified, and based on the modified columns, it will generate the UPDATE SQL
statement. Here, it will use the primary key column in the where condition. If one column
558
value is not updated, it will not include that column while updating the database. So you can
see the UPDATE statement. It only includes the First Name and Last Name columns.
Delete Operation in Entity Framework Core:
We need to use the Remove method of the DbSet or DbContext object to delete an entity
using Entity Framework Core. The DbSet or DbContext Remove method works for existing
and newly added entities tracked by the context object.
Calling the Remove method on an existing entity being tracked by the context object will not
immediately delete the entity from the database. Rather, it will mark the entity state as
Deleted. When we call the SaveChanged method, the Entity will be deleted from the
database by generating and executing the DELETE SQL Statement. Once the
SaveChanges method is executed successfully, it will mark the entity state as Detached,
which means the context object will not track the entity.
In the following example, we remove a student from the database whose StudentId is 1.
The following example code is self-explained, so please go through the comment lines.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
if (student != null)
context.Students.Remove(student);
//context.Remove<Student>(student);
//context.Remove(student);
context.SaveChanges();
else
Console.WriteLine($"Error: {ex.Message}"); ;
Console.ReadKey();
}
560
}
In the above example, context.Students.Remove(student) marks the student entity state
as Deleted. When we call the SaveChanges method on the Context object, the Entity
Framework Core API generates and executes the DELETE SQL Statement in the database.
Then, it will mark the entity state as Detached, which means the context object is no longer
tracking this entity. Run the above application code, and you should get the following
output.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:18 / 03:1710 Sec
The Entity State represents the state of an entity. An entity is always in any one of the
following states.
1. Added: The entity is tracked by the context but does not yet exist in the
database.
2. Deleted: The entity is tracked by the context and exists in the database. It
has been marked for deletion from the database but has not yet been
deleted.
3. Modified: The entity is tracked by the context and exists in the database.
Some or all of its property values have been modified but not updated in the
database.
4. Unchanged: The entity is tracked by the context and exists in the database.
Its property values have not changed from those in the database.
5. Detached: The entity is not being tracked by the context.
The Context object not only holds the reference to all the entity objects as soon as retrieved
from the database but also keeps track of entity states and maintains modifications to the
entity’s properties. This feature is known as Change Tracking.
What is Change Tracking?
Entity Framework Core (EF Core) uses the concept called change tracking to determine
what operations (like insert, update, or delete) should be executed when the
SaveChanges() method is called. It uses something called “Entity States” to determine what
operations to be done. Each entity tracked by the DbContext has a state, which indicates
how the entity should be processed during a SaveChanges() operation.
Entity Lifecycle in Entity Framework Core
When working with Entity Framework Core (EF Core), it’s important to understand the entity
lifecycle clearly. The entity lifecycle involves different states and transitions that determine
how entities interact with the DbContext and, ultimately, to the database. Understanding the
Entity lifecycle is crucial for maintaining data integrity in your applications.
562
The change in the entity state from the Unchanged to the Modified state is the only state
automatically handled by the context class. All other changes must be made explicitly using
the proper DbContext class methods. The following diagram shows the different states of an
entity in the Entity Framework.
namespace EFCoreCodeFirstDemo
try
FirstName = "Pranaya",
LastName = "Rout",
Height = 5.10m,
Weight = 72
};
context.Students.Add(newStudent);
//context.Add<Student>(newStudent);
//context.Add(newStudent);
context.SaveChanges();
//Now the Entity State will change from Added State to Unchanged State
Console.WriteLine($"Error: {ex.Message}"); ;
Console.ReadKey();
}
Output:
If you verify the database, you will see the above entity is being added to the Students
database table, as shown below. Please note the Student ID, which we will use in our next
examples.
namespace EFCoreCodeFirstDemo
{
565
try
if (student != null)
context.SaveChanges();
//Check the Entity State After Calling the SaveChanges Method of Context Object
else
Console.WriteLine($"Error: {ex.Message}"); ;
566
Console.ReadKey();
}
Output:
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
StudentId = 2,
FirstName = "Pranaya",
LastName = "Rout",
Height = 5.10m,
Weight = 72
};
context.Students.Attach(existingStudent);
//context.Attach<Student>(existingStudent);
//context.Attach(existingStudent);
//Or you can also attach an existing entity as follows by updating the entity state
//context.Entry(existingStudent).State = EntityState.Unchanged;
context.SaveChanges();
//Check the Entity State After Calling the SaveChanges Method of Context Object
}
568
Console.WriteLine($"Error: {ex.Message}"); ;
Console.ReadKey();
}
Output:
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
{
569
FirstName = "Rakesh",
LastName = "Kumar",
Height = 5.10m,
Weight = 72
};
context.Students.Attach(existingStudent);
//context.Attach<Student>(existingStudent);
//context.Attach(existingStudent);
//Or you can also attach an existing entity as follows by updating the entity state
//context.Entry(existingStudent).State = EntityState.Unchanged;
context.SaveChanges();
//Check the Entity State After Calling the SaveChanges Method of Context Object
Console.WriteLine($"Error: {ex.Message}"); ;
Console.ReadKey();
}
Output:
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
{
571
try
if (student != null)
context.Entry(student).State = EntityState.Detached;
context.SaveChanges();
//Check the Entity State After Calling the SaveChanges Method of Context Object
else
572
Console.WriteLine($"Error: {ex.Message}"); ;
Console.ReadKey();
}
Output:
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
{
573
try
if (student != null)
context.SaveChanges();
else
}
574
Console.WriteLine($"Error: {ex.Message}"); ;
Console.ReadKey();
}
Output:
Note: The Entity framework keeps track of the properties that have been modified. The
Columns in the Update statement are set for only those columns whose values are
modified.
Deleted State in EF Core with Example
The entity will be removed from the context and marked as a “Deleted” state whenever we
call the Remove method. When the SaveChanges method is called, the corresponding row
is deleted from the database.
The Deleted entity state indicates that the entity is marked for deletion but not yet deleted
from the database. It also indicates that the entity exists in the database. The DbContext
generates the DELETE SQL statement to remove the entity from the database. The entity is
removed from the context once the delete operation succeeds after the SaveChanges.
For a better understanding, please have a look at the following example. The following
example code is self-explained, so please go through the comment line.
using EFCoreCodeFirstDemo.Entities;
575
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
if (student != null)
context.Students.Remove(student);
//context.Remove<Student>(student);
//context.Remove(student);
context.SaveChanges();
else
Console.WriteLine($"Error: {ex.Message}"); ;
Console.ReadKey();
}
Output:
executing the INSERT SQL Statement, and once the SaveChanges method
execution is successful, then those entities are moved from Added to the
Unchanged state.
3. If the entities are in Modified State, then those entities are updated in the
database when the SaveChanges method is called by generating and
executing the UPDATE SQL Statement. Once the SaveChanges method
execution is successful, those entities are moved from Modified to
Unchanged.
4. If the entities are in Deleted State, then those entities are deleted from the
database when the SaveChanges method is called by generating and
executing the DELETE SQL Statement. Then, they are detached from the
context object, i.e., their state is no longer maintained by the context object.
Note: The point that you need to remember is Entity Framework builds and executes the
INSERT, UPDATE, and DELETE commands based on the state of an entity when the
SaveChanges() method is called. It generates and executes the INSERT command for the
entities with the Added state, generates and executes the UPDATE command for the
entities with the Modified state, and generates and executes the DELETE command for the
entities in the Deleted state.
using System.ComponentModel.DataAnnotations.Schema;
namespace EFCoreCodeFirstDemo.Entities
{
578
[Column(TypeName = "decimal(18,4)")]
[Column(TypeName = "decimal(18,4)")]
}
Standard.cs
namespace EFCoreCodeFirstDemo.Entities
}
579
Context Class:
The following is our EFCoreDbContext class.
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB1;Trusted_Connection=True;TrustServerCertif
icate=True;";);
//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API
}
LINQ to Entities Queries in Entity Framework Core
Please ensure the following records are inside the Students and Standards table.
Please use the below SQL Scripts to create and populate the above in the database.
USE EFCoreDB1
GO
GO
GO
GO
LINQ-to-Entities Queries in Entity Framework Core:
The Entity Framework Core DbSet class is derived from the IQuerayable interface. As the
DbSet class is derived from the IQuerayable interface, we can use LINQ (Language
Integrated Query) for querying against DbSet, and the DbSet then covert the queries into
the SQL queries that the underlying database can understand and execute and gets the
result set, converts it into appropriate entity type objects and returns it as a query result.
What is LINQ?
The LINQ (Language Integrated Query) is part of a language but not a complete language.
Microsoft introduced LINQ with .NET Framework 3.5 and C# 3.0, available in
the System.Linq namespace.
LINQ provides a Common Query Syntax that allows us to query the data from various
sources. Using a single query, we can get or set the data from various data sources such as
SQL Server database, XML documents, ADO.NET Datasets, and other in-memory objects
such as Collections, Generics, etc. Please read the Architecture of LINQ article.
What is Projection?
Projection is nothing but the mechanism which is used to select the data from a data
source. You can select the data in the same form (i.e., the original data in its original state).
It is also possible to create a new form of data by performing some operations on it.
For example, in the database table, there is a Salary column. If you want, you can get the
Salary Column Values as it is, i.e., in their original form. But, if you want to modify the
Salary column values by adding a bonus amount of 10,000, then it is also possible. Don’t
worry if this is unclear now; we will understand this with real-time examples.
LINQ Query Methods:
The LINQ Standard Query Methods are implemented as Extension Methods, and those
methods can be used with LINQ-to-Entities queries. The following are examples of some of
the standard LINQ Query methods.
1. First() or FirstOrDefault()
2. Single()or SingleOrDefault()
3. ToList()
4. Count()
5. Min() and Max()
6. Sum()
7. Distinct()
8. Last() or LastOrDefault()
9. Average(), and many more
582
Note: Besides the above LINQ Extension methods, you can use the DbSet Find() method
to search an entity based on the primary key value.
DbSet Find() Method in Entity Framework Core:
The Find method belongs to the DbSet class. This method finds an entity with the given
primary key values. Suppose an entity with the given primary key values exists in the
context object. In that case, it is returned immediately without requesting the database, i.e.,
EF Core implements the first-level caching. If the value does not exist in the context object,
a request is made to the database to fetch the entity with the given primary key values, and
this entity, if found, is attached to the context and returned. Null is returned if no entity is
found in the context object or the database.
Example to Understand DbSet Find() Method in EF Core:
In our example, EFCoreDbContext is our DbContext class, and Students is the DbSet
property, and we want to find the student whose ID is 1. Here, StudentId is the primary key
in our Students table. So, modify the Main method of the Program class as follows.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
//It will return the data from the database by executing SELECT SQL Statement
//It will return the data from the context object as the context object tracking the same data
//It will return the data from the database by executing SELECT SQL Statement
Console.WriteLine($"Error: {ex.Message}"); ;
}
First or FirstOrDefault Method in EF Core
These two methods are used when we want to fetch a single entity from the list of entities.
For example, it might be possible that there is more than one student with the FirstName
Pranaya, but our requirement is only to fetch the first student whose FirstName is Pranaya.
In this case, we need to use the First or FirstOrDefault method, as shown in the example
below. Again, we can write the LINQ queries in two different ways, i.e., using Method
Syntax and using Query Syntax,
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
584
select s).FirstOrDefault();
select s).First();
{
585
Console.WriteLine($"Error: {ex.Message}"); ;
}
Differences Between First and FirstOrDefault Methods in LINQ:
First and FirstOrDefault methods in LINQ return the first element from a data source. But if
the data source is null or the specified condition does not return any data, then the First
method will throw an exception while the FirstOrDefault method will not throw an exception;
instead, it returns a default value based on the element’s data type.
In our database, the Students table does not have any student with the name Smith, and if
we try to fetch the student whose FirstName is Smith using the LINQ First method, then it
will throw an exception, as shown in the below example. But, if we use the FirstOrDefault
method, it will not throw an exception; rather, it will return the default value of the Student
type, i.e., null.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output: Error: Sequence contains no elements
Parameterized Query in Entity Framework Core:
The Entity Framework Core can build and execute a parameterized query in the database if
the LINQ-to-Entities query uses parameters. For a better understanding, please have a look
at the following example. In the below example, we use the parameter FirstName.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
{
587
//Creating a Variable
Console.WriteLine($"Error: {ex.Message}"); ;
}
Run the above code, and you should get the following output. Please observe the
generated SELECT SQL Statement; it uses the parameter @__FirstName_0.
namespace EFCoreCodeFirstDemo
try
Console.WriteLine($"Error: {ex.Message}"); ;
}
589
}
Note: Along with ToList, you can also use ToArray, ToDictionary, or ToLookup per your
business needs.
Note: Deferred execution is a key feature of LINQ. The query is executed only when
iterating over the query results. Methods like ToList(), ToArray(), Count(), and First() cause
the query to execute immediately.
LINQ OrderBy Method:
We use the OrderBy method with ascending/descending keywords in the LINQ query to get
the sorted entity list. In simple terms, we can say that Ordering is a process to manage the
data in a particular order. It does not change the data or output. Rather, this operation
arranges the data in a particular order, i.e., ascending or descending. In this case, the count
will be the same, but the order of the elements will change.
In the following example, we sorted the students based on their First Names. Here, I am
showing you how to use Order By using the Method and Query syntax.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
select s;
Console.WriteLine($"Error: {ex.Message}"); ;
}
Anonymous Object Result:
Instead of projecting the data to a particular type like Student, we can also project the data
to an anonymous type using LINQ-to-Entities queries. Here, you may choose some or all of
an entity’s properties.
The following example returns a list of anonymous objects that contain only the FirstName,
LastName, and Height properties. The projection Result in the example below will be
anonymous because no class/entity has these properties. So, the compiler will mark it as an
anonymous type.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
{
591
try
select new
firstName = std.FirstName,
lastName = std.LastName,
height = std.Height
});
{
592
firstName = std.FirstName,
lastName = std.LastName,
height = std.Height
}).ToList();
Console.WriteLine($"Error: {ex.Message}"); ;
}
LINQ Join Method:
The LINQ Inner join returns only the matching records from both the data sources while the
non-matching elements are removed from the result set. So, if you have two data sources,
let us say Students and Standards, and when you perform the LINQ inner join, only the
matching records, i.e., the records in both Students and Standards tables, are included in
the result set. For a better understanding, please have a look at the following example.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
{
593
try
.Join(
(student, standard) => new //Projecting the data into an anonymous type
StandrdId = standard.StandardId,
StandardDescriptin = standard.Description,
StudentHeight = student.Height
}).ToList();
{
594
select new
StandrdId = standard.StandardId,
StudentHeight = student.Height
}).ToList();
Console.WriteLine($"Error: {ex.Message}"); ;
}
595
}
Note: You need to remember that there are no differences in the generated SQL whether
you use Method syntax or Query Syntax using LINQ to Entities query.
Entity Framework Core New Features for Querying:
Let us learn the new querying features introduced in Entity Framework Core.
C#/VB.NET Functions in Queries
Entity Framework Core has a new feature in LINQ-to-Entities where we can include C# or
VB.NET functions in the query. This was not possible in EF 6. For a better understanding,
please have a look at the following example.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
.ToList();
Console.WriteLine($"Error: {ex.Message}"); ;
return "Pranaya";
}
In the above example, we have included the GetName() C# function in the Where clause.
Run the above code, and you should get the following output.
Note: LINQ to Entities in EF Core provides a convenient, type-safe way to create database
queries. EF Core then translates the queries into corresponding SQL queries based on the
EF Core Database Provider and backend database. However, it’s important to consider
performance issues like the N+1 query problem and optimize complex queries by examining
the generated SQL.
}
Standard.cs
namespace EFCoreCodeFirstDemo.Entities
598
}
StudentAddress.cs
using System.ComponentModel.DataAnnotations;
namespace EFCoreCodeFirstDemo.Entities
[Key]
}
599
}
Course.cs
namespace EFCoreCodeFirstDemo.Entities
}
Teacher.cs
namespace EFCoreCodeFirstDemo.Entities
}
600
}
Modifying the Context Class:
Modify the EFCoreDbContext class as follows:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB1;Trusted_Connection=True;TrustServerCertif
icate=True;");
//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API
}
Note: As we already discussed, whenever we add or update domain classes or
configurations, we need to sync the database with the model using add-
migration and update-database commands using Package Manager Console or .NET
Core CLI. In Visual Studio, open the NuGet Package Manager Console. To launch Package
Manager Console, select Tools => NuGet Package Manager => Package Manager
Console from the menu below.
602
This will open the Package Manager Console. Now, type the add-migration
CreateEFCoreDB2 command, select the project where your Context class is, and press the
enter button, as shown in the image below.
You will get the following message once the above command is executed
successfully.
We are getting one warning that an operation was scaffolded, possibly resulting in data
loss. Please review the migration for accuracy. So, he is saying that before updating the
database, please review the migration because there might be some data loss. You can
ignore this at this moment.
Once this command is executed successfully, you will see that another migration class file
will be created with the current date and time, along with the name you provided in the
603
migration command. You can see this class file within the Migrations folder, as shown in the
below image.
Don’t think the database is created after creating the migration file using the add-migration
command. We still need to update the database using the Update-Database command. We
can use the –verbose option to view the SQL statements executed in the target database.
So, open the Package Manager Console and execute the Update-Database –
verbose command, as shown in the image below.
Once the above script is executed successfully, you can verify the database table using
SSMS, as shown in the image below.
604
USE EFCoreDB1;
GO
GO
605
GO
GO
GO
GO
GO
How many ways can we load the Related Entities in Entity Framework
Core?
In Entity Framework Core, we can load the related entities in three ways. They are Eager
Loading, Lazy Loading, and Explicit Loading. All these three techniques, i.e., Eager
Loading, Lazy Loading, and Explicit Loading, refer to loading the related entities of the main
entity. The only difference is they define when to load the related or child entities of the
main entity.
First, we must understand what is related or child entities in the Entity Framework Core. For
a better understanding, please look at the following Student Entity. Inside this Student
Entity, we have three navigation properties, i.e., Standard and StudentAddress as
Reference Navigation Property and Courses as Collection Navigation Property. These
607
Navigation properties are nothing but pointing to some other Entities. These other entities
are nothing but the related entities of the Student Entity. Related entities are entities created
using Foreign Keys.
When loading the Student entity, i.e., retrieving the data from the Student database table,
how can we retrieve the related entities, i.e., Standard, StudentAddress, and Courses? We
can load these related entities in three ways while retrieving the Student entity. They
are Eager Loading, Lazy Loading, and Explicit Loading. In this article, we will discuss
Eager Loading in detail, and in our next two articles, we will discuss Lazy
Loading and Explicit Loading with Examples.
What is Eager Loading in Entity Framework Core?
Eager Loading in Entity Framework Core loads related entities whenever we load the main
entity in a single query. This helps reduce the number of database queries or round-trips
and improves performance by retrieving all necessary data in one go. This approach is
efficient as it saves bandwidth and server CPU time. Eager loading is especially useful
when we want to access properties of related entities without triggering additional database
requests. Entity Framework Core achieves this by using JOINs to load the related entities
instead of separate SQL queries.
How to Implement Eager Loading in EF Core?
Entity Framework Core supports Eager Loading of related entities, same as EF 6, using
the Include() extension method. In addition to this, it also provides
the ThenInclude() extension method to load multiple levels of related entities. (EF 6 does
not support the ThenInclude() method)
Example to Understand Eager Loading in EF Core:
What our requirement is, when loading the Student entities, we also need to eager-load the
corresponding Standard entities. The students and their Standards will be retrieved in a
single query using the SQL join.
In the following example, we are fetching all the students from the Student database table
and its standards using the DbSet Include() method. As shown in the example, we can
specify a lambda expression as a parameter in the Include() method to specify a navigation
property. To use the Include method, we need to
608
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
//While Loading the Student table data, it also load the Standard table data
.ToList();
// .Include("Standard")
// .ToList();
609
// select s).ToList();
// select s).ToList();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
610
As you can see in the above output, EF Core uses SQL Join to fetch the data.
Note: The Include() method with a lambda expression is recommended. This is because,
with the Include method with a string name, we will get a runtime exception if the property
name is misspelled or does not exist. So, always use the Include() method with the lambda
expression to detect the error during compile time.
FromSql() Method in EF Core:
Using the FromSql() Method in EF Core, we can specify the RAW T-SQL Statements
executed in the database. The Include() extension method in EF Core can also be used
after the FromSql() method. For a better understanding, please have a look at the following
example.
using EFCoreCodeFirstDemo.Entities;
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
//While Loading the Student Information, also load the related Standard data
.FromSql(sql)
.FirstOrDefault();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
If you look at the Student Entity, you will see that the Student Entity Has three related
entities as Standard, StudentAddress as Reference Navigational Entities, and Courses as
Collection Navigation Entity, as shown in the image below.
Our requirement is to load the Standard, StudentAddreess, and Courses Entities while
loading the Student Entity. Is it possible in Entity Framework Core? Yes, it is possible. We
can eagerly load multiple related entities using the Include method multiple times in Entity
Framework Core.
So, we need to use the Include() method multiple times to load multiple navigation
properties of the same entity. In the below example, we eagerly load the Student, Standard,
StudentAddress, and Courses entities while loading the Student Entity.
using EFCoreCodeFirstDemo.Entities;
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
{
613
//While Loading the Student table data, it is also going to load the Standard, StudentAddress
and Courses tables data
.ToList();
// select s).ToList();
{
614
Console.WriteLine($"Error: {ex.Message}"); ;
}
Now, run the application, and you should get the Student and all the related entities data as
expected, as shown in the below image.
called Standard, i.e., it includes it as a Navigation property. Now, go to the Standard entity
or Standard class. It includes one related entity called Teachers, i.e., the Standard Entity
includes the Teachers entity as a collection navigation entity.
Now, what we want is, we want to load Student, Standard, and Teacher entities. This is
nothing but multiple levels of related entities. For a better understanding, please have a look
at the following example.
using EFCoreCodeFirstDemo.Entities;
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
//While Loading the Student table data, it is also going to load the Standard, StudentAddress
and Courses tables data
//Method Synatx
.FirstOrDefault();
if(student?.Standard != null)
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
We can also load multiple related entities using the projection query instead of the Include()
or ThenInclude() methods. The following example demonstrates the projection query to load
the Student, Standard, and Teacher entities. In the below example, the Select extension
method is used to include the Student, Standard, and Teacher entities in the result. This will
execute the same SQL query as ThenInclude() method.
using EFCoreCodeFirstDemo.Entities;
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
//While Loading the Student table data, it is also going to load the Standard, StudentAddress
and Courses tables data
Student = s,
Standard = s.Standard,
})
.FirstOrDefault();
if(std?.Student?.Standard != null)
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
619
all related data in one query can cause performance issues, especially if the
data is not used immediately.
3. Complex Queries: Eagerly loading multiple levels of related entities can lead
to complex and potentially inefficient SQL queries, negatively impacting
database performance and query execution time.
4. Increased Initial Query Time: Eager loading can impact the application
responsiveness due to increased initial query execution time, particularly
when joining multiple tables or retrieving significant data.
5. Inflexible Loading Strategy: Eager loading loads all related data upfront,
which may not be suitable for all scenarios. It can lead to unnecessary data
retrieval when only a subset of related data is needed.
6. Increased Complexity: Although eager loading can simplify code in some
scenarios, it may also bring in unnecessary data, adding complexity.
Developers must optimize eager loading configurations carefully.
the StudentAddress entity. At the same time, you will also find a navigation property for
Standard and a collection navigation property for Courses Entities.
Ad
1/2
00:07
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem TV
In Lazy Loading, the context object first loads the Student entity data from the database.
Then, it will load the related entities when you access those navigation properties for the
first time. If you are not accessing the Navigation Properties, it will not load those related
data from the database.
For example, when executing the below statement, the context object will only load the
Student data using a SELECT SQL Statement. It will not load the related StudentAddress,
Standard, or Courses table data.
Student? student = context.Students.FirstOrDefault(std => std.StudentId == 1);
Later, suppose you execute the below statement, i.e., access the StudentAddress Property
for the First Time. In that case, the Context object will issue a SELECT SQL Statement and
load the related StudentAddress data from the database table.
StudentAddress? studentAddress = student?.StudentAddress;
Similarly, when you execute the below statement, the Context object will generate and
execute another SELECT SQL Statement to fetch the related data from the Standards
database table.
Standard? standard = student?.Standard;
How to Implement Lazy Loading in EF Core?
We can implement Lazy loading in Entity Framework Core in two ways. They are as follows:
1. Lazy Loading With Proxies
2. Lazy loading Without Proxies
Lazy Loading With Proxies in EF Core:
622
To enable Lazy Loading using Proxies in Entity Framework Core, the first step is to install
the Proxy Package provided by Microsoft. This can be done by
installing Microsoft.EntityFrameworkCore.Proxies package, which will add all the
necessary proxies. There are three steps involved in enabling Lazy Loading using Proxies.
1. Install Proxies Package
2. Enable Lazy Loading using UseLazyLoadingProxies
3. Mark the Navigation Property as a Virtual
Step 1: Install Microsoft.EntityFrameworkCore.Proxies Package
So, open NuGet Package Manager UI by selecting Tools => NuGet Package Manager =>
Manage NuGet Packages for Solution from the Visual Studio Menu, which will open the
following window. From this window, select the Search tab, then search
for Microsoft.EntityFrameworkCore.Proxies package and
select Microsoft.EntityFrameworkCore.Proxies package and click the Install button, as
shown in the image below.
Once you click the Install button, it will open the Preview Changes window to tell what
packages will be installed. Review the changes and click the OK button in the image below.
623
Once you click on the OK button, it will open the License Acceptance window. Click on the I
Accept button as shown in the below image.
624
Once you click on the I Accept button, it will install the package, and you can verify the
package inside the Dependencies => Packages folder of your project, as shown in the
image below.
625
using Microsoft.Extensions.Logging;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
optionsBuilder.UseLazyLoadingProxies();
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB1;Trusted_Connection=True;TrustServerCertif
icate=True;");
//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API
}
Step3: Mark the Navigation Property as Virtual
Finally, we need to mark the navigation property as virtual, which we want to Laod Lazily.
So, if you see our Student, we have already marked the Navigation Properties as Virtual, as
shown in the below image.
627
Note: If you have not marked the navigation properties as virtual, then please add the
virtual keyword, and then, as we already discussed, whenever we add or update domain
classes, we need to sync the database with the model using add-migration and update-
database commands using Package Manager Console or .NET Core CLI.
Using Lazy Loading in EF Core:
How It Works: When you access a navigation property, EF Core checks if
the related data is already loaded. If not, it automatically queries the database
to load the data.
Usage: Lazy loading is useful when you’re unsure whether you’ll need related
data and want to avoid the overhead of loading it unnecessarily.
Now, modify the Main method of the Program class as follows to use Lazy Loading in Entity
Framework Core.
using EFCoreCodeFirstDemo.Entities;
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
628
Console.WriteLine();
//Loading the Student Address (it will execute Separate SQL query)
Console.WriteLine();
Console.WriteLine();
//{
// {
// Console.WriteLine($"CourseName: {course.CourseName}");
629
// }
//}
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
As you can see in the above output, it is issuing three different Select Statements to load
the data from the database. This means it is using Lazy Loading to load the related data.
Further, you can see that it is not loading the Courses table data, as we have commented
on the statement accessing the Courses navigation property.
Note: Lazy loading can lead to the N+1 problem, which causes unnecessary database
roundtrips.
Lazy Loading Without Proxies in Entity Framework Core:
The second approach to enabling Lazy Loading in Entity Framework Core is using
the ILazyLoader interface. Implementing Lazy-Loading Without Proxies in EF Core is done
by injecting the ILazyLoader service into an entity.
1. The ILazyLoader interface is responsible for loading navigation properties
that have not yet been loaded.
2. The feature can be easily integrated into the main component of the
database. That is to the Principal Entity.
630
Once you click the Install button, it will open the Preview Changes window to tell what
packages will be installed. Review the changes and click the OK button in the image below.
Once you click on the OK button, it will open the License Acceptance window. Click on the I
Accept button as shown in the below image.
631
Once you click on the I Accept button, it will install the package, and you can verify the
package inside the Dependencies => Packages folder of your project, as shown in the
image below.
632
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace EFCoreCodeFirstDemo.Entities
public Student()
_lazyLoader = lazyLoader;
//Public Setter and Getter Property for the Private _Standard Field
//Public Setter and Getter Property for the Private _StudentAddress Field
//Public Setter and Getter Property for the Private _Courses Field
}
Next, modify the EFCoreDbContext class as follows. Here, we are removing
the optionsBuilder.UseLazyLoadingProxies() method call, which enables Lazy Loading
using Proxies.
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace EFCoreCodeFirstDemo.Entities
{
635
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB1;Trusted_Connection=True;TrustServerCertif
icate=True;");
//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API
}
We don’t need to make any changes to the Main method. So, now, run the application, and
you should get the following output as expected. As you can see, EF Core uses separate
SELECT SQL Statements to load the related entities.
So, modify the EFCoreDbContext class to disable Lazy Loading for the whole application in
Entity Framework Core.
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace EFCoreCodeFirstDemo.Entities
{
637
this.ChangeTracker.LazyLoadingEnabled = false;
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB1;Trusted_Connection=True;TrustServerCertif
icate=True;");
//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API
}
With these changes in place, if you run the application, you will see that the related entities
will not be loaded when we access them, as shown in the image below.
Note: The default value of LazyLoadingEnabled is set to true. Although we have not set this
property value to true, Lazy Loading is still working in our application. Even if you want, then
while accessing the data, you can enable and disable lazy loading.
For a better understanding, please have a look at the following example. In the example
below, we disable the Lazy Loading using the statement context after fetching the Student
data, i.e., ChangeTracker.LazyLoadingEnabled = false; hence, it will not load the
Standard data. Then, we again enabled the Lazy loading by using
the context.ChangeTracker.LazyLoadingEnabled = true; statement, and then we are
accessing the StudentAddress navigation property, and this time, as Lazy Loading is
enabled, it will issue a separate SELECT SQL Statement and load the Student Address
data.
using EFCoreCodeFirstDemo.Entities;
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
Console.WriteLine();
context.ChangeTracker.LazyLoadingEnabled = false;
//As Lazy Loading is Disabled so, it will not load the Standard data
Console.WriteLine();
context.ChangeTracker.LazyLoadingEnabled = true;
//As Lazy Loading is Enabled so, it will load the StudentAddress data
Console.WriteLine();
}
640
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
Eager loading is the process of loading the main entity and its related entities from the
database in a single query. This is typically done using the Include and ThenInclude
methods in a LINQ query.
Characteristics of Eager Loading in EF Core:
Single Query: Related data is loaded in the same query as the main entity,
typically resulting in a more complex query with joins.
Immediate Loading: All specified related data is loaded upfront, regardless
of whether it will be used.
Controlled Loading: You have explicit control over which related entities are
loaded.
Performance: This can result in more efficient loading with fewer round trips
to the database, which is especially beneficial for known data requirements.
Lazy Loading in EF Core:
On the other hand, Lazy Loading defers the loading of related data until it is explicitly
accessed for the first time.
Characteristics of Lazy Loading in EF Core:
Separate Queries: Related data is loaded in separate queries, each
triggered when a navigation property is accessed for the first time.
Delayed Loading: Data is loaded on-demand, which can save resources if
the related data is never accessed.
Automatic Loading: EF Core automatically loads the related data, which can
simplify code but may lead to unintended queries.
Potential for Performance Issues: This can lead to the N+1 query problem,
where multiple queries are executed, potentially impacting performance.
Key Differences Between Eager Loading and Lazy Loading in EF Core:
Query Strategy: Eager loading retrieves all data in one query, while lazy
loading retrieves data in multiple queries.
Performance: Eager loading can be more efficient for known data needs,
avoiding multiple round trips. Lazy loading can be more efficient for
conditional data access but may lead to performance issues if not used
carefully.
Data Retrieval: Eager loading retrieves all related data upfront, while lazy
loading retrieves it as needed.
Control and Visibility: Eager loading provides more control and visibility
over the data retrieval process, whereas lazy loading abstracts this away.
Use Cases: Eager loading is suitable when you know you need related data
for each entity. Lazy loading is useful when you need to access related data
conditionally or in a non-predictable manner.
Which is good – Eager Loading or Lazy Loading in Entity Framework
Core?
Before deciding which approach to use, we must consider the application requirements and
goals. Lazy Loading and Eager Loading have strengths and weaknesses, and we have
observed significant performance differences when using each method to accomplish the
same task.
The choice between eager and lazy loading in Entity Framework Core (EF Core) depends
on your specific application requirements, data access patterns, and performance
considerations. Eager loading can be more efficient for complete and predictable data sets,
while lazy loading can benefit dynamic, user-driven data access patterns.
643
using Microsoft.Extensions.Logging;
namespace EFCoreCodeFirstDemo.Entities
{
644
this.ChangeTracker.LazyLoadingEnabled = false;
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB1;Trusted_Connection=True;TrustServerCertif
icate=True;");
//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API
}
Next, modify the Program class Main method as shown below to ensure Lazy Loading is
Disabled and it is not loading the related data when we explicitly access the navigation
properties.
using EFCoreCodeFirstDemo.Entities;
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
Console.WriteLine();
//As Lazy Loading is Disabled so, it will not load the Standard data
646
Console.WriteLine();
//As Lazy Loading is Disabled so, it will load the StudentAddress data
Console.WriteLine();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
As you can see in the above output, it is not loading the Standard and Address data of the
Student. Now, modify the Main method of the Program class as follows to load the related
Standard and Address data explicitly.
using EFCoreCodeFirstDemo.Entities;
647
namespace EFCoreCodeFirstDemo
try
Console.WriteLine();
//First, call the Reference method by passing the property name that you want to load
if(student?.Standard != null )
Console.WriteLine();
else
Console.WriteLine();
if (student?.StudentAddress != null)
Console.WriteLine();
else
Console.WriteLine();
//First, call the Collection method by passing the property name that you want to load
if (student != null)
if (student?.Courses != null)
Console.WriteLine($"CourseName: {course.CourseName}");
else
Console.WriteLine();
650
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
As you can see in the above output, it is using Explicit Loading to load the related entities.
First, it issues one select statement to fetch the student entity, and then it issues two
separate SQL queries to get the related standard and course data. However, it is not
loading the Student Address, as we are not explicitly loading this entity.
Understanding the Reference, Collection, Entry, and Load Method
651
2. Due to multiple database round trips, this can also lead to performance
issues if not used carefully.
3. Requires additional setup in EF Core, such as installing a separate package
and modifying navigation properties to be virtual.
Eager Loading:
How it works: Related data is loaded upfront using methods like Include and ThenInclude.
Advantages:
1. Data is loaded in a single roundtrip, which can be more efficient in many
scenarios.
2. It indicates which related data will be fetched, providing transparency.
Disadvantages:
1. It might fetch more data than necessary if not used properly.
2. Initial queries can be heavier, fetching all related data at a time, even if some
data isn’t needed immediately.
When to use Which Approach:
Explicit Loading: If you want complete control over when to fetch the related data
depending on conditions or logic in your code, then you should use EF Core Explicit
Loading to load Related Entities.
Lazy Loading: When you want to minimize upfront data retrieval and are okay with fetching
related data on the fly, you can use EF Core Lazy Loading to load Related Entities.
Eager Loading: If you know the related data you need and want to fetch everything at
once, then you need to use EF Core Eager Loading to load Related Entities.
In Real-time Applications, you might use a combination of these strategies based on the
specific needs and performance characteristics of different components of your application.
It is important to understand each approach to make well-informed decisions. It is also
important to keep track of and analyze your database queries to ensure that you achieve
the desired performance and behavior.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:13 / 03:1710 Sec
}
Standard.cs
Next, please create another class file named Standard.cs and copy and paste the following
code.
655
namespace EFCoreCodeFirstDemo.Entities
}
Grade.cs
Please create a new class file named Grade.cs and copy and paste the following code.
using System.ComponentModel.DataAnnotations;
namespace EFCoreCodeFirstDemo.Entities
}
Creating the Context Class:
656
Next, create a class file named EFCoreDbContext.cs and copy and paste the following
code. Here, we have the Student and Standard as the DbSet Properties but we have not
added the Grade Entity as a DbSet Property.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API
//In The database it will create the table with the name Students
//In The database it will create the table with the name Standard
}
Note: As we already discussed, whenever we add or update domain classes or
configurations, we need to sync the database with the model using add-
migration and update-database commands using Package Manager Console or .NET
Core CLI. Open the Package Manager Console, and then execute the add-migration
CreateEFCoreDB1 command as follows:
Once the Update-Database command is executed successfully, it will update the database
with these two tables.
What are the Default Entity Framework Core Conventions?
Let us understand the Default Entity Framework Core Conventions. They are as follows:
Schema:
By default, the Entity Framework Core API creates all the database objects with the dbo
schema. If you verify the database tables, you will see that the database tables are created
with the dbo schema, as shown in the image below.
658
Table Name:
The EF Core API creates database tables for all properties defined in the context class
as DbSet<TEntity> with the same name as the property name. It can also create tables for
entities not listed as DbSet properties but are still reachable through reference properties in
other DbSet entities. In our example, EF Core will create the Students table for the
DbSet<Student> property, which is there in the EFCoreDbContext class, and
the Grade table for the Grade property, which we defined inside the Student entity class,
despite the EFCoreDbContext class not featuring the DbSet<Grade> property. Similarly, for
the DbSet<Standard> Standard property, EF Core will create the Standard table in the
database. The diagram below may provide a better understanding.
In our example, we added StudentId, GradeId, and Id to the respective model classes for
Student, Grade, and Standard. As a result, these three properties will serve as the Primary
Key columns in the corresponding database tables, as illustrated in the image below.
Note: If both Id and <Entity Class Name> + Id properties are present in a single model
class, then it will create the Primary key based on the Id column only. If the model class has
no key property, then Entity Framework will throw an exception.
Foreign Key Column Name:
EF Core API will create a foreign key column for each reference navigation property in an
entity with one of the following naming patterns as per the foreign key convention.
1. <Reference Navigation Property Name>Id
2. <Reference Navigation Property Name><Principal Primary Key Property
Name>
In our example (Student, Grade, and Standard entities), EF Core will create foreign key
columns GradeId and StandardId in the Students table, as shown in the image below.
Here, you can see that in the Student Entity, we have GradeId Property, which will become
the Foreign Key for the Grade table. But we don’t add any Foreign Key Property for
Standard Reference Property. So, in the case of the Standard Reference Property, it will
660
add (Standard + Id) as a Reference Property. This is because the Standard Entity has ID as
the primary key. For a better understanding, please have a look at the Student table.
DB Columns Order:
The Entity Framework Core will create the Database table columns in the same order as the
properties are added in the entity class. However, the primary key columns would be moved
to the first position in the table.
Column Data Type
The data type for columns in the database table depends on how the provider for the
database has mapped the C# data type to the data type of a selected database. The
following table lists mapping between the C# data type and the SQL Server column data
type.
661
Index
EF Core creates a clustered index on Primary key columns and a non-clustered index on
Foreign Key columns by default.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem TV
Framework Core will create a database table whose name is the same as the DbSet
Property name.
Note: In Entity Framework Core (EF Core), the [Table] Data Annotation Attribute is used to
configure the database table name to which an entity should be mapped. This is particularly
useful if the default table name EF Core generates based on the entity class name doesn’t
meet your requirements or if you’re working on an existing database and must match table
names exactly.
All the Data Annotation Attributes are inherited from the Attribute abstract class. Now, if you
go to the definition of Table Attribute class, then you will see the following.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:12 / 03:1710 Sec
As you can see in the above TableAttribute class, it has one constructor, two properties,
and one private field. The constructor takes one string parameter, which is nothing but the
database table name, which is mandatory. The Schema property is optional, and the use of
the Name and Schema properties are as follows:
1. Name: The name of the table the class is mapped to. It is a read-only
property.
2. Schema: The schema of the table the class is mapped to. This is optional. It
is a read-write property.
Note: Using square bracket [], we need to specify the attributes.
Syntax to use Table Attribute: [Table(string name, Properties:[Schema = string])
Example to use Table Attribute: [Table(“StudentInfo”, Schema=”Admin”)]
Examples to understand Table Data Annotation Attributes in EF Core:
Let us understand the Table Data Annotation Attribute in Entity Framework Core with an
example. Let us modify the Student Entity class as follows. As you can see, we have
specified the table name as StudentInfo. So, when we add the migration and update the
database, it should create a database table with the name StudentInfo in the database,
665
which will map with the following Student Entity. The Table Attribute belongs to the
System.ComponentModel.DataAnnotations.Schema namespace.
using System.ComponentModel.DataAnnotations.Schema;
namespace EFCoreCodeFirstDemo.Entities
[Table("StudentInfo")]
}
The Table Attribute is applied to the Student Entity class in the above example. So, the
Entity Framework Core will override the default conventions and create the StudentInfo
database table instead of the Students table in the database, which will be mapped with the
above Student Entity class. Next, modify the context class as follows. As you can see, we
only include the Student as a DbSet Property.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
666
}
Note: As we already discussed, whenever we add or update domain classes or
configurations, we need to sync the database with the codebase using add-
migration and update-database commands using Package Manager Console or .NET
Core CLI.
So, open the Package Manager Console and Execute the add-migration and update-
database commands as follows. You can give any name to your migration. Here, I am
giving Mig1. The name that you are giving it should not be given earlier.
Now, you can also verify the database, and you should see the following.
667
As you can see, it created the database table name that we specified in our Student model
class using the Table Attribute. Further, you can notice it is created with the schema dbo.
Now, if you want to create the table with a different schema, such as Admin, you need to
use the Schema property of the Table Attribute. For a better understanding, please modify
the Student entity as follows.
using System.ComponentModel.DataAnnotations.Schema;
namespace EFCoreCodeFirstDemo.Entities
}
So, again, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. So, every time we change our
model, we need to execute the following two commands. I cannot use Mig1, so I am giving
the migration name Mig2.
668
Now, you can verify the database and see the schema as Admin and the table name as
StudentInfo, as shown in the image below.
The Column Data Annotation Attribute can be applied to one or more properties of a domain
entity to configure the corresponding database column name, column order, and column
data type. That means it represents the database column that a property is mapped to.
In Entity Framework Core (EF Core), the [Column] attribute allows developers to provide
additional configuration related to how an entity class property maps to a database column.
With the [Column] attribute, you can specify things like the name of the database column, its
order, and its data type.
The Column Attribute in EF Core overrides the default convention. As per the default
conventions in Entity Framework Core, it creates the database table column with the same
name and in the same order as the property names specified in the model class. Now, if
you go to the definition of Column Attribute, then you will see the following.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:12 / 03:1710 Sec
As you can see in the above image, this class has two constructors. The 0-Argument
constructor will create the database column with the same name as the property name. In
contrast, the other overloaded constructor, which takes the string name as a parameter, will
create the database table column with the name you passed to the constructor. Again, this
Column attribute has three properties. The meaning of the properties is as follows:
1. Name: The name of the column the property is mapped to. This is a read-only
property.
2. Order: The zero-based order of the column the property is mapped to. It
returns the order of the column. This is a read-write property.
3. TypeName: The database provider-specific data type of the column the
property is mapped to. It returns the database provider-specific data type of
the column the property is mapped to. This is a read-write property.
670
Note: The default value for the _order filed is -1 means EF Core will ignore the order.
Syntax to use Column Attribute: [Column (string name, Properties: [Order = int],
[TypeName = string])]
Example to use Column Attribute: [Column(“DOB”, Order = 2, TypeName =
“DateTime2”)]
Examples to understand Column Data Annotation Attribute in EF Core:
Let us understand the Column Data Annotation Attribute in Entity Framework Core with an
example. Let us modify the Student Entity class as follows. As you can see, we have
specified the Column Attribute with the FirstName and LastName properties. With the
FirstName property, we have not provided the string name, i.e., using the 0-Argument
constructor, so in this case, it will create the database table column with the same name as
the Property name. With the LastName property, we have specified the name as LName,
i.e., using the constructor, which takes one string parameter, so in this case, it will create
the database table column with the name LName mapped to the LastName property. The
Column attribute belongs to the System.ComponentModel.DataAnnotations.Schema
namespace.
using System.ComponentModel.DataAnnotations.Schema;
namespace EFCoreCodeFirstDemo.Entities
[Column]
[Column("LName")]
}
As we already discussed, whenever we add or update domain classes or configurations, we
need to sync the database with the model using add-migration and update-
database commands using Package Manager Console or .NET Core CLI.
So, open the Package Manager Console and Execute the add-migration and update-
database commands as follows. You can give any name to your migration. Here, I am
giving Mig3. The name that you are giving it should not be given earlier.
671
With the above changes in place, run the application and verify the database, and you
should see the following. As you can see, the FirstName is created as FirstName, but the
LastName property is created as the LName column in the database, as expected.
namespace EFCoreCodeFirstDemo.Entities
[Column]
[Column("LName")]
}
So, again, open Package Manager Console and Execute the following add-
migration and update-database commands as we modify the Student Entity.
If you verify the database, it should create a column with the name as DOB and the data
type as DateTime2 instead of DateTime, as shown in the image below.
673
namespace EFCoreCodeFirstDemo.Entities
[Column(Order = 0)]
[Column(Order = 2)]
[Column(Order = 1)]
}
So, again, open Package Manager Console and Execute the following add-
migration and update-database commands as we modify the Student Entity.
Here, you need to observe one thing. We are getting one warning: Column orders are only
used when the table is first created. In our example, we are not creating the tables but
updating the table structure in the database.
Note: It will be ignored when we apply the Order Property of Column Attribute of an existing
Entity.
So, to understand the Order Property of Column Attribute, let’s create a new table. So,
modify the Student Entity Name from Student to CollegeStudent as follows.
using System.ComponentModel.DataAnnotations.Schema;
namespace EFCoreCodeFirstDemo.Entities
{
675
[Column(Order = 0)]
[Column(Order = 2)]
[Column(Order = 1)]
}
Next, modify the Context class as follows.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
676
}
Note: Use CollegeStudent instead of Student in other Entities.
With the above changes in place, open Package Manager Console and Execute the
following add-migration and update-database commands as we modify the Student
Entity.
Now, verify the database table column’s order, and it should be in proper order per the
Order parameter shown in the image below.
677
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
namespace EFCoreCodeFirstDemo
try
GetAttribute(typeof(CollegeStudent));
Console.WriteLine($"Error: {ex.Message}"); ;
ColumnAttribute? columnAttribute =
(ColumnAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(ColumnAttribute));
if (columnAttribute == null)
else
}
When you run the above code, whatever Name, Order, and Data type you have specified
using the Column Data Annotation Attribute, only those details will be printed on the
Console window, as shown in the image below.
Now, let us fetch single-column information. We have set the Name, Order, and Data Type
of the DateOfBirth property. Let us fetch that property-related database column information.
To do so, modify the Main Method of the Program class as follows:
using EFCoreCodeFirstDemo.Entities;
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
namespace EFCoreCodeFirstDemo
try
GetAttribute(typeof(CollegeStudent));
Console.WriteLine($"Error: {ex.Message}"); ;
ColumnAttribute columnAttribute =
(ColumnAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(ColumnAttribute));
if (columnAttribute == null)
else
}
Output:
key constraint is applied. Using the primary key, we can enforce entity integrity, i.e., using
the primary key column value, we can uniquely identify a record.
A table should contain only 1 Primary Key, which can be on single or multiple columns, i.e.,
the composite primary key is also possible. A table must have a primary key to identify each
record uniquely. Entity Framework Core will throw an error while creating or updating the
database if we don’t specify the Primary Key Property.
Key Attribute in Entity Framework Core
The Key Data Annotation Attribute in Entity Framework Core can be applied to a property of
an entity to make it the key property, and it will also make that corresponding column the
Primary Key column in the database.
}
Next, modify the content class, i.e., EFCoreDbContext.cs, as follows. Here, we are adding
the Student entity as a DbSet Property and providing the property name as Students, so the
database will create the table with the name Students.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
{
683
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
As we already discussed, whenever we add or update domain classes or configurations, we
need to sync the database with the model using add-migration and update-
database commands using Package Manager Console or .NET Core CLI.
So, open the Package Manager Console and Execute the add-migration and update-
database commands as follows. You can give any name to your migration. Here, I am
giving Mg1. The name that you are giving it should not be given earlier.
Now, if you verify the database, you should see the following. As you can see, the StudentId
column is created as the primary key column.
684
Now, what our business requirement is, we do not want to make StudentId or Id the Primary
key column. Instead, we want a different column. Let us say StudentRegdNo as the primary
key in our database. Then, this default convention of EF Core will not work. Let us prove
this. Let us modify the Student entity class as follows. Here, instead of StudentId, we are
using the StudentRegdNo property.
namespace EFCoreCodeFirstDemo.Entities
}
With the above changes, open the Package Manager Console and Execute the add-
migration command.
685
As you can see in the above image, executing the add-migration command throws an
exception saying the entity type ‘Student’ requires a primary key to be defined. If you intend
to use a keyless entity type, call ‘HasNoKey’ in ‘OnModelCreating.’
How to Overcome the Above Error?
How to overcome the above error means how can we make the StudentRegdNo property
the key property so that EF Core will make this property the Primary Key column in the
corresponding database table?
We need to use the Key Data Annotation Attribute in EF Core. If you go to the definition of
Key Attribute, then you will see the following. It is a sealed class that has a parameterless
constructor. The Key Attribute denotes one or more properties that uniquely identify an
entity.
So, modify the Student class as follows. As you can see in the below code, we decorate the
StudentRegdNo property with the Key Attribute. The Key Attribute belongs to the
System.ComponentModel.DataAnnotations namespace, so include that namespace as well.
The key Attribute will mark the StudentRegdNo property as the key property, and the entity
framework core will generate the Primary key for the StudentRegdNo property in the
database.
using System.ComponentModel.DataAnnotations;
namespace EFCoreCodeFirstDemo.Entities
{
686
[Key]
}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows.
Now, verify the database, and you should see StudentRegdNo as the Primary Key shown in
the image below.
687
Note: The Key Attribute denotes one or more properties uniquely identifying an entity. The
Key attribute can be applied to a property of any primitive data type except unsigned
integers.
How to Make Composite Primary Key in Entity Framework Core?
If you want to make a composite primary key using Entity Framework Core, it is impossible
to use the Key Attribute. It is possible in Entity Framework but not in Entity Framework
Core.
In Entity Framework, the Key Attribute and the Column Data Annotation Attribute with the
Column Order Property can be applied to multiple entity properties, creating a composite
primary key in the corresponding database table.
In EF Core, we need to use the PrimaryKey Attribute to make a Composite Primary Key.
This PrimaryKey attribute can be used for both keys of a single property and composite
keys of multiple properties. If you go to the definition of PrimaryKey Attribute, then you will
see the following. It is a sealed class having one constructor, which takes two parameters.
The first parameter is the string property name, and the second parameter is the optional
params array. Using this optional params array, we can specify other property names.
Note: You cannot apply this Attribute on an already created database table, and if you try,
you will get the following error. The [PrimaryKey] attribute was introduced in EF Core 7.0.
Use Fluent API in older versions. The Composite primary keys are configured by placing the
[PrimaryKey] attribute on the entity type class.
System.InvalidOperationException: To change the IDENTITY property of a column,
the column needs to be dropped and recreated.
To simplify things, let us first delete the EFCoreDB database from the SQL Server using
SSMS and then delete the Migration folder from our project. Next, modify the Student Entity
Class as follows. As you can see here, we have applied the PrimaryKey Attribute on the
Student Entity, and to this attribute, we are passing the RegdNo and SerialNo properties.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
[PrimaryKey(nameof(RegdNo), nameof(SerialNo))]
}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows.
Now, verify the database, and you should see a composite primary key based on two
columns, as shown in the below image.
689
Note: In Entity Framework Core, the most important point you need to remember is that if
you create the Primary Key on a single column, it will also mark that column as an identity
column, provided the column is of integer type. However, creating a Composite Primary Key
will not mark the primary key columns as identity columns.
Can we Create a Primary Key on String Properties using EF Core?
Yes, we can also create the Primary key on a string property or properties. For a better
understanding, modify the Student entity as follows. As you can see, here we have created
the StudentId property with the string data type, and EF core will mark this property as the
Primary Key in the database. But this will not be marked as an Identity column.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
}
We can also create a composite Primary Key using string properties. For a better
understanding, modify the Student entity as follows. As you can see, we have marked
the RegdNo and SerialNo properties as String type, and then we create a composite
primary key based on these two properties, and it will work.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
[PrimaryKey(nameof(RegdNo), nameof(SerialNo))]
{
690
}
When to use Key Attribute in EF Core?
In EF Core, entities have a primary key that uniquely identifies each record in a table. The
[Key] attribute indicates which property or properties make up the primary key. By default,
EF Core assumes that the property named Id or <ClassName>Id serves as the primary key,
but the [Key] attribute provides an explicit way to specify this. Primary keys are crucial in
relational databases for identifying records in a table in a unique way. We need to use the
[Key] attribute,
When EF Core’s convention does not correctly identify the primary key
property.
When working with existing databases where primary key columns might
have non-standard names.
When being explicit about your intentions for clarity.
The [Key] attribute provides a declarative way to specify the primary key of an entity directly
within the class. If you need more advanced configurations or prefer to separate
configurations from the domain classes, you can utilize the Fluent API.
In Entity Framework Core (EF Core), the ForeignKey Attribute is used to configure the
relationship between two entities. This attribute overrides the EF Core’s default foreign key
convention, which is followed otherwise. By default, Entity Framework Core searches for a
foreign key property in the Dependent Entity with the same name as the primary key
property in the Principal Entity.
}
Next, modify the Student Entity as follows. In our example, the Student Entity is the
Dependent Entity. Note that we have included the Standard Reference Navigational
Property, which is mandatory for establishing the Foreign Key in the Student database
table. Additionally, we have included the StandardId property, which refers to the Primary
Key Property of the Standard table.
namespace EFCoreCodeFirstDemo.Entities
//To Create a Foreign Key it should have the Standard Navigational Property
}
In this example, we will demonstrate the one-to-many relationship between the Student
and Standard Entities. This relationship is represented by including StandardId in
the Student class, which has a reference property called Standard. Meanwhile,
the Standard entity class includes a collection navigation property called Students.
The StandardId property in the Student Class matches the primary key property of
the Standard class. As a result, the StandardId property in the Student class will
automatically become a Foreign Key Property, and the corresponding column in the
database table will also become a Foreign Key Column.
Note: You must remember that both Entities should and must have Navigational Properties
to implement Foreign Key Relationships. In our example, the Student Entity has the
Standard Reference Navigational Property, and the Standard Entity has the Students
693
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
Note: Please delete the EFCoreDB database from the SQL Server using SSMS if it already
exists, and then also delete the Migration folder from your project if it exists.
As we already discussed, whenever we add or update domain classes or configurations, we
need to sync the database with the model using add-migration and update-
database commands using Package Manager Console or .NET Core CLI.
So, open the Package Manager Console and Execute the add-migration and update-
database commands as follows. You can give any name to your migration. Here, I am
giving DbMig1. The name that you are giving it should not be given earlier.
694
If you verify the database, the Foreign key should have been created in the Student
database, as shown in the image below. Here, the Foreign Key Property is created with the
name StudentId.
Suppose the Foreign Key Property does not exist in the Dependent Entity class. In that
case, Entity Framework will create a Foreign Key column in the Database table with
the Primary Key Property Name of the Principal Entity.
To understand this, modify the Student Entity class as follows. Here, you can see we have
not added the StandardId property. So, in this case, Entity Framework will create the
Foreign Key with the name StandardId. We must have added the Standard reference
navigation Property. Otherwise, the foreign key will not be created.
namespace EFCoreCodeFirstDemo.Entities
{
695
//To Create a Foreign Key it should have the Standard Navigational Property
}
With the above changes in place, open Package Manager Console and Execute the
following add-migration and update-database commands as shown below.
If you verify the database, the Foreign key should have been created in the Student
database table with StudentId, as shown in the image below.
696
Entity Framework follows these default conventions to create the Foreign Key column in the
database.
What is ForeignKey Attribute in Entity Framework?
The ForeignKey Attribute in Entity Framework Core configures a foreign key relationship
between the entities. It overrides the default foreign key convention, followed by EF Core. It
allows us to specify the foreign key property in the Dependent Entity whose name does not
match the primary key property of the principal entity.
Now, if you go to the definition of ForeignKey Attribute, you will see the following signature.
As you can see, this class has one constructor that takes the associated navigation
property’s name as an input parameter.
namespace EFCoreCodeFirstDemo.Entities
[ForeignKey("Standard")]
}
With the above changes in place, open the Package Manager Console and Execute add-
migration and update-database commands as shown below.
698
If you verify the database, the foreign key should have been created in the Student
database table with StandardReferenceId, as shown in the image below.
namespace EFCoreCodeFirstDemo.Entities
{
699
[ForeignKey("StandardReferenceId")]
}
In the above example, the [ForeignKey] attribute is applied on the Standard navigation
property, and the name of the foreign key scaler property StandardReferenceId is specified.
Here, Entity Framework Core will create the foreign key column StandardReferenceId in the
Students table in the database.
With the above changes, open Package Manager Console and Execute add-
migration and update-database commands as shown below.
If you verify the database, the foreign key should have been created in the Student
database table with StandardReferenceId, as shown in the image below.
700
}
701
namespace EFCoreCodeFirstDemo.Entities
[ForeignKey("StandardReferenceId")]
}
With the above changes, open Package Manager Console and Execute add-
migration and update-database commands as shown below.
702
If you verify the database, the foreign key should have been created in the Student
database table with StandardReferenceId, as shown in the image below.
Note: In this article, we have implemented the One-To-Many relationship using EF Core. In
our upcoming articles, I will discuss One-to-One, One-to-Many, and Many-to-Many
Relationships in detail using Entity Framework Core.
Advantages of ForeignKey Attribute in Entity Framework Core
Explicitness: Using the ForeignKey attribute, you clearly clarify which
property is being used as a foreign key. This clarity can help other developers
who read the code to understand the relationships without inferring them from
conventions.
Flexibility in Naming: EF Core’s convention for foreign keys relies on
specific naming patterns. If you have existing database schemas or prefer a
different naming convention, the ForeignKey attribute allows you to map your
entities correctly without adhering to the default convention.
Composite Foreign Keys: For relationships involving multiple columns
(composite keys), the ForeignKey attribute must define which properties are
part of the foreign key.
Multiple Relationships: In cases where an entity has multiple relationships
to another entity (e.g., a Person entity having both Mother and Father
navigation properties pointing to another Person), the ForeignKey attribute
can help clarify which foreign key property corresponds to which navigation
property.
Disadvantages of ForeignKey Attribute in Entity Framework Core:
Difficult to Understand: Using the ForeignKey attribute can make your entity
classes difficult to understand, especially if you have many relationships to
define. This can make the code harder to read and maintain compared to
relying on conventions.
703
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:11 / 03:1710 Sec
704
Note: The most important point you need to remember is that you can apply the Index
Attribute at the class level, and to the constructor, we need to specify the property or
properties on which we want to create indexes.
Let us modify the Student Entity Class to use the Index Attribute. Here, you can see we
have applied the Index Attribute on the Student class, and to the constructor, we are
passing the RegistrationNumber property. In this case, an index will be created with the
default naming convention and has no column order, clustering, or uniqueness specified.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
[Index(nameof(RegistrationNumber))]
}
705
}
Next, modify the context class as follows:
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
As we already discussed, whenever we add or update domain classes or configurations, we
need to sync the database with the model using add-migration and update-
database commands using Package Manager Console or .NET Core CLI.
So, open the Package Manager Console and Execute the add-migration and update-
database commands as follows. You can give any name to your migration. Here, I am
giving EFCoreDBMig1. The name that you are giving it should not be given earlier.
706
By default, it will create the Index with the name IX_{Table name}_{Property Name}. As
we have Applied the Index Attribute on the RegistrationNumber property, EF Core will
create the Index with the name IX_Students_ RegistrationNumber shown in the image
below. As you can see, by default, the Index is created as Non-Unique and Non-
Clustered. Later, I will show you how to create a unique Clustered Index.
You might have one question: we applied the Index Attribute on a Single Property, but
here we can see two indexes. How is that possible? Yes, it is possible. This is because,
by default, one Clustered Index is created when we create the Primary Key in a database.
Now, if you want to give a different name to your Index name rather than the auto-
generated index name, you need to use the other overloaded version of the Constructor,
which takes the name parameter. For a better understanding, modify the Student class as
follows. Here, you can see we are providing the name as Index_RegistrationNumber.
using Microsoft.EntityFrameworkCore;
707
namespace EFCoreCodeFirstDemo.Entities
}
Now, open the Package Manager Console and Execute the add-migration and update-
database commands as follows.
Now, it should create the index with the specified name in the database, as shown in the
below image.
708
namespace EFCoreCodeFirstDemo.Entities
}
Now, open the Package Manager Console and Execute add-migration and update-
database commands as follows.
If you verify the database, it should create the index with the specified name based on the
two columns, as shown in the image below.
710
namespace EFCoreCodeFirstDemo.Entities
}
With the above changes, open the Package Manager Console and Execute add-
migration and update-database commands as follows.
711
Now, if you verify the database, then it should create a unique index with the specified
name, as shown in the below image.
namespace EFCoreCodeFirstDemo.Entities
{
712
}
You may also specify the sort order on a column-by-column basis as follows.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
}
713
}
Can we Create Multiple Indexes on a Table using EF Core?
Yes. It is possible to create multiple indexes on a table using EF Core. To achieve this, we
must decorate the Entity with Multiple Index Attribute. For a better understanding, please
modify the Student Entity as follows. Here, you can see we are creating two composite
indexes. One index on the FirstName and LastName column with the name
Index_FirstName_LastName. The other index is on the RegistrationNumber and
RollNumber columns with the name Index_RegistrationNumber_RollNumber.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
}
Advantages and Disadvantages of using Indexes:
Advantages of using Indexes:
Performance: Properly indexed columns can dramatically speed up data
retrieval times, making queries more efficient.
Uniqueness: By setting an index as unique, you ensure that no two rows
have the same value in the indexed column(s).
Disadvantages of using Indexes:
714
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:10 / 03:1710 Sec
The InverseProperty Attribute informs the EF Core, which navigational property relates to
the other end of the relationship. The default convention in Entity Framework Core correctly
identifies only if a single relationship exists between two entities. However, in the case of
multiple relationships between two entities, it fails to identify them correctly. In such cases,
we must use the InverseProperty Attribute to help the EF Core correctly identify the
relationship. If this is unclear now, don’t worry; we will try to understand this with an
example.
Let us understand InverseProperty Attribute in EF Core with one example. To understand
this concept, we will create two Entities, i.e., Course and Teacher. In our example, the
Teacher Entity will be the Principal Entity, and the Course Entity will be the Dependent
Entity. We will create the Foreign Keys inside the Dependent Entity to establish the
relationships.
715
Create a class file named Course.cs, then copy and paste the following code. Here, we
have created the following class with CourseId, CourseName, and Description properties
along with the Teacher Reference Navigation Property, which makes the relationship One-
To-One between the Course and Teacher.
namespace EFCoreCodeFirstDemo.Entities
}
Create a class file named Teacher.cs and copy and paste the following code. Here, we
have created the following class with TeacherId and Name properties and the Course
Collection Navigation Property, which makes the relationship One-to-Many between the
Teacher and Course.
namespace EFCoreCodeFirstDemo.Entities
}
716
The two entities have a one-to-many relationship, which is possible because of the
Navigation Property, as shown in the image below. So, each entity must have a navigation
property that maps to the navigation property of another entity.
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
717
In our example, the Course and Teacher entities have a one-to-many relationship where
one teacher can teach many online courses, and a single teacher can teach one course. As
per the default conventions of Entity Framework Core, the above example would create the
following tables in the database.
718
Now, suppose our requirement is to add another one-to-many relationship between the
Teacher and Course Entities. For example, apart from OnlineTeacher, we now need to add
OfflineTeacher. First, modify the Teacher class to include the OfflineCourses collection
Navigation Property.
namespace EFCoreCodeFirstDemo.Entities
{
719
}
Next, modify the Course Entity as follows to include the OfflineTeacher Reference
Navigational property.
namespace EFCoreCodeFirstDemo.Entities
}
Now, the Course and Teacher entities have two one-to-many relationships. An online
teacher or an offline teacher can teach a Course. In the same way, a teacher can teach
multiple online courses as well as multiple offline courses.
So, if we have only one relationship between two entities, it works fine. But when we have
more than one relationship between two entities, EF Core throws an exception while
executing the add-migration.
With the above changes, open the Package Manager Console and Execute the add-
migration command. And you should get the following error. It is clearly saying that it is
unable to determine the relationship.
720
Let us modify the Teacher Entity class as follows to use the InverseProperty Attribute. As
you can see in the code below, we have decorated the InverseProperty Attribute with
the OnlineCourses and OfflineCourses property and also specified the Course Entity
Navigation property, which should point to make the relationship. With
this, OnlineCourses will have a relationship with the OnlineTeacher property,
and OfflineCourses will have a relationship with the OfflineTeacher property.
using System.ComponentModel.DataAnnotations.Schema;
namespace EFCoreCodeFirstDemo.Entities
[InverseProperty("OnlineTeacher")]
[InverseProperty("OfflineTeacher")]
}
As you can see, we have applied the [InverseProperty] attribute on two collection
navigation properties, i.e., OnlineCourses and OfflineCourses, to specify their related
navigation property in the Course entity. For a better understanding, please have a look at
the below image.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving DBMigration2. The name that you are giving it should not be
given earlier.
Now, verify the database, and you should see the following. As you can see, in this case,
Entity Framework Core creates foreign keys OnlineTeacherTeacherId and
OfflineTeacherTeacherId.
722
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:08 / 03:1710 Sec
namespace EFCoreCodeFirstDemo.Entities
{
724
[NotMapped]
}
Note: If you have a property in your entity that you do not want to be represented as a
column in the database, you can decorate it with the NotMapped attribute.
Please create another class file named StudentOffline.cs class and copy and paste the
following code. As you can see, we also created the class with the same set of properties
here. Then, we applied the NotMapped attribute on the StudentOffline Entity class, which
should exclude the corresponding database table to be created in the database.
using System.ComponentModel.DataAnnotations.Schema;
namespace EFCoreCodeFirstDemo.Entities
[NotMapped]
}
Note: If you have a class that you use in your DbContext but don’t want a corresponding
table in the database, you can decorate the whole class with the NotMapped attribute.
Next, modify the context class as follows. As you can see, we have registered the two
model classes within the context class using DbSet properties.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
Note: Before proceeding further, I am deleting the Migration Folder from our project and the
database.
With the changes in place, open Package Manager Console and Execute the following add-
migration and update-database commands. You can give any name to your migration.
Here, I am giving DBMigration1. The name that you are giving it should not be given earlier.
726
Now, if you verify the database, you will only see the StudentOnlines table, and you can
also verify that there is no column mapping for the RegistrationNumber property, as
shown in the image below.
Note: Even though we have specified the StudentOffline entity in the context class, the
corresponding database table is not generated. This is because we have applied the
StudentOffline entity with the NotMapped Attribute.
The Entity Framework Core will not create a column in the database table for a property if it
does not have both getters or setters. To better understand, please modify
the StudentOnline.cs class as follows. As you can see, for the
Property RegistrationNumber, we only GET Accessor, not the SET Accessor. And for the
Property RollNumber, we have only the SET Accessor, not the GET Accessor. So, in this
case, for RollNumber and RegistrationNumber Properties, the corresponding database
columns will not be created in the database.
namespace EFCoreCodeFirstDemo.Entities
{
727
}
With the changes in place, open Package Manager Console and Execute the following add-
migration and update-database commands. You can give any name to your migration.
Here, I am giving DBMigration2. The name that you are giving it should not be given earlier.
If you verify the database, you will see no column mapping for the RegistrationNumber and
RollNumber properties, as shown in the image below.
728
When using Entity Framework Core (EF Core), the NotMapped attribute specifies that a
particular property or class should not be mapped to a table or column in the database. EF
Core usually maps all entity properties to database columns, but in some cases, certain
properties should not be persisted in the database. In these situations, the NotMapped
attribute is used to exclude those properties from being persisted in the database.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:07 / 03:1710 Sec
The above Required Attribute class specifies that a data field value is required. As you can
see, this class has one parameterless constructor, one property, i.e., AllowEmptyStrings,
and one overridden method, i.e., IsValid.
Note: When using Entity Framework Core (EF Core), the Required Data Annotation
Attribute is used to specify that a property on an entity must have a value and cannot be
null. This attribute is applied to both the database schema (by creating a non-nullable
column) and used for model validation when working with ASP.NET Core MVC and Web
API Applications.
Examples to Understand Required Attribute in Entity Framework Core:
730
By default, for Nullable .NET Data types such as String, EF Core creates the column as a
NULL column, which can accept NULL Values. Let us first understand this default Entity
Framework Core Conventions, and then we will see how to use the Required Data
Annotation Attribute. First, modify the Student.cs class file as follows.
namespace EFCoreCodeFirstDemo.Entities
}
As you can see, here we have created the Student Entity class with four properties. Two
Integer properties and two string properties. As the integers are primitive types, they cannot
hold null values by default. so, for the integer properties, the Entity Framework Core will
create the database column with NOT NULL Constraint, which cannot store a null value.
The string is a reference type, and as they hold a null value by default, for string type
properties, the EF Core will create a NULL type column in the database that can store a
NULL value.
Next, modify the context class as follows. As you can see, we have registered the Student
model class within the context class using the DbSet property.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
{
731
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
With the changes in place, open Package Manager Console and Execute the following add-
migration and update-database commands. You can give any name to your migration.
Here, I am giving DBMigration101. The name that you are giving it should not be given
earlier.
If you verify the database, you will see NOT NULL columns for integer properties. You will
see NULL columns for string properties, as shown in the image below.
732
namespace EFCoreCodeFirstDemo.Entities
[Required]
}
733
}
With the changes in place, open Package Manager Console and Execute the following add-
migration and update-database commands. You can give any name to your migration.
Here, I am giving DBMigration101. The name that you are giving it should not be given
earlier.
If you check the Students database table, you will see that the Name database column is
created using the NOT NULL constraint, as shown in the image below.
Uses:
Database Level: When migrations are created and applied, EF Core will
generate an SQL schema where the column corresponding to the Name
property is non-nullable.
734
Runtime Validation: If you attempt to save an entity that violates any of the
Required constraints (for instance, if you try to save a Student entity without a
Name), EF Core will throw a DbUpdateException.
Example:
Now, to see whether it is working as expected or not, please modify the Main method of the
Program class as shown below. As you can see, we are not providing any value for the
Name property, and hence, it will take the default value null, and when we call the
SaveChanges method, it will throw a runtime exception.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
Address = "Test",
RollNumber = 1
};
context.Add(std);
context.SaveChanges();
Console.WriteLine($"Error: {ex.Message}"); ;
}
How to Restrict Empty Strings in NOT NULL Properties?
Now, our requirement is we will not allow a NULL value, but we want to allow an Empty
value. It would be best to remember that NULL and Empty are different. They are different.
The value null represents the absence of any object, while the empty string is an object of
type String with zero characters.
To accept an empty string in a NOT NULL String Column, we need to use
the AllowEmptyStrings property and set its value to true. This property of
the Required Attribute class gets or sets a value indicating whether an empty string is
allowed. You need to set the value to true if an empty string is allowed; otherwise, false.
The default value is false.
Let us modify the Student Entity class as follows to use Required Attribute with
AllowEmptyStrings property and let us set its value to true.
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
namespace EFCoreCodeFirstDemo.Entities
[Required(AllowEmptyStrings = true)]
}
736
}
Advantages and Disadvantages of Required Attribute in Entity
Framework Core
Advantages of Required Attribute in Entity Framework Core:
Data Integrity: By marking a property as Required, we enforce that specific
pieces of data are present, maintaining the integrity and consistency of the
data in your database.
Clear Model Definitions: By marking properties as Required, developers
can immediately understand which fields are mandatory, leading to clearer
and more understandable entity models.
Automatic Validation: EF Core will automatically perform validation checks
against entities that use the Required attribute. EF Core will ensure that
required fields are populated before changes are saved to the database,
reducing the risk of erroneous data entering the database.
Database Schema Enforcement: The Required attribute Apply the NOT
NULL constraint to the Column in the database, ensuring the rule is enforced
at both the application and database levels.
Disadvantages of Required Attribute in Entity Framework Core:
Initial Migration Challenges: If you add the Required attribute to an existing
property and the database already contains data, you might face challenges
when applying the migration. Existing null values in the database will conflict
with the new non-nullable constraint. Handling this requires additional work,
such as providing default values or cleaning up existing data.
Overhead: Validation checks introduce a slight overhead. In most cases, this
is negligible, but it’s something to consider if you’re doing bulk inserts or
updates.
Flexibility: There might be scenarios where a property might change based
on evolving business rules or application logic. In such cases, using the
Required attribute can be restrictive.
enter a value less than the specified size, it will throw an exception. It is a validation
attribute that will not change the database schema.
By default, for string or byte[] properties of an entity, Entity Framework will set the size of
the database column as max. For string properties, it will create the column as
nvarchar(max), and for byte[] properties, it will create the column as varbinary(max).
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:10 / 03:1710 Sec
MaxLength Attribute:
Limits the number of characters allowed in a string field.
It can also be applied to byte array properties, limiting the number of bytes in
the field.
When used with string properties, it affects the underlying database schema,
setting the maximum length for the corresponding column.
MinLength Attribute:
Specifies the minimum length of the string.
Unlike MaxLength, MinLength does not affect the database schema. It’s
purely a validation constraint and will be checked when you attempt to save
the entity.
Example to Understand MaxLength and MinLength Attribute in EF Core
Let us understand the default convention with an example, and then we will see how to use
MinLength and MaxLength Data Annotation Attributes in EF Core. Please modify the
Student Entity as follows. Here, we have created the Student Entity with four properties.
One Integer Property, two string properties, and one byte[] property. In this case, for string
properties, it will set the corresponding database column as nvarchar(max). For the byte[]
property, it will set the corresponding database column as varbinary(max).
namespace EFCoreCodeFirstDemo.Entities
}
738
Next, modify the context class as shown below. As you can see, we have registered the
Student model within the context class using DbSet.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving DBMig1. The name that you are giving it should not be given
earlier.
739
Now, if you check the Students database table, you will see it created the corresponding
database columns with the maximum size shown in the image below.
using System.ComponentModel.DataAnnotations;
namespace EFCoreCodeFirstDemo.Entities
[MaxLength(50)]
[MinLength(5)]
}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving DBMig2. The name that you are giving it should not be given
earlier.
If you verify the database, you will see that the FirstName column will be created with size
50, and the LastName column will be created with the size max, as shown in the image
below.
741
Note: You must remember that Entity Framework validates the MaxLength and MinLength
property values. For MaxLength, whatever value you specified will be set as the
corresponding database column size. For MinLength, Entity Framework will set the
corresponding database column size as max. The MinLength validation will come into the
picture when we work with the ASP.NET Core MVC and ASP.NET Core Web API
Applications.
How can we set both MaxLength and MinLength Attribute in a Single
Property?
Applying both MaxLength and MinLength Attribute in a single Property is also possible. For
example, our requirement is to set the Maximum Length of the Student’s first name as 10
Characters and the Minimum Length for the Student’s first name as 5 Characters. Then, we
need to use both MaxLength and MinLength Attribute in the FirstName property of the
Student Entity as follows.
using System.ComponentModel.DataAnnotations;
namespace EFCoreCodeFirstDemo.Entities
[MaxLength(10), MinLength(5)]
StringLength Attribute: In ASP.NET Core, the StringLength attribute is used for model
validation. It provides a way to set both minimum and maximum length constraints on a
string property. This is particularly useful for input validation when handling form
submissions.
Advantages and DisAdvantages of MaxLength and MinLength Attribute
in Entity Framework Core
Using the MaxLength and MinLength attributes in Entity Framework Core provides a way to
impose constraints on the data and maintain data integrity. Let’s understand their
advantages and disadvantages:
Advantages:
Data Integrity: MaxLength and MinLength ensure that the data adheres to
the set length constraints, preventing errors or inconsistencies in the data.
Database Optimization: MaxLength helps optimize storage, as setting
appropriate column sizes can save space in the database. For example, a
VARCHAR(50) typically uses less storage than a VARCHAR(MAX).
Automatic Schema Generation: When using Code First migrations in EF
Core, MaxLength will influence the database schema, setting the size of
string columns automatically based on the annotation.
Consistent Validation: Setting these constraints at the model level ensures
consistent validation throughout the application. The validation logic isn’t
scattered or duplicated.
Expressive Code: Annotations make the model self-documenting.
Developers can easily discern the intended length constraints by looking at
the model class.
Disadvantages:
No Database Impact with MinLength: The MinLength attribute is purely for
validation during runtime in EF Core. It doesn’t affect the database schema,
meaning you rely on application logic rather than database constraints to
ensure data integrity for minimum lengths.
Overhead: Applying constraints introduces overhead when saving data, as
EF Core needs to validate entities against these constraints during
SaveChanges().
Less Flexibility for Schema Evolution: If you set a MaxLength and later
decide to need a larger column size, you’ll have to modify the model and then
create and apply a migration to change the database schema.
Error Handling: You must handle validation exceptions arising from violating
these constraints. If not handled appropriately, it might result in poor user
experiences.
Coupling: Data annotations couple the domain model to the persistence
layer. This might not be ideal for architectures aiming for a strict separation
between domain logic and data persistence.
743
As you can see in the above image, the constructor takes one parameter of type
DatabaseGeneratedOption. That means the DatabaseGenerated attribute accepts a
DatabaseGeneratedOption enum as its parameter. Now, if you go to the definition of
DatabaseGeneratedOption, you will see that it is an enum with three values, as shown in
the image below.
744
There are different values you can set for the DatabaseGenerated Attribute in EF Core:
1. DatabaseGeneratedOption.None: This is the default option and indicates
that the database does not generate the property value. It’s typically used for
properties you want to set in your application manually. Use this option if you
do not want the database to generate a value.
2. DatabaseGeneratedOption.Identity: This option indicates that the property
is an identity column in the database, and the database automatically
generates its value during an insert operation. This is often used for primary
key columns. If you want the database to generate a value for a property
when a row is inserted, then you need to use this option.
3. DatabaseGeneratedOption.Computed: This option indicates that the
database computes the property value during insert or update operations.
This can be used for properties whose values are based on calculations or
expressions involving other columns. If you want the database to generate a
value for a property when a row is inserted or updated, then you need to use
this option.
Let us understand each of the above options with examples.
DatabaseGeneratedOption.None in EF Core
This DatabaseGeneratedOption.None is the default option, indicating that the database
does not generate the property value. It’s typically used for properties you want to set in
your application manually. Use this option if you do not want the database to generate a
value.
This option will be useful to override the default convention for the id properties. If you
remember, for the Id Property or <Entity Name>+Id Property, Entity Framework Makes that
column an Identity Column and automatically generates the value for that column.
For example, we want to provide our own values to Id Property or <Entity Name>+Id
Property instead of autogenerated database values. Then, in this case, we need to use
the DatabaseGeneratedOption.None option. For a better understanding, please modify
745
the Student Entity class as follows. As you can see, we have decorated the StudentId
property with the DatabaseGenerated Attribute with DatabaseGeneratedOption.None value.
using System.ComponentModel.DataAnnotations.Schema;
namespace EFCoreCodeFirstDemo.Entities
[DatabaseGenerated(DatabaseGeneratedOption.None)]
}
In this case, the Entity Framework Core will mark the StudentId column in the database as
the Primary Key Column but will not mark this column as an IDENTITY column. So, we
need to specify values for this column while inserting a new record. Next, modify the context
class as shown below. As you can see, we have registered the Student model within the
context class using DbSet.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
746
}
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMigration1. The name that you are giving it should
not be given earlier.
Now, if you check the Students database table Create SQL SQL Script, you will see it
created the primary key column without Identity, as shown in the image below.
747
namespace EFCoreCodeFirstDemo
try
context.Students.Add(student1);
var student2 = new Student() { StudentId = 102, FirstName = "Hina", LastName = "Sharma"
};
context.Students.Add(student2);
//The following Entity will throw an exception as StudentId has duplicate value
//context.Students.Add(student3);
//The following Entity will throw an exception as we have not supplied value for StudentId
//context.Students.Add(student4);
context.SaveChanges();
Console.WriteLine("Students Added");
Console.ReadKey();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Once you execute the above code, please check the database table, and you should see
the above two students as shown in the below image.
749
DatabaseGeneratedOption.Identity in EF Core
It is also possible to mark the non-key properties as DB-generated properties in Entity
Framework Core using the DatabaseGeneratedOption.Identity option. This option
specifies that the property’s value will be generated automatically by the database on the
INSERT SQL statement. This Identity property cannot be updated.
Please note that how the database will generate the value of the Identity property depends
on the database provider. It can be identity, RowVersion, or GUID. SQL Server makes an
identity column for an integer property.
Let us modify the Student Entity as follows. As you can see here, we have applied
the DatabaseGeneratedOption.Identity option with DatabaseGenerated Attribute on
SequenceNumber property. This will mark the SequenceNumber as the Identity Column in
the database. One more point that you need to remember is in a database table, only one
column can be marked as Identity. So, in this case, StudentId will be the Primary Key
column without Identity, and SequenceNumber will be the Identity column.
using System.ComponentModel.DataAnnotations.Schema;
namespace EFCoreCodeFirstDemo.Entities
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
}
750
}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMigration2. The name that you are giving it should
not be given earlier.
Now, if you check the Students database table Create SQL Script, then you will see it
created the primary key column without Identity and SequenceNumber with Identity column
as shown in the below image.
Before proceeding further, let us truncate the Students database table by executing the
following Truncate table statement.
Truncate table Students;
Modifying the Main Method:
Next, we need to modify the Main method of the Program class as follows. Here, you can
see we are explicitly providing values for the StudentId but not for SequenceNumber. If we
provide duplicate value or if we don’t provide any value for the StudentId, then it will throw a
Runtime Exception.
751
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
context.Students.Add(student1);
//We need to provide the unique StudentId value as StudentId is Primary Key
var student2 = new Student() { StudentId = 102, FirstName = "Hina", LastName = "Sharma"
};
context.Students.Add(student2);
context.SaveChanges();
Console.WriteLine("Students Added");
Console.ReadKey();
}
752
Console.WriteLine($"Error: {ex.Message}"); ;
}
Once you execute the above code, please check the database table, and you should see
the above two students as shown in the below image.
DatabaseGeneratedOption.Compute in EF Core
The DatabaseGeneratedOption.Compute option specifies that the property’s value will be
generated by the database when the value is first saved and subsequently regenerated
every time the value is updated. The practical effect is that Entity Framework will not include
the property in INSERT or UPDATE statements but will obtain the computed value from the
database on retrieval.
Similar to Identity, the way the database generates the value depends on the database
provider. You may configure a default value or use a trigger for this computed column.
Database providers differ in the way that values are automatically generated. Some will
generate values for the Selected Data Types, such as Identity, RowVersion, and GUID.
Others may require manual configuration, such as setting default values or triggers or
configuring the column as Computed.
Before proceeding further, let us truncate the Students database table by executing the
following Truncate table statement.
Truncate table Students;
Let us modify the Student Entity as follows. As you can see here, we have applied
the DatabaseGeneratedOption.Computed option with DatabaseGenerated Attribute
on CreatedDate and FullName properties. This will mark the CreatedDate and FullName
columns as the Computed Column in the database, and for the Computed column, we don’t
need to pass any values.
753
using System.ComponentModel.DataAnnotations.Schema;
namespace EFCoreCodeFirstDemo.Entities
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
}
Modifying the Context Class:
We also need to provide some logic so that the values for this column are calculated and
stored in the database automatically. For this, in EF Core, we need to use Fluent API. So,
modify the context class as follows. As you can see inside the OnModelCreating Method,
we use Fluent API to set the computed column values. Here, we have set
the GetUtcDate as the value for the CreatedDate property and First Name + Last
Name as the value for the Full Name Property.
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace EFCoreCodeFirstDemo.Entities
754
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
//We will discuss OnModelCreating Method and Fluent API In Details in our upcoming
articles
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Student>()
modelBuilder.Entity<Student>()
.HasComputedColumnSql("GetUtcDate()");
}
755
Note: In our upcoming articles, we will discuss the OnModelCreating Method and Fluent
API In Detail.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMigration3. The name that you are giving it should
not be given earlier.
Now, if you check the Students database table Create SQL Script, then you will see it
created the primary key column without Identity and SequenceNumber with Identity column,
Fullname, and CreatedDate as Computed Columns, as shown in the below image.
namespace EFCoreCodeFirstDemo
{
756
try
context.Students.Add(student1);
context.SaveChanges();
Console.WriteLine("Student Details:");
Console.ReadKey();
Console.WriteLine($"Error: {ex.Message}"); ;
757
}
Output:
Once you execute the above code, please check the database table, and you should see
the data as expected, as shown in the below image.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:14 / 03:1710 Sec
If you go to the definition of TimeStamp Attribute in EF Core, you will see the following. As
you can see, this class has a parameterless constructor.
759
So, using the Timestamp Attribute in Entity Framework Core is one of the ways to handle
the Concurrency Issues. The Concurrency issue arises when multiple users or transactions
attempt to update/delete the same row simultaneously. We can handle this Concurrency
issue using the Timestamp column. TimeStamp can also be referred to as RowVersion.
What is TimeStamp or RowVersion in SQL Server?
In SQL Server, ROWVERSION and TIMESTAMP are Synonyms. The ROWVERSION has
been available since SQL Server 2005, while TIMESTAMP is deprecated and will be
removed in a future version of SQL Server.
ROWVERSION (TIMESTAMP) is an incrementing 8-byte binary number that does not
store any DateTime-related information. In SQL Server, ROWVERSION and TIMESTAMP
data types represent automatically generated unique binary numbers within the database.
This helps us maintain the integrity of the database when multiple users are updating and
deleting rows simultaneously.
Suppose a table contains a ROWVERSION (or TIMESTAMP) column. In that case, any
time a new row is inserted or any existing row is updated, the value of the ROWVERSION
(or TIMESTAMP) column is set to the current ROWVERSION (or TIMESTAMP) value. This
is true even when an UPDATE statement does not change the data in the database.
Example to Understand Timestamp Attribute in EF Core:
Let us understand the Timestamp Attribute in Entity Framework Core with an example.
Please modify the Student Entity class as follows. Here, we have applied the TimeStamp
Attribute of the RowVersion Property, which is byte[] array type. In an Entity, we can only
apply the TimeStamp Attribute once. The database will create the column with
the TimeStamp data type.
using System.ComponentModel.DataAnnotations;
namespace EFCoreCodeFirstDemo.Entities
[Timestamp]
}
Next, modify the context class as follows:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
{
761
}
Next, modify the Main Method of the Program class as follows. Here, you can see we have
added two entities to the database. The point that you need to remember is that the
database autogenerates the TimeStamp value. So, we do not need to set the value for that
property.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
context.Students.Add(student1);
context.Students.Add(student2);
context.SaveChanges();
Console.WriteLine("Students Added");
Console.ReadKey();
}
762
Console.WriteLine($"Error: {ex.Message}"); ;
}
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
If you verify the database, the RowVersion column should be created with the TimeStamp
data type, as shown in the image below.
763
Now run the application and then verify the Students table. You will see that the auto-
incrementing 8-byte binary number generated, unique within the database, will be stored in
the RowVersion column, as shown in the image below.
The timestamp column will be included in the where clause whenever we update an entity
and call the SaveChanges method. We have already added two students with StudentId 1
and 2. Let us update the student’s first and last names, whose ID is 1. To do so, modify the
Main Method of the Program class as follows.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
{
764
try
if(studentId1 != null)
context.SaveChanges();
Console.WriteLine("Student Updated");
Console.ReadKey();
Console.WriteLine($"Error: {ex.Message}"); ;
}
765
When you run the above code, you will get the following output. Here, you can see that the
where clause and the Primary Key Column also use the RowVersion column within the
UPDATE SQL Statement. This is to handle the concurrency issues.
If you verify the database, the RowVersion column should be updated with the latest
increment value with Student ID 1, as shown in the image below.
Let us understand how to handle concurrency issues with Entity Framework Core. Please
modify the Main Method of the Program class as follows. Here, we are updating the same
entity using two different threads simultaneously. Both Method1 and Method2 read the
student entity whose ID is 1 and also read the same RowVersion value.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
t1.Start();
t2.Start();
t1.Join();
t2.Join();
Console.ReadKey();
Console.WriteLine($"Error: {ex.Message}"); ;
//USER 1 | Transaction 1
Thread.Sleep(TimeSpan.FromSeconds(2));
if (studentId1 != null)
context.SaveChanges();
//USER 2 | Transaction 2
768
Thread.Sleep(TimeSpan.FromSeconds(5));
if (studentId1 != null)
context.SaveChanges();
}
Let us assume Method 1 starts updating first. So, he will update the data in the database
and the RowVersion value. Now, Method2 tries to update the same entity. If you remember,
it will use the RowVersion column while updating, but that RowVersion value with the
Method2 entity has already been modified by Method1. So, Method2 has a RowVersion
value that no longer exists in the database; hence, the Method2 SaveChanges method will
throw an exception showing concurrency issues.
769
If you verify the database, you will see the updated data with the updated RowVersion value
for the student whose ID is 1, as shown in the image below. So, in this way, TimeStamp
Attribute handles the concurrency issues. Each time you perform an INSERT or UPDATE
Operation, the RowVersion value will increment.
So, using the ConcurrencyCheck Attribute in Entity Framework Core is another way to
handle the Concurrency Issues. The Concurrency issue arises when multiple users or
transactions attempt to update/delete the same row simultaneously. We can handle this
Concurrency issue using the ConcurrencyCheck column.
In Entity Framework Core (EF Core), the ConcurrencyCheck attribute indicates that a
particular property should be included in concurrency conflict detection. When a property is
marked with this attribute, EF Core will include it in the WHERE clause of UPDATE and
DELETE commands, ensuring that the value hasn’t changed since the entity was fetched.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:11 / 03:1710 Sec
If you go to the definition of ConcurrencyCheck Attribute in EF Core, you will see the
following. As you can see, this class has a parameterless constructor.
namespace EFCoreCodeFirstDemo.Entities
[ConcurrencyCheck]
[ConcurrencyCheck]
772
}
Next, modify the context class as follows:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
773
}
Next, modify the Main Method of the Program class as follows. Here, you can see we have
added two entities to the database. Unlike TimeStamp, we must provide values for
[ConcurrencyCheck] attribute properties.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
var student1 = new Student() { Name = "Pranaya", Branch = "CSE", RegdNumber = 1001 };
context.Students.Add(student1);
var student2 = new Student() { Name = "Hina", Branch = "CSE", RegdNumber = 1002 };
context.Students.Add(student2);
context.SaveChanges();
Console.WriteLine("Students Added");
Console.ReadKey();
Console.WriteLine($"Error: {ex.Message}"); ;
774
}
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS
and Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
Now, run the application, and you will see the records are being inserted into the Students
database table as expected, as shown in the image below.
The ConcurrencyCheck column(s) will be included in the where clause whenever we update
or delete an entity and call the SaveChanges method. We have already added two students
with StudentId 1 and 2. Let us update the Name and Branch of the Student whose ID is 1.
To do so, modify the Main Method of the Program class as follows.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
{
775
try
if (studentId1 != null)
context.SaveChanges();
Console.ReadKey();
Console.WriteLine($"Error: {ex.Message}"); ;
}
776
When you run the above code, you will get the following output. Here, you can see, in the
where clause, along with the Primary Key Column, it also uses the Name and RegdNumber
columns. This is to handle the concurrency issues.
Let us understand how to handle concurrency issues with Entity Framework using the
ConcurrencyCheck column. Please modify the Main Method of the Program class as
follows. Here, we are updating the same student using two different threads simultaneously.
Both Method1 and Method2 read the student entity whose ID is 1 and also read the same
Name and RegdNumber values.
Let us assume Method 1 starts updating first. So, he will update the data in the database
and the Name and RegdNumber column values. Now, Method2 tries to update the same
entity. If you remember, while updating, it will use the Name and RegdNumber columns in
the where clause, but Method1 has already modified the Name and RegdNumber column
value with the Method2 entity. So, Method2 has Name and RegdNumber values that no
longer exist in the database, and hence Method2 SaveChanges method will throw an
exception showing concurrency issues. It might be possible that thread2 starts its execution
first; in that case, Method1 will throw an exception.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
{
777
try
t1.Start();
t2.Start();
t1.Join();
t2.Join();
Console.ReadKey();
Console.WriteLine($"Error: {ex.Message}"); ;
Thread.Sleep(TimeSpan.FromSeconds(2));
if (studentId1 != null)
context.SaveChanges();
Thread.Sleep(TimeSpan.FromSeconds(2));
if (studentId1 != null)
context.SaveChanges();
779
}
You will get the following exception when you run the above application, which makes
sense.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:10 / 03:1710 Sec
Now, if we want to consume the above Student class, we generally create an instance of
the Student class, and then we need to set the respective properties of the Student class as
shown in the image below.
The Fluent Interfaces Design Pattern simplifies our object consumption code by making our
code more simple, readable, and understandable. Is it not nice to set the Student object
properties as shown in the below image?
Consuming the object like the above interface is like speaking a sentence that would make
the class consumption code more simple, readable, and understandable. The next big thing
we must understand is how to implement this. To implement this, we have something called
Method Chaining.
What is Method Chaining?
Method chaining is nothing but a technique or process where each method sets the value of
a property of an object and then returns an object, and all these methods can be chained
together to form a single statement. To implement method chaining, first, we must create a
wrapper class around the Student class, as shown in the image below.
784
As you can see in the above FluentStudent class, we have created methods for each
Student property. First, we create an instance of Student Class, and then, in each method,
we set the value of the respective Student Property. Further, notice that the return type of
each method is set to the FluentStudent, which is important; because of this, we can call
subsequent methods using the dot operator. Now, the above fluent interface is going to be
consumed by the client. So, with the above FluentStudent class in place, the client code
looks as shown below.
785
namespace FluentInterfaceDesignPattern
student.StudentRegedNumber("BQPPR123456")
.NameOfTheStudent("Pranaya Rout")
.BornOn("10/10/1992")
.StudyOn("CSE")
.StaysAt("BBSR, Odisha");
Console.Read();
student.RegdNo = RegdNo;
return this;
student.Name = Name;
return this;
student.DOB = Convert.ToDateTime(DOB);
return this;
787
student.Branch = Branch;
return this;
student.Address = Address;
return this;
}
Advantages and Disadvantages of the Fluent Interface Design Pattern:
Advantages of the Fluent Interface Design Pattern:
1. Readability: Method chaining provides a natural, readable flow, often
resembling a sequence of statements or commands.
2. Conciseness: Fluent syntax can make the code more concise and
expressive.
3. Discoverability: The fluent interface often guides users toward available
options and actions.
4. Intuitive: The syntax can be more intuitive, especially for configuring or
building complex objects.
Disadvantages of the Fluent Interface Design Pattern:
1. Learning Curve: While fluent interfaces can make code more expressive,
they might have a learning curve for those unfamiliar with the pattern.
2. Complexity: Overuse or excessive method chaining can lead to overly
complex and difficult-to-debug code.
3. Immutability: Fluent interfaces may not be suitable for cases where object
immutability is crucial.
Now, I hope you understand the Fluent Interface Design Pattern. With this kept in mind, let
us proceed and try to understand Fluent API in Entity Framework Core.
Configuring Fluent API in Entity Framework Core:
788
The point that you need to remember is Fluent API configuration can only be applied when
Entity Framework Core builds the models from your domain classes. That means you can
use Fluent API Only when you are working with EF Core Code First Approach.
In EF Core, the DbModelBuilder class acts as a Fluent API, using which we can configure
many different things. It provides more options for configurations than Data Annotation
attributes.
In this case, we need to override the OnModelCreating method of the DbContext class in
our context class to inject the Fluent API configurations, something like the one below in
Entity Framework Core. We call many methods using the same modelBuilder object using
the dot (.) operators, which is nothing but method chaining.
Note: The point that you need to remember is that, in Entity Framework Core, you can
configure a domain class using both Data Annotation Attributes and Fluent API
simultaneously. In this case, EF Core will give precedence to Fluent API over Data
Annotations Attributes.
Fluent API Configurations in Entity Framework Core:
In EF Core, the Fluent API configures the following things of a model class.
1. Model-Wide Configuration: Configures an EF model to database mappings.
Configures the default Schema, DB functions, additional data annotation
attributes, and entities to be excluded from mapping.
2. Entity Configuration: Configures entity to table and relationships mapping.
For example, PrimaryKey, AlternateKey, Index, table name, one-to-one, one-
to-many, many-to-many relationships, etc.
3. Property Configuration: Configures property to column mapping. For
example, column name, default value, nullability, Foreignkey, data type,
concurrency column, etc.
Now, let us proceed and try to understand the different methods available in each
configuration.
Model-Wide Configurations in EF Core
789
Potential for Mistakes: Due to the detailed nature of Fluent API, there’s a
higher chance of making mistakes, especially when configuring complex
relationships.
Maintenance: As your application grows, the OnModelCreating method can
become large and unwieldy, making maintenance challenging.
In Real-Time Applications, many teams find a balance by combining Fluent API and Data
Annotations. Simple configurations might be handled with annotations for clarity and brevity.
In contrast, Fluent API can do more complex configurations or those that need to be
separated from the entity class. The choice ultimately depends on the specific needs of the
project and the team’s preferences.
2. One-to-Many Relationship
3. Many-to-Many Relationship
4. Self-Referencing Relationship
One-to-One (1:1) Relationship:
EF Core Establishes a One-to-One Relationship when both entities have a reference
navigation property pointing to the other entity. Annotations like [ForeignKey] and
[InverseProperty] are often used to define the relationship explicitly. In this relationship, one
entity is related to one and only one instance of another entity.
In the One-To-One relationship, a row in Table A can have no more than one matching row
in Table B, and vice versa. A one-to-one relationship is created if both of the related
columns are primary keys or have unique constraints. In the one-to-one relationship, the
primary key acts additionally as a foreign key, and there is no separate foreign key column.
One-to-Many (1:N) Relationship:
This type of relationship is established when one entity has a collection navigation property
pointing to a reference navigation property. The related entity often has a reference
navigation property pointing back to the single entity (the “one” side). One entity instance
can be related to multiple instances of another entity but not vice versa.
The One-To-Many relationship is the most common type of relationship between database
tables or entities. In this type of relationship, a row in one table, let’s say Table A, can have
many matching rows in another table, let’s say Table B, but a row in Table B can have only
one matching row in Table A.
Many-to-Many (M:N) Relationship:
EF Core represents many-to-many relationships by introducing a joining table in the
database schema. Both entities have collection navigation properties pointing to related
entities.
In Many-to-Many Relationships, a row in one table, let’s say Table A, can have many
matching rows in another table, let’s Table B, and vice versa is also true, i.e., a row in Table
B can have many matching rows in Table A. In Relational Databases, we can create many-
to-many relationships by defining a third table, whose primary key consists of the foreign
keys from tables A and B. So, here, the Primary Key is a composite Primary key.
}
795
Passport.cs
Next, create another class file with the name Passport.cs and copy and paste the following
code. As you can see, this class contains a few scaler properties and a reference navigation
property pointing to the Person entity.
namespace EFCoreCodeFirstDemo.Entities
}
Now, we need to configure the above Person and Passport entities in such a way that EF
Core should create the Person and Passport tables in the Database and make
the PersonId column in the Person table as the Primary Key (PK) and PersonId column
in the Passport table as Foreign Key (FK) and should apply the Unique Constraints on
the PersonId Foreign Key Column.
Note: In the One-To-One or One-To-Zero relationship, a row in Table A can have a
maximum of one or zero matching rows in Table B, and vice versa is also true.
How to Configure One-to-One Relationship using Fluent API?
We can configure the One-to-One required relationship between two entities using both the
Data Annotation Attribute and Fluent API. We have already discussed configuring the One-
to-One relationship between the two entities using the ForeignKey Data Annotation
Attribute.
Now, let us proceed and try to understand how to Configure the One-to-One relationships
between two entities using Entity Framework Core Fluent API. To implement this, we must
use the following HasOne(), WithOne(), and HasForeignKey() Fluent API methods.
HasOne(): The HasOne() method specifies the navigation property that
represents the principal end of the relationship.
WithOne(): The WithOne() method specifies the navigation property on the
dependent side.
HasForeignKey<TDependent>(): The HasForeignKey<TDependent>() me
thod is used to specify which property in the dependent entity represents the
foreign key.
796
Next, modify the context class as follows. The following code sets a one-to-one required
relationship between the Person and Passport entities using Entity Framework Core Fluent
API.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<Person>()
}
In the context class, we must configure the relationship using Fluent API within the
OnModelCreating method. The following code configures the One-to-One relationship
between the Person and Passport entities.
Now, if you verify the database, you should see the following.
Further, if you verify the Passport table, you will see that it applies the Unique Index on the
PersonId column, as shown in the image below. This unique index allows the column value
to be unique, and the value must be an existing PersonId, hence implementing a One-to-
One Relationship.
799
Next, modify the Main method of the Program class as follows. We have specified the
Passport reference property here while creating the Person Entity.
using EFCoreCodeFirstDemo.Entities;
using System.Net;
namespace EFCoreCodeFirstDemo
try
context.Persons.Add(Person);
context.SaveChanges();
Console.ReadKey();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Now, run the application and verify the database. You should see both Persons and
Passports database tables filled with one record, as shown below.
namespace EFCoreCodeFirstDemo.Entities
}
UserProfile.cs
Next, create another class file named UserProfile.cs and copy and paste the following
code. As you can see, this class contains a few scaler properties and a reference navigation
property pointing to the User entity.
using System.ComponentModel.DataAnnotations;
namespace EFCoreCodeFirstDemo.Entities
[Key]
}
When the Primary Key (PK) of one table becomes the Primary Key (PK) and Foreign Key
(FK) in another table in a relational database such as SQL Server, MySQL, Oracle, etc.,
802
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<User>()
}
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
Now, if you verify the database, you should see the following.
804
Next, modify the Main method of the Program class as follows. While creating the User
Entity, we have specified the UserProfile reference property.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
{
805
context.Users.Add(user);
context.SaveChanges();
Console.ReadKey();
Console.WriteLine($"Error: {ex.Message}"); ;
}
You can also start configuring with the UserProfile entity in the same way, as follows.
namespace EFCoreCodeFirstDemo.Entities
806
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<UserProfile>()
//modelBuilder.Entity<User>()
}
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes in place, open the Package Manager Console and Execute
the add-migration and update-database commands as follows. You can give any name to
your migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should
not be given earlier.
Now, if you verify the database, you should see the following.
808
The choice between principal-dependent and shared primary key relationships should be
based on the domain model and specific requirements of your application. The principal-
dependent relationship is more common in real-world scenarios.
Note: By using the Fluent API, we have more control over the relationship configuration,
such as customizing the foreign key property names, specifying cascading delete behavior,
and other advanced features. Remember that the Fluent API configuration takes
precedence over data annotations when both are used together.
In Entity Framework Core (EF Core), a one-to-many (1:N) relationship means that one
entity (often termed the “principal” or “parent”) can be related to multiple instances of
another entity (often termed the “dependent” or “child”), but each instance of the dependent
entity relates back to only one instance of the principal entity.
For a one-to-many relationship, the principal entity typically has a collection navigation
property, while the dependent entity will have a reference navigation property and a foreign
key property. To represent a one-to-many relationship, we will typically have:
Enchanted by the Beautiful City near Cambodia Border - Nếm TV00:17 / 02:4810 Sec
A collection navigation property in the principal entity points to a collection of
dependent entities.
A foreign key property in the dependent entity pointing back to the principal
entity.
A reference navigation property in the dependent entity pointing back to the
principal entity.
Examples of Understanding One-to-Many Relationships in EF Core
In Entity Framework Core (EF Core), you can define a one-to-many relationship between
entities using the Fluent API. The Fluent API provides a way to configure the relationships
and mappings between entities in a more flexible and detailed manner than using only data
annotations.
Let us understand this with an example. We will implement a One-to-Many relationship
between the following Author and Book Entities. Consider the following Author and Book
classes where the Author entity includes many Book entities, but each book entity is
associated with only one Author entity. So, an Author can write multiple Books, but each
Book has only one Author.
Author.cs
First, create a class file named Author.cs, and copy and paste the following code. As you
can see, this entity has a few scaler properties and one collection navigation property
pointing to the Book entity. This will be our Principal Entity.
namespace EFCoreCodeFirstDemo.Entities
}
810
}
Book.cs
Next, create another class file named Book.cs, and copy and paste the following code. As
you can see, this entity has a few scaler properties and one reference navigation property
pointing to the Author entity. AuthorId is going to be the Foreign Key. This will be our
Dependent Entity.
namespace EFCoreCodeFirstDemo.Entities
}
How to Configure One-to-Many Relationships Using Fluent API in EF
Core?
Generally, we do not need to configure the one-to-many relationship in the entity framework
core because one-to-many relationship conventions cover all combinations. However, you
may configure relationships using Fluent API in one place to make them more maintainable.
Now, you can configure the one-to-many relationship for the above two entities in Entity
Framework Core using Fluent API by overriding the OnModelCreating method in the context
class. To do so, we will use the following Fluent API methods.
1. HasMany(): Configures a relationship where this entity type has a collection
that contains instances of the other type in the relationship. That means it
specifies the ‘many’ side of the relationship.
2. WithOne(): Configures a relationship where this entity type has a reference
that contains an instance of the other type in the relationship. That means it
specifies the ‘one’ side of the relationship.
3. HasForeignKey(): Configures the property(s) to use as the foreign key for
this relationship. That means it specifies which property is the foreign key.
811
Now, modify the context class as follows. In this example, the Author is the Principal Entity,
and the Book is the dependent Entity. One Author can have multiple books, and each book
can belong to a single author. So, in the Book Entity, we will create the Foreign Key.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<Author>()
.HasMany(a => a.Books) // Author has many Books, specifies the 'many' side of the
relationship
.WithOne(b => b.Author) // Book is associated with one Author, specifies the 'one' side of the
relationship
.HasForeignKey(b => b.AuthorId); // AuthorId is the Foreign key in Book table, specifies
which property is the foreign key
}
812
}
In the context class, we need to configure the relationship using EF Core Fluent API within
the OnModelCreating method. The following code configures the One-to-Many relationship
between the Author and Book entities. That means one Author can have multiple books (0
or 1, n number of books), and one book can belong to a single Author.
Code Explanation:
modelBuilder.Entity<Author>(): First, we must start configuring with one
entity class, either Author or Book. So, here, we start configuring the Author
entity.
HasMany(a => a.Books): Specifies the ‘many’ side of the relationship. This
method specifies that the Author entity includes one Books collection
navigation property using a lambda expression. It configures a relationship
where this entity type, i.e., Author entity, has a collection property, i.e., Books,
that points to instances of the other entity, i.e., Book, in the relationship. That
means the Author has many Books.
WithOne(b => b.Author): Specifies the ‘one’ side of the relationship. This
method configures the other end of the relationship, the Book. It specifies
that the Book entity includes a reference navigation property of Author type,
i.e., Book entity, has a reference property, i.e., Author, that points to an
instance of the other entity, i.e., Author, in the relationship. That means the
one Book is associated with only one Author.
HasForeignKey(b => b.AuthorId): Specifies which property is the foreign
key. This method specifies the foreign key property name. It configures the
property, i.e., AuthorId, as the foreign key for this relationship. That means
AuthorId is the Foreign key in the Book table.
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
813
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
Now, if you verify the database, you should see the following.
namespace EFCoreCodeFirstDemo
814
try
};
context.Authors.Add(author);
context.SaveChanges();
Console.ReadKey();
{
815
Console.WriteLine($"Error: {ex.Message}"); ;
}
Execute the above code and then verify the database. You will see one Author record with
three corresponding books, as shown in the image below.
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<Author>()
.HasMany(a => a.Books) // Author has many Books, specifies the 'many' side of the
relationship
.WithOne(b => b.Author) // Book is associated with one Author, specifies the 'one' side of the
relationship
.HasForeignKey(b => b.AuthorId) // AuthorId is the Foreign key in Book table, specifies
which property is the foreign key
}
817
}
The OnDelete() of the Fluent API method uses the DeleteBehavior parameter. If you go to
the definition of DeleteBehavior, you will see it is an enum with the following values.
DeleteBehavior Indicates how a delete operation is applied to dependent entities in a
relationship when the principal is deleted, or the relationship is severed.
You can specify any of the above DeleteBehavior values based on your requirements. The
meaning of the above enum named constants are as follows:
1. Cascade: For entities being tracked by the context, dependent entities will be
deleted when the related principal is deleted.
2. ClientSetNull: For entities being tracked by the context, the values of foreign
key properties in dependent entities are set to null when the related principal
is deleted. If a property cannot be set to null because it is not a nullable type,
an exception will be thrown when SaveChanges() is called.
3. Restrict: For entities being tracked by the context, it prevents the principal
entity from being deleted if any related dependents exist. This is useful to
prevent accidental data loss.
4. SetNull: For entities being tracked by the context, the values of foreign key
properties in dependent entities are set to null when the related principal is
deleted. If a property cannot be set to null because it is not a nullable type, an
exception will be thrown when SaveChanges() is called.
818
Currently, we have one author in the Authors database table with the AuthorId 1, and it has
three corresponding records in the Books database table, as shown in the image below.
819
namespace EFCoreCodeFirstDemo
try
if (Id1 != null)
context.Remove(Id1);
context.SaveChanges();
Console.WriteLine("Author Deleted");
else
Console.ReadKey();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Once you execute the application code, verify the database and check the Books table, and
you will see the corresponding Author records should have been deleted.
Points to Remember:
1. Database Provider Limitations: Not all delete behaviors are supported by
all database providers. It’s essential to check the capabilities of your database
provider when choosing a delete behavior.
2. Data Integrity: Cascading deletes can lead to unintentional data loss. Always
ensure you understand the implications of your chosen delete behavior,
especially in production scenarios.
821
table/entity in our model. This simplification makes it easier to model and work with many-
to-many relationships.
Examples of Understanding Many-to-Many Relationships in EF Core
Using Fluent API
In Entity Framework Core (EF Core), we can define a many-to-many relationship between
entities using the Fluent API. Let’s configure a many-to-many relationship using the Fluent
API in EF Core. Suppose we have two entities, Student and Course, where each student
can enroll in multiple courses, and each course can have multiple students. So, let us
create the following two entities.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:12 / 03:1710 Sec
Student.cs
First, create a class file named Student.cs and copy and paste the following code. Here,
you can see we have a collection navigation property that is pointing to the Course entity.
namespace EFCoreCodeFirstDemo.Entities
}
Course.cs
Next, create another class file with the name Course.cs and then copy and paste the
following code into it. Here, you can see we have a collection navigation property that is
pointing to the Student entity.
namespace EFCoreCodeFirstDemo.Entities
}
EF Core will automatically infer the many-to-many relationship with these entities due to the
presence of navigation properties. However, if you want to configure the relationship
explicitly using Fluent API, you can do so. So, modify the EFCoreDbContext class as
follows to configure Many to Many Relationships using Fluent API. In this configuration, we
use the HasMany, WithMany, and UsingEntity methods to define the many-to-many
relationship between Student and Course.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
824
modelBuilder.Entity<Student>()
}
In this Fluent API configuration:
HasMany(s => s.Courses): Specifies that a Student can have many
Courses.
WithMany(c => c.Students): Specifies that a Course can have many
Students.
UsingEntity(j => j.ToTable(“StudentCourses”)): Explicitly names the join
table. If you don’t specify this, EF Core will use a default name based on the
two entity names.
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
825
Now, if you verify the database, you should see the following.
namespace EFCoreCodeFirstDemo
try
};
context.Courses.AddRange(courses);
context.SaveChanges();
Console.WriteLine("Courses Added");
context.Students.Add(Pranaya);
context.Students.Add(Hina);
context.SaveChanges();
Console.WriteLine("Students Added");
827
Console.ReadKey();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Now, run the above code and verify the StudentCourses table, and you should see the
following.
{
828
}
Course.cs
Next, modify the Course class as follows. Here, you can see we have a collection
navigation property that is pointing to the Student entity. Also, we have the StudentCourses
property, which is required for Implementing Many-to-Many Relationships.
namespace EFCoreCodeFirstDemo.Entities
}
829
Next, define a new joining entity class that includes both entities’ foreign key and reference
navigation properties. So, create a class file named StudentCourse.cs and copy and paste
the following code. This entity has two foreign key properties (StudentId and CourseId) and
two reference navigation properties pointing to the corresponding entities.
namespace EFCoreCodeFirstDemo.Entities
}
The foreign keys must be the composite primary key in the joining table. This can only be
configured using Fluent API. So, modify the context class as follows to configure both the
foreign keys in the joining entity as a composite key using Fluent API. In this configuration,
we use the HasMany, WithMany, and UsingEntity methods to define the many-to-many
relationship between Student and Course.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<Student>()
.UsingEntity<StudentCourse>(
}
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
831
Now, if you verify the database, you should see the following.
namespace EFCoreCodeFirstDemo
try
};
context.Courses.AddRange(courses);
context.SaveChanges();
Console.WriteLine("Courses Added");
context.Students.Add(Pranaya);
context.Students.Add(Hina);
context.SaveChanges();
Console.WriteLine("Students Added");
Console.ReadKey();
}
833
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
If you check the StudentCourse database table, you’ll find this data.
With EF Core 5.0 and later, setting up many-to-many relationships has become more easier
and less error-prone. The ability to implicitly handle the join table or further configure it using
the Fluent API provides developers with flexibility while maintaining clarity in their data
models.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:07 / 03:1710 Sec
namespace EFCoreCodeFirstDemo.Entities
}
In the above example, an Employee entity has a Manager navigation property that points to
another Employee, representing the manager of the current employee representing the one-
to-one relationship. Additionally, it has a collection navigation property called Subordinates
to represent the employees who report to the current employee, representing the one-to-
many relationships. In this model:
ManagerId is a foreign key pointing to another Employee (the manager).
Manager is a reference navigation property pointing to the manager.
Subordinates is a collection navigation property containing all Subordinates
(employees) under the current Employee.
835
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<Employee>()
.HasOne(e => e.Manager) // Specifies that each employee has one manager.
.WithMany(e => e.Subordinates) // Specifies that each manager can have many Subordinates
.HasForeignKey(e => e.ManagerId) // Specifies the foreign key property for this relationship.
}
836
}
In this configuration, we use the HasOne and WithMany methods to define the self-
referencing relationship between Employee and himself. The HasForeignKey method
specifies the foreign key property (ManagerId) in the Employee table. You can also specify
the delete behavior using the OnDelete method, which determines what happens to related
entities when an entity is deleted. In this Fluent API configuration:
HasOne(e => e.Manager): Specifies that each Employee has one Manager
(who is also an Employee).
WithMany(m => m.Subordinates): Specifies that an Employee (as a
manager) can have many Subordinates.
HasForeignKey(e => e.ManagerId): Specifies the foreign key property for
the relationship.
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
Now, if you verify the database, you should see the following.
837
namespace EFCoreCodeFirstDemo
try
//Create a Manager
context.Add(mgr);
context.SaveChanges();
Console.WriteLine("Manager Added");
838
context.Add(emp1);
context.Add(emp2);
context.SaveChanges();
Console.WriteLine("Employees Added");
Console.WriteLine("List of Employees");
if (Manager.Subordinates != null)
}
839
Console.ReadKey();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
}
Standard.cs
namespace EFCoreCodeFirstDemo.Entities
841
}
Configure Default Schema using Entity Framework Core Fluent API
Configuration:
Let us understand how to configure the default schema for the tables in the database.
However, changing the schema while creating the individual tables is also possible. By
default, the schema is created as dbo. For a better understanding, please modify the
context class as follows.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
Now, if you verify the database, you should see the following.
843
Now, what our requirement is, instead of dbo, we want to set the default schema as Admin.
To do so, we need to use the HasDefaultSchema method, and to this method, we need to
pass the default schema name.
So, to configure a default schema for all tables in Entity Framework Core using the Fluent
API, we need to use the ModelBuilder’s HasDefaultSchema method within the
OnModelCreating method of our DbContext class. So, modify the Context class as follows.
In the below code, we have set Admin as a default schema.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.HasDefaultSchema("Admin");
}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig2. The name that you are giving it should not be
given earlier.
Now, if you verify the database, you should see the following. You will see that both the
tables will be created with Admin schema, as shown in the below image.
By setting the default schema using HasDefaultSchema, every entity will be created under
this schema unless specifically overridden using the ToTable method with the schema
845
parameter. Later in this article, I will explain how to specify a different schema using the
ToTable method.
Map Entity to Table using Fluent API in Entity Framework Core.
By default, the Entity Framework Core Code-First Approach will create the tables in the
database with the name DbSet properties in the context class. For example, modify the
context class as follows. Here, we have included two DbSet Properties named Students and
Standards.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
In this case, the Entity Framework Core will create two tables in the database with the
name Students and Standards as the DbSet Properties name are Students and
Standards. We can override this default convention using Data Annotation Attribute and
846
Fluent API in EF Core. We have already discussed using the Table Data Annotation
Attribute with Entity Framework Core. Let us understand how to override the default
convention using Entity Framework Core Fluent API.
How to Map Entity to Table using Fluent API in Entity Framework Core?
To Map an Entity to a Table using Entity Framework Fluent API, we must use the ToTable
method. The ToTable() method is used to specify the name of the database table
associated with the entity. Useful when the table name does not match the default naming
convention
There are two overloaded versions available of this method. One overloaded version takes
the tableName as a parameter, while the other method takes tableName and database
schemaName as parameters.
So, modify the context class to give a table name different than the DbSet properties name.
Here, we need to override the onModelCreating method. Even though the DbSet property
names are Students and Standards, the Entity Framework Core will generate tables with
the names StudentInfo and StandardInfo in the database. The point that you need to
remember is that modelBuilder.Entity<TEntity>() returns
the EntityTypeConfiguration object, using which we can configure many things.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
As you can see in the above code, we start with the Entity<TEntity>() method. Most of the
time, we have to start with the Entity<TEntity>() method to configure the Entity using
Fluent API, and here, we need to provide the Entity name for which we want to provide
configuration. Then, we used the ToTable() method to map the Student entity to
the StudentInfo table and the Standard entity to the StandardInfo table. Here, the
StandardInfo table will be created with the Admin schema because we have specified the
Admin schema for the StandardInfo table. Studentinfo table will be created with the default
schema as we have not specified schema, and in this case, the default schema will be dbo.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig3. The name that you are giving it should not be
given earlier.
Now, if you verify the database, you should see the following. Here, you can see the
StudentInfo table is created with the default dbo schema, and the StandardInfo table is
created with the Admin schema.
848
modelBuilder.Entity<Employee>()
modelBuilder.Entity<Order>()
}
Next, modify the context class as follows:
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<Employee>()
modelBuilder.Entity<Employee>()
modelBuilder.Entity<Employee>()
modelBuilder.Entity<Employee>()
}
851
}
In this example, we configure the Employee entity with an alternate key or Unique Key on
the Email property, a unique index on the RegdNumber property, a primary key on the
EmpId property, and exclude the TemporaryAddress property from mapping to the
database.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig3. The name that you are giving it should not be
given earlier.
Now, if you verify the database, you should see the following.
852
How to Create a Table without Primary Key using EF Core Fluent API?
In Entity Framework (EF) Core, it’s a common practice to define a primary key for each
table, as primary keys uniquely identify rows in a table. However, there are scenarios where
you might want to create a table without a primary key, such as mapping to an existing
database table or dealing with a non-standard database schema. You can achieve this
using EF Core’s Fluent API by configuring the entity as a keyless entity.
Let us see how we can create a table without a primary key using EF Core Fluent API.
Define the entity class without a primary key. Create a class file with the name
TableWithOutPrimary key. The following entity will represent the table without a primary
key.
namespace EFCoreCodeFirstDemo.Entities
// Other properties...
}
Next, modify the context class as follows. In our DbContext class, we need to override the
OnModelCreating method and use the Fluent API to configure the entity as a keyless entity
using the HasNoKey method:
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<TableWithoutPrimaryKey>()
}
854
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig4. The name that you are giving it should not be
given earlier.
Now, if you verify the database, you should see the following.
Primary Key and Composite Primary Key using Entity Framework Core Fluent
API
In this article, I will discuss Configuring Primary Key and Composite Primary Key using
Entity Framework Core (EF Core) Fluent API with Examples. Please read our previous
article discussing Entity Configurations using Entity Framework Core Fluent API with
Examples. At the end of this article, you will understand the following pointers.
855
}
Let us understand how to Configure Primary Key and Composite Primary Key using Entity
Framework Core Fluent API with an example. If you see our Student domain class, it does
not have either Id or <Entity Name>+ Id property. The Entity Framework Core will not follow
the default code-first conventions while generating the database tables. So, in situations like
this, we can go with Entity Framework Core Fluent API and configure the key property using
the HasKey() method.
Ad
856
1/2
00:48
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem TV
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
Now, if you verify the database and check the Primary Key column, you can see
StudentRegdNumber will be created as the Primary key, as shown in the below image.
Switching off Identity for Numeric Primary Key using EF Core Fluent
API:
In Entity Framework Core, we can configure a numeric primary key column not to use the
identity/auto-increment feature. The Identity column will be generated for the Integer Type
key only. If we want to provide the values for the Primary Key column explicitly, we need to
858
switch off the identity. To do this using the Fluent API, we need to specify that the column
should not be generated as an identity column. We can achieve this by using
the ValueGeneratedNever method.
In our DbContext class, we must use the Fluent API in the OnModelCreating method to
configure the primary key column. So, modify the context class as follows. Here, we specify
the primary key for the Student entity using the HasKey method. We then configure the
StudentRegdNumber property not to be generated as an identity column by using
the ValueGeneratedNever method.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<Student>()
}
This configuration tells EF Core not to generate values for the StudentRegdNumber column,
and it will be our responsibility to provide unique values when inserting new records. This
can be useful when we have an existing database with specific primary key values that we
want to manage manually.
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project. This is because we are going to update the Identity
Column.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
Next, to test whether Identity is off, add two new entities into the Students table with user-
defined values for the Primary key column. So, modify the Main method of the Program
class as follows:
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
StudentRegdNumber = 101,
StudentRollNumber = 5001,
FirstName = "Pranaya",
LastName = "Rout"
};
StudentRegdNumber = 102,
StudentRollNumber = 5011,
FirstName = "Hina",
LastName = "Sharma"
};
context.Add(std1);
context.Students.Add(std2);
//Call the Save Changes Method to add these two entities into the Students database table
861
context.SaveChanges();
Console.WriteLine("Student Addedd");
Console.ReadKey();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Now, run the application and verify the Students database table, and you should see the
following two records in the database with the explicitly provided StudentRegdNumber.
namespace EFCoreCodeFirstDemo.Entities
{
862
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<Student>()
}
In this example, the Student entity has a composite primary key consisting of two
properties: StudentRegdNumber and StudentRollNumber.
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project as we modify the Primary key and Identity column.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
863
Now, if you verify the database, you should see the Primary key created on both the
StudentRegdNumber and StudentRollNumber columns of the Students table in the
database, as shown in the image below.
}
Now, modify the context class as follows. Here, we are making the FirstName Property of
the Student Entity as the Primary Key column in the database. In our DbContext class, we
need to use the Fluent API in the OnModelCreating method to configure the primary key for
the entity.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<Student>()
865
}
With this configuration, EF Core will treat the StudentRegdNumber property as the primary
key when generating the database schema. It’s important to note that string primary keys
are supported in EF Core, but you should ensure that the values you assign to this property
are unique, as primary keys must be unique within the table to maintain database integrity.
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project as we modify the Primary key and Identity column.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
If you verify the database, you should see the Primary key created on the
StudentRegdNumber column of the Students table in the database, which type is nvarchar,
as shown in the image below.
866
Note: Remember that a primary key uniquely identifies each row in a table, so make sure
the chosen properties or combination of properties are unique within the entity. EF Core
also supports conventions, so if your entity follows the convention of having a property
named Id and Entity Name + ID, EF Core will automatically consider it the primary key.
However, you can override this convention using the Fluent API if needed.
the HasColumnOrder method, and to configure the column data type; we need to use
the HasColumnType method.
HasColumnName(): We need to use the EF Core Fluent API
HasColumnName method to configure the column name for a property. This
method allows us to specify the name of the column in the database that
corresponds to the property.
HasColumnType(): We need to use the EF Core Fluent API HasColumnType
method to configure the data type of a column. This method allows us to
specify the data type that the column should have in the database.
HasColumnOrder(): We need to use the EF Core Fluent API
HasColumnOrder method to configure the order of a column in the database
table. This method allows us to specify the position of the column relative to
other columns in the table.
Let us understand this with an example. First, modify the Student Entity as follows.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:19 / 03:1710 Sec
namespace EFCoreCodeFirstDemo.Entities
}
Next, modify the context class as follows. Here, we set the Column’s Name, Type, and
Order. The Column Order will only work if we create the database table for the first time,
and if we apply the Order for all the properties of the entity, else the order will not work.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
{
868
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<Student>()
.HasColumnName("DOB")
.HasColumnOrder(2)
.HasColumnType("datetime2");
modelBuilder.Entity<Student>()
.HasColumnType("int")
.HasColumnOrder(0);
modelBuilder.Entity<Student>()
.HasColumnType("nvarchar(50)")
.HasColumnOrder(1);
modelBuilder.Entity<Student>()
.HasColumnOrder(3);
}
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
If you verify the database, you should see the column with the proper name, order, and
type, as expected, as shown in the image below.
870
{
871
}
Let us first check the default data type and size of the columns, and then we will see how
we can change the size of the column using EF Core Fluent API. So, modify the context
class as follows.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
Now, if you verify the database, you should see the database column with the default size
and data type, as shown in the below image.
Next, modify the context class as follows. We have applied the HasMaxLength method on
the FirstName, LastName, and Photo properties and the IsFixedLength method on the
LastName property. We have also applied the HasPrecision method on the Height Property.
873
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
//Set FirstName column size to 50, by default data type as nvarchar i.e. variable length
modelBuilder.Entity<Student>()
.HasMaxLength(50);
modelBuilder.Entity<Student>()
.HasMaxLength(50)
.IsFixedLength();
874
modelBuilder.Entity<Student>()
.HasPrecision(2, 2);
modelBuilder.Entity<Student>()
.HasMaxLength(50);
}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig2. The name that you are giving it should not be
given earlier.
Now, if you verify the database, you should see the column with the appropriate size and
data type as expected, as shown in the image below.
875
}
Configure a Column to Allow NULL Values (Nullable):
876
To allow a column to accept NULL values, use the IsRequired(false) method in the EF Core
Fluent API configuration. By default, EF Core assumes that columns are nullable, so you
only need to use this method if you want to allow NULL values explicitly.
modelBuilder.Entity<Person>()
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
877
modelBuilder.Entity<Student>()
modelBuilder.Entity<Student>()
.IsRequired(true);
modelBuilder.Entity<Student>()
.IsRequired(false);
}
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
878
Now, verify the database, and you should see the columns with NULL and NOT NULL
constraints as expected, as shown in the below image.
typically used when we have properties in our entity class that should not be persisted in the
database.
The Fluent API Ignore method is used to disable property mapping to a column in the
database. Let us understand this with an example. First, modify the Student Entity class as
follows.
namespace EFCoreCodeFirstDemo.Entities
}
Now, while generating the database for the above Student Entity, we do not want to map
the Branch property in the corresponding database. To Ignore the Branch property, we
need to modify the context class as follows. Here, you can see on the Student entity, we call
the Ignore method and specify the Branch property.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
Note: It’s important to note that when you ignore a column using Ignore, the property won’t
be part of the database schema, and EF Core won’t generate SQL statements for it during
database operations.
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
Now, verify the database, and you will see that the Branch column is not in the Students
database, as shown in the image below. In this case, the Ignore method excludes property
from the model, so it will not be mapped to the database.
881
}
Next, modify the context class as follows. The following example code will ensure that the
Standard table is not created in the database.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
{
882
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Ignore<Standard>();
}
As you can see in the above code, we have created Standard Entity as a DbSet property.
But then we used the Ignore method and specified the Standard Entity, which will exclude
the Standard entity while generating the database.
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
883
Now, verify the database, and you should see only the Students table but not the Standard
table in the database, as shown in the image below.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:10 / 03:1710 Sec
namespace EFCoreCodeFirstDemo.Entities
}
Next, modify the Context class as follows:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
With this, our Database with Students database table is created, as shown in the below
image:
886
namespace EFCoreCodeFirstDemo
{
887
try
};
BulkInsert(newStudents);
GetStudents("CSE");
Console.ReadKey();
Console.WriteLine($"Error: {ex.Message}"); ;
context.Students.AddRange(newStudents);
888
//context.AddRange(newStudents);
context.SaveChanges();
}
Output:
889
Note: This is one of the biggest changes they have made to the AddRange method in
Entity Framework Core. Using a Single Round Trip with the database, using the SQL
MERGE and INSERT Statement, they are performing the BULK Insert Operation. While the
performance is better in EF Core than the old EF6, the solution becomes very inefficient
when inserting thousands of entities.
Bulk Update in Entity Framework Core:
Bulk Updating is the process of efficiently updating many records into a database table. In
BULK UPDATE, first, we need to fetch all the records from the database that need to be
updated and then update the required properties of the entities. When we update the
properties of entities, it will mark the entity state as Modified. When we call the
SaveChanges() method, the Entity Framework Core will generate the UPDATE SQL
Statement to update the changes in the database.
For a better understanding, please modify the Program class as follows: In the BulkUpdate
method, first, we fetch all the students whose Branch is CSE and then update the
FirstName and LastName properties. Once we update the FirstName and LastName
properties, we call the SaveChanges method to update the data in the database. In this
case, the EF Core will generate and execute separate SQL UPDATE Statements in the
database.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
890
BulkUpdate("CSE");
GetStudents("CSE");
Console.ReadKey();
Console.WriteLine($"Error: {ex.Message}"); ;
std.FirstName += "Changed";
std.LastName += "Changed";
}
891
context.SaveChanges();
}
Output:
892
As you can see, using a single database connection, or you can say using a single
database round trip, it executes the three update statements in the database. So, it will
perform better than EF6, but it is not optimized. In our next article, I will show you how to
perform the BULK UPDATE operations in an optimized manner.
Note: By looking at the three UPDATE SQL Statements, please don’t think it makes three
database round trips. This is happening in Entity Framework, not in Entity Framework Core.
In EF Core, they have improved the performance at some level by sending all these three
statements in a single database round trip. The problem has occurred when you are going
to update thousands of records.
Bulk Delete in Entity Framework Core:
Bulk deleting involves removing a large number of records efficiently. In Entity Framework
Core, we can use the DbSet RemoveRange() method or the DbContext RemoveRange()
method to perform bulk delete operations. What the RemoveRange() method does is attach
a collection of entities to the context object with the Deleted state, and when we call the
SaveChanges method, it will execute the DELETE SQL Statement in the database for all
the entities.
For a better understanding, please modify the Program class as follows. In the below
example, we are fetching all the students from the Student database table where the
Branch is CSE and storing them in studentsList collection variable. Then, pass that list of
students to the RemoveRange method, marking the Entity State of all those students as
Deleted. When we call the SaveChanges method, it will generate and execute separate
DELETE SQL Statements into the database for each entity.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
{
893
try
BulkDelete("CSE");
//If Delete Operation Successful, then it should not display any data
GetStudents("CSE");
Console.ReadKey();
Console.WriteLine($"Error: {ex.Message}"); ;
context.Students.RemoveRange(studentsList);
context.SaveChanges();
if (studentsList.Count > 0 )
else
}
895
Output:
Similar to Bulk Update Operation, in this case, it executes the three DELETE SQL
statements in the database using a single database round trip. That means it will also
perform better than EF6 but is not optimized. In our next article, I will show you how to
perform the BULK UPDATE operations in an optimized manner.
Note: By looking at the three DELETE SQL Statements, please don’t think it makes three
database round trips. This is happening in Entity Framework, not in Entity Framework Core.
In EF Core, they have improved the performance at some level by sending all these three
statements in a single database round trip. The problem occurs when you are going to
delete thousands of records.
While the performance is better in EF Core than the old EF6, the solution becomes very
inefficient when inserting thousands of entities. The fastest way of inserting multiple data is
by using the Entity Framework Extensions third-party library. Depending on the provider,
performance can be increased by up to 50x faster and more.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem TV
First, open the NuGet Package Manager for Solution and
search Z.EntityFramework.Extensions.EFCore package. Select
Z.EntityFramework.Extensions.EFCore, select the Project, choose the latest version, and
finally click the Install button, as shown in the image below.
You can also install it using the NuGet Package Manager Console as follows:
Install-Package Z.EntityFramework.Extensions.EFCore
897
Once you install the Z.EntityFramework.Extensions.EFCore package, you can verify the
same inside the Packages folder as shown in the below image.
}
898
using Microsoft.Extensions.Logging;
namespace EFCoreCodeFirstDemo.Entities
//optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
899
With this, our Database with Students database table is created, as shown in the below
image:
namespace EFCoreCodeFirstDemo
900
try
};
context.BulkInsert(newStudents);
GetStudents("CSE");
Console.ReadKey();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Performance Comparison Between SaveChanges and BulkInsert in EF
Core:
Let’s see the performance benchmark between the Entity Framework Core SaveChanges
method and the BulkInsert Extension Method with an example. We will Insert 1000 students
using both approaches (i.e., AddRange with SaveChanges Method and BulkInsert
Extension Method). We will measure the time taken to complete the task by both
approaches.
For a better understanding, please modify the Program class as follows. In the below code,
please don’t consider the FirstTimeExecution method for performance testing, as we know it
will take longer to execute something for the first time.
902
using EFCoreCodeFirstDemo.Entities;
using System.Diagnostics;
namespace EFCoreCodeFirstDemo
try
//This warmup
FirstTimeExecution();
context1.Students.AddRange(studentList);
SaveChangesStopwatch.Start();
context1.SaveChanges();
SaveChangesStopwatch.Stop();
903
BulkInsertStopwatch.Start();
BulkInsertStopwatch.Stop();
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
context.Students.AddRange(stduentsList);
//Call the SaveChanges Method to INSERT the data into the database
context.SaveChanges();
context.BulkDelete(stduentsList);
context.BulkInsert(stduentsList);
context.BulkDelete(stduentsList);
{
905
return listOfStudents;
}
Run the above code, and you will get the following output. As you can see, the
SaveChanges method took 85 MS to insert 1000 entities into the database, while the
BulkInsert Extension method took only 38 MS to insert 1000 entities into the database. So,
you can imagine how dangerous Entity Framework Core AddRange and SaveChanges
methods are when performance is considered. Many factors that affect the benchmark time
need to be considered, such as index, column type, latency, throttling, etc.
namespace EFCoreCodeFirstDemo
try
{
906
BulkUpdate("CSE");
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
// Call the BulkUpdate Extension Method to perform the Bulk Update Operation
context.BulkUpdate(studentsList);
}
907
}
Performance Comparison Between SaveChanges and BulkUpdate
Extension Method:
Before Proceeding further, please truncate the Student table using the following syntax:
truncate table Students
With an Example, let’s see the performance benchmark between the SaveChanges and
BulkUpdate Extension Method in Entity Framework Core. In the example below, first, we
insert 1000 students into the student table, and then we will update the first and last names
of all the students using both approaches. Here, we will also print the time taken to
complete the task by both approaches.
using EFCoreCodeFirstDemo.Entities;
using System.Diagnostics;
namespace EFCoreCodeFirstDemo
try
AddStudents(1000);
{
908
std.FirstName += "_UpdateBySaveChanges";
std.LastName += "_UpdateBySaveChanges";
SaveChangesStopwatch.Start();
context.SaveChanges();
SaveChangesStopwatch.Stop();
std.FirstName += "_UpdatedByBulkUpdate";
std.LastName += "_UpdatedByBulkUpdate";
}
909
BulkUpdateStopwatch.Start();
context.BulkUpdate(studentList);
BulkUpdateStopwatch.Stop();
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
return listOfStudents;
}
When you run the above code, you will get the following output. As you can see, the
SaveChanges method took 241 MS time to update 1000 records into the database, while
the BulkUpdate Extension Method took 137 MS to update the same 1000 records. So,
clearly, there is a huge performance difference between both approaches. So, in real-time
applications, you should avoid using the SaveChanges method if performance is the major
factor while performing BULK UPDATE.
namespace EFCoreCodeFirstDemo
{
911
try
BulkDelete();
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
//Fetch all the students from the database where Branch == "CSE"
// Call the BulkDelete Extension Method to perform the Bulk Delete Operation
context.BulkDelete(StudentsListToBeDeleted);
}
912
}
Performance Comparison Between SaveChanges and BulkDelete
Extension Method:
Let us see the performance benchmark between the SaveChanges and BulkDelete
Extension Method in Entity Framework Core with an Example. In the below example, first,
we are inserting 1000 students into the student table. Then, we will delete the same 1000
records using the SaveChanges method. Then again, we insert 1000 students into the
student table and then delete those 1000 records using the BulkDelete Extension Method.
Here, we also print the time taken to complete the task by both approaches.
using EFCoreCodeFirstDemo.Entities;
using System.Diagnostics;
namespace EFCoreCodeFirstDemo
try
AddStudents(1000);
//Then Call the Remove Range method which will mark the Entity State as Deleted
context.Students.RemoveRange(StudentListToBeDeleted);
SaveChangesStopwatch.Start();
context.SaveChanges();
SaveChangesStopwatch.Stop();
AddStudents(1000);
BulkDeleteStopwatch.Start();
context.BulkDelete(StudentListToBeDeleted);
BulkDeleteStopwatch.Stop();
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
return Studentlist;
}
When you run the above code, you will get the following output. As you can see, the
SaveChanges method took 183 MS to delete 1000 records from the database, while the
BulkDelete Extension Method took 127 MS to delete the same 1000 records. So, clearly,
there is a huge performance difference between both approaches. So, in real-time
applications, while performing BULK DELETE, you should avoid using the SaveChanges
method if performance is the major factor and go with the BulkDelete Extension method.
Points to Remember:
Entity Framework Extensions (EF Extensions), which ZZZ Projects develop, offers both free
and paid versions. The library provides various features for optimizing and simplifying bulk
operations in Entity Framework Core.
Free Version (Community Edition): EF Extensions offers a free Community
Edition that provides basic functionality for bulk operations. This free version
includes features like bulk insert, bulk update, bulk delete, and more. It can
be used in your projects without requiring a license.
Paid Versions (Professional and Enterprise Editions): EF Extensions also
offers paid versions, such as the Professional and Enterprise Editions. These
paid versions provide additional features, performance enhancements, and
support. Licensing fees may apply for the Professional and Enterprise
Editions, and the pricing may vary based on the specific edition and usage
requirements.
918
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem TV
Async Methods in EF Core:
Entity Framework Core (EF Core) provides asynchronous methods for most synchronous
methods. For example, you can use ToListAsync, FirstOrDefaultAsync,
SaveChangesAsync, etc.
// Synchronous query
// Asynchronous query
919
}
Cancellation:
You can pass a CancellationToken to asynchronous methods to allow for task cancellation.
This is useful for scenarios where you want to cancel a database operation that is taking too
long.
var cancellationToken = new CancellationToken();
.ToListAsync(cancellationToken);
Async Database Updates:
You can use SaveChangesAsync for asynchronous database updates. This is especially
important when making multiple changes to the database within a single transaction.
// Asynchronous database update
await context.SaveChangesAsync();
Avoid Mixing Synchronous and Asynchronous Code:
Avoid mixing synchronous and asynchronous code within a single method or operation, as it
can lead to deadlocks or inefficient code. Stick to asynchronous code when performing
database operations asynchronously.
920
}
Next, modify the Context class as follows:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
921
}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
With this, our Database with Students table is created, as shown in the below image:
922
Please execute the following SQL Script to insert demo data into the Students database
table.
INSERT INTO Students Values ('Pranaya', 'Rout', 'CSE');
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
923
//Here, we are blocking the Main thread to get the result from the GetStudent Method
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
return student;
}
Output:
As you can see in the above output, the Main method is started and completed by a thread
whose ID is 1. But please focus on the GetStudent method. It is started by the Thread,
whose ID is 1. But once it starts executing the FirstOrDefaultAsync asynchronous method,
the Thread whose Id is 1 is released, and that thread starts executing the other code in the
Main method. Once the query execution is completed, another thread whose ID is 5 will
come from the thread pool and start executing the rest of the code inside the GetStudent
method.
Asynchronous Save using Entity Framework Core
The Entity Framework Core provides an asynchronous version of
the SaveChanges() method called the SaveChangesAsync() to save entities into the
database asynchronously. For a better understanding, please look at the example below,
which shows saving a Student Entity to the database asynchronously. In this case, when we
call the SaveChangesAsync() method, the calling thread will be released and do another
925
task. Once the SaveChangesAsync() method execution is completed, another thread pool
thread will come and execute the rest of the code of the SaveStudent async method.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
FirstName = "James",
LastName = "Smith",
Branch = "ETC"
};
//await result;
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
context.Students.Add(newStudent);
await (context.SaveChangesAsync());
}
927
}
Output:
As you can see in the above output, the Main method is executed by one thread, and two
threads execute the SaveStudent method. This is possible because when the
SaveChangesAsync method is called, it releases the calling thread, and when the
SaveChangesAsync method completes its execution, another thread from the thread pool
will come and execute the rest of the code.
Asynchronous Delete in Entity Framework Core
In this case, first, you need to Remove the entity from the context object, which will mark the
entity state as Deleted. When we call the SaveChangesAsync() method, it will remove the
entity from the database asynchronously. For a better understanding, please have a look at
the following example. The following example code is self-explained, so please go through
the comment lines.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
{
928
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
{
929
if(DeleteStudent != null )
//Then remove the entity by calling the Remove Method which will mark the Entity State as
Deleted
context.Students.Remove(DeleteStudent);
await (context.SaveChangesAsync());
}
Output:
930
Note: If the student you are trying to delete has any foreign key relationship data, it will not
allow you to delete the student. In such cases, first, you need to delete foreign key table
data and then delete the student data. In most real-time applications, we never delete any
data from the database. In most cases, we use some kind of flag, such as IsActive, which
indicates whether the record is active or inactive.
Asynchronous Operation with Cancellation Token in EF Core:
Cancellation is an important feature of asynchronous programming, especially when
working with Entity Framework Core (EF Core), to ensure that long-running database
operations can be canceled when needed. Let us see an example to understand how to use
cancellation with EF Core asynchronous queries:
using EFCoreCodeFirstDemo.Entities;
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
.ToListAsync(cancellationToken);
Console.Read();
catch (OperationCanceledException)
Console.WriteLine($"Error: {ex.Message}"); ;
}
In this code:
932
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:11 / 03:1710 Sec
934
Disconnected entities in Entity Framework Core refer to entity instances not currently
tracked by any DbContext instance. This scenario is common in Web Applications, Web
APIs, and other distributed systems where an entity is retrieved in one request and then
modified in a subsequent request. Managing disconnected entities properly is important for
ensuring data consistency and integrity.
How to Use Disconnected Entities Entity Framework Core (EF Core)?
Working with disconnected entities is a common scenario when dealing with application
data. Here are the typical steps to work with disconnected entities in EF Core:
Create the Disconnected Entity: Instantiate an entity object representing
the data you want to work with. You can create these entities using their
parameterless or any other constructor you define.
Modify the Entity: Make changes to the entity’s properties as needed.
Attach to Context: When you are ready to save changes to the database,
you need to attach the disconnected entity to the EF Core context. You can
attach the entity to the context using the Attach, Entry, or Add method.
Save Changes: After attaching the entity to the context and specifying the
desired state (e.g., Added, Modified, Deleted, or Unchanged), you can call
SaveChanges to persist the changes to the database.
This process allows us to work with entity objects outside the context and then bring them
back into the context for saving changes. Remember that when attaching entities, you must
ensure the primary key values are set correctly to match the database records you intend to
update or insert.
How to Save a Disconnected Entity in Entity Framework?
In Entity Framework Core, saving data in the disconnected environment differs from saving
data in the connected environment. In a disconnected environment, the context object (an
instance of the DbContext class) is not aware of the state of the entities, whether the
entities are new or existing. Such entities are called Disconnected Entities. In this case, i.e.,
in the disconnected scenario, first of all, we need to attach the disconnected entities to the
context object with the appropriate Entity State to perform INSERT, UPDATE, or DELETE
operations in the database.
In the Entity Framework Core Disconnected Scenario, it is our key responsibility to figure
out whether the entity is a new one or an existing entity, and based on this, we need to set
the appropriate entity state. The important question that should come to your mind is how
we will determine whether the entity is new or existing. For this, we need to check the key
property value, i.e., the Primary key value of the entity.
If the key property value is greater than zero (in case of Integer column) or Not Null or
Empty (in case of String Column), then it is an existing Entity, and we can set the Entity
State as Modified. On the other hand, if the key value is zero (in case of Integer column) or
Null or Empty (in case of String Column), then it is a new entity, and we need to set the
Entity State as Added.
For a better understanding, please have a look at the below diagram. First, we need to
attach the Entity. While attaching the entity to the context object, we need to check the
primary key column corresponding property value and if the value is greater than 0 (let us
assume it is an integer property). We need to set the Entity State as Modified, and if the
value equals zero, then we need to set the Entity State as Added. Then, we need to call the
SaveChanges method, which will generate either the INSERT or UPDATE SQL Statement
based on the Entity State and execute the SQL Statement in the database.
935
}
Next, modify the context class as follows:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace EFCoreCodeFirstDemo.Entities
{
936
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
937
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
//Disconnected entity
FirstName = "Pranaya",
938
LastName = "Rout"
};
//Setting the Entity State Based on the StudentId Key Property Value
//If StudentId == 0, Means It is a New Entity, So, Set the Entity State as Added
//If StudentId > 0, Means It is an Existing Entity, So, Set the Entity State as Modified
if(student.StudentId > 0)
//Attaching the Entity to the Context Object with Entity State as Modified
context.Entry(student).State = EntityState.Modified;
else if(student.StudentId == 0)
//Attaching the Entity to the Context Object with Entity State as Added
context.Entry(student).State = EntityState.Added;
else
context.SaveChanges();
939
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
}
In our example, we have not set the StudentId value while creating the student object, so
the default value is 0. Hence, in this example, it will set the Entity State as Added, and when
we call the SaveChanges method, it will generate and execute the INSERT SQL Statement
in the database. So, when you run the above example code, you will get the following
output.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
//Disconnected entity
FirstName = "Pranaya",
LastName = "Rout"
};
//Setting the Entity State Based on the StudentId Key Property Value
//If StudentId == 0, Means It is a New Entity, So, Set the Entity State as Added
//If StudentId > 0, Means It is an Existing Entity, So, Set the Entity State as Modified
if(student.StudentId > 0)
//Attaching the Entity to the Context Object with Entity State as Modified
941
context.Entry(student).State = EntityState.Modified;
else if(student.StudentId == 0)
//Attaching the Entity to the Context Object with Entity State as Added
context.Entry(student).State = EntityState.Added;
else
context.SaveChanges();
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
}
942
Output:
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
//Disconnected entity
};
943
context.Entry(student).State = EntityState.Deleted;
context.SaveChanges();
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
}
The student instance contains only the StudentId key property in the above example.
Deleting an entity using the entity framework only requires the key
property. context.Entry(student).State = EntityState.Deleted attaches an entity to the
context object and sets its state to Deleted. When we call the SaveChanges method on the
context object, it generates and executes the DELETE SQL Statement in the database. So,
when you run the above example, you will get the following output.
944
}
Standard.cs
namespace EFCoreCodeFirstDemo.Entities
}
StudentAddress.cs
using System.ComponentModel.DataAnnotations;
namespace EFCoreCodeFirstDemo.Entities
[Key]
947
}
In this case, the Student Entity is the Main or Parent entity. Standard, StudentAddress, and
Courses are the Child Entities or related entities, and combined together, we can say it’s an
Entity Graph. Next, modify the context class as follows:
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
Now, if you verify the database, then you will see the following:
Let’s see how the above methods change the EntityState of each entity in a disconnected
entity graph in Entity Framework Core. Before Proceeding further, please Insert the
following record into the Standards database table:
INSERT INTO Standards Values ('STD1', 'STD1 Description');
Attach() Method in Entity Framework Core (EF Core):
In Entity Framework Core, the Attach method attaches an entity to the DbContext instance
so that the context object tracks it. Attaching an entity means EF Core is aware of its
existence and will start tracking changes to that entity. This is commonly used when
working with entities disconnected from the context, such as entities created outside or
fetched from another context object.
The DbContext.Attach() and DbSet.Attach() method attaches the specified disconnected
entity graph and starts tracking it. The Attach Method attaches an entire Entity Graph
(Parent and Child Entities) to the context object with the specified state to the parent entity.
Also, it sets different Entity State to related entities, i.e., to the child entities.
The Attach() method sets Added EntityState to the root entity (in this case, Student)
irrespective of whether it contains the Key value. A child entity containing the key value will
be marked as Unchanged. Otherwise, it will be marked as Added. The output of the above
example shows that the Student entity has an Added Entity State, the child entities with
non-empty key values have an Unchanged Entity State, and the ones with empty key
values have an Added state. The following table lists the behavior of the Attach() method
when setting a different EntityState to a disconnected entity graph.
To better understand how to use the Attach method to attach an Entity Graph to the context
object, please modify the Program class as follows. The following example code is self-
explained, so please go through the comment lines.
using EFCoreCodeFirstDemo.Entities;
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
FirstName = "Pranaya",
LastName = "Rout",
StandardId = 1,
//Make Sure the StandardId with Value 1 Exists in the Database, else you will get Exception
StandardId = 1,
StandardName = "STD1",
},
{
951
};
context.Attach(student).State = EntityState.Added;
context.SaveChanges();
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
}
952
}
Output:
In the above example, the student is an instance of the Student entity graph, which
includes references to StudentAddress and Standard
entities. context.Attach(student).State = EntityState.Added; attaches the student entity
graph to a context object and sets Added state.
Entry() Method in Entity Framework Core:
In Entity Framework Core (EF Core), the Entry method is used to get an EntityEntry object
representing an entity that the DbContext tracks. The EntityEntry object provides various
methods and properties that allow us to inspect and manipulate the state of the entity being
tracked, such as changing property values, setting the entity state, and more.
Consider the following example for a better understanding. In this case, only the root entity
will be added, updated, or modified in the database. It will not affect the Child Entities.
using EFCoreCodeFirstDemo.Entities;
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
FirstName = "Pranaya",
LastName = "Rout",
StandardId = 1,
StandardId = 1,
StandardName = "STD1",
},
};
context.Entry(student).State = EntityState.Added;
context.SaveChanges();
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output: Entity: Student, State: Added
In the above example, context.Entry(student).State = EntityState.Added; attaches an
entity to a context and applies the specified EntityState (in this case, Added) to the root
entity, irrespective of whether it contains a Key property value. It ignores all the child entities
in a graph and does not attach or set their EntityState. The following table lists different
behaviors of the DbContext.Entry() method.
call the SaveChanges method. The Add method is commonly used when creating and
inserting a new record into the database. In this case, providing explicit values for the
Identity column is restricted. You will get a Run Time Exception if you provide the Identity
Column values. For a better understanding, please have a look at the following example:
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
FirstName = "Pranaya",
LastName = "Rout",
StandardId = 1,
{
956
StandardName = "STD1",
},
};
context.Students.Add(student);
context.SaveChanges();
Console.Read();
}
957
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
The following diagram lists the possible EntityState of each entity in a graph using the
DbContext.Add or DbSet.Add methods.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
958
try
FirstName = "Pranaya",
LastName = "Rout",
StandardId = 1,
StandardId = 1,
StandardName = "STD1",
},
};
context.ChangeTracker.TrackGraph(student, e =>
if (e.Entry.IsKeySet)
//If Key is Available set the State as Unchanged or Modified as Per Your Requirement
e.Entry.State = EntityState.Unchanged;
else
e.Entry.State = EntityState.Added;
});
context.SaveChanges();
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
In the above example, the TrackGraph() method sets the state for each entity of the Student
entity graph. The first parameter is an entity graph, and the second parameter is a function
that sets the state of each entity. We used a lambda expression to set the Unchanged state
for entities that have valid key values and the Added state for entities that have empty key
values. The IsKeySet becomes true when an entity has a valid key property value. So, we
can use the ChangeTracker TrackGraph() method to set different States for each entity in a
graph.
Another Example to Understand TrackGraph() Method:
Suppose we want to set the Root Entity State as Modified when the Key Property Value is
available; if the key value is unavailable, we need to set the state as Added. But for the
Child Entity, we need to set the Entity State as UnChanged when the Key Value is
available, the Entity State should be Added. To achieve this, please modify the Main
Method of the Program class as follows:
using EFCoreCodeFirstDemo.Entities;
961
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
FirstName = "Hina",
LastName = "Sharma",
StandardId = 1,
StandardId = 1,
StandardName = "STD1",
},
};
if (std.StudentId > 0)
nodeEntry.Entry.State = EntityState.Modified;
else
nodeEntry.Entry.State = EntityState.Added;
}
963
else
if (nodeEntry.Entry.IsKeySet)
//If Key is Available set the State as Unchanged as Per Your Requirement
nodeEntry.Entry.State = EntityState.Unchanged;
else
// If Key is not Available set the State as Added as Per Your Requirement
nodeEntry.Entry.State = EntityState.Added;
});
context.SaveChanges();
Console.Read();
964
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
We create a graph of entities starting with the Student entity as the root, containing related
Standard and StudentAddress entities. We use the ChangeTracker TrackGraph method to
track the entire entity graph. Inside the delegate provided to TrackGraph, we customize the
tracking behavior for each entity based on our requirements.
When to use Disconnected Entity Graph in Entity Framework Core?
Disconnected Entity Graphs in Entity Framework Core are used in scenarios where you
want to work with entity data outside of the context in which it was originally retrieved, and
later, you want to reconnect those entities to a new context to perform database operations
like updating, inserting, or deleting records.
This approach is common in applications where entities must be passed between different
layers, such as a user interface, business logic, and data access layers, or when entities
must be serialized and sent across a network. Disconnected entity graphs are useful in the
following scenarios:
Web Applications: In web applications, entities are often retrieved from the
database within an HTTP request, but they must be passed to a different
layer for processing. Later, when the HTTP request is complete, the entities
may need to be updated or saved back to the database. Disconnected
entities allow you to work with the data across different parts of your
application.
Batch Processing: In batch processing scenarios, you might retrieve many
entities from the database, process them in a batch job, and then save the
changes. Disconnected entities allow you to efficiently work with these
entities, even if they are not continuously connected to a context.
Client-Server Applications: In client-server applications, entities may be
sent from a client to a server for processing and then returned to the client.
965
Disconnected entities allow you to serialize and deserialize the data without
maintaining a continuous database connection on the client side.
Caching: You can use disconnected entities to store a data snapshot in
memory for caching purposes. When the data is requested again, you can
reattach the entities to a new context to apply updates or insert new data.
//Output Parameter
ParameterName = "CustomerName",
SqlDbType = SqlDbType.VarChar,
Direction = ParameterDirection.Output,
Size = 50
};
}
Next, modify the Context class as follows:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace EFCoreCodeFirstDemo.Entities
//optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
968
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
Now, the database should be created with the Students database table as shown in the
below image:
969
@FirstName VARCHAR(100),
@LastName VARCHAR(100),
@Branch VARCHAR(100),
@Gender VARCHAR(100),
AS
BEGIN
END
970
@StudentId INT,
@FirstName VARCHAR(100),
@LastName VARCHAR(100),
@Branch VARCHAR(100),
@Gender VARCHAR(100)
AS
BEGIN
UPDATE Students
LastName = @LastName,
Branch = @Branch,
Gender = @Gender
END
Stored Procedure for Delete an Existing Student:
The following Stored Procedure is used to Delete an existing Student from the Student
Database Table based on the StudentId.
-- Delete Student Stored Procedure
@StudentId int
AS
971
BEGIN
END
Stored Procedure to Fetch All Students:
The following Stored Procedure fetches all Student data from the Student Database Table.
-- Get All Student Stored Procedure
AS
BEGIN
FROM Students;
END
Stored Procedure to Fetch Student by Student ID:
The following Stored Procedure is used to fetch a particular Student data from the Student
Database Table based on the StudentId.
-- Get Student by Student Id Stored Procedure
@StudentId INT
AS
BEGIN
FROM Students
END
How to Call Stored Procedure in Entity Framework Core?
To Call a Stored Procedure in Entity Framework Core, we need to use the following two
methods:
972
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using System.Data;
namespace EFCoreCodeFirstDemo
try
FirstName = "Pranaya",
973
LastName = "Rout",
Branch = "CSE",
Gender = "Male"
};
FirstName = "Hina",
LastName = "Sharma",
Branch = "CSE",
Gender = "Female"
};
//Call the AddStudent Method to add a new Student into the Database
//Call the GetStudentById Method to Retrieve a Single Student from the Database
Console.WriteLine("\nGetStudentById: 1");
//Call the GetAllStudents Method to Retrieve All Students from the Database
Console.WriteLine("\nGetAllStudents");
974
if (student != null)
student.FirstName = "Prateek";
student.LastName = "Sahoo";
UpdateStudent(student);
student = GetStudentById(student.StudentId);
Console.WriteLine("After Updation");
if (student != null)
DeleteStudent(student.StudentId);
Console.Read();
}
975
Console.WriteLine($"Error: {ex.Message}"); ;
//The following Method is going to add a New Student into the database
int result = 0;
try
Value = student.FirstName
};
Value = student.LastName
};
Value = student.Branch
976
};
Value = student.Gender
};
Direction = ParameterDirection.Output
};
if (NumberOfRowsAffected > 0)
return result;
}
977
//The following Method is going to return a Single Student from the database based on
Student Id
try
Value = StudentId
};
//Execute the non-composable part of your query first using FromSqlRaw or SqlQuery, and
//materialize the results by calling ToList, ToArray, or AsEnumerable to ensure the data is
retrieved from the database
if(result.Count > 0)
student = result.FirstOrDefault();
}
978
return student;
//The following Method is going to return all Students from the database
try
students = context.Students
.FromSqlRaw("EXEC spGetAllStudents")
.ToList();
return students;
979
//The following Method is going to Update an Existing Student into the database
try
Value = student.StudentId
};
Value = student.FirstName
};
Value = student.LastName
};
Value = student.Branch
};
Value = student.Gender
};
if (result > 0)
else
//The following Method is going to Delete an Existing Student from the database based on
Student Id
981
try
Value = StudentId
};
if(result > 0)
else
}
982
return student;
}
Output:
ExecuteSqlInterpolated:
Usage: This method allows you to execute a SQL query using interpolated
string syntax, which makes the code more readable and concise.
Parameters: It supports C# interpolated strings, where you can embed the
parameters directly into the SQL query string. EF Core will automatically
parameterize these values to prevent SQL injection.
Security: It’s generally safe from SQL injection attacks as EF Core converts
the interpolated values into parameters.
Choosing Between ExecuteSqlRaw and ExecuteSqlInterpolated:
Readability: ExecuteSqlInterpolated often leads to more readable code due
to using interpolated strings.
Safety: Both methods are safe from SQL injection when used properly.
However, ExecuteSqlInterpolated might be more prone to misuse if
developers aren’t careful about directly embedding user input into the query
string.
Preference: The choice can also depend on personal or team preference
and coding standards.
FromSqlRaw vs. FromSqlInterpolated in EF Core
In Entity Framework Core (EF Core), FromSqlRaw and FromSqlInterpolated are methods
used to execute raw SQL queries and map the results to entities or other types. Both serve
a similar purpose but differ in handling SQL queries and parameters.
FromSqlRaw:
Usage: This method is used to execute raw SQL queries. You must pass the
SQL query as a string and any parameters as separate arguments.
Parameters: Parameters are passed explicitly. The query string contains
placeholders (like {0}, {1}, etc.), and the parameters are provided as
additional arguments.
Security: FromSqlRaw is safe from SQL injection if parameters are used
correctly and not concatenated into the query string. Always use placeholders
for parameters instead of concatenating or interpolating user input directly
into the SQL string.
FromSqlInterpolated:
Usage: This method allows us to write SQL queries using interpolated string
syntax, making the code more readable.
Parameters: It supports C# interpolated strings, enabling you to embed
parameters directly within the SQL query string. EF Core automatically
parameterizes these values to prevent SQL injection.
Security: FromSqlInterpolated is designed to be safe from SQL injection
attacks as it converts interpolated values into parameters.
Choosing Between FromSqlRaw and FromSqlInterpolated:
Readability: FromSqlInterpolated uses interpolated strings, which can be
more readable and concise. FromSqlRaw requires explicit parameter
passing, which may be less concise but clearer in some cases.
Safety: Both methods are safe from SQL injection when used correctly.
However, FromSqlInterpolated might be easier to misuse if developers
embed user inputs directly into the SQL string.
984
Use Case: The choice between these methods often comes down to
personal preference, coding standards, and the specific context in which they
are used.
Example Using ExecuteSqlInterpolated and FromSqlInterpolated
Methods in EF Core
In our previous example, we used the ExecuteSqlRaw and FromSqlRaw methods to call the
Stored Procedures. Let us rewrite the same example using ExecuteSqlInterpolated and
FromSqlInterpolated Methods in EF Core to call the Stored Procedures. For a better
understanding, please look at the below example.
using EFCoreCodeFirstDemo.Entities;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using System.Data;
namespace EFCoreCodeFirstDemo
try
FirstName = "Pranaya",
LastName = "Rout",
Branch = "CSE",
Gender = "Male"
};
985
FirstName = "Hina",
LastName = "Sharma",
Branch = "CSE",
Gender = "Female"
};
//Call the AddStudent Method to add a new Student into the Database
//Call the GetStudentById Method to Retrieve a Single Student from the Database
Console.WriteLine("\nGetStudentById: 1");
//Call the GetAllStudents Method to Retrieve All Students from the Database
Console.WriteLine("\nGetAllStudents");
if (student != null)
student.FirstName = "Prateek";
student.LastName = "Sahoo";
UpdateStudent(student);
student = GetStudentById(student.StudentId);
Console.WriteLine("After Updation");
if (student != null)
DeleteStudent(student.StudentId);
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
}
987
//The following Method is going to add a New Student into the database
int result = 0;
try
ParameterName = "StudentId",
SqlDbType = SqlDbType.Int,
Direction = ParameterDirection.Output
};
if (NumberOfRowsAffected > 0)
{
988
result = (int)StudentIdOutParam.Value;
return result;
//The following Method is going to return a Single Student from the database based on
Student Id
try
.ToList();
if (result.Count > 0)
{
989
student = result.FirstOrDefault();
return student;
//The following Method is going to return all Students from the database
try
students = context.Students
.FromSqlInterpolated($"EXEC spGetAllStudents")
.ToList();
return students;
//The following Method is going to Update an Existing Student into the database
try
if (result > 0)
else
{
991
//The following Method is going to Delete an Existing Student from the database based on
Student Id
try
if (result > 0)
{
992
else
return student;
}
Considerations and Best Practices of Executing Stored Procedure Using
EF Core
Performance: Stored procedures can sometimes offer performance
advantages, especially for complex operations, as they are executed on the
server side.
Maintainability: While stored procedures encapsulate logic within the
database, which can be beneficial, they also create an additional layer of
code to maintain outside your application.
Portability: Relying on stored procedures can reduce the portability of your
application across different database systems, as stored procedures are often
specific to a particular SQL dialect.
Debugging: Debugging issues can be more challenging with stored
procedures since the logic is executed outside the EF Core and .NET
environments.
Compatibility: Ensure that your version of Entity Framework Core supports
the features you need for working with stored procedures, as EF Core’s
support for stored procedures has evolved over time.
993
in Entity Framework Core with Examples. At the end of this article, you will understand the
following pointers:
1. What do you mean by Entity Framework Core Inheritance?
2. Table Per Hierarchy (TPH) Inheritance in Entity Framework Core
3. Example to Understand Table Per Hierarchy (TPH) in EF Core
4. Table Per Type (TPT) Inheritance in Entity Framework core
5. Example to Understand Table Per Type (TPT) in EF Core
6. Table Per Concrete Type (TPC) Inheritance in Entity Framework Core
7. Example to Understand Table Per Concrete Type (TPC) Inheritance in EF
Core
8. TPH vs. TPT vs. TPC in EF Core
9. How to Choose the Right Strategy?
What do you mean by Entity Framework Core Inheritance?
Inheritance is one of the Object-Oriented Programming (OOP) Principles where a class
(known as a Derived, Child, or Subclass) can inherit properties and methods from another
class (known as a Base, Parent, or Child class). This allows code reusability functionality.
Entity Framework Core supports inheritance, allowing you to map an inheritance hierarchy
of .NET classes to a relational database. EF Core supports three common inheritance
mapping patterns:
1. Table Per Hierarchy (TPH) – 1 Table Per Hierarchy
2. Table Per Type (TPT) – 1 Table Per Type
3. Table Per Concrete Class (TPC) – 1 Table Per Each Concrete Class, not
for Abstract classes or Interfaces
Table Per Hierarchy (TPH) Inheritance in Entity Framework Core:
Table Per Hierarchy (TPH) is called Single-Table Inheritance. In this strategy, all the
classes in the inheritance hierarchy are stored in a single database table. There’s a
discriminator column that EF Core uses to differentiate between the types.
{
995
}
Derived Classes:
Next, we must create derived classes that inherit from the base class and represent the
specialized entities in your hierarchy. Each derived class can have additional properties
specific to that entity type. So, create the following Derived Entities:
namespace EFCoreCodeFirstDemo.Entities
}
TPH Configuration in EF Core:
In EF Core, you must configure TPH inheritance using Fluent API in your DbContext class.
Here, we need to specify that all entities in the hierarchy should be stored in a single
database table and use a discriminator column to differentiate between different entity
types. Modify the DbContext class as follows:
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
996
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<BaseEntity>()
.ToTable("Entities")
.HasDiscriminator<string>("entity_type")
.HasValue<DerivedEntityA>("EntityA")
.HasValue<DerivedEntityB>("EntityB");
}
Points to Remember:
Discriminator Column: EF Core will add a discriminator column to the
Entities database table named entity_type, which we configured using the
HasDiscriminator method. This column will store a value that indicates the
type of entity each row represents. The discriminator column, i.e., entity_type
column, will contain EntityA or EntityB, which we configured using the
HasValue Method.
997
Now, if you verify the database, then you will see the following:
Modify the Program class to check whether TPH Inheritance works as expected. The
following example code is self-explained, so please go through the comment lines for a
better understanding:
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
{
998
try
//Now, you can create and work with instances of the derived classes
context.BaseEntites.AddRange(derivedEntityA, derivedEntityB);
context.SaveChanges();
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
1000
If you verify the database table, you will see the following data.
Performance Considerations: The table can become wide with many nullable columns,
especially if the inheritance hierarchy is large with many properties. Consider the
performance implications when using TPH, especially when dealing with many derived
types with many unique properties.
So, the above example demonstrates how to implement Table-Per-Hierarchy (TPH)
inheritance in EF Core to store related entities in a single database table while preserving
the type information.
Table Per Type (TPT) Inheritance in Entity Framework core:
Table Per Type (TPT) is also known as Class Table Inheritance. In this strategy, each
class in the inheritance hierarchy has its own corresponding database table (database
tables for both base/parent and derived/child classes). Common properties are stored in a
shared table, while specific properties for each derived class are stored in their respective
tables. EF Core automatically creates relationships between the shared table and derived
tables.
Each class in the hierarchy is mapped to a separate table.
Properties that belong to the base class are stored in the base table, and
properties specific to derived classes are stored in their own tables.
Relationships between tables reflect the inheritance structure and are usually
implemented using primary and foreign keys.
The Base Table Primary Key will be created as the Primary Key and Foreign
Key in the Child Tables.
Example to Understand Table Per Type (TPT) in EF Core:
In TPT, each class in the inheritance hierarchy is mapped to its own table in the database.
This approach can be useful when you clearly distinguish between base and derived
entities and don’t want to mix derived entity properties in the base table. Here’s how you
can implement TPT inheritance in EF Core:
Define Your Base Class:
Start by defining a base class representing the common properties and fields shared by all
the classes in your inheritance hierarchy. This base class should be decorated with
the [Table] attribute to specify the table name explicitly and with [Key] to indicate the
primary key. This is the parent class in our model. It will be represented by its own table in
the database. So, create the following Base Entity.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
1001
namespace EFCoreCodeFirstDemo.Entities
[Table("BaseTable")]
[Key]
}
Define Your Derived Classes:
Each derived class will also be represented by its own table in the database. The table for a
derived class includes a primary key column (this will be the Primary Key Column of the
Base table) that is also a foreign key referencing the base class table. Create the derived
classes that inherit from the base class. Each derived class should have its own properties
and fields, and they can be decorated with [Table] attributes to specify their table names.
using System.ComponentModel.DataAnnotations.Schema;
namespace EFCoreCodeFirstDemo.Entities
[Table("DerivedTable1")]
[Table("DerivedTable2")]
{
1002
}
Configure the Model:
In your DbContext class, use the modelBuilder to configure the TPT inheritance strategy
using the HasBaseType method.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<DerivedEntity1>().HasBaseType<BaseEntity>();
modelBuilder.Entity<DerivedEntity2>().HasBaseType<BaseEntity>();
}
1003
Now, if you verify the database, then you will see the following:
Modify the Program class to check whether TPT Inheritance is working as expected. The
following example code is self-explained, so please go through the comment lines for a
better understanding:
using EFCoreCodeFirstDemo.Entities;
1004
namespace EFCoreCodeFirstDemo
try
//Now, you can create and work with instances of the derived classes
context.BaseEntites.AddRange(derivedEntityA, derivedEntityB);
context.SaveChanges();
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
}
1006
}
Output:
If you verify the database table, you will see the following data.
types in the hierarchy, or Table-Per-Type (TPT), where each type has its own table but only
stores data for the properties declared at that level, TPC has separate tables that are not
connected through foreign keys and each table repeats the columns for the inherited
properties. This strategy is useful when avoiding joins and efficiently retrieving data for a
specific derived type. Here’s how you can implement TPC inheritance in EF Core:
Define Your Base Class:
Start by defining a base class (abstract class or interface) representing the common
properties and fields shared by all the child classes in your inheritance hierarchy. In our
example, we are going to use the following abstract class.
using System.ComponentModel.DataAnnotations;
namespace EFCoreCodeFirstDemo.Entities
[Key]
}
Define Your Derived Classes:
Create the derived classes that inherit from the abstract base class. Each derived class
should have its own properties and fields, and they can be decorated with [Table] attributes
to specify their table names. But in our example, we will use Fluent API configuration to
provide the table names. So, create the following derived concrete classes.
namespace EFCoreCodeFirstDemo.Entities
}
Configure the Model:
In your DbContext class, use the modelBuilder to configure the TPC inheritance strategy. All
the concrete types are mapped to individual tables in the TPC mapping pattern. For TPC
mapping call the modelBuilder.Entity<BaseEntity>().UseTpcMappingStrategy() on each
root entity type, as shown below.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<BaseEntity>().UseTpcMappingStrategy();
modelBuilder.Entity<DerivedEntity1>().ToTable("DerivedTable1");
modelBuilder.Entity<DerivedEntity2>().ToTable("DerivedTable2");
}
1009
}
Migrate and Apply Database Changes:
After defining your entities and configuring the model, you can use EF Core’s migration
commands to create or update the database schema based on your model. So, open the
Package Manager Console and Execute the add-migration and update-database
commands as follows. You can give any name to your migration. Here, I am giving Mig3.
The name that you are giving it should not be given earlier.
Now, you will have separate database tables for each derived type (DerivedTable1 and
DerivedTable2). These tables will include the properties inherited from the base class
(BaseEntity) and the properties specific to each derived class. Now, if you verify the
database, then you will see the following:
1010
Modify the Program class to check whether TPC Inheritance is working as expected. The
following example code is self-explained, so please go through the comment lines for a
better understanding. In the example below, the OfType method filters the entities retrieved
from the DbSet based on their concrete type, either DerivedEntity1 or DerivedEntity2. The
filtered entities are then loaded into memory using the ToList method and displayed in the
console.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
{
1011
//Now, you can create and work with instances of the derived classes
context.BaseEntites.Add(derivedEntityA);
context.BaseEntites.Add(derivedEntityB);
context.SaveChanges();
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
Now, if you verify the database table, you will see the following data:
1013
{
1016
}
Next, modify the context class as follows:
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
Migrate and Apply Database Changes:
1017
After defining your entities and configuring the model, you can use EF Core’s migration
commands to create or update the database schema based on your model. So, open the
Package Manager Console and Execute the add-migration and update-
database commands as follows. You can give any name to your migration. Here, I am
giving Mig1. The name that you are giving it should not be given earlier.
The SaveChanges methods automatically begin and commit a transaction if one is not
already in progress. For a better understanding, please modify the Program class as
follows:
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
{
1018
try
FirstName = "Pranaya",
LastName = "Rout"
};
context.Students.Add(std1);
context.SaveChanges();
FirstName = "Tarun",
LastName = "Kumar"
};
context.Students.Add(std2);
context.SaveChanges();
Console.Read();
}
1019
Console.WriteLine($"Error: {ex.Message}"); ;
}
In this example, the SaveChanges method is used to persist the changes to the database,
and the transaction management ensures that all operations are committed or none if an
error occurs. It is important to handle exceptions properly to avoid unintended writes to the
database and ensure that transactions are always properly completed or rolled back.
Manual Transactions in Entity Framework Core using
Database.BeginTransaction()
If you need more control over the transaction, for instance, if you want to include several
SaveChanges() calls in one transaction, you can start a transaction manually
using Database.BeginTransaction() or Database.BeginTransactionAsync(). That means
you can begin a transaction manually using the BeginTransaction method on the database
connection or the Database property of your context object. If the transaction is completed
successfully, call the Commit method. If any exception occurs, invoke the Rollback method
using the transaction object. The following is the syntax:
1020
In Entity Framework Core (EF Core), you can manually control transactions using the
Database.BeginTransaction() or Database.BeginTransactionAsync() methods on your
DbContext. This is useful when you want to execute multiple operations as a single unit of
work, ensuring that all operations succeed or fail. You can roll back all the changes if any
part of the transaction fails. Please look at the following example for a better understanding:
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
try
FirstName = "Pranaya",
LastName = "Rout"
1021
};
context.Students.Add(std1);
// SaveChanges() but do not commit yet, this will persist changes but hold the commit until
context.SaveChanges();
FirstName = "Tarun",
LastName = "Kumar"
};
context.Students.Add(std2);
// SaveChanges() but do not commit yet, this will persist changes but hold the commit until
context.SaveChanges();
transaction.Commit();
transaction.Rollback();
Console.WriteLine(ex.Message);
1022
throw;
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
}
In the above code:
BeginTransaction() is called on the Database property of the context to start a
new transaction.
Database operations are then performed normally, such as adding an entity
and saving changes.
Commit() is called on the transaction object to commit all the operations as an
atomic unit if all operations succeed without throwing exceptions.
If there is an exception, Rollback() is called to undo all the operations that
were part of this transaction.
It is important to wrap the transaction code within a try-catch block to ensure
exceptions are handled, and resources are cleaned up properly.
Asynchronous Transactions in Entity Framework Core
In EF Core 2.1 and later, you can begin a transaction explicitly using the
BeginTransactionAsync method for asynchronous operations. That means you can also
manage transactions asynchronously, which is particularly useful in web applications where
you don’t want to block threads. The syntax is given below:
1023
It’s also a good practice to use async and await for database operations to avoid blocking
calls, especially in a web application environment. For a better understanding, please have
a look at the following example.
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
{
1024
try
FirstName = "Pranaya",
LastName = "Rout"
};
context.Students.Add(std1);
// SaveChangesAsync() but do not commit yet, this will persist changes but hold the commit
until
await context.SaveChangesAsync();
FirstName = "Tarun",
LastName = "Kumar"
};
context.Students.Add(std2);
// SaveChangesAsync() but do not commit yet, this will persist changes but hold the commit
until
await context.SaveChangesAsync();
await transaction.CommitAsync();
await transaction.RollbackAsync();
Console.WriteLine(ex.Message);
throw;
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Distributed Transaction using TransactionScope in EF Core:
In Entity Framework Core, the TransactionScope class can manage transaction boundaries
across multiple database contexts or different types of databases. It’s a .NET Framework
1026
feature that allows you to run a code block within a transaction without interacting directly
with the transaction itself. The syntax to use Transaction Scope in EF Core is given below:
When working with TransactionScope, it is important to note that it relies on the open
database connection within the scope. Transactions are automatically promoted to a
distributed transaction if necessary (for example, when the operations span multiple
databases). For a better understanding, please have a look at the following example:
using EFCoreCodeFirstDemo.Entities;
using System.Transactions;
namespace EFCoreCodeFirstDemo
try
{
1027
IsolationLevel = IsolationLevel.ReadCommitted,
};
try
FirstName = "Pranaya",
LastName = "Rout"
};
context1.Students.Add(std1);
context1.SaveChanges();
FirstName = "Rakesh",
LastName = "Kumar"
};
context2.Students.Add(std2);
context2.SaveChanges();
scope.Complete();
Console.WriteLine(ex.Message);
Console.WriteLine(ex.Message);
}
1029
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
}
In this Example:
The TransactionScope is created with a specified isolation level and timeout
period.
The TransactionScope is created using a “using” block, ensuring that it is
disposed of properly, committing or rolling back the transaction depending on
whether the scope.Complete() is called.
Within the transaction scope, multiple database operations are performed
across different contexts.
Database operations are performed within different contexts but are covered
by the same transaction scope.
If everything executes successfully, call scope.Complete() commits all the
operations.
scope.Complete() indicates that all operations within the scope are completed
successfully. If this method is not called, the transaction will be rolled back
when the scope is disposed.
If an exception is thrown, or if scope.Complete() is not called, the transaction
will be rolled back, and none of the operations will be persisted.
Using Existing Transaction in EF Core:
You can attach an existing transaction to the DbContext using
DbContext.Database.UseTransaction(). The syntax is given below:
1030
In Entity Framework Core (EF Core), if you want to use an existing database transaction
across multiple operations or contexts, you can pass the existing DbTransaction to another
DbContext instance. This allows you to share the same transaction across multiple
contexts, ensuring that all operations commit or rollback together. This can be helpful in
scenarios where a transaction is started using the underlying database connection, and you
want EF Core to enlist in that transaction.
First, let’s assume you have a method that starts a transaction using the database
connection:
public static DbTransaction BeginDatabaseTransaction(EFCoreDbContext context)
if (connection.State != ConnectionState.Open)
connection.Open();
return connection.BeginTransaction();
}
Now, you can use this DbTransaction in your operations with EF Core:
public static void UseExistingTransaction(EFCoreDbContext context, DbTransaction
transaction)
context.Database.UseTransaction(transaction);
try
FirstName = "Rakesh",
LastName = "Kumar"
};
context.Students.Add(std1);
context.SaveChanges();
// You can perform more operations here, and they will all be part of the same transaction
// ...
// Commit or rollback is controlled outside this method since the transaction was passed in
// Handle exception
// ...
Console.WriteLine($"Error: {ex.Message}");
// Note that the responsibility for rolling back the transaction, if needed, lies outside this
method
finally
1032
// Detach the context from the transaction after the work is done
context.Database.UseTransaction(null);
}
And here is how you can call UseExistingTransaction:
using (var context = new EFCoreDbContext())
try
FirstName = "Pranaya",
LastName = "Rout"
};
context.Students.Add(std1);
UseExistingTransaction(context, transaction);
transaction.Commit();
Console.WriteLine("Entities Added");
}
1033
transaction.Rollback();
throw;
}
The complete example code is given below:
using EFCoreCodeFirstDemo.Entities;
using Microsoft.EntityFrameworkCore;
using System.Data.Common;
using System.Data;
namespace EFCoreCodeFirstDemo
if (connection.State != ConnectionState.Open)
connection.Open();
}
1034
return connection.BeginTransaction();
context.Database.UseTransaction(transaction);
try
FirstName = "Rakesh",
LastName = "Kumar"
};
context.Students.Add(std1);
context.SaveChanges();
// You can perform more operations here, and they will all be part of the same transaction
// ...
// Commit or rollback is controlled outside this method since the transaction was passed in
// Handle exception
1035
// ...
Console.WriteLine($"Error: {ex.Message}");
// Note that the responsibility for rolling back the transaction, if needed, lies outside this
method
finally
// Detach the context from the transaction after the work is done
context.Database.UseTransaction(null);
try
try
{
1036
FirstName = "Pranaya",
LastName = "Rout"
};
context.Students.Add(std1);
UseExistingTransaction(context, transaction);
transaction.Commit();
Console.WriteLine("Entities Added");
transaction.Rollback();
throw;
Console.Read();
Console.WriteLine($"Error: {ex.Message}");
}
1037
}
In the above Example:
You begin by starting a transaction with the database directly.
You pass this transaction to the UseExistingTransaction method, which tells
EF Core to use this transaction for all operations within the current context.
You perform your database operations within the try block.
After the operations, you either commit or roll back the transaction. Note that
this decision is taken outside of the UseExistingTransaction method.
It is important to handle exceptions and ensure the transaction is rolled back
in case of an error.
Finally, make sure to detach the context from the transaction once you’re
done by passing null to UseTransaction.
Choosing The Right Transaction in Entity Framework Core:
It’s important to choose the right approach based on the scope and requirements of your
transaction. The implicit transaction provided by SaveChanges() might be sufficient for
small, single-operation transactions, whereas more complex scenarios might require
BeginTransaction() or even TransactionScope. Always ensure that any manual transactions
are properly disposed of to avoid resource leaks, and remember that transactions should be
as short as possible to avoid locking resources for longer than necessary.
Scalability and Performance: Nested or distributed transactions can impact
performance. TransactionScope may escalate to a distributed transaction if it
spans multiple connections, which is heavier on resources.
Ease of Use: SaveChanges() within a DbContext is the simplest and most
recommended for basic operations.
Flexibility: BeginTransaction() gives you more control and is recommended
when you need to span a transaction across multiple operations within a
single context or multiple instances of DbContext.
Cross-Resource Transactions: TransactionScope is suitable for
transactions spanning multiple databases or resources like file systems.
Compatibility: Not all database providers support TransactionScope or
distributed transactions.
Error Handling: Make sure to handle exceptions properly to avoid partial
commits and ensure that transactions are rolled back in the event of an error.
Transaction Challenges in Entity Framework Core:
Avoid Long-Running Transactions: Keeping transactions open for long
periods can lock resources and impact the performance of your application
and database.
Handling Exceptions: Always ensure you handle exceptions properly to
avoid uncommitted transactions that can cause locks.
Avoiding Ambient Transactions If Not Needed: TransactionScope can be
convenient but can lead to escalated transactions if multiple resource
managers get involved.
Concurrency Considerations: Be aware of the database’s concurrency
model (optimistic/pessimistic) and how transactions are handled concurrently.
1038
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem TV
using System.ComponentModel.DataAnnotations.Schema;
namespace EFCoreCodeFirstDemo.Entities
[Table("CountryMaster")]
}
1039
[Table("StateMaster")]
[Table("CityMaster")]
}
Add Seed Data to Your DbContext:
Inside your DbContext class, override the OnModelCreating method to specify the seed
data for your entities. You can specify seed data in the OnModelCreating method of your
DbContext class using the HasData method of the entity configuration. So, modify the
Context class as follows:
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<Country>().HasData(
);
modelBuilder.Entity<State>().HasData(
);
modelBuilder.Entity<City>().HasData(
);
}
1041
}
Create Migrations and Update the Database:
Once you have defined your seed data, you need to add a new migration that includes
these changes. Then, apply the migration to your database to insert the seed data. This can
be done by running the Update-Database command in the Package Manager Console, as
shown below:
Remember, seed data is inserted into the database only if the table is empty. If you add
new seed data and want to reapply all seeds, you must ensure the tables are in the
expected state or handle it programmatically.
Verify the Seed Data:
To verify the Seed Data, modify the Program class as follows:
using EFCoreCodeFirstDemo.Entities;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using System.Data;
namespace EFCoreCodeFirstDemo
{
1042
try
Console.WriteLine("Country Master:");
Console.WriteLine("State Master:");
Console.WriteLine("City Master:");
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Now run the application, and you should get the following output.
Data Size: While there’s no strict limit on the amount of seed data, very large
data sets might be better served by a custom data import process outside of
EF Core migrations for performance reasons.
Data Changes: If you need to change seed data after it has been added to
the database, you should create a new migration. Changing the HasData
method and applying a new migration will adjust the seeded data accordingly.
Custom Initialization for Seed Data in EF Core
In Entity Framework Core, seeding data typically involves specifying static data directly
within the OnModelCreating method of your DbContext using the HasData method.
However, in some scenarios, you may need more custom and dynamic data initialization,
which HasData does not directly support. For such cases, you can implement custom logic
to seed data in your application startup routine or as part of the EF Core migration process.
Custom initialization in Entity Framework Core allows you more control over the seeding
process, particularly when you have complex logic or large amounts of data to insert. This
typically involves executing code to explicitly insert data into the database rather than using
the HasData method, part of the model configuration.
Define the Seed Data Logic:
Create a method that contains the logic for seeding your database. This could be inside a
static class or part of your DbContext. So, create a static class named DbInitializer (you can
give any name) and then copy and paste the following code.
namespace EFCoreCodeFirstDemo.Entities
context.Database.EnsureCreated();
// Check if there are any Country or State or City already in the database
return;
else
1045
//Here, you need to Implement the Custom Logic to Seed the Master data
countries.Add(IND);
countries.Add(AUS);
context.CountryMaster.AddRange(countries);
states.Add(Odisha);
states.Add(Delhi);
context.StateMaster.AddRange(states);
cities.Add(BBSR);
cities.Add(CTC);
context.CityMaster.AddRange(cities);
context.SaveChanges();
}
1046
}
Modify the Context Class:
Next, modify the context class as follows:
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
}
Before proceeding further, let us delete the database and Migration folder and create the
Migration file again to update the database, as shown in the image below.
1047
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using System.Data;
namespace EFCoreCodeFirstDemo
try
{
1048
DbInitializer.Initialize(context);
Console.WriteLine("Country Master:");
Console.WriteLine("State Master:");
Console.WriteLine("City Master:");
Console.Read();
}
1049
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
Handling Changes: If you update your seed method, you must handle those changes
manually, as they are not part of the EF Core migrations. You might need to write additional
logic to check what data is already present and what needs to be updated, inserted, or
deleted.
Considerations for Production: When deploying to production, you should be careful
about when and how you run your seed method. You wouldn’t want to overwrite or duplicate
data accidentally. Ensure that your seeding logic checks for existing data and only inserts
where appropriate.
Using Transactions: For larger seeding operations or when data integrity is critical, you
might use database transactions to ensure all seed data is committed or rolled back as a
single unit.
namespace EFCoreCodeFirstDemo.Entities
{
1050
try
context.Database.EnsureCreated();
// Check if there are any Country or State or City already in the database
return;
else
//Here, you need to Implement the Custom Logic to Seed the Master data
countries.Add(IND);
countries.Add(AUS);
context.CountryMaster.AddRange(countries);
states.Add(Odisha);
states.Add(Delhi);
context.StateMaster.AddRange(states);
cities.Add(BBSR);
cities.Add(CTC);
context.CityMaster.AddRange(cities);
context.SaveChanges();
transaction.Commit();
transaction.Rollback();
}
Considerations for Custom Seeding in EF Core:
1052
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:18 / 03:1710 Sec
As you can see in the above diagram, the Shadow Properties in EF Core are not part of our
entity classes. So, we cannot access these Shadow Properties as we access the other
properties of an entity. Shadow Properties can only be configured for an entity type while
building an Entity Data Model and mapped to a database column.
Example to Understand Shadow Properties in Entity Framework Core:
Shadow properties can be used for various purposes, such as:
Auditing: Storing information like creation and modification timestamps.
Concurrency Control: Managing concurrency with fields like timestamps or
version numbers.
Tracking Additional information: Storing metadata or flags related to
entities.
Consider a real-time example of using shadow properties in an ASP.NET Core application
with Entity Framework Core. Imagine you have a blog post entity that wants to track when
each post was created and last updated, but you don’t want these audit fields to be part of
your domain model. Let us see how we can implement this using shadow Properties.
Define the BlogPost Entity Without Audit Properties:
Create a class file named BlogPost.cs and copy and paste the following code. Here, we
have not added the CreatedAt or LastUpdatedAt properties.
namespace EFCoreCodeFirstDemo.Entities
}
Configure Shadow Properties in the OnModelCreating Method of your
DbContext:
Shadow properties are typically defined in the OnModelCreating method of your DbContext
class using the Fluent API. So, to define a Shadow Property in EF Core, we can use the
ModelBuilder API in our DbContext class’s OnModelCreating method. You can use the
Property method to configure a shadow property. In our example, we have created two
Shadow Properties named CreatedAt and LastUpdatedAt for the BlogPost class. So,
modify the context class as follows:
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<BlogPost>().Property<DateTime>("CreatedAt");
modelBuilder.Entity<BlogPost>().Property<DateTime>("LastUpdatedAt");
1055
}
Set the Shadow Properties Values when Saving Changes:
We can do the same by overriding our context class’s SaveChanges() method, or while
defining the Shadow Property, we can provide the default value for the Shadow Property.
Let us first see how we can override the SaveChanges() method to set the Shadow
Properties Values in EF Core.
For a better understanding, please have a look at the following image. As you can see, we
have overridden the SaveChanges() method. Within the SaveChanges() method, we loop
through all the Entities whose Entity Type is BlogPost and check whether the entity state is
added or modified. If the Entity is in Added State, we set both CreatedAt and LastUpdatedAt
Shadow Properties values to the current date. If the Entity is in the Modified State, we set
the LastUpdatedAt Shadow Properties value to the current date.
using System.Diagnostics;
using System.Threading.Channels;
1056
using System;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<BlogPost>().Property<DateTime>("CreatedAt");
modelBuilder.Entity<BlogPost>().Property<DateTime>("LastUpdatedAt");
if (entry.State == EntityState.Added)
{
1057
entry.Property("CreatedAt").CurrentValue = timestamp;
entry.Property("LastUpdatedAt").CurrentValue = timestamp;
entry.Property("LastUpdatedAt").CurrentValue = timestamp;
return base.SaveChanges();
}
Generate Migration and Apply Database Changes:
Open the Package Manager Console and Execute the add-migration and update-
database commands as follows. You can give any name to your migration. Here, I am
giving Mig1. The name that you are giving it should not be given earlier.
Now, verify the database, and you should see the Shadow Properties in the table as shown
in the below image:
1058
namespace EFCoreCodeFirstDemo
try
};
context.BlogPosts.Add(blogPost);
context.SaveChanges();
// Entity Framework Core will set the "CreatedAt" and "LastUpdatedAt" Shadow Properties
Value
context.SaveChanges();
// Entity Framework Core will update the "LastUpdatedAt" shadow property value.
Console.WriteLine("BlogPost Updated..");
Console.Read();
Console.WriteLine($"Error: {ex.Message}");
}
Output:
Now, verify the BlogPosts database table, and you should see the following with the
Shadow Properties:
1060
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
Title = bp.Title,
Content = bp.Content,
})
.ToList();
Console.WriteLine($"\tContent: {blogPost.Content}");
Console.Read();
Console.WriteLine($"Error: {ex.Message}");
}
Output:
Note: In our example, the BlogPost class doesn’t contain CreatedAt or LastUpdatedAt
properties, but they are part of the BlogPosts table in the database. When we add or update
a BlogPost, the SaveChanges method sets these shadow properties automatically. When
querying, we can still retrieve these values even though they are not part of the BlogPost
class using the EF.Property method.
How Do We Check the Shadow Properties of an Entity in EF Core?
In Entity Framework Core, we can inspect an entity’s shadow properties at runtime using
the DbContext and its associated ChangeTracker. Here’s how you can check for shadow
properties:
Retrieve the Entry for the Entity: Use the DbContext.Entry method to get
the EntityEntry for the entity you’re interested in.
1062
Use the Metadata Property: The EntityEntry has a Metadata property that
provides access to metadata about the entity, including information on
shadow properties.
Iterate Over Properties: You can then iterate over the
Metadata.GetProperties() collection and check for properties that are shadow
properties using the IsShadowProperty flag.
Here’s an example of how to write a method that prints out the names of all shadow
properties for a given entity:
using EFCoreCodeFirstDemo.Entities;
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
};
context.BlogPosts.Add(blogPost);
context.SaveChanges();
PrintShadowProperties(context, blogPost);
1063
Console.Read();
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine(propName);
}
Output:
1064
}
Configure the shadow foreign key property in the OnModelCreating method:
using Microsoft.EntityFrameworkCore;
1065
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<Post>()
}
Setting the shadow foreign key value when adding a new Post:
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
1066
try
context.Blogs.Add(blog);
context.SaveChanges();
var post = new Post { Title = "Hello World", Content = "Welcome to my Blog!" };
context.Posts.Add(post);
context.Entry(post).Property("BlogId").CurrentValue = blog.BlogId;
context.SaveChanges();
Console.Read();
Console.WriteLine($"Error: {ex.Message}");
}
1067
}
Using the shadow foreign key property in a query:
using EFCoreCodeFirstDemo.Entities;
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
Post = p,
})
.ToList();
Console.Read();
{
1068
Console.WriteLine($"Error: {ex.Message}");
}
In this example, the Post class does not contain a BlogId property but is part of the Posts
table in the database as a shadow property. This allows us to have a clean domain model
without the foreign key property while still being able to set and retrieve the foreign key
value using EF Core functionalities.
Shadow Property with Default Value in EF Core
In Entity Framework Core, we can configure shadow properties with a default value using
the Fluent API. This can be useful for columns that should always start with a specific value
when a new record is created, such as a default state, creation date, or a flag. Here’s an
example of how to set up a shadow property with a default value:
Define your entity without the property you want as a shadow property:
namespace EFCoreCodeFirstDemo.Entities
// IsActive is not included here, it will be a shadow property with a default value
}
Configure the shadow property and its default value in the OnModelCreating method:
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
modelBuilder.Entity<User>().Property<bool>("IsActive").HasDefaultValue(true);
}
In this example, we are creating a User entity and a corresponding IsActive shadow
property that isn’t defined in the User class but is expected to be in the database table. The
HasDefaultValue method sets the default value for the shadow property. In this case, when
a new User is added to the database if the IsActive value isn’t explicitly set, it will default to
true.
Adding a new User Without Setting the Shadow Property:
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
context.Users.Add(user);
context.SaveChanges();
Console.Read();
Console.WriteLine($"Error: {ex.Message}");
}
In the above code, when the SaveChanges method is called, EF Core will insert a new User
with the IsActive property set to true by default in the database.
Querying the Shadow Property Value:
using EFCoreCodeFirstDemo.Entities;
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
{
1071
try
User = u,
})
.ToList();
Console.Read();
Console.WriteLine($"Error: {ex.Message}");
}
This LINQ query includes the shadow property IsActive in the result set using
the EF.Property<T> method. This allows you to read the value of the shadow property
even though it’s not part of your entity class.
When to use Shadow Properties in Entity Framework Core?
Shadow properties in Entity Framework Core are useful in various scenarios:
1072
Auditing: Shadow properties are often used for audit data, like when an
entity was created or last modified. This keeps audit details out from the
domain model but still in the database.
Querying: You can query shadow properties using LINQ, but you must use
the EF.Property static method to refer to them in your queries.
Migrations: When you add or change shadow properties, you must create a
new migration to update the database schema.
Data Models and Mappings: Note that shadow properties are part of the EF
model, but they won’t be visible in your C# data models. This can sometimes
make it harder to understand the complete data model just by looking at the
classes.
Model Clarity: While useful, overuse of shadow properties can lead to a less
transparent model, where significant parts of the data model are not
immediately visible in the code.
Database Provider Compatibility: Ensure that any default values or
behaviors you set for shadow properties are compatible with your database
provider.
Modeling Database Concerns: Sometimes, there are columns in a database
table that have no direct relevance to the domain model’s behavior or business
logic but are necessary for database operations or constraints. These can be
handled as shadow properties.
Encapsulation: When you want to keep certain data private to the entity and
not expose it to the domain directly, shadow properties can be useful. They
allow the data to be stored and retrieved without defining it in the domain
model.
Soft Delete: A shadow property can hold this flag in scenarios where you do
not delete records but mark them as deleted with a flag.
useful for things like soft delete functionality, multi-tenancy, or any scenario where you have
a common filtering logic that should be applied globally.
Key Points of Global Query Filters in Entity Framework Core:
Definition: You can define Global Query Filters in the OnModelCreating
method of your DbContext class. It uses LINQ to specify the filter conditions.
This is done by using the HasQueryFilter method on an entity type builder.
Automatic Application: Once the Global Query Filter is defined, the filter is
automatically applied to every LINQ query that retrieves data of the specified
entity type. This includes direct queries and indirect data retrievals, such as
navigation properties.
Usage with LINQ Queries: When you use LINQ to query your entities, EF
Core automatically applies these filters, so you don’t need to specify the filter
conditions each time.
Disabling Filters: In certain scenarios, you might want to bypass these filters
(e.g., for an admin role). EF Core allows us to ignore filters using the
IgnoreQueryFilters method in your queries.
Parameterized Filters: EF Core also supports parameterized filters, allowing
you to pass parameters into your Global Query Filters. This is particularly
useful for scenarios like multi-tenancy, where the tenant ID might be
determined at runtime.
Performance: While Global Query Filters can improve performance by
reducing the need for repeated filtering logic in queries, they can also
potentially impact performance if not used properly, as they add additional
criteria to all queries against the filtered entity types.
Common Uses of Global Query Filter in EF Core:
Soft Delete: A common use case for Global Query Filters is implementing
soft delete functionality. Instead of physically deleting records from the
database, a flag (e.g., IsDeleted) is set. By using a filter
like builder.Entity<MyEntity>().HasQueryFilter(e => !e.IsDeleted);, you
can automatically exclude logically deleted entities from all queries of the
entity MyEntity.
Multi-Tenancy: In a multi-tenant application, a filter can ensure that each
query only retrieves data belonging to the tenant associated with the current
user. For instance, you might filter entities based on a tenant ID.
Security: Filtering data based on user roles or permissions.
Soft Delete Example in EF Core Using Global Query Filter
Let us see an example of implementing soft delete functionality using Global Query Filters
in Entity Framework Core (EF Core). The soft delete pattern marks entities as deleted
without removing them from the database.
Define the Entity with a Soft Delete Property:
First, you need an entity with a property to indicate whether it is deleted. This is typically a
boolean flag. So, create a class file named Product.cs and copy and paste the following
code. In our example, it is the IsDeleted property, which indicates whether a product is
deleted.
namespace EFCoreCodeFirstDemo.Entities
}
Configure the Global Query Filter in DbContext:
In your DbContext class, override the OnModelCreating method to define the global query
filter. This filter will automatically exclude entities marked as deleted in all queries. So,
modify the DbContext class as follows:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
base.OnModelCreating(modelBuilder);
}
Generate Migration and Apply Database Changes:
Open the Package Manager Console and Execute the add-migration and update-
database commands as follows. You can give any name to your migration. Here, I am
giving Mig1. The name that you are giving it should not be given earlier.
We need demo data to check whether the Global Query Filter works. So, please execute
the following insert statement into the Product database tables. Please note that we have a
few records whose IsDeleted column value is set to false.
INSERT INTO Products VALUES ('Product-1', 1); -- 1 Means True, i.e., Deleted
1076
INSERT INTO Products VALUES ('Product-2', 0); -- 0 Means False, i.e., Not Deleted
INSERT INTO Products VALUES ('Product-3', 1); -- 1 Means True, i.e., Deleted
INSERT INTO Products VALUES ('Product-4', 0); -- 0 Means False, i.e., Not Deleted
Querying Entities:
When you query Products, the global query filter is automatically applied, and ‘deleted’
product entities are excluded from the result set. For a better understanding, please modify
the Main method of the Program class as follows:
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
try
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
As you can see in the above output, the SQL Select Query applies with the Global Query
Filter in the Where Clause to fetch only those not deleted records.
Bypassing the Global Query Filter in Entity Framework Core:
In cases where you need to access all entities, including those marked as deleted (like for
an admin view), you can bypass the global query filter in EF Core using
the IgnoreQueryFilters() method. For a better understanding, modify the Program class as
follows:
using EFCoreCodeFirstDemo.Entities;
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
1078
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
// Other properties...
int GetCurrentTenantId();
// This might involve parsing a subdomain, reading a cookie, or checking user credentials:
1080
return 1;
}
Modifying Your Entities to Include TenantId
Each entity that should be tenant-specific needs a TenantId field. Please modify the Product
class as follows:
namespace EFCoreCodeFirstDemo.Entities
}
Setting Up the DbContext:
Inject the ITenantService into your DbContext and use it in the OnModelCreating method to
apply the Global Query Filter. In your DbContext class, override the OnModelCreating
method to define the global query filter. This filter will automatically include entities based on
the TenantId.
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Product>()
}
Next, generate migration and update the database. Once you update the database, execute
the following SQL Script.
INSERT INTO Products (Name, TenantId) VALUES ('Product-1', 1);
Querying Entities
When querying Products, the global filter is automatically applied. Modify the Program class
as follows and then verify the output:
using EFCoreCodeFirstDemo.Entities;
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
Console.Read();
{
1083
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
Considerations:
Security: It’s important to ensure the tenant ID cannot be tampered with,
especially in multi-user or public-facing applications.
Performance: Be careful of performance implications, especially if you have
many tenants or a large dataset.
Indexing: Consider indexing the TenantId field for performance optimization.
Data Security Example in EF Core Using Global Query Filter
Implementing data security in Entity Framework Core using Global Query Filters involves
creating filters based on security levels or user roles. This ensures that users can only
access data they are authorized to see. Let’s go through a detailed example where we
implement a security level-based data access control:
Define a Security Level Property in Your Entities
Add a SecurityLevel property to each entity that needs to be secured. This property
indicates the required clearance level to access the entity. So, modify the Product class as
follows to include the SecurityLevel property.
namespace EFCoreCodeFirstDemo.Entities
}
1084
namespace EFCoreCodeFirstDemo.Entities
optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
base.OnModelCreating(modelBuilder);
//You can inject this value based on the login user from sessions or cookies
int _userSecurityLevel = 2;
}
1085
}
Next, generate migration and update the database. Once you update the database, execute
the following SQL Script.
INSERT INTO Products (Name, SecurityLevel) VALUES ('Product-1', 1);
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
{
1086
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
Global Query Filters in Entity Framework Core are powerful for applying automatic filtering
logic to all queries against a particular entity type. Here are scenarios when it’s particularly
useful to use them:
Soft Delete: In applications where entities are not permanently deleted but
marked as inactive or deleted (soft delete), Global Query Filters can
automatically exclude these records from all queries.
Multi-Tenancy: When building applications that serve multiple tenants (like
different companies or groups), Global Query Filters can ensure that each
tenant only accesses their own data by filtering based on tenant ID.
Row-Level Security: Global Query Filters can enforce these rules if your
application has complex security requirements where users have access to
only certain rows of data (based on their role or department).
Data Partitioning: For applications where data needs to be partitioned (like
based on region, department, etc.), Global Query Filters can automatically
segregate data accordingly in every query.
Audit or Historical Data: In scenarios where you want to keep historical or
audit data in the same table but normally query only the current data, a
Global Query Filter can exclude historical records from standard queries.
Common Conditions: If a common condition almost always applies to
queries of a particular entity (like only showing active users or products in
stock), a Global Query Filter can enforce this condition by default.
When Not to Use Global Query Filters in EF Core?
Performance Considerations: If your filter conditions are complex and could
lead to performance issues, applying filters at the query level might be better.
Global Impact: Remember that Global Query Filters impact every query
against the entity. If there are many scenarios where the filter needs to be
bypassed (using IgnoreQueryFilters), applying filters at the query level might
be better.
Simplicity: For simpler applications or when filters are not consistently
required across all queries, applying filters directly in queries might be more
straightforward and maintainable.
Core supports two primary ways of developing a data access layer: Database-
First and Code-First. Here, we’ll focus on the Database-First approach.
The Entity Framework Core (EF Core) Database First approach is a technique where we
start with an existing database and use EF Core to create the data access layer of our
application. This approach is useful when working with a pre-existing database or when a
database administrator or a separate team leads the database design.
Steps to Implement EF Core Database-First Approach:
Existing Database: You need an existing database to begin with. The
database can be designed and managed using any compatible database
management system like SQL Server, MySQL, Oracle, etc.
Install EF Core Tools: Ensure you have the EF Core CLI tools or the EF
Core Power Tools (for Visual Studio) installed. These tools are essential for
generating the code from the database.
Create a .NET Core Project: Start by creating a .NET Core project in which
you want to use EF Core. This could be any .NET Core project, like an
ASP.NET Core web application or a Console Application.
Install EF Core NuGet Packages: Install the necessary EF Core NuGet
packages required for EF Core. You would typically
need Microsoft.EntityFrameworkCore and provider-specific packages
(like Microsoft.EntityFrameworkCore.SqlServer for SQL Server).
Generate the Models: Use the EF Core CLI or Power Tools to scaffold the
DbContext and Entity Classes from your existing database. This is done using
the Scaffold-DbContext command. This command generates C# code that
represents the tables in your database as classes and properties.
Use the DbContext: Once the DbContext and entities are scaffolded, you
can use them in your application to perform CRUD operations on the
database.
Handling Database Changes: If the database schema changes, you need to
re-run the scaffolding command to update your models. Be aware that this
may overwrite any customizations you have made to the models.
Migrations: Unlike the Code First approach, in Database First, migrations
are usually handled directly in the database using SQL scripts or other
database management tools rather than through EF Core.
Testing and Deployment: Finally, test your application to ensure it works
correctly with the database, then deploy it.
Example to Understand EF Core Database First Approach:
Let us see an Example to Understand the EF Core Database First Approach with Multiple
Tables, Views, Stored Procedures, and Functions. Let us create a database in SQL Server
with the following:
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:12 / 03:1710 Sec
Database: EFCoreDB:
Table: Employees: This table should contain employee-related information.
Table: Departments: This table should store department details.
Views: EmployeeDetails: A view to aggregate and present detailed
employee information.
1089
USE EFCoreDB;
);
GO
-- It includes an ID as the primary key, personal details like name and email,
Email NVARCHAR(100),
Salary INT,
DepartmentID INT,
);
GO
-- Joins data from the Employees and Departments tables to EmployeeDetails View
SELECT
e.EmployeeID,
e.FirstName,
e.LastName,
e.Email,
e.Salary,
d.DepartmentName
FROM Employees e
ON e.DepartmentID = d.DepartmentID;
GO
@EmployeeID INT
AS
BEGIN
SELECT
e.EmployeeID,
1091
e.FirstName,
e.LastName,
e.Email,
e.Salary,
e.DepartmentID,
d.DepartmentName
FROM Employees e
ON e.DepartmentID = d.DepartmentID
END;
GO
-- This will calculate and return the Bonus of the Employee Based on the EmployeeID
@EmployeeID INT
RETURNS DECIMAL(12, 2)
AS
BEGIN
RETURN @Bonus;
END;
At this point, your database structure should be as shown in the image below:
Console Application. Once you create the Console Application, please add the following two
packages from NuGet, which are required for EF Core.
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
Creating and implementing DB Context
Now, we will see how to create the Context and Entity classes from our existing EFCoreDB
database in Entity Framework Core. Creating Context and Entity classes for an existing
database is called Database-First Approach. Entity Framework Core does not support
Visual Designer for DB Model and Wizard to create the entity and context classes similar to
Entity Framework 6. Here, we need to use the Scaffold-DbContext command.
This Scaffold-DbContext command creates entity and context classes based on the
schema of the existing database, and we need to do this using Package Manager Console
(PMC) tools.
Understanding Scaffold-DbContext Command in EF Core:
The Scaffold-DbContext command in Entity Framework Core (EF Core) is a powerful
feature used for reverse engineering. It essentially generates entity and context classes
based on an existing database, allowing developers to work with a database using EF
Core’s object-relational mapping capabilities.
Purpose: The primary purpose of Scaffold-DbContext is to create a set of classes that
represent the tables and relationships in an existing database. This allows developers to
interact with the database using strongly typed objects rather than writing raw SQL queries.
Usage: This command is used within the Package Manager Console in Visual Studio or via
the .NET Core CLI (Command Line Interface). It requires a few parameters to be specified:
Connection String: The database connection string tells EF Core how to
connect to the database.
Provider: Specifies the EF Core database provider to use. This is typically
the NuGet package corresponding to the database used (e.g.,
Microsoft.EntityFrameworkCore.SqlServer for SQL Server).
Options (Optional): Additional options include specifying specific tables to
scaffold, the directory where the classes should be created, the context’s
namespace, and whether to use data annotations or Fluent API for
configuration.
Result: After running the command, EF Core generates several classes in your project:
DbContext Class: This class derives from DbContext and includes
properties (DbSets) for each table.
Entity Classes: Each table in the database corresponds to an entity class
with properties that map to the columns in the table.
Customization: The scaffolded classes can be modified to suit the needs of your
application. However, it’s important to note that changes to these classes may be
overwritten if you re-run the command.
Updating Models: If the database schema changes, the EF Core models need to be
updated. This can be done by re-running Scaffold-DbContext with the -Force option to
overwrite the existing models. However, as mentioned, this will overwrite any
customizations.
Syntax: Scaffold-DbContext “Your_Connection_String”
Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -Tables Employees,
Departments -DataAnnotations
1094
Once you press the Enter button, if everything is provided correctly, you will get the
following message.
The entity classes and DBContext class will be created in the Models folder, as shown in
the image below.
Now, let us Proceed and see how we can perform the database CRUD Operations using
the Entity Framework Core Database First Approach. Later, I will show how to use the
Views, Stored Procedures, and Stored Functions.
CRUD Operations Using Database First Approach:
Modify the Main method of the Program class as follows. The following example performs
the database CRUD Operations using the EF Core Database First Approach:
1095
using EFCoreCodeFirstDemo.Models;
namespace EFCoreCodeFirstDemo
try
DepartmentName = "IT",
};
context.Add(ITDepartment);
DepartmentName = "HR",
};
context.Add(HRDepartment);
context.SaveChanges();
1096
//Create Operations
context.Employees.Add(employee1);
context.Employees.Add(employee2);
context.Employees.Add(employee3);
context.SaveChanges();
//Read Operations
//Update Operation
employee1.LastName = "Parida";
employee1.Salary = 900000;
employee1.DepartmentId = HRDepartment.DepartmentId;
context.SaveChanges();
Console.WriteLine("After Updation");
//Delete Operation
context.Remove(employee3);
context.SaveChanges();
employees = context.Employees.ToList();
Console.Read();
{
1098
Console.WriteLine($"Error: {ex.Message}"); ;
}
Output:
Now, let us proceed and see how we can use the Views, Stored Procedures, and Functions
in Entity Framework Core Database First Approach:
Using Views and Stored Procedures in EF Core DB First Approach:
Please modify the Main Method of the Program class as follows: I am showing how to use
the Stored procedure and views with Entity Framework Core Database First Approach using
the FromSqlRaw method.
using EFCoreCodeFirstDemo.Models;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
try
Console.WriteLine("Using View");
//As the Stored Procedure Returning an Emloyee Details, we can use FromSqlRaw Method
.AsEnumerable()
.FirstOrDefault();
Console.Read();
Console.WriteLine($"Error: {ex.Message}"); ;
}
Note: The most important point you need to remember is while using
the FromSqlRaw method, it will map the result to the specified Entity type, and it is
mandatory to return the required columns in the Stored Procedures or from the Views.
Otherwise, the mapping will not be done, and it will throw a Runtime Exception.
Calling Stored Function using EF Core:
Even though we are using a Database-First approach, we need to manually define the
function in our DbContext class, as EF Core does not automatically import functions through
the scaffolding process. So, add the following method in your DbContext that represents the
scalar function. Use the DbFunction attribute to link it to the SQL function.
//Specify the database Function name and database schema
}
Once you have defined the function in your DbContext class, you can call it like any other
static method in your code. So, modify the Program class as follows:
using EFCoreCodeFirstDemo.Models;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
1101
try
Salary = d.Salary,
DepartmentName = d.Department.DepartmentName,
Bonus = EfcoreDbContext.CalculateBonus(d.EmployeeId)
}).FirstOrDefault();
Console.Read();
1102
Console.WriteLine($"Error: {ex.Message}"); ;
This package is the Entity Framework Core database provider for Microsoft SQL Server and
Azure SQL Database. It is necessary for any ASP.NET Core application that intends to use
these databases. The primary functions of this package include:
Database Connection: It allows EF Core to establish connections to a SQL
Server database.
SQL Generation: It translates the LINQ queries from your application into
SQL queries that the SQL Server can understand.
Schema Generation: It is responsible for translating the entity data models
into SQL Server database schemas. This is particularly useful when creating
and migrating databases.
Optimizing Performance: The package includes SQL Server-specific
optimizations, enhancing the performance of data access operations.
Microsoft.EntityFrameworkCore.Tools:
This package provides additional tools for working with Entity Framework Core, which is
especially useful during development. These tools enhance the development experience
and simplify many database-related tasks. Key features include:
Migrations: Commands for creating and managing migrations are included,
which help evolve the database schema over time without losing data.
Database Update: This tool allows applying migrations to update the
database schema directly from the command line or via the Package
Manager Console in Visual Studio.
Database Scaffolding: It can reverse engineer a database schema to create
entity model classes and a DbContext based on an existing database, which
is particularly useful in database-first scenarios.
Script Generation: Generate SQL scripts from migrations, which can be
useful for manual reviews or when deploying databases through scripts.
You can install the above two Packages using NuGet Package Manager for Solution or by
using Package Manager Console. Please execute the following command using the
Package Manager Console to install the Packages.
Install-Package Microsoft.EntityFrameworkCore.Tools
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Your project structure should look like the one shown below after Step 1. However, your
package might be different when you read this article.
1104
}
Create another class file named Employee.cs within the Models folder, and then copy and
paste the following code:
1105
namespace CRUDinCoreMVC.Models
}
By default, we have implemented one-to-many relationships between Employees and
Departments. An employee belongs to a single department, and one department can have
many employees.
Step 3: Configure the Database Connection
Instead of hard-coding the connection string with the DbContext class, we will store the
connection string in the appsettings.json file. So, add your database connection string in
the appsettings.json file as follows:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
},
"AllowedHosts": "*",
1106
"ConnectionStrings": {
"EFCoreDBConnection": "Server=LAPTOP-6P5NK25R\\
SQLSERVER2022DEV;Database=EFCoreMVCDB;Trusted_Connection=True;TrustServerC
ertificate=True;"
}
Step 4: Configure DbContext
Create a DbContext class for the application to manage the database. In Entity Framework
Core (EF Core), the DbContext class is the component that serves as a bridge between
your application’s code and the database. It plays an important role in managing the
interactions with the underlying database in an efficient and performance-oriented manner.
So, add a class named EFCoreDBContext.cs and then copy and paste the following code.
using Microsoft.EntityFrameworkCore;
using System.Diagnostics.Metrics;
namespace CRUDinCoreMVC.Models
}
Role of DbContext in EF Core:
Querying: DbContext provides the necessary methods and properties to
query the database. It converts LINQ (Language Integrated Query)
expressions into SQL queries that the database can understand.
Saving Changes: It tracks changes made to objects and applies them to the
database when SaveChanges() or SaveChangesAsync() is called. This
includes translating the changes into insert, update, or delete commands.
Model Mapping: It maps classes to database tables and properties to table
columns through the ModelBuilder class used in the OnModelCreating
method.
Change Tracking: DbContext tracks entities’ states during their lifecycle.
This tracking ensures that only actual changes are updated in the database
during a save operation, which helps optimize database access and improve
performance.
Step 5: Configure the Database Connection:
Next, we need to configure the connection string with the DbContex class. Please add the
following code to the Program class. The following code configures the DbContext in an
ASP.NET Core application using Entity Framework Core (EF Core).
//Configure the ConnectionString and DbContext class
builder.Services.AddDbContext<EFCoreDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("EFCoreDBConnection"))
;
});
Code Explanation:
builder.Services: builder.Services refer to the IServiceCollection provided by
the ASP.NET Core host builder. This collection registers services that the
1108
With this, our Database with Departments and Employees table is created, as shown in the
below image:
1109
Before proceeding and performing the database CRUD Operations, let us first insert some
master data into the Departments database table by executing the following INSERT SQL
statements, which we will use while performing the Employee CRUD operation.
INSERT INTO Departments VALUES ('IT');
Once you click on the Add button, the following window will open. Here, provide the Model
Class as Employee, provide the DbContext Class as EFCoreDBContext, Keep the rest
of the setting for Views as it is, provide the Controller name as EmployeesController, and
then click on the Add button as shown in the below image:
Once you click the Add button, it will take some time to create the controller, all the action
methods to perform the database CRUD Operations, and the corresponding views for us.
The following is the auto-generated Employees Controller class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using CRUDinCoreMVC.Models;
namespace CRUDinCoreMVC.Controllers
1111
_context = context;
// GET: Employees
// GET: Employees/Details/5
return NotFound();
if (employee == null)
return NotFound();
return View(employee);
// GET: Employees/Create
return View();
// POST: Employees/Create
// To protect from overposting attacks, enable the specific properties you want to bind to.
[HttpPost]
[ValidateAntiForgeryToken]
if (ModelState.IsValid)
_context.Add(employee);
1113
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
return View(employee);
// GET: Employees/Edit/5
return NotFound();
if (employee == null)
return NotFound();
return View(employee);
// POST: Employees/Edit/5
1114
// To protect from overposting attacks, enable the specific properties you want to bind to.
[HttpPost]
[ValidateAntiForgeryToken]
if (id != employee.EmployeeId)
return NotFound();
if (ModelState.IsValid)
try
_context.Update(employee);
await _context.SaveChangesAsync();
catch (DbUpdateConcurrencyException)
if (!EmployeeExists(employee.EmployeeId))
return NotFound();
}
1115
else
throw;
return RedirectToAction(nameof(Index));
return View(employee);
// GET: Employees/Delete/5
return NotFound();
if (employee == null)
return NotFound();
1116
return View(employee);
// POST: Employees/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
if (_context.Employees == null)
if (employee != null)
_context.Employees.Remove(employee);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
1117
}
Now, if you verify the Views folder, then you will see the views for the Employees controller
as shown in the below image:
Note: The Scaffolded Controllers will contain methods for CRUD operations. As we
progress, we will customize these methods and views, as per our application requirements.
Creating Department Controller:
The way we have created the EmployeesController, in the same way, we can also create
the DepartmentsController. So, please follow the same steps and create the Departments
Controller. While creating the Controller, you must provide the Model class as Department.
Testing
Run the application and test all CRUD operations for both employees and departments.
Ensure that the department selection works correctly when creating or editing an
employee. Before testing, first, modify the Default controller and action to Employee and
Index in the Program class as follows:
app.MapControllerRoute(
name: "default",
pattern: "{controller=Employees}/{action=Index}/{id?}");
Now, if you run the application and go to the Employees/Create URL, you will see it
displays the Department ID in the Drop-Down List instead of the Department name, as
shown in the image below.
1118
To display the Department name instead of ID, modify the Create Action Method (both Get
and Post) of the EmployeesController as follows:
// GET: Employees/Create
return View();
// POST: Employees/Create
// To protect from overposting attacks, enable the specific properties you want to bind to.
[HttpPost]
1119
[ValidateAntiForgeryToken]
if (ModelState.IsValid)
_context.Add(employee);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
return View(employee);
}
Now, run the application and navigate to the Employees/Create URL, and you should see
it showing the Department name in the dropdown list. Let us create one employee and click
the Create button, as shown in the image below.
1120
Once you click on the Create button, the new employee will be added to the database.
Then, it will redirect you to the Index page, which will display all the Employees, as shown in
the image below. We have created only one employee, and that employee’s information will
be displayed here.
If you look at the Index view, it is showing the Department as 2. So, instead of showing the
Department ID, we need to show the Department name. To do so, modify the Index view of
the Employees controller as follows:
@model IEnumerable<CRUDinCoreMVC.Models.Employee>
@{
ViewData["Title"] = "Index";
1121
<h1>Index</h1>
<p>
</p>
<table class="table">
<thead>
<tr>
<th>
</th>
<th>
</th>
<th>
</th>
<th>
</th>
<th></th>
</tr>
</thead>
<tbody>
1122
<tr>
<td>
</td>
<td>
</td>
<td>
</td>
<td>
</td>
<td>
</td>
</tr>
1123
</tbody>
</table>
Now, run the application, and it should display the Department name in the Index view as
shown in the below image:
To see the Employee details, click the Details button as shown in the above image. Once
you click on the Details button, it will open the following Details view.
As you can see, the Department ID is also displayed here. To show the Department name
instead of the Department ID, please modify the Details view of the Employee controller as
follows:
@model CRUDinCoreMVC.Models.Employee
@{
ViewData["Title"] = "Details";
<h1>Details</h1>
<div>
<h4>Employee</h4>
1124
<hr />
<dl class="row">
</dt>
</dd>
</dt>
</dd>
</dt>
</dd>
</dt>
</dd>
</dl>
</div>
<div>
</div>
Now, run the application and see the Details of the Employee and it should show the
Department name as expected, as shown in the image below:
Now, click the Edit button either from the Details view or Index view to edit an employee.
Once you click the Edit button, the following view with prepopulated employee information
will open.
1126
Further, if you notice, it shows the Department ID in the Dropdown List. Instead of showing
ID, if you want to show the Name of the Department, then please modify the Edit action
method (both Get and Post) of the Employees controller as follows:
// GET: Employees/Edit/5
return NotFound();
if (employee == null)
1127
return NotFound();
return View(employee);
// POST: Employees/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
[HttpPost]
[ValidateAntiForgeryToken]
if (id != employee.EmployeeId)
return NotFound();
if (ModelState.IsValid)
try
_context.Update(employee);
1128
await _context.SaveChangesAsync();
catch (DbUpdateConcurrencyException)
if (!EmployeeExists(employee.EmployeeId))
return NotFound();
else
throw;
return RedirectToAction(nameof(Index));
return View(employee);
}
Now, it should display the Department Name in the dropdown list. Let us modify the
Employee Department to IT and Position to DBA and click the Save button, as shown in the
image below.
1129
Once you update the data and click on the Save button, it will save the data into the
database and redirect to the Index view, where you can see the updated data, as shown in
the image below.
Now, click the Delete button, as shown in the above image, to Remove the Employee from
the database. Once you click the Delete button, the following Delete View will open.
1130
As you can see in the above image, it is showing the Department ID value instead of the
Department Name. To display the Department Name, modify the Delete view of the
Employees controller as follows:
@model CRUDinCoreMVC.Models.Employee
@{
ViewData["Title"] = "Delete";
<h1>Delete</h1>
<div>
<h4>Employee</h4>
<hr />
<dl class="row">
</dt>
</dd>
</dt>
</dd>
</dt>
</dd>
</dt>
@if(Model.Department != null)
}
1132
</dd>
</dl>
<form asp-action="Delete">
</form>
</div>
With the above changes, run the application, go to the Index View, and click the Delete
button. This time, it should display the Department Name instead of the Department ID, as
shown in the image below. Click on the Delete button to delete the Employee from the
database.
Once you click the Delete button, it will delete the employee and then navigate to the Index
view.
While creating and updating an employee, the dropdown list name is displayed as
DepartmentId. If you want to display Department Name instead of DepartmentId, modify the
Employee model as follows. Here, you can see we are decorating
the DepartmentId property with a Display Attribute and setting the Name Property as
Department Name.
using System.ComponentModel.DataAnnotations;
1133
namespace CRUDinCoreMVC.Models
}
Note: Similarly, you can test the Department Controller and Views and Perform the
database CRUD Operations.
Enhancements
As per your business requirements, you can make the following enhancements:
Validation: Implement data annotations for model validation.
Exception Handling: Include proper error handling in your application.
User Interface: Use CSS and JavaScript to improve the UI.
Advanced Features: Consider adding features like search, sorting, and
pagination.