10 most frequently asked technical questions in .NET Core interviews

Abnoan Muniz
.Net Programming
Published in
13 min readFeb 10, 2023
.net | most frequently asked technical questions | technical questions | interview Questions | technical questions .net | .net core

Welcome to the world of .NET Core! You've come to the right place if you're preparing for an interview and want to brush up on your knowledge of this popular framework. In this post, we'll cover the ten most frequently asked technical questions in .NET Core interviews, so you can impress your interviewer and land that dream job.

1: What is the difference between .NET and .NET Core?

ASP.NET is a mature framework that has been around for over a decade. It is built on the .NET Framework and runs on Windows. ASP.NET Core, on the other hand, is a newer and more modular version of ASP.NET. It is built on the .NET Core runtime and can run on Windows, Linux, and macOS.

One of the main differences between the two is that ASP.NET Core is more lightweight and efficient than ASP.NET. It also has a smaller footprint and is optimized for cloud-based environments. Additionally, ASP.NET Core supports modern web standards and technologies such as WebSockets, HTTP/2, and the latest versions of C#.

Here's an example of code that demonstrates one difference between the two frameworks:

//ASP.NET
var server = new HttpServer(new HttpConfiguration());
server.OpenAsync().Wait();
Console.WriteLine("ASP.NET Server running...");

//ASP.NET Core
var host = new WebHostBuilder()
.UseKestrel()
.UseStartup<Startup>()
.Build();

host.Run();
Console.WriteLine("ASP.NET Core Server running...");

2: What is the Startup/Program class for?

The Startup class and Program serves as the entry point for an ASP.NET Core application. The Startup is where you configure the application's services and the request pipeline. The Program is where you build and run the web host for the application.

The Startup typically contains two methods: ConfigureServices and Configure. The ConfigureServices is where you register the application's services, such as dependency injection, logging, and database services. The Configure is where you define the middleware pipeline that processes the incoming requests and sends the responses.

The Program typically contains a Main that builds and runs the web host. This method creates an instance of the WebHostBuilder , sets the startup class, and starts the host.

Here is an example of a simple Startup and Program classes using .NET 5:

// Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Register services
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Configure the request pipeline
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello, ASP.NET Core!");
});
});
}
}

// Program.cs
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseStartup<Startup>()
.Build();

host.Run();
}
}

In .NET 6, the Startup and Program classes have become more modular and composable, allowing more flexibility in configuring and running the application. For example, you can specify different Startup classes for different environments and select other Program classes for other platforms.

3: How to add settings (key-value pair)?

One way to add configuration settings (key-value pairs) to an ASP.NET Core application is by using the IConfiguration . The IConfiguration service allows you to access configuration data from various sources, such as JSON files, environment variables, and command-line arguments.

Here's an example of how you can add configuration settings using the IConfiguration:

1-In the Startup , add a constructor that takes an IConfiguration parameter:

    public class Startup
{
private readonly IConfiguration _config;

public Startup(IConfiguration config)
{
_config = config;
}

2-In the ConfigureServices , register the IConfiguration:

    public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSingleton(_config);
}

3-In the Program , add the configuration files to the WebHostBuilder:

    public class Program
{
public static void Main(string[] args)
{
var host = new HostBuilder()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.UseKestrel();
webBuilder.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
});
})
.Build();

host.Run();
}
}

The Startup constructor takes a IConfiguration parameter, which allows the class to access the configuration data. The IConfiguration is then registered in the ConfigureServices So that it can be injected into other parts of the application.

At the Program , the WebHostBuilder is configured to add a JSON file called "appsettings.json" as a configuration source, but you could also add other sources like environment variables or command-line arguments.

Once you've set up the configuration, you can access the values using the IConfiguration, like this:

    var mySetting = _config["MySetting"];

4: How to implement caching?

That can be achieved using the IMemoryCache . It provides an in-memory cache that can store frequently accessed data, reducing the time it needs to be retrieved from a slower source.

Here's an example of how you can implement caching in an ASP.NET Core application:

