# Minimal API

> **Note**: Requires .NET 6+

```cs linenums="1"
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<IService, Service>();
builder.Services.AddScoped<IService, Service>();
builder.Services.AddTransient<IService, Service>();

var app = builder.Build();

// [...]

app.Run();
//or
app.RunAsync();
```

## Application Settings

App settings are loaded (in order) from:

1. `appsettings.json`
2. `appsettings.<Environment>.json`
3. User Secrets

The environment is controlled by the env var `ASPNETCORE_ENVIRONMENT`. If a setting is present in multiple locations, the last one is used and overrides the previous ones.

### User Secrets

User secrets are specific to each machine and can be initialized with `dotnet user-secrets init`. Each application is linked with it's settings by a guid.

The settings are stored in:

- `%APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets.json` (Windows)
- `~/.microsoft/usersecrets/<user_secrets_id>/secrets.json` (Linux/macOS)

Setting a value is done with `dotnet user-secrets set <key> <value>`, keys can be nested by separating each level with `:` or `__`.

## Swagger

```cs linenums="1"
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// [...]

app.UseSwagger();
app.UseSwaggerUI();

// add returned content metadata to Swagger
app.MapGet("/route", Handler).Produces<Type>(statusCode);

// add request body contents metadata to Swagger
app.MapPost("/route", Handler).Accepts<Type>(contentType);
```

## MVC

```cs linenums="1"
builder.Services.AddControllersWithViews();
//or
builder.Services.AddControllers();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Home/Error");

    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

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

app.UseRouting();

app.UseAuthorization();

app.MapControllers();
// or
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
```

## Routing, Handlers & Results

To define routes and handlers using Minimal APIs, use the `Map(Get|Post|Put|Delete)` methods.

```cs linenums="1"
// the dependencies are passed as parameters in the handler delegate
app.MapGet("/route/{id}", (IService service, int id) => {
    
    return entity is not null ? Results.Ok(entity) : Results.NotFound();
});

// pass delegate to use default values
app.MapGet("/search/{id}", Search);
IResult Search(int id, int? page = 1, int? pageSize = 10) { /* ... */ }
```

### Route Groups

The `MapGroup()` extension method, which helps organize groups of endpoints with a common prefix.  
It allows for customizing entire groups of endpoints with a singe call to methods like `RequireAuthorization()` and `WithMetadata()`.

```cs linenums="1"
var group = app.MapGroup("<route-prefix>");

group.MapGet("/", GetAllTodos);  // route: /<route-prefix>
group.MapGet("/{id}", GetTodo);  // route: /<route-prefix>/{id}

// [...]
```

### `TypedResults`

The `Microsoft.AspNetCore.Http.TypedResults` static class is the “typed” equivalent of the existing `Microsoft.AspNetCore.Http.Results` class.  
It's possible to use `TypedResults` in minimal APIs to create instances of the in-framework `IResult`-implementing types and preserve the concrete type information.

```cs linenums="1"
public static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
```

```cs linenums="1"
[Fact]
public async Task GetAllTodos_ReturnsOkOfObjectResult()
{
    // Arrange
    var db = CreateDbContext();

    // Act
    var result = await TodosApi.GetAllTodos(db);

    // Assert: Check the returned result type is correct
    Assert.IsType<Ok<Todo[]>>(result);
}
```

### Multiple Result Types

The `Results<TResult1, TResult2, TResultN>` generic union types, along with the `TypesResults` class, can be used to declare that a route handler returns multiple `IResult`-implementing concrete types.

```cs linenums="1"
// Declare that the lambda returns multiple IResult types
app.MapGet("/todos/{id}", async Results<Ok<Todo>, NotFound> (int id, TodoDb db)
{
    return await db.Todos.FindAsync(id) is Todo todo
        ? TypedResults.Ok(todo)
        : TypedResults.NotFound();
});
```

## Filters

