71

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?

5 Answers5

91

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");
Martin Zikmund
  • 38,440
  • 7
  • 70
  • 91
  • What nuget package do I need for `AddApplicationPart` ? – Bilal Fazlani Mar 10 '18 at 18:19
  • 3
    It is in the `Microsoft.AspNetCore.Mvc.Core.dll` assembly and has `Microsoft.Extensions.DependencyInjection` namespace – Martin Zikmund Mar 10 '18 at 18:21
  • See the docs for more info and source code sample - https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/app-parts – Martin Zikmund Mar 10 '18 at 18:22
  • i am getting error while loading assembly. throwing error, cannot load assembly. – Sumit Joshi Apr 09 '19 at 09:59
  • 2
    If you reference the assembly you can use `var assembly = typeof(SomeTypeFromTheAssembly).Assembly` – Martin Zikmund Apr 17 '19 at 17:34
  • This is kind of blowing my mind. It means that `AddMvc` does not traverse the references to other projects. I've loaded this up and tried it several different ways and in every case I have to `AddApplicationPart` or MVC will not see controllers in another assembly. Is there any .net core documentation that highlights the fact that you have to do this to load controllers in another assembly? – crthompson Aug 14 '19 at 22:32
  • FTA above it says `By default MVC will search the dependency tree and find controllers (even in other assemblies). To load an arbitrary assembly (for instance, from a plugin that isn't referenced at compile time), you can use an application part.` This is clearly not the case however. It does not search the dependency tree. – crthompson Aug 14 '19 at 22:34
  • I always put my controllers on their own separate project.... Never had to to this. – Jonathan Alfaro Dec 01 '19 at 02:16
  • 2
    The key for me was the `AddControllersAsServices()` -- Many Thanks!! – mhand Jan 07 '20 at 14:15
67

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));
}
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
Pawel Hofman
  • 1,326
  • 11
  • 10
  • 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? – mohammad rostami siahgeli Dec 02 '19 at 06:10
  • 1
    Please take a look at the [migration guide](https://learn.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.0&tabs=visual-studio#mvc-service-registration). There are 3 new extension methods, although `AddMvc()` should still work fine. – Pawel Hofman Dec 02 '19 at 11:55
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.

Oskar
  • 1,996
  • 1
  • 22
  • 39
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.

Artur
  • 4,595
  • 25
  • 38
Walter Verhoeven
  • 3,867
  • 27
  • 36
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)

andrew pate
  • 3,833
  • 36
  • 28