1-In the Startup , add the IMemoryCache in the ConfigureServices:

    public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddMemoryCache();
}

2-In a controller, you can use them IMemoryCache to cache the data

    public class MyController : Controller
{
private readonly IMemoryCache _cache;

public MyController(IMemoryCache cache)
{
_cache = cache;
}

[HttpGet("data")]
public IActionResult GetData()
{
// Try to get the data from the cache
if (!_cache.TryGetValue("MyData", out List<MyData> data))
{
// If the data is not in the cache, retrieve it from the data source
data = GetDataFromDataSource();

// Set the cache options
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds(30));

// Save the data in the cache
_cache.Set("MyData", data, cacheEntryOptions);
}

return Ok(data);
}
}

In this example, the IMemoryCache is added to the ConfigureServices So that it can be injected into the controller. In the GetData , the code first tries to get the data from the cache using the TryGetValue . If the data is not in the cache, the code retrieves it from the data source and saves it in the cache using the Set.

You can also set a sliding expiration time for the cache data to automatically remove it from the cache after a certain period has passed.

5: How to implement logging?

That can be achieved using the ILogger. It is a built-in logging service that can log messages to various destinations, such as the console, file, or database.

Here's an example of how you can implement logging in an ASP.NET Core application:

1-In the Startup, add the ILoggerin the ConfigureServices:

    public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddLogging(loggingBuilder =>
{
loggingBuilder.AddConsole();
loggingBuilder.AddDebug();
});
}

2-In a controller, you can use the ILoggerto log messages:

    public class MyController : Controller
{
private readonly ILogger<MyController> _logger;

public MyController(ILogger<MyController> logger)
{
_logger = logger;
}

[HttpGet("data")]
public IActionResult GetData()
{
_logger.LogInformation("Retrieving data from the data source");
var data = GetDataFromDataSource();
_logger.LogInformation("Data retrieved successfully");
return Ok(data);
}
}

In this example, the ILoggeris added to the ConfigureServicesSo that it can be injected into the controller. In the GetData action method, the code uses the LogInformation to log messages about retrieving the data from the data source.

You can also use other logging levels like LogDebug, LogError, LogWarning.

  • LogDebug It is used for those only useful during development, such as detailed information about the application's inner workings.
  • LogError It is used to indicate an error has occurred. These messages are typically used to track and troubleshoot issues with the application.
  • LogWarning It is used to indicate a potential issue that should be investigated. These messages are typically used to track and troubleshoot issues with the application.
  • LogInformation It is used for messages that provide general information about the application's behavior. These messages are typically used to track and troubleshoot issues with the application and to monitor the overall health of the application.

6: How to implement routines?

Implementing background tasks or "scheduled jobs" in an ASP.NET Core application is a common requirement, and there are several ways to achieve this.

One popular way is to use the IHostedService. It is a service that runs in the background and can be used to perform background tasks.

Here's an example of how you can implement a scheduled job using IHostedService:

1-Create a new class that implements IHostedService and overrides the StartAsync and StopAsync methods:

    public class MyBackgroundService : IHostedService
{
private readonly ILogger<MyBackgroundService> _logger;
private Timer _timer;

public MyBackgroundService(ILogger<MyBackgroundService> logger)
{
_logger = logger;
}

public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("MyBackgroundService starting.");

_timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));

return Task.CompletedTask;
}

private void DoWork(object state)
{
_logger.LogInformation("MyBackgroundService is working.");
}

public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("MyBackgroundService is stopping.");

_timer?.Change(Timeout.Infinite, 0);

return Task.CompletedTask;
}
}

2-In the Startup , add the IHostedService in the ConfigureServices:

    public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddHostedService<MyBackgroundService>();
}

In this example, the MyBackgroundService implements the IHostedService and uses a timer to perform a task every 5 seconds. The StartAsync starts the timer and the StopAsync stops the timer.

You can also use third-party libraries like Hangfire or Quartz.Net to help with scheduled jobs.

It's essential to keep in mind that running background tasks in your application can impact performance, so it's necessary to test and optimize your background tasks to ensure they don't negatively impact the performance of your application.