```cs linenums="1"
public class ExampleFilter : IRouteHandlerFilter
{
    public async ValueTask<object?> InvokeAsync(RouteHandlerInvocationContext context, RouteHandlerFilterDelegate next)
    {
        // before endpoint call
        var result = next(context);
        /// after endpoint call
        return result;
    }
}
```

```cs linenums="1"
app.MapPost("/route", Handler).AddFilter<ExampleFilter>();
```

## Context

With Minimal APIs it's possible to access the contextual information by passing one of the following types as a parameter to your handler delegate:

- `HttpContext`
- `HttpRequest`
- `HttpResponse`
- `ClaimsPrincipal`
- `CancellationToken` (RequestAborted)

```cs linenums="1"
app.MapGet("/hello", (ClaimsPrincipal user) => {
    return "Hello " + user.FindFirstValue("sub");
});
```

## OpenAPI

The `Microsoft.AspNetCore.OpenApi` package exposes a `WithOpenApi` extension method that generates an `OpenApiOperation` derived from a given endpoint’s route handler and metadata.

```cs linenums="1"
app.MapGet("/todos/{id}", (int id) => ...)
    .WithOpenApi();

app.MapGet("/todos/{id}", (int id) => ...)
    .WithOpenApi(operation => {
        operation.Summary = "Retrieve a Todo given its ID";
        operation.Parameters[0].AllowEmptyValue = false;
    });
```

## Validation

