When preparing for a .NET developer interview, candidates often face a range of technical questions that assess their understanding of core concepts and practical skills. To help you stand out in your interview, this article consolidates key tasks and topics that interviewers may ask about, including:
- JSON Web Tokens (JWT)
- In-Memory Databases
- Dependency Injection
- Global Filters
- Unit Tests
Understanding these concepts not only prepares you for potential questions but also equips you with the knowledge to build robust, maintainable applications.
1. JWT Tokens: Secure Your Applications
What is a JWT?
JSON Web Tokens (JWT) are a compact and self-contained method for securely transmitting information between parties. They are widely used for authentication and information exchange in web applications. A JWT consists of three parts: header, payload, and signature.
Header: Contains metadata about the token, such as its type and the signing algorithm.
Payload: Holds claims or information you want to transmit, including user data and permissions.
Signature: Ensures the token has not been altered by combining the encoded header, payload, and a secret key.
Example of JWT (JSON Web Token) authentication
In your Startup/Program.cs, apply the code under:
// Configure JWT Authentication
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.EntityFrameworkCore;
using MyTutorial.Models;
using MyTutorial.Services;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Configure Swagger/OpenAPI
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Register IHttpContextAccessor
builder.Services.AddHttpContextAccessor();
// Register IUserGroupIdProvider
builder.Services.AddTransient<IUserGroupIdProvider, UserGroupIdProvider>();
// Add In-Memory DbContext
builder.Services.AddDbContext<MySecuredResourceDbContext>(options =>
options.UseInMemoryDatabase("TestDatabase"));
// Configure JWT Authentication
builder.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 = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
2. In-Memory Database: Fast and Efficient Testing
What is an In-Memory Database?
An in-memory database is a database management system that primarily relies on memory for data storage. This type of database is particularly useful for testing and development, as it allows for quick data manipulation without the need for a physical database setup.
Example of Using an In-Memory Database with Entity Framework Core
In your Startup/Program.cs, register the in-memory database:
// Add In-Memory DbContext
builder.Services.AddDbContext<MySecuredResourceDbContext>(options =>
options.UseInMemoryDatabase("TestDatabase"));
3. Dependency Injection: Building Maintainable Applications
What is Dependency Injection?
Dependency Injection (DI) is a design pattern used to implement Inversion of Control (IoC), allowing for better separation of concerns and making code easier to test and maintain. In .NET, DI is built into the framework.
Example of Dependency Injection
This interface defines a contract for a service that retrieves the user group ID from the authenticated user's claims.
using System;
using System.Linq;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
namespace MyTutorial.Services
{
public interface IUserGroupIdProvider
{
int GetUserGroupId();
}
public class UserGroupIdProvider : IUserGroupIdProvider
{
private const string UserGroupIdClaimType = "user_group_id";
private readonly IHttpContextAccessor _httpContextAccessor;
public UserGroupIdProvider(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
public int GetUserGroupId()
{
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext == null || !httpContext.User.Identity.IsAuthenticated)
{
throw new InvalidOperationException("User is not authenticated.");
}
var userGroupIdClaim = httpContext.User.Claims.FirstOrDefault(c => c.Type == UserGroupIdClaimType);
if (userGroupIdClaim == null)
{
throw new InvalidOperationException($"Claim '{UserGroupIdClaimType}' not found.");
}
if (!int.TryParse(userGroupIdClaim.Value, out int userGroupId))
{
throw new InvalidOperationException($"Claim '{UserGroupIdClaimType}' is not a valid integer.");
}
return userGroupId;
}
}
}
In Startup/Program.cs, register your service with the DI container:
// Register IUserGroupIdProvider
builder.Services.AddTransient<IUserGroupIdProvider, UserGroupIdProvider>();
4. Global Filters: Cross-Cutting Concerns
What is a Global Filter?
Global filters in ASP.NET Core are middleware components that can be applied to all requests or specific types of requests, allowing for cross-cutting concerns such as logging, authorization, and filtering.
Example of Setting Up a Global Filter inside OnModelCreating
Configures the entity model, applying a global query filter to restrict results based on the user group ID obtained from the IUserGroupIdProvider.
using Microsoft.EntityFrameworkCore;
namespace MyTutorial.Models
{
public class MySecuredResourceDbContext : DbContext
{
private readonly IUserGroupIdProvider _userGroupIdProvider;
public MySecuredResourceDbContext(DbContextOptions<MySecuredResourceDbContext> options, IUserGroupIdProvider userGroupIdProvider)
: base(options)
{
_userGroupIdProvider = userGroupIdProvider ?? throw new ArgumentNullException(nameof(userGroupIdProvider));
}
public DbSet<ResourceEntity> ResourceEntities { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ResourceEntity>()
.HasQueryFilter(e => e.UserGroupId == _userGroupIdProvider.GetUserGroupId());
}
}
public class ResourceEntity
{
public int Id { get; set; }
public int UserGroupId { get; set; }
public int Value { get; set; }
}
}
5. Unit Tests: Keep Your Code Healthy
What is a Unit Test?
A unit test is a type of software testing that focuses on verifying the functionality of a specific section of code, typically at the level of individual functions or methods. The main goal of unit testing is to validate that each unit of the software performs as expected.
Example of Unit Tests
using System.Collections.Generic;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using MyTutorial.Models;
using MyTutorial.Services;
using Xunit;
namespace TestProject
{
public class UnitTest1
{
private ServiceProvider _serviceProvider;
public UnitTest1()
{
var services = new ServiceCollection();
services.AddHttpContextAccessor();
services.AddTransient<IUserGroupIdProvider, UserGroupIdProvider>();
services.AddDbContext<MySecuredResourceDbContext>(options =>
options.UseInMemoryDatabase("TestDatabase"));
_serviceProvider = services.BuildServiceProvider();
}
[Fact]
public void Should_Return_UserGroupId_When_Claim_Exists()
{
// Arrange
var userGroupId = 1;
var claims = new[]
{
new Claim("user_group_id", userGroupId.ToString())
};
var identity = new ClaimsIdentity(claims, "test");
var user = new ClaimsPrincipal(identity);
var httpContextAccessor = _serviceProvider.GetService<IHttpContextAccessor>();
httpContextAccessor.HttpContext = new DefaultHttpContext { User = user };
var userGroupIdProvider = _serviceProvider.GetService<IUserGroupIdProvider>();
// Act
var result = userGroupIdProvider.GetUserGroupId();
// Assert
Assert.Equal(userGroupId, result);
}
[Fact]
public void Should_Filter_Data_When_UserGroupId_Is_Provided()
{
// Arrange
var userGroupId = 1;
var claims = new[]
{
new Claim("user_group_id", userGroupId.ToString())
};
var identity = new ClaimsIdentity(claims, "test");
var user = new ClaimsPrincipal(identity);
var httpContextAccessor = _serviceProvider.GetService<IHttpContextAccessor>();
httpContextAccessor.HttpContext = new DefaultHttpContext { User = user };
var context = _serviceProvider.GetService<MySecuredResourceDbContext>();
context.ResourceEntities.AddRange(new List<ResourceEntity>
{
new ResourceEntity { Id = 1, UserGroupId = userGroupId, Value = 100 },
new ResourceEntity { Id = 2, UserGroupId = 2, Value = 200 }
});
context.SaveChanges();
// Act
var filteredEntities = context.ResourceEntities.ToList();
// Assert
Assert.Single(filteredEntities);
Assert.Equal(userGroupId, filteredEntities[0].UserGroupId);
}
}
}
Should_Return_UserGroupId_When_Claim_Exists: Tests whether the user group ID is correctly retrieved from the claims.
Should_Filter_Data_When_UserGroupId_Is_Provided: Tests whether the data is filtered correctly based on the user group ID.
Conclusion
Preparing for a .NET developer interview involves understanding several key concepts, including JWT tokens, in-memory databases, dependency injection, global filters, and unit tests. By consolidating your knowledge of these topics, you position yourself as a well-rounded candidate capable of building robust, maintainable applications. Mastering these concepts not only enhances your interview performance but also equips you for success in your career as a .NET developer. Good luck!
Love C#
Top comments (1)
These concepts have been asked many times in my previous interviews. 😉