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

SOF*_*ser 0 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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

新的注射类

 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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

全球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.
        }
    }
Run Code Online (Sandbox Code Playgroud)

默认 ASPX

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

错误 :

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)'. 
Run Code Online (Sandbox Code Playgroud)

quj*_*jck 5

您可以使用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"])));
Run Code Online (Sandbox Code Playgroud)

实际上,您将需要一些比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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个测试,演示了您的要求:

[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());
}
Run Code Online (Sandbox Code Playgroud)

注意我删除了InstancePerRequestHttpContext.Current因为我正在使用单元测试项目。