How to work with Startup class in ASP.NET Core

In this article, let's talk in detail about the Startup class and its methods, parameters etc in a typical ASP.NET Core application

What is Startup.cs in ASP.NET Core?

A Startup class is a simple C# class used to configure and construct an application request pipeline.

It is one of the very first components which are picked up and executed by the ASP.NET Core runtime during application bootstrapping.

We can register abstractions and their implementations for the ASP.NET Core container to inject the instances of these implementations in place of the abstractions wherever requested.

What is a Request Pipeline?

The collection on which these classes are registered is called a service pipeline and the classes registered in this manner are called services. We can also define a series of components which are to be executed in order while processing an incoming request.

This is called a request pipeline and every single request passes through each and every component registered in the request pipeline before transforming into a response.

Any component which is tied to the request pipeline is called a Middleware .

Generally, the class comes along with the boilerplate code for all the web templates (web api, mvc) provided in Dotnet Core CLI.

It is linked up to the application host at the main method while building the web host using the UseStartup() method, which takes a type parameter of the Startup class.

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }


    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

What does a Startup class contain?

A typical startup class consists of two methods along with an optional injectable constructor. The Dotnet Core host allows injecting only the below three services into the constructor by default:

IHostEnvironment

It is an interface for accessing the hosted application file system (typically the contents of the wwwroot folder), using which we can access the file system contents and perform I/O operations.

This interface was introduced starting from ASP.NET Core 3.1 and replaced the existing IHostingEnvironment used in ASP.NET Core 2.2 and below.

IConfiguration

It is an interface for accessing the application configuration from a variety of sources such as appsettings json, keyvaults, environmental variables and so on.

It is a dictionary of keys and values with the keys being the key names used in appsettings and Values relating to the values contained for the respective keys. appsettings can also contain groups of configurations called Sections.

These are accessed by the GetSection() method of the IConfiguration instance.

In a similar way, the ConnectionStrings section of the appsettings json can be accessed via the IConfiguration using the method GetConnectionString()

Example

configuration.GetConnectionString("DefaultConnection")

ILoggerFactory

It is an interface for logging framework provided by Dotnet Core. It can be used to write logs onto various sources, like a database, file, or cloud logging providers like Azure Application Insights or AWS CloudWatch.

The loggerFactory segregates logging traces into Information, Debug and Error; which defines the type of log being made – Verbose, Debug purpose or Error logs respectively.

We can also maintain a separate Startup class for each environment – for Development, Staging or Release.

A Startup class for a specific environment must be appended with the environment name.

For example, Startup class for a Development environment would be StartupDevelopment.cs and the standalone Startup class represents a Startup class for Production / Release.

Use of ConfigureServices and Configure methods in Startup Class

The Startup class contains two methods: ConfigureServices() and Configure()

ConfigureServices(IServiceCollection services)

The ConfigureServices method takes in a single parameter of type IServiceCollection and is a void method. The parameter IServiceCollection object is used to create and add new services.

We register classes as services to be injected by the container whenever some other component requests for an instance.

We register a class as a service along with its abstraction and the container injects the instance of the concrete class in place of the abstraction wherever requested.

This is called Dependency Injection which is a phenomenon following the principle of Dependency Inversion.

The services are added onto the pipeline using the Use<> method onto the IServiceCollection instance. Based on how a service is registered, the lifetimes for which the service object is persisted in the container varies.

We can use the ConfigureServices method to define predefined services like MVC, Routing, Database, Authentication and so on.

It can also be used to register user-defined services with varying lifetimes as the singleton, transient or scoped services.

Configure(IApplicationBuilder app)

The Configure() method configures an application pipeline and defines the steps a request needs to pass through, before reaching the endpoint.

It takes a parameter of type IApplicationBuilder, and we can also pass the services which are defined in the ConfigureServices method as additional parameters.

These stages are called Middleware and each Middleware is configured to execute a specific functionality and hold a single responsibility.

The order of execution of these Middlewares can be varied by the order in which they are added to the request pipeline within the configure method.

The IApplicationBuilder instance is passed on to the Configure method by the Hosting and not registered to the service container.

The components which are to be added onto the pipeline are added to the IApplicationBuilder instance using the Use<> method.

