问题描述
更新 09.08.2018
Unity 正在
所以经过一番研究,我想出了以下解决方案:
将 Unity 与 ASP 结合使用
为了能够在 ASP 中使用 Unity,我需要一个自定义的 IServiceProvider (ASP 文档) 所以我为 IUnityContainer 编写了一个包装器,看起来像这样
公共类 UnityServiceProvider : IServiceProvider{私有 IUnityContainer _container;公共 IUnityContainer UnityContainer =>_容器;公共 UnityServiceProvider(){_container = 新 UnityContainer();}#region IServiceProvider 的实现///<summary>获取指定类型的服务对象.</summary>///<returns>类型为<paramref name="serviceType"/>的服务对象.-或-如果没有类型<paramref name="serviceType"/>的服务对象,则为null.</返回>///<param name="serviceType">一个对象,指定要获取的服务对象的类型.</参数>公共对象 GetService(类型 serviceType){//将GetService委托给Containers Resolve方法return _container.Resolve(serviceType);}#endregion}
我还必须在我的 Startup 类中更改 ConfigureServices 方法的签名:
public void ConfigureServices(IServiceCollection services)
到这里:
public IServiceProvider ConfigureServices(IServiceCollection services)
现在我可以返回我的自定义 IServiceProvider,它将被使用而不是默认的.
完整的 ConfigureServices 方法显示在底部的 Wire up 部分中.
解析控制器
我找到了这篇博文.从中我了解到 MVC 使用 IControllerActivator 接口来处理控制器实例化.所以我写了自己的,看起来像这样:
公共类 UnityControllerActivator : IControllerActivator{私有的统一容器_unityContainer;公共 UnityControllerActivator(IUnityContainer 容器){_unityContainer = 容器;}#region IControllerActivator 的实现公共对象创建(ControllerContext 上下文){返回 _unityContainer.Resolve(context.ActionDescriptor.ControllerTypeInfo.AsType());}公共无效释放(ControllerContext上下文,对象控制器){//忽略}#endregion}
现在,如果一个 Controller 类被激活,它将被我的 UnityContainer 实例化.因此我的 UnityContainer 必须知道如何解析任何控制器!
下一个问题:使用默认的IServiceProvider
现在,如果我在 ASP.NET 中注册 Mvc 等服务,我通常会这样做:
services.AddMvc();
现在,如果我使用 UnityContainer,所有 MVC 依赖项都无法解析,因为它们没有注册.所以我可以注册它们(比如 AutoFac)或者我可以创建一个 UnityContainerExtension.我选择了扩展并提出了以下两个类别:
UnityFallbackProviderExtension
公共类 UnityFallbackProviderExtension : UnityContainerExtension{#region 常量///用于解析UnityFallbackProviderStrategy类里面的Default Container公共常量字符串 FALLBACK_PROVIDER_NAME = "UnityFallbackProvider";#endregion#region 变量//默认的服务提供者,所以我可以将它注册到 IUnityContainer私有 IServiceProvider _defaultServiceProvider;#endregion#region 构造函数///<总结>///创建 UnityFallbackProviderExtension 类的新实例///</总结>///<param name="defaultServiceProvider">用于回退到</param>的默认Provider公共 UnityFallbackProviderExtension(IServiceProvider defaultServiceProvider){_defaultServiceProvider = defaultServiceProvider;}#endregion#region 覆盖 UnityContainerExtension///<总结>///使用此扩展的功能初始化容器.///</总结>///<备注>///当在派生类中被覆盖时,此方法将修改给定的///<见 cref="T:Microsoft.Practices.Unity.ExtensionContext"/>通过添加策略、政策等///将它的函数安装到容器中.</remarks>受保护的覆盖无效初始化(){//使用名称注册默认的 IServiceProvider.//现在 UnityFallbackProviderStrategy 可以根据需要解析默认 ProviderContext.Container.RegisterInstance(FALLBACK_PROVIDER_NAME, _defaultServiceProvider);//使用我们的 UnityContainer 创建 UnityFallbackProviderStrategyvar strategy = new UnityFallbackProviderStrategy(Context.Container);//添加要使用 PreCreation LifeCycleHook 执行的 UnityFallbackProviderStrategy//PreCreation 因为如果它没有注册到 IUnityContainer 就会有一个异常//现在,如果 IUnityContainer 神奇地"获得了一个类型的实例,它将接受它并继续前进Context.Strategies.Add(strategy, UnityBuildStage.PreCreation);}#endregion}
UnityFallbackProviderStrategy:
公共类 UnityFallbackProviderStrategy : BuilderStrategy{私有 IUnityContainer _container;公共 UnityFallbackProviderStrategy(IUnityContainer 容器){_container = 容器;}#region 覆盖 BuilderStrategy///<总结>///在构建操作的责任链期间调用.这///PreBuildUp 方法在链正在执行时被调用///前进方向.///</总结>///<param name="context">构建操作的上下文.</param>公共覆盖无效 PreBuildUp(IBuilderContext 上下文){NamedTypeBuildKey key = context.OriginalBuildKey;//检查我们正在解析的类型是否注册到容器中if (!_container.IsRegistered(key.Type)){//如果不是,我们首先获取默认的 IServiceProvider,然后尝试使用它解析类型//然后我们把Type保存在IBuilderContext的Existing Property中告诉Unity//它不需要解析类型context.Existing = _container.Resolve(UnityFallbackProviderExtension.FALLBACK_PROVIDER_NAME).GetService(key.Type);}//否则我们做默认的事情base.PreBuildUp(上下文);}#endregion}
现在,如果我的 UnityContainer 没有注册某些东西,它只需向默认提供者询问它.
我从几篇不同的文章中学到了所有这些
- MSDN Unity 文章
- 自动模拟 Unity 容器扩展
- 自定义对象工厂 Unity 扩展李>
这种方法的好处是我现在还可以混合"依赖项.如果我需要我的任何服务和来自 ASP 的 IOptions 接口,我的 UnityContainer 将解析所有这些依赖项并将它们注入我的控制器!
唯一要记住的是,如果我使用我自己的任何依赖项,我必须向 Unity 注册我的 Controller 类,因为默认的 IServiceProvider 不能再解析我的控制器依赖项.
最后:连线
现在在我的项目中,我使用不同的服务(ASP 选项、带有选项的 MVC).为了让这一切正常工作,我的 ConfigureServices 方法现在看起来像这样:
public IServiceProvider ConfigureServices(IServiceCollection services){//在此处添加所有的 ASP 服务//#区域 ASP服务.AddOptions();services.Configure<WcfOptions>(Configuration.GetSection("wcfOptions"));var globalAuthFilter = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().建造();services.AddMvc(options => { options.Filters.Add(new AuthorizeFilter(globalAuthFilter)); }).AddJsonOptions(选项=>options.SerializerSettings.ContractResolver = new DefaultContractResolver());//#endregion ASP//创建 UnityServiceProvidervar unityServiceProvider = 新 UnityServiceProvider();统一容器容器 = unityServiceProvider.UnityContainer;//添加控制器激活器//注意!!!在构建 ServiceProvider 之前执行此操作!!!services.AddSingleton<IControllerActivator>(new UnityControllerActivator(container));//现在构建服务提供者var defaultProvider = services.BuildServiceProvider();//配置 UnityContainer//#区域统一//使用默认提供者添加Fallback扩展container.AddExtension(new UnityFallbackProviderExtension(defaultProvider));//在此处注册自定义类型container.RegisterType();container.RegisterType();container.RegisterType();//#endregion 统一返回统一服务提供者;}
由于我在过去一周了解了大部分关于 DI 的知识,所以我希望我没有破坏任何大的 Pricipal/Pattern,如果是的话,请告诉我!
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:
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
- MSDN Unity article
- Auto-Mocking Unity Container Extension
- Custom Object Factory Unity Extension
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!
这篇关于Unity 与 ASP.NET Core 和 MVC6 (Core)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!