【问题标题】:How to use Dependency Injection with ASP.NET Web Forms如何在 ASP.NET Web 窗体中使用依赖注入
【发布时间】:2010-10-10 00:23:35
【问题描述】:

我正在尝试找出一种将依赖注入与 ASP.NET Web 窗体控件一起使用的方法。

我有很多控件可以直接创建存储库,并使用它们来访问和绑定数据等。

我正在寻找一种可以将存储库传递给外部控件 (IoC) 的模式,因此我的控件仍然不知道存储库是如何构建的以及它们来自何处等。

我不希望我的控件依赖于 IoC 容器,因此我只想能够使用构造函数或属性注入来构造控件。

(更复杂的是,这些控件是由 CMS 在运行时构建并放置在页面上的!)

有什么想法吗?

【问题讨论】:

    标签: asp.net dependency-injection inversion-of-control webforms


    【解决方案1】:

    2019 年更新: 随着 Web Forms 4.7.2 的引入,现在对 DI 有更好的支持。这使以下内容无效。见:Wiring up Simple Injector in WebForms in .NET 4.7.2

    您可以通过将默认的PageHandlerFactory 替换为自定义的PageHandlerFactory 来使用自动构造函数注入。这样,您可以使用重载的构造函数来加载依赖项。您的页面可能如下所示:

    public partial class HomePage : System.Web.UI.Page
    {
        private readonly IDependency dependency;
    
        public HomePage(IDependency dependency)
        {
            this.dependency = dependency;
        }
    
        // Do note this protected ctor. You need it for this to work.
        protected HomePage () { }
    }
    

    可以在 web.config 中配置自定义 PageHandlerFactory,如下所示:

    <?xml version="1.0"?>
    <configuration>
      <system.web>
        <httpHandlers>
          <add verb="*" path="*.aspx"
            type="YourApp.CustomPageHandlerFactory, YourApp"/>
        </httpHandlers>
      </system.web>
    </configuration>
    

    您的CustomPageHandlerFactory 可能如下所示:

    public class CustomPageHandlerFactory : PageHandlerFactory
    {
        private static object GetInstance(Type type)
        {
            // TODO: Get instance using your favorite DI library.
            // for instance using the Common Service Locator:
            return Microsoft.Practices.ServiceLocation
                .ServiceLocator.Current.GetInstance(type);
        }
    
        public override IHttpHandler GetHandler(HttpContext cxt, 
            string type, string vPath, string path)
        {
            var page = base.GetHandler(cxt, type, vPath, path);
    
            if (page != null)
            {
                // Magic happens here ;-)
                InjectDependencies(page);
            }
    
            return page;
        }
    
        private static void InjectDependencies(object page)
        {
            Type pageType = page.GetType().BaseType;
    
            var ctor = GetInjectableCtor(pageType);
    
            if (ctor != null)
            {
                object[] arguments = (
                    from parameter in ctor.GetParameters()
                    select GetInstance(parameter.ParameterType)
                    .ToArray();
    
                ctor.Invoke(page, arguments);
            }
        }
    
        private static ConstructorInfo GetInjectableCtor(
            Type type)
        {
            var overloadedPublicConstructors = (
                from constructor in type.GetConstructors()
                where constructor.GetParameters().Length > 0
                select constructor).ToArray();
    
            if (overloadedPublicConstructors.Length == 0)
            {
                return null;
            }
    
            if (overloadedPublicConstructors.Length == 1)
            {
                return overloadedPublicConstructors[0];
            }
    
            throw new Exception(string.Format(
                "The type {0} has multiple public " +
                "ctors and can't be initialized.", type));
        }
    }
    

    缺点是这仅在完全信任的情况下才有效。您可以阅读更多关于它的信息here。但请注意,在部分信任 seems a lost cause 的情况下开发 ASP.NET 应用程序。

    【讨论】:

    • 嗨 Steven,我在我的项目中实现了类似的功能,效果非常好。但我现在面临一个问题。此处描述为“stackoverflow.com/questions/15692499/…”。能否请您看一下并分享一些意见?
    • 温莎城堡。没关系,我通过在页面中从 Boostrapper 解析解决了它。这不是很酷,但是嘿,它正在工作并且看起来仍然不错。
    • 我发现这篇优秀的文章codemag.com/Article/1210031 (我认为链接自另一个 SO 答案,但现在我找不到哪一个)其中包含与上述解决方案相关的更多示例代码,而且有趣的是,展示了 Microsoft Managed Extensibility Framework (MEF) 如何帮助您以一种非常有用且略微不标准的方式解决此问题和类似的依赖注入问题。
    • @steven 从 .NET 4.7.2(新增功能)开始,开发人员现在可以轻松地在 WebForms 应用程序中使用依赖注入。 info.
    【解决方案2】:

    Autofac supports 在 ASP.NET WebForms 中相当隐蔽的依赖注入。我的理解是它只是使用 http 模块连接到 ASP.NET 页面生命周期并进行属性注入。唯一的问题是,对于控件,我认为这要等到 Init 事件之后才会发生。

    【讨论】:

      【解决方案3】:

      从 .NET 4.7.2 (what's new) 开始,开发人员现在可以轻松地在 WebForms 应用程序中使用依赖注入。使用 UnityAdapter,您可以通过 4 个简单的步骤将其添加到现有的 WebForms 应用程序中。 See this博客。

      【讨论】:

      • 是只支持Unity还是可以使用其他IoC容器?
      • AspNet.WebFormsDependencyInjection.Unity nuget 包目前仅支持 Unity,但以the source code 为例,您可以使用其他 IoC 容器。这是我customized it to use SimpleInjector的方式。
      【解决方案4】:

      最好的方法是为控件创建一个基类,例如:

      public class PartialView : UserControl
      {
          protected override void OnInit(System.EventArgs e)
          {
              ObjectFactory.BuildUp(this);
              base.OnInit(e);
          }
      }
      

      这将注入从该基类继承的任何控件(使用结构映射)。将其与基于属性的配置相结合,您将能够拥有如下控件:

      public partial class AdminHeader : PartialView
      {
         IMyRepository Repository{get;set;}
      }
      

      更新1:如果你不能让控件继承,也许CMS在创建控件后有一个钩子,你可以在那里调用BuildUp。此外,如果 CMS 允许您挂钩某些东西来获取实例,您可以使用基于构造函数的注入,但我更喜欢 BuildUp 在这种特定情况下,因为 asp.net 没有挂钩。

      【讨论】:

      • 感谢您的回复。我的完美主义者希望控件不依赖于 ObjectFactory 框架,即纯依赖注入。显然,这意味着一些外部的东西,即创建控件。
      • 回复:更新 1。我将在 CMS 中四处寻找,看看是否能找到任何东西。我猜想 ASP.NET 中基于构造函数的注入的一个问题是控件在那时变得“不可设计”。除非设计师知道如何构建它们。
      【解决方案5】:

      您还可以在 Application_Start global.asax 事件中创建一些单例实例,并将它们用作公共静态只读属性。

      【讨论】:

        【解决方案6】:

        这是我最近用来避免与管道挂钩的解决方案(我发现这会让以后查看我的代码的每个人都感到困惑,但是是的,我也看到了它的好处):

        public static class TemplateControlExtensions
        {
            static readonly PerRequestObjectManager perRequestObjectManager = new PerRequestObjectManager();
        
            private static WIIIPDataContext GetDataContext(this TemplateControl templateControl)
            {
                var dataContext = (WIIIPDataContext) perRequestObjectManager.GetValue("DataContext");
        
                if (dataContext == null) 
                {
                   dataContext = new WIIIPDataContext();
                   perRequestObjectManager.SetValue("DataContext", dataContext);   
                }
        
                return dataContext;
            }
        
            public static IMailer GetMailer(this TemplateControl templateControl)
            {
                return (IMailer)IoC.Container.Resolve(typeof(IMailer));
            }
        
            public static T Query<T>(this TemplateControl templateControl, Query<T> query)
            {
                query.DataContext = GetDataContext(templateControl);
                return query.GetQuery();
            }
        
            public static void ExecuteCommand(this TemplateControl templateControl, Command command)
            {
                command.DataContext = GetDataContext(templateControl);
                command.Execute();
            }
        
            private class PerRequestObjectManager
            {
                public object GetValue(string key)
                {
                    if (HttpContext.Current != null && HttpContext.Current.Items.Contains(key))
                        return HttpContext.Current.Items[key];
                    else
                        return null;
                }
        
                public void SetValue(string key, object newValue)
                {
                    if (HttpContext.Current != null)
                        HttpContext.Current.Items[key] = newValue;
                }
            }
        }
        

        这展示了如何轻松创建自己的生命周期管理器,并根据需要连接到 IoC 容器。哦,我还使用了一种不相关的查询/命令结构,但更多关于其背后的推理可以在这里找到:

        Limit your abstractions: Refactoring toward reduced abstractions

        【讨论】:

          猜你喜欢
          • 2017-10-22
          • 1970-01-01
          • 2018-12-16
          • 2018-03-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多