Kentico Xperience Design Patterns: Good Startup.cs Hygiene
Tags: xperience, kentico, aspnetcore, csharp
Published: 2021-02-01 15:15:12

---
title: Kentico Xperience Design Patterns: Good Startup.cs Hygiene
published: true
description: Learn how to maintain a clean and focused Startup.cs with a some design patterns and extension methods.
tags: xperience, kentico, aspnetcore, csharp
series: Kentico Xperience Design Patterns
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/hcusgcjdsp4aozej50g9.jpg
---

The migration from Kentico CMS on .NET Framework to Kentico Xperience on ASP.NET Core means a completely new way of setting up our application.

Gone are the `Global.asax.cs` files with application events that we hook into. Gone are the `web.config` files with HttpModule and HttpHandler XML declarations.

Instead we have the all encompassing `Startup.cs`!

But is there a lesson we can learn from those ASP.NET files of old?

## 📚 What Will We Learn?

- The role of ASP.NET Core's `Startup.cs` compared to ASP.NET's `Global.asax.cs` and `web.config`
- Using a fluent syntax to increase readibility
- How to maintain DI (Dependency Injection) registration
- How to maintain a middleware pipeline

## 🚀 What's a Startup.cs?

In versions of Kentico Xperience before 13.0, we had two primary files where the low level, functional configuration of our applications happened - the `web.config` and `Global.asax.cs`.

