ASP.NET MVC 4,“WebSecurity.InitializeDatabaseConnection” 方法只能调用一次。

7

我正在使用Visual Studio 2012 Express开发一个基于代码优先的Web应用程序。

我在web.config中使用以下连接字符串:

<add name="myContext" connectionString="Data Source=.;Integrated Security=True;Initial Catalog=cityKingMVC4" providerName="System.Data.SqlClient" />

我正在使用SimpleMembership。

我正在尝试在Filters/InitializeSimpleMembershipAttribute.cs中种植1个管理员:

WebSecurity.InitializeDatabaseConnection("myContext", "Users", "UserId", "Email", autoCreateTables: true);

// A: Create Admin user
if (!WebSecurity.ConfirmAccount("admin@mydom.com"))
{
   WebSecurity.CreateUserAndAccount("admin@mydom.com", "password");
}

// B: Create admin role if not exist
if (!Roles.RoleExists("Administrator"))
{
   Roles.CreateRole("Administrator");
   Roles.AddUserToRole("admin@mydom.com", "Administrator");
}

如果我注释掉A和B,它就不会崩溃。如果我不注释掉,就会出现以下错误信息:“WebSecurity.InitializeDatabaseConnection”方法只能被调用一次。
如果我进行调试并在“WebSecurity.InitializeDatabaseConnection”上设置断点——它只会调用一次,并且没有其他代码在任何地方调用WebSecurity.InitializeDatabaseConnection。
如果我进行调试,它会在文件的更高位置(标准SimpleAuthentication文件)崩溃: LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
错误信息如下: 目标发生了一个调用的异常。
堆栈跟踪:
[InvalidOperationException: The "WebSecurity.InitializeDatabaseConnection" method can be called only once.]
   WebMatrix.WebData.WebSecurity.InitializeMembershipProvider(SimpleMembershipProvider simpleMembership, DatabaseConnectionInfo connect, String userTableName, String userIdColumn, String userNameColumn, Boolean createTables) +87978
   WebMatrix.WebData.WebSecurity.InitializeProviders(DatabaseConnectionInfo connect, String userTableName, String userIdColumn, String userNameColumn, Boolean autoCreateTables) +86
   myapPMVC4.Filters.SimpleMembershipInitializer..ctor() in c:\Users\name\Documents\Visual Studio 2012\Projects\myapPMVC4\myapPMVC4\Filters\InitializeSimpleMembershipAttribute.cs:43

[InvalidOperationException: The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588]
   myapPMVC4.Filters.SimpleMembershipInitializer..ctor() in c:\Users\name\Documents\Visual Studio 2012\Projects\myapPMVC4\myapPMVC4\Filters\InitializeSimpleMembershipAttribute.cs:88

