Unity with ASP.NET Core and MVC6 (Core)
Asked Answered
C

2

25

Update 09.08.2018
Unity is being developed here but I haven't had the time to test how it plays with the ASP.NET Core framework.

Update 15.03.2018
This solution is for the specific problem of using ASP.NET Core v1 with Unity while using the .NET Framework 4.5.2 NOT the .NET Core Framework. I had to use this setup since I needed some .Net 4.5.2 DLLs but for anyone starting afresh I would not recommend this approach. Also Unity is not being developed any further (to my knowlage) so I would recommend using the Autofac Framework for new projects. See this Post for more info on how to do that.

Intro
I am building a Web Application using ASP.NET with MVC. This Application depends on certain services (a WCF Service a Datastore service etc). Now to keep things nice and decoupled I want to use a DI (Dependecy Injection) Framework, specifically Unity.

Initial Research
I found this blog post but sadly its not working. The idea though is nice.
It basically says that you should not register all the services registered in the ServiceCollection into your own container, but rather reference the default ServiceProvider.
So. if something needs to be resolved the default ServiceProvider is called and in case it has no resolution the type will be resolved using your custom UnityContainer.

The Problems
MVC always tries to resolve the Controller with the default ServiceProvider.
Also, I noticed that even if the Controller would get resolved correctly, I can never "mix" Dependencies. Now, if I want to use one of my Services but also an IOptions interface from ASP the class can never be resolved because not one of those two containers has resolutions for both types.

What I need
So to recap I need the following things:

  • A setup where I dont need to copy ASP.NET Dependencies into my UnityContainer
  • A container which can resolve my MVC Controllers
  • A container which can resolve "mixed" Dependencies

EDIT:
So the question is how can I achieve these points ?

Environment
project.json:
enter image description here

Clearcole answered 26/8, 2016 at 19:19 Comment(4)
not really a questionViscountcy
Better now ? If not how should I rephrase it ?Clearcole
Unity is being developed, your mistake. github.com/unitycontainer/containerOrgan
hm seems like I missed that. In any case post is updatedClearcole
C
43

So after some research I came up with the following solutions to my problems:

Use Unity with ASP
To be able to use Unity with ASP I needed a custom IServiceProvider (ASP Documentation) so I wrote a wrapper for the IUnityContainer which looks like this

public class UnityServiceProvider : IServiceProvider
{
    private IUnityContainer _container;

    public IUnityContainer UnityContainer => _container;

    public UnityServiceProvider()
    {
        _container = new UnityContainer();
    }

    #region Implementation of IServiceProvider

    /// <summary>Gets the service object of the specified type.</summary>
    /// <returns>A service object of type <paramref name="serviceType" />.-or- null if there is no service object of type <paramref name="serviceType" />.</returns>
    /// <param name="serviceType">An object that specifies the type of service object to get. </param>
    public object GetService(Type serviceType)
    {
        //Delegates the GetService to the Containers Resolve method
        return _container.Resolve(serviceType);
    }

    #endregion
}

Also I had to change the Signature of the ConfigureServices method in my Startup class from this:

public void ConfigureServices(IServiceCollection services)

to this:

public IServiceProvider ConfigureServices(IServiceCollection services)

Now I can return my custom IServiceProvider and it will be used instead of the default one.
The full ConfigureServices Method is shown in the Wire up section at the bottom.

Resolving Controllers
I found this blog post. From it I learned that MVC uses an IControllerActivator interface to handle Controller instantiation. So I wrote my own which looks like this:

public class UnityControllerActivator : IControllerActivator
{
    private IUnityContainer _unityContainer;

    public UnityControllerActivator(IUnityContainer container)
    {
        _unityContainer = container;
    }

    #region Implementation of IControllerActivator

    public object Create(ControllerContext context)
    {
        return _unityContainer.Resolve(context.ActionDescriptor.ControllerTypeInfo.AsType());
    }


    public void Release(ControllerContext context, object controller)
    {
        //ignored
    }

    #endregion
}

Now if a Controller class is activated it will be instatiated with my UnityContainer. Therefore my UnityContainer must know how to Resolve any Controller!

Next Problem: Use the default IServiceProvider
Now if I register services such as Mvc in ASP.NET I normally would do it like this:

services.AddMvc();

Now if I use a UnityContainer all the MVC Dependencies could not be Resolved because they aren't Registered. So I can either Register them (like AutoFac) or I can create a UnityContainerExtension. I opted for the Extension and came up with following two clases :
UnityFallbackProviderExtension

public class UnityFallbackProviderExtension : UnityContainerExtension
{
    #region Const