The `web.config` file let us [register modules and handlers](https://docs.microsoft.com/en-us/troubleshoot/aspnet/http-modules-handlers) that became part of our request processing pipeline.

The `Global.asax.cs` file provided application lifecycle events we could hook into and with the advent of MVC it [became a place to register MVC specific components](https://docs.microsoft.com/en-us/aspnet/core/migration/proper-to-2x/?view=aspnetcore-5.0#globalasax-file-replacement).

Now, in ASP.NET Core, all of this has been replaced with the `Startup.cs` file and its two key methods, `ConfigureServices()` and `Configure()` 😮.

### 🛒 ConfigureServices

`ConfigureServices()` is called first, and it does exactly what the name says - it is used to configure all the services we want to use in our application. Specifically, it gives us a place to register them with the [dependency injection](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0) container.

> If you are unfamiliar with dependency injection, check out my post [ASP.NET Core: What is Dependency Injection?](https://xperience.io/discover/blog/2020-11/asp-net-core-what-is-dependency-injection).

The registration of services in our dependency injection container happens in code, and this replaces the XML `web.config` registration of HttpHandlers and HttpModules from classic ASP.NET.

We can think of `ConfigureServices()` as the grocery store. Each of our services are things we are putting in our cart for checkout, which means in this analogy, the dependency injection container is the shopping cart 🛒.

"I want one of these 🍎 and one of these 🍉 ... oh and I'll definitely need this 🍰".

Continuing with shopping cart analogy, we can add items to our cart in any order, and similarly, we can register dependencies in the container in any order, because nothing is finalized until we get to the checkout line, which for us, is the end of the `ConfigureServices()` method.

We can call methods on the `IServiceCollection` type (most of which are extension methods) to add types explicitly, or configure and add the types from libraries we want to use with our application:

```csharp
public void ConfigureServices(IServiceCollection services)
{
    services.AddKentico();
}
```

In the example above, we call `AddKentico()` which is an extension method that does all the hard work 💪🏽 of configuring and registering types from Xperience into our application for us to use.

### 🍲 Configure

`Configure()` serves a very different role!

If `ConfigureServices()` is our trip to the grocery store, then `Configure()` is the recipe we follow when we get home and start cooking dinner 🍲.

Unlike a trip to the grocery store, where we can visit parts of the store and add items to our shopping cart in any order, recipes require exact steps in a specific order.

`Configure()` is a recipe 📃 where we define all the [middleware](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-5.0) that gets to process incoming HTTP requests and their outgoing responses. And, middleware is what replaces the functionality HttpHandlers and HttpModules of classic ASP.NET.

```csharp
public void Configure(IApplicationBuilder app)
{
    // Our static files will now only be served via https
    app.UseHttpsRedirection();

    app.UseStaticFiles();

    // ...
}
```

If we try to chop the vegetables after cooking them, we're [going to have a bad time](https://youtu.be/XAuUPmRUxZg?t=35).

If we make sure and put our `UseHttpsRedirection` middleware is before our `UseStaticFiles` middleware, then we can serve `301` responses for `http` requests to our static files and ensure they always serve correctly over `https` 👏🏻!

## 💅 Maintaining Our Startup.cs

If we look at an example `web.config` from a Kentico Xperience Content Management application, we can see [they can be a bit overwhelming](https://gist.github.com/seangwright/2ed55b6496498885a82eb916c363165f#file-web-config). This one is 324 lines long 😱!

To be fair, some of this file's content is for configuring IIS, some is application settings, and some is handling `.dll` version incompatibilities (which .NET Core / .NET 5 handles for us transparently).

But that's kind of beside the point because this file is too big and complex to be easily edited or compared in version control.

Let's keep this in mind as we look at an example `Startup.cs` to identify key issues:

```csharp
public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IPaymentHelper, PaymentHelper>();
    services.AddScoped<IPaymentResponsitory, PaymentRepository>();
    
    services.AddLocalization(options => 
    {
        options.ResourcesPath = "Resources";
    });
    services.AddControllersWithViews(options =>
    {
        options.Conventions.Add(...);
        options.Conventions.Add(...);
    });

    services.AddOptions<PaymentApiKeys>().Configure(keys =>
    {
        keys.Secret = Configuration["payment:keys:secret"];
        keys.Public = Configuration["payment:keys:public"];
    });

    services.Configure<RouteOptions>(options =>
    {
        options.LowercaseUrls = true
    });

    var kentico = services.AddKentico(features =>
    {
        features.UsePageBuilder();
        features.UsePageRouting(new PageRoutingOptions
        {
            EnableAlternativeUrls = true,
            EnableRouting = true
        });
    });
    kentico.SetAdminCookiesSameSiteNone();
}
```

> Most Xperience applications with have a `ConfigureServices()` method far more complex than this one, which means my recommendations will be even more effective for those apps 👍.

### 📂 Organizing Our Concerns

First, we're mixing concerns and not setting a good pattern for future configuration.

We have some application service registration (`IPaymentHelper`) followed by ASP.NET Core localization and MVC conventiontion customization.

Then comes more application registration (`PaymentApiKeys`) and MVC configuration (`RouteOptions`), followed by Xperience integration (`AddKentico()`, `SetAdminCookiesSameSiteNone()`).

⚠ We need to manage dependency registration early on in our development process, because it always grows in complexity and size.

Is it clear, when reading this file, what features our application has 🙄? Is it likely we'll need to scroll up and down the file to keep track of what's going on 🤔?

Let's re-arrange our registration so that related types are grouped together.

While we're at it, let's also use the fluent syntax enabled by these extension methods (they all return `IServiceCollection`), and convert our statements to expressions!

```csharp
public void ConfigureServices(IServiceCollection services)
{
    services
        .AddLocalization(options => 
        {
            options.ResourcesPath = "Resources";
        })
        .Configure<RouteOptions>(options =>
        {
            options.LowercaseUrls = true
        })
        .AddControllersWithViews(options =>
        {
            options.Conventions.Add(...);
            options.Conventions.Add(...);
        });

    services
        .AddKentico(features =>
        {
            features.UsePageBuilder();
            features.UsePageRouting(new PageRoutingOptions
            {
                EnableAlternativeUrls = true,
                EnableRouting = true
            });
        })
        .SetAdminCookiesSameSiteNone();

    services
        .AddSingleton<IPaymentHelper, PaymentHelper>()
        .AddScoped<IPaymentResponsitory, PaymentRepository>()
        .AddOptions<PaymentApiKeys>().Configure(keys =>
        {
            keys.Secret = Configuration["payment:keys:secret"];
            keys.Public = Configuration["payment:keys:public"];
        });
}
```

Our reorganization is looking good 🤩! We've chained our registrations together, which reduces the syntax noise in the file, and we've grouped our registrations together into related groups.

We start at the low level parts of the application - the ASP.NET Core configuration and move on to Xperience and then our custom application types.

### 🏗 Extracting To Extensions

The next step step in making sure our `Startup.cs` is well maintained is the practice of moving our configuration out of `Startup.cs`.

`Startup.cs` is the _root_ of our configuration, but that doesn't mean it needs to be the source of it.

It's too easy to let this file become a big ball of mud 💩 where we continually patch in new functionality until it grows so large that no one can figure out where to make changes or what is going on 😵.

I like the pattern of creating a `/Configuration` folder at the root of the application and adding extension method classes there, which I then call in `Startup.cs`.

Let's try that below:

```csharp
public void ConfigureServices(IServiceCollection services) =>
    services
        .AddAppMvc()
        .AddAppXperience()
        .AddApp();
```

Each section that was in our `ConfigureServices()` method has been moved out into other files, and we've named our new extension methods after the sections that used to be here.

> I like adding the `App` prefix to these extension methods to prevent them from conflicting with anything supplied by a library. Use any convention you want, but stick to it 😀.

It's now very clear, at a high level, what parts our application is using and customizing - Mvc, Kentico Xperience, and our own application code.

As we add more integrations, like [ASP.NET Core Identity](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity-configuration?view=aspnetcore-5.0), we'll add new extension classes, and `Startup.cs` starts to look like a table of contents 📑, rather than the first 100 pages, of a book 📗.

We've even changed to the [expression bodied method](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members#methods) syntax, getting rid of a couple curly braces 🧐.

I think this is a small, but important change.

### 😁 A Digression on Expressions

Expression bodied members require expressions and cannot be used with statements.

By not allowing statements in our `ConfigureServices()` method, we are forcing new service registrations to follow the pattern of being organized in an existing (or new) extension method class 🤓, and not in the `ConfigureSerivces()` call.

Just for reference, let's look at the definition of the `AddApp()` method that we're now using:

```csharp
// ~/Configuration/AppConfiguration.cs

public static class AppConfiguration
{
    public static IServiceCollection AddApp(
        this IServiceCollection services) => 

        services
        .AddSingleton<IPaymentHelper, PaymentHelper>()
        .AddScoped<IPaymentResponsitory, PaymentRepository>()
        .AddOptions<PaymentApiKeys>().Configure(keys =>
        {
            keys.Secret = Configuration["payment:keys:secret"];
            keys.Public = Configuration["payment:keys:public"];
        });
}
```

Even here, we continue the use of expression bodied methods, and enable a fluent syntax by returning `IServiceCollection` so that the next extension method can chain off this one.

With these configuration classes, it's much more clear where to add the next type registration or configuration. Each file is smaller which means it's easier to read and merge in source control.

### ⛵ Staying Fluent

Those familiar with Xperience on ASP.NET Core might have noticed that some of our fluent method chaining doesn't always work how we'd like.

`AddControllersWithViews()` returns an `IMvcBuilder` type instead of `IServiceCollection` and Xperience's `SetAdminCookiesSameSiteNone()` returns a `IKenticoServiceCollection`.

If these appear at the end of our method chaining, we aren't going to be able to return `IServiceCollection`, which means we can't have the nice `services.AddAppX().AddAppY().AddAppZ();` calls in our `Startup.us`, that we saw above 😢.

How do we return `IServiceCollection` without statements like `return services`? What if we want to make as much of our configuration expression based and fluent as possible?

Fortunately, we can stay in the land of fluent expressions a few ways.

For the case of `AddControllersWithViews()`, which returns `IMvcBuilder`, we can make a new extension method (extension-ception!) that gives us exactly what we want:

```csharp
public static class ServiceCollectionExtensions
{
    public static IServiceCollection ToServiceCollection(
        this IMvcBuilder _, 
        IServiceCollection services) => services;
}
```

That's it! We discard the `IMvcBuilder` because we're done with it and return the `IServiceCollection` that's passed in as a parameter.

In practice it looks like this:

```csharp
// ~/Configuration/MvcConfiguration.cs

public static class MvcConfiguration
{
    public static IServiceCollection AddAppMvc(
        this IServiceCollection services) =>

        services
            .AddLocalization(options => 
            {
                options.ResourcesPath = "Resources";
            })
            // ... more registrations
            .AddControllersWithViews(...)
            .ToServiceCollection(services);
}
```

For `SetAdminCookiesSameSiteNone()` which returns an `IKenticoServiceCollection`, we can rely on the fact that the Xperience development team was kind 🙏 enough to expose the inner `IServiceCollection` as a `.Service` property:

```csharp
// ~/Configuration/XperienceConfiguration.cs

public static class XperienceConfiguration
{
    public static IServiceCollection AddAppXperience(
        this IServiceCollection services) =>

        services
            .AddKentico(...)
            .SetAdminCookiesSameSiteNone()
            .Services;
}
```

Easy peasy 😎.

## 🚿 Configuring our Pipeline

It's always going to be the case that our `ConfigureServices()` method is going to require a lot more coding that our `Configure()` method, but that doesn't mean there aren't good practices for keeping `Configure()` well maintained 😏.

### 💨 Environmental Conditions

If we want to apply the same fluent expression bodied method approach to our middleware pipeline that we applied to our service registration, we're going to quickly run into a problem 😑.

Every ASP.NET Core application has a `Configure()` method that starts with the following:

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

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    // ...
}
```

In C#, conditionals are almost always statements rather than expressions, and this `if()` block is exactly the type of code that stops an expression based fluent syntax in its tracks.

However, here again, we can rely on a custom extension method to turn our statement into a chainable method call:

```csharp
public static class ApplicationBuilderExtensions
{
    public static IApplicationBuilder IfDevelopment(
        this IApplicationBuilder builder, 
        IWebHostEnvironment env,
        Action<IApplicationBuilder> operation)
    {
        if (env.IsDevelopment() && operation is not null)
        {
            operation(builder);
        }

        return builder;
    }
}
```

This simple extension lets us conditionally add middleware but as a fluent method. We keep chaining, and the middleware will only be added to the pipeline in the Development environment.

Let's return to our previous example to see how it would be used:

```csharp
public void Configure(
    IApplicationBuilder app, 
    IWebHostEnvironment env) =>

    app
      .IfDevelopment(env, a => a.UseDeveloperExceptionPage())
      .UseHttpsRedirection()
      .UseStaticFiles()
      // ... 
}
```

We now have a clean middleware setup, with an environment dependent pipeline, but no statements 💪🏼.

## 🧠 Conclusion

The `Startup.cs` file we started with was destined to become an unmaintainable dumping ground 💩 for the service registration and middleware pipeline configuration 🤦🏽‍♀️.

By establishing some conventions and identifying patterns, we extracted common behavior to other classes.

To get around technical issues with our desired fluent syntax, we created some helpful extension methods.

What we end up with is a `Startup.cs` that will grow with our application but also read like a table of contents for its core functionality 🧐:

```csharp
public class Startup
{
    public Startup(...)
    {
        // ...
    }

    public void ConfigureServices(IServiceCollection services) =>
        services
            .AddAppMvc()
            .AddAppXperience()
            .AddApp();

    public void Configure(
        IApplicationBuilder app,
        IWebHostEnvironment env) =>

        app
          .IfDevelopment(env, a => a.UseDeveloperExceptionPage())
          .UseHttpsRedirection()
          .UseStaticFiles()
          // ... 
    }
}
```

Ah, [so fresh and so clean](https://www.youtube.com/watch?v=X5AfjAXcBXY)... 😎

As always, thanks for reading 🙏!

---

<figcaption>Photo by <a href="https://unsplash.com/@markusspiske">Markus Spiske
</a> on <a href="https://unsplash.com">Unsplash</a></figcaption>

---

## References

- [Xperience Docs: Starting with ASP.NET Core development](https://docs.xperience.io/developing-websites/developing-xperience-applications-using-asp-net-core/starting-with-asp-net-core-development)
- [.NET Docs: App startup in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/startup?view=aspnetcore-5.0)
- [.NET Docs: Dependency injection in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0)
- [.NET Docs: ASP.NET Core Middleware](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-5.0)

---

We've put together a list over on [Kentico's GitHub account](https://github.com/Kentico/Home/blob/master/RESOURCES.md) of developer resources. Go check it out!

If you are looking for additional Kentico content, checkout the Kentico or Xperience tags here on DEV.

{% tag kentico %}

{% tag xperience %}

Or my [Kentico Xperience blog series](https://dev.to/seangwright/series), like:

- [Kentico Xperience Xplorations](https://dev.to/seangwright/series/8185)
- [Kentico Xperience MVC Widget Experiments](https://dev.to/seangwright/series/9483)
- [Bits of Xperience](https://dev.to/seangwright/series/8740)