[TargetInvocationException: Exception has been thrown by the target of an invocation.]
   System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck) +0
   System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark) +159
   System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark) +256
   System.Activator.CreateInstance(Type type, Boolean nonPublic) +127
   System.Activator.CreateInstance(Type type) +11
   System.Threading.LazyHelpers`1.ActivatorFactorySelector() +72
   System.Threading.LazyInitializer.EnsureInitializedCore(T& target, Boolean& initialized, Object& syncLock, Func`1 valueFactory) +241
   System.Threading.LazyInitializer.EnsureInitialized(T& target, Boolean& initialized, Object& syncLock) +139
   myapPMVC4.Filters.InitializeSimpleMembershipAttribute.OnActionExecuting(ActionExecutingContext filterContext) in c:\Users\name\Documents\Visual Studio 2012\Projects\myapPMVC4\myapPMVC4\Filters\InitializeSimpleMembershipAttribute.cs:22
   System.Web.Mvc.Async.AsyncControllerActionInvoker.InvokeActionMethodFilterAsynchronously(IActionFilter filter, ActionExecutingContext preContext, Func`1 nextInChain) +145
   System.Web.Mvc.Async.AsyncControllerActionInvoker.InvokeActionMethodFilterAsynchronously(IActionFilter filter, ActionExecutingContext preContext, Func`1 nextInChain) +840201
   System.Web.Mvc.Async.<>c__DisplayClass37.<BeginInvokeActionMethodWithFilters>b__31(AsyncCallback asyncCallback, Object asyncState) +266
   System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +146
   System.Web.Mvc.Async.AsyncResultWrapper.Begin(AsyncCallback callback, Object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate`1 endDelegate, Object tag, Int32 timeout) +202
   System.Web.Mvc.Async.AsyncResultWrapper.Begin(AsyncCallback callback, Object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate`1 endDelegate, Object tag) +112
   System.Web.Mvc.Async.<>c__DisplayClass25.<BeginInvokeAction>b__1e(AsyncCallback asyncCallback, Object asyncState) +839055
   System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +146
   System.Web.Mvc.Async.AsyncResultWrapper.Begin(AsyncCallback callback, Object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate`1 endDelegate, Object tag, Int32 timeout) +166
   System.Web.Mvc.Async.AsyncResultWrapper.Begin(AsyncCallback callback, Object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate`1 endDelegate, Object tag) +27
   System.Web.Mvc.<>c__DisplayClass1d.<BeginExecuteCore>b__17(AsyncCallback asyncCallback, Object asyncState) +50
   System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +146
   System.Web.Mvc.Async.AsyncResultWrapper.Begin(AsyncCallback callback, Object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate`1 endDelegate, Object tag, Int32 timeout) +166
   System.Web.Mvc.Controller.BeginExecuteCore(AsyncCallback callback, Object state) +826145
   System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +146
   System.Web.Mvc.Async.AsyncResultWrapper.Begin(AsyncCallback callback, Object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate`1 endDelegate, Object tag, Int32 timeout) +166
   System.Web.Mvc.Async.AsyncResultWrapper.Begin(AsyncCallback callback, Object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate endDelegate, Object tag) +27
   System.Web.Mvc.Controller.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) +401
   System.Web.Mvc.<>c__DisplayClass8.<BeginProcessRequest>b__2(AsyncCallback asyncCallback, Object asyncState) +786250
   System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +146
   System.Web.Mvc.Async.AsyncResultWrapper.Begin(AsyncCallback callback, Object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate`1 endDelegate, Object tag, Int32 timeout) +166
   System.Web.Mvc.Async.AsyncResultWrapper.Begin(AsyncCallback callback, Object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate endDelegate, Object tag) +27
   System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) +343
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +12550291
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +288

发生了什么?

谢谢

using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Threading;
using System.Web.Mvc;
using WebMatrix.WebData;
using System.Web.Security;
using myapPMVC4.Models;

namespace myapPMVC4.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)
        {
           LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
        }

        private class SimpleMembershipInitializer
        { 
            public SimpleMembershipInitializer()
            {
                Database.SetInitializer<UsersContext>(null);

                try
                {
                    using (var context = new UsersContext())
                    {
                        if (!context.Database.Exists())
                        {
                            // Create the SimpleMembership database without Entity Framework migration schema
                            ((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
                        }
                    }

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

                    if (!WebSecurity.Initialized)
                    {
                       WebSecurity.InitializeDatabaseConnection("myapPMVC4DBContext", "Users", "UserId", "Email", autoCreateTables: true);
                    }


                    // Create Admin user
                    if (!WebSecurity.ConfirmAccount("admin@myapp.com"))
                    {
                       //WebSecurity.CreateUserAndAccount("admin", "pass", new { email = "a@b.com" });
                       WebSecurity.CreateUserAndAccount("admin@myapp.com", "pass");
                    }

                    // Create admin role if not exist
                    if (!Roles.RoleExists("Administrator"))
                    {
                       Roles.CreateRole("Administrator");
                       Roles.AddUserToRole("admin@myapp.com", "Administrator");
                    }


                }
                catch (Exception ex)
                {
                    throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
                }
            }
        }
    }
}

展示你的代码。堆栈跟踪是什么? - SLaks
1
没有重复 - 看到了,但对我没有帮助。 - niico
在上方添加堆栈跟踪 - niico
5个回答

14

问题...

问题在于InitializeDatabaseConnection内部调用了WebSecurity.InitializeProviders,而这个方法不是线程安全的,再加上Web应用程序本质上是多线程环境...当WebSecurity.InitializedWebSecurity.InitializeDatabaseConnection一起使用时,它们不是线程安全的——它们会创建一个典型的竞争条件。

种子(用于迁移)意味着您的WebSecurity可以被初始化多次,因为您可能还需要在Global.asax.cs中初始化它以进行关闭种子的部署,并且您的InitializeSimpleMembershipAttribute在实时部署中可能会被多个HTTP请求同时调用。

将初始化代码放在多个地方也会破坏您的DRYness。

解决方案...

确保您的init调用是线程安全的,并且每个AppDomain实例仅发生一次。使用线程安全的单例类来实现这一点;并减少代码重复。

请从适合您的应用程序的以下任何/所有位置调用单例的EnsureInitialize方法:
  • Global.asax.cs(在任何其他内容之前的Application_Start方法中)
  • Migrations\Configuration.cs Seed方法(在创建用户之前)
  • Filters\InitializeSimpleMembershipAttribute.cs(在上下文初始化后SimpleMembershipInitializer构造函数中)
这是一个简单的单例示例:
// Call this with WebSecurityInitializer.Instance.EnsureInitialize()
public class WebSecurityInitializer {
    private WebSecurityInitializer() { }
    public static readonly WebSecurityInitializer Instance = new WebSecurityInitializer();
    private bool isNotInit = true;
    private readonly object SyncRoot = new object();
    public void EnsureInitialize() {
        if (isNotInit) {
            lock (this.SyncRoot) {
                if (isNotInit) {
                    isNotInit = false;
                    WebSecurity.InitializeDatabaseConnection("MyContextName",
                        userTableName: "UserProfile", userIdColumn: "UserId", userNameColumn: "UserName",
                        autoCreateTables: true);
                }
            }
        }
    }
}

一旦我完成了这个操作,你提到的错误就消失了,再也没有出现过。

脚注

单例模式还可以让你的代码保持DRY,这在应用程序早期开发阶段特别有用,如果你需要稍后更改WebSecurity.InitializeDatabaseConnection的配置,因为它只会存在于一个地方(最后编辑)。

我也保持SimpleMembershipInitializer的简洁,并将我的用户种子与迁移中的常规种子一起种植在Migrations\Configuration.cs Seed方法中。这有助于通过将所有内容放在一个地方来测试迁移的可测试性。我使用单元测试确保我们始终可以上下遍历迁移树,因此这样做更容易。

然而,您种子代码的位置并不重要,更重要的是,全局上,您已经在AppDomain中仅初始化了WebSecurity一次,并且任何对InitializeDatabaseConnection的调用都是线程安全的。


谢谢 - 这看起来非常有趣。我会尽快回复(离线一天)。 - niico
现在正在实施 - 你能推荐一些可以下载的示例应用程序,其中包含最佳实践 - 就像这个一样吗?我对MVC很陌生,这将非常有帮助。MVCMusicStore远远不够... - niico
@user2254951。作为自己的类,它可以放在任何地方,只要所有需要调用它的点都可以访问到它。我不确定有没有示例应用程序 - VS提供的模板是一个很好的开始,但我还没有找到任何好的、大型的最佳实践示例。 - Andy Brown
为了更好地理解情况,我在Application_Start()的第一行中添加了以下内容: 'if (!WebSecurity.Initialized) { System.Data.Entity.Database.SetInitializer(new MyAppMVC4.Models.SampleData()); }'此外,在InitializeSimpleMembershipAttribute.cs中也有以下内容: if (!WebSecurity.Initialized) { WebSecurity.InitializeDatabaseConnection("MyAppMVC4DBContext", "Users", "UserId", "Email", autoCreateTables: true); }你的意思是,它们之间存在冲突,需要将它们移动到只执行一次的相同代码位置吗? - niico
@user2254951。你的第一行看起来有点奇怪,我从未这样做过,也没有将其链接到WebSecurity(参见Place to put Database.SetInitializershotgun surgery)。完全分开地,在这两个地方我都使用我的EnsureInitialize而没有任何“如果WebSec...Init...”测试,以允许各种部署方案。 - Andy Brown
显示剩余3条评论

13

将此代码添加到Global.asax.cs中。这将确保在任何其他执行之前始终初始化数据库。还要确保它是在Application_Start()中的第一个注册。

if (!WebSecurity.Initialized)
                WebSecurity.InitializeDatabaseConnection("DefaultConnection",
"UserProfile", "UserId", "UserName", autoCreateTables: true);

如果您希望随时可以回到这里,则需要删除Filters/InitializeSimpleMembershipAttribute.cs或仅注释其中的代码。

AccountController.cs顶部删除[InitializeSimpleMembership]

另外,如果您还没有启用迁移,我建议您这样做。这样,当您运行Enable-Migrations时,在Migration文件夹中创建的Configuration.cs中可以进行种子操作。


2
如果已经初始化,请确保您的第一次调用:
if (!WebSecurity.Initialized)
{
    // Do the initialization first.
}

// The rest of the code

这样你就可以确保不会重复初始化过程。

这似乎没有帮助 - 请参见上文。FYI,我的SampleData.cs种子方法派生自'dropcreatedatabasealways': public class SampleData:DropCreateDatabaseAlways <myContext> - niico
我尝试将LazyLoader(请参见上面的AccountController.cs)包装在其中-没有帮助。 - niico

0

你的AccountController类上也有InitializeSimpleMembership属性吗?(这是默认设置)。如果是这样,那么你正在进行两次初始化。


1
在调用“WebSecurity”类的任何其他方法之前,您必须调用“WebSecurity.InitializeDatabaseConnection”方法。此调用应放置在站点根目录下的“_AppStart.cshtml”文件中。 - niico
@user2254951 - 你确定这是InitalizeDatabaseConnection方法唯一的位置吗?你确定你没有在多个地方使用这个属性吗? - Erik Funkenbusch
不,正如我上面所说 - 如果我在AccountController.cs中删除[IntializeSimpleMembership] - 我会得到上面的错误(这意味着它根本没有被初始化) - niico
我在_AppStart.cshtml中没有任何代码,只有布局信息(我想这是旧的WebMatrix遗留下来的?) - 也没有web.config引用。 - niico
我想我已经缩小了范围——如果我注释掉这些行,它似乎可以工作:' // 创建管理员用户 if (!WebSecurity.ConfirmAccount("admin@mydom.com")) { //WebSecurity.CreateUserAndAccount("admin", "pass", new { email = "a@b.com" }); WebSecurity.CreateUserAndAccount("admin@mydom.com", "pass"); }// 如果不存在,则创建管理员角色 if (!Roles.RoleExists("Administrator")) { Roles.CreateRole("Administrator"); Roles.AddUserToRole("admin@mydom.com", "Administrator"); } ' - niico
显示剩余5条评论

0

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