使用EF模型优先与SimpleMembership

24

能否在使用EF模型优先的情况下使用SimpleMembership?当我尝试时,调用WebSecurity.InitializeDatabaseConnection时会出现"无法找到所请求的.NET Framework数据提供程序"的错误。

换句话说,当连接字符串使用System.Data.EntityClient提供程序(例如在使用模型优先方法时)时,我无法使对WebSecurity.InitializeDatabaseConnection的调用起作用。

为了重现这个问题,请创建一个MVC 4应用程序,并用您在实体设计器中创建的模型优先User类替换您使用MVC 4模板免费获得的代码优先 UserProfile实体类:

  1. 在VS 2012中创建一个MVC 4应用程序并添加一个新的空白实体数据模型。
  2. 向模型添加一个名为User的实体,包含Id、UserName和FullName字段。此时,User数据实体映射到一个Users表,并通过使用System.Data.EntityClient提供程序的奇怪连接字符串进行访问。
  3. 验证EF是否可以访问User实体。一种简单的方法是基于User表和其相关的DbContext生成一个Users控制器。
  4. 编辑AccountModels.cs文件,删除UserProfile类及其关联的UsersContext类。将对(现已缺失的)UserProfileUsersContext类的引用替换为对您的新User类及其相关的DbContext类的引用。
  5. 将对InitializeDatabaseConnection的调用从InitializeSimpleMembershipAttribute过滤器类移动到Global.asax.cs中的Application_Start方法。在此期间,修改参数以使用您的新用户实体的连接。
字符串、表名和用户ID列名。
删除不再使用的InitializeSimpleMembershipAttribute类及其引用。
当你运行这个复现程序时,在调用InitializeDatabaseConnection时会出现异常
Bob

1
你的意思是用 (public DbSet<UserFromEntityFirst> userProfiles{get;set;}) 替换 public DbSet<UserProfile> UserProfiles { get; set; } 吗?我有点为难。 - Paul Plato
1
@PeterEdike 是的,那就是我尝试去做的事情。我试图用我的模型优先方法替换掉所有的代码优先部分,但是我发现SimpleMembership无法与连接字符串中的模型优先魔法配合使用。正如Mario Zderic所说,解决方案是使用类似于代码优先连接字符串的东西。 - Bob.at.Indigo.Health
5个回答

24

SimpleMembership可以与模型先存在一起。以下是解决方法。

1. 从MVC 4 Internet应用程序模板中的InitializeSimpleMembershipAttribute.cs应该像这样:

namespace WebAndAPILayer.Filters
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
    {
        private static SimpleMembershipInitializer _initializer;
        private static object _initializerLock = new object();
        private static bool _isInitialized;

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            // Ensure ASP.NET Simple Membership is initialized only once per app start
            LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
        }

        private class SimpleMembershipInitializer
        {
            public SimpleMembershipInitializer()
            {
                try
                {
                    WebSecurity.InitializeDatabaseConnection("ConnStringForWebSecurity", "UserProfile", "Id", "UserName", autoCreateTables: true);
                }
                catch (Exception ex)
                {
                    throw new InvalidOperationException("Something is wrong", ex);
                }
            }
        }
    }
}

2.从 AcountModel.cs 中删除 CodeFirst 类。

3.修复 AccountCotroler.cs 以与你的 Model-first DbContext(ExternalLoginConfirmation(RegisterExternalLoginModel model, string returnUrl) 方法)一起使用。

4.定义你的 "ConnStringForWebSecurity" 连接字符串,它不同于那个用于模型优先数据库访问的奇怪连接字符串,注意我们使用提供程序 System.Data.SqlClient 而不是 System.Data.EntityClient

 <connectionStrings>
         <add name="ModelFirstEntityFramework" connectionString="metadata=res://*/Context.csdl|res://*/Context.ssdl|res://*/Context.msl;provider=System.Data.SqlClient;provider
 connection string=&quot;data source=.\SQLEXPRESS;Initial
 Catalog=aspnet-MVC4;Integrated
 Security=SSPI;multipleactiveresultsets=True;App=EntityFramework&quot;"
 providerName="System.Data.EntityClient" />
         <add name="ConnStringForWebSecurity" connectionString="data source=.\SQLEXPRESS;Initial Catalog=aspnet-MVC4;Integrated
 Security=SSPI" providerName="System.Data.SqlClient" />
       </connectionStrings>

2
谢谢你,马里奥!这引发了一些关于两个DbContext连接如何相互协作的问题。我将为这两个问题创建新的论坛主题。 - Bob.at.Indigo.Health
2
双重连接字符串是我在DbFirst中缺失的部分!因此,SimpleMembershipProvider助手基本上只能使用经典连接字符串而不是EF连接字符串。 - profMamba
1
我是MVC世界的新手,现在我也遇到了同样的问题...你在第2点和第3点中所说的意思是什么,请您能给像我这样的新手更好的解释吗?提前感谢。 - Nudier Mena

12

这是MVC 4中的一个bug。 这篇博客文章中有一个解决方法

