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

5 回答 5

14

问题 ...

是内部InitializeDatabaseConnection调用WebSecurity.InitializeProviders,并且此方法不是线程安全的,然后将其与 WebSecurity 通常需要从各个地方初始化的事实结合起来。这会产生影响,因为 Web 应用程序本质上是多线程环境......并且WebSecurity.Initialized在一起WebSecurity.InitializeDatabaseConnection 使用时不是线程安全的- 它们会产生典型的竞争条件。

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

将初始化代码放在多个地方也会破坏你的DRY ness

解决方案 ...

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

EnsureInitialize根据您的应用程序,从以下任何/所有方法调用单例方法:

  • Global.asax.cs (Application_Start方法之前)
  • Migrations\Configuration.csSeed方法(在创建用户之前)
  • 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因为它只会在一个地方(结束编辑)

我还保持清洁,而是将我的用户与 Migrations\Configuration.cs方法SimpleMembershipInitializer中的常见种子一起播种。Seed通过将所有内容保存在一个地方,这有助于通过我的迁移进行播种的可测试性。我使用单元测试来确保我们总是可以在迁移树上上下移动,所以这样做更容易。

但是,种子代码的位置无关紧要,更重要的是确保在全局范围内,您在 AppDomain 中只初始化了一次 WebSecurity,并且任何调用InitializeDatabaseConnection都是线程安全的。

于 2013-05-12T22:30:26.743 回答
13

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

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

摆脱Filters/InitializeSimpleMembershipAttribute.cs或只是注释里面的代码,以防你想回到它。

[InitializeSimpleMembership]在顶部删除AccountController.cs

此外,如果您尚未启用迁移,我鼓励您这样做。这样,您可以在运行时Configuration.cs在内部创建种子Migration folderEnable-Migrations

于 2013-05-13T02:36:47.887 回答
2

如果它已经初始化,那么请确保您的第一次调用:

if (!WebSecurity.Initialized)
{
    // Do the initialization first.
}

// The rest of the code

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

于 2013-05-12T18:21:24.373 回答
0

您的 AccountController 类上是否还有 InitializeSimpleMembership 属性?(这是默认设置)。如果是这样,您将初始化两次。

于 2013-05-12T18:38:19.143 回答
0

要确保WebSecurity.InitializeDatabaseConnection没有被调用两次,只需使用WebSecurity.Initialized来检查它是否已经被调用。这篇博文提供了有关播种和自定义 SimpleMembership 的详细说明。此博客中有一个关于使用 SimpleMembership 的系列,我还建议您查看将SimpleMembership 从您的 ASP.NET MVC Application 中解耦。您可以在此处获取这些示例的完整源代码

于 2013-05-13T13:37:43.623 回答