在MVC4 + Unity中重新加载应用程序的分辨率失败

时间:2013-11-29 19:33:38

标签: asp.net-mvc-4 unity-container

我正在使用Unity 3作为DI框架的ASP.NET MVC4应用程序。 我以模块化方式设计了该体系结构,创建了几个卫星库项目(外观,管理器,数据库适配器等),所有这些项目都静态链接到应用程序(这意味着添加为对Web项目的引用)。 这种架构背后的原因是允许在其他环境中重用(例如,单独的基于REST的后端服务,作为独立的Web项目实现)。

我正在使用依赖注入,它发生在Web项目级别,但也发生在附属DLL中。为了集中DI分辨率,我使用了一个简单的界面

public interface IIocInstaller
{
    void Setup(IUnityContainer container);
}

每个库项目都有一个实现该接口的类。使用反射,Web应用程序扫描所有已加载的程序集以查找实现该接口的类,并调用Setup方法。

启动应用时,一切正常。但是,当Web服务器由于不活动而卸载时,下次请求时会抛出以下异常

[InvalidOperationException: The type ICustomerFacade does not have an accessible constructor.]
   Microsoft.Practices.ObjectBuilder2.DynamicMethodConstructorStrategy.ThrowForNullExistingObject(IBuilderContext context) +178
   lambda_method(Closure , IBuilderContext ) +25
   Microsoft.Practices.ObjectBuilder2.<>c__DisplayClass1.<GetBuildMethod>b__0(IBuilderContext context) +35
   Microsoft.Practices.ObjectBuilder2.DynamicMethodBuildPlan.BuildUp(IBuilderContext context) +10
   Microsoft.Practices.ObjectBuilder2.BuildPlanStrategy.PreBuildUp(IBuilderContext context) +196
   Microsoft.Practices.ObjectBuilder2.StrategyChain.ExecuteBuildUp(IBuilderContext context) +193
   Microsoft.Practices.ObjectBuilder2.BuilderContext.NewBuildUp(NamedTypeBuildKey newBuildKey) +113
   Microsoft.Practices.Unity.ObjectBuilder.NamedTypeDependencyResolverPolicy.Resolve(IBuilderContext context) +48
   lambda_method(Closure , IBuilderContext ) +111
   Microsoft.Practices.ObjectBuilder2.<>c__DisplayClass1.<GetBuildMethod>b__0(IBuilderContext context) +35
   Microsoft.Practices.ObjectBuilder2.DynamicMethodBuildPlan.BuildUp(IBuilderContext context) +10
   Microsoft.Practices.ObjectBuilder2.BuildPlanStrategy.PreBuildUp(IBuilderContext context) +196
   Microsoft.Practices.ObjectBuilder2.StrategyChain.ExecuteBuildUp(IBuilderContext context) +193
   Microsoft.Practices.Unity.UnityContainer.DoBuildUp(Type t, Object existing, String name, IEnumerable`1 resolverOverrides) +165

[ResolutionFailedException: Resolution of the dependency failed, type = "Eden.SMS.UI.Web.Controllers.CustomersController", name = "(none)".
Exception occurred while: while resolving.
Exception is: InvalidOperationException - The type ICustomerFacade does not have an accessible constructor.
-----------------------------------------------
At the time of the exception, the container was:

  Resolving Eden.SMS.UI.Web.Controllers.CustomersController,(none)
  Resolving parameter "customerFacade" of constructor Eden.SMS.UI.Web.Controllers.CustomersController(Eden.SMS.Service.Facades.Interface.ICustomerFacade customerFacade)
    Resolving Eden.SMS.Service.Facades.Interface.ICustomerFacade,(none)
]
   Microsoft.Practices.Unity.UnityContainer.DoBuildUp(Type t, Object existing, String name, IEnumerable`1 resolverOverrides) +329
   Microsoft.Practices.Unity.UnityContainer.Resolve(Type t, String name, ResolverOverride[] resolverOverrides) +15
   Microsoft.Practices.Unity.UnityContainerExtensions.Resolve(IUnityContainer container, Type t, ResolverOverride[] overrides) +18
   Unity.Mvc4.UnityDependencyResolver.GetService(Type serviceType) +67
   System.Web.Mvc.DefaultControllerActivator.Create(RequestContext requestContext, Type controllerType) +41

[InvalidOperationException: An error occurred when trying to create a controller of type 'Eden.SMS.UI.Web.Controllers.CustomersController'. Make sure that the controller has a parameterless public constructor.]
   System.Web.Mvc.DefaultControllerActivator.Create(RequestContext requestContext, Type controllerType) +178
   System.Web.Mvc.DefaultControllerFactory.GetControllerInstance(RequestContext requestContext, Type controllerType) +77
   System.Web.Mvc.DefaultControllerFactory.CreateController(RequestContext requestContext, String controllerName) +66
   System.Web.Mvc.MvcHandler.ProcessRequestInit(HttpContextBase httpContext, IController& controller, IControllerFactory& factory) +191
   System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) +50
   System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, Object state) +48
   System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData) +16
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +301
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155

所有与统一相关的代码都在Application_Start中执行。 ICustomerFacade接口注入一个控制器,它本身也可以注入其他实例,当然还是使用DI。 错误消息警告控制器有一个无参数构造函数,当然没有实现,因为我需要一个带注入参数的 - 我也试图指定要使用的构造函数(通过使用InjectionConstructor),但没有运气。

