asp.net-mvc-3 - Multi-Tenancy SQLMembershipProvider ASP.NET MVC

标签 asp.net-mvc-3 azure azure-sql-database multi-tenant sqlmembershipprovider

我正在尝试使用 Multi-Tenancy ASP.NET MVC 应用程序迁移到 Azure(包括 SQL Azure)。每个客户都有自己的独立数据库,包括他们所有的成员(member)凭证。

我们能够在初始化 SqlMembershipProvider 对象时将连接字符串设置为 SqlMembershipProvider。但是,对不同子域(在同一 session 中)的后续请求不会更改连接字符串。我发现了一个示例,其中实现覆盖了 SqlMembershipProviders ConnectionString,但这在 System.Web dll 版本 4.0 中是不可能的。

我们可以实现一个成员(member)数据库并对其进行身份验证......但我们希望在此 SAAS 模型中保持客户凭据的隔离。

所以问题是如何为每个请求动态更改 SQLMembershipProviders 连接字符串?

Web.config

<membership defaultProvider="TenantMembershipProvider">
        <providers>
            <clear/>        
    <add name="TenantMembershipProvider" type="ABC.Infrastructure.MultiTenancy.TenantMembershipProvider, ABC"
         connectionStringName="ApplicationServices" enablePasswordRetrieval="true" enablePasswordReset="true" requiresQuestionAndAnswer="false"
         requiresUniqueEmail="false" passwordFormat="Clear" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6"
         minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" passwordStrengthRegularExpression="" applicationName="/"/>            

        </providers>
    </membership>

处理初始化的TenantMembershipProvider.cs

public class TenantMembershipProvider : SqlMembershipProvider
{

    private SiteLinqSession _session;
    private MasterSession _masterSession;
    private static readonly Dictionary<string, Customer> _customers = new Dictionary<string, Customer>();
    private static string _host;


    public override void Initialize(string name, NameValueCollection config)
    {

        base.Initialize(name, config);

        string connectionString = GetConnectionString();
        FieldInfo connectionStringField = GetType().BaseType.GetField("_sqlConnectionString", BindingFlags.Instance | BindingFlags.NonPublic);
                    connectionStringField.SetValue(this, connectionString);

    }

    private string GetConnectionString()
    {
        var headers = HttpContext.Current.Request.Headers["Host"];
        string[] host = headers.Split('.');

        _host = host[0];

        if (_host == "127") _host = "demo";

        var customer = GetSite(_host);

        return BuildTenantConnectionString(customer.ConnectionSetting);

    }


    private Customer GetSite(string host)
    {
        Customer customer;

        //check dictionary if customer exists for the subdomain           
        _customers.TryGetValue(host, out customer);

        if (customer != null)
            return customer;

        //if not get the customer record and add it to the dictionary
        _masterSession = new MasterSession();
        var customers = _masterSession.All<Customer>();
        customer = customers.SingleOrDefault(x => x.SubDomain == _host);

        if (customer != null)
            _customers.Add(host, customer);

        return customer;
    }

    private string BuildTenantConnectionString(ConnectionSetting setting)
    {

        return string.Format("Data Source={0};Initial Catalog={1};User Id={2};Password={3};", setting.DataSource, setting.Catalog, setting.Username, setting.Password);

    }
}

最佳答案

通过 Adam 发布的链接节省人们一些时间。

在 Application_PreRequestHandlerExecute 事件的 Global.asax 文件中

    protected void Application_PreRequestHandlerExecute()
    {
        SetProviderConnectionString(GetConnectionString());
    }

    private void SetProviderConnectionString(string connectionString)
    {
        // Set private property of Membership, Role and Profile providers. Do not try this at home!!
        var connectionStringField = Membership.Provider.GetType().GetField("_sqlConnectionString", BindingFlags.Instance | BindingFlags.NonPublic);
        if (connectionStringField != null)
            connectionStringField.SetValue(Membership.Provider, connectionString);

        var roleField = Roles.Provider.GetType().GetField("_sqlConnectionString", BindingFlags.Instance | BindingFlags.NonPublic);
        if (roleField != null)
            roleField.SetValue(Roles.Provider, connectionString);

        var profileField = ProfileManager.Provider.GetType().GetField("_sqlConnectionString", BindingFlags.Instance | BindingFlags.NonPublic);
        if (profileField != null)
            profileField.SetValue(ProfileManager.Provider, connectionString);
    }

    private string GetConnectionString()
    {
        return string.Format("Data Source={0};", @".\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|demo.mdf;User Instance=true");
    }

如果您创建了自定义membershipProvider,那么您将获得BaseType

   var connectionStringField = Membership.Provider.GetType().BaseType.GetField("_sqlConnectionString", BindingFlags.Instance | BindingFlags.NonPublic);

我不确定这是否是最合适的解决方案,但它似乎完成了为membershipProvider启用动态connectionString的工作,而无需滚动您自己的解决方案。不过感觉有点老套。

关于asp.net-mvc-3 - Multi-Tenancy SQLMembershipProvider ASP.NET MVC,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8525297/

相关文章:

powershell - 在现有存储帐户中创建虚拟机

asp.net-mvc-3 - MVC3 中的富文本区域

c# - 在 .cshtml 页面中发送和接收数据

asp.net-mvc-3 - ASP.NET MVC3 中的 HtmlHelper 使用 Action<T> 作为模板 : Razor syntax?

Azure 应用服务设置 CPU 和 RAM 配额

azure - 从 Azure Blob 存储中的任何身份验证检查中排除 sig querystring 参数

visual-studio - 是否可以通过 Visual Studio 使用 Azure AD 和 MFA 进行连接以进行 Entity Framework 调用?

asp.net-web-api - EF 核心 : An exception occurred in the database while iterating the results of a query

azure - 将数据从 SQL Azure 迁移到 Azure Table 的最佳设计解决方案

asp.net-mvc-3 - ASP.NET MVC 3 Ajax.ActionLink