Using [Minimal Validation](https://github.com/DamianEdwards/MinimalValidation) by Damian Edwards.  
Alternatively it's possible to use [Fluent Validation](https://fluentvalidation.net/).

```cs linenums="1"
app.MapPost("/widgets", (Widget widget) => {
    var isValid = MinimalValidation.TryValidate(widget, out var errors);

    if(isValid)
    {
        return Results.Created($"/widgets/{widget.Name}", widget);
    }

    return Results.BadRequest(errors);
});

class Widget
{
    [Required, MinLength(3)]
    public string? Name { get; set; }

    public override string? ToString() => Name;
}
```

## JSON Serialization

```cs linenums="1"
// Microsoft.AspNetCore.Http.Json.JsonOptions
builder.Services.Configure<JsonOptions>(opt =>
{
    opt.SerializerOptions.PropertyNamingPolicy = new SnakeCaseNamingPolicy();
});
```

## Authorization

```cs linenums="1"
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();

builder.Services.AddAuthorization();
// or
builder.Services.AddAuthorization(options =>
{
    // for all endpoints
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
      .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
      .RequireAuthenticatedUser();
})
// or
builder.Authentication.AddJwtBearer();  // will automatically add required middlewares

// [...]

app.UseAuthentication();
app.UseAuthorization(); // must come before routes

// [...]

app.MapGet("/alcohol", () => Results.Ok()).RequireAuthorization("<policy>");  // on specific endpoints
app.MapGet("/free-for-all", () => Results.Ok()).AllowAnonymous();
app.MapGet("/special-secret", () => "This is a special secret!")
    .RequireAuthorization(p => p.RequireClaim("scope", "myapi:secrets"));
```

### Local JWT Tokens

The `user-jwts` tool is similar in concept to the existing `user-secrets` tools, in that it can be used to manage values for the app that are only valid for the current user (the developer) on the current machine.  
In fact, the `user-jwts` tool utilizes the `user-secrets` infrastructure to manage the key that the JWTs will be signed with, ensuring it’s stored safely in the user profile.

```sh linenums="1"
dotnet user-jwts create  # configure a dev JWT fot the current user
```

## Output Caching

```cs linenums="1"
builder.Services.AddOutputCaching();  // no special options
builder.Services.AddOutputCaching(options => 
{
    options => options.AddBasePolicy(x => x.NoCache())  // no cache policy

    Func<OutputCacheContext, bool> predicate = /* discriminate requests */
    options.AddBasePolicy(x => x.With(predicate).CachePolicy());
    options.AddBasePolicy("<policy-name>", x => x.CachePolicy());  // named policy
});

// [...]

app.UseOutputCaching();  // following middlewares can use output cache

// [...]

app.MapGet("/<route>", RouteHandler).CacheOutput();  // cache forever
app.MapGet("/<route>", RouteHandler).CacheOutput().Expire(timespan);

app.MapGet("/<route>", RouteHandler).CacheOutput(x => x.CachePolicy());
app.MapGet("/<route>", RouteHandler).CacheOutput("<policy-name>");

app.MapGet("/<route>", RouteHandler).CacheOutput(x => x.VaryByHeader(/* headers list */));
app.MapGet("/<route>", RouteHandler).CacheOutput(x => x.VaryByQuery(/* query key */));
app.MapGet("/<route>", RouteHandler).CacheOutput(x => x.VaryByValue());

app.MapGet("/<route>", [OutputCache(/* options */)]RouteHandler);
```

### Cache Eviction

```cs linenums="1"

app.MapGet("/<route-one>", RouteHandler).CacheOutput(x => x.Tag("<tag>"));  // tag cache portion

app.MapGet("/<route-two>", (IOutputCacheStore cache, CancellationToken token) => 
{
    await cache.EvictByTag("<tag>", token);  // invalidate a portion of the cache
});
```

### Custom Cache Policy

```cs linenums="1"
app.MapGet("/<route-one>", RouteHandler).CacheOutput(x => x.AddCachePolicy<CustomCachePolicy>());
```

```cs linenums="1"
class CustomCachePolicy : IOutputCachePolicy
{
    public ValueTask CacheRequestAsync(OutputCacheContext context, CancellationToken cancellationToken) { }

    public ValueTask ServeFromCacheAsync(OutputCacheContext context, CancellationToken cancellationToken) { }

    public ValueTask ServeResponseAsync(OutputCacheContext context, CancellationToken cancellationToken) { }
}
```

## Options Pattern

The *options pattern* uses classes to provide strongly-typed access to groups of related settings.

```json linenums="1"
{
    "SecretKey": "Secret key value",
    "TransientFaultHandlingOptions": {
        "Enabled": true,
        "AutoRetryDelay": "00:00:07"
    },
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Information"
        }
    }
}
```

```cs linenums="1"
// options model for binding
public class TransientFaultHandlingOptions
{
    public bool Enabled { get; set; }
    public TimeSpan AutoRetryDelay { get; set; }
}
```

```cs linenums="1"
// setup the options
builder.Services.Configure<TransientFaultHandlingOptions>(builder.Configuration.GetSection<TransientFaultHandlingOptions>(nameof(Options)));
builder.Services.Configure<TransientFaultHandlingOptions>(builder.Configuration.GetSection<TransientFaultHandlingOptions>(key));
```

```cs linenums="1"
class DependsOnOptions
{
  private readonly IOptions<TransientFaultHandlingOptions> _options;

  public DependsOnOptions(IOptions<TransientFaultHandlingOptions> options) => _options = options;
}
```

### [Options interfaces](https://docs.microsoft.com/en-us/dotnet/core/extensions/options#options-interfaces)

`IOptions<TOptions>`:

- Does not support:
  - Reading of configuration data after the app has started.
  - Named options
- Is registered as a Singleton and can be injected into any service lifetime.

`IOptionsSnapshot<TOptions>`:

- Is useful in scenarios where options should be recomputed on every injection resolution, in scoped or transient lifetimes.
- Is registered as Scoped and therefore cannot be injected into a Singleton service.
- Supports named options

`IOptionsMonitor<TOptions>`:

- Is used to retrieve options and manage options notifications for `TOptions` instances.
- Is registered as a Singleton and can be injected into any service lifetime.
- Supports:
  - Change notifications
  - Named options
  - Reloadable configuration
  - Selective options invalidation (`IOptionsMonitorCache<TOptions>`)
