ASP.NET Core 2 web service without a Startup class
Tags: aspnet, dotnetcore, fsharp, incaseiforget
Published: 2018-05-31 19:27:33

---
title: ASP.NET Core 2 web service without a Startup class
published: true
description: In case I am not the only one who wants this.
tags: #aspnet #dotnetcore #fsharp #incaseiforget
---

# Update 2018-07-06

The content of this post is somewhat diminished by the fact that ASP.NET Core does not support running `Configure(...)` multiple times. Instead only the last Configure function will be run.

However, I tweaked my helpers a little bit to keep track of the needed Configure functions separately and only apply them on build. Here is an update example for OAuth with JWT.

```fsharp
type AppBuilder = IApplicationBuilder -> IApplicationBuilder


let setAuth0Auth audKey authKey (host : IWebHostBuilder, configs : AppBuilder list) =
    ( host
        .ConfigureServices(fun (context : WebHostBuilderContext) (services : IServiceCollection) ->
            let config = context.Configuration
            services
                .AddAuthentication(fun options ->
                    options.DefaultAuthenticateScheme <- JwtBearerDefaults.AuthenticationScheme
                    options.DefaultChallengeScheme <- JwtBearerDefaults.AuthenticationScheme
                )
                .AddJwtBearer(fun options ->
                    options.Audience <- config.[audKey]
                    options.Authority <- config.[authKey]
                )
            |> ignore
        )
    , configs @ [ fun app -> app.UseAuthentication() ]
    )


let run (host : IWebHostBuilder, configs : AppBuilder list) =
    host
        .Configure(fun (app : IApplicationBuilder) ->
            configs
            |> List.fold (fun x f -> f x) app
            |> ignore
        )
        .Build()
        .Run()
```

Otherwise, the content below is still  serviceable.

----


I have been bashing my head on the keyboard for a few days trying to get an ASP.NET Core 2 web service properly configured to run without using a Startup class. And I finally got it working.

## Why?

A Startup class is a pretty egregious mix of concerns and an unnecessary abstraction to take on and unnecessary conventions to commit to memory. Not only that, but using a different approach can actually make it easier to create and maintain service configurations.

## How?

I'll start from the end result and work backward. Here is what `main` looks like on my service.

```fsharp
[<EntryPoint>]
let main args =
    let basePath = Directory.GetCurrentDirectory()

    new WebHostBuilder()
    |> setConfig basePath args
    |> setSerilogLogger
    |> removeServerHeader // bye X-Powered-By
    |> setAuth0Auth Keys.Audience Keys.Authority
    |> setCorsPolicy
    |> setRouteHandler MyApi.routes
    |> run

    0 // exit code
```

Looking at this code, it is (hopefully) obvious what I am configuring. These are, of course, helper functions which I have to create myself initially. But since these functions are self-contained, they are reusable in other projects. They also allow me to opt-in or out of various features by adding or removing a line. For example, I could replace `setSerilogLogger` with `setLogaryLogger`.

It is true that the builder already has fluent methods -- such as `Configure` and `ConfigureServices`. And you could make some of the same claims for them. However, some features are not configured with only a single fluent method. For example, the `setAuth0Auth` function has to use a couple of methods internally to properly configure authentication:

```fsharp
let setAuth0Auth audienceKey authorityKey (host : IWebHostBuilder) =
    host
        .ConfigureServices(fun (context : WebHostBuilderContext) (services : IServiceCollection) ->
            let config = context.Configuration
            services
                .AddAuthentication(fun options ->
                    options.DefaultAuthenticateScheme <- JwtBearerDefaults.AuthenticationScheme
                    options.DefaultChallengeScheme <- JwtBearerDefaults.AuthenticationScheme
                )
                .AddJwtBearer(fun options ->
                    options.Authority <- config.[authorityKey]
                    options.Audience <- config.[audienceKey]
                )
                |> ignore
        )
        .Configure(fun builder -> builder.UseAuthentication() |> ignore)
```

Organizing this into its own function encapsulates exactly what needs to be done to setup auth, and only auth. Since it only uses what is passed into it, this function is very reusable for other services. (It is a bit unfortunate that the builder uses mutation, but this is not likely to be an issue since this code only runs on startup.)

The tricky part to figuring out how to create a Startup-less service is accessing things you might need, such as the `IConfiguration` and `IHostingEnvironment` states. Every bit of advice I found was: "Inject them into a Startup class" 🙃. But it turns out they are pretty easy to find with one of the [`ConfigureService` overloads](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.iwebhostbuilder.configureservices?view=aspnetcore-2.1). The one with the `WebHostBuilderContext` is the key. It has both the configuration and hosting objects as properties. You can see in the example above that I used it to get the `IConfiguration` object.

Getting these items from `Configure` is a little more challenging because it doesn't have an overload with these as immediate properties. However, you can still find them (and other things) buried in the provided `IApplicationBuilder`.

```fsharp
webHostBuilder
    .Configure(fun (builder : IApplicationBuilder) ->
        let services = builder.ApplicationServices
        let env = services.GetService<IHostingEnvironment>()
        let config = services.GetService<IConfiguration>()
        ...
    )
```

## Summary

A few helper functions go a long way to taming the complexity of configuring services. And when they are reusable for creating other services, all the better!