知道抛出异常的原因吗?


更新

看起来问题是由于在卫星项目中调用初始化程序失败。

这是我用来扫描查找实现IIocInstaller接口的实例的所有程序集的代码

public class AssemblyInjector
{
    /// <summary>
    /// Initialize IoC in all assemblies implementing the IocExportAssembly attribute
    /// </summary>
    /// <param name="container"></param>
    /// <param name="assemblyPrefix">The prefix of the assembly names to load</param>
    public static void RegisterAssemblyTypes(IUnityContainer container, string assemblyPrefix)
    {
        var bootstrapers = EnumerateIocBootstraperTypes(assemblyPrefix);

        foreach ( Type type in bootstrapers )
        {
            var instance = (IIocInstaller) Activator.CreateInstance(type);
            instance.Setup(container);
        }
    }

    /// <summary>
    /// Given a list of assemblies, find all types exposing the 
    /// <see cref="IocExportAssemblyAttribute"/> attribute
    /// </summary>
    /// <param name="assemblyPrefix">The prefix of the assembly names to load</param>
    /// <returns>list of types exposing the <see cref="IocExportAssemblyAttribute"/> attribute</returns>
    private static IEnumerable<Type> EnumerateIocBootstraperTypes(string assemblyPrefix)
    {
        var assemblies = EnumerateIocAssemblies(assemblyPrefix);
        var iocInterface = typeof(IIocInstaller);

        var bootstrapers = assemblies.SelectMany(s => s.GetTypes())
            .Where(iocInterface.IsAssignableFrom);

        return bootstrapers;
    }

    /// <summary>
    /// Enumerate and return all assemblies whose name starts by "Eden.SMS"
    /// </summary>
    /// <returns><see cref="IEnumerable{T}"/>list of assemblies</returns>
    /// <param name="assemblyPrefix">The prefix of the assembly names to load</param>
    private static IEnumerable<Assembly> EnumerateIocAssemblies(string assemblyPrefix)
    {
        return from assembly in AppDomain.CurrentDomain.GetAssemblies()
                     where assembly.GetName().Name.StartsWith(assemblyPrefix)
                     select assembly;
    }
}

,这在Unity引导程序中使用

/// <summary>
/// Dependency injection bootstrapper
/// </summary>
public static class Bootstrapper
{
    private const string ASSEMBLY_PREFIX = "Eden.SMS";

    public static IUnityContainer Initialise()
    {
        var container = BuildUnityContainer();

        DependencyResolver.SetResolver(new UnityDependencyResolver(container));

        return container;
    }

    private static IUnityContainer BuildUnityContainer()
    {
        var container = new UnityContainer();

        AssemblyInjector.RegisterAssemblyTypes(container, ASSEMBLY_PREFIX);
        //RegisterSatelliteTypes(container);

        // Register local types
        RegisterTypes(container);

        foreach ( var registration in container.Registrations )
        {
            Debug.WriteLine(string.Format("Registered {0} as {1}", registration.RegisteredType, registration.MappedToType));
        }

        return container;
    }

    private static void RegisterSatelliteTypes(UnityContainer container)
    {
        new Eden.SMS.Data.Adapters.Mssql.Bootstrapper().Setup(container);
        new Eden.SMS.Data.Managers.Bootstrapper().Setup(container);
        new Eden.SMS.Service.Bootstrapper().Setup(container);
    }

    private static void RegisterTypes(IUnityContainer container)
    {
        container.RegisterType<IController, CustomersController>(new InjectionConstructor(typeof(ICustomerFacade)));
        container.RegisterType<IController, AuthenticationController>(new InjectionConstructor(typeof(IAuthenticationFacade)));
    }
}

反过来,引导程序从Global.asax中的Application_Start()调用。

如果在引导程序中我评论此行

AssemblyInjector.RegisterAssemblyTypes(container, ASSEMBLY_PREFIX);

并取消注释

RegisterSatelliteTypes(container);

它按预期工作(在Web服务器卸载应用程序后没有抛出异常)。 该更改禁用动态查找实现IIocInstaller的类,通过直接调用每个附属程序集来替换它。

此解决方法修复了该错误,但我希望坚持原始的“动态”计划。所以我想弄清楚为什么会发生这种情况以及如何解决它。有什么建议吗?

1 个答案:

答案 0 :(得分:3)

我相信你的问题是this other post describes关于.Net Framework将DLL加载到AppDomain的方式:

  

.NET Framework将加载程序集推迟到当前   AppDomain直到他们需要。例如,如果你打电话给   第三方库只来自SomeMethod(),第三方DLL   通常在第一次运行SomeMethod()之前不会加载。

     

AppDomain.GetAssemblies()为您提供已经存在的所有程序集   已加载到当前的AppDomain中。

好消息是有方法BuildManager.GetReferencedAssemblies()返回从Web.config和其他地方引用的所有程序集的列表,并将这些程序集加载到当前的AppDomain中。

这是this issue与您相似的原因,使用BuildManager.GetReferencedAssemblies()代替AppDomain.CurrentDomain.GetAssemblies()解决了这个问题。

希望它有所帮助!