77

For the sake of modularity, I have created some controllers in different assemblies. Each assembly represents a bounded context (a module, a sub-system, a division, etc.) of the overall system.

Each module's controllers are developed by someone that knows nothing about other modules, and a central orchestrator is about to cover all these modules in one single application.

So, there is this module called school, and it has a TeacherController in it. The output of it is Contoso.School.UserService.dll.

The main orchestrator is called Education and it has a reference to Contoso.School.UserService.dll.

My program.cs is:

    public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args).UseKestrel()
            .UseStartup<Startup>()
            .Build();

Yet for the routes of teacher controller, I get 404. How to use controllers in other assemblies?

6 Answers 6

92

Inside the ConfigureServices method of the Startup class you have to call the following:

services.AddMvc().AddApplicationPart(assembly).AddControllersAsServices();

Where assembly is the instance Assembly representing Contoso.School.UserService.dll.

You can load it either getting it from any included type or like this:

var assembly = Assembly.Load("Contoso.School.UserService");
9
  • What nuget package do I need for AddApplicationPart ? Commented Mar 10, 2018 at 18:19
  • 3
    It is in the Microsoft.AspNetCore.Mvc.Core.dll assembly and has Microsoft.Extensions.DependencyInjection namespace Commented Mar 10, 2018 at 18:21
  • See the docs for more info and source code sample - learn.microsoft.com/en-us/aspnet/core/mvc/advanced/app-parts Commented Mar 10, 2018 at 18:22
  • 2
    If you reference the assembly you can use var assembly = typeof(SomeTypeFromTheAssembly).Assembly Commented Apr 17, 2019 at 17:34
  • 2
    The key for me was the AddControllersAsServices() -- Many Thanks!!
    – mhand
    Commented Jan 7, 2020 at 14:15
69

For .NET Core 3.0 the API has been slightly changed and the easiest way to register controllers from external assembly in Startup.cs looks like:

public void ConfigureServices(IServiceCollection services)
{
    var assembly = typeof(**AnyTypeFromRequiredAssembly**).Assembly;

    services.AddControllers()
        .PartManager.ApplicationParts.Add(new AssemblyPart(assembly));
}
3
  • 2
    I use the old syntax in .NET Core 3.0, and I still get the results. Could you also provide a link to your answer for further reading? Commented Dec 2, 2019 at 6:10
  • 1
    Please take a look at the migration guide. There are 3 new extension methods, although AddMvc() should still work fine. Commented Dec 2, 2019 at 11:55
  • what about the common mortals that do not use .NET code, but just old .NET framework?
    – albanx
    Commented Apr 4 at 14:43
6

FYI, in ASP.NET Core 7, this works automagically.

To be more precise, just by the fact that my main server app had a hard reference to a class library with an [ApiController] class in a Controllers folder, routes on that controller class just worked

3
  • Still working in version 8.
    – colinD
    Commented Mar 25 at 10:48
  • @colinD, are you sure? I just upgraded from ASP/.NET v7 to v8.0.3 and all automatic inclusion of class library controllers broke (i.e., the only controllers added to the list of endpoints at runtime were the ones within the executable server project)
    – BCA
    Commented Apr 3 at 19:56
  • No longer working in 8.0.3 Commented Apr 6 at 15:27
5

I'm trying to resolve the controllers while migrating legacy unit tests from .NET Framework to aspnet core 3.1, this is the only way I got it working:

var startupAssembly = typeof(Startup).Assembly;

var services = new ServiceCollection();

// Add services
...

// Add Controllers
services
    .AddControllers()
    .AddApplicationPart(startupAssembly)
    .AddControllersAsServices();

If I change the order of the three last lines it does not work.


Avoid doing this for unit testing unless you really have to, i.e. for legacy reasons. If you are not working with legacy code you are probably looking for integration tests.

3

There is nothing wrong with the above answers, I just use a 1 liners as I know the class that is included in the external assembly that I'd like to load.

The bellow sample comes from the ASP-WAF firewall and is used to load reporting endpoints and dashboard web pages in an existing web application in .net Core 3.1.

services.AddMvc(options =>
{
    options.Filters.Add<Walter.Web.FireWall.Filters.FireWallFilter>();
    options.Filters.Add<Walter.Web.FireWall.Filters.PrivacyPreferencesFilter>();
}).AddApplicationPart(Assembly.GetAssembly(typeof(Walter.Web.FireWall.DefaultEndpoints.ReportingController)));

so to answer your question, let's assume that the TeacherController is in namespace Contoso.School.UserService your implementation would be:

services.AddMvc(options =>
{                
    //any option
}).AddApplicationPart(Assembly.GetAssembly(typeof(Contoso.School.UserService.TeacherController )));

or, if you do not have any options then just ignore them and use this:

services.AddMvc()
    .AddApplicationPart(Assembly.GetAssembly(typeof(Contoso.School.UserService.TeacherController)));

If you are not sure about the class in the assembly then us intelisence in your code starting with the namespace and look for a type to use or open object browser in visual studio

Let me know if you have any issues.

0

Martin above has the answer, thanks. However it is not immediately obvious how you pass an assembly to Startup.ConfigureServices. How I achieved this was... in the code where I create and start the webHost I call IWebHostBuilder.ConfigureServices and give it something containing the assembly (in my case a custom interface called IOutputProcess)

            _webHost = CreateWebHostBuilder().ConfigureServices(e => e.AddSingleton(_outputProcess)).Build();
            _webHost.Start();

then in my Startup.ConfigureServices I pull that instance back out of the IServiceCollection with...

    public void ConfigureServices(IServiceCollection services)
    {
        var sp = services.BuildServiceProvider();
        var outputProcess = sp.GetService<IOutputProcess>();
        services.AddMvc().AddApplicationPart(outputProcess.ControllerAssembly).AddControllersAsServices();
    }

I doubt instantiating a service provider purely for this purpose is the cleanest way to do things, but it does work. (I'm open to better ideas)

1
  • use a one liner services.AddMvc() .AddApplicationPart(Assembly.GetAssembly(typeof(Contoso.School.UserService.TeacherController))); Commented Oct 10, 2020 at 8:37

Not the answer you're looking for? Browse other questions tagged or ask your own question.