【问题标题】:Strategy for resolving correct interface implementation in multi-tenant environment解决多租户环境中正确接口实现的策略
【发布时间】:2014-08-11 22:56:20
【问题描述】:

给定这个界面:

public interface ILoanCalculator
{
    decimal Amount { get; set; }
    decimal TermYears { get; set; }
    int TermMonths { get; set; }
    decimal IntrestRatePerYear { get; set; }
    DateTime StartDate { get; set; }
    decimal MonthlyPayments { get; set; }
    void Calculate();
}

以及它的 2 个实现:

namespace MyCompany.Services.Business.Foo
{
    public interface ILoanCalculator : Common.ILoanCalculator
    {

    }

    public class LoanCalculator : ILoanCalculator
    {
        public decimal Amount { get; set; }
        public decimal TermYears { get; set; }
        public int TermMonths { get; set; }
        public decimal IntrestRatePerYear { get; set; }
        public DateTime StartDate { get; set; }
        public decimal MonthlyPayments { get; set; }
        public void Calculate()
        {
            throw new NotImplementedException();
        }
    }
}

namespace MyCompany.Services.Business.Bar
{
    public interface ILoanCalculator : Common.ILoanCalculator
    {

    }

    public class LoanCalculator : ILoanCalculator
    {
        public decimal Amount { get; set; }
        public decimal TermYears { get; set; }
        public int TermMonths { get; set; }
        public decimal IntrestRatePerYear { get; set; }
        public DateTime StartDate { get; set; }
        public decimal MonthlyPayments { get; set; }
        public void Calculate()
        {
            throw new NotImplementedException();
        }
    }
}

鉴于上面的简单代码,假设每个公司的 Calculate 方法的实现会有所不同。在初始化期间加载程序集并调用正确程序集的正确方法的正确方法是什么?我已经弄清楚了最简单的部分是确定请求是针对哪个公司的,现在我只需要调用与当前业务相对应的正确方法即可。

谢谢你, 斯蒂芬

更新的示例代码

向@Scott 大声喊叫,这是我必须做出的更改,才能使接受的答案正常工作。

在这种情况下,我必须使用 Assembly Resolver 来查找我的类型。请注意,我使用属性来标记我的程序集,以便基于它的过滤更简单且不易出错。

public T GetInstance<T>(string typeName, object value) where T : class
{
    // Get the customer name from the request items
    var customer = Request.GetItem("customer") as string;
    if (customer == null) throw new Exception("Customer has not been set");

    // Create the typeof the object from the customer name and the type format
    var assemblyQualifiedName = string.Format(typeName, customer);
    var type = Type.GetType(
        assemblyQualifiedName,
        (name) =>
        {
            return AppDomain.CurrentDomain.GetAssemblies()
                .Where(a => a.GetCustomAttributes(typeof(TypeMarkerAttribute), false).Any()).FirstOrDefault();
        },
        null,
        true);

    if (type == null) throw new Exception("Customer type not loaded");

    // Create an instance of the type
    var instance = Activator.CreateInstance(type) as T;

    // Check the instance is valid
    if (instance == default(T)) throw new Exception("Unable to create instance");

    // Populate it with the values from the request
    instance.PopulateWith(value);

    // Return the instance
    return instance;
}

标记属性

[AttributeUsage(AttributeTargets.Assembly)]
public class TypeMarkerAttribute : Attribute { }

在插件组装中的使用

[assembly: TypeMarker]

最后,对静态 MyTypes 稍作更改以支持限定名称

public static class MyTypes
{
    // assemblyQualifiedName
    public static string LoanCalculator = "SomeName.BusinessLogic.{0}.LoanCalculator, SomeName.BusinessLogic.{0}, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
}

