How to register api controller from a library with configuration

What I have done is created a small API in a class library. This API would be used by other sites. Think of it as a standard endpoint that all of our websites will contain.

[Route("api/[controller]")]
[ApiController]
public class CustomController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "value1", "value2" };
    }
}

The above is in a class library. Now what i would like to do is be able to add this to the projects in a simple manner.

app.UseCustomAPI("/api/crap");

I am not exactly sure how i should handle routing to the api controllers in the library. I created a CustomAPIMiddleware which is able to catch that i called "/api/crap" however i am not sure how i should forward the request over to CustomController in the library

public async Task Invoke(HttpContext context)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));
        PathString matched;
        PathString remaining;
        if (context.Request.Path.StartsWithSegments(_options.PathMatch, out matched, out remaining))
        {
            PathString path = context.Request.Path;
            PathString pathBase = context.Request.PathBase;
            context.Request.PathBase = pathBase.Add(matched);
            context.Request.Path = remaining;
            try
            {
                await this._options.Branch(context);
            }
            finally
            {
                context.Request.PathBase = pathBase;
                context.Request.Path = path;
            }
            path = new PathString();
            pathBase = new PathString();
        }
        else
            await this._next(context);
    }

After having done that i am starting to think i may have approached this in the wrong manner and should actually be trying to add it directly to the routing tables somehow. That being said i would like it if they could customize the endpoint that the custom controller reads from.

Update

The following does work. Loading and registering API Controllers From Class Library in ASP.NET core

services.AddMvc()
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
            .AddApplicationPart(Assembly.Load(new AssemblyName("WebAPI"))); 

However i am really looking for a middlewere type solution so that users can simply add it and i can configure the default settings or they can change some of the settings. The above example would not allow for altering the settings.

 app.UseCustomAPI("/api/crap");

Update from comment without Assembly

If i dont add the .AddApplicationPart(Assembly.Load(new AssemblyName("WebAPI")));

This localhost page can’t be found No webpage was found for the web address: https://localhost:44368/api/Custom


Solution 1:

To customise the routing for a controller at runtime, you can use an Application Model Convention. This can be achieved with a custom implementation of IControllerModelConvention:

public class CustomControllerConvention : IControllerModelConvention
{
    private readonly string newEndpoint;

    public CustomControllerConvention(string newEndpoint)
    {
        this.newEndpoint = newEndpoint;
    }

    public void Apply(ControllerModel controllerModel)
    {
        if (controllerModel.ControllerType.AsType() != typeof(CustomController))
            return;

        foreach (var selectorModel in controllerModel.Selectors)
            selectorModel.AttributeRouteModel.Template = newEndpoint;
    }
}

This example just replaces the existing template (api/[controller]) with whatever is provided in the CustomControllerConvention constructor. The next step is to register this new convention, which can be done via the call to AddMvc. Here's an example of how that works:

services.AddMvc(o =>
{
    o.Conventions.Add(new CustomControllerConvention("api/whatever"));
});

That's all that's needed to make things work here, but as you're offering this up from another assembly, I'd suggest an extension method based approach. Here's an example of that:

public static class MvcBuilderExtensions
{
    public static IMvcBuilder SetCustomControllerRoute(
        this IMvcBuilder mvcBuilder, string newEndpoint)
    {
        return mvcBuilder.AddMvcOptions(o =>
        {
            o.Conventions.Add(new CustomControllerConvention(newEndpoint));
        });
    }
}

Here's how that would be called:

services.AddMvc()
    .SetCustomControllerRoute("api/whatever");

This whole approach means that without a call to SetCustomControllerRoute, api/Custom will still be used as a default.