Web Api
Web Api
RESTful API: APIs in ASP.NET Core follow the principles of REST (Representational State Transfer), which relies
on HTTP for communication and statelessness.
4. Creating ASP.NET Core Web API Project using .NET Core CLI
To create a new ASP.NET Core Web API using the .NET Core CLI, follow these steps:
1. Open the terminal (or command prompt).
2. Run the following command to create a new Web API project:
dotnet new webapi -n MyWebApi bash
namespace MyWebApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return "value";
}
// POST api/values
[HttpPost]
public void Post([FromBody] string value)
{
}
// PUT api/values/5
[HttpPut("{id}")]
public void Put(int id, [FromBody] string value)
{
}
// DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}
In this example:
The GET method retrieves a list of values.
The POST , PUT , and DELETE methods perform CRUD operations.
This model represents a Product entity with Id , Name , and Price properties. It can be used for CRUD
operations in the controller.
Example of Using Model in Controller:
[HttpPost] csharp
public IActionResult Post([FromBody] Product product)
{
if (product == null)
{
return BadRequest();
}
[HttpGet]
public IEnumerable<Product> Get()
{
return _products;
}
[HttpPost]
public IActionResult Post([FromBody] Product product)
{
_products.Add(product);
return CreatedAtAction(nameof(Get), new { id = product.Id }, product);
}
}
Conclusion
ASP.NET Core Web API is an
essential tool for building modern RESTful web services. This training provides you with a foundational understanding
of the key concepts such as HTTP methods, routing, controllers, models, and Swagger. Additionally, by following this
structured approach, you can develop and test your own Web API projects effectively.
Conventional Routing: Configured globally in Startup.cs , where a pattern is defined that matches controller
names, actions, and parameters.
Basic Routing Example
In this example, the routing is done via attribute routing, where the route is specified directly on the controller or
action.
// Controller csharp
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
// GET api/products
[HttpGet]
public IActionResult Get()
{
return Ok(new string[] { "Product1", "Product2" });
}
// GET api/products/5
[HttpGet("{id}")]
public IActionResult Get(int id)
{
return Ok($"Product {id}");
}
}
In this example:
api/products maps to Get() action, which returns all products.
api/products/{id} maps to Get(int id) action, which retrieves a specific product by ID.
This will match any URL like api/products/5 and pass the value 5 to the id parameter.
Query Strings
Query strings are typically used for optional parameters. They follow the ? symbol in the URL.
Example:
[HttpGet] csharp
public IActionResult Get([FromQuery] string category)
{
return Ok($"Fetching products in category: {category}");
}
In this example:
Both GET api/products/list and GET api/products/all will invoke the GetAllProducts()
method and return the list of products.
This is particularly useful when you want to provide multiple ways to access the same resource.
// GET api/products/5
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
return Ok($"Product {id}");
}
}
In this case, the Route("api/[controller]") applies the prefix api/products to both actions, and the id
token can be appended to the URL for the second action.
In this case, the {id:int} constraint ensures that only integer values can be passed in place of {id} .
Common Route Constraints:
int : Matches only integers.
// GET api/products
[HttpGet]
public IActionResult Get()
{
return Ok(_products);
}
// GET api/products/5
[HttpGet("{id:int}")]
public IActionResult GetById(int id)
{
var product = _products.FirstOrDefault(p => p.Id == id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
// GET api/products/cheap
[HttpGet("cheap")]
public IActionResult GetCheapProducts()
{
var cheapProducts = _products.Where(p => p.Price < 600).ToList();
return Ok(cheapProducts);
}
// GET api/products/search?name=laptop
[HttpGet("search")]
public IActionResult SearchByName([FromQuery] string name)
{
var products = _products.Where(p => p.Name.Contains(name, StringComparison.Ordi
nalIgnoreCase)).ToList();
return Ok(products);
}
}
Explanation:
1. Route Prefix: The [Route("api/products")] attribute sets the base route for all actions.
2. Route Parameters: The GetById method uses {id:int} to ensure only integers can be passed as the id .
3. Multiple URLs: The GetCheapProducts() action is mapped to both GET api/products/cheap and GET
api/products/search?name=laptop , showing different URL access points for the same resource.
4. Query String: The SearchByName() method demonstrates how to retrieve data using query parameters.
Step 3: Run and Test with Postman/Swagger
GET http://localhost:5000/api/products returns all products.
GET
http://localhost:5000/api/products/1 returns the product with ID 1.
T : Directly returning a data object, which can automatically be serialized to JSON by default.
Example:
public class ProductsController : ControllerBase csharp
{
[HttpGet]
public IActionResult GetProducts()
{
var products = new List<string> { "Product1", "Product2" };
return Ok(products); // Returns 200 OK with product list
}
[HttpGet("{id}")]
public ActionResult<string> GetProduct(int id)
{
if (id <= 0) return NotFound(); // Returns 404 Not Found
return Ok($"Product {id}");
}
}
if (Request.Headers.ContainsKey("If-Modified-Since"))
{
var clientDate = DateTime.Parse(Request.Headers["If-Modified-Since"]);
if (clientDate >= lastModified)
{
return StatusCode(304); // Returns 304 Not Modified
}
}
return Ok("New Data");
}
If the client is not authenticated, they will receive a 401 Unauthorized response.
403 HTTP Status Code (Forbidden)
The 403 Forbidden status code indicates that the client does not have permission to access the resource, even if
authenticated.
Example:
[HttpGet("admin")] csharp
[Authorize(Roles = "Admin")]
public IActionResult AdminData()
{
return Ok("Admin-only data.");
}
If the user does not have the Admin role, a 403 Forbidden status is returned.
404 HTTP Status Code (Not Found)
The 404 Not Found status code indicates that the requested resource could not be found on the server.
Example:
[HttpGet("{id}")] csharp
public IActionResult GetProduct(int id)
{
var product = _products.FirstOrDefault(p => p.Id == id);
if (product == null)
{
return NotFound(); // Returns 404 Not Found
}
return Ok(product);
}
// If a client sends a GET request to this endpoint, it will return 405 Method Not Allo
wed
}); }
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseCors("AllowSpecificMethods"); }
---
### **Conclusion**
Understanding the various HTTP status codes and their usage in ASP.NET Core Web API is
essential for building effective RESTful APIs. By using appropriate status codes for di
fferent actions, you help clients understand the outcome of their requests. Additionall
y, you can control the flow of your application, implement secure access, handle errors
gracefully, and ensure proper routing of HTTP requests.
Example:
[HttpPost("create")] csharp
public IActionResult CreateProduct([FromForm] Product product)
{
// The product will be populated from the form data
return Ok(product);
}
Here, when a user submits a form with data, the Product model will be populated with the form data.
For the URL https://api.example.com/product/1 , the value 1 will be used to bind the id parameter.
Here, if the request includes a header Authorization: Bearer token_value , it will be passed to the
authorization parameter.
Here, the data from the request body (in JSON or XML format) will be deserialized into the Product object.
Now, use this custom model binder in the controller action method:
[HttpGet("getProduct")] csharp
public IActionResult GetProduct([ModelBinder(BinderType = typeof(CustomProductBinder))]
Product product)
{
return Ok(product);
}
7. How to Apply Binding Attributes to Model Properties in ASP.NET Core Web API
You can use attributes like FromQuery , FromBody , FromHeader , and FromForm on individual model
properties to control where each property is bound from. This is useful when a model contains multiple properties that
come from different parts of the request.
Example:
public class Product csharp
{
[FromQuery]
public int Id { get; set; }
[FromBody]
public string Name { get; set; }
[FromHeader]
public string Authorization { get; set; }
}
In this example, the Id will be taken from the query string, Name from the body, and Authorization from the
header.
Example:
[HttpGet] csharp
public IActionResult GetProduct()
{
var product = new Product { Id = 1, Name = "Laptop" };
return Ok(product); // The response format will depend on the Accept header.
}
9. Include and Exclude Properties from Model Binding in ASP.NET Core Web API
To control which properties of a model are bound or excluded from model binding, you can use attributes like Bind
and JsonIgnore .
[Bind] : Allows you to specify which properties are bound in a model.
Example:
public class Product csharp
{
[BindRequired]
public int Id { get; set; }
[JsonIgnore]
public string InternalNotes { get; set; }
}
In this example:
Id is required for model binding.
You can also use the [Bind] attribute to allow binding only specific properties:
public class Product csharp
{
public int Id { get; set; }
[Bind("Name")]
public string Name { get; set; }
}
In this case, only the Name property will be bound, while other properties like Id will not.
Summary
Model Binding is the process of binding data from various parts of the HTTP request (query strings, route
parameters, headers, form data, body) to C# model properties.
Binding Attributes such as FromQuery , FromBody , FromRoute , FromHeader , FromForm are used to
specify where each property should be bound from.
Custom Model Binding can be implemented when the default model binding behavior does not meet the
requirements of your application.
Content Negotiation allows your API to return data in multiple formats (e.g., JSON, XML) based on the client's
Accept header.
Binding Constraints like [Bind] and [JsonIgnore] help control which properties can or cannot be bound
during model binding.
These features in ASP.NET Core Web API provide flexibility to handle various input scenarios in a clean and efficient
way, making your API more adaptable to different client needs.
// AutoMapper Profile
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Order, OrderDTO>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.OrderId))
.ForMember(dest => dest.Product, opt => opt.MapFrom(src => src.ProductNam
e))
.ForMember(dest => dest.TotalPrice, opt => opt.MapFrom(src => src.Price));
}
}
Now you can use AutoMapper to map between Order and OrderDTO .
3. Mapping Complex Type to Primitive Type using AutoMapper in ASP.NET Core Web API
AutoMapper also supports mapping complex types (like classes or collections) to primitive types (like strings, ints,
etc.).
Example:
public class Product csharp
{
public int Id { get; set; }
public string Name { get; set; }
}
// AutoMapper Profile
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Product, string>() // Mapping Product to string (Name)
.ConvertUsing(src => src.Name);
}
}
In this example, the Product class is mapped to just the Name property, which is a string.
// AutoMapper Profile
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Order, OrderDTO>()
.ReverseMap(); // Enable reverse mapping
}
}
Now, you can map from Order to OrderDTO and vice versa using AutoMapper.
In this example, Price will be mapped only if it has a value; otherwise, it will be set to "N/A".
Example:
public class Product csharp
{
public string Name { get; set; }
public decimal Price { get; set; }
}
Here, PreCondition and PostCondition ensure that the price is positive and the name is not empty before
mapping.
In this case, if the Name property is null, it will be replaced with "Unknown" during mapping.
9. Fixed, Dynamic, Null Substitution, and Ignore Property Mapping using AutoMapper
You can combine various features of AutoMapper, such as fixed values, dynamic values, null substitution, and ignoring
properties, in one mapping configuration.
Example:
public class Product csharp
{
public string Name { get; set; }
public decimal Price { get; set; }
}
In this example, the Name property is ignored, while Price will use null substitution, and Name will default to
"Default Name" if it is null.
// AutoMapper Profile
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap
<Product, ProductDTO>(); } }
// Product Controller [Route("api/[controller]")] public class ProductsController : ControllerBase { private readonly
IProductService productService; private readonly IMapper mapper;
public ProductsController(IProductService productService, IMapper mapper)
{
_productService = productService;
_mapper = mapper;
}
[HttpGet("{id}")]
public ActionResult<ProductDTO> GetProduct(int id)
{
var product = _productService.GetProductById(id);
if (product == null)
{
return NotFound();
}
In this real-world example, `ProductDTO` is used to send data from the API to the clien
t, while `Product` is the entity model that is used for database operations. AutoMapper
simplifies the transformation between the two objects.
---
### **Summary**
AutoMapper in ASP.NET Core Web API simplifies the process of mapping between different
object models (such as DTOs and entities). Key features include:
By using AutoMapper, you can reduce boilerplate code and improve maintainability, espec
ially when dealing with complex object transformations.
Here, the GetAllProducts method retrieves all products from the service and returns them with an HTTP 200 OK
status.
HTTP GET Query Parameters: You can also handle query parameters by adding them as method parameters:
[HttpGet] csharp
public IActionResult GetProductById(int id)
{
var product = _productService.GetById(id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
Here, the CreateProduct method adds a new product to the service and returns HTTP 201 Created along with the
URI of the newly created resource.
_productService.Update(product);
return NoContent();
}
Here, the UpdateProduct method updates the existing product identified by id and returns HTTP 204 No
Content.
patchDocument.ApplyTo(product);
_productService.Update(product);
return Ok(product);
}
Here, the PartiallyUpdateProduct method applies a JSON Patch document to update specific fields of the
product.
_productService.Delete(id);
return NoContent();
}
In this example, the DeleteProduct method deletes a product from the database and returns HTTP 204 No
Content.
Here, the CheckProductExistence method checks if the resource exists, but no body is returned, just the headers.
Here, the GetAllowedMethods method informs the client of the allowed HTTP methods for a particular resource.
Now, Serilog will log messages to both the console and a file.
Logging to Database using Serilog in ASP.NET Core Web API
You can also log messages to a database by using the Serilog.Sinks.MSSqlServer package.
Step 1: Install the required NuGet package:
Install-Package Serilog.Sinks.MSSqlServer bash
This writes logs to the Logs table in the configured SQL Server database.
3. Using In-Memory Cache: Inject IMemoryCache into your controllers or services to use it.
public class ProductService csharp
{
private readonly IMemoryCache _memoryCache;
2. Configure Redis in Startup.cs : In the ConfigureServices method, register Redis as a distributed cache.
public void ConfigureServices(IServiceCollection services) csharp
{
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379"; // Redis server address
options.InstanceName = "SampleInstance"; // Name of the Redis instance
});
}
3. Use Response Caching in the Controller: You can apply the ResponseCache attribute to your controller or
actions.
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Client, NoStore =csharp
fa
lse)]
public IActionResult GetProduct(int id)
{
var product = _productService.GetById(id);
return Ok(product);
}
This example caches the response for 60 seconds.
By understanding these caching strategies, you can choose the most suitable one for your application, whether it's in-
memory caching for simple scenarios or distributed caching like Redis or NCache for scalable, multi-server
applications.
2. Configure FluentValidation in Startup.cs: In your Startup.cs file, you need to add FluentValidation services
to the dependency injection container.
public void ConfigureServices(IServiceCollection services) csharp
{
services.AddControllers()
.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<
Startup>());
}
3. Create a Validator Class: Next, create a validator class that contains the validation rules for your model.
public class ProductValidator : AbstractValidator<Product> csharp
{
public ProductValidator()
{
RuleFor(x => x.Name).NotEmpty().WithMessage("Product name is required.");
RuleFor(x => x.Price).GreaterThan(0).WithMessage("Price must be greater tha
n zero.");
RuleFor(x => x.Description).MaximumLength(200).WithMessage("Description ca
n't exceed 200 characters.");
}
}
4. Apply Fluent Validation to Models: Now, when you pass a model to an action method, FluentValidation will
automatically validate it:
[HttpPost] csharp
public IActionResult CreateProduct([FromBody] Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
The ProductValidator class ensures that the Product model is validated according to the specified rules.
In this case, BeUniqueName is an asynchronous method that queries the database to check if a product name
already exists. This method is used with the MustAsync validation rule.
In this example, the Custom method allows you to write custom logic to validate the price of the product.
In this case, the When method is used to apply the validation rule only when the Discount property is greater than
0.
In this case, the CategoryValidator is applied to the Category property of the Product model.
FluentValidation will recursively validate nested models.
Collection Validation Example:
You can also validate collections (arrays, lists) of models.
public class Order csharp
{
public List<Product> Products { get; set; }
}
This validator ensures that every product in the Products list is validated according to the ProductValidator .
Summary:
Fluent API Validation: Enables you to define complex and flexible validation rules in a fluent and readable
manner.
Async Validators: Support asynchronous validation for scenarios involving external data sources.
Custom Validators: Allows you to create custom validation logic using the Custom method.
Conditional Validations: FluentValidation supports conditional validation based on the state of other properties
or conditions.
Nested and Collection Validations: FluentValidation can validate nested models and collections, ensuring that
all parts of complex structures are validated.
By using FluentValidation, you can create clean, maintainable, and reusable validation rules in your ASP.NET Core Web
API applications.
Filters in ASP.NET Core Web API
In ASP.NET Core Web API, filters are a mechanism that allows developers to run code before or after an action
method runs. Filters are commonly used to handle cross-cutting concerns such as logging, error handling,
authentication, authorization, caching, and more.
ASP.NET Core provides different types of filters which can be applied globally, at the controller level, or at the action
level. Filters can be categorized into the following types:
1. Authorization Filters – Run before any other filter, used for checking if the user is authorized to execute the
action.
2. Resource Filters – Run after the authorization filter but before model binding and action execution.
3. Action Filters – Run before and after an action method executes, ideal for logic that needs to be executed
before or after a specific action.
4. Exception Filters – Handle exceptions thrown during the execution of a request.
5. Result Filters – Run before and after the action result is executed, used for modifying the response before it is
returned to the client.
In this article, we will specifically focus on Authorization Filters and Resource Filters.
if (!isAuthenticated)
{
// Return an Unauthorized response if not authenticated
context.Result = new UnauthorizedResult();
}
}
}
Controller-level registration:
[ServiceFilter(typeof(CustomAuthorizationFilter))] csharp
public class MyController : ControllerBase
{
public IActionResult Get()
{
return Ok("Authorized");
}
}
Action-level registration:
[HttpGet] csharp
[ServiceFilter(typeof(CustomAuthorizationFilter))]
public IActionResult GetData()
{
return Ok("Authorized");
}
In the example above, the CustomAuthorizationFilter checks whether the user is authenticated. If the user is
not authenticated, an UnauthorizedResult is returned.
Controller-level Registration:
[ServiceFilter(typeof(CustomResourceFilter))] csharp
public class MyController : ControllerBase
{
public IActionResult Get()
{
return Ok("Resource Filter Applied");
}
}
Action-level Registration:
[HttpGet] csharp
[ServiceFilter(typeof(CustomResourceFilter))]
public IActionResult GetData()
{
return Ok("Resource Filter Applied");
}
In this example, the logs the start and end time of a request. The
CustomResourceFilter
OnResourceExecuting method is executed before the action method, and OnResourceExecuted is executed
after the action method completes.
Conclusion
Authorization Filters: Used to ensure that a user is authorized to perform a certain action. They are typically
used to handle authentication and authorization logic.
Resource Filters: Used for actions that need to execute before model binding and action execution. They are
ideal for tasks like caching or pre-processing data.
These filters allow for a modular, reusable, and maintainable way to add cross-cutting concerns such as security,
logging, and caching in an ASP.NET Core Web API application. They can be applied globally or at the controller or
action level, depending on the specific needs of the application.
Security and Authentication in ASP.NET Core Web API
Security is one of the most important concerns when developing web APIs. ASP.NET Core provides various
mechanisms for securing and authenticating API requests, including password hashing, token-based authentication
(JWT), and encryption. Below, we cover several key concepts and implementations related to security and
authentication in ASP.NET Core Web API.
public UserService()
{
_passwordHasher = new PasswordHasher<ApplicationUser>();
}
2. Storing the hashed password in a database (e.g., SQL Server) will ensure that passwords are never stored in
plaintext.
Why Use Password Hashing?
Security: Even if the database is compromised, attackers won’t have access to users' actual passwords.
Compliance: Hashing passwords helps meet various compliance standards (e.g., GDPR, PCI DSS).
2. HMAC Authentication in ASP.NET Core Web API
HMAC (Hash-based Message Authentication Code) is used for ensuring the integrity and authenticity of a
message. It's commonly used for secure API communication.
HMAC Implementation Example:
1. Generate HMAC Hash:
using System.Security.Cryptography; csharp
using System.Text;
2. Verify HMAC Hash: The server can verify the hash by recalculating it using the same secret key and comparing
it to the sent hash.
2. Decrypt Data:
public string DecryptData(string encryptedData, string key) csharp
{
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = Encoding.UTF8.GetBytes(key);
aesAlg.IV = new byte[16]; // Assuming the IV used for encryption is 16 byt
es.
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
public RsaService()
{
_rsa = new RSACryptoServiceProvider();
}
2. Register Middleware:
public void Configure(IApplicationBuilder app) csharp
{
app.UseMiddleware<BasicAuthMiddleware>();
}
2. Refresh JWT Token: When the access token expires, the client can use the refresh token to get a new one.
These security practices, including password hashing, HMAC, encryption, JWT authentication, and CORS, help to
secure the data, authentication, and access to your ASP.NET Core Web API endpoints, ensuring that your application
is secure and follows modern security best practices.
SSO Authentication in ASP.NET Core Web API
SSO (Single Sign-On) Authentication enables users to authenticate once and gain access to multiple applications
without having to sign in again. It is commonly used in organizations to simplify the user experience and centralize
authentication. In an SSO system, a central Identity Provider (IdP) authenticates the user, and other applications
(called Service Providers) trust the IdP to validate the user and authorize their access.
In ASP.NET Core Web API, SSO Authentication is typically implemented using OpenID Connect (OIDC) and OAuth
2.0. These protocols allow a user to authenticate with a central authority (e.g., an identity provider like Azure AD,
IdentityServer, or Okta), and access multiple APIs without needing to log in separately.
Steps to Implement SSO Authentication in ASP.NET Core Web API:
1. Install NuGet Packages: You'll need to install the required packages for OpenID Connect and JWT
authentication:
dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect bash
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
services.AddControllers();
}
3. Use Authentication Middleware: Add authentication and authorization middleware in the Configure method
of Startup.cs :
public void Configure(IApplicationBuilder app) csharp
{
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Define the Config class that contains your clients, API scopes, and identity resources:
public static class Config csharp
{
public static IEnumerable<Client> Clients =>
new Client[]
{
new Client
{
ClientId = "mvc_client",
AllowedGrantTypes = GrantTypes.Code,
ClientSecrets = { new Secret("secret".Sha256()) },
RedirectUris = { "https://localhost:5002/signin-oidc" },
PostLogoutRedirectUris = { "https://localhost:5002/signout-callback
-oidc" },
AllowedScopes = { "openid", "profile", "api1" }
}
};
2. Protect Endpoints with Authorization: Use the [Authorize] attribute to protect API endpoints:
[Authorize] csharp
[HttpGet("secure-data")]
public IActionResult GetSecureData()
{
return Ok(new { Data = "This is protected data" });
}
3. Validate the Token: The API will validate the incoming JWT token automatically based on the configuration. Only
valid tokens will allow access to the secured resources.
3. Authorization in MVC:
Protect the controller actions that require authentication by using the [Authorize] attribute:
[Authorize] csharp
public IActionResult SecurePage()
{
return View();
}
2. Secure Endpoints:
As with Client Application One, use the [Authorize] attribute to secure the routes in Client Application
Two:
[Authorize] csharp
public IActionResult Dashboard()
{
return View();
}
Conclusion
SSO Authentication allows users to authenticate once with a central identity provider and access multiple
applications or APIs without needing to log in separately.
The Authentication Server (Identity Provider) issues tokens and manages user authentication.
The Resource Server validates tokens and serves data or services to authenticated clients.
Client Applications (ASP.NET Core MVC) interact with the Authentication Server to authenticate users and
consume resources from the Resource Server.
By setting up a robust SSO system, you can streamline user authentication, increase security, and improve user
experience across multiple web applications and APIs.
ASP.NET Core Web API Versioning
API Versioning is an important concept when it comes to managing different versions of your API. It allows you to
introduce new features and changes to the API without breaking existing clients. ASP.NET Core provides several ways
to handle versioning to support backward compatibility.
There are multiple approaches to versioning an API in ASP.NET Core Web API. Some common strategies include:
1. Query String Versioning: The version is passed as a parameter in the query string.
2. URL Path Versioning: The version is included directly in the URL path.
3. Header Versioning: The version is specified in the request header.
4. Media Type Versioning: The version is specified using the "Accept" header in the request, commonly using
MIME types.
ASP.NET Core Web API Versioning
To implement versioning in your API, you need to use the Microsoft.AspNetCore.Mvc.Versioning package. This
package provides a simple and flexible way to manage versions for your Web API.
1. Install NuGet Package for Versioning:
dotnet add package Microsoft.AspNetCore.Mvc.Versioning bash
services.AddControllers();
}
This configuration sets up the versioning system for query strings, headers, and URL paths.
services.AddControllers();
}
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/products")]
[ApiController]
public class ProductsV2Controller : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok("Products API Version 2");
}
}
services.AddControllers();
}
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/products")]
[ApiController]
public class ProductsV2Controller : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok("Products API Version 2");
}
}
services.AddControllers();
}
[ApiVersion("2.0")]
[Route("api/products")]
[ApiController]
public class ProductsV2Controller : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok("Products API Version 2");
}
}
3. Example Request:
To access version 1.0, include the following header:
GET /api/products
X-API-Version: 1.0
services.AddControllers();
}
[ApiVersion("2.0")]
[Route("api/products")]
[ApiController]
public class ProductsV2Controller : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok("Products API Version 2");
}
}
3. Example Request:
To access version 1.0, set the Accept header as:
GET /api/products
Accept: application/vnd.myapi.v1+json
Conclusion
Query String Versioning: The version is passed as a query string parameter (e.g., ?api-version=1.0 ).
URL Path Versioning: The version is included as part of the URL path (e.g., /api/v1/products ).
Header Versioning: The version is specified in the request header (e.g., X-API-Version: 1.0 ).
Media Type Versioning: The version is specified in the Accept header (e.g.,
application/vnd.myapi.v1+json ).
These versioning strategies provide flexibility in how your API exposes different versions to clients, helping you
maintain backward compatibility while evolving your API.
E-Commerce Real-Time Application Development using ASP.NET Core Web API
Developing an E-Commerce application using ASP.NET Core Web API requires building a system that handles
products, orders, users, payment gateways, and various business logic. This guide walks through the steps to build a
simple E-Commerce platform with real-time features using ASP.NET Core Web API.
Key Features of an E-Commerce API
1. User Management: Authentication, Authorization, and User Profile.
2. Product Management: Add, Edit, Delete Products, and retrieve product information.
3. Cart Management: Add products to the shopping cart, view, and update cart items.
4. Order Management: Create, view, and update orders, including processing payments.
5. Payment Gateway Integration: Handle payment processes (mock or actual).
6. Real-Time Notifications: Notify users when their order is confirmed or when there are updates (using SignalR or
another method).
Steps to Build E-Commerce Application with ASP.NET Core Web API
1. Set Up Project
1. Create a New ASP.NET Core Web API Project: You can create a new project using Visual Studio or CLI.
dotnet new webapi -n ECommerceApp bash
cd ECommerceApp
2. Database Design
A basic E-Commerce system may have the following entities:
User: Users can register, log in, and manage their profiles.
Product: Store products available for purchase.
Cart: Store products added by users before checkout.
Order: Store orders once the user completes a purchase.
Payment: Handle payment transactions.
Entities:
User Entity:
public class User csharp
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string PasswordHash { get; set; }
public string Role { get; set; }
}
Product Entity:
public class Product csharp
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public int StockQuantity { get; set; }
}
Order Entity:
public class Order csharp
{
public int Id { get; set; }
public int UserId { get; set; }
public DateTime OrderDate { get; set; }
public string Status { get; set; } // "Pending", "Confirmed", "Shipped", "Deliv
ered"
public decimal TotalAmount { get; set; }
public List<OrderItem> OrderItems { get; set; }
}
services.AddControllers();
services.AddDbContext<ECommerceDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnectio
n")));
services.AddSwaggerGen();
}
[HttpPost]
public IActionResult CreateProduct([FromBody] Product product)
{
_context.Products.Add(product);
_context.SaveChanges();
return CreatedAtAction(nameof(GetProducts), new { id = product.Id }, produc
t);
}
}
Cart Controller:
[ApiController] csharp
[Route("api/cart")]
public class CartController : ControllerBase
{
private readonly ECommerceDbContext _context;
[HttpGet]
public IActionResult GetCartItems(int userId)
{
var cartItems = _context.CartItems.Where(c => c.UserId == userId).ToList();
return Ok(cartItems);
}
[HttpPost]
public IActionResult AddToCart([FromBody] CartItem item)
{
_context.CartItems.Add(item);
_context.SaveChanges();
return Ok(item);
}
}
[HttpPost]
public IActionResult PlaceOrder([FromBody] Order order)
{
_context.Orders.Add(order);
_context.SaveChanges();
return Ok(order);
}
[HttpGet("{id}")]
public IActionResult GetOrder(int id)
{
var order = _context.Orders.Include(o => o.OrderItems).FirstOrDefault(o =>
o.Id == id);
if (order == null)
return NotFound();
return Ok(order);
}
}
Hotel:
public class Hotel csharp
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public string Description { get; set; }
public decimal Rating { get; set; }
}
RoomType:
public class RoomType csharp
{
public int Id { get; set; }
public string Name { get; set; } // "Single", "Double", "Suite"
public decimal Price { get; set; }
}
Room:
public class Room csharp
{
public int Id { get; set; }
public int HotelId { get; set; }
public int RoomTypeId { get; set; }
public bool IsAvailable { get; set; }
public decimal Price { get; set; }
public int Capacity { get; set; }
}
Amenity:
public class Amenity csharp
{
public int Id { get; set; }
public string Name { get; set; } // "Wi-Fi", "Breakfast", "Pool"
}
RoomAmenity:
public class RoomAmenity csharp
{
public int RoomId { get; set; }
public int AmenityId { get; set; }
}
Booking:
public class Booking csharp
{
public int Id { get; set; }
public int UserId { get; set; }
public int RoomId { get; set; }
public DateTime CheckInDate { get; set; }
public DateTime CheckOutDate { get; set; }
public string Status { get; set; } // "Confirmed", "Pending", "Cancelled"
}
2. Install Necessary NuGet Packages: Install packages for Entity Framework Core and Swagger (for API
documentation).
dotnet add package Microsoft.EntityFrameworkCore.SqlServer bash
dotnet add package Swashbuckle.AspNetCore
[HttpPost("register")]
public IActionResult Register([FromBody] RegisterModel model)
{
// Implement user registration logic here
// Hash password, save user, etc.
return Ok();
}
[HttpPost("login")]
public IActionResult Login([FromBody] LoginModel model)
{
// Implement login logic and return JWT token
return Ok(new { Token = "your_generated_token" });
}
}
[HttpGet]
public IActionResult GetRoomTypes()
{
var roomTypes = _context.RoomTypes.ToList();
return Ok(roomTypes);
}
}
[HttpGet]
public IActionResult GetRooms(int hotelId)
{
var rooms = _context.Rooms.Where(r => r.HotelId == hotelId).ToList();
return Ok(rooms);
}
}
[HttpGet]
public IActionResult GetAmenities()
{
var amenities = _context.Amenities.ToList();
return Ok(amenities);
}
}
[HttpPost]
public IActionResult BookRoom([FromBody] BookingModel model)
{
var booking = new Booking
{
UserId = model.UserId,
RoomId = model.RoomId
---
- **Cancel a Booking**:
Users can cancel their existing booking.
```csharp
[ApiController]
[Route("api/cancellations")]
public class CancellationsController : ControllerBase
{
private readonly HotelBookingDbContext _context;
[HttpPost]
public IActionResult CancelBooking([FromBody] CancellationModel model)
{
var booking = _context.Bookings.FirstOrDefault(b => b.Id == model.BookingId &&
b.UserId == model.UserId);
if (booking == null || booking.Status == "Cancelled")
return BadRequest("Booking cannot be cancelled.");
booking.Status = "Cancelled";
_context.SaveChanges();
return Ok(booking);
}
}
Conclusion
By following the above modules and steps, you can create a complete Hotel Booking System using ASP.NET Core
Web API. This application can be expanded to include features like payment integration, reviews, discounts, and
user notifications using tools like SignalR. Each module can be independently scaled, and the application
architecture will support a real-time hotel booking solution.
Introduction to Unit Testing in ASP.NET Core
Unit testing is an essential practice in software development that allows you to verify the behavior of individual units of
code (such as methods or functions). In ASP.NET Core, unit testing is crucial for ensuring that your Web API behaves
as expected under different conditions, without requiring a real server or database.
Unit tests focus on testing a small, isolated part of the application, typically a method or a service, ensuring that it
produces the expected results.
In ASP.NET Core, you can perform unit testing using various testing frameworks, such as xUnit, NUnit, and MSTest.
xUnit is one of the most popular frameworks in the .NET ecosystem due to its simplicity, extensibility, and good
integration with ASP.NET Core.
Unit Testing in ASP.NET Core Web API using xUnit Framework
1. Create a Unit Test Project: First, create a unit test project in the same solution as your ASP.NET Core Web API
project. In Visual Studio, you can add a new xUnit Test Project.
Using the .NET CLI:
dotnet new xunit -n MyApi.Tests bash
cd MyApi.Tests
dotnet add reference ../MyApi/MyApi.csproj
2. Install Necessary NuGet Packages: Install xUnit, Moq (for mocking dependencies), and
Microsoft.AspNetCore.Mvc.Testing for integration tests.
dotnet add package xunit bash
dotnet add package Moq
dotnet add package Microsoft.AspNetCore.Mvc.Testing
dotnet add package FluentAssertions
3. Basic Unit Test for a Controller Action: Let's say you have a BookingsController in your Web API that has
a method GetBookingById . You want to write a unit test for that method.
Controller (BookingsController.cs):
[ApiController] csharp
[Route("api/[controller]")]
public class BookingsController : ControllerBase
{
private readonly IBookingService _bookingService;
[HttpGet("{id}")]
public IActionResult GetBookingById(int id)
{
var booking = _bookingService.GetBookingById(id);
if (booking == null)
return NotFound();
return Ok(booking);
}
}
public BookingsControllerTests()
{
_mockBookingService = new Mock<IBookingService>();
_controller = new BookingsController(_mockBookingService.Object);
}
[Fact]
public void GetBookingById_ReturnsNotFound_WhenBookingDoesNotExist()
{
// Arrange
_mockBookingService.Setup(service => service.GetBookingById(It.IsAny<int>
())).Returns((Booking)null);
// Act
var result = _controller.GetBookingById(1);
// Assert
Assert.IsType<NotFoundResult>(result);
}
[Fact]
public void GetBookingById_ReturnsOk_WhenBookingExists()
{
// Arrange
var booking = new Booking { Id = 1, Name = "Test Booking" };
_mockBookingService.Setup(service => service.GetBookingById(1)).Returns(boo
king);
// Act
var result = _controller.GetBookingById(1);
// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var returnBooking = Assert.IsType<Booking>(okResult.Value);
Assert.Equal(1, returnBooking.Id);
}
}
4. Running the Unit Test: You can run the unit tests using Visual Studio Test Explorer or by using the .NET CLI:
dotnet test bash
// Assert
Assert.Equal(expected, result);
}
}
In this example, the test method AddNumbers_ReturnsCorrectSum runs three times with different inputs.
[HttpGet]
public async Task<IActionResult> GetBookings()
{
var bookings = await _bookingService.GetBookingsAsync();
return Ok(bookings);
}
}
Unit Test for Asynchronous Method:
public class BookingsControllerTests csharp
{
private readonly BookingsController _controller;
private readonly Mock<IBookingService> _mockBookingService;
public BookingsControllerTests()
{
_mockBookingService = new Mock<IBookingService>();
_controller = new BookingsController(_mockBookingService.Object);
}
[Fact]
public async Task GetBookings_ReturnsOk_WhenBookingsExist()
{
// Arrange
var bookings = new List<Booking> { new Booking { Id = 1, Name = "Test Booki
ng" } };
_mockBookingService.Setup(service => service.GetBookingsAsync()).ReturnsAsy
nc(bookings);
// Act
var result = await _controller.GetBookings();
// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var returnBookings = Assert.IsType<List<Booking>>(okResult.Value);
Assert.Single(returnBookings);
}
}
Notice how GetBookings is an asynchronous method ( Task<IActionResult> ), and the unit test uses
async and await to test the controller action.
2. Example of Fluent Assertions: Here’s how you can use FluentAssertions to make assertions more fluent in your
unit tests:
public class BookingsControllerTests csharp
{
private readonly BookingsController _controller;
private readonly Mock<IBookingService> _mockBookingService;
public BookingsControllerTests()
{
_mockBookingService = new Mock<IBookingService>();
_controller = new BookingsController(_mockBookingService.Object);
}
[Fact]
public void GetBookingById_ReturnsCorrectBooking_WhenBookingExists()
{
// Arrange
var booking = new Booking { Id = 1, Name = "Test Booking" };
_mockBookingService.Setup(service => service.GetBookingById(1)).Returns(boo
king);
// Act
var result = _controller.GetBookingById(1);
// Assert
var okResult = result.Should().BeOfType<OkObjectResult>().Subject;
var returnBooking = okResult.Value.Should().BeAssignableTo<Booking>().Subje
ct;
returnBooking.Id.Should().Be(1);
returnBooking.Name.Should().Be("Test Booking");
}
}
In this example:
Should().BeOfType<OkObjectResult>() checks the result type.
Should().BeAssignableTo<Booking>() ensures the result is of the correct type.
Conclusion
By using xUnit for unit testing in ASP.NET Core Web API, you can ensure that your API is thoroughly tested and
behaves as expected in various scenarios. The ability to write asynchronous tests, use Fluent Assertions, and apply
Theory for parameterized tests allows for efficient,
Minimal API in ASP.NET Core
In ASP.NET Core, Minimal APIs provide a lightweight and simplified way to build HTTP APIs with less boilerplate code.
They allow you to define API routes with minimal setup, using the Program.cs file rather than creating a controller
class.
Minimal APIs are suitable for microservices or small applications where you don't need the overhead of controllers,
and you want to build APIs quickly.
How to Create a Minimal API in ASP.NET Core
1. Create a New ASP.NET Core Project: Using the .NET CLI, create a new web API project:
dotnet new web -n MinimalApiDemo bash
cd MinimalApiDemo
2. Define Endpoints: In Program.cs , you can define minimal API routes directly:
var builder = WebApplication.CreateBuilder(args); csharp
var app = builder.Build();
app.Run();
Alternatively, you can use Exception Middleware for centralized error handling:
app.UseExceptionHandler("/error"); csharp
2. Logging: To enable logging in Minimal APIs, you can use the built-in logging functionality provided by ASP.NET
Core.
var logger = builder.Services.GetRequiredService<ILogger<Program>>(); csharp
app.MapGet("/log", () =>
{
logger.LogInformation("This is an information log.");
return Results.Ok("Logged an info message.");
});
By default, ASP.NET Core will log to the console, but you can configure it to log to other destinations like a file or
a database.
2. Asynchronous Database Call (with Entity Framework Core): If you're using Entity Framework Core for data
access, you can define async endpoints like this:
app.MapGet("/users", async (ApplicationDbContext db) => csharp
{
var users = await db.Users.ToListAsync();
return Results.Ok(users);
});
app.Use(logFilter);
2. Apply Endpoint Filters: You can apply endpoint filters to specific routes or globally.
csharp
app.MapGet("/secure", [Authorize] () => Results.Ok("This is a secure endpoint."));
4. Generate JWT Tokens: You can also create a route to generate JWT tokens for user login:
app.MapPost("/login", (UserLogin login) => csharp
{
var claims = new[]
{
new Claim(ClaimTypes.Name, login.Username),
new Claim(ClaimTypes.Role, "User")
};
4. Versioning Header: You can also define versioning based on request headers:
app.MapGet("/products", () => new { Version = "1.0" }) csharp
.WithMetadata(new ApiVersionModel("1.0"));
By leveraging Minimal APIs in ASP.NET Core, you can create lightweight, efficient, and simple RESTful APIs, while
integrating advanced features like JWT authentication, error handling, logging, asynchronous programming, and
API versioning easily. This setup is perfect for modern microservice architectures or small-to-medium scale
applications.
Build ASP.NET Core Web API Project From Scratch
Creating an ASP.NET Core Web API from scratch is a straightforward process using the .NET CLI or Visual Studio.
Here, we'll walk through building a Web API project step by step using the .NET CLI.
Steps to Build an ASP.NET Core Web API Project from Scratch
1. Create a New ASP.NET Core Web API Project
Open a terminal and use the .NET CLI to create a new Web API project:
dotnet new webapi -n MyApi bash
cd MyApi
This creates a folder called MyApi with the default Web API template.
2. Run the Application
To run the application:
dotnet run bash
You should see the API running on http://localhost:5000 or a similar URL. The default template includes a
basic WeatherForecast API.
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.MapControllers();
}
}
2. Program.cs (For older versions, you would use this to call the Startup class):
public class Program csharp
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
Starting with ASP.NET Core 6, the configuration has been streamlined, and we now have everything inside
Program.cs .
namespace MyApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
// In-memory products list for demonstration
private static readonly List<string> Products = new List<string>
{
"Product1", "Product2", "Product3"
};
// GET: api/products
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return Ok(Products);
}
// GET: api/products/1
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
if (id < 0 || id >= Products.Count)
{
return NotFound();
}
return Ok(Products[id]);
}
}
}
Why .NET 6?
.NET 6 is a Long-Term Support (LTS) release of the .NET platform, and it brings several important improvements and
new features to help developers build modern applications efficiently.
Here’s why .NET 6 is a good choice for building ASP.NET Core Web APIs:
1. Simplified Hosting Model: .NET 6 introduces the minimal API model, which simplifies the process of creating
and hosting APIs by reducing the need for multiple files like Startup.cs and Program.cs .
The simplified setup is especially useful for smaller applications or microservices that don't require complex
configurations.
2. Unified Platform: .NET 6 unifies .NET Framework, .NET Core, and Xamarin into a single platform, which makes it
easier to develop applications across different platforms (Windows, macOS, Linux, Android, iOS, etc.).
3. Performance Improvements:
.NET 6 brings significant performance improvements over previous versions of .NET Core, especially in
terms of web APIs and server-side applications.
It has faster startup times, reduced memory usage, and improved throughput.
4. Cross-Platform Support: .NET 6 provides full cross-platform support, so you can develop and deploy
applications on Windows, Linux, and macOS without any compatibility issues.
5. C# 10 Features:
.NET 6 includes C# 10, which brings features like global using directives, file-scoped namespaces, and
improved pattern matching that help make your code cleaner and more concise.
6. Long-Term Support (LTS):
.NET 6 is an LTS release, meaning Microsoft will provide support and security updates for at least three
years (until November 2024).
7. Integration with Latest Libraries: .NET 6 integrates the latest libraries for technologies like Entity Framework
Core, SignalR, gRPC, and Blazor. This ensures that you can build modern applications with the most up-to-date
tools and frameworks.
8. Built-in Tools for Modern Development:
Hot Reload for rapid development.
Minimal API for quick API development.
Improved Diagnostic tools.
Better Blazor support for client-side development.
9. Better Cloud Integration:
With .NET 6, cloud services and microservices are made easier, as it includes libraries and tools to integrate
with popular cloud providers like Azure, AWS, and Google Cloud.
Summary
1. Build ASP.NET Core Web API: Create your Web API from scratch using the dotnet new webapi command.
2. Add Web Host Builder: In .NET 6, configure the Web Host in Program.cs to set up the application.
3. Configure Startup Class: In earlier versions, Startup.cs handled configuration, but now it's done directly in
Program.cs for simplicity.
4. Adding Controller: Create a controller class with [ApiController] to handle HTTP requests.
5. Why .NET 6: The latest LTS version provides performance improvements, simplicity, cross-platform support, and
enhanced tools for modern web application development.
By leveraging .NET 6, developers can build faster, cleaner, and more efficient Web APIs with minimal overhead.
Middleware in ASP.NET Core Web API
Middleware in ASP.NET Core refers to components that are used to handle requests and responses in a pipeline. They
are invoked in the order they are added to the pipeline in the Configure method of the Startup class (or directly
in Program.cs for .NET 6+). Middleware can handle things like authentication, logging, error handling, request
modification, and more.
Each piece of middleware is responsible for either passing the request to the next middleware in the pipeline or
handling the request and returning a response.
Core Concepts of Middleware
1. Request Handling: Middleware handles incoming requests and can modify the request before passing it along to
the next middleware in the pipeline.
2. Response Handling: Middleware can also modify the response before sending it back to the client.
3. Order of Execution: Middleware is executed in the order it is configured. This order is important, especially for
authentication, logging, and exception handling.
Run, Use, and Next Method in ASP.NET Core
ASP.NET Core provides three primary methods to work with middleware: Use , Run , and Map . These methods are
part of the IApplicationBuilder class, and they allow you to define and configure how middleware behaves in the
request-response pipeline.
1. Use Method
The Use method is used to add middleware components to the pipeline. Middleware added with Use must
call next() to pass the request to the next middleware in the pipeline.
Example:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) csharp
{
app.Use(async (context, next) =>
{
// Custom logic before request is passed to the next middleware
Console.WriteLine("Before Request");
The next.Invoke() method is necessary for passing control to the next middleware.
This pattern is often used for logging, authentication, and error handling.
2. Run Method
The Run method is a terminal middleware, meaning it doesn’t pass control to the next middleware. Once Run
is called, it finishes the request pipeline processing.
Example:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) csharp
{
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello from Run middleware!");
});
}
The Run method is useful when you need to handle a request completely, without invoking other
middleware after it.
3. Next Method
The next method is used within custom middleware to pass control to the next middleware in the pipeline. It is
often used in the Use method as shown above.
app.Use(async (context, next) => csharp
{
// Perform something before
await next.Invoke(); // Call the next middleware in the pipeline
// Perform something after
});
Path-based branching: In the above example, requests to /admin will be handled by the /admin
middleware, and requests to /user will be handled by the /user middleware. All other requests will be
handled by the final Run middleware.
Custom Middleware in ASP.NET Core
Custom middleware in ASP.NET Core is a powerful feature that lets you define your own request and response
processing logic.
You can create custom middleware by defining a class with an Invoke or InvokeAsync method that takes an
HttpContext object and a next delegate.
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
The UseMiddleware method adds your custom middleware to the pipeline.
The middleware logic will be executed for every request that comes into the application, allowing you to
modify the request or response as needed.
Example of Logging Middleware:
public class RequestLoggingMiddleware csharp
{
private readonly RequestDelegate _next;
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Summary
Middleware: Handles HTTP requests and responses, typically used for authentication, logging, exception
handling, etc.
Use: Adds middleware that invokes the next middleware in the pipeline.
Run: Adds terminal middleware that does not call the next middleware.
Map: Branches the pipeline based on a specific request condition (e.g., request path).
Custom Middleware: Allows you to define custom logic to handle requests and responses. You can create a
custom class implementing logic in the InvokeAsync method and add it to the pipeline using
UseMiddleware .
This flexibility in ASP.NET Core Web API's middleware system allows you to customize and extend the request-
processing pipeline to suit your needs.
Microservices using ASP.NET Core
Microservices is an architectural style that structures an application as a collection of loosely coupled, independently
deployable services. Each microservice typically focuses on a single business capability, is independently scalable,
and can be developed using different technologies. ASP.NET Core provides a robust framework to build
microservices with features like high performance, cross-platform support, and modular architecture.
2. Service Communication
Microservices communicate with each other through HTTP or messaging systems like RabbitMQ, Kafka, etc. In this
example, we’ll use HTTP for inter-service communication.
RESTful APIs: Each microservice exposes a set of REST APIs that other services can consume.
Example of an API for a Product Service:
[ApiController] csharp
[Route("api/products")]
public class ProductsController : ControllerBase
{
private readonly IProductService _productService;
[HttpGet]
public async Task<IActionResult> GetAllProducts()
{
var products = await _productService.GetProductsAsync();
return Ok(products);
}
}
For inter-service communication, you can use HttpClient to call the APIs of other services:
public class OrderService csharp
{
private readonly HttpClient _httpClient;
In the Startup.cs file of each service, you can configure the database connection for the specific service:
public void ConfigureServices(IServiceCollection services) csharp
{
services.AddDbContext<ProductContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("ProductDatabase")));
}
4. Service Discovery
In a microservices environment, services need to find each other to communicate. There are multiple ways to handle
service discovery:
Static URLs: Hardcode the URLs of the services (not ideal in dynamic environments).
Service Registry: Use tools like Consul, Eureka, or HashiCorp Vault for dynamic service discovery.
Reverse Proxy: Use a reverse proxy like Nginx or Kong to manage requests to microservices.
For ASP.NET Core, you can integrate with tools like Consul for service discovery.
5. API Gateway
An API Gateway serves as a single entry point for the client to interact with microservices. It handles routing requests
to the appropriate microservices and can also provide additional functionality like rate limiting, load balancing, and
authentication.
For an API Gateway, you can use Ocelot with ASP.NET Core.
dotnet add package Ocelot bash
6. Security in Microservices
To secure communication between services, you can implement JWT Authentication. This ensures that only
authorized users or services can interact with your APIs.
Example of JWT authentication:
public void ConfigureServices(IServiceCollection services) csharp
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://your-identity-provider.com";
options.Audience = "microservices-api";
});
services.AddAuthorization();
}
You can also implement OAuth 2.0, Role-Based Access Control (RBAC), or API Key Authentication depending on
your use case.
7. Dockerizing Microservices
Microservices are typically containerized using Docker to achieve portability and scalability. You can create a
Dockerfile for each service to build its container.
You can then build and run the Docker containers for each microservice and the API Gateway (if used).
Conclusion
Building microservices using ASP.NET Core is a powerful approach for modern, scalable, and maintainable
applications. By decomposing a monolithic application into smaller, self-contained services, you gain flexibility,
independent scaling, and improved fault tolerance. However, building and managing microservices comes with
challenges, such as handling inter-service communication, managing data consistency, and ensuring security.
By leveraging the tools and frameworks within the ASP.NET Core ecosystem, you can build high-performance
microservices that are easy to maintain and scale.
Here is a comprehensive guide to ASP.NET Core Web API Interview Questions and Answers at various levels:
2. What is the difference between ASP.NET Web API and ASP.NET Core Web API?
Answer:
ASP.NET Web API is a framework for building HTTP-based services in the traditional ASP.NET framework (older,
Windows-only).
ASP.NET Core Web API is a modern, cross-platform framework built on ASP.NET Core, which works on
Windows, Linux, and macOS. It is more lightweight and faster, with improved performance and better support for
dependency injection, modularity, and configuration.
[HttpGet]
public IActionResult GetProducts()
{
var products = _productService.GetAllProducts();
return Ok(products);
}
}
3. What is the difference between IActionResult and ActionResult<T> in ASP.NET Core Web API?
Answer:
IActionResult is a base interface that represents the result of an action method in a controller. It is flexible
and can return different types of responses (e.g., Ok() , BadRequest() , NotFound() ).
ActionResult<T> is a specialized type that allows returning a strongly typed response, combining
IActionResult and a value. It is useful when you need to return data along with a status code.
Example:
// IActionResult csharp
public IActionResult GetProduct(int id) { ... }
// ActionResult<T>
public ActionResult<Product> GetProduct(int id) { ... }
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
ASP.NET Core Web API Advanced Interview Questions and Answers
1. What is the difference between AddSingleton , AddScoped , and AddTransient in ASP.NET Core?
Answer:
AddSingleton : The service is created once and shared across all requests. It is a singleton instance.
AddScoped : The service is created once per request (per scope). It is shared within the same request but not
across requests.
AddTransient : A new instance of the service is created every time it is requested.
Example:
services.AddSingleton<ISingletonService, SingletonService>(); csharp
services.AddScoped<IScopedService, ScopedService>();
services.AddTransient<ITransientService, TransientService>();
2. What is CORS, and how do you enable it in ASP.NET Core Web API?
Answer:
CORS (Cross-Origin Resource Sharing) is a security feature that allows or restricts resources from being requested
from another domain. It is useful when you need to allow external clients (e.g., a frontend on a different domain) to
interact with your API.
To enable CORS:
1. Define CORS policies in Startup.cs :
public void ConfigureServices(IServiceCollection services) csharp
{
services.AddCors(options =>
{
options.AddPolicy("AllowAll", builder =>
{
builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
});
});
}
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapRazorPages();
});
}
5. What is Dependency Injection, and how is it used in ASP.NET Core Web API?
Answer:
Dependency Injection (DI) is a design pattern where a class receives its dependencies (services, data, etc.) rather
than creating them internally. In ASP.NET Core, DI is built into the framework and allows services like database
contexts, logging, and business logic to be injected into controllers, middleware, or other components.
Example:
public class ProductController : ControllerBase csharp
{
private readonly IProductService _productService;
In Startup.cs:
public void ConfigureServices(IServiceCollection services) csharp
{
services.AddScoped<IProductService, ProductService>();
}
These questions and answers will help you prepare for ASP.NET Core Web API interviews at various experience
levels, from beginners to advanced.