The Dotnet Core library also provides specific methods for adding predefined middlewares such as Routing, Https Redirection, Authentication, Authorization, Exception Handling and so on.

Any middleware component added to the pipeline using the Use<>() method takes a delegate function of two parameters httpContext and Task.

The consequent Middlewares are invoked from one another by using the Invoke method of the RequestDelegate type, similar to a Chain of Responsibility

A complete Startup class looks like below (as used in ContainerNinja.CleanArchitecture – my .NET 6 Boilerplate that demonstrates all the Industry Standard best practices. Do check it out and leave a Star!)

namespace ContainerNinja
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }


        public IConfiguration Configuration { get; }


        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors();


            services.AddPersistence(Configuration);
            services.AddCore(Configuration);


            // prevents Mvc to throw 400 on invalid RequestBody
            // this is since we're using Fluent to do the same
            // within the Action Method
            services.Configure<ApiBehaviorOptions>(options =>
            {
                options.SuppressModelStateInvalidFilter = true;
            });


            services.AddJwtBearerAuthentication();


            //services.AddResponseCaching();


            services.AddControllers(options =>
            {
                options.Filters.Add<AddHandlerHostHeaderResponseFilter>();
            });


            services.AddSwaggerWithVersioning();
        }


        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }


            app.UseCors(builder =>
            {
                builder.AllowAnyHeader()
                .AllowAnyOrigin()
                .AllowAnyMethod();
            });


            app.UseSwaggerWithVersioning(provider);


            app.UseRouting();


            app.UseAuthentication();


            app.UseAuthorization();


            //app.UseCaching();


            app.UseEndpoints(endpoints =>
            {
                endpoints.MapDefaultControllerRoute();
            });
        }
    }
}

If a service is not injected from the parameter of the Configure method, we can still request the container to provide an instance of the required service by calling GetRequiredService() method as below.

public void Configure(IApplicationBuilder app)
{
    // the IWebHostEnvironment instance can be
    // created ready to use without injection
    using (var scope = app.ApplicationServices.CreateScope())
    {
        var service = scope.ServiceProvider
            .GetRequiredService<IWebHostEnvironment>();
    }
}

Differences between ConfigureServices and Configure method in .NET

ConfigureServicesConfigure
configures and registers services within the application’s dependency injection containerconfigures the application’s request pipeline, sets up middleware components, and defines the order of execution
called only once at application startup before the Configure methodcalled after the ConfigureServices method, and is invoked for each HTTP request that the application receives
typically has a void return typecan also return an IApplicationBuilder to enable further chaining of configurations
It is a required method to be provided for registering servicesIt can be optional

How to Configure Services without Startup

If using a startup class is not the case, then we can define ConfigureServices and Configure methods at the Host building step itself using the convenience methods provided.

public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }


    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
                webBuilder.ConfigureServices(services =>
                {
                    // register services here
                }).Configure(app =>
                {
                    // tag to request pipeline here
                });
            });
}

Configuring Services and Middlewares in .NET 6

Starting from .NET 6, we can register services and middlewares within the Program class. We can create an application host instance using WebApplication.CreateBuilder() method and add services and middlewares as needed.

A typical code looks like below –

using StudentGrades.WebAPI.Data;


var builder = WebApplication.CreateBuilder(args);


// Add services to the container.


builder.Services.AddControllers();


builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();


builder.Services.AddSqlServer<GradesdbContext>(builder.Configuration.GetConnectionString("DefaultConnection"));


var app = builder.Build();


// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}


app.UseHttpsRedirection();


app.UseAuthorization();


app.MapControllers();


app.Run();

This removes the need for a separate Startup class in newer .NET applications, but the old approach still works fine.

In this way, the Startup class in an ASP.NET Core application provides us with options to register services and construct the request pipeline with desired components for building powerful web applications.


Buy Me A Coffee

Found this article helpful? Please consider supporting!

Ram
Ram

I'm a full-stack developer and a software enthusiast who likes to play around with cloud and tech stack out of curiosity. You can connect with me on Medium, Twitter or LinkedIn.

Leave a Reply

Your email address will not be published. Required fields are marked *