    ///Used for Resolving the Default Container inside the UnityFallbackProviderStrategy class
    public const string FALLBACK_PROVIDER_NAME = "UnityFallbackProvider";

    #endregion

    #region Vars

    // The default Service Provider so I can Register it to the IUnityContainer
    private IServiceProvider _defaultServiceProvider;

    #endregion

    #region Constructors

    /// <summary>
    /// Creates a new instance of the UnityFallbackProviderExtension class
    /// </summary>
    /// <param name="defaultServiceProvider">The default Provider used to fall back to</param>
    public UnityFallbackProviderExtension(IServiceProvider defaultServiceProvider)
    {
        _defaultServiceProvider = defaultServiceProvider;
    }

    #endregion

    #region Overrides of UnityContainerExtension

    /// <summary>
    /// Initializes the container with this extension's functionality.
    /// </summary>
    /// <remarks>
    /// When overridden in a derived class, this method will modify the given
    /// <see cref="T:Microsoft.Practices.Unity.ExtensionContext" /> by adding strategies, policies, etc. to
    /// install it's functions into the container.</remarks>
    protected override void Initialize()
    {
        // Register the default IServiceProvider with a name.
        // Now the UnityFallbackProviderStrategy can Resolve the default Provider if needed
        Context.Container.RegisterInstance(FALLBACK_PROVIDER_NAME, _defaultServiceProvider);

        // Create the UnityFallbackProviderStrategy with our UnityContainer
        var strategy = new UnityFallbackProviderStrategy(Context.Container);

        // Adding the UnityFallbackProviderStrategy to be executed with the PreCreation LifeCycleHook
        // PreCreation because if it isnt registerd with the IUnityContainer there will be an Exception
        // Now if the IUnityContainer "magically" gets a Instance of a Type it will accept it and move on
        Context.Strategies.Add(strategy, UnityBuildStage.PreCreation);
    }

    #endregion
}


UnityFallbackProviderStrategy:

public class UnityFallbackProviderStrategy : BuilderStrategy
{
    private IUnityContainer _container;

    public UnityFallbackProviderStrategy(IUnityContainer container)
    {
        _container = container;
    }

    #region Overrides of BuilderStrategy

    /// <summary>
    /// Called during the chain of responsibility for a build operation. The
    /// PreBuildUp method is called when the chain is being executed in the
    /// forward direction.
    /// </summary>
    /// <param name="context">Context of the build operation.</param>
    public override void PreBuildUp(IBuilderContext context)
    {
        NamedTypeBuildKey key = context.OriginalBuildKey;

        // Checking if the Type we are resolving is registered with the Container
        if (!_container.IsRegistered(key.Type))
        {
            // If not we first get our default IServiceProvider and then try to resolve the type with it
            // Then we save the Type in the Existing Property of IBuilderContext to tell Unity
            // that it doesnt need to resolve the Type
            context.Existing = _container.Resolve<IServiceProvider>(UnityFallbackProviderExtension.FALLBACK_PROVIDER_NAME).GetService(key.Type);
        }

        // Otherwise we do the default stuff
        base.PreBuildUp(context);
    }

    #endregion
}

Now if my UnityContainer has no Registration for something it just ask the default Provider for it.
I learned all of this from several different articles

The nice thing about this approach is that I can also "mix" Dependencies now. If I need any of my Services AND an IOptions Interface from ASP my UnityContainer will resolve all of these Dependencies and Inject them into my Controller !!!
The only thing to remember is that if I use any of my own Dependencies I have to register my Controller class with Unity because the default IServiceProvider can no longer Resolve my Controllers Dependencies.

Finally: Wire up
Now in my project I use different services (ASP Options, MVC with options). To make it all work my ConfigureServices Method looks like this now:

public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        // Add all the ASP services here
        // #region ASP
        services.AddOptions();
        services.Configure<WcfOptions>(Configuration.GetSection("wcfOptions"));

        var globalAuthFilter = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();

        services.AddMvc(options => { options.Filters.Add(new AuthorizeFilter(globalAuthFilter)); })
                .AddJsonOptions
            (
                options => options.SerializerSettings.ContractResolver = new DefaultContractResolver()
            );
        // #endregion ASP

        // Creating the UnityServiceProvider
        var unityServiceProvider = new UnityServiceProvider();

        IUnityContainer container = unityServiceProvider.UnityContainer;

        // Adding the Controller Activator
        // Caution!!! Do this before you Build the ServiceProvider !!!
        services.AddSingleton<IControllerActivator>(new UnityControllerActivator(container));

        //Now build the Service Provider
        var defaultProvider = services.BuildServiceProvider();

        // Configure UnityContainer
        // #region Unity

        //Add the Fallback extension with the default provider
        container.AddExtension(new UnityFallbackProviderExtension(defaultProvider));

        // Register custom Types here

        container.RegisterType<ITest, Test>();

        container.RegisterType<HomeController>();
        container.RegisterType<AuthController>();

        // #endregion Unity

        return unityServiceProvider;
    }

