使用带参数构造函数的Autofac Webform

时间:2015-09-07 18:33:03

标签: c# webforms inversion-of-control autofac

我们有一些遗留应用程序假设我们无法更改该SiteSettings类,因为完整的项目编码数千行会打扰。所以我们想用DI解决问题。我在此处创建了POC应用,您可以在global asax中看到有评论//HOW CAN I PASS TenantId HERE so it will be same for this complete httprequest life.

遗留代码:

public class OrderController
    {
        public static string CompleteOrder()
        {
            return SiteSettings.Instance.DefaultTimeZone();
        }
    }


    public class SiteSettings
    {
        public ITenantSettings TenantSettings { get; set; }

        private static SiteSettings _instance;

        private SiteSettings() { }

        public static SiteSettings Instance => _instance ?? (_instance = new SiteSettings());

        public string DefaultTimeZone()
        {
            return TenantSettings.DefaultTimeZone();
        }
    }

新的注射类

 public interface ITenantSettings
    {
        string DefaultTimeZone();
    }

    public class TenantSettings : ITenantSettings
    {
        private readonly int _tenantId;

        public TenantSettings(int tenantId)
        {
            _tenantId = tenantId;
        }

        public string DefaultTimeZone()
        {
            return "USA Time For Tenant ID " + _tenantId.ToString();
        }
    }

全球ASAX

public class Global : HttpApplication, IContainerProviderAccessor
    {
        // Provider that holds the application container.
        static IContainerProvider _containerProvider;

        // Instance property that will be used by Autofac HttpModules
        // to resolve and inject dependencies.
        public IContainerProvider ContainerProvider => _containerProvider;

        protected void Application_Start(object sender, EventArgs e)
        {
            // Build up your application container and register your dependencies.
            var builder = new ContainerBuilder();
            builder.RegisterType<TenantSettings>().As<ITenantSettings>().InstancePerRequest();

            _containerProvider = new ContainerProvider(builder.Build());
        }

        protected void Application_BeginRequest(object sender, EventArgs e)
        {
            int id = 0;
            int.TryParse(HttpContext.Current.Request.QueryString["id"], out id);

            var cpa = (IContainerProviderAccessor)HttpContext.Current.ApplicationInstance;
            var cp = cpa.ContainerProvider;
            cp.RequestLifetime.InjectProperties(SiteSettings.Instance);
            //HOW CAN I PASS TENANTID HERE so it will be same for this complete httprequest life.
        }
    }

默认ASPX

public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            Response.Write(OrderController.CompleteOrder());
        }
    }

错误:

None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'CoreLibrary.Tenants.TenantSettings' can be invoked with the available services and parameters:
Cannot resolve parameter 'Int32 tenantId' of constructor 'Void .ctor(Int32)'. 

1 个答案:

答案 0 :(得分:4)

您可以使用WithParameter,在这种情况下,我会建议ResolvedParameter

builder.RegisterType<TenantSettings>()
    .As<ITenantSettings>()
    .InstancePerRequest()
    .WithParameter(
        new ResolvedParameter(
            (pi, ctx) => pi.ParameterType == typeof(int) && pi.Name == "tenantId",
            (pi, ctx) => int.Parse(HttpContext.Current.Request.QueryString["id"])));

实际上你需要比int.Parse(HttpContext.Current.Request.QueryString["id"])更有弹性的东西,但这会给你一种解决方案的味道

更新

如果我们要注入依赖项,我们需要删除行_instance ?? (_instance = new SiteSettings())。在我的示例SiteSettings中,现在有一个static Initialise方法,此方法用于构造 SiteSettings.Instance的值。

目前我们只对注入ITenantSettings感兴趣,因为我们希望ITenantSettings的生命范围(每个请求)小于SiteSettings(单一)的范围,我们应该注入代表(Func<ITenantSettings>)。

public class SiteSettings 
{
    private static SiteSettings _instance;
    private Func<ITenantSettings> _tenantSettingsFactory;

    private SiteSettings(Func<ITenantSettings> tenantSettingsFactory)
    {
        _tenantSettingsFactory = tenantSettingsFactory;
    }

    public static void Initialise(Func<ITenantSettings> tenantSettingsFactory) 
    {
        _instance = new SiteSettings(tenantSettingsFactory);
    }

    public ITenantSettings TenantSettings { get { return _tenantSettingsFactory(); } }

    public static SiteSettings Instance
    {
        get {
            if (_instance == null) throw new InvalidOperationException();
            return _instance;
        }
    }

    public string DefaultTimeZone() 
    {
        return TenantSettings.DefaultTimeZone();
    }
}

这是一个测试,展示了你的要求:

[Fact]
public void Demonstrate_TenantSettingsFactory_AlwaysResolvesCurrentTenantId()
{
    int tenantId = 0;

    var builder = new ContainerBuilder();
    builder.RegisterType<TenantSettings>()
        .As<ITenantSettings>()
        .WithParameter(
            new ResolvedParameter(
                (pi, ctx) => pi.ParameterType == typeof(int) && pi.Name == "tenantId",
                (pi, ctx) => tenantId));


    var container = builder.Build();

    SiteSettings.Initialise(container.Resolve<ITenantSettings>);

    tenantId = 1;
    Assert.Equal("USA Time For Tenant ID 1", SiteSettings.Instance.DefaultTimeZone());
    tenantId = 2;
    Assert.Equal("USA Time For Tenant ID 2", SiteSettings.Instance.DefaultTimeZone());
}

注意我在使用单元测试项目时删除了InstancePerRequestHttpContext.Current