在多租户环境中解决正确接口实现的策略

时间:2014-08-11 22:56:20

标签: c# servicestack funq

鉴于此界面:

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

和它的两个实现:

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

1 个答案:

答案 0 :(得分:3)

我不认为有一个简单或特别优雅的解决方案,因为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<T>,我们在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<T>,该方法负责根据客户名称T解析正确的实例,在这种情况下是ILoanCalculator

该方法适用于:

  1. Request.GetItem("customer")确定客户名称。您当前的方法需要在识别客户时使用Request方法在Items Request.SetItem个集合上设置客户标识符。 或者可能将识别机制移入此方法。

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

  3. 然后将该类型的实例创建为T,即界面ILoanCalculator

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

  5. 然后填充DefaultCalculatorILoanCalculator类型的请求中的值。

  6. 返回实例。

  7. 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方法执行此操作。< / p>

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

    显然,此方法依赖于具有特定格式的完整类型名称,以使其与客户匹配。还有其他方法,但我相信这很简单。

    我希望有所帮助。