18

I am asking myself if it is possible to load a DLL with Controllers in it at runtime and use it.

The only solution I've found is to add an assembly via ApplicationPart on the StartUp:

var builder = services.AddMvc();
builder.AddApplicationPart(
    AssemblyLoadContext.Default.LoadFromAssemblyPath(
        @"PATH\ExternalControllers.dll"
));

Do anyone know if it is possible to register Controller at any time, because the issue with that solution is, that you have to restart the WebService when you want to add another DLL with Controllers in it. It would be nice when you just can add them at any time and register them at any time in the application.

8
  • 4
    For example I when I want to programm a plugin based WebAPI where you can modifiy the WebAPI by simply adding a DLL to a folder and then grab it and load it into the WebAPI. This way the WebAPI is super dynamic and the raw WebAPI is stupid as hell. It improves his intelligence by every single plugin. Commented Sep 11, 2017 at 14:12
  • 2
    And the problem with restarting the application is what? Commented Sep 11, 2017 at 22:31
  • The problem is @poke that it's kind of annoying to restart it in production. I mean its not as bad as dont having this feature, but if it is makable without a restart that would be perfect. Commented Sep 12, 2017 at 6:32
  • 1
    Then you’re pretty much out of luck. The standard dependency injection system does not allow you to change the service collection after it has been materialized. So you would have to use a different DI container for this that allows for later modification (or adding DI modules; I believe Autofac does that). At that point, you are probably already pretty deep in terms of complexity, so I’m really not sure if it’s worth it. – Have you considered using multiple independent web applications yet? Commented Sep 12, 2017 at 7:58
  • 1
    @ChrisPratt If you're building a reusable framework then there are situations where you want to create controllers on the fly with the generic arguments as configurable from the calling assembly. Commented Oct 5, 2017 at 14:56

2 Answers 2

21

This is possible now on .net core 2.0+

Please see code ActionDescriptorCollectionProvider.cs:

    public ActionDescriptorCollectionProvider(
        IEnumerable<IActionDescriptorProvider> actionDescriptorProviders,
        IEnumerable<IActionDescriptorChangeProvider> actionDescriptorChangeProviders)
    {
        _actionDescriptorProviders = actionDescriptorProviders
            .OrderBy(p => p.Order)
            .ToArray();

        _actionDescriptorChangeProviders = actionDescriptorChangeProviders.ToArray();

        ChangeToken.OnChange(
            GetCompositeChangeToken,
            UpdateCollection);
    }

Step 1:Implement IActionDescriptorChangeProvider class:

public class MyActionDescriptorChangeProvider : IActionDescriptorChangeProvider
{
    public static MyActionDescriptorChangeProvider Instance { get; } = new MyActionDescriptorChangeProvider();

    public CancellationTokenSource TokenSource { get; private set; }

    public bool HasChanged { get; set; }

    public IChangeToken GetChangeToken()
    {
        TokenSource = new CancellationTokenSource();
        return new CancellationChangeToken(TokenSource.Token);
    }
}

Step 2:AddSingleton on Startup.ConfigureServices():

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddSingleton<IActionDescriptorChangeProvider>(MyActionDescriptorChangeProvider.Instance);
    services.AddSingleton(MyActionDescriptorChangeProvider.Instance);
}

Step 3: Register controller at runtime:

public class TestController : Controller
{
    private readonly ApplicationPartManager _partManager;
    private readonly IHostingEnvironment _hostingEnvironment;
    public TestController(
        ApplicationPartManager partManager,
        IHostingEnvironment env)
    {
        _partManager = partManager;
        _hostingEnvironment = env;
    }
    public IActionResult RegisterControllerAtRuntime()
    {
        string assemblyPath = @"PATH\ExternalControllers.dll";
        var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
        if (assembly != null)
        {
            _partManager.ApplicationParts.Add(new AssemblyPart(assembly));
            // Notify change
            MyActionDescriptorChangeProvider.Instance.HasChanged = true;
            MyActionDescriptorChangeProvider.Instance.TokenSource.Cancel();
            return Content("1");
        }
        return Content("0");
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

Can you explain how does this work or why and if there is a doc on it? Thank you.
@cnxiaboy do you really need 3 different references to the singleton MyActionDescriptorChange()? Could the static Instance property and services.AddSingleton(MyActionDescriptorChangeProvider.Instance) line be removed, followed by this simply being services.AddSingleton<IActionDescriptorChangeProvider>(new MyActionDescriptorChangeProvider())?
13

This is possible now.

Please see the updated documentation about how to add dynamic controllers:

public class GenericControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
    public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
    {
        // This is designed to run after the default ControllerTypeProvider, 
        // so the list of 'real' controllers has already been populated.
        foreach (var entityType in EntityTypes.Types)
        {
            var typeName = entityType.Name + "Controller";
            if (!feature.Controllers.Any(t => t.Name == typeName))
            {
                // There's no 'real' controller for this entity, so add the generic version.
                var controllerType = typeof(GenericController<>)
                    .MakeGenericType(entityType.AsType()).GetTypeInfo();
                feature.Controllers.Add(controllerType);
            }
        }
    }
}

6 Comments

I don't understand your question?
This controller registration is only once during the setup not every request to the controller correct
Yes. I just dd a quick run on my localhost and it was called just once. It also makes sense that a provider type is registered once and then invoked multiple times.
If you want to use this to define which controllers are included and which one not, then use feature.Controllers.Clear() at the beginning, thereafter loop through all controllers you want to include (use reflection)
How do you give the name for your controller? Because the routing guesses the controller based on the class name.
|

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.