How to use a controller in another assembly in ASP.NET Core MVC 2.0?

C#asp.net Core-Mvc

C# Problem Overview


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?

C# Solutions


Solution 1 - C#

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");

Solution 2 - C#

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));
}

Solution 3 - C#

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.

Solution 4 - C#

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.

Solution 5 - C#

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)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionmohammad rostami siahgeliView Question on Stackoverflow
Solution 1 - C#Martin ZikmundView Answer on Stackoverflow
Solution 2 - C#Pawel HofmanView Answer on Stackoverflow
Solution 3 - C#OskarView Answer on Stackoverflow
Solution 4 - C#Walter VerhoevenView Answer on Stackoverflow
Solution 5 - C#andrew pateView Answer on Stackoverflow