InitializeSimpleMembershipAttribute作为一个操作筛选器,在OnActionExecuting中进行懒加载初始化工作,但这可能在生命周期中太晚了。如果Authorize属性需要在OnAuthorization期间执行基于角色的访问检查,则需要更早地准备提供程序。换句话说,如果对站点的第一个请求命中以下控制器操作:

[Authorize(Roles="Sales")]

如果你在过滤器中检查用户的角色,而提供程序没有被初始化,那么你会遇到异常。

我的建议是从项目中删除ISMA,并在应用程序启动事件期间初始化WebSecurity。


谢谢Craig。当我第一次开始将我的模型优先实体与SimpleMembership魔法融合时,我实际上已经删除了ISMA,并将InitializeDatabaseConnection的调用从延迟初始化器移动到Global.asax.cs中的Application_Start方法中。我只是不想在原始帖子中复杂化这个场景。“找不到提供程序”异常发生在我尝试将我的模型优先实体连接到SimpleMembership时,无论我是在延迟初始化器中还是在Application_Start中进行调用。 - Bob.at.Indigo.Health
1
是啊,30行负代码的解决方案!我喜欢。+1 - Goran Obradovic

10

1 - 您需要启用迁移,最好使用EntityFramework 5。

2 - 将您的

WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "EmailAddress", autoCreateTables: true); 

在您的YourMvcApp/Migrations/Configuration.cs类中的Seed方法中

    protected override void Seed(UsersContext context)
    {
        WebSecurity.InitializeDatabaseConnection(
            "DefaultConnection",
            "UserProfile",
            "UserId",
            "UserName", autoCreateTables: true);

        if (!Roles.RoleExists("Administrator"))
            Roles.CreateRole("Administrator");

        if (!WebSecurity.UserExists("lelong37"))
            WebSecurity.CreateUserAndAccount(
                "lelong37",
                "password",
                new {Mobile = "+19725000000", IsSmsVerified = false});

        if (!Roles.GetRolesForUser("lelong37").Contains("Administrator"))
            Roles.AddUsersToRoles(new[] {"lelong37"}, new[] {"Administrator"});
    }
现在EF5将负责创建您的UserProfile表,创建完成后,您将调用WebSecurity.InitializeDatabaseConnection方法,只向已创建的UserProfile表注册SimpleMembershipProvider(在您的情况下,可以使用您自定义的表名称替换"UserProfile"参数值),并告诉SimpleMembershipProvider哪列是UserId和UserName。我还展示了如何在Seed方法中添加用户、角色以及与自定义UserProfile属性/字段(例如用户的手机号码)相关联的示例。
3- 现在,当您从Package Manager控制台运行update-database命令时,EF5将提供所有自定义属性的表结构。
如需更多参考,请参阅此文章及其源代码:http://blog.longle.net/2012/09/25/seeding-users-and-roles-with-mvc4-simplemembershipprovider-simpleroleprovider-ef5-codefirst-and-custom-user-properties/

感谢Long Le。不幸的是,我的问题是我正在使用“模型优先”,而不是“代码优先”。据我所知(如果我错了,请纠正我),“迁移”功能仅适用于“代码优先”,而不适用于“模型优先”。但是,一旦我让基本的claptrap工作起来,创建角色和用户的代码示例将非常有帮助!- Bob - Bob.at.Indigo.Health
1
Bob.as.SBS - 很高兴我的回答中至少有一些对你有帮助,你介意给这个回答点个赞吗?感谢。 - LeLong37

1

这个问题是由于WebSecurity.InitializeDatabaseConnection无法使用带有System.Data.EntityClient提供程序名称的连接字符串所引起的。

提供双重连接字符串并不好,因此您可以在部分类的构造函数中首先为EF模型生成连接字符串。

代码如下所示:

public partial class MyDataContext 
{
    private static string GenerateConnectionString(string connectionString)
    {
        var cs = System.Configuration.ConfigurationManager
                     .ConnectionStrings[connectionString];

        SqlConnectionStringBuilder sb = 
             new SqlConnectionStringBuilder(cs.ConnectionString);
        EntityConnectionStringBuilder builder = 
             new EntityConnectionStringBuilder();
        builder.Provider = cs.ProviderName;
        builder.ProviderConnectionString = sb.ConnectionString;
        builder.Metadata = "res://*/MyDataContext.csdl|" +
              "res://*/MyDataContext.ssdl|res://*/MyDataContext.msl";
        return builder.ToString();
    }

    public MyDataContext(string connectionName) : 
          base(GenerateConnectionString(connectionName)) { }
}

通过这个技巧,您可以在Web配置文件中使用单个连接字符串,但是有一个问题,您不能在数据上下文的默认构造函数中使用它,而是应该在实例化数据上下文时在各处使用连接字符串名称。但是,当您使用依赖注入模式时,这不是一个大问题。


0

我无法使用EF和WebMatrix的webSecurity类,为了避免这个问题并继续前进:

首先将我的Ef模型更改为代码优先。

更改连接字符串以使用providerName="System.Data.SqlClient"(删除所有元数据信息)或使用EF连接。

在我的情况下,模型、数据和Web是不同的项目,因此对我来说,从Web.project的web.config中删除此信息并不是一个问题。

现在,websecuroty.initializedatabase不再使用EF连接字符串运行。

希望这有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接