How to use a controller in another assembly in ASP.NET Core MVC 2.0?
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");
How to use a controller in another assembly in ASP.NET Core 3.0?
The solution is to .AddApplicationPart(assembly)
when adding the controllers in Startup.cs
.
So if I have a projectMyApp.WebApi
that has a dependency to the nuGet package: Microsoft.AspNetCore.Mvc.Core
(current version is 2.2.5)
with the following controller:
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace MyApp.WebApi.Controllers
{
[ApiController]
[Route("[controller]")]
public class SamplesController
: ControllerBase
{
[HttpGet]
public IEnumerable<string> Get()
{
var samples =
new List<string>
{
"sample1", "sample2", "sample3"
};
return samples;
}
}
}
and I have my Startup.cs
in MyApp.Host
as:
using MyApp.WebApi.Controllers;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Reflection;
namespace MyApp.Cmd.Host
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
var sampleAssembly = Assembly.GetAssembly(typeof(SamplesController));
services
.AddControllers()
.AddApplicationPart(sampleAssembly)
.AddControllersAsServices();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
then the app will detect the controllers in different assembly and I'll be able to access: https://localhost:5001/samples
Use another controller from different project on asp.net core
First, your other projects shouldn't be an Web application, but a normal PCL.
Second, after you reference it, you need to tell ASP.NET Core that it should look for controllers there. For that reason the Application Parts Api exists.
services.AddMvc()
.AddApplicationPart(typeof(CanaryController).GetTypeInfo().Assembly);
P.S. while the docs state it should work out of the box, when doing integration tests/unit tests on the controllers the .AddApplicationPart
is still required, at least was the case in ASP.NET Core 2.0
ASP.NET Core MVC main project can't reach Controllers in separate assembly
So, if sum this up:
1) You need to Add Reference
to your separate project with
controllers.
2) Configure services (part):
public void ConfigureServices(IServiceCollection services)
{
var controllersAssembly = Assembly.Load(newAssemblyName("SomeProject.MeetTheControllers"));
services.AddMvc().AddApplicationPart(controllersAssembly).AddControllersAsServices();
}
3) My configure looks like that (pay attention to UseMvcWithDefaultRoute
):
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseReact(config =>
{
});
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
So in my case of controller like this you don't need to put Routes
attribute here, just call it by Some/Test
url
namespace SomeProject.MeetTheControllers.Test
{
using Microsoft.AspNetCore.Mvc;
public class SomeController: Controller
{
public void Test(string name, string notname)
{
string username = name;
string nan = notname;
}
}
}
ASP.NET Core MVC controllers in separate assembly
I believe you are hitting the following known issue in RC2.
https://github.com/aspnet/Mvc/issues/4674 (workaround is mentioned in the bug)
This has been fixed since then but will only be available in next release (unless you are ok with using nightly builds)
MVC 6 RC2 Controllers in another assembly
After some time spending working with the ControllerFeature
directly with no result it was time to go back to basics.
Basically at start up of the application controllers are registered into the controller feature container not from the controller feature. This is key, as you need to get the controllers registered.
I was browsing the GitHub repository for RC2 and came across the ControllerFeatureProvider
. As stated.
Discovers controllers from a list of <see cref="ApplicationPart"/>
And then has a method further down to PopulateFeature
where we can see it grabs all the parts registered to the application and extracts the controller interfaces (the IsController()
method is worth a review).
/// <inheritdoc />
public void PopulateFeature(
IEnumerable<ApplicationPart> parts,
ControllerFeature feature)
{
foreach (var part in parts.OfType<IApplicationPartTypeProvider>())
{
foreach (var type in part.Types)
{
if (IsController(type) && !feature.Controllers.Contains(type))
{
feature.Controllers.Add(type);
}
}
}
}
So now we know how the controllers are found, they come from an ApplicationPart
registered to the application. Next question was how do we create an application part.
After some review and trying to use dependency injection, manually adding the part to the application to get my parts registered I came across another concept.
The interface IMvcBuilder
has the extension method AddApplicationPart
which adds an Assembly
to the application parts. This is done by wrapping the assembly in an AssemblyPart
application part. On review of the AssemblyPart
this part returns all of the types found in the assembly to the calling part system (in our case the ControllerFeatureProvider
).
/// <inheritdoc />
public IEnumerable<TypeInfo> Types => Assembly.DefinedTypes;
Now something interesting with the AssemblyPart
is the method GetReferencePaths()
/// <inheritdoc />
public IEnumerable<string> GetReferencePaths()
{
var dependencyContext = DependencyContext.Load(Assembly);
if (dependencyContext != null)
{
return dependencyContext.CompileLibraries.SelectMany(library => library.ResolveReferencePaths());
}
// If an application has been compiled without preserveCompilationContext, return the path to the assembly
// as a reference. For runtime compilation, this will allow the compilation to succeed as long as it least
// one application part has been compiled with preserveCompilationContext and contains a super set of types
// required for the compilation to succeed.
return new[] { Assembly.Location };
}
It appears that the final piece of the puzzle is to enable preserveCompilationContext
within the modules (or external assembly's) project.json file.
"preserveCompilationContext": {
"type": "boolean",
"description": "Set this option to preserve reference assemblies and other context data to allow for runtime compilation.",
"default": false
}
Finally the implementation and resolution for this became quite simple. Each of our external assemblies (or modules) are loaded through our ModuleManager
class. This has a list of all referenced module assemblies. So in the ConfigureServices
method in the Startup.cs
file where the MVC is registered we simply call the extension method AddApplicationPart
for each module assembly as.
var mvcBuilder = services.AddMvc();
foreach(var module in ModulesManager.ReferencedModules)
{
mvcBuilder.AddApplicationPart(module.ReferencedAssembly);
}
Once making these small changes my external controllers stopped returning a 404
.
Related Topics
Microsoft.Office.Interop.Excel Really Slow
Suppress Properties with Null Value on ASP.NET Web API
Binding Property to Control in Winforms
How to Execute an X86 Assembly Sequence from Within C#
Send Http Post Message in ASP.NET Core Using Httpclient Postasjsonasync
How to Get the Network Interface and Its Right Ipv4 Address
.Net, Event Every Minute (On the Minute). Is a Timer the Best Option
Multi-Tenant with Code First Ef6
Performance of Find() VS. Firstordefault()
Declaring a Variable Inside or Outside an Foreach Loop: Which Is Faster/Better
Who Should Call Dispose on Idisposable Objects When Passed into Another Object
Button Inside a Winforms Textbox
What's a Good Threadsafe Singleton Generic Template Pattern in C#
String.Replace() VS. Stringbuilder.Replace()
How to Unmask Password Text Box and Mask It Back to Password
Formatting Numbers with Significant Figures in C#
The Type Arguments for Method Cannot Be Inferred from the Usage