我正在使用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&amp; 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
发生了什么事?
THX
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);
}
}
}
}
}
答案 0 :(得分:14)
是InitializeDatabaseConnection
内部调用WebSecurity.InitializeProviders
,此方法不是线程安全的,然后将此与WebSecurity通常需要从各个位置初始化的事实相结合。这具有影响,因为Web应用程序本身就是多线程环境......并且WebSecurity.Initialized
和WebSecurity.InitializeDatabaseConnection
在一起使用时不是线程安全的 - 它们会创建典型的竞争条件。
种子设定(用于迁移)意味着您的WebSecurity
可以多次初始化,因为您可能还需要在Global.asax.cs中对其进行初始化,以便在关闭种子的情况下部署您的InitializeSimpleMembershipAttribute
可以通过实时部署等中的http请求同时多次调用
将初始化代码放在多个位置也会破坏您的DRY nss
确保您的init调用是线程安全的,并且每个AppDomain实例只发生一次。使用线程安全的单例类来执行此操作;并减少重复的代码。
根据您的应用,从以下任何/所有方法调用单身人士的EnsureInitialize
方法:
Application_Start
方法)Seed
方法(在创建用户之前)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
的任何调用都是线程安全的。
答案 1 :(得分: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 folder
内创建Enable-Migrations
种子。
答案 2 :(得分:2)
如果已经初始化,请确保第一次通话:
if (!WebSecurity.Initialized)
{
// Do the initialization first.
}
// The rest of the code
这样您就可以确保不重复初始化过程。
答案 3 :(得分:0)
您的AccountController类还有InitializeSimpleMembership属性吗? (这是默认值)。如果是这样,那么您正在初始化两次。
答案 4 :(得分:0)
要确保WebSecurity.InitializeDatabaseConnection未被调用两次,只需使用WebSecurity.Initialized检查它是否已被调用。这blog post provides detailed instructions on seeding and customizing SimpleMembership。本博客中有一系列关于使用SimpleMembership的内容,我还建议您查看decoupling SimpleMembership from your ASP.NET MVC Application。您可以获得complete source code for these examples here。