ASP.NET Core Startup Validation Process - Complete Guide
Validation is an important part of any ASP.NET Core application. You want to validate user input on the client side with JavaScript/TypeScript and on the server side in your C# code to make sure bad or malformed data doesn’t make its way into your database or cause errors in your application.
In this post, we’ll focus specifically on validating startup configuration and service registration in ASP.NET Core. When your application starts, the Startup class’s ConfigureServices method runs and registers all your app’s services. The Configure method then sets up your request processing pipeline. It’s important to validate that things are configured correctly at startup before your app starts handling requests.
We’ll cover several approaches to validate your app on startup:
- Using the built-in ASP.NET Core hosting infrastructure with IHostingEnvironment
- Custom validation in Startup with ValidateOnStartup
- Validation attributes on startup services
- Third-party libraries like Scrutor
For each approach, we’ll see code examples and discuss the pros and cons. By the end, you’ll have several good options to implement startup validation in your ASP.NET Core web apps and APIs.
Built-in IHostingEnvironment Validation
The ASP.NET Core hosting interfaces include an IHostingEnvironment
interface that provides useful startup validation capabilities out of the box.
It includes an EnvironmentName
property that indicates the current environment name like "Development," "Staging," or "Production". You can check EnvironmentName
in ConfigureServices
or Configure
and throw an exception if incorrect:
public void ConfigureServices(IServiceCollection services)
{
if (env.EnvironmentName != "Development")
{
throw new Exception("Invalid environment!");
}
// register services
}
It validates that the code is not accidently running in the wrong environment. IHostingEnvironment
also provides an IsEnvironment
method to check for specific environments:
if (!env.IsEnvironment("Development")
{
// throw error
}
Pros:
- Simple validation using built-in ASP.NET Core interfaces
- Can validate environment name in ConfigureServices and Configure
Cons:
- Very basic — only validates environment name
- Still allows invalid services and configs to be registered
Overall, IHostingEnvironment
provides a simple way to check for the proper environment on startup. But it doesn't allow validating other aspects of your configuration and services.
Custom Validation in Startup
For more advanced startup validation, you can create custom validation code directly in your Startup class’s ConfigureServices
and Configure
methods.
Here is an example:
public void ConfigureServices(IServiceCollection services)
{
// custom validation
ValidateDatabaseSettings(Configuration);
// register services
services.AddDbContext<AppDbContext>(...);
}
private void ValidateDatabaseSettings(IConfiguration config)
{
// check connection strings
var cnn = config.GetConnectionString("DefaultConnection");
if (string.IsNullOrEmpty(cnn)
{
throw new Exception("DefaultConnection missing!");
}
// check provider
if (cnn.IndexOf("SQLServer") < 0)
{
throw new Exception("Invalid DB provider!");
}
}
This allows you to call private helper methods like ValidateDatabaseSettings
to validate important app configurations and settings.
You can also validate registered services directly in ConfigureServices
once they are registered:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
ValidateMvcServices(services);
}
private void ValidateMvcServices(IServiceCollection services)
{
if (!services.Any(x => x.ServiceType == typeof(Controllers.HomeController))
{
throw new Exception("Missing controller!");
}
}
This validates that essential framework services like MVC controllers are correctly registered before the app starts.
Pros:
- Complete flexibility to validate app config and services
- Can integrate with other validation libraries
Cons:
- Puts large validation code blocks directly in the Startup class
- No separation of validation code from main app code
The main downside is that the validation code ends up directly coupled to the Startup class configuration logic. That reduces the separation of concerns.
Validation Attributes on Startup Services
A cleaner approach is to create custom validation attributes that can be applied to services registered in ConfigureServices
. Here is an example:
First, define a custom validation attribute:
public class StartupServiceValidator : Attribute
{
Action<IServiceProvider> Validate;
public StartupServiceValidator(Action<IServiceProvider> validate)
{
Validate = validate;
}
}
This attribute takes an action delegate that will handle validation.
Next, apply it to startup services:
[StartupServiceValidator(ValidateDbContext)]
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(...);
}
private void ValidateDbContext(IServiceProvider provider)
{
using var scope = provider.CreateScope();
var context = scope.ServiceProvider.GetService<AppDbContext>();
if (context == null)
throw new Exception("DbContext not registered!");
}
Here the StartupServiceValidator
attribute runs the ValidateDbContext
method after AppDbContext
is registered to validate it instantiated correctly.
Multiple validations can be defined on different methods by creating multiple StartupServiceValidator
attributes.
Pros:
- Decouples validation code from main startup logic
- Allows fine-grained control over validations
Cons:
- It is still somewhat cumbersome to define validations
- It is easy to forget to add attributes during later code changes
The attribute approach keeps validations separated from primary startup logic. However, creating and remembering to add attributes during code changes can be cumbersome.
Third-Party Libraries
Finally, there are some nice third-party libraries created specifically for startup validation in ASP.NET Core. One great option is Scrutor by Khellang.
Scrutor enhances ASP.NET Core’s built-in service registration to allow registering validators to access the full IServiceProvider
.
To validate with Scrutor:
- Install NuGet package
- Call
Scrutor.Scan
instead ofservices.Scan
in ConfigureServices - Register startup validators using Scrutor’s
IStartupValidator
interface
Here is an example:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.Scan(scan => scan
.FromAssemblyOf<Startup>()
.AddClasses()
.AsSelf()
.WithTransientLifetime());
services.ScrutorScan(scan => scan
.FromAssemblyOf<Startup>()
.AddClasses()
.AsSelf()
.WithTransientLifetime())
.RegisterValidators();
}
This registers Scrutor instead of the default service scanning.
Next, create validators:
public class DbContextValidator : IStartupValidator
{
public void Validate(IServiceProvider provider)
{
// validate db context
}
}
public class MvcValidator : IStartupValidator
{
public void Validate(IServiceProvider provider)
{
// validate mvc
}
}
These implement Scrutor’s IStartupValidator
interface and have full access to the IServiceProvider
to validate app services and configuration. Scrutor discovers them automatically on startup.
Pros:
- Simple and discoverable validation setup
- No attributes required
Cons:
- Additional third-party dependency
Scrutor provides an easy yet flexible way to implement startup validation in ASP.NET Core without a lot of custom code. The main downside is adding an extra NuGet dependency.
Conclusion
Validating startup configuration is important to catch errors early before an ASP.NET Core app starts handling requests.
There are several good approaches built in or available from third parties:
- IHostingEnvironment for basic environment checks
- Custom validation code in Startup
- Validation attributes on startup services
- Third-party libraries like Scrutor
Scrutor makes it easy to create discoverable validators with full access to app services for validation without a lot of glue code.
Overall, there are good options to fit different needs and complexity levels for validating app startup in ASP.NET Core. Proper validation will catch errors early and help avoid nasty production issues down the road!