Exploring AddControllers() method in AspNetCore (.NET 7)

Exploring AddControllers() method in AspNetCore (.NET 7)

Introduction

In our previous posts we have been looking into the source code of AspNetCore to understand the boilerplate code generated by Visual Studio when creating a new WebAPI project. In both of the previous posts we have just gone through the very first line of the WebAPI Application.

var builder = WebApplication.CreateBuilder(args);

You can find the links to the previous posts below -

  1. Setup and debug microsoft aspnetcore source code
  2. Understanding CreateBuilder() method in AspNetCore (.NET 7)

In this post we would go a step further and explore the AddControllers() method in the AspNetCore source code.

builder.Services.AddControllers();

The code above is present in the Program.cs file of the WebAPI project created in .NET 7.

Where do we find the source code of the AddControllers() method ?

AddControllers() is an extension method of the IServiceCollection class present inside the file MvcServiceCollectionExtensions.cs file in the Microsoft.AspNetCore.Mvc project. This file contains not just the AddControllers() method but also all other methods that are used for the configuration of an Mvc application.

AddControllers() code

public static IMvcBuilder AddControllers(this IServiceCollection services)
    {
        if (services == null)
        {
            throw new ArgumentNullException(nameof(services));
        }

        var builder = AddControllersCore(services);
        return new MvcBuilder(builder.Services, builder.PartManager);
    }

A thing to note about the method above is that the method calls another method that is private namely AddControllersCore() which internally contains the details of the services that are injected for an MvcApplication.

It is a good practice to just perform the validations first in the parent public method i.e AddControllers() here and then call the private core method i.e AddControllersCore() which will have sanitised arguments to perform the main operation.

AddControllersCore() code

private static IMvcCoreBuilder AddControllersCore(IServiceCollection services)
    {
        // This method excludes all of the view-related services by default.
        var builder = services
            .AddMvcCore()
            .AddApiExplorer()
            .AddAuthorization()
            .AddCors()
            .AddDataAnnotations()
            .AddFormatterMappings();

        if (MetadataUpdater.IsSupported)
        {
            services.TryAddEnumerable(
                ServiceDescriptor.Singleton<IActionDescriptorChangeProvider, HotReloadService>());
        }

        return builder;
    }

What does the AddControllers() method do?

As you might know that AspNetCore has an inbuilt dependency injection framework. This means that we do not need containers like the UnityContainer to inject the dependencies into our application.

Now for an Mvc Application there will be lots of classes that needs to be injected to have the Mvc application working. These classes are all categorised and bundled inside their own extensions methods of IServiceCollection. In the code above the following are the extensions method that bundles the services to be injected for their functionality.

  1. AddMvcCore()
  2. AddApiExplorer()
  3. AddAuthorization()
  4. AddCors()
  5. AddDataAnnotations()
  6. AddFormatterMappings()

All these methods are chained and invoked inside the parent wrapper extension method that is AddControllersCore() which is called from the AddControllers() method.

Now we will see the purpose of each of the above mentioned extension methods.

1. AddMvcCore()

This method will register the minimum set of services that are needed for request routing and invoking Controllers.

public static IMvcCoreBuilder AddMvcCore(this IServiceCollection services)
    {
        if (services == null)
        {
            throw new ArgumentNullException(nameof(services));
        }

        var environment = GetServiceFromCollection<IWebHostEnvironment>(services);
        var partManager = GetApplicationPartManager(services, environment);
        services.TryAddSingleton(partManager);

        ConfigureDefaultFeatureProviders(partManager);
        ConfigureDefaultServices(services);
        AddMvcCoreServices(services);

        var builder = new MvcCoreBuilder(services, partManager);

        return builder;
    }

2. AddApiExplorer()

Used to configure the set of services for configuring an ApiExplorer for the MvcBuilder.

public static IMvcCoreBuilder AddApiExplorer(this IMvcCoreBuilder builder)
    {
        if (builder == null)
        {
            throw new ArgumentNullException(nameof(builder));
        }

        AddApiExplorerServices(builder.Services);
        return builder;
    }

    // Internal for testing.
    internal static void AddApiExplorerServices(IServiceCollection services)
    {
        services.TryAddSingleton<IApiDescriptionGroupCollectionProvider, ApiDescriptionGroupCollectionProvider>();
        services.TryAddEnumerable(
            ServiceDescriptor.Transient<IApiDescriptionProvider, DefaultApiDescriptionProvider>());
    }

