【问题标题】:C# Different instance per "organization" / custom code for SaaSC# 每个“组织”的不同实例/SaaS 的自定义代码
【发布时间】:2021-10-05 03:15:52
【问题描述】:

我正在编写一个旨在用作 SaaS 的 (C# ASP.NET API) 程序。该程序具有标准功能,但旧的(非 SaaS)也为每个客户提供了一些自定义代码,用于连接他们的系统。导致每个客户的代码版本不同。

对此我有一个解决方案是使用松散耦合。但是我怎样才能使它在运行时调用接口的不同实例呢?最好没有 switch-case / if-tree。

对此解决方案的任何帮助都会很好,但我也想知道这是否是要走的路?

在其他编程语言中,例如javascript 你可以加载和运行代码 JIT。 另一个连接的解决方案是在需要连接到客户系统的部分使用微服务。通过这样做,我可以独立于主程序更新连接器。

【问题讨论】:

  • 在某个地方,必须有人输入if/switch,但您始终可以将其隐藏在适当的抽象后面。
  • 这对于 StackOverflow 来说可能有点过于开放了。我将研究的一般方法是在您的 DI 系统中使用工厂模式来实例化适当的实现。像这样:gavilan.blog/2020/01/20/…
  • 您能否解释一下您有哪些要求阻止在启动路径中使用单个“switch-case / if-tree”成为有效的解决方案?此外,.NET 还允许在运行时加载和 JIT 代码。有很多方法可以实现这一目标。可能最简单的方法是使用Microsoft.CodeAnalysis.Scripting.CSharpScript 类。另一种选择是在配置中添加程序集和类名并动态加载它。然后,您可以在 DI 容器中注册此类型。这称为后期绑定。最佳解决方案在很大程度上取决于您的要求。

标签: c# asp.net-core dependency-injection .net-5


【解决方案1】:

您可以连接依赖注入容器中的实现并在运行时选择实现。

假设您有一个IDiscountCalculator 接口,根据客户要求有几个不同的实现。

您将从请求中获取当前租户/客户信息并决定需要运行哪个实现,然后返回适当的实现。

以这个例子为例(我将抽象级别保持在最低水平)

services.AddScoped<IDiscountCalculator>(provider =>
{
    // find the tenant
    var user = provider.GetRequiredService<IHttpContextAccessor>().HttpContext!.User;
    var tenantId = user.FindFirstValue("tenant_id");
    var tenant = provider.GetRequiredService<AppDbContext>().Tenants.Single(t => t.Id == tenantId);
    
    // return the implementation based on tenant's preferences
    return tenant.DiscountType switch
    {
        typeof(SpecialDiscountCalculator).Name => provider.GetRequiredService<SpecialDiscountCalculator>(),
        // ... other implementations

        // default to standard discount
        _ => provider.GetRequiredService<StandardDiscountCalculator>();
    };
});

您可能会将其抽象为工厂 DiscountCalculatorFactory 以获得更好的衡量标准:

class DiscountCalculatorFactory
{
    private IPrincipalAccessor _principalAccessor;
    private AppDbContext _dbContext;

    public DiscountCalculatorFactory(AppDbContext dbContext, IPrincipalAccessor principalAccessor)
    {
        _dbContext = dbContext;
        _principalAccessor = principalAccessor;
    }

    public async Task<IDiscountCalculator> CreateAsync()
    {
        var user = _principalAccessor.Principal;
        var tenantId = user.FindFirstValue("tenant_id");
        var tenant = await _dbContext.Tenants.SingleAsync(t => t.Id == tenantId);
        
        return tenant.DiscountType switch
        {
            typeof(SpecialDiscountCalculator).FullName => provider.GetRequiredService<SpecialDiscountCalculator>(),
            // ... other implementations

            // default
            _ => provider.GetRequiredService<DefaultDiscountCalculator>();
        };
    }
}

public interface IPrincipalAccessor {
    ClaimsPrincipal? Principal { get; }
}

public class HttpPrincipalAccessor : IPrincipalAccessor
{
    private IHttpContextAccessor _httpContextAccessor;

    public HttpPrincipalAccessor(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public ClaimsPrincipal? Principal => _httpContextAccessor?.HttpContext?.User;
}

【讨论】:

    猜你喜欢
    • 2011-02-23
    • 1970-01-01
    • 2020-01-11
    • 1970-01-01
    • 1970-01-01
    • 2010-10-06
    • 1970-01-01
    • 2023-01-16
    • 1970-01-01
    相关资源
    最近更新 更多