Asp.net

ASP.NET MVC 4,“WebSecurity.InitializeDatabaseConnection”方法只能呼叫一次

  • April 27, 2016

我正在 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);
               }
           }
       }
   }
}

問題 …

是內部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都是執行緒安全的。

引用自:https://stackoverflow.com/questions/16508934