100 .NET and ASP.NET Core Interview Questions and Answers
Interview preparation · Technical guide
.NET and ASP.NET Core Interview Questions and Answers
Modern .NET and ASP.NET Core interview material. Legacy .NET Core/Startup terminology is placed in context of current .NET hosting, EF Core, and security guidance.
Examples are independent teaching snippets and may require application types, imports, packages, schema, and configuration. Framework behavior is version-dependent. Corrections address identified issues; the complete source code collection has not been compiled or integration-tested.
1. Explain the differences between .NET Framework, .NET Core, and .NET 5+
.NET Framework is the original Windows-only implementation, maintained mainly for existing applications. .NET Core was the cross-platform implementation through 3.1. Starting with .NET 5, the product is named .NET; .NET 5+ is the continuing cross-platform implementation. “.NET Core” is therefore historical terminology for new applications. .NET Standard describes an API contract, not a runtime. Reference: What is .NET?.
2. What is the difference between .NET Standard and .NET Core?
.NET Standard
- Specification/Contract defining APIs that must be available
- Version-based (1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 2.0, 2.1)
- Target framework for libraries
- Ensures compatibility across different .NET implementations
.NET Core
- Implementation of .NET Standard
- Runtime and framework that implements the standard
- Platform-specific optimizations
- Actual execution environment
Coding Example - Targeting .NET Standard:
<!-- Library project targeting .NET Standard 2.1 -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
</Project>
<!-- Application targeting .NET 6 -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
</Project>
3. Explain the .NET Core runtime and its components
Core Components:
- CoreCLR - Common Language Runtime
- CoreFX - Base Class Library
- Roslyn - Compiler platform
- Runtime Host - Application bootstrap
Coding Example - Runtime Information:
using System.Runtime.InteropServices;
// Get runtime information
Console.WriteLine($"Runtime: {RuntimeInformation.FrameworkDescription}");
Console.WriteLine($"OS: {RuntimeInformation.OSDescription}");
Console.WriteLine($"Architecture: {RuntimeInformation.OSArchitecture}");
Console.WriteLine($"Process Architecture: {RuntimeInformation.ProcessArchitecture}");
// Check if running in container
bool isContainerized = Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") == "true";
Console.WriteLine($"Containerized: {isContainerized}");
4. What are the differences between .NET Core and .NET Framework?
| Aspect | .NET Framework | .NET Core |
|---|---|---|
| Platform | Windows-only | Cross-platform |
| Deployment | Machine-wide | Self-contained/Side-by-side |
| Performance | Slower startup | Faster startup |
| Memory | Higher memory usage | Lower memory usage |
| Containerization | Limited support | Excellent support |
| Microservices | Not optimized | Optimized |
Coding Example - Performance Comparison:
// .NET Core optimized code
public class OptimizedService
{
private readonly ILogger<OptimizedService> _logger;
public OptimizedService(ILogger<OptimizedService> logger)
{
_logger = logger;
}
// Value types for better performance
public ValueTask<Result> ProcessAsync(ReadOnlyMemory<byte> data)
{
// Efficient memory usage
return ValueTask.FromResult(Result.Success());
}
}
5. Explain the .NET Core CLI and its commands
Essential CLI Commands:
Create new projects
dotnet new webapi -n MyApi dotnet new classlib -n MyLibrary dotnet new console -n MyConsole
Build and run
dotnet build dotnet run dotnet run --environment Development
Package management
dotnet add package Newtonsoft.Json dotnet remove package Newtonsoft.Json dotnet list package
Testing
dotnet test dotnet test --filter "Category=Integration"
Publishing
dotnet publish -c Release -r win-x64 --self-contained dotnet publish -c Release -r linux-x64 --self-contained
Tool management
dotnet tool install -g dotnet-ef dotnet tool list -g
**Coding Example - Custom CLI Tool:**
// Program.cs for custom CLI tool
using System.CommandLine;
var rootCommand = new RootCommand("Sample CLI Tool");
var nameOption = new Option<string>("--name", "Name to greet");
var countOption = new Option<int>("--count", () => 1, "Number of times to greet");
rootCommand.AddOption(nameOption);
rootCommand.AddOption(countOption);
rootCommand.SetHandler((name, count) =>
{
for (int i = 0; i < count; i++)
{
Console.WriteLine($"Hello, {name}!");
}
}, nameOption, countOption);
await rootCommand.InvokeAsync(args);
6. What is the difference between .NET Core and .NET 5/6/7/8?
.NET Core ended with 3.1. .NET 5 unified the product naming, followed by .NET 6, 7, 8, 9, and 10. Select a currently supported LTS or STS release based on your support policy; do not treat .NET Core and .NET 5+ as parallel product lines.
7. Explain the .NET Core project structure and files
Standard Project Structure:
MyProject/
├── src/
│ ├── MyProject.Api/
│ │ ├── Controllers/
│ │ ├── Program.cs
│ │ ├── appsettings.json
│ │ └── MyProject.Api.csproj
│ └── MyProject.Core/
│ ├── Services/
│ ├── Models/
│ └── MyProject.Core.csproj
├── tests/
│ ├── MyProject.Api.Tests/
│ └── MyProject.Core.Tests/
├── docs/
├── .gitignore
├── Directory.Build.props
└── MyProject.sln
Key Configuration Files:
<!-- Directory.Build.props - Shared properties -->
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
<!-- Project file with modern SDK -->
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
</Project>
8. What are the differences between SDK and Runtime?
SDK (Software Development Kit)
- Development tools (compiler, CLI, templates)
- Build-time dependencies
- Larger size (~100MB+)
- Includes runtime
Runtime
- Execution environment only
- Runtime dependencies
- Smaller size (~30-50MB)
- Production deployment
Coding Example - Runtime Detection:
public class RuntimeInfo
{
public static void DisplayRuntimeInfo()
{
Console.WriteLine($"Framework: {RuntimeInformation.FrameworkDescription}");
Console.WriteLine($"Runtime Version: {Environment.Version}");
Console.WriteLine($"OS: {RuntimeInformation.OSDescription}");
Console.WriteLine($"Architecture: {RuntimeInformation.OSArchitecture}");
// Check if SDK is available
bool hasSdk = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_ROOT"));
Console.WriteLine($"SDK Available: {hasSdk}");
}
}
9. Explain the .NET Core dependency injection container
Built-in DI Container Features:
// Program.cs - Service registration
var builder = WebApplication.CreateBuilder(args);
// Singleton - One instance for entire application
builder.Services.AddSingleton<ICacheService, CacheService>();
// Scoped - One instance per request
builder.Services.AddScoped<IUserService, UserService>();
// Transient - New instance every time
builder.Services.AddTransient<IEmailService, EmailService>();
// Factory pattern
builder.Services.AddTransient<IDataService>(provider =>
{
var config = provider.GetRequiredService<IConfiguration>();
var connectionString = config.GetConnectionString("Default");
return new DataService(connectionString);
});
// Options pattern
builder.Services.Configure<EmailSettings>(
builder.Configuration.GetSection("EmailSettings"));
var app = builder.Build();
Advanced DI Patterns:
// Generic service registration
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddRepositories(this IServiceCollection services)
{
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
return services;
}
}
// Conditional registration
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddConditionalServices(
this IServiceCollection services,
IConfiguration configuration)
{
var useRedis = configuration.GetValue<bool>("UseRedis");
if (useRedis)
{
services.AddSingleton<ICacheService, RedisCacheService>();
}
else
{
services.AddSingleton<ICacheService, MemoryCacheService>();
}
return services;
}
}
10. What is the difference between .NET Core and .NET Framework deployment?
.NET Framework Deployment:
- Machine-wide installation
- GAC (Global Assembly Cache)
- XCOPY deployment (limited)
- Windows-specific
.NET Core Deployment:
- Self-contained or Framework-dependent
- Side-by-side installation
- Cross-platform
- Container-friendly
Coding Example - Deployment Configuration:
<!-- Self-contained deployment -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
</Project>
// Runtime-specific code
public class PlatformService
{
public string GetPlatformInfo()
{
#if WINDOWS
return "Windows-specific code";
#elif LINUX
return "Linux-specific code";
#elif OSX
return "macOS-specific code";
#else
return "Unknown platform";
#endif
}
}
Docker Example:
Multi-stage build for .NET Core
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src COPY ["MyApp.csproj", "./"] RUN dotnet restore COPY . . RUN dotnet build -c Release -o /app/build RUN dotnet publish -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime WORKDIR /app COPY --from=build /app/publish . ENTRYPOINT ["dotnet", "MyApp.dll"]
11. Explain the ASP.NET Core Middleware Pipeline
The middleware pipeline in ASP.NET Core is a series of components that process HTTP requests and responses. Each middleware component can perform operations before and after the next component in the pipeline.
Key Concepts: - Request Pipeline: Processes incoming HTTP requests - Response Pipeline: Processes outgoing HTTP responses - Order Matters: Middleware executes in the order they're added - Short-Circuiting: Middleware can end the pipeline early
Example:
public class Startup
{
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Exception handling middleware (should be first)
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
// HTTPS redirection
app.UseHttpsRedirection();
// Static files middleware
app.UseStaticFiles();
// Routing middleware
app.UseRouting();
// Authentication middleware
app.UseAuthentication();
// Authorization middleware
app.UseAuthorization();
// Custom middleware
app.Use(async (context, next) =>
{
// Pre-processing
var startTime = DateTime.UtcNow;
await next(); // Call next middleware
// Post-processing
var duration = DateTime.UtcNow - startTime;
context.Response.Headers.Add("X-Response-Time", duration.TotalMilliseconds.ToString());
});
// Endpoint middleware
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
Custom Middleware Example:
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestLoggingMiddleware> _logger;
public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
_logger.LogInformation($"Request: {context.Request.Method} {context.Request.Path}");
await _next(context);
_logger.LogInformation($"Response: {context.Response.StatusCode}");
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred processing the request");
throw;
}
}
}
// Extension method for easy registration
public static class RequestLoggingMiddlewareExtensions
{
public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestLoggingMiddleware>();
}
}
12. What is the difference between ASP.NET Core and ASP.NET Framework?
| Aspect | ASP.NET Core | ASP.NET Framework |
|---|---|---|
| Cross-Platform | Yes (Windows, Linux, macOS) | Windows only |
| Performance | Significantly faster | Slower due to legacy overhead |
| Dependency Injection | Built-in, first-class citizen | Requires third-party containers |
| Configuration | JSON, environment variables, command line | Web.config XML |
| Hosting | Self-hosted, IIS, Kestrel | IIS only |
| Middleware | Flexible pipeline | HTTP modules and handlers |
| Unified Framework | MVC and Web API unified | Separate MVC and Web API |
| Open Source | Yes | No |
| Cloud-Optimized | Yes, lightweight | No, heavier footprint |
| Dependency Management | NuGet packages | NuGet packages + GAC |
Example - Configuration Comparison:
ASP.NET Framework (Web.config):
<configuration>
<connectionStrings>
<add name="DefaultConnection"
connectionString="Server=.;Database=MyDb;Trusted_Connection=true;" />
</connectionStrings>
<appSettings>
<add key="ApiKey" value="your-api-key" />
</appSettings>
</configuration>
ASP.NET Core (appsettings.json):
{
"ConnectionStrings": {
"DefaultConnection": "Server=.;Database=MyDb;Trusted_Connection=true;"
},
"ApiKey": "your-api-key",
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning"
}
}
}
13. Explain the ASP.NET Core Startup Process
Modern ASP.NET Core commonly uses the minimal hosting model: create WebApplicationBuilder, register services with builder.Services, build the app, configure middleware, map endpoints, then run. The Startup class and Configure/ConfigureServices are supported historical patterns, but are not required in current templates.
14. What are the differences between Configure and ConfigureServices?
| ConfigureServices | Configure |
|---|---|
| Purpose | Register services with DI container |
| Execution | Called once at startup |
| Parameters | IServiceCollection |
| Order | Executes first |
| Scope | Application-level configuration |
ConfigureServices Example:
public void ConfigureServices(IServiceCollection services)
{
// Register services
services.AddDbContext<ApplicationDbContext>();
services.AddScoped<IUserService, UserService>();
services.AddSingleton<ICacheService, CacheService>();
// Configure options
services.Configure<EmailSettings>(Configuration.GetSection("Email"));
// Add framework services
services.AddControllers();
services.AddSwaggerGen();
}
Configure Example:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env,
ILogger<Startup> logger)
{
// Configure middleware pipeline
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
15. Explain ASP.NET Core Dependency Injection and Service Lifetime
ASP.NET Core has a built-in DI container that manages object creation and disposal.
Service Lifetimes:
- Singleton: One instance for the entire application
- Scoped: One instance per HTTP request
- Transient: New instance every time requested
Example:
public interface IEmailService
{
Task SendEmailAsync(string to, string subject, string body);
}
public class EmailService : IEmailService
{
private readonly ILogger<EmailService> _logger;
private readonly Guid _instanceId;
public EmailService(ILogger<EmailService> logger)
{
_logger = logger;
_instanceId = Guid.NewGuid();
_logger.LogInformation($"EmailService instance created: {_instanceId}");
}
public async Task SendEmailAsync(string to, string subject, string body)
{
_logger.LogInformation($"Sending email from instance: {_instanceId}");
// Email sending logic
await Task.CompletedTask;
}
}
// Registration in ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ICacheService, CacheService>();
services.AddScoped<IEmailService, EmailService>();
services.AddTransient<IValidator, Validator>();
}
Usage in Controller:
[ApiController]
[Route("api/[controller]")]
public class EmailController : ControllerBase
{
private readonly IEmailService _emailService;
private readonly ILogger<EmailController> _logger;
public EmailController(IEmailService emailService, ILogger<EmailController> logger)
{
_emailService = emailService;
_logger = logger;
}
[HttpPost("send")]
public async Task<IActionResult> SendEmail([FromBody] EmailRequest request)
{
await _emailService.SendEmailAsync(request.To, request.Subject, request.Body);
return Ok(new { message = "Email sent successfully" });
}
}
16. What is the difference between AddSingleton, AddScoped, and AddTransient?
Transient creates a service each time it is requested. Scoped creates one service per DI scope, commonly each HTTP request. Singleton creates one service for the service provider lifetime. A singleton must be thread-safe if it has shared mutable state and must not directly capture a scoped service. Reference: DI guidelines.
17. Explain ASP.NET Core Routing and Attribute Routing
Routing determines how URLs map to controller actions.
Conventional Routing:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
});
}
Attribute Routing:
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
// GET /api/users
[HttpGet]
public async Task<ActionResult<IEnumerable<User>>> GetUsers()
{
var users = await _userService.GetAllUsersAsync();
return Ok(users);
}
// GET /api/users/5
[HttpGet("{id:int}")]
public async Task<ActionResult<User>> GetUser(int id)
{
var user = await _userService.GetUserByIdAsync(id);
if (user == null)
return NotFound();
return Ok(user);
}
// POST /api/users
[HttpPost]
public async Task<ActionResult<User>> CreateUser([FromBody] CreateUserRequest request)
{
var user = await _userService.CreateUserAsync(request);
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
// PUT /api/users/5
[HttpPut("{id:int}")]
public async Task<IActionResult> UpdateUser(int id, [FromBody] UpdateUserRequest request)
{
await _userService.UpdateUserAsync(id, request);
return NoContent();
}
// DELETE /api/users/5
[HttpDelete("{id:int}")]
public async Task<IActionResult> DeleteUser(int id)
{
await _userService.DeleteUserAsync(id);
return NoContent();
}
// Custom route: GET /api/users/search?query=john
[HttpGet("search")]
public async Task<ActionResult<IEnumerable<User>>> SearchUsers([FromQuery] string query)
{
var users = await _userService.SearchUsersAsync(query);
return Ok(users);
}
// Nested route: GET /api/users/5/orders
[HttpGet("{id:int}/orders")]
public async Task<ActionResult<IEnumerable<Order>>> GetUserOrders(int id)
{
var orders = await _userService.GetUserOrdersAsync(id);
return Ok(orders);
}
}
Route Constraints:
[HttpGet("{id:int:min(1)}")] // Integer with minimum value 1
[HttpGet("{name:alpha}")] // Alphabetic characters only
[HttpGet("{email:email}")] // Email format
[HttpGet("{date:datetime}")] // DateTime format
[HttpGet("{guid:guid}")] // GUID format
[HttpGet("{*path}")] // Catch-all parameter
18. What are the differences between MVC and Web API in ASP.NET Core?
In ASP.NET Core, MVC and Web API are unified, but they serve different purposes:
| Aspect | MVC | Web API |
|---|---|---|
| Purpose | Server-side rendering | Data exchange (JSON/XML) |
| Return Type | Views, Partial Views | JSON, XML, HTTP status codes |
| Content Negotiation | No | Yes |
| Model Binding | Form data, query strings | JSON, XML, form data |
| Validation | Server-side only | Client and server-side |
| CORS | Not typically needed | Often required |
MVC Controller Example:
public class HomeController : Controller
{
private readonly IUserService _userService;
public HomeController(IUserService userService)
{
_userService = userService;
}
// Returns a view
public async Task<IActionResult> Index()
{
var users = await _userService.GetAllUsersAsync();
return View(users);
}
// Returns a partial view for AJAX
public async Task<IActionResult> UserList()
{
var users = await _userService.GetAllUsersAsync();
return PartialView("_UserList", users);
}
// Redirects to another action
public IActionResult About()
{
return RedirectToAction("Contact");
}
// Returns JSON (Web API style)
public async Task<IActionResult> GetUsersJson()
{
var users = await _userService.GetAllUsersAsync();
return Json(users);
}
}
Web API Controller Example:
[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<User>), 200)]
[ProducesResponseType(500)]
public async Task<ActionResult<IEnumerable<User>>> GetUsers()
{
try
{
var users = await _userService.GetAllUsersAsync();
return Ok(users);
}
catch (Exception ex)
{
return StatusCode(500, new { error = "Internal server error" });
}
}
[HttpPost]
[Consumes("application/json")]
[ProducesResponseType(typeof(User), 201)]
[ProducesResponseType(400)]
public async Task<ActionResult<User>> CreateUser([FromBody] CreateUserRequest request)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
var user = await _userService.CreateUserAsync(request);
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
}
19. Explain ASP.NET Core Authentication and Authorization
Authentication verifies who the user is, while Authorization determines what they can access.
Authentication Setup:
public void ConfigureServices(IServiceCollection services)
{
// Add authentication
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
// Add authorization policies
services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
policy.RequireRole("Admin"));
options.AddPolicy("MinimumAge", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(c =>
c.Type == "Age" &&
int.TryParse(c.Value, out var age) &&
age >= 18)));
});
}
JWT Token Service:
public interface IJwtService
{
string GenerateToken(User user);
ClaimsPrincipal ValidateToken(string token);
}
public class JwtService : IJwtService
{
private readonly IConfiguration _configuration;
public JwtService(IConfiguration configuration)
{
_configuration = configuration;
}
public string GenerateToken(User user)
{
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Email, user.Email),
new Claim(ClaimTypes.Role, user.Role),
new Claim("Age", user.Age.ToString())
};
var key = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"],
audience: _configuration["Jwt:Audience"],
claims: claims,
expires: DateTime.UtcNow.AddHours(1),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
public ClaimsPrincipal ValidateToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]);
var validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidIssuer = _configuration["Jwt:Issuer"],
ValidateAudience = true,
ValidAudience = _configuration["Jwt:Audience"],
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
return tokenHandler.ValidateToken(token, validationParameters, out _);
}
}
Authentication Controller:
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly IUserService _userService;
private readonly IJwtService _jwtService;
public AuthController(IUserService userService, IJwtService jwtService)
{
_userService = userService;
_jwtService = jwtService;
}
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginRequest request)
{
var user = await _userService.ValidateUserAsync(request.Username, request.Password);
if (user == null)
return Unauthorized(new { message = "Invalid credentials" });
var token = _jwtService.GenerateToken(user);
return Ok(new { token, user = new { user.Id, user.Username, user.Email, user.Role } });
}
}
Authorization in Controllers:
[ApiController]
[Route("api/[controller]")]
[Authorize] // Require authentication for all actions
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
[HttpGet]
[AllowAnonymous] // Allow unauthenticated access
public async Task<ActionResult<IEnumerable<User>>> GetUsers()
{
var users = await _userService.GetAllUsersAsync();
return Ok(users);
}
[HttpGet("{id}")]
[Authorize(Roles = "Admin,Manager")] // Require specific roles
public async Task<ActionResult<User>> GetUser(int id)
{
var user = await _userService.GetUserByIdAsync(id);
return Ok(user);
}
[HttpPost]
[Authorize(Policy = "AdminOnly")] // Use custom policy
public async Task<ActionResult<User>> CreateUser([FromBody] CreateUserRequest request)
{
var user = await _userService.CreateUserAsync(request);
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
[HttpPut("{id}")]
[Authorize(Policy = "MinimumAge")] // Use age-based policy
public async Task<IActionResult> UpdateUser(int id, [FromBody] UpdateUserRequest request)
{
await _userService.UpdateUserAsync(id, request);
return NoContent();
}
[HttpDelete("{id}")]
[Authorize(Roles = "Admin")] // Admin only
public async Task<IActionResult> DeleteUser(int id)
{
await _userService.DeleteUserAsync(id);
return NoContent();
}
}
20. What is the difference between Authentication and Authorization?
| Authentication | Authorization |
|---|---|
| Purpose | Verifies identity |
| Question | "Who are you?" |
| Timing | Happens first |
| Methods | Username/password, tokens, certificates |
| Scope | User identity |
Authentication Example:
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginRequest request)
{
// Authentication: Verify user credentials
var user = await _userService.ValidateUserAsync(request.Username, request.Password);
if (user == null)
return Unauthorized("Invalid credentials");
// Generate token for authenticated user
var token = _jwtService.GenerateToken(user);
return Ok(new { token });
}
Authorization Example:
[HttpGet("admin/users")]
[Authorize(Roles = "Admin")] // Authorization: Only admins can access
public async Task<ActionResult<IEnumerable<User>>> GetAdminUsers()
{
var users = await _userService.GetAllUsersAsync();
return Ok(users);
}
[HttpGet("profile")]
[Authorize] // Authorization: Any authenticated user can access
public async Task<ActionResult<User>> GetProfile()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var user = await _userService.GetUserByIdAsync(int.Parse(userId));
return Ok(user);
}
Custom Authorization Handler:
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public int MinimumAge { get; }
public MinimumAgeRequirement(int minimumAge)
{
MinimumAge = minimumAge;
}
}
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
var ageClaim = context.User.FindFirst("Age");
if (ageClaim != null &&
int.TryParse(ageClaim.Value, out var age) &&
age >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
// Registration
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy("MinimumAge18", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(18)));
});
services.AddScoped<IAuthorizationHandler, MinimumAgeHandler>();
}
21. Explain the ASP.NET Core configuration system
The ASP.NET Core configuration system is a flexible, hierarchical configuration framework that allows you to read configuration data from multiple sources and bind it to strongly-typed objects.
Key Features: - Multiple configuration providers (JSON, environment variables, command line, etc.) - Hierarchical configuration with override capabilities - Strongly-typed configuration binding - Configuration validation - Dependency injection integration
Basic Setup:
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Configuration is automatically loaded from:
// 1. appsettings.json
// 2. appsettings.{Environment}.json
// 3. Environment variables
// 4. Command line arguments
// 5. User secrets (development only)
var configuration = builder.Configuration;
22. What are the differences between IConfiguration and IOptions?
IConfiguration: - Raw configuration access - String-based key-value pairs - No caching or change notifications - Direct access to configuration values
IOptions: - Strongly-typed configuration objects - Singleton pattern with caching - No change notifications - Better for static configuration
// IConfiguration example
public class WeatherController : ControllerBase
{
private readonly IConfiguration _configuration;
public WeatherController(IConfiguration configuration)
{
_configuration = configuration;
}
public IActionResult Get()
{
var apiKey = _configuration["WeatherApi:ApiKey"];
var baseUrl = _configuration["WeatherApi:BaseUrl"];
return Ok(new { apiKey, baseUrl });
}
}
// IOptions example
public class WeatherApiSettings
{
public string ApiKey { get; set; } = string.Empty;
public string BaseUrl { get; set; } = string.Empty;
}
public class WeatherController : ControllerBase
{
private readonly WeatherApiSettings _settings;
public WeatherController(IOptions<WeatherApiSettings> options)
{
_settings = options.Value;
}
public IActionResult Get()
{
return Ok(new { _settings.ApiKey, _settings.BaseUrl });
}
}
23. Explain configuration providers and their hierarchy
Configuration providers are loaded in a specific order, with later providers overriding earlier ones:
var builder = WebApplication.CreateBuilder(args);
// Configuration providers hierarchy (from lowest to highest priority):
builder.Configuration
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.AddCommandLine(args)
.AddUserSecrets<Program>(optional: true)
.AddAzureKeyVault(new Uri("https://your-keyvault.vault.azure.net/"), new DefaultAzureCredential());
Priority Order (highest to lowest): 1. Command line arguments 2. Environment variables 3. User secrets (development) 4. appsettings.{Environment}.json 5. appsettings.json 6. Azure Key Vault 7. Default values
24. What is the difference between appsettings.json and environment variables?
appsettings.json: - File-based configuration - Version controlled - Human-readable - Environment-specific files - Good for non-sensitive data
Environment Variables: - Runtime configuration - Not version controlled - Platform-specific - Good for sensitive data - Override file-based settings
// appsettings.json
{
"Database": {
"ConnectionString": "Server=localhost;Database=MyApp;Trusted_Connection=true;",
"MaxRetryCount": 3
},
"Logging": {
"LogLevel": {
"Default": "Information"
}
}
}
Environment variables (override appsettings.json)
setx Database__ConnectionString "Server=prod-server;Database=MyApp;User Id=appuser;Password=secret;" setx Database__MaxRetryCount "5" setx Logging__LogLevel__Default "Warning"
25. Explain strongly-typed configuration with IOptions pattern
The IOptions pattern provides type-safe configuration binding with dependency injection:
// Configuration class
public class DatabaseSettings
{
public const string SectionName = "Database";
public string ConnectionString { get; set; } = string.Empty;
public int MaxRetryCount { get; set; } = 3;
public TimeSpan CommandTimeout { get; set; } = TimeSpan.FromSeconds(30);
}
public class EmailSettings
{
public const string SectionName = "Email";
public string SmtpServer { get; set; } = string.Empty;
public int Port { get; set; } = 587;
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public bool EnableSsl { get; set; } = true;
}
// Program.cs - Registration
builder.Services.Configure<DatabaseSettings>(
builder.Configuration.GetSection(DatabaseSettings.SectionName));
builder.Services.Configure<EmailSettings>(
builder.Configuration.GetSection(EmailSettings.SectionName));
// Service usage
public class UserService
{
private readonly DatabaseSettings _dbSettings;
private readonly EmailSettings _emailSettings;
public UserService(
IOptions<DatabaseSettings> dbOptions,
IOptions<EmailSettings> emailOptions)
{
_dbSettings = dbOptions.Value;
_emailSettings = emailOptions.Value;
}
public async Task CreateUserAsync(User user)
{
using var connection = new SqlConnection(_dbSettings.ConnectionString);
// Use _dbSettings.MaxRetryCount for retry logic
// Use _emailSettings for sending welcome email
}
}
26. What are the differences between IOptions, IOptionsSnapshot, and IOptionsMonitor?
IOptions<T>: - Singleton pattern - No change notifications - Configuration loaded once at startup - Good for static configuration
IOptionsSnapshot<T>: - Scoped pattern - Captures current configuration at request time - Supports configuration reloading - Good for per-request configuration
IOptionsMonitor<T>: - Singleton pattern with change notifications - Supports configuration reloading - Can subscribe to configuration changes - Good for long-running services
// IOptions - Static configuration
public class StaticService
{
private readonly DatabaseSettings _settings;
public StaticService(IOptions<DatabaseSettings> options)
{
_settings = options.Value; // Loaded once at startup
}
}
// IOptionsSnapshot - Per-request configuration
public class DynamicService
{
private readonly DatabaseSettings _settings;
public DynamicService(IOptionsSnapshot<DatabaseSettings> options)
{
_settings = options.Value; // Current configuration at request time
}
}
// IOptionsMonitor - Change notifications
public class NotificationService : IHostedService
{
private readonly IOptionsMonitor<DatabaseSettings> _optionsMonitor;
private IDisposable? _changeToken;
public NotificationService(IOptionsMonitor<DatabaseSettings> optionsMonitor)
{
_optionsMonitor = optionsMonitor;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_changeToken = _optionsMonitor.OnChange(settings =>
{
// Handle configuration changes
Console.WriteLine($"Database settings changed: {settings.ConnectionString}");
});
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_changeToken?.Dispose();
return Task.CompletedTask;
}
}
27. Explain configuration validation and custom validators
Configuration validation ensures configuration values meet business requirements:
// Configuration with validation attributes
public class DatabaseSettings
{
[Required]
public string ConnectionString { get; set; } = string.Empty;
[Range(1, 10)]
public int MaxRetryCount { get; set; } = 3;
[Range(typeof(TimeSpan), "00:00:01", "00:05:00")]
public TimeSpan CommandTimeout { get; set; } = TimeSpan.FromSeconds(30);
}
public class EmailSettings
{
[Required]
[Url]
public string SmtpServer { get; set; } = string.Empty;
[Range(1, 65535)]
public int Port { get; set; } = 587;
[Required]
[EmailAddress]
public string Username { get; set; } = string.Empty;
[Required]
[MinLength(8)]
public string Password { get; set; } = string.Empty;
}
// Custom validator
public class DatabaseSettingsValidator : IValidateOptions<DatabaseSettings>
{
public ValidateOptionsResult Validate(string? name, DatabaseSettings options)
{
var errors = new List<string>();
if (string.IsNullOrEmpty(options.ConnectionString))
{
errors.Add("ConnectionString is required");
}
if (!options.ConnectionString.Contains("Database="))
{
errors.Add("ConnectionString must specify a database");
}
if (options.MaxRetryCount <= 0)
{
errors.Add("MaxRetryCount must be greater than 0");
}
return errors.Count > 0
? ValidateOptionsResult.Fail(errors)
: ValidateOptionsResult.Success();
}
}
// Program.cs - Registration with validation
builder.Services.Configure<DatabaseSettings>(
builder.Configuration.GetSection(DatabaseSettings.SectionName))
.ValidateDataAnnotations()
.ValidateOnStart();
builder.Services.Configure<EmailSettings>(
builder.Configuration.GetSection(EmailSettings.SectionName))
.ValidateDataAnnotations()
.ValidateOnStart();
// Register custom validator
builder.Services.AddSingleton<IValidateOptions<DatabaseSettings>, DatabaseSettingsValidator>();
28. What is the difference between configuration and settings?
Configuration: - Application-level settings - Environment-specific - Infrastructure concerns - Examples: connection strings, API endpoints, logging levels
Settings: - User preferences - Feature flags - Business rules - Examples: UI themes, default page sizes, feature toggles
// Configuration (infrastructure)
public class InfrastructureConfig
{
public DatabaseSettings Database { get; set; } = new();
public EmailSettings Email { get; set; } = new();
public LoggingSettings Logging { get; set; } = new();
}
// Settings (business/user preferences)
public class ApplicationSettings
{
public int DefaultPageSize { get; set; } = 20;
public string DefaultTheme { get; set; } = "Light";
public bool EnableNotifications { get; set; } = true;
public FeatureFlags Features { get; set; } = new();
}
public class FeatureFlags
{
public bool EnableAdvancedSearch { get; set; } = false;
public bool EnableExport { get; set; } = true;
public bool EnableAnalytics { get; set; } = false;
}
29. Explain user secrets and sensitive data management
User secrets provide a secure way to store sensitive data during development:
// Development only - stores secrets in user profile
// Right-click project → Manage User Secrets
{
"Database": {
"ConnectionString": "Server=localhost;Database=MyApp;User Id=devuser;Password=devpassword;"
},
"Email": {
"Password": "emailpassword"
},
"ApiKeys": {
"WeatherApi": "your-weather-api-key",
"PaymentApi": "your-payment-api-key"
}
}
// Program.cs - User secrets registration
if (builder.Environment.IsDevelopment())
{
builder.Configuration.AddUserSecrets<Program>();
}
// Production - Use Azure Key Vault or environment variables
if (builder.Environment.IsProduction())
{
builder.Configuration.AddAzureKeyVault(
new Uri("https://your-keyvault.vault.azure.net/"),
new DefaultAzureCredential());
}
// Secure configuration access
public class SecureService
{
private readonly IConfiguration _configuration;
public SecureService(IConfiguration configuration)
{
_configuration = configuration;
}
public string GetConnectionString()
{
// Will get from user secrets in development, Key Vault in production
return _configuration["Database:ConnectionString"] ??
throw new InvalidOperationException("Connection string not configured");
}
}
30. What are the differences between development and production configuration?
Development Configuration: - Detailed logging - User secrets - Debug information - Local resources - Hot reload enabled
Production Configuration: - Minimal logging - Secure secrets management - Performance optimized - External resources - Security hardened
// Program.cs - Environment-specific configuration
var builder = WebApplication.CreateBuilder(args);
// Environment-specific configuration files
builder.Configuration
.AddJsonFile("appsettings.json", optional: false)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true);
// Development-specific settings
if (builder.Environment.IsDevelopment())
{
builder.Configuration.AddUserSecrets<Program>();
builder.Services.AddDeveloperExceptionPage();
// Detailed logging
builder.Logging.AddConsole();
builder.Logging.AddDebug();
builder.Logging.SetMinimumLevel(LogLevel.Debug);
}
// Production-specific settings
if (builder.Environment.IsProduction())
{
// Azure Key Vault for secrets
builder.Configuration.AddAzureKeyVault(
new Uri("https://your-keyvault.vault.azure.net/"),
new DefaultAzureCredential());
// Minimal logging
builder.Logging.AddApplicationInsights();
builder.Logging.SetMinimumLevel(LogLevel.Warning);
// Security headers
builder.Services.AddHsts(options =>
{
options.MaxAge = TimeSpan.FromDays(365);
options.IncludeSubDomains = true;
options.Preload = true;
});
}
// Environment-specific service registration
if (builder.Environment.IsDevelopment())
{
builder.Services.AddSingleton<IEmailService, MockEmailService>();
}
else
{
builder.Services.AddSingleton<IEmailService, SmtpEmailService>();
}
// appsettings.Development.json
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Database": {
"ConnectionString": "Server=localhost;Database=MyApp_Dev;Trusted_Connection=true;"
},
"Email": {
"SmtpServer": "localhost",
"Port": 1025
}
}
// appsettings.Production.json
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Database": {
"ConnectionString": "Server=prod-server;Database=MyApp;User Id=appuser;Password=***;"
},
"Email": {
"SmtpServer": "smtp.company.com",
"Port": 587
}
}
31. Explain the .NET Core dependency injection container
The .NET Core DI container is a built-in IoC (Inversion of Control) container that manages object creation, lifetime, and disposal. It's part of the Microsoft.Extensions.DependencyInjection namespace.
Key Features: - Automatic dependency resolution - Lifetime management - Service registration and resolution - Built-in support for constructor injection
// Basic setup
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
// Register services
services.AddTransient<IEmailService, EmailService>();
services.AddScoped<IUserRepository, UserRepository>();
services.AddSingleton<IConfigurationService, ConfigurationService>();
// Build the container
var serviceProvider = services.BuildServiceProvider();
// Resolve services
var emailService = serviceProvider.GetService<IEmailService>();
32. What are the differences between service lifetimes?
There are three service lifetimes in .NET Core:
Transient
- New instance created every time
- Never shared
- Lightweight, stateless services
services.AddTransient<IEmailService, EmailService>();
// Each call creates a new instance
var service1 = serviceProvider.GetService<IEmailService>();
var service2 = serviceProvider.GetService<IEmailService>();
// service1 != service2
Scoped
- One instance per scope (typically per HTTP request)
- Shared within the same scope
- Database contexts, repositories
services.AddScoped<IUserRepository, UserRepository>();
using (var scope = serviceProvider.CreateScope())
{
var repo1 = scope.ServiceProvider.GetService<IUserRepository>();
var repo2 = scope.ServiceProvider.GetService<IUserRepository>();
// repo1 == repo2 (same scope)
}
Singleton
- One instance for the entire application lifetime
- Shared across all requests
- Configuration, logging, caching
services.AddSingleton<IConfigurationService, ConfigurationService>();
var config1 = serviceProvider.GetService<IConfigurationService>();
var config2 = serviceProvider.GetService<IConfigurationService>();
// config1 == config2 (same instance)
33. Explain service registration and resolution
Service Registration
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Interface to implementation
services.AddTransient<IEmailService, EmailService>();
// Self-registration
services.AddTransient<EmailService>();
// Factory pattern
services.AddTransient<IEmailService>(provider =>
{
var config = provider.GetService<IConfiguration>();
return new EmailService(config.GetConnectionString("Email"));
});
// Multiple implementations
services.AddTransient<IEmailService, SmtpEmailService>();
services.AddTransient<IEmailService, SendGridEmailService>();
}
}
Service Resolution
public class UserController : ControllerBase
{
private readonly IEmailService _emailService;
// Constructor injection (preferred)
public UserController(IEmailService emailService)
{
_emailService = emailService;
}
// Method injection
public async Task<IActionResult> SendEmail(
[FromServices] IEmailService emailService)
{
await emailService.SendAsync("test@example.com", "Hello");
return Ok();
}
}
34. What is the difference between constructor injection and method injection?
Constructor Injection
- Dependencies are injected when the class is instantiated
- Dependencies are required and immutable
- Better for required dependencies
public class UserService
{
private readonly IUserRepository _userRepository;
private readonly IEmailService _emailService;
public UserService(IUserRepository userRepository, IEmailService emailService)
{
_userRepository = userRepository ?? throw new ArgumentNullException(nameof(userRepository));
_emailService = emailService ?? throw new ArgumentNullException(nameof(emailService));
}
public async Task CreateUserAsync(User user)
{
await _userRepository.AddAsync(user);
await _emailService.SendWelcomeEmailAsync(user.Email);
}
}
Method Injection
- Dependencies are injected per method call
- Useful for optional dependencies or when dependencies vary
- Uses
[FromServices]attribute
public class NotificationController : ControllerBase
{
public async Task<IActionResult> SendNotification(
string message,
[FromServices] IEmailService emailService,
[FromServices] ISmsService smsService)
{
// Use services only when needed
if (message.Length > 100)
{
await emailService.SendAsync("admin@example.com", message);
}
else
{
await smsService.SendAsync("+1234567890", message);
}
return Ok();
}
}
35. Explain circular dependencies and how to resolve them
A circular dependency normally indicates responsibilities that need reshaping. Move orchestration into another service, extract a smaller interface, or invert one dependency. Delaying resolution can hide the cycle but retains architectural coupling; do not assume the built-in container automatically supports Lazy<T>.
36. What are the differences between IServiceProvider and IServiceCollection?
IServiceCollection
- Used for registering services
- Part of the configuration phase
- Mutable collection of service descriptors
public void ConfigureServices(IServiceCollection services)
{
// Register services
services.AddTransient<IEmailService, EmailService>();
services.AddScoped<IUserRepository, UserRepository>();
services.AddSingleton<IConfigurationService, ConfigurationService>();
// Build the provider
var serviceProvider = services.BuildServiceProvider();
}
IServiceProvider
- Used for resolving services
- Part of the runtime phase
- Immutable container that resolves dependencies
public class UserController : ControllerBase
{
private readonly IServiceProvider _serviceProvider;
public UserController(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task<IActionResult> GetUser(int id)
{
// Resolve services at runtime
var userRepository = _serviceProvider.GetService<IUserRepository>();
var user = await userRepository.GetByIdAsync(id);
return Ok(user);
}
}
37. Explain custom service registration and factory patterns
Custom Service Registration
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddCustomServices(this IServiceCollection services)
{
// Conditional registration
if (Environment.GetEnvironmentVariable("USE_SENDGRID") == "true")
{
services.AddTransient<IEmailService, SendGridEmailService>();
}
else
{
services.AddTransient<IEmailService, SmtpEmailService>();
}
// Configuration-based registration
services.AddTransient<IEmailService>(provider =>
{
var config = provider.GetService<IConfiguration>();
var emailProvider = config["Email:Provider"];
return emailProvider switch
{
"SendGrid" => new SendGridEmailService(config),
"SMTP" => new SmtpEmailService(config),
_ => throw new NotSupportedException($"Email provider {emailProvider} not supported")
};
});
return services;
}
}
Factory Pattern
public interface IEmailServiceFactory
{
IEmailService Create(string provider);
}
public class EmailServiceFactory : IEmailServiceFactory
{
private readonly IServiceProvider _serviceProvider;
public EmailServiceFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IEmailService Create(string provider)
{
return provider switch
{
"SendGrid" => _serviceProvider.GetService<SendGridEmailService>(),
"SMTP" => _serviceProvider.GetService<SmtpEmailService>(),
_ => throw new NotSupportedException($"Provider {provider} not supported")
};
}
}
// Registration
services.AddTransient<IEmailServiceFactory, EmailServiceFactory>();
services.AddTransient<SendGridEmailService>();
services.AddTransient<SmtpEmailService>();
38. What is the difference between AddScoped and AddTransient?
AddTransient
- Creates a new instance every time the service is requested
- No sharing between consumers
- Suitable for lightweight, stateless services
services.AddTransient<IEmailService, EmailService>();
// In a controller
public class UserController : ControllerBase
{
private readonly IEmailService _emailService1;
private readonly IEmailService _emailService2;
public UserController(IEmailService emailService1, IEmailService emailService2)
{
_emailService1 = emailService1; // New instance
_emailService2 = emailService2; // Different new instance
// _emailService1 != _emailService2
}
}
AddScoped
- Creates one instance per scope (typically per HTTP request)
- Same instance shared within the scope
- Suitable for database contexts, repositories
services.AddScoped<IUserRepository, UserRepository>();
// In a controller
public class UserController : ControllerBase
{
private readonly IUserRepository _userRepository1;
private readonly IUserRepository _userRepository2;
public UserController(IUserRepository userRepository1, IUserRepository userRepository2)
{
_userRepository1 = userRepository1; // Same instance
_userRepository2 = userRepository2; // Same instance
// _userRepository1 == _userRepository2 (within same HTTP request)
}
}
39. Explain service descriptors and their properties
Service descriptors contain metadata about how to create and manage services.
public class ServiceDescriptor
{
public Type ServiceType { get; } // Interface type
public Type ImplementationType { get; } // Concrete type
public ServiceLifetime Lifetime { get; } // Transient, Scoped, Singleton
public object ImplementationInstance { get; } // Singleton instance
public Func<IServiceProvider, object> ImplementationFactory { get; } // Factory method
}
// Manual service descriptor creation
var descriptor = new ServiceDescriptor(
serviceType: typeof(IEmailService),
implementationType: typeof(EmailService),
lifetime: ServiceLifetime.Transient
);
services.Add(descriptor);
// Factory-based descriptor
var factoryDescriptor = new ServiceDescriptor(
serviceType: typeof(IEmailService),
implementationFactory: provider => new EmailService("smtp.example.com"),
lifetime: ServiceLifetime.Singleton
);
services.Add(factoryDescriptor);
40. What are the differences between built-in and custom services?
Built-in Services
- Provided by .NET Core framework
- Automatically registered
- Core functionality
// Built-in services automatically available
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// These are built-in services
services.AddControllers(); // IControllerFactory, IActionInvokerFactory
services.AddDbContext<AppDbContext>(); // DbContext
services.AddAuthentication(); // IAuthenticationService
services.AddAuthorization(); // IAuthorizationService
services.AddLogging(); // ILogger<T>
services.AddConfiguration(); // IConfiguration
}
}
Custom Services
- Created by developers
- Must be manually registered
- Business logic and application-specific functionality
// Custom services
public interface IUserService
{
Task<User> GetUserAsync(int id);
Task CreateUserAsync(User user);
}
public class UserService : IUserService
{
private readonly IUserRepository _userRepository;
private readonly IEmailService _emailService;
public UserService(IUserRepository userRepository, IEmailService emailService)
{
_userRepository = userRepository;
_emailService = emailService;
}
public async Task<User> GetUserAsync(int id)
{
return await _userRepository.GetByIdAsync(id);
}
public async Task CreateUserAsync(User user)
{
await _userRepository.AddAsync(user);
await _emailService.SendWelcomeEmailAsync(user.Email);
}
}
// Registration
services.AddScoped<IUserService, UserService>();
Key Differences
| Aspect | Built-in Services | Custom Services |
|---|---|---|
| Registration | Automatic | Manual |
| Purpose | Framework functionality | Business logic |
| Lifetime | Usually Singleton/Scoped | Varies based on needs |
| Dependencies | Framework-managed | Developer-managed |
| Testing | Mocked by framework | Must be mocked manually |
41. Explain ASP.NET Core middleware and its execution order
Answer: Middleware in ASP.NET Core is software components that are assembled into an application pipeline to handle requests and responses. Each middleware component can perform operations before and after the next component in the pipeline.
Execution Order: Middleware executes in the order they are added to the pipeline, but responses flow back through the pipeline in reverse order.
public class Startup
{
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Middleware execution order:
// 1. Exception handling
app.UseExceptionHandler("/Error");
// 2. HTTPS redirection
app.UseHttpsRedirection();
// 3. Static files
app.UseStaticFiles();
// 4. Routing
app.UseRouting();
// 5. Authentication
app.UseAuthentication();
// 6. Authorization
app.UseAuthorization();
// 7. Endpoints
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
Request Flow:
Request → Middleware 1 → Middleware 2 → Middleware 3 → Endpoint
Response ← Middleware 1 ← Middleware 2 ← Middleware 3 ← Endpoint
42. What are the differences between Use, Map, and MapWhen?
Answer:
Use
- Adds middleware to the pipeline that executes for all requests
- Most common middleware registration method
app.Use(async (context, next) =>
{
// Pre-processing
await next();
// Post-processing
});
Map
- Branches the pipeline based on path matching
- Creates separate middleware pipeline for matched paths
app.Map("/api", apiApp =>
{
apiApp.Use(async (context, next) =>
{
context.Response.Headers.Add("X-API-Version", "1.0");
await next();
});
apiApp.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
});
app.Map("/admin", adminApp =>
{
adminApp.UseAuthentication();
adminApp.UseAuthorization();
adminApp.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
});
MapWhen
- Branches based on custom predicate conditions
- More flexible than Map for complex routing logic
app.MapWhen(context =>
context.Request.Headers.ContainsKey("X-Custom-Header"),
customApp =>
{
customApp.Use(async (context, next) =>
{
// Custom middleware for requests with X-Custom-Header
await next();
});
});
app.MapWhen(context =>
context.Request.ContentType?.Contains("application/json") == true,
jsonApp =>
{
jsonApp.Use(async (context, next) =>
{
// JSON-specific middleware
await next();
});
});
43. Explain custom middleware creation and registration
Answer: Custom middleware can be created using inline delegates or as separate classes.
Inline Middleware
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.Use(async (context, next) =>
{
var startTime = DateTime.UtcNow;
// Pre-processing
context.Response.Headers.Add("X-Request-Start", startTime.ToString());
await next();
// Post-processing
var duration = DateTime.UtcNow - startTime;
context.Response.Headers.Add("X-Request-Duration", duration.TotalMilliseconds.ToString());
});
}
Class-based Middleware
public class RequestTimingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestTimingMiddleware> _logger;
public RequestTimingMiddleware(RequestDelegate next, ILogger<RequestTimingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var startTime = DateTime.UtcNow;
try
{
await _next(context);
}
finally
{
var duration = DateTime.UtcNow - startTime;
_logger.LogInformation("Request {Method} {Path} took {Duration}ms",
context.Request.Method, context.Request.Path, duration.TotalMilliseconds);
}
}
}
// Extension method for easy registration
public static class RequestTimingMiddlewareExtensions
{
public static IApplicationBuilder UseRequestTiming(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestTimingMiddleware>();
}
}
// Registration
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRequestTiming();
// Other middleware...
}
Factory-based Middleware
public class CustomMiddlewareFactory : IMiddlewareFactory
{
private readonly IServiceProvider _serviceProvider;
public CustomMiddlewareFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IMiddleware Create(Type middlewareType)
{
return (IMiddleware)ActivatorUtilities.CreateInstance(_serviceProvider, middlewareType);
}
public void Release(IMiddleware middleware)
{
// Cleanup if needed
}
}
public class CustomMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
// Middleware logic
await next(context);
}
}
44. What is the difference between middleware and filters?
Answer:
Middleware
- Operates at the HTTP pipeline level
- Executes for all requests (unless branched)
- Cannot access MVC-specific context
- Runs before routing
// Middleware - HTTP level
app.Use(async (context, next) =>
{
// Works with HttpContext
context.Response.Headers.Add("X-Middleware", "true");
await next();
});
Filters
- Operate at the MVC/Web API level
- Execute only for controller actions
- Have access to MVC context (ActionContext, ActionDescriptor)
- Run after routing
// Action Filter - MVC level
public class CustomActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// Access to ActionContext, ActionDescriptor
var actionName = context.ActionDescriptor.DisplayName;
context.HttpContext.Response.Headers.Add("X-Action", actionName);
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Post-action processing
}
}
// Registration
services.AddControllers(options =>
{
options.Filters.Add<CustomActionFilter>();
});
Execution Order
Request → Middleware → Routing → Filters → Action → Filters → Middleware → Response
45. Explain middleware ordering and its importance
Answer: Middleware ordering is crucial because each middleware can modify the request/response, and later middleware depends on the state set by earlier middleware.
Critical Ordering Rules
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 1. Exception handling FIRST (catches exceptions from all downstream middleware)
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
// 2. HTTPS redirection (before static files to avoid unnecessary redirects)
app.UseHttpsRedirection();
// 3. Static files (before routing to serve static content efficiently)
app.UseStaticFiles();
// 4. Routing (required for endpoint routing)
app.UseRouting();
// 5. CORS (before authentication/authorization)
app.UseCors("MyPolicy");
// 6. Authentication (before authorization)
app.UseAuthentication();
// 7. Authorization (after authentication)
app.UseAuthorization();
// 8. Custom middleware (after core middleware)
app.UseCustomMiddleware();
// 9. Endpoints LAST
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Common Ordering Mistakes
// WRONG: Authentication after endpoints
app.UseEndpoints(endpoints => endpoints.MapControllers());
app.UseAuthentication(); // This won't work!
// CORRECT: Authentication before endpoints
app.UseAuthentication();
app.UseEndpoints(endpoints => endpoints.MapControllers());
46. What are the differences between app.Use and app.Run?
Answer:
app.Use
- Adds middleware that can call the next middleware in the pipeline
- Can perform pre and post-processing
- Must call
next()to continue the pipeline
app.Use(async (context, next) =>
{
// Pre-processing
context.Response.Headers.Add("X-Pre-Processing", "true");
await next(); // Continue to next middleware
// Post-processing
context.Response.Headers.Add("X-Post-Processing", "true");
});
app.Run
- Adds terminal middleware that ends the pipeline
- Cannot call next middleware
- Always the last middleware in a branch
app.Run(async context =>
{
await context.Response.WriteAsync("Hello World!");
// No next() call - pipeline ends here
});
// This middleware will never execute
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("This won't run!");
await next();
});
Practical Example
app.Map("/api", apiApp =>
{
apiApp.Use(async (context, next) =>
{
// Pre-processing for API requests
context.Response.Headers.Add("X-API", "true");
await next();
});
apiApp.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
});
app.Map("/health", healthApp =>
{
healthApp.Run(async context =>
{
context.Response.StatusCode = 200;
await context.Response.WriteAsync("Healthy");
});
});
47. Explain middleware branching and conditional execution
Answer: Middleware branching allows different middleware pipelines for different request paths or conditions.
Path-based Branching
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Common middleware for all requests
app.Use(async (context, next) =>
{
context.Response.Headers.Add("X-Common", "true");
await next();
});
// Branch for API requests
app.Map("/api", apiApp =>
{
apiApp.Use(async (context, next) =>
{
context.Response.Headers.Add("X-API-Version", "2.0");
await next();
});
apiApp.UseAuthentication();
apiApp.UseAuthorization();
apiApp.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
});
// Branch for admin requests
app.Map("/admin", adminApp =>
{
adminApp.Use(async (context, next) =>
{
// Admin-specific authentication
if (!context.User.IsInRole("Admin"))
{
context.Response.StatusCode = 403;
return;
}
await next();
});
adminApp.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
});
// Default branch
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Conditional Branching with MapWhen
app.MapWhen(context =>
context.Request.Headers.ContainsKey("X-API-Key"),
apiKeyApp =>
{
apiKeyApp.Use(async (context, next) =>
{
var apiKey = context.Request.Headers["X-API-Key"].FirstOrDefault();
if (!ValidateApiKey(apiKey))
{
context.Response.StatusCode = 401;
return;
}
await next();
});
apiKeyApp.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
});
app.MapWhen(context =>
context.Request.ContentType?.Contains("multipart/form-data") == true,
fileUploadApp =>
{
fileUploadApp.Use(async (context, next) =>
{
// File upload specific middleware
context.Response.Headers.Add("X-File-Upload", "true");
await next();
});
fileUploadApp.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
});
Dynamic Branching
public class DynamicMiddleware
{
private readonly RequestDelegate _next;
private readonly IConfiguration _configuration;
public DynamicMiddleware(RequestDelegate next, IConfiguration configuration)
{
_next = next;
_configuration = configuration;
}
public async Task InvokeAsync(HttpContext context)
{
var feature = context.Request.Headers["X-Feature"].FirstOrDefault();
if (feature == "beta" && _configuration.GetValue<bool>("EnableBetaFeatures"))
{
context.Response.Headers.Add("X-Beta-Feature", "enabled");
}
await _next(context);
}
}
48. What is the difference between middleware and HTTP modules?
Answer:
HTTP Modules (ASP.NET Framework)
- Part of the old ASP.NET Framework
- Global application-level components
- Require web.config configuration
- Limited to IIS pipeline
// Old ASP.NET Framework HTTP Module
public class CustomHttpModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest += OnBeginRequest;
context.EndRequest += OnEndRequest;
}
private void OnBeginRequest(object sender, EventArgs e)
{
var application = (HttpApplication)sender;
application.Response.Headers.Add("X-Old-Module", "true");
}
private void OnEndRequest(object sender, EventArgs e)
{
// Cleanup
}
public void Dispose()
{
// Cleanup
}
}
// web.config registration
<system.webServer>
<modules>
<add name="CustomModule" type="MyApp.CustomHttpModule"/>
</modules>
</system.webServer>
Middleware (ASP.NET Core)
- Modern, cross-platform approach
- Configured in code (Startup.cs)
- Works with any hosting model
- More flexible and testable
// ASP.NET Core Middleware
public class CustomMiddleware
{
private readonly RequestDelegate _next;
public CustomMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// Pre-processing
context.Response.Headers.Add("X-New-Middleware", "true");
await _next(context);
// Post-processing
}
}
// Startup.cs registration
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<CustomMiddleware>();
}
Key Differences
| Aspect | HTTP Modules | Middleware |
|---|---|---|
| Platform | ASP.NET Framework only | Cross-platform |
| Configuration | web.config | Code-based |
| Hosting | IIS only | Any hosting model |
| Testing | Difficult | Easy to unit test |
| Performance | Slower | Faster |
| Flexibility | Limited | Highly flexible |
49. Explain middleware for authentication and authorization
Answer:
Authentication Middleware
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Authentication middleware
app.UseAuthentication();
// Authorization middleware
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
// Custom Authentication Middleware
public class CustomAuthMiddleware
{
private readonly RequestDelegate _next;
private readonly IConfiguration _configuration;
public CustomAuthMiddleware(RequestDelegate next, IConfiguration configuration)
{
_next = next;
_configuration = configuration;
}
public async Task InvokeAsync(HttpContext context)
{
var token = context.Request.Headers["Authorization"]
.FirstOrDefault()?.Split(" ").Last();
if (!string.IsNullOrEmpty(token))
{
try
{
var claims = ValidateToken(token);
var identity = new ClaimsIdentity(claims, "Bearer");
context.User = new ClaimsPrincipal(identity);
}
catch
{
// Token validation failed
}
}
await _next(context);
}
private IEnumerable<Claim> ValidateToken(string token)
{
// Token validation logic
return new List<Claim>
{
new Claim(ClaimTypes.Name, "user@example.com"),
new Claim(ClaimTypes.Role, "User")
};
}
}
Role-based Authorization Middleware
public class RoleAuthorizationMiddleware
{
private readonly RequestDelegate _next;
private readonly string _requiredRole;
public RoleAuthorizationMiddleware(RequestDelegate next, string requiredRole)
{
_next = next;
_requiredRole = requiredRole;
}
public async Task InvokeAsync(HttpContext context)
{
if (!context.User.Identity.IsAuthenticated)
{
context.Response.StatusCode = 401;
return;
}
if (!context.User.IsInRole(_requiredRole))
{
context.Response.StatusCode = 403;
return;
}
await _next(context);
}
}
// Extension method
public static class RoleAuthorizationMiddlewareExtensions
{
public static IApplicationBuilder UseRoleAuthorization(
this IApplicationBuilder builder, string role)
{
return builder.UseMiddleware<RoleAuthorizationMiddleware>(role);
}
}
// Usage
app.Map("/admin", adminApp =>
{
adminApp.UseRoleAuthorization("Admin");
adminApp.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
});
JWT Authentication Middleware
public class JwtMiddleware
{
private readonly RequestDelegate _next;
private readonly IConfiguration _configuration;
public JwtMiddleware(RequestDelegate next, IConfiguration configuration)
{
_next = next;
_configuration = configuration;
}
public async Task InvokeAsync(HttpContext context)
{
var token = context.Request.Headers["Authorization"]
.FirstOrDefault()?.Split(" ").Last();
if (token != null)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Secret"]);
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidIssuer = _configuration["Jwt:Issuer"],
ValidateAudience = true,
ValidAudience = _configuration["Jwt:Audience"],
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var userId = jwtToken.Claims.First(x => x.Type == "id").Value;
// Attach user to context
context.Items["User"] = userId;
}
catch
{
// Token validation failed
}
}
await _next(context);
}
}
50. What are the differences between built-in and custom middleware?
Answer:
Built-in Middleware
- Provided by Microsoft
- Well-tested and optimized
- Handle common scenarios
- Part of ASP.NET Core framework
// Built-in middleware examples
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Exception handling
app.UseExceptionHandler("/Error");
// HTTPS redirection
app.UseHttpsRedirection();
// Static files
app.UseStaticFiles();
// Routing
app.UseRouting();
// CORS
app.UseCors("MyPolicy");
// Authentication
app.UseAuthentication();
// Authorization
app.UseAuthorization();
// Response compression
app.UseResponseCompression();
// Response caching
app.UseResponseCaching();
// Endpoints
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Custom Middleware
- Created by developers
- Handle application-specific logic
- Can extend built-in functionality
- More flexible but requires testing
// Custom middleware examples
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestLoggingMiddleware> _logger;
public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var startTime = DateTime.UtcNow;
var originalBodyStream = context.Response.Body;
using var memoryStream = new MemoryStream();
context.Response.Body = memoryStream;
try
{
await _next(context);
memoryStream.Position = 0;
await memoryStream.CopyToAsync(originalBodyStream);
var duration = DateTime.UtcNow - startTime;
_logger.LogInformation(
"Request {Method} {Path} => {StatusCode} in {Duration}ms",
context.Request.Method,
context.Request.Path,
context.Response.StatusCode,
duration.TotalMilliseconds);
}
finally
{
context.Response.Body = originalBodyStream;
}
}
}
public class ApiKeyMiddleware
{
private readonly RequestDelegate _next;
private readonly IConfiguration _configuration;
public ApiKeyMiddleware(RequestDelegate next, IConfiguration configuration)
{
_next = next;
_configuration = configuration;
}
public async Task InvokeAsync(HttpContext context)
{
if (context.Request.Path.StartsWithSegments("/api"))
{
var apiKey = context.Request.Headers["X-API-Key"].FirstOrDefault();
var validApiKey = _configuration["ApiKey"];
if (string.IsNullOrEmpty(apiKey) || apiKey != validApiKey)
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Invalid API Key");
return;
}
}
await _next(context);
}
}
public class PerformanceMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<PerformanceMiddleware> _logger;
public PerformanceMiddleware(RequestDelegate next, ILogger<PerformanceMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var sw = Stopwatch.StartNew();
await _next(context);
sw.Stop();
if (sw.ElapsedMilliseconds > 1000)
{
_logger.LogWarning(
"Slow request: {Method} {Path} took {Duration}ms",
context.Request.Method,
context.Request.Path,
sw.ElapsedMilliseconds);
}
}
}
Comparison Table
| Aspect | Built-in Middleware | Custom Middleware |
|---|---|---|
| Source | Microsoft | Developer |
| Testing | Extensive | Developer responsibility |
| Performance | Optimized | Depends on implementation |
| Maintenance | Microsoft handles | Developer handles |
| Flexibility | Limited | High |
| Use Cases | Common scenarios | Application-specific |
| Documentation | Well-documented | Developer responsibility |
Best Practices for Custom Middleware
// 1. Use extension methods for clean registration
public static class CustomMiddlewareExtensions
{
public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestLoggingMiddleware>();
}
}
// 2. Handle exceptions properly
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in custom middleware");
throw; // Re-throw to let exception middleware handle it
}
}
// 3. Use dependency injection
public class CustomMiddleware
{
private readonly RequestDelegate _next;
private readonly IServiceProvider _serviceProvider;
public CustomMiddleware(RequestDelegate next, IServiceProvider serviceProvider)
{
_next = next;
_serviceProvider = serviceProvider;
}
public async Task InvokeAsync(HttpContext context)
{
using var scope = _serviceProvider.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<IMyService>();
// Use service...
await _next(context);
}
}
51. Explain ASP.NET Core routing and its types
ASP.NET Core routing is the process of mapping incoming HTTP requests to controller actions. It determines which controller and action method should handle a specific URL.
Types of Routing:
1. Conventional Routing (Template-based)
// In Startup.cs or Program.cs
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Read" });
2. Attribute Routing
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult Get() { /* ... */ }
[HttpGet("{id}")]
public IActionResult Get(int id) { /* ... */ }
[HttpPost]
public IActionResult Create([FromBody] Product product) { /* ... */ }
}
3. Minimal API Routing
// In Program.cs
app.MapGet("/products", () => GetProducts());
app.MapGet("/products/{id}", (int id) => GetProduct(id));
app.MapPost("/products", (Product product) => CreateProduct(product));
52. What are the differences between conventional and attribute routing?
| Aspect | Conventional Routing | Attribute Routing |
|---|---|---|
| Location | Configured globally in startup | Applied directly on controllers/actions |
| Flexibility | Less flexible, follows conventions | Highly flexible, custom patterns |
| Maintenance | Centralized but harder to maintain | Distributed but easier to understand |
| Complexity | Simple for standard patterns | Better for complex routing scenarios |
Conventional Routing Example:
// Startup.cs
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
// HomeController.cs
public class HomeController : Controller
{
public IActionResult Index() { /* ... */ } // Maps to /Home/Index
public IActionResult About() { /* ... */ } // Maps to /Home/About
}
Attribute Routing Example:
[Route("api/v1/[controller]")]
public class UsersController : ControllerBase
{
[HttpGet]
public IActionResult GetAll() { /* ... */ } // Maps to /api/v1/users
[HttpGet("{id:int}")]
public IActionResult GetById(int id) { /* ... */ } // Maps to /api/v1/users/123
[HttpPost("register")]
public IActionResult Register([FromBody] User user) { /* ... */ } // Maps to /api/v1/users/register
}
53. Explain route constraints and their usage
Route constraints restrict how URL segments can be matched, ensuring type safety and validation.
Built-in Constraints:
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
// int constraint - only accepts integers
[HttpGet("{id:int}")]
public IActionResult GetById(int id) { /* ... */ }
// int with min/max values
[HttpGet("{id:int:min(1):max(1000)}")]
public IActionResult GetById(int id) { /* ... */ }
// alpha constraint - only letters
[HttpGet("{category:alpha}")]
public IActionResult GetByCategory(string category) { /* ... */ }
// regex constraint
[HttpGet("{code:regex(^[A-Z]{{2}}\\d{{4}}$)}")]
public IActionResult GetByCode(string code) { /* ... */ }
// optional parameter with constraint
[HttpGet("{id:int?}")]
public IActionResult Get(int? id) { /* ... */ }
}
Custom Route Constraints:
public class ValidEmailConstraint : IRouteConstraint
{
public bool Match(HttpContext? httpContext, IRouter? route, string routeKey,
RouteValueDictionary values, RouteDirection routeDirection)
{
if (!values.TryGetValue(routeKey, out var value))
return false;
var email = value?.ToString();
return !string.IsNullOrEmpty(email) &&
email.Contains("@") &&
email.Contains(".");
}
}
// Register in Startup.cs
services.Configure<RouteOptions>(options =>
{
options.ConstraintMap.Add("validemail", typeof(ValidEmailConstraint));
});
// Usage
[HttpGet("{email:validemail}")]
public IActionResult GetByEmail(string email) { /* ... */ }
54. What is the difference between routing and URL rewriting?
| Aspect | Routing | URL Rewriting |
|---|---|---|
| Purpose | Maps URLs to application logic | Changes URL before processing |
| Timing | Early in request pipeline | Can happen at any stage |
| Transparency | URL remains the same | URL can be changed |
| Use Case | Application logic mapping | SEO, legacy URL support |
Routing Example:
[Route("api/products")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult Get(int id)
{
// URL: /api/products/123
// Maps to this action
return Ok($"Product {id}");
}
}
URL Rewriting Example:
// In Program.cs
app.UseRewriter(new RewriteOptions()
.AddRewrite(@"^old-products/(\d+)", "api/products/$1", skipRemainingRules: false)
.AddRedirect(@"^legacy/(.*)", "api/$1", statusCode: 301));
// This rewrites /old-products/123 to /api/products/123
// And redirects /legacy/products to /api/products
55. Explain controller actions and their return types
Controller actions are methods that handle HTTP requests and return responses.
Action Return Types:
1. IActionResult (Most Common)
public class ProductsController : ControllerBase
{
public IActionResult Get(int id)
{
if (id <= 0)
return BadRequest("Invalid ID");
var product = _service.GetProduct(id);
if (product == null)
return NotFound();
return Ok(product);
}
}
2. ActionResult<T> (Strongly Typed)
[HttpGet("{id}")]
public ActionResult<Product> Get(int id)
{
var product = _service.GetProduct(id);
if (product == null)
return NotFound();
return product; // Automatically wrapped in Ok()
}
3. Specific Return Types
public class OrdersController : ControllerBase
{
// Returns JSON
public JsonResult GetOrders()
{
return Json(new { orders = _service.GetOrders() });
}
// Returns View
public ViewResult Index()
{
return View(_service.GetOrders());
}
// Returns File
public FileResult Download(int id)
{
var fileBytes = _service.GetFileBytes(id);
return File(fileBytes, "application/pdf", "document.pdf");
}
// Returns Redirect
public RedirectResult RedirectToExternal()
{
return Redirect("https://external-site.com");
}
// Returns Status Code
public StatusCodeResult NoContent()
{
return NoContent();
}
}
56. What are the differences between ActionResult and IActionResult?
| Aspect | ActionResult | IActionResult |
|---|---|---|
| Type | Abstract class | Interface |
| Flexibility | More specific return types | More flexible, can return any implementation |
| Usage | When you know exact return type | When return type varies based on conditions |
| Performance | Slightly better (no interface overhead) | Minimal overhead |
ActionResult Example:
public class ProductsController : ControllerBase
{
public ActionResult<Product> Get(int id)
{
var product = _service.GetProduct(id);
if (product == null)
return NotFound(); // Returns NotFoundResult
return product; // Returns OkObjectResult<Product>
}
public ActionResult<IEnumerable<Product>> GetAll()
{
var products = _service.GetAllProducts();
return Ok(products); // Explicit Ok result
}
}
IActionResult Example:
public class OrdersController : ControllerBase
{
public IActionResult Process(int id)
{
if (!User.IsInRole("Admin"))
return Forbid(); // Returns ForbidResult
if (id <= 0)
return BadRequest("Invalid ID"); // Returns BadRequestObjectResult
var order = _service.GetOrder(id);
if (order == null)
return NotFound(); // Returns NotFoundResult
if (order.Status == "Completed")
return RedirectToAction("Details", new { id }); // Returns RedirectToActionResult
return View(order); // Returns ViewResult
}
}
57. Explain action filters and their execution order
Action filters are attributes that allow you to run code before or after action methods execute.
Filter Execution Order:
- Authorization Filters (IAuthorizationFilter)
- Resource Filters (IResourceFilter)
- Action Filters (IActionFilter)
- Exception Filters (IExceptionFilter)
- Result Filters (IResultFilter)
Custom Action Filter Example:
public class LoggingActionFilter : IActionFilter
{
private readonly ILogger<LoggingActionFilter> _logger;
public LoggingActionFilter(ILogger<LoggingActionFilter> logger)
{
_logger = logger;
}
public void OnActionExecuting(ActionExecutingContext context)
{
_logger.LogInformation($"Action {context.ActionDescriptor.DisplayName} is executing");
}
public void OnActionExecuted(ActionExecutedContext context)
{
_logger.LogInformation($"Action {context.ActionDescriptor.DisplayName} has executed");
}
}
// Usage
[ServiceFilter(typeof(LoggingActionFilter))]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult Get() { /* ... */ }
}
Async Action Filter:
public class AsyncLoggingActionFilter : IAsyncActionFilter
{
private readonly ILogger<AsyncLoggingActionFilter> _logger;
public AsyncLoggingActionFilter(ILogger<AsyncLoggingActionFilter> logger)
{
_logger = logger;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var startTime = DateTime.UtcNow;
// Before action execution
_logger.LogInformation("Action starting");
var result = await next(); // Execute the action
// After action execution
var duration = DateTime.UtcNow - startTime;
_logger.LogInformation($"Action completed in {duration.TotalMilliseconds}ms");
}
}
58. What is the difference between action filters and middleware?
| Aspect | Action Filters | Middleware |
|---|---|---|
| Scope | Controller/Action specific | Application-wide |
| Execution | Part of MVC pipeline | Part of HTTP pipeline |
| Context | MVC-specific context | HTTP context |
| Flexibility | Limited to MVC actions | Can handle any request |
Action Filter Example:
public class ValidationFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
public void OnActionExecuted(ActionExecutedContext context) { }
}
[ServiceFilter(typeof(ValidationFilter))]
public class OrdersController : ControllerBase
{
[HttpPost]
public IActionResult Create([FromBody] Order order) { /* ... */ }
}
Middleware Example:
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestLoggingMiddleware> _logger;
public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var startTime = DateTime.UtcNow;
_logger.LogInformation($"Request {context.Request.Method} {context.Request.Path} started");
await _next(context);
var duration = DateTime.UtcNow - startTime;
_logger.LogInformation($"Request completed in {duration.TotalMilliseconds}ms with status {context.Response.StatusCode}");
}
}
// Registration in Program.cs
app.UseMiddleware<RequestLoggingMiddleware>();
59. Explain model binding and validation
Model binding automatically maps HTTP request data to action method parameters. Validation ensures data meets business rules.
Model Binding Example:
public class Product
{
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; } = string.Empty;
[Range(0, 10000)]
public decimal Price { get; set; }
[EmailAddress]
public string? ContactEmail { get; set; }
[Url]
public string? Website { get; set; }
}
public class ProductsController : ControllerBase
{
[HttpPost]
public IActionResult Create([FromBody] Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// Process valid product
return CreatedAtAction(nameof(Get), new { id = product.Id }, product);
}
[HttpGet("{id}")]
public IActionResult Get(int id) { /* ... */ }
}
Custom Model Binding:
public class CustomProductBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
var valueProviderResult = bindingContext.ValueProvider.GetValue("productData");
if (valueProviderResult == ValueProviderResult.None)
return Task.CompletedTask;
var value = valueProviderResult.FirstValue;
if (string.IsNullOrEmpty(value))
return Task.CompletedTask;
// Custom parsing logic
var parts = value.Split(',');
if (parts.Length >= 3)
{
var product = new Product
{
Name = parts[0],
Price = decimal.Parse(parts[1]),
ContactEmail = parts[2]
};
bindingContext.Result = ModelBindingResult.Success(product);
}
return Task.CompletedTask;
}
}
public class Product
{
[ModelBinder(typeof(CustomProductBinder))]
public string ProductData { get; set; } = string.Empty;
}
60. What are the differences between model binding and manual parameter extraction?
| Aspect | Model Binding | Manual Parameter Extraction |
|---|---|---|
| Automation | Automatic | Manual |
| Type Safety | Strong typing | String-based |
| Validation | Built-in validation | Manual validation |
| Complexity | Simple for complex objects | Complex for nested objects |
| Performance | Optimized | Slower |
Model Binding Example:
public class OrdersController : ControllerBase
{
[HttpPost]
public IActionResult Create([FromBody] Order order)
{
// Model binding automatically:
// - Deserializes JSON to Order object
// - Applies validation attributes
// - Populates ModelState
if (!ModelState.IsValid)
return BadRequest(ModelState);
return Ok(order);
}
}
Manual Parameter Extraction Example:
public class OrdersController : ControllerBase
{
[HttpPost]
public IActionResult Create()
{
// Manual extraction
var orderId = Request.Form["OrderId"].ToString();
var customerName = Request.Form["CustomerName"].ToString();
var amount = Request.Form["Amount"].ToString();
// Manual validation
if (string.IsNullOrEmpty(customerName))
return BadRequest("Customer name is required");
if (!decimal.TryParse(amount, out var amountValue))
return BadRequest("Invalid amount");
if (amountValue <= 0)
return BadRequest("Amount must be positive");
// Manual object creation
var order = new Order
{
OrderId = orderId,
CustomerName = customerName,
Amount = amountValue
};
return Ok(order);
}
}
Hybrid Approach (Best Practice):
public class OrdersController : ControllerBase
{
[HttpPost]
public IActionResult Create([FromBody] Order order)
{
// Use model binding for basic validation
if (!ModelState.IsValid)
return BadRequest(ModelState);
// Manual business rule validation
if (order.Amount > 10000 && !User.IsInRole("Manager"))
{
ModelState.AddModelError("Amount", "Only managers can create orders over $10,000");
return BadRequest(ModelState);
}
// Custom validation logic
if (order.DeliveryDate < DateTime.Today)
{
ModelState.AddModelError("DeliveryDate", "Delivery date cannot be in the past");
return BadRequest(ModelState);
}
return Ok(order);
}
}
Let me create a more concise version of the technical interview guide:
61. Explain ASP.NET Core Authentication and its Providers
Authentication establishes an identity; authorization decides whether that identity may perform an operation. Configure named authentication schemes, authenticate before authorization middleware, and use policies/roles/claims for access decisions. Validate bearer-token issuer, audience, signing keys, expiration, and algorithm; do not accept tokens simply because they are well-formed.
62. What are the differences between Authentication and Authorization?
| Aspect | Authentication | Authorization |
|---|---|---|
| Purpose | Verifies identity | Determines permissions |
| When | Happens first | Happens after authentication |
| Question | "Who are you?" | "What can you do?" |
| Methods | Username/password, tokens, biometrics | Roles, claims, policies |
Code Example:
[HttpGet("authenticated")]
[Authorize] // Requires authentication only
public IActionResult AuthenticatedEndpoint()
{
return Ok($"Hello {User.Identity.Name}, you are authenticated!");
}
[HttpGet("admin-only")]
[Authorize(Roles = "Admin")] // Requires authentication + authorization
public IActionResult AdminOnlyEndpoint()
{
return Ok("Only admins can see this");
}
63. Explain JWT Authentication and its Implementation
JWT (JSON Web Token) consists of Header.Payload.Signature and is stateless.
JWT Service Implementation:
public class JwtService : IJwtService
{
public string GenerateToken(User user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.Email, user.Email)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"],
audience: _configuration["Jwt:Audience"],
claims: claims,
expires: DateTime.UtcNow.AddHours(1),
signingCredentials: creds
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
64. What is the difference between JWT and Session-based Authentication?
| Aspect | JWT Authentication | Session-based Authentication |
|---|---|---|
| Storage | Client-side | Server-side |
| Stateless | Yes | No |
| Scalability | Better for microservices | Requires session sharing |
| Security | Depends on implementation | Generally more secure |
| Revocation | Difficult (until expiry) | Easy (delete session) |
Session-based Example:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Account/Login";
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.SlidingExpiration = true;
});
65. Explain OAuth 2.0 and OpenID Connect in ASP.NET Core
OAuth 2.0: Authorization framework for delegated access OpenID Connect: Identity layer on top of OAuth 2.0
Implementation:
services.AddAuthentication()
.AddGoogle(options =>
{
options.ClientId = Configuration["Authentication:Google:ClientId"];
options.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
})
.AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi();
66. What are the differences between Claims and Roles?
| Aspect | Claims | Roles |
|---|---|---|
| Granularity | Fine-grained | Coarse-grained |
| Flexibility | Highly flexible | Limited |
| Complexity | More complex | Simpler |
| Use Cases | Custom permissions, user attributes | Simple role-based access |
Claims-based Example:
options.AddPolicy("PremiumUser", policy =>
policy.RequireClaim("SubscriptionType", "Premium"));
options.AddPolicy("MinimumAge", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(18)));
67. Explain Custom Authentication Handlers and Schemes
Custom Authentication Handler:
public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions>
{
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.ContainsKey("X-API-Key"))
{
return Task.FromResult(AuthenticateResult.Fail("API Key not found"));
}
var apiKey = Request.Headers["X-API-Key"].ToString();
if (apiKey != Options.ApiKey)
{
return Task.FromResult(AuthenticateResult.Fail("Invalid API Key"));
}
var claims = new[] { new Claim(ClaimTypes.Name, "API User") };
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}
68. What is the difference between Authentication and Identity?
| Aspect | Authentication | Identity |
|---|---|---|
| Purpose | Verifies who you are | Manages user data and accounts |
| Scope | Login/logout process | User management, profiles, roles |
| Components | Middleware, handlers | User store, password hashing, email confirmation |
Identity Configuration:
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.User.RequireUniqueEmail = true;
options.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
69. Explain Authorization Policies and Requirements
Authorization Policies are rules that determine what actions a user can perform.
Policy Configuration:
services.AddAuthorization(options =>
{
options.AddPolicy("PremiumUser", policy =>
policy.RequireClaim("SubscriptionType", "Premium"));
options.AddPolicy("MinimumAge", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(18)));
options.AddPolicy("ResourceOwner", policy =>
policy.Requirements.Add(new ResourceOwnerRequirement()));
});
Custom Authorization Handler:
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
var ageClaim = context.User.FindFirst("Age");
if (ageClaim != null && int.TryParse(ageClaim.Value, out int age))
{
if (age >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}
70. What are the differences between Role-based and Claim-based Authorization?
| Aspect | Role-based | Claim-based |
|---|---|---|
| Granularity | Coarse (Admin, User, Manager) | Fine (CanRead, CanWrite, Department) |
| Flexibility | Limited | Highly flexible |
| Complexity | Simple | More complex |
| Scalability | Limited | Better for complex scenarios |
Role-based Example:
[Authorize(Roles = "Admin")]
public IActionResult AdminOnly()
{
return Ok("Admin access");
}
Claim-based Example:
[Authorize(Policy = "CanRead")]
public IActionResult ReadResource()
{
return Ok("Resource read");
}
Hybrid Approach (Best Practice):
[Authorize(Roles = "Manager,Admin")] // Role-based
[Authorize(Policy = "CanCreate")] // Claim-based
public IActionResult CreateWithRestrictions()
{
return Ok("Creation with restrictions");
}
71. Explain Entity Framework Core and its differences from EF6
Entity Framework Core (EF Core) is Microsoft's modern, lightweight, extensible, and cross-platform version of Entity Framework. It's a complete rewrite designed for .NET Core and .NET 5+.
Key Differences from EF6:
1. Cross-Platform Support: - EF6: Windows-only - EF Core: Cross-platform (Windows, Linux, macOS)
2. Performance:
// EF Core has better performance due to:
// - Lazy loading implementation
// - Query translation improvements
// - Better memory management
// EF6
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Category Category { get; set; } // Lazy loading
}
// EF Core
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public Category Category { get; set; } // No virtual needed for lazy loading
}
3. Configuration:
// EF6 - Fluent API in OnModelCreating
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.Property(p => p.Name)
.IsRequired()
.HasMaxLength(100);
}
// EF Core - Fluent API in OnModelCreating
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>(entity =>
{
entity.Property(p => p.Name)
.IsRequired()
.HasMaxLength(100);
});
}
4. Migration System:
- EF6: Uses Add-Migration and Update-Database commands
- EF Core: Uses dotnet ef migrations add and dotnet ef database update
72. What are the differences between Code First and Database First?
Code First Approach:
// Define entities first, then generate database
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Product> Products { get; set; }
}
public class ApplicationDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.HasOne(p => p.Category)
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId);
}
}
Database First Approach:
// Scaffold existing database to generate entities
// Command: dotnet ef dbcontext scaffold "ConnectionString" Microsoft.EntityFrameworkCore.SqlServer
// Generated entities (read-only)
public partial class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
}
// Partial class for customizations
public partial class Product
{
public string FullName => $"{Name} - ${Price}";
}
Key Differences:
- Code First: Start with C# classes, generate database schema
- Database First: Start with existing database, generate C# classes
- Code First: Full control over entity design
- Database First: Limited to existing database structure
73. Explain EF Core migrations and their management
Creating Migrations:
// Initial migration
dotnet ef migrations add InitialCreate
// Add new migration after model changes
dotnet ef migrations add AddProductCategory
// Remove last migration
dotnet ef migrations remove
Migration Structure:
public partial class AddProductCategory : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Categories",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Categories", x => x.Id);
});
migrationBuilder.AddColumn<int>(
name: "CategoryId",
table: "Products",
type: "int",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_Products_CategoryId",
table: "Products",
column: "CategoryId");
migrationBuilder.AddForeignKey(
name: "FK_Products_Categories_CategoryId",
table: "Products",
column: "CategoryId",
principalTable: "Categories",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Products_Categories_CategoryId",
table: "Products");
migrationBuilder.DropIndex(
name: "IX_Products_CategoryId",
table: "Products");
migrationBuilder.DropColumn(
name: "CategoryId",
table: "Products");
migrationBuilder.DropTable(
name: "Categories");
}
}
Applying Migrations:
// Update to latest migration
dotnet ef database update
// Update to specific migration
dotnet ef database update AddProductCategory
// Generate SQL script
dotnet ef migrations script
// Generate SQL script from specific migration
dotnet ef migrations script InitialCreate AddProductCategory
74. What is the difference between migrations and database initialization?
Migrations:
- Purpose: Track schema changes over time
- Use Case: Production deployments, version control
- Granularity: Individual schema changes
// Migration approach
public class AddProductCategory : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
// Specific schema changes
migrationBuilder.AddColumn<int>(name: "CategoryId", table: "Products");
}
}
Database Initialization:
- Purpose: Create database from scratch
- Use Case: Development, testing, seeding
- Granularity: Complete database creation
// Database initialization approach
public class ApplicationDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Configure all entities
modelBuilder.Entity<Product>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired();
});
}
// Ensure database is created
public void EnsureDatabaseCreated()
{
Database.EnsureCreated();
}
// Seed data
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>().HasData(
new Category { Id = 1, Name = "Electronics" },
new Category { Id = 2, Name = "Books" }
);
}
}
75. Explain EF Core change tracking and its modes
Change Tracking Modes:
public class ChangeTrackingExample
{
public void DemonstrateChangeTracking()
{
using var context = new ApplicationDbContext();
// 1. Snapshot Change Tracking (Default)
var product = context.Products.First();
product.Name = "Updated Name";
context.SaveChanges(); // Detects changes by comparing snapshots
// 2. Changed Tracking
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.TrackAll;
// 3. No Tracking
var products = context.Products.AsNoTracking().ToList();
}
}
Change Tracking States:
public class ChangeTrackingStates
{
public void TrackEntityStates()
{
using var context = new ApplicationDbContext();
var product = new Product { Name = "New Product" };
context.Products.Add(product);
// EntityState.Detached - Not tracked
Console.WriteLine(context.Entry(product).State); // Detached
// EntityState.Added - Will be inserted
context.Products.Add(product);
Console.WriteLine(context.Entry(product).State); // Added
// EntityState.Unchanged - Tracked but unchanged
var existingProduct = context.Products.First();
Console.WriteLine(context.Entry(existingProduct).State); // Unchanged
// EntityState.Modified - Tracked and modified
existingProduct.Name = "Modified Name";
Console.WriteLine(context.Entry(existingProduct).State); // Modified
// EntityState.Deleted - Will be deleted
context.Products.Remove(existingProduct);
Console.WriteLine(context.Entry(existingProduct).State); // Deleted
}
}
76. What are the differences between AsNoTracking and change tracking?
AsNoTracking avoids tracking query results and is often a good default for read-only projections. It does not make DbContext thread-safe, and tracked queries can still be appropriate when changes will be saved. DbContext is designed for a short unit of work; await operations before using the same context again. Reference: DbContext lifetime.
77. Explain EF Core relationships and navigation properties
One-to-Many Relationship:
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
// Navigation property - one category has many products
public ICollection<Product> Products { get; set; } = new List<Product>();
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
// Foreign key
public int CategoryId { get; set; }
// Navigation property - one product belongs to one category
public Category Category { get; set; }
}
// Fluent API configuration
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>(entity =>
{
entity.HasOne(p => p.Category)
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId)
.OnDelete(DeleteBehavior.Cascade);
});
}
Many-to-Many Relationship:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Tag> Tags { get; set; } = new List<Tag>();
}
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Product> Products { get; set; } = new List<Product>();
}
// EF Core automatically creates junction table
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.HasMany(p => p.Tags)
.WithMany(t => t.Products)
.UsingEntity(junction => junction.ToTable("ProductTags"));
}
One-to-One Relationship:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public ProductDetail Detail { get; set; }
}
public class ProductDetail
{
public int Id { get; set; }
public string Description { get; set; }
public Product Product { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>(entity =>
{
entity.HasOne(p => p.Detail)
.WithOne(d => d.Product)
.HasForeignKey<ProductDetail>(d => d.Id);
});
}
78. What is the difference between lazy loading and eager loading?
Lazy Loading:
public class LazyLoadingExample
{
public void DemonstrateLazyLoading()
{
using var context = new ApplicationDbContext();
// Enable lazy loading
context.ChangeTracker.LazyLoadingEnabled = true;
var product = context.Products.First();
Console.WriteLine(product.Name);
// Category is loaded only when accessed (lazy loading)
Console.WriteLine(product.Category.Name); // Database query executed here
}
}
// Configure lazy loading
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLazyLoadingProxies();
}
}
Eager Loading:
public class EagerLoadingExample
{
public void DemonstrateEagerLoading()
{
using var context = new ApplicationDbContext();
// Load related data upfront
var products = context.Products
.Include(p => p.Category)
.Include(p => p.Tags)
.ToList();
// No additional database queries
foreach (var product in products)
{
Console.WriteLine($"{product.Name} - {product.Category.Name}");
}
}
public void DemonstrateProjection()
{
using var context = new ApplicationDbContext();
// Project only needed data
var productInfo = context.Products
.Select(p => new
{
p.Name,
CategoryName = p.Category.Name
})
.ToList();
}
}
Performance Comparison:
public class LoadingPerformanceComparison
{
public async Task<List<Product>> GetProductsLazyLoading()
{
using var context = new ApplicationDbContext();
var products = await context.Products.ToListAsync();
// N+1 problem - one query for products + one query per product for category
foreach (var product in products)
{
var categoryName = product.Category.Name; // Additional query
}
return products;
}
public async Task<List<Product>> GetProductsEagerLoading()
{
using var context = new ApplicationDbContext();
// Single query with JOIN
return await context.Products
.Include(p => p.Category)
.ToListAsync();
}
}
79. Explain EF Core query optimization and performance
Query Optimization Techniques:
public class QueryOptimization
{
public async Task<List<Product>> OptimizedQuery()
{
using var context = new ApplicationDbContext();
// 1. Use projection to select only needed fields
var products = await context.Products
.Select(p => new { p.Id, p.Name, p.Price })
.ToListAsync();
// 2. Use AsNoTracking for read-only operations
var readOnlyProducts = await context.Products
.AsNoTracking()
.ToListAsync();
// 3. Use compiled queries for repeated queries
var compiledQuery = EF.CompileQuery((ApplicationDbContext ctx, decimal minPrice) =>
ctx.Products.Where(p => p.Price >= minPrice).ToList());
var expensiveProducts = compiledQuery(context, 100.0m);
return readOnlyProducts;
}
public async Task<List<Product>> AvoidNPlusOne()
{
using var context = new ApplicationDbContext();
// Bad - N+1 problem
var products = await context.Products.ToListAsync();
foreach (var product in products)
{
var category = await context.Categories.FindAsync(product.CategoryId);
}
// Good - Single query with JOIN
var optimizedProducts = await context.Products
.Include(p => p.Category)
.ToListAsync();
return optimizedProducts;
}
}
Performance Monitoring:
public class PerformanceMonitoring
{
public async Task MonitorQueryPerformance()
{
using var context = new ApplicationDbContext();
// Enable query logging
context.Database.SetCommandTimeout(30);
// Use SQL Server Profiler or Application Insights
var products = await context.Products
.Where(p => p.Price > 100)
.ToListAsync();
// Check generated SQL
var query = context.Products.Where(p => p.Price > 100);
var sql = query.ToQueryString();
Console.WriteLine(sql);
}
}
Indexing Strategy:
public class IndexingStrategy
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>(entity =>
{
// Create indexes for frequently queried columns
entity.HasIndex(p => p.Name);
entity.HasIndex(p => p.Price);
entity.HasIndex(p => new { p.CategoryId, p.Price }); // Composite index
});
}
}
80. What are the differences between LINQ to Entities and LINQ to Objects?
LINQ to Entities:
public class LinqToEntitiesExample
{
public async Task DemonstrateLinqToEntities()
{
using var context = new ApplicationDbContext();
// LINQ to Entities - translated to SQL
var expensiveProducts = await context.Products
.Where(p => p.Price > 100)
.OrderBy(p => p.Name)
.Select(p => new { p.Name, p.Price })
.ToListAsync();
// Generated SQL:
// SELECT [p].[Name], [p].[Price]
// FROM [Products] AS [p]
// WHERE [p].[Price] > 100
// ORDER BY [p].[Name]
}
public async Task DemonstrateQueryTranslation()
{
using var context = new ApplicationDbContext();
// This gets translated to SQL
var products = await context.Products
.Where(p => p.Name.Contains("Phone"))
.ToListAsync();
// This throws exception - cannot translate to SQL
// var products = await context.Products
// .Where(p => p.Name.Contains("Phone", StringComparison.OrdinalIgnoreCase))
// .ToListAsync();
}
}
LINQ to Objects:
public class LinqToObjectsExample
{
public void DemonstrateLinqToObjects()
{
// LINQ to Objects - executed in memory
var products = new List<Product>
{
new Product { Id = 1, Name = "Phone", Price = 500 },
new Product { Id = 2, Name = "Laptop", Price = 1000 }
};
var expensiveProducts = products
.Where(p => p.Price > 100)
.OrderBy(p => p.Name)
.Select(p => new { p.Name, p.Price })
.ToList();
// All operations executed in memory
var complexFilter = products
.Where(p => p.Name.Contains("Phone", StringComparison.OrdinalIgnoreCase))
.ToList();
}
}
Key Differences:
public class LinqDifferences
{
public async Task DemonstrateDifferences()
{
using var context = new ApplicationDbContext();
// LINQ to Entities - Database execution
var dbProducts = await context.Products
.Where(p => p.Price > 100)
.ToListAsync(); // Executes SQL query
// LINQ to Objects - Memory execution
var memoryProducts = dbProducts
.Where(p => p.Price > 100)
.ToList(); // Executes in memory
// Performance implications
var largeDataset = await context.Products.ToListAsync(); // Loads all data
var filteredInMemory = largeDataset.Where(p => p.Price > 100).ToList(); // Filters in memory
// Better approach - filter at database level
var filteredAtDatabase = await context.Products
.Where(p => p.Price > 100)
.ToListAsync(); // Filters in database
}
}
Best Practices:
public class BestPractices
{
public async Task DemonstrateBestPractices()
{
using var context = new ApplicationDbContext();
// Good - Filter at database level
var expensiveProducts = await context.Products
.Where(p => p.Price > 100)
.ToListAsync();
// Bad - Load all data then filter in memory
var allProducts = await context.Products.ToListAsync();
var expensiveProductsInMemory = allProducts.Where(p => p.Price > 100).ToList();
// Good - Use projection to select only needed fields
var productNames = await context.Products
.Select(p => p.Name)
.ToListAsync();
// Bad - Load entire entities when only names are needed
var products = await context.Products.ToListAsync();
var names = products.Select(p => p.Name).ToList();
}
}
ASP.NET Core Logging & Diagnostics - Technical Lead Interview Guide
81. Explain ASP.NET Core logging and its providers
Overview
ASP.NET Core has a built-in logging framework that provides a unified logging API across the application. The logging system is designed to be extensible and supports multiple logging providers simultaneously.
Key Components
ILogger Interface: The core logging interface that applications use to write log messages.
ILoggerFactory: Creates logger instances and manages logging providers.
Logging Providers: Implementations that handle where and how log messages are written.
Built-in Providers
// Program.cs or Startup.cs
var builder = WebApplication.CreateBuilder(args);
// Configure logging with multiple providers
builder.Logging.ClearProviders(); // Clear default providers
builder.Logging.AddConsole(); // Console logging
builder.Logging.AddDebug(); // Debug window logging
builder.Logging.AddEventSourceLogger(); // EventSource logging
builder.Logging.AddEventLog(); // Windows Event Log
builder.Logging.AddAzureWebAppDiagnostics(); // Azure App Service
builder.Logging.AddApplicationInsights(); // Application Insights
// Configure log levels
builder.Logging.SetMinimumLevel(LogLevel.Information);
builder.Logging.AddFilter("Microsoft", LogLevel.Warning);
builder.Logging.AddFilter("System", LogLevel.Warning);
builder.Logging.AddFilter("MyApp", LogLevel.Debug);
Custom Provider Example
public class CustomLoggingProvider : ILoggerProvider
{
private readonly string _filePath;
public CustomLoggingProvider(string filePath)
{
_filePath = filePath;
}
public ILogger CreateLogger(string categoryName)
{
return new CustomLogger(_filePath);
}
public void Dispose() { }
private class CustomLogger : ILogger
{
private readonly string _filePath;
private readonly object _lock = new object();
public CustomLogger(string filePath)
{
_filePath = filePath;
}
public IDisposable BeginScope<TState>(TState state) => null;
public bool IsEnabled(LogLevel logLevel) => true;
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
Exception exception, Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel)) return;
var message = formatter(state, exception);
var logEntry = $"{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} [{logLevel}] {message}";
lock (_lock)
{
File.AppendAllText(_filePath, logEntry + Environment.NewLine);
}
}
}
}
// Registration
builder.Logging.AddProvider(new CustomLoggingProvider("logs/app.log"));
82. What are the differences between ILogger and ILoggerFactory?
ILogger
- Purpose: Interface for writing log messages
- Scope: Individual logger instance for a specific category
- Usage: Injected into classes to write logs
- Responsibility: Log message formatting and writing
ILoggerFactory
- Purpose: Factory for creating logger instances
- Scope: Application-wide logger management
- Usage: Creates and configures loggers
- Responsibility: Logger lifecycle management and provider coordination
Code Examples
// ILogger usage in a service
public class UserService
{
private readonly ILogger<UserService> _logger;
public UserService(ILogger<UserService> logger)
{
_logger = logger;
}
public async Task<User> GetUserAsync(int userId)
{
_logger.LogInformation("Getting user with ID: {UserId}", userId);
try
{
var user = await _userRepository.GetByIdAsync(userId);
_logger.LogInformation("Successfully retrieved user: {UserName}", user?.Name);
return user;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to get user with ID: {UserId}", userId);
throw;
}
}
}
// ILoggerFactory usage for custom logger creation
public class CustomService
{
private readonly ILogger _logger;
public CustomService(ILoggerFactory loggerFactory)
{
// Create logger with custom category
_logger = loggerFactory.CreateLogger("CustomService");
// Or create logger for specific type
var typedLogger = loggerFactory.CreateLogger<CustomService>();
}
public void DoWork()
{
_logger.LogInformation("Starting work");
// ... work logic
_logger.LogInformation("Work completed");
}
}
Key Differences Summary
| Aspect | ILogger | ILoggerFactory |
|---|---|---|
| Purpose | Write logs | Create loggers |
| Scope | Category-specific | Application-wide |
| Dependency | Consumes logging | Provides logging |
| Lifetime | Per-category | Singleton |
| Configuration | Uses factory settings | Manages settings |
83. Explain structured logging and its benefits
What is Structured Logging?
Structured logging captures log data as structured objects rather than plain text, making logs machine-readable and easier to analyze.
Benefits
- Searchability: Easy to filter and search logs
- Analytics: Better log aggregation and analysis
- Performance: More efficient log processing
- Consistency: Standardized log format across application
- Correlation: Better request tracing and debugging
Implementation Examples
// Basic structured logging
public class OrderService
{
private readonly ILogger<OrderService> _logger;
public OrderService(ILogger<OrderService> logger)
{
_logger = logger;
}
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
{
// Structured logging with parameters
_logger.LogInformation(
"Creating order for customer {CustomerId} with {ItemCount} items. Total: {TotalAmount:C}",
request.CustomerId,
request.Items.Count,
request.TotalAmount
);
try
{
var order = await _orderRepository.CreateAsync(request);
_logger.LogInformation(
"Order {OrderId} created successfully for customer {CustomerId}",
order.Id,
order.CustomerId
);
return order;
}
catch (Exception ex)
{
_logger.LogError(
ex,
"Failed to create order for customer {CustomerId}. Request: {@Request}",
request.CustomerId,
request // Serializes entire object
);
throw;
}
}
}
Using Log Scopes for Context
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestLoggingMiddleware> _logger;
public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
using (_logger.BeginScope(new Dictionary<string, object>
{
["RequestId"] = context.TraceIdentifier,
["UserId"] = context.User?.Identity?.Name ?? "anonymous",
["UserAgent"] = context.Request.Headers["User-Agent"].ToString(),
["IPAddress"] = context.Connection.RemoteIpAddress?.ToString()
}))
{
_logger.LogInformation("Starting request {Method} {Path}",
context.Request.Method, context.Request.Path);
var sw = Stopwatch.StartNew();
await _next(context);
sw.Stop();
_logger.LogInformation("Request completed in {ElapsedMs}ms with status {StatusCode}",
sw.ElapsedMilliseconds, context.Response.StatusCode);
}
}
}
JSON Logging Configuration
// Program.cs
builder.Logging.ClearProviders();
builder.Logging.AddJsonConsole(options =>
{
options.JsonWriterOptions = new JsonWriterOptions
{
Indented = true
};
options.IncludeScopes = true;
options.TimestampFormat = "yyyy-MM-dd HH:mm:ss ";
});
// appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning"
},
"Console": {
"FormatterName": "json",
"FormatterOptions": {
"IncludeScopes": true,
"TimestampFormat": "yyyy-MM-dd HH:mm:ss "
}
}
}
}
84. What is the difference between logging and tracing?
Logging
- Purpose: Record application events and state
- Focus: What happened and when
- Granularity: Event-level information
- Use Case: Debugging, monitoring, auditing
Tracing
- Purpose: Follow request flow through system
- Focus: How request flows through components
- Granularity: Request-level with correlation
- Use Case: Performance analysis, debugging distributed systems
Code Examples
// Logging Example
public class PaymentService
{
private readonly ILogger<PaymentService> _logger;
public async Task<PaymentResult> ProcessPaymentAsync(PaymentRequest request)
{
_logger.LogInformation("Processing payment for amount {Amount}", request.Amount);
// Process payment logic
var result = await _paymentProcessor.ProcessAsync(request);
_logger.LogInformation("Payment processed with status {Status}", result.Status);
return result;
}
}
// Tracing Example
public class PaymentServiceWithTracing
{
private readonly ILogger<PaymentServiceWithTracing> _logger;
private readonly ActivitySource _activitySource;
public PaymentServiceWithTracing(ILogger<PaymentServiceWithTracing> logger)
{
_logger = logger;
_activitySource = new ActivitySource("PaymentService");
}
public async Task<PaymentResult> ProcessPaymentAsync(PaymentRequest request)
{
using var activity = _activitySource.StartActivity("ProcessPayment");
activity?.SetTag("payment.amount", request.Amount);
activity?.SetTag("payment.currency", request.Currency);
try
{
_logger.LogInformation("Starting payment processing");
// Validate payment
using (var validationActivity = _activitySource.StartActivity("ValidatePayment"))
{
await ValidatePaymentAsync(request);
validationActivity?.SetStatus(ActivityStatusCode.Ok);
}
// Process payment
using (var processingActivity = _activitySource.StartActivity("ProcessPayment"))
{
var result = await _paymentProcessor.ProcessAsync(request);
processingActivity?.SetTag("payment.status", result.Status);
processingActivity?.SetStatus(ActivityStatusCode.Ok);
return result;
}
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
_logger.LogError(ex, "Payment processing failed");
throw;
}
}
}
Distributed Tracing Setup
// Program.cs
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSqlClientInstrumentation()
.AddSource("PaymentService")
.AddJaegerExporter(options =>
{
options.AgentHost = "localhost";
options.AgentPort = 6831;
})
.AddConsoleExporter());
Key Differences Summary
| Aspect | Logging | Tracing |
|---|---|---|
| Purpose | Event recording | Request flow tracking |
| Scope | Individual events | Request lifecycle |
| Correlation | Optional | Essential |
| Performance Impact | Low | Medium |
| Use Cases | Debugging, monitoring | Performance analysis, debugging |
85. Explain ASP.NET Core diagnostics and health checks
Diagnostics Overview
ASP.NET Core diagnostics provide tools for monitoring application health, performance, and behavior in production environments.
Health Checks
Health checks are endpoints that report the health status of your application and its dependencies.
Implementation Examples
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add health checks
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy())
.AddCheck("database", () =>
{
try
{
// Check database connectivity
using var connection = new SqlConnection(builder.Configuration.GetConnectionString("Default"));
connection.Open();
return HealthCheckResult.Healthy();
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy("Database is not accessible", ex);
}
})
.AddCheck("external-api", async () =>
{
try
{
using var client = new HttpClient();
var response = await client.GetAsync("https://api.external.com/health");
return response.IsSuccessStatusCode
? HealthCheckResult.Healthy()
: HealthCheckResult.Unhealthy($"API returned {response.StatusCode}");
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy("External API is not accessible", ex);
}
})
.AddCheck("disk-space", () =>
{
var drive = new DriveInfo(Path.GetPathRoot(Environment.CurrentDirectory));
var freeSpacePercent = (double)drive.AvailableFreeSpace / drive.TotalSize * 100;
return freeSpacePercent > 10
? HealthCheckResult.Healthy($"Free space: {freeSpacePercent:F1}%")
: HealthCheckResult.Degraded($"Low disk space: {freeSpacePercent:F1}%");
});
var app = builder.Build();
// Configure health check endpoints
app.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = WriteHealthCheckResponse,
AllowCachingResponses = false
});
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready"),
ResponseWriter = WriteHealthCheckResponse
});
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = _ => false, // No checks for liveness
ResponseWriter = WriteHealthCheckResponse
});
Custom Health Check
public class DatabaseHealthCheck : IHealthCheck
{
private readonly string _connectionString;
public DatabaseHealthCheck(IConfiguration configuration)
{
_connectionString = configuration.GetConnectionString("Default");
}
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context,
CancellationToken cancellationToken = default)
{
try
{
using var connection = new SqlConnection(_connectionString);
await connection.OpenAsync(cancellationToken);
using var command = connection.CreateCommand();
command.CommandText = "SELECT 1";
command.CommandTimeout = 5;
await command.ExecuteScalarAsync(cancellationToken);
return HealthCheckResult.Healthy("Database is accessible");
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy("Database is not accessible", ex);
}
}
}
// Registration
builder.Services.AddHealthChecks()
.AddCheck<DatabaseHealthCheck>("database");
Custom Health Check Response Writer
private static Task WriteHealthCheckResponse(HttpContext context, HealthReport report)
{
context.Response.ContentType = "application/json";
var result = new
{
status = report.Status.ToString(),
checks = report.Entries.Select(entry => new
{
name = entry.Key,
status = entry.Value.Status.ToString(),
description = entry.Value.Description,
duration = entry.Value.Duration.TotalMilliseconds,
tags = entry.Value.Tags
}),
totalDuration = report.TotalDuration.TotalMilliseconds
};
return context.Response.WriteAsync(JsonSerializer.Serialize(result, new JsonSerializerOptions
{
WriteIndented = true
}));
}
Health Check UI
// Add health check UI
builder.Services.AddHealthChecksUI(setup =>
{
setup.SetEvaluationTimeInSeconds(30);
setup.MaximumHistoryEntriesPerEndpoint(60);
})
.AddInMemoryStorage();
// Configure UI endpoints
app.MapHealthChecksUI(config =>
{
config.UIPath = "/health-ui";
config.AddHealthCheckEndpoint("api", "/health");
});
86. What are the differences between health checks and monitoring?
Health Checks
- Purpose: Determine if application is healthy/unhealthy
- Scope: Binary status (healthy/unhealthy/degraded)
- Frequency: On-demand or periodic
- Use Case: Load balancer decisions, deployment validation
Monitoring
- Purpose: Continuous observation of application metrics
- Scope: Detailed metrics and trends
- Frequency: Continuous real-time collection
- Use Case: Performance analysis, capacity planning, alerting
Code Examples
// Health Check Example
public class ApplicationHealthCheck : IHealthCheck
{
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context,
CancellationToken cancellationToken = default)
{
// Simple binary check
var isHealthy = CheckApplicationHealth();
return Task.FromResult(isHealthy
? HealthCheckResult.Healthy()
: HealthCheckResult.Unhealthy("Application is not responding"));
}
private bool CheckApplicationHealth()
{
// Basic health check logic
return true; // Simplified
}
}
// Monitoring Example
public class ApplicationMetrics
{
private readonly IMetrics _metrics;
private readonly Counter _requestCounter;
private readonly Histogram _responseTimeHistogram;
private readonly Gauge _activeConnections;
public ApplicationMetrics(IMetrics metrics)
{
_metrics = metrics;
_requestCounter = _metrics.CreateCounter("http_requests_total", "Total HTTP requests");
_responseTimeHistogram = _metrics.CreateHistogram("http_request_duration_seconds", "HTTP request duration");
_activeConnections = _metrics.CreateGauge("active_connections", "Number of active connections");
}
public void RecordRequest(string method, string endpoint, int statusCode, TimeSpan duration)
{
_requestCounter.Increment(new CounterTags
{
{ "method", method },
{ "endpoint", endpoint },
{ "status_code", statusCode.ToString() }
});
_responseTimeHistogram.Record(duration.TotalSeconds, new HistogramTags
{
{ "method", method },
{ "endpoint", endpoint }
});
}
public void SetActiveConnections(int count)
{
_activeConnections.Set(count);
}
}
// Monitoring Middleware
public class MetricsMiddleware
{
private readonly RequestDelegate _next;
private readonly ApplicationMetrics _metrics;
public MetricsMiddleware(RequestDelegate next, ApplicationMetrics metrics)
{
_next = next;
_metrics = metrics;
}
public async Task InvokeAsync(HttpContext context)
{
var sw = Stopwatch.StartNew();
try
{
await _next(context);
}
finally
{
sw.Stop();
_metrics.RecordRequest(
context.Request.Method,
context.Request.Path,
context.Response.StatusCode,
sw.Elapsed
);
}
}
}
Key Differences Summary
| Aspect | Health Checks | Monitoring |
|---|---|---|
| Purpose | Status verification | Continuous observation |
| Output | Binary/ternary status | Detailed metrics |
| Frequency | On-demand/periodic | Continuous |
| Complexity | Simple checks | Complex metrics collection |
| Use Case | Load balancing, deployment | Performance analysis, alerting |
87. Explain application insights integration
Overview
Application Insights is Azure's application performance management (APM) service that provides deep insights into application performance and usage.
Integration Setup
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add Application Insights
builder.Services.AddApplicationInsightsTelemetry(options =>
{
options.ConnectionString = builder.Configuration["ApplicationInsights:ConnectionString"];
options.EnableAdaptiveSampling = true;
options.EnablePerformanceCounterCollectionModule = true;
options.EnableQuickPulseMetricStream = true;
});
// Configure logging to Application Insights
builder.Logging.AddApplicationInsights(options =>
{
options.TrackExceptionsAsExceptionTelemetry = true;
options.IncludeScopes = true;
});
var app = builder.Build();
// Configure Application Insights middleware
app.UseApplicationInsightsRequestTelemetry();
app.UseApplicationInsightsExceptionTelemetry();
Custom Telemetry
public class OrderService
{
private readonly ILogger<OrderService> _logger;
private readonly TelemetryClient _telemetryClient;
public OrderService(ILogger<OrderService> logger, TelemetryClient telemetryClient)
{
_logger = logger;
_telemetryClient = telemetryClient;
}
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
{
using var operation = _telemetryClient.StartOperation<RequestTelemetry>("CreateOrder");
try
{
// Add custom properties
operation.Telemetry.Properties["CustomerId"] = request.CustomerId.ToString();
operation.Telemetry.Properties["OrderType"] = request.OrderType;
// Track custom event
_telemetryClient.TrackEvent("OrderCreationStarted", new Dictionary<string, string>
{
["CustomerId"] = request.CustomerId.ToString(),
["ItemCount"] = request.Items.Count.ToString()
});
// Track custom metric
_telemetryClient.TrackMetric("OrderValue", request.TotalAmount, new Dictionary<string, string>
{
["Currency"] = request.Currency
});
var order = await _orderRepository.CreateAsync(request);
// Track success
_telemetryClient.TrackEvent("OrderCreated", new Dictionary<string, string>
{
["OrderId"] = order.Id.ToString(),
["CustomerId"] = order.CustomerId.ToString()
});
return order;
}
catch (Exception ex)
{
// Track exception
_telemetryClient.TrackException(ex, new Dictionary<string, string>
{
["CustomerId"] = request.CustomerId.ToString(),
["Operation"] = "CreateOrder"
});
operation.Telemetry.Success = false;
throw;
}
}
}
Custom Telemetry Initializer
public class CustomTelemetryInitializer : ITelemetryInitializer
{
private readonly IHttpContextAccessor _httpContextAccessor;
public CustomTelemetryInitializer(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
var context = _httpContextAccessor.HttpContext;
if (context != null)
{
// Add user information
if (context.User?.Identity?.IsAuthenticated == true)
{
telemetry.Context.User.Id = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
telemetry.Context.User.AuthenticatedUserId = context.User.Identity.Name;
}
// Add custom properties
telemetry.Context.Properties["Environment"] = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
telemetry.Context.Properties["MachineName"] = Environment.MachineName;
telemetry.Context.Properties["ApplicationVersion"] = typeof(Program).Assembly.GetName().Version?.ToString();
// Add request-specific properties
if (context.Request.Headers.ContainsKey("X-Correlation-ID"))
{
telemetry.Context.Properties["CorrelationId"] = context.Request.Headers["X-Correlation-ID"];
}
}
}
}
// Registration
builder.Services.AddSingleton<ITelemetryInitializer, CustomTelemetryInitializer>();
Dependency Tracking
public class DatabaseService
{
private readonly TelemetryClient _telemetryClient;
private readonly string _connectionString;
public DatabaseService(TelemetryClient telemetryClient, IConfiguration configuration)
{
_telemetryClient = telemetryClient;
_connectionString = configuration.GetConnectionString("Default");
}
public async Task<T> ExecuteQueryAsync<T>(string query, object parameters = null)
{
using var dependency = _telemetryClient.StartOperation<DependencyTelemetry>("DatabaseQuery");
try
{
dependency.Telemetry.Type = "SQL";
dependency.Telemetry.Data = query;
dependency.Telemetry.Target = "Database";
using var connection = new SqlConnection(_connectionString);
await connection.OpenAsync();
// Execute query logic here
var result = await ExecuteQueryInternalAsync<T>(connection, query, parameters);
dependency.Telemetry.Success = true;
return result;
}
catch (Exception ex)
{
dependency.Telemetry.Success = false;
_telemetryClient.TrackException(ex);
throw;
}
}
}
Configuration in appsettings.json
{
"ApplicationInsights": {
"ConnectionString": "InstrumentationKey=your-key-here",
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning"
}
},
"Logging": {
"ApplicationInsights": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning"
}
}
}
}
88. What is the difference between logging and telemetry?
Logging
- Purpose: Record application events and state for debugging
- Focus: Human-readable messages
- Storage: Local files, databases
- Use Case: Development, troubleshooting
Telemetry
- Purpose: Collect application metrics and performance data
- Focus: Machine-readable data for analysis
- Storage: Monitoring systems, analytics platforms
- Use Case: Performance monitoring, business intelligence
Code Examples
// Logging Example
public class UserService
{
private readonly ILogger<UserService> _logger;
public async Task<User> GetUserAsync(int userId)
{
_logger.LogInformation("Getting user with ID: {UserId}", userId);
try
{
var user = await _userRepository.GetByIdAsync(userId);
_logger.LogInformation("Successfully retrieved user: {UserName}", user?.Name);
return user;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to get user with ID: {UserId}", userId);
throw;
}
}
}
// Telemetry Example
public class UserServiceWithTelemetry
{
private readonly ILogger<UserServiceWithTelemetry> _logger;
private readonly TelemetryClient _telemetryClient;
public async Task<User> GetUserAsync(int userId)
{
using var operation = _telemetryClient.StartOperation<RequestTelemetry>("GetUser");
try
{
// Track custom metric
_telemetryClient.TrackMetric("UserLookup", 1, new Dictionary<string, string>
{
["UserId"] = userId.ToString()
});
// Track custom event
_telemetryClient.TrackEvent("UserRequested", new Dictionary<string, string>
{
["UserId"] = userId.ToString(),
["Timestamp"] = DateTime.UtcNow.ToString("O")
});
var user = await _userRepository.GetByIdAsync(userId);
// Track success metrics
_telemetryClient.TrackMetric("UserRetrievalSuccess", 1);
// Track user properties
if (user != null)
{
_telemetryClient.TrackEvent("UserRetrieved", new Dictionary<string, string>
{
["UserId"] = user.Id.ToString(),
["UserType"] = user.Type,
["IsActive"] = user.IsActive.ToString()
});
}
return user;
}
catch (Exception ex)
{
// Track failure metrics
_telemetryClient.TrackMetric("UserRetrievalFailure", 1);
_telemetryClient.TrackException(ex, new Dictionary<string, string>
{
["UserId"] = userId.ToString(),
["Operation"] = "GetUser"
});
operation.Telemetry.Success = false;
throw;
}
}
}
Custom Telemetry Processor
public class CustomTelemetryProcessor : ITelemetryProcessor
{
private readonly ITelemetryProcessor _next;
public CustomTelemetryProcessor(ITelemetryProcessor next)
{
_next = next;
}
public void Process(ITelemetry item)
{
// Filter out sensitive data
if (item is RequestTelemetry request)
{
// Remove sensitive headers
if (request.Properties.ContainsKey("Authorization"))
{
request.Properties.Remove("Authorization");
}
}
// Add custom properties
item.Context.Properties["ProcessedBy"] = "CustomProcessor";
item.Context.Properties["ProcessingTime"] = DateTime.UtcNow.ToString("O");
// Pass to next processor
_next.Process(item);
}
}
// Registration
builder.Services.AddApplicationInsightsTelemetryProcessor<CustomTelemetryProcessor>();
Key Differences Summary
| Aspect | Logging | Telemetry |
|---|---|---|
| Purpose | Debugging, troubleshooting | Performance monitoring, analytics |
| Audience | Developers, operators | Monitoring systems, business users |
| Format | Human-readable text | Structured data |
| Volume | High (detailed events) | Lower (aggregated metrics) |
| Retention | Short-term | Long-term |
89. Custom Logging Providers and Sinks
Custom Logging Providers extend .NET's built-in logging system to write logs to custom destinations.
Custom Logging Provider Example:
public class CustomFileLoggerProvider : ILoggerProvider
{
private readonly string _filePath;
private readonly object _lock = new object();
public CustomFileLoggerProvider(string filePath)
{
_filePath = filePath;
}
public ILogger CreateLogger(string categoryName)
{
return new CustomFileLogger(_filePath, categoryName);
}
public void Dispose() { }
private class CustomFileLogger : ILogger
{
private readonly string _filePath;
private readonly string _categoryName;
private readonly object _lock = new object();
public CustomFileLogger(string filePath, string categoryName)
{
_filePath = filePath;
_categoryName = categoryName;
}
public IDisposable BeginScope<TState>(TState state) => null;
public bool IsEnabled(LogLevel logLevel) => true;
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
Exception exception, Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel)) return;
var message = $"{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} [{logLevel}] " +
$"[{_categoryName}] {formatter(state, exception)}";
if (exception != null)
message += $"\nException: {exception}";
lock (_lock)
{
File.AppendAllText(_filePath, message + Environment.NewLine);
}
}
}
}
Registration in Program.cs:
builder.Logging.AddProvider(new CustomFileLoggerProvider("logs/app.log"));
Custom Logging Sink (using Serilog):
public class CustomSink : ILogEventSink
{
private readonly ITextFormatter _formatter;
private readonly string _filePath;
public CustomSink(ITextFormatter formatter, string filePath)
{
_formatter = formatter;
_filePath = filePath;
}
public void Emit(LogEvent logEvent)
{
var stringWriter = new StringWriter();
_formatter.Format(logEvent, stringWriter);
var formattedLog = stringWriter.ToString();
File.AppendAllText(_filePath, formattedLog);
}
}
90. Differences Between Log Levels
| Log Level | Description | Use Case | Example |
|---|---|---|---|
| Trace | Most detailed information | Debugging specific issues | Method entry/exit, variable values |
| Debug | Detailed diagnostic information | Development debugging | SQL queries, API calls |
| Information | General application flow | Monitoring application behavior | User login, data processing |
| Warning | Unexpected but handled situations | Potential issues | Retry attempts, deprecated API usage |
| Error | Error conditions | Error handling | Exceptions, failed operations |
| Critical | Critical failures | System failures | Database connection lost, out of memory |
Example Usage:
public class UserService
{
private readonly ILogger<UserService> _logger;
public UserService(ILogger<UserService> logger)
{
_logger = logger;
}
public async Task<User> GetUserAsync(int userId)
{
_logger.LogTrace("Entering GetUserAsync with userId: {UserId}", userId);
try
{
_logger.LogDebug("Querying database for user {UserId}", userId);
var user = await _dbContext.Users.FindAsync(userId);
if (user == null)
{
_logger.LogWarning("User {UserId} not found", userId);
return null;
}
_logger.LogInformation("Successfully retrieved user {UserId}", userId);
return user;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error retrieving user {UserId}", userId);
throw;
}
}
}
91. ASP.NET Core Performance Optimization Techniques
1. Response Caching:
[ResponseCache(Duration = 300, Location = ResponseCacheLocation.Any)]
public class ProductController : ControllerBase
{
[ResponseCache(Duration = 600, VaryByQueryKeys = new[] { "category" })]
public async Task<IActionResult> GetProducts(string category)
{
// Implementation
}
}
2. Output Caching (ASP.NET Core 7+):
[OutputCache(Duration = 300, VaryByQueryKeys = new[] { "id" })]
public async Task<IActionResult> GetProduct(int id)
{
var product = await _productService.GetByIdAsync(id);
return View(product);
}
3. Memory Caching:
public class ProductService
{
private readonly IMemoryCache _cache;
private readonly IProductRepository _repository;
public async Task<Product> GetProductAsync(int id)
{
var cacheKey = $"product_{id}";
if (!_cache.TryGetValue(cacheKey, out Product product))
{
product = await _repository.GetByIdAsync(id);
var cacheOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(10))
.SetAbsoluteExpiration(TimeSpan.FromHours(1));
_cache.Set(cacheKey, product, cacheOptions);
}
return product;
}
}
4. Database Optimization:
public class OptimizedProductService
{
public async Task<List<Product>> GetProductsAsync()
{
// Use projection to select only needed fields
return await _context.Products
.Select(p => new Product
{
Id = p.Id,
Name = p.Name,
Price = p.Price
})
.AsNoTracking() // For read-only operations
.ToListAsync();
}
}
5. Compression:
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
});
app.UseResponseCompression();
92. Synchronous vs Asynchronous Operations
Async I/O frees the calling thread while an I/O operation waits; it does not make CPU work faster or automatically create parallelism. Keep request paths asynchronous end-to-end, avoid blocking on Task.Result or Wait(), and propagate cancellation. Do not run parallel EF Core operations on the same DbContext.
93. Response Caching and Strategies
Response caching follows HTTP cache rules and is suitable only for public, identity-independent responses that meet cache eligibility. In ASP.NET Core, ResponseCache primarily sets headers; response-caching middleware and output caching are different mechanisms. Do not cache personalized responses with a shared cache policy.
94. Caching vs Session State
Memory Cache:
public class MemoryCacheService
{
private readonly IMemoryCache _cache;
public MemoryCacheService(IMemoryCache cache)
{
_cache = cache;
}
public void SetUserPreferences(int userId, UserPreferences preferences)
{
var cacheKey = $"user_preferences_{userId}";
_cache.Set(cacheKey, preferences, TimeSpan.FromHours(1));
}
public UserPreferences GetUserPreferences(int userId)
{
var cacheKey = $"user_preferences_{userId}";
return _cache.Get<UserPreferences>(cacheKey);
}
}
Session State:
public class SessionService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public SessionService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void SetUserSession(UserSession session)
{
var httpContext = _httpContextAccessor.HttpContext;
httpContext.Session.SetString("UserId", session.UserId.ToString());
httpContext.Session.SetString("UserName", session.UserName);
httpContext.Session.SetString("UserRole", session.UserRole);
}
public UserSession GetUserSession()
{
var httpContext = _httpContextAccessor.HttpContext;
if (!httpContext.Session.Keys.Contains("UserId"))
return null;
return new UserSession
{
UserId = int.Parse(httpContext.Session.GetString("UserId")),
UserName = httpContext.Session.GetString("UserName"),
UserRole = httpContext.Session.GetString("UserRole")
};
}
}
Key Differences:
| Aspect | Memory Cache | Session State |
|---|---|---|
| Scope | Application-wide | User-specific |
| Lifetime | Configurable expiration | Session duration |
| Storage | In-memory or distributed | Server memory/Redis |
| Use Case | Shared data, performance | User-specific data |
| Scalability | Shared across instances | Per-user isolation |
95. Output Caching and Configuration
Output caching is server-controlled caching for endpoint responses and is distinct from HTTP response caching. Define a policy that varies by the actual inputs that affect output, set appropriate eviction/expiration, and avoid caching authenticated or personalized results without an explicit safe design.
96. Memory Cache vs Distributed Cache
Memory Cache:
public class MemoryCacheService
{
private readonly IMemoryCache _cache;
public MemoryCacheService(IMemoryCache cache)
{
_cache = cache;
}
public async Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null)
{
if (_cache.TryGetValue(key, out T value))
return value;
value = await factory();
var options = new MemoryCacheEntryOptions();
if (expiration.HasValue)
options.SetAbsoluteExpiration(expiration.Value);
_cache.Set(key, value, options);
return value;
}
public void Remove(string key)
{
_cache.Remove(key);
}
}
Distributed Cache (Redis):
public class DistributedCacheService
{
private readonly IDistributedCache _cache;
public DistributedCacheService(IDistributedCache cache)
{
_cache = cache;
}
public async Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null)
{
var cachedValue = await _cache.GetStringAsync(key);
if (cachedValue != null)
return JsonSerializer.Deserialize<T>(cachedValue);
var value = await factory();
var serializedValue = JsonSerializer.Serialize(value);
var options = new DistributedCacheEntryOptions();
if (expiration.HasValue)
options.SetAbsoluteExpiration(expiration.Value);
await _cache.SetStringAsync(key, serializedValue, options);
return value;
}
public async Task RemoveAsync(string key)
{
await _cache.RemoveAsync(key);
}
}
Configuration:
// Memory Cache
builder.Services.AddMemoryCache();
// Distributed Cache (Redis)
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("Redis");
options.InstanceName = "SMChat_";
});
// Hybrid approach
public class HybridCacheService
{
private readonly IMemoryCache _memoryCache;
private readonly IDistributedCache _distributedCache;
public async Task<T> GetAsync<T>(string key)
{
// Try memory cache first
if (_memoryCache.TryGetValue(key, out T memoryValue))
return memoryValue;
// Fall back to distributed cache
var distributedValue = await _distributedCache.GetStringAsync(key);
if (distributedValue != null)
{
var value = JsonSerializer.Deserialize<T>(distributedValue);
_memoryCache.Set(key, value, TimeSpan.FromMinutes(5)); // Cache in memory
return value;
}
return default(T);
}
}
97. ASP.NET Core Compression and Benefits
Compression Configuration:
public class CompressionConfig
{
public static void ConfigureCompression(IServiceCollection services)
{
services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
// Custom compression for specific MIME types
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/json", "text/html", "text/css", "application/javascript" });
});
services.Configure<BrotliCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});
services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Optimal;
});
}
}
Custom Compression Provider:
public class CustomCompressionProvider : ICompressionProvider
{
public string EncodingName => "custom";
public bool SupportsFlush => true;
public Stream CreateStream(Stream outputStream, CompressionLevel compressionLevel)
{
return new CustomCompressionStream(outputStream, compressionLevel);
}
}
public class CustomCompressionStream : Stream
{
private readonly Stream _outputStream;
private readonly MemoryStream _buffer;
public CustomCompressionStream(Stream outputStream, CompressionLevel compressionLevel)
{
_outputStream = outputStream;
_buffer = new MemoryStream();
}
public override void Write(byte[] buffer, int offset, int count)
{
// Custom compression logic
var compressed = CompressData(buffer, offset, count);
_buffer.Write(compressed, 0, compressed.Length);
}
private byte[] CompressData(byte[] data, int offset, int count)
{
// Implementation of custom compression algorithm
return data; // Placeholder
}
// Implement other Stream methods...
}
Compression Middleware:
public class CompressionMiddleware
{
private readonly RequestDelegate _next;
private readonly ICompressionProvider _compressionProvider;
public CompressionMiddleware(RequestDelegate next, ICompressionProvider compressionProvider)
{
_next = next;
_compressionProvider = compressionProvider;
}
public async Task InvokeAsync(HttpContext context)
{
var acceptEncoding = context.Request.Headers["Accept-Encoding"].ToString();
if (acceptEncoding.Contains("br") && _compressionProvider.EncodingName == "br")
{
context.Response.Headers["Content-Encoding"] = "br";
context.Response.Body = _compressionProvider.CreateStream(context.Response.Body, CompressionLevel.Optimal);
}
await _next(context);
}
}
98. Gzip vs Brotli Compression
Gzip Compression:
public class GzipCompressionService
{
public byte[] Compress(byte[] data)
{
using var outputStream = new MemoryStream();
using var gzipStream = new GZipStream(outputStream, CompressionLevel.Optimal);
gzipStream.Write(data, 0, data.Length);
gzipStream.Flush();
return outputStream.ToArray();
}
public byte[] Decompress(byte[] compressedData)
{
using var inputStream = new MemoryStream(compressedData);
using var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress);
using var outputStream = new MemoryStream();
gzipStream.CopyTo(outputStream);
return outputStream.ToArray();
}
}
Brotli Compression:
public class BrotliCompressionService
{
public byte[] Compress(byte[] data)
{
using var outputStream = new MemoryStream();
using var brotliStream = new BrotliStream(outputStream, CompressionLevel.Optimal);
brotliStream.Write(data, 0, data.Length);
brotliStream.Flush();
return outputStream.ToArray();
}
public byte[] Decompress(byte[] compressedData)
{
using var inputStream = new MemoryStream(compressedData);
using var brotliStream = new BrotliStream(inputStream, CompressionMode.Decompress);
using var outputStream = new MemoryStream();
brotliStream.CopyTo(outputStream);
return outputStream.ToArray();
}
}
Compression Comparison:
public class CompressionComparison
{
public void CompareCompression()
{
var data = File.ReadAllBytes("large-file.json");
var gzipService = new GzipCompressionService();
var brotliService = new BrotliCompressionService();
var stopwatch = Stopwatch.StartNew();
var gzipCompressed = gzipService.Compress(data);
var gzipTime = stopwatch.ElapsedMilliseconds;
stopwatch.Restart();
var brotliCompressed = brotliService.Compress(data);
var brotliTime = stopwatch.ElapsedMilliseconds;
Console.WriteLine($"Original size: {data.Length} bytes");
Console.WriteLine($"Gzip: {gzipCompressed.Length} bytes ({gzipTime}ms)");
Console.WriteLine($"Brotli: {brotliCompressed.Length} bytes ({brotliTime}ms)");
}
}
Key Differences:
| Aspect | Gzip | Brotli |
|---|---|---|
| Compression Ratio | Good | Better (10-20% smaller) |
| Speed | Faster compression | Slower compression |
| CPU Usage | Lower | Higher |
| Browser Support | Universal | Modern browsers |
| Best For | Real-time compression | Pre-compressed content |
99. ASP.NET Core Bundling and Minification
WebOptimizer Configuration:
public class BundlingConfig
{
public static void ConfigureBundling(IServiceCollection services)
{
services.AddWebOptimizer(pipeline =>
{
// CSS bundling
pipeline.AddCssBundle("/css/bundle.css",
"wwwroot/css/bootstrap.css",
"wwwroot/css/site.css",
"wwwroot/css/custom.css");
// JavaScript bundling
pipeline.AddJavaScriptBundle("/js/bundle.js",
"wwwroot/js/jquery.js",
"wwwroot/js/bootstrap.js",
"wwwroot/js/site.js");
// Minification
pipeline.MinifyCssFiles("wwwroot/css/**/*.css");
pipeline.MinifyJsFiles("wwwroot/js/**/*.js");
});
}
}
Custom Bundle Configuration:
public class CustomBundleConfig
{
public static void ConfigureCustomBundles(IServiceCollection services)
{
services.AddWebOptimizer(pipeline =>
{
// Environment-specific bundles
if (Environment.IsDevelopment())
{
pipeline.AddJavaScriptBundle("/js/dev-bundle.js",
"wwwroot/js/dev/*.js");
}
else
{
pipeline.AddJavaScriptBundle("/js/prod-bundle.js",
"wwwroot/js/prod/*.js");
}
// Conditional bundling
pipeline.AddJavaScriptBundle("/js/admin-bundle.js",
"wwwroot/js/admin/*.js")
.UseContentRoot()
.AddResponseHeader("Cache-Control", "public,max-age=31536000");
});
}
}
Custom Minification:
public class CustomMinifier : IAssetProcessor
{
public async Task<byte[]> ExecuteAsync(byte[] source, string input)
{
// Custom minification logic
var content = Encoding.UTF8.GetString(source);
// Remove comments
content = Regex.Replace(content, @"//.*$", "", RegexOptions.Multiline);
content = Regex.Replace(content, @"/\*.*?\*/", "", RegexOptions.Singleline);
// Remove unnecessary whitespace
content = Regex.Replace(content, @"\s+", " ");
content = content.Trim();
return Encoding.UTF8.GetBytes(content);
}
}
// Registration
services.AddWebOptimizer(pipeline =>
{
pipeline.AddFiles("text/javascript", "wwwroot/js/custom.js")
.Process(new CustomMinifier());
});
Bundle Tag Helpers:
<!-- In Razor views -->
<weboptimizer-bundle name="css/bundle.css" />
<weboptimizer-bundle name="js/bundle.js" />
<!-- With attributes -->
<weboptimizer-bundle name="css/bundle.css"
asp-append-version="true"
asp-fallback-href="~/css/fallback.css" />
100. Development vs Production Optimizations
Development Optimizations:
public class DevelopmentConfig
{
public static void ConfigureDevelopment(WebApplicationBuilder builder)
{
if (builder.Environment.IsDevelopment())
{
// Detailed error pages
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
// Hot reload
builder.Services.AddRazorPages().AddRazorRuntimeCompilation();
// Detailed logging
builder.Logging.AddConsole();
builder.Logging.AddDebug();
builder.Logging.SetMinimumLevel(LogLevel.Debug);
// CORS for development
builder.Services.AddCors(options =>
{
options.AddPolicy("Development", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
// Disable caching for development
builder.Services.AddResponseCaching(options =>
{
options.MaximumBodySize = 0;
});
}
}
}
Production Optimizations:
public class ProductionConfig
{
public static void ConfigureProduction(WebApplicationBuilder builder)
{
if (builder.Environment.IsProduction())
{
// Performance optimizations
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
});
// Caching
builder.Services.AddResponseCaching();
builder.Services.AddMemoryCache();
builder.Services.AddDistributedRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("Redis");
});
// Security headers
builder.Services.AddHsts(options =>
{
options.MaxAge = TimeSpan.FromDays(365);
options.IncludeSubDomains = true;
options.Preload = true;
});
// Logging optimization
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.AddApplicationInsights();
builder.Logging.SetMinimumLevel(LogLevel.Information);
// Database connection pooling
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"),
sqlOptions => sqlOptions.EnableRetryOnFailure());
});
}
}
}
Environment-Specific Middleware:
public class EnvironmentMiddleware
{
private readonly RequestDelegate _next;
private readonly IWebHostEnvironment _environment;
public EnvironmentMiddleware(RequestDelegate next, IWebHostEnvironment environment)
{
_next = next;
_environment = environment;
}
public async Task InvokeAsync(HttpContext context)
{
if (_environment.IsDevelopment())
{
// Development-specific middleware
context.Response.Headers.Add("X-Environment", "Development");
context.Response.Headers.Add("X-Debug-Info", "Enabled");
}
else if (_environment.IsProduction())
{
// Production-specific middleware
context.Response.Headers.Add("X-Environment", "Production");
context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
context.Response.Headers.Add("X-Frame-Options", "DENY");
context.Response.Headers.Add("X-XSS-Protection", "1; mode=block");
}
await _next(context);
}
}
Configuration Differences:
// appsettings.Development.json
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=SMChat_Dev;Trusted_Connection=true;"
},
"Caching": {
"EnableResponseCaching": false,
"EnableOutputCaching": false
}
}
// appsettings.Production.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ConnectionStrings": {
"DefaultConnection": "Server=prod-server;Database=SMChat_Prod;Integrated Security=true;"
},
"Caching": {
"EnableResponseCaching": true,
"EnableOutputCaching": true,
"CacheDuration": 3600
}
}