DEV Community

Cover image for 5 Key Concepts Every .NET Developer Should Know for Job Interviews
David Au Yeung
David Au Yeung

Posted on • Edited on

5 Key Concepts Every .NET Developer Should Know for Job Interviews

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:

  1. JSON Web Tokens (JWT)
  2. In-Memory Databases
  3. Dependency Injection
  4. Global Filters
  5. 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();

Enter fullscreen mode Exit fullscreen mode

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"));
Enter fullscreen mode Exit fullscreen mode

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;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In Startup/Program.cs, register your service with the DI container:

// Register IUserGroupIdProvider
builder.Services.AddTransient<IUserGroupIdProvider, UserGroupIdProvider>();
Enter fullscreen mode Exit fullscreen mode

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; }
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
auyeungdavid_2847435260 profile image
David Au Yeung

These concepts have been asked many times in my previous interviews. 😉