如何在ASP.NET Web窗体中使用依赖注入

时间:2021-08-24 15:16:09

I am trying to work out a way to use dependency injection with ASP.NET Web Forms controls.

我试图找到一种方法来使用ASP.NET Web窗体控件的依赖注入。

I have got lots of controls that create repositories directly, and use those to access and bind to data etc.

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

I am looking for a pattern where I can pass repositories to the controls externally (IoC), so my controls remain unaware of how repositories are constructed and where they come from etc.

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

I would prefer not to have a dependency on the IoC container from my controls, therefore I just want to be able to construct the controls with constructor or property injection.

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

(And just to complicate things, these controls are being constructed and placed on the page by a CMS at runtime!)

(只是为了使事情复杂化,这些控件正在构建并在运行时由CMS放置在页面上!)

Any thoughts?

有什么想法吗?

5 个解决方案

#1


30  

You can use automatic constructor injection by replacing the default PageHandlerFactory with a custom one. This way you can use an overloaded constructor to load the dependencies. Your page might look like this:

您可以使用自定义构造函数注入替换默认的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 () { }
}

Configuring that custom PageHandlerFactory can be done in the web.config as follows:

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

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

Your CustomPageHandlerFactory can look like this:

您的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));
    }
}

Downside is that this only works when running your side in Full Trust. You can read more about it here. But do note that developing ASP.NET applications in partial trust seems a lost cause.

缺点是这只适用于在Full Trust中运行您的一方。你可以在这里读更多关于它的内容。但请注意,部分信任开发ASP.NET应用程序似乎是一个失败的原因。

#2


5  

Autofac supports fairly unobtrusive dependency injection in ASP.NET WebForms. My understanding is it just hooks into the ASP.NET page lifecycle using an http module and does property injection. The only catch is that for controls I don't think this happens until after the Init event.

Autofac支持ASP.NET WebForms中相当不引人注意的依赖注入。我的理解是它只是使用http模块挂钩到ASP.NET页面生命周期并进行属性注入。唯一的问题是,对于控件,我认为直到Init事件之后才会发生这种情况。

#3


3  

The best way is to have a base class for the controls like:

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

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

That will inject any control that inherits from that base class (uses structuremap). Combining that with a property based config, you will be able to have controls like:

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

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

Update 1: If you can't have the controls inherit, perhaps the CMS has a hook right after creating the controls, in there you can call the BuildUp. Also if the CMS allows you to hook something to fetch the instance you could use constructor based injection, but I prefer BuildUp on this specific scenario as asp.net doesn't have a hook for this.

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

#4


1  

You could also create some singleton instances in the Application_Start global.asax event and have them available as public static readonly properties.

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

#5


0  

This is a solution I recently used to avoid hooking into the pipeline (I find that confuses everyone that looks at my code in the future, but yes, I see its benefits as well):

这是我最近用来避免陷入管道的解决方案(我发现将来会混淆每个查看我的代码的人,但是,我也看到它的好处):

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;
        }
    }
}

This shows how you can create your own life time manager pretty easily as well as hook into an IoC container if you so desire. Oh, and I am also using a query/command structure which is sort of unrelated, but more on the reasoning behind that can be found here:

这显示了如何创建自己的终身经理以及如果您愿意,可以挂钩到IoC容器中。哦,我也在使用一种不相关的查询/命令结构,但更多关于背后的推理可以在这里找到:

Limit your abstractions: Refactoring toward reduced abstractions

限制你的抽象:重构减少抽象

#1


30  

You can use automatic constructor injection by replacing the default PageHandlerFactory with a custom one. This way you can use an overloaded constructor to load the dependencies. Your page might look like this:

您可以使用自定义构造函数注入替换默认的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 () { }
}

Configuring that custom PageHandlerFactory can be done in the web.config as follows:

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

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

Your CustomPageHandlerFactory can look like this:

您的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));
    }
}

Downside is that this only works when running your side in Full Trust. You can read more about it here. But do note that developing ASP.NET applications in partial trust seems a lost cause.

缺点是这只适用于在Full Trust中运行您的一方。你可以在这里读更多关于它的内容。但请注意,部分信任开发ASP.NET应用程序似乎是一个失败的原因。

#2


5  

Autofac supports fairly unobtrusive dependency injection in ASP.NET WebForms. My understanding is it just hooks into the ASP.NET page lifecycle using an http module and does property injection. The only catch is that for controls I don't think this happens until after the Init event.

Autofac支持ASP.NET WebForms中相当不引人注意的依赖注入。我的理解是它只是使用http模块挂钩到ASP.NET页面生命周期并进行属性注入。唯一的问题是,对于控件,我认为直到Init事件之后才会发生这种情况。

#3


3  

The best way is to have a base class for the controls like:

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

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

That will inject any control that inherits from that base class (uses structuremap). Combining that with a property based config, you will be able to have controls like:

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

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

Update 1: If you can't have the controls inherit, perhaps the CMS has a hook right after creating the controls, in there you can call the BuildUp. Also if the CMS allows you to hook something to fetch the instance you could use constructor based injection, but I prefer BuildUp on this specific scenario as asp.net doesn't have a hook for this.

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

#4


1  

You could also create some singleton instances in the Application_Start global.asax event and have them available as public static readonly properties.

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

#5


0  

This is a solution I recently used to avoid hooking into the pipeline (I find that confuses everyone that looks at my code in the future, but yes, I see its benefits as well):

这是我最近用来避免陷入管道的解决方案(我发现将来会混淆每个查看我的代码的人,但是,我也看到它的好处):

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;
        }
    }
}

This shows how you can create your own life time manager pretty easily as well as hook into an IoC container if you so desire. Oh, and I am also using a query/command structure which is sort of unrelated, but more on the reasoning behind that can be found here:

这显示了如何创建自己的终身经理以及如果您愿意,可以挂钩到IoC容器中。哦,我也在使用一种不相关的查询/命令结构,但更多关于背后的推理可以在这里找到:

Limit your abstractions: Refactoring toward reduced abstractions

限制你的抽象:重构减少抽象