Since I learned most of what I know about DI in the past week I hope I didnt break any big Pricipal/Pattern if so please tell me!

Clearcole answered 26/8, 2016 at 19:19 Comment(21)
Nice, thinking to convert it into a post on blog. If you allow.Mediatory
Sure. The more people it reaches the more help it might do :DClearcole
Thanks so much for this solution. Saved me hours!Crockett
Your solution got me searching on the Internet and these links could give you an alternative to the Fallback mechanism: fueltravel.com/blog/… and dzimchuk.net/bring-your-own-di-container-to-aspnet-5-unity Credits to you and the authors of those articles, thxRevulsive
Nice solution except when I try and configure using app.UseMvc() it is unable to create the internal class Microsoft.AspNetCore.Mvc.Internal.MvcRouteHandler as part of the fallback.Brunn
well without any code I cant really tell why that is. I had this problem before using the FallbackProviderClearcole
If a type is resolved using the fallback extension, would there be a problem if that type depended upon types in Unity container?Patagonia
Its been a while since I wrote this post but as far as I remember it would not be a problem. Each dependency is again resolved and when it will be resolved it should first look in the unity container and if it cant be found there again look in the old providerClearcole
@D4rthB4n3 Heloo i am new in creating api ,now i need to create webaopi core with unity and i found best answer of yours ,but here what is WcfOptions in ConfigureServices(IServiceCollection services) method ?its class?Barriebarrientos
@Barriebarrientos You probably dont have to worry about those. These are options from an appsettings.json file that I used but they have nothing to do with Unity. If your intereseted in using the IOptions Interface for Configuring your Application you can read up on it here learn.microsoft.com/en-us/aspnet/core/fundamentals/…Clearcole
@D4rthB4n3 I'm getting a error saying that theres not resolution for IServer... any ideas?Concern
@D4rthB4n3 on the fallback, i removed the "!" and everything is working fine nowConcern
@Concern not sure where the IServer Interface comes from but its not from this part of the code. On the FallbackProvider, well what you are saying is that If the Type is registered with our container you will go and ask the other container to provide it. Doesnt really make sense but maybe its bc you set something else up differently ?Clearcole
@D4rthB4n3 the IServer is from Microsoft.AspNetCore.Hosting.Server.IServerConcern
@D4rthB4n3 the IServer (and a bunch others) come from the AddMvc(). The problem is that !_container.IsRegistered(key.Type) is returning a false positive for these types...Concern
@D4rthB4n3 if you create a new project from scratch using .net core v2 you'll get an exception... pls check it out... i would really like to use unity here...Concern
@Concern well it was written for .Net Core 1.0 so yeah might not work with 2.0. Problem is I dont really use Unity anmore (its not beeing developed further) and dont think it makes sense to invest time into it. If your not really set on Unity try Autofac. Since ASP.NET Core is using it under the hood there are extensions to copy all registrations and therefore you dont have to use a workaround like this one. (any it worked like a charm so far)Clearcole
@Concern also this was a kinda wanky setup since the actual Framework was .Net 4.5.2 (project.json line 30) which then pulled in Core packages. In my scenario this was desired since I wanted to use some DLLs which I wrote for .NET 4.5.2 but if you start a fresh project I would not recommend this approach anymoreClearcole
@Concern maybe Karels answer is what you were looking for.Clearcole
For ASP.Net Core 2.0 and Unity there is official solution available from Unity authors as NuGet. (https://mcmap.net/q/131140/-unity-with-asp-net-core-and-mvc6-core)Organ
what is the Nuget package of Unity for Core 3.1?Oxazine
O
32

For ASP.Net Core 2.0, 2.1, 2.2, 3.1 and Unity there is official solution available from Unity authors as NuGet package here: NuGetPackage

Here is Git repository with samples: Git repo

Usage is very simple (from Git repo homepage):

public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
       .UseUnityServiceProvider()   <---- Add this line
       .UseStartup<Startup>()
       .Build();

And here is example with Unity DI for ASP.Net Core.

I am using this solution in my ASP.Net Core application and works good.

Organ answered 11/5, 2018 at 11:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.