7: How to implement dependency injection, and what are the differences between Transient, Scoped, and Singleton?

Dependency injection (DI) is a design pattern that allows a class to receive its dependencies from an external source rather than create them. ASP.NET Core provides built-in support for dependency injection, which makes it easy to implement in your application.

Here's an example of how to implement dependency injection in an ASP.NET Core application:

1 — Create an interface for a service that you want to inject:

    public interface IMyService
{
void DoSomething();
}

2 — Create a class that implements the interface:

    public class MyService : IMyService
{
public void DoSomething()
{
// Do something here
}
}

3 — In the Startup, add the service in the ConfigureServices:

    public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddTransient<IMyService, MyService>();
}

In this example, AddTransient is used to register the service, which tells the DI container to create a new service instance each time it is requested.

There are three-lifetime options available in ASP.NET Core:

  • Transient: A new instance is created every time the service is requested.
  • Scoped: A new instance is created once per request.
  • Singleton: A single instance is created for the lifetime of the application.

It's essential to choose the right lifetime option based on the requirements of your application. For example, if a service is stateless and thread-safe, it can be registered as a singleton. On the other hand, if a service is stateful or not thread-safe, it should be registered as transient or scoped.

8: What HTTP response codes and when to use in a REST API?

In an API REST, HTTP response codes are used to indicate the status of a request. Here are some of the most common HTTP response codes and when to use them:

  • 200 OK: Indicates that the request was successful. This is the most common response code and should be used when a request returns a successful result.
  • 201 Created: Indicates that a new resource was created due to the request. This should be used when a request makes a new resource, such as a new user account.
  • 204 No Content: Indicates that the request was successful, but there is no content to return. This should be used when a request updates a resource, but the response has no content to return.
  • 400 Bad Request: Indicates that the request was invalid. This should be used when the client sends a request with invalid parameters or a missing required field.
  • 401 Unauthorized: Indicates that the request requires authentication. This should be used when a client tries to access a protected resource without providing valid authentication credentials.
  • 403 Forbidden: Indicates that the client does not have the necessary permissions to access the resource. This should be used when clients try to access a resource they are not authorized to access.
  • 404 Not Found: Indicates that the requested resource could not be found. This should be used when the client requests a resource that does not exist.
  • 500 Internal Server Error: This indicates that an error occurred on the server. This should be used when there is an unexpected error on the server, such as a database connection error.

It's essential to use the appropriate HTTP response codes in an API REST to provide clear and meaningful feedback to the client.

9: How to implement validation?

ASP.NET Core provides built-in model validation support, making it easy to implement in your application. Here's an example of how to implement validation in an ASP.NET Core controller:

1-Create a POCO (Plain Old CLR Object) class that represents the request data and define validation rules using data annotations:

    public class CreateProductRequest
{
[Required]
[StringLength(50, MinimumLength = 3)]
public string Name { get; set; }

[Range(1, 100)]
public decimal Price { get; set; }
}

2-In the controller action, use the ModelState to check if the requested data is valid:

    [HttpPost]
public IActionResult CreateProduct([FromBody] CreateProductRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

// Create the product
var product = new Product
{
Name = request.Name,
Price = request.Price
};

return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
}

In this example, we use the Required attribute to indicate that the Name is required, and the StringLength to specify that the Name must have a minimum length of 3 and a maximum length of 50. Similarly, we use the Range to indicate that the Price must be between 1 and 100.

In case of invalid data, the ModelState.IsValid will be false, and the controller will return BadRequest with the ModelState which contains the error messages.

It's important to note that validation can also be implemented on the client side. Still, it's also important to validate it server-side to secure the API and avoid malicious requests.

Another way to create validation is with HTTP Filter

That is how to perform additional logic before or after a controller action is executed in ASP.NET Core. For example, one everyday use case for filters is implementing validation logic applied across multiple controllers or actions.

Here's an example of how to create an HTTP filter to validate that a request contains a valid API key:

