多租户SQLMembershipProvider ASP.NET MVC

Die*_*337 4 azure sqlmembershipprovider multi-tenant asp.net-mvc-3 azure-sql-database

我正在尝试使用多租户的ASP.NET MVC应用程序迁移到Azure(包括SQL Azure).每个客户都拥有自己的数据库,这些数据库是独立的,包括所有会员资格.

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

我们可以实现单个成员资格数据库并对其进行身份验证......但我们希望在此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>
Run Code Online (Sandbox Code Playgroud)

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

    }
}
Run Code Online (Sandbox Code Playgroud)

Ste*_* G. 5

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

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

   var connectionStringField = Membership.Provider.GetType().BaseType.GetField("_sqlConnectionString", BindingFlags.Instance | BindingFlags.NonPublic);
Run Code Online (Sandbox Code Playgroud)

我不确定这是否是最合适的解决方案,但它似乎已完成工作,以便为membershipProvider启用动态connectionString而无需自行滚动.虽然觉得有点hackish.