【问题讨论】:

    标签: c# servicestack funq


    【解决方案1】:

    我不认为有一个简单或特别优雅的解决方案,因为 ServiceStack 基于具体的类而不是接口来解析它的服务,这超出了 Funq 的能力。不过也不是没有可能。

    您需要为每个要用作 DTO 的接口提供一个默认实现,因为 ServiceStack 使用具体类进行解析。

    所以基本上这里我们有一个DefaultCalculator,它将为我们提供进入我们的操作方法的路线。

    [Route("/Calculate","GET")]
    public class DefaultCalculator : ILoanCalculator
    {
        public decimal Amount { get; set; }
        public decimal TermYears { get; set; }
        public int TermMonths { get; set; }
        public decimal IntrestRatePerYear { get; set; }
        public DateTime StartDate { get; set; }
        public decimal MonthlyPayments { get; set; }
        public void Calculate()
        {
            throw new NotImplementedException();
        }
    }
    

    然后我们的操作方法几乎照常使用,除了我们调用一个方法GetInstance&lt;T&gt;,我们在此服务扩展的MyServiceBase 中实现该方法,而不是Service,因为它更容易共享此方法跨服务。

    public class TestService : MyServiceBase
    {
        public decimal Get(DefaultCalculator request)
        {
            // Get the instance of the calculator for the current customer
            var calculator = GetInstance<ILoanCalculator>(MyTypes.LoanCalculator, request);
    
            // Perform the action
            calculator.Calculate();
    
            // Return the result
            return calculator.MonthlyPayments;
        }
    }
    

    MyServiceBase 中,我们实现了GetInstance&lt;T&gt; 方法,该方法负责根据客户名称解析T 的正确实例,在本例中为ILoanCalculator

    该方法的工作原理:

    1. 根据Request.GetItem("customer") 确定客户名称。您当前的方法需要在您识别客户的位置使用Request.SetItem 方法在RequestItems 集合上设置客户标识符。 或者也许将识别机制移到这个方法中。

    2. 在已知客户名称的情况下,可以根据传入的类型名称模板构建完整的类型名称。即MyCompany.Services.Business.Foo.LoanCalculator,其中Foo 是客户。如果包含程序集已在启动时加载,这应该解析类型。

    3. 然后该类型的实例被创建为T,即接口ILoanCalculator

    4. 然后进行安全检查以确保一切正常。

    5. 然后从请求中填充 DefaultCalculator 中的值,该类型也是 ILoanCalculator

    6. 返回实例。

    public class MyServiceBase : Service
    {
        public T GetInstance<T>(string typeName, object value)
        {
            // Get the customer name from the request items
            var customer = Request.GetItem("customer") as string;
            if(customer == null) throw new Exception("Customer has not been set");
    
            // Create the typeof the object from the customer name and the type format
            var type = Type.GetType(string.Format(typeName, customer));
    
            // Create an instance of the type
            var instance = Activator.CreateInstance(type) as T;
    
            // Check the instance is valid
            if(instance == default(T)) throw new Exception("Unable to create instance");
    
            // Populate it with the values from the request
            instance.PopulateWith(value);
    
            // Return the instance
            return instance;
        }
    }
    

    您可以选择添加实例缓存,以防止必须为每个请求使用Activator.CreateInstance

    如果您要动态创建许多不同的类型,那么您可能希望将它们的类型字符串组织到一个静态类中:

    public static class MyTypes
    {
        public static string LoanCalculator = "MyCompany.Services.Business.{0}.LoanCalculator";
        public static string AnotherType = "MyCompany.Services.Business.{0}.AnotherType";
        //...//
    }
    

    那么剩下要做的就是确保将具有不同客户实现的程序集加载到应用程序中,这可以通过 AppHost Configure 方法完成。

    foreach(var pluginFileName in Directory.GetFiles("Plugins", "*.dll"))
        Assembly.Load(File.ReadAllBytes(pluginFileName));
    

    显然,这种方法依赖于特定格式的完整类型名称来匹配客户。还有其他方法,但我认为这很简单。

    希望对你有帮助。

    【讨论】:

    • 完成您非常详细的回答,谢谢。
    • @StephenPatten 真棒。别客气。如果您有任何问题,请告诉我。
    • 我能说的唯一“问题”是 Fusion 在 bin 文件夹中查找,而不是在内存中查找类型。
    • @StephenPatten 如果您决定将其与主程序集一起构建,则不必在 bin 文件夹中查找该类型。这完全是可选的。您只需确保该类型存在。因此,如果您想将它与您的应用程序一起包含,那可以忽略插件部分。
    • 我无法从插件文件夹中加载和使用类型。只有当所有东西都位于 bin 文件夹中时,我才能让该类型工作
    猜你喜欢
    • 2018-08-16
    • 2016-10-30
    • 2014-06-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-15
    • 2019-04-16
    • 1970-01-01
    相关资源
    最近更新 更多