13

I have a .NET Core worker project and want to add a library providing several HTTP endpoints. I have to stay with the worker project, I can't change it to a Web API project. What I have done so far:

  • I created a worker project
  • I created a library project
  • I added a reference to the library in the worker project
  • In the Worker.csproj and Lib.csproj I added <FrameworkReference Include="Microsoft.AspNetCore.App" /> to the item group to gain access to the webbuilder stuff
  • I installed the package Microsoft.AspNetCore.Mvc.Core in the library project
  • In the library project I add several extensions classes and a web API controller for testing purposes

.

public static class IApplicationBuilderExtensions
{
    public static IApplicationBuilder AddLibrary(this IApplicationBuilder applicationBuilder)
    {
        applicationBuilder.UseRouting();
        applicationBuilder.UseEndpoints(endpoints => { endpoints.MapControllers(); });
        return applicationBuilder;
    }
}

public static class IServiceCollectionExtensions
{
    public static IServiceCollection AddLibrary(this IServiceCollection services)
    {
        services.AddMvc(); // this might be not needed
        services.AddControllers();
        return services;
    }
}

public static class KestrelServerOptionsExtensions
{
    public static void AddLibrary(this KestrelServerOptions kestrelServerOptions)
    {
        kestrelServerOptions.ListenLocalhost(5000); // value from config
    }
}

[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase
{
    [HttpGet]
    public async Task<ActionResult> Test()
    {
        return Ok();
    }
}
  • In the worker project I created a Startup class

.

internal class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddLibrary();
    }

    public void Configure(IApplicationBuilder applicationBuilder)
    {
        applicationBuilder.AddLibrary();
    }
}
  • In the worker project I modified the Program class to

.

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webHostBuilder =>
            {
                webHostBuilder.UseKestrel(kestrelServerOptions =>
                {
                    kestrelServerOptions.AddLibrary();
                }).UseStartup<Startup>();
            })
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<Worker>();
            });
}
  • I run the project and call GET http://localhost:5000/users
  • I would expect a 200 but get a 404 and the debugger does not hit the controller endpoint in the library project

Does someone know what I'm missing?

It is possible for me to Add Web API controller endpoint to Kestrel worker project but it is not possible for me to add web controllers to a library project and call them from the library.

4
  • 1
    Hmm I did the same and I can reproduce his problem ... Commented Oct 1, 2020 at 19:00
  • 2
    I am able to reproduce this problem, in the logs I can see that no routes are found for /Users, but I can't figure out why. You can make it work by changing your worker project to a web project, by using Microsoft.NET.Sdk.Web instead of Microsoft.NET.Sdk.Worker. The worker service will run the same way with this Sdk. This doesn't answer your question but it's a workaround. Commented Oct 2, 2020 at 9:30
  • 1
    yeah thanks for your reply. Unfortunately it's not possible for me to change it to a Web API project Commented Oct 2, 2020 at 9:53
  • try changing [Route("[controller]")] to [Route("[controller]/[action]")]. I hope this would resolve the error. Thanks, Commented Oct 4, 2020 at 0:30

1 Answer 1

9
+200

I have a working solution on my computer.

Solving the problem

It turns out it's actually really simple. MVC loads ApplicationParts from different assemblies. In a normal web application setup, this seems to be picked up properly, even when using Razor Class Libraries. However, when using a worker service and either a library, or a Razor Class Library, it doesn't work, and so the route for your controller isn't registered.

To add the library as an application part, open up the IServiceCollectionExtensions class you created, and change it to this:

public static class IServiceCollectionExtensions
{
    public static IServiceCollection AddLibrary(this IServiceCollection services)
    {
        services
            .AddControllers()
            // Notice the assembly is the type of this class, as this
            // is the assembly the controller is in.
            // You'll have to call this for every assembly you have
            // controllers in, except for any controllers
            // you might put in your worker service project.
            .AddApplicationPart(typeof(IServiceCollectionExtensions).Assembly);

        return services;
    }
}

Now, starting the app and sending a request to http://localhost:5000/users will work. I've confirmed this works for both libraries and Razor Class Libraries.

worker service routing

I should also say that controllers in the worker service project itself will work as normal - there is no special setup required for those.

Things you don't need

  1. You don't need the call to services.AddMvc() in IServiceCollectionExtensions. Calling this, without parameters, is the same as calling AddControllersWithViews() and AddRazorPages().
  2. You don't need the Microsoft.AspNetCore.Mvc.Core package in your library project - mine runs fine just with the framework reference you added. The only package I have in there is for Microsoft.AspNetCore.Server.Kestrel.Core.
Sign up to request clarification or add additional context in comments.

7 Comments

hey, thanks for your solution! adding this .AddApplicationPart(typeof(IServiceCollectionExtensions).Assembly); solved the problem.
But there is one question left: kestrelServerOptions.ListenLocalhost(myValueFromConfig); by doing so the server starts to listen for two ports ... 5000 and my config value ... I made a screenshot from the console i.imgur.com/AvhcUDh.png
so the listener works fine for my configured port but why does it listen to the port 5000 too?
@OlafSvenson No problem. Kestrel is only listening to one port for me. I just tried calling kestrelServerOptions.ListenLocalhost(n) with two different numbers, and it does bind to two different ports. So have you accidentally called it twice? (Maybe once from the library, and once in your worker's Program?)
ah exactly, sorry my bad!
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.