3. AddAuthorization()

This method configures the authentication and authorization related services for the MvcBuilder.

public static IMvcCoreBuilder AddAuthorization(this IMvcCoreBuilder builder)
    {
        AddAuthorizationServices(builder.Services);
        return builder;
    }

internal static void AddAuthorizationServices(IServiceCollection services)
    {
        services.AddAuthenticationCore();
        services.AddAuthorization();

        services.TryAddEnumerable(
            ServiceDescriptor.Transient<IApplicationModelProvider, AuthorizationApplicationModelProvider>());
    }

4. AddCors()

Configures services to use CORS for the MvcBuilder

public static IMvcCoreBuilder AddCors(this IMvcCoreBuilder builder)
    {
        if (builder == null)
        {
            throw new ArgumentNullException(nameof(builder));
        }

        AddCorsServices(builder.Services);
        return builder;
    }

internal static void AddCorsServices(IServiceCollection services)
    {
        services.AddCors();

        services.TryAddEnumerable(
            ServiceDescriptor.Transient<IApplicationModelProvider, CorsApplicationModelProvider>());
        services.TryAddTransient<CorsAuthorizationFilter, CorsAuthorizationFilter>();
    }

public static IServiceCollection AddCors(this IServiceCollection services)
    {
        if (services == null)
        {
            throw new ArgumentNullException(nameof(services));
        }

        services.AddOptions();

        services.TryAdd(ServiceDescriptor.Transient<ICorsService, CorsService>());
        services.TryAdd(ServiceDescriptor.Transient<ICorsPolicyProvider, DefaultCorsPolicyProvider>());

        return services;
    }

5. AddDataAnnotations()

Registers services need for DataAnnotations for the MvcBuilder

public static IMvcCoreBuilder AddDataAnnotations(this IMvcCoreBuilder builder)
    {
        if (builder == null)
        {
            throw new ArgumentNullException(nameof(builder));
        }

        AddDataAnnotationsServices(builder.Services);
        return builder;
    }

 internal static void AddDataAnnotationsServices(IServiceCollection services)
    {
        services.TryAddEnumerable(
            ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcDataAnnotationsMvcOptionsSetup>());
        services.TryAddSingleton<IValidationAttributeAdapterProvider, ValidationAttributeAdapterProvider>();
    }

6. AddFormatterMappings()

Adds services to support formatter mappings used to specify mapping between the URL Format and corresponding media type.

public static IMvcCoreBuilder AddFormatterMappings(this IMvcCoreBuilder builder)
    {
        AddFormatterMappingsServices(builder.Services);
        return builder;
    }

internal static void AddFormatterMappingsServices(IServiceCollection services)
    {
        services.TryAddSingleton<FormatFilter, FormatFilter>();
    }

I have not gotten into the details of these methods further as this would be beyond the scope of this post but you can debug the source code to find out the details further.

All these extensions methods are doing is injecting a set of classes designed for a specific operation related to an MvcApplication.

Conclusion

We have come to the end of the post and now its time to summarize what we have covered above.

  1. AddControllers is an extension method to injecting services related to an Mvc Application.

  2. We explored the source code of the AddControllers() action method.

  3. AddControllers() method further calls multiple other extension methods of IServiceCollection that is designed for bundling together the services needed for different aspects and injecting them. .AddMvcCore() .AddApiExplorer() .AddAuthorization() .AddCors() .AddDataAnnotations() .AddFormatterMappings();

  4. We explored the pupose of the extension methods list above and their source code to some extent.

I hope you might have got an idea of these methods that are called additonally in the AspNetCore application. Although we went along with the AddControllers() method the idea is same for any other methods that you want to explore.

You can also create your own extension method in a similar fashion for your application to seggregate the bunch of classes injected into the application based on a category of the functionality for which the class is designed for.

Thank you for reading and see you in the next post.