1-Create a new class that inherits from ActionFilterAttribute:

    public class ValidateApiKeyAttribute : ActionFilterAttribute
{
private readonly string _apiKey;

public ValidateApiKeyAttribute(string apiKey)
{
_apiKey = apiKey;
}
}

2-Override the OnActionExecuting method to perform the validation:

    public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.HttpContext.Request.Headers.TryGetValue("ApiKey", out var apiKey) ||
apiKey != _apiKey)
{
context.Result = new UnauthorizedResult();
}
}

3-Apply the filter to the controllers or actions that you want to protect:

    [ValidateApiKey("secret-key")]
public class ProductsController : Controller
{
// ...
}

In this example, we created an ValidateApiKeyAttribute that inherits from ActionFilterAttribute. The class takes an API key as a constructor parameter and checks if the ApiKey header in the request matches it. If it doesn't fit, the filter sets the Result of the ActionExecutingContext to an UnauthorizedResult, which will return a 401 Unauthorizedto the client.

HTTP filters are a powerful way to implement cross-cutting concerns in your ASP.NET Core application. They allow you to encapsulate common logic, such as validation or exception handling, in a reusable and composable way.

10: How to implement unit tests, and what are the benefits?

Unit testing is a software testing method where individual units or components of a software application are tested in isolation. In ASP.NET Core, unit tests can be written using a variety of testing frameworks such as MSTest, xUnit, and NUnit.

To implement unit tests in an ASP.NET Core application, use the built-in test project template in Visual Studio or a .NET test runner like dotnet test.

Here's an example of how to write a unit test using xUnit for a simple calculator class:

1-Create a new test project and reference the project you want to test. You will also need to install the xUnit NuGet package.

2-Create a new test class and add the [Fact] attribute to the method you want to test.

    public class CalculatorTests
{
[Fact]
public void Add_ShouldReturnSumOfTwoNumbers()
{
// Arrange
var calculator = new Calculator();

// Act
var result = calculator.Add(1, 2);

// Assert
Assert.Equal(3, result);
}
}

In this example, we created a test class called CalculatorTests and a test method called Add_ShouldReturnSumOfTwoNumbers. Inside this method, we arrange the test by creating a new instance of the calculator, and then we act by calling the Add method and passing in 1 and 2 as the arguments. Finally, we assert that the result Add is equal to 3.

Benefits of unit testing include:

  1. Improved code quality: Helps to identify and fix bugs early in the development process before they have a chance to become more complex and costly to repair. This leads to higher-quality code that is more robust and less prone to errors.
  2. Increased confidence in code changes: Provides a safety net that ensures that changes to the code do not break existing functionality. This increases the confidence of developers to make changes to the legend, knowing that the unit tests will catch any issues.
  3. Better design and maintainability: This helps ensure the code is designed to make it easy to test. This often leads to a better overall design and architecture of the code, which makes it more maintainable in the long run.
  4. Faster development: Helps to automate the testing process and reduces the need for manual testing. This means developers can work more efficiently, knowing the code is being tested automatically.
  5. Easy to debug: This makes it easy to identify the problem when a test fails, as it focuses on a specific code unit. This makes debugging faster and more efficient.
  6. Improved collaboration and communication: Make the development process more transparent and collaborative. When developers write unit tests, they effectively document how the code is supposed to behave, which helps improve communication and collaboration between team members.
  7. Better documentation: It can serve as a form of documentation for the code, providing insights into how the code is supposed to behave and how it is intended to be used.
  8. Greater scalability: This helps identify and fix issues early on before they become more complex and costly. This helps ensure the code is more scalable and can handle increasing users or change.
  9. Better performance: Identify performance bottlenecks early and improve the code's performance.
  10. Enhance the Quality of the product: Improve the Quality of the product and ensure it meets the customers' requirements.

Thanks for reading! Before you go:

If you find this article helpful, please consider giving claps and following me. 👏👉

Take advantage of my other stories! 👇🔽

--

--

Abnoan Muniz
.Net Programming

Senior .NET Developer, Passionate about problem-solving. Support me: https://ko-fi.com/abnoanmuniz, Get in touch: linktr.ee/AbnoanM