Git Product home page Git Product logo

structuredminimalapi's Introduction

Build status Downloads

StructuredMinimalApi

The goal of this project it's to show how to use MinimalApi.Endpoint package.
It demontrate how to configure API endpoints as individual classes based on minimal Api (.Net 6)

Program.cs

Use AddEndpoints extenion method to create each endpoint.

And also MapEndpoint extension method to use new routing APIs

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpoints();

var app = builder.Build();

app.MapEndpoints();

app.Run();

Define an endpoint

To create and define one endpoint, it needs to implement IEndpoint interface

public class GetWithParamEndpoint : IEndpoint<string, string>
    {
        public void AddRoute(IEndpointRouteBuilder app)
        {
            app.MapGet("/Todo/2/{param1}", (string param1) => HandleAsync(param1));
        }

        public Task<string> HandleAsync(string request)
        {
            return Task.FromResult($"Hello World! 2 {request}");
        }
    }

Projects Using MinimalApi.Endpoint

Nuget Package

A nuget package is available here.

structuredminimalapi's People

Contributors

jboeke avatar michelcedric avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

structuredminimalapi's Issues

Register endpoints as Singleton instead

The AddEndpoints extension method registers all IEndpoint implementation as Scoped. Registering them as scoped, however, is dangerous, because it could easily lead to undetected Captive Dependencies, memory leaks, concurrency bugs, and other hard-to-spot issues. I will explain why this is.

The home page shows the following example endpoint:

public class GetWithParamEndpoint : IEndpoint<string, string>
{
    public void AddRoute(IEndpointRouteBuilder app)
    {
        app.MapGet("/Todo/2/{param1}", (string param1) =>
            HandleAsync(param1));
    }

    public Task<string> HandleAsync(string request)
    {
        return Task.FromResult($"Hello World! 2 {request}");
    }
}

When MapEndpoints is called at startup, this endpoint will be resolved from a newly created scope and its AddRoute method will be called.

At that point, GetWithParamEndpoint will register its HandleAsync method to the MapGet method. This effectively keeps the GetWithParamEndpoint instance alive for the duration of the application. This has the following consequences:

  • The HandleAsync method of the same GetWithParamEndpoint instance can be called in parallel by multiple requests, as ASP.NET Core does not implement any synchronization on this
  • If GetWithParamEndpoint gets any dependencies injected into its constructor, those dependencies might, therefore, be called in parallel, which will typically be a problem for Scoped dependencies.
  • Those injected dependencies with will stay referenced for the duration of the application. This is problematic for dependencies such as DbContext implementations, as they cache data and become stale over time.

It seems to me that you are partly aware of the issues that this model might cause, because I noticed that MapEndpoints doesn't dispose its created scope. If MapEndpoints would dispose of the scope from which GetWithParamEndpoint is resolved, it will quickly cause trouble when either GetWithParamEndpoint or one of its (direct or indirect) Transient or Scoped dependencies implements IDisposable - DbContext being the most obvious one. You likely noticed this issue, which probably lead to removal of the call to Dispose from MapEndpoints. But I'm afraid that removing Dispose on the IServiceScope only fixed the symptoms, not the underlying problem.

These problems popup when GetWithParamEndpoint gets injected dependencies. Any endpoint with no dependencies doesn't exhibit these issues. It still might have concurrency issues in case it contains mutable state, but I'd say that this is a less likely scenario.

Probably the simplest way around this issue to let AddEndpoints register all endpoints as Singleton instead of Scoped. This doesn't change the way the application is working - the endpoints were effectively Singletons anyway. But it allows MS.DI and the framework to validate the endpoints' object graphs. MS.DI disallows injecting scoped dependencies into Singletons (and for good reason). This means that whenever a user tries to inject a DbContext into their endpoint class, the framework will prevent this - as it should, as it will absolutely lead to bugs down the line.

In this case you should also resolve the endpoints from the root container, rather than creating a service scope, as what's happening in MapEndpoints. This service scope goes out of scope at the end of MapEndpoints while it is never disposed of, not even when the application ends. This means that any disposable Transient that is part of the endpoints object graph, will also never get disposed of when the application ends. This can be easily solved by resolving from the root container as those disposable transients will be tracked by the root container and the framework will ensure the root container is disposed of when the application ends.

This is how to resolve from the root scope:

public static void MapEndpoints(this WebApplication builder)
{
    // builder.Services is the root scope.
    var endpoints = builder.Services.GetServices<IEndpoint>();

    foreach (var endpoint in endpoints)
    {
        endpoint.AddRoute(builder);
    }
}

Of course, making endpoints Singleton is not your only option, but probably the simplest - it has the least impact on usability.

Another option is to leave the current behavior in tact and instead document explicitly that endpoints should not have scoped dependencies, or document that endpoints should have no dependencies at all. Problem, of course, is that when injecting seems to work, developers will do it anyway. Developers are notoriously bad in reading the fine manual.

The issue resolves around the definition of an abstraction that has its AddRoute method called at startup, while its HandleAsync method is called on a per-request basis. A last option to consider, therefore, is to radically change the design of this library. You could try to design this api such that endpoints are resolved per request and their HandleAsync method is called on that resolved instance. This might, however, be a complete redesign and I certainly understand if that's not a change you're willing to make.

I hope this all makes sense.

[Question] Dependency Injection Scoped

Thanks for the nice and understandable library for Endpoints ๐Ÿ‘

I'm facing an issue with the library, and I believe this is related to DI-scoped visibility. But couldn't figure out if an issue on my side or not.
Although I haven't had an issue while a simple static class was used ๐Ÿค” code

Down below I'll explain the conditions and provide an example repo.

Having a simple Minimal API service

  • connecting to DB through static class and use service.AddScoped... code
  • Endpoints classes having Services/Mappers as DI trough controller - code

Everything works fine, until I run StressTest with 10 consecutive connections - immediately after 2 requests I got an error -
There is already an open DataReader associated with this Connection which must be closed first.

Repo - https://github.com/artem-galas/ComicsShop/tree/master/ComicsShop.Rest

Steps to reproduce:

  • run DB docker image
  • run application in docker
  • install Deps in StressTest folder (npm i)
  • run test npm run test-rest code

Appreciate your help :)

CancellationToken

Hello!
Is it possible to work with cancellation token on the request?
Thanks for the project

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.