ASP.NET MVC 4, The "WebSecurity.InitializeDatabaseConnection" method can be called only once
Asked Answered
O

5

7

I am developing a code first web app in Visual Studio 2012 Express.

I use this connection string in the web.config:

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

I am using SimpleMembership.

I am trying to seed 1 administrator from Filters/InitializeSimpleMembershipAttribute.cs: ...

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

// A: Create Admin user
if (!WebSecurity.ConfirmAccount("[email protected]"))
{
   WebSecurity.CreateUserAndAccount("[email protected]", "password");
}

// B: Create admin role if not exist
if (!Roles.RoleExists("Administrator"))
{
   Roles.CreateRole("Administrator");
   Roles.AddUserToRole("[email protected]", "Administrator");
}

If I comment A & B it doesn't crash. If I don't I get this: The "WebSecurity.InitializeDatabaseConnection" method can be called only once.

If I debug and put a breakpoint on 'WebSecurity.InitializeDatabaseConnection' - it only calls it once and there is no other code calling WebSecurity.InitializeDatabaseConnection anywhere.

If I debug - it crashes on a different line higher up in the file (standard SimpleAuthentication file): LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);

with this error: Exception has been thrown by the target of an invocation.

Stack Trace:

[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

What's going on?

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("[email protected]"))
                    {
                       //WebSecurity.CreateUserAndAccount("admin", "pass", new { email = "[email protected]" });
                       WebSecurity.CreateUserAndAccount("[email protected]", "pass");
                    }

                    // Create admin role if not exist
                    if (!Roles.RoleExists("Administrator"))
                    {
                       Roles.CreateRole("Administrator");
                       Roles.AddUserToRole("[email protected]", "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);
                }
            }
        }
    }
}
Orose answered 12/5, 2013 at 15:31 Comment(4)
Show us your code. What's the stack trace?Bagehot
possible duplicate of WebSecurity.InitializeDatabaseConnection method can be called only onceOvermuch
not duplicate - saw that, didn't help meOrose
added stack trace aboveOrose
R
14

The problem ...

is that InitializeDatabaseConnection calls WebSecurity.InitializeProviders internally, and this method is not thread-safe, then combine this with the fact that WebSecurity typically needs initializing from various places. This has implications as web applications are inherently multi-threaded environments ... and WebSecurity.Initialized and WebSecurity.InitializeDatabaseConnection are not thread-safe when used together - they create a typical race condition.

Seeding (for migrations) means that your WebSecurity can be initialized more than once as you may also need to initialise it in Global.asax.cs for deployments with seeding turned off, and your InitializeSimpleMembershipAttribute can potentially be called multiple times simultaneusly by http requests in live deployments etc.

Putting the initialisation code in multiple places also breaks your DRYness

The solution ...

Make sure your init calls are thread-safe, and only occur once per instance of an AppDomain. Use a thread-safe singleton class to do this; and reduce duplication of code.

Call the singleton's EnsureInitialize method from any/all of the following, as appropriate for your application:

  • Global.asax.cs (Application_Start method before anything else)
  • Migrations\Configuration.cs Seed method (before creating the users)
  • Filters\InitializeSimpleMembershipAttribute.cs (in the SimpleMembershipInitializer constructor after the context is initialised)

Here is a simple example singleton:

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

Once I had done this, my case of the error you mention disappeared, not to be seen again.


Footnotes

The singleton class also keeps your code DRY, which is especially useful during early stage app development if you need to later change the configuration of your WebSecurity.InitializeDatabaseConnection as it will only be in one place (end edit)

I also keep the SimpleMembershipInitializer clean, and instead seed my users along with the common seeding in Migrations\Configuration.cs Seed method. This helps with testability of seeding through my migrations by keeping everything in one place. I use unit testing to make sure we can always go up and down the migrations tree, so this makes it easier to do that.

However the location of your seeding code won't matter, it is more just making sure that, globally, you have initialised WebSecurity only once within your AppDomain, and that any call to InitializeDatabaseConnection is thread-safe.

Riffle answered 12/5, 2013 at 22:30 Comment(8)
Thanks - this looks really interesting. Will report back ASAP (offline for a day)Orose
Implementing this now - can you recommend any sample applications I can download that incorporate best practice - such as this. I am new to MVC and it would be very helpful. MVCMusicStore doesn't go nearly far enough...Orose
@user2254951. As its own class, it can go anywhere, as long as it is reachable by all the points that I mentioned that need to call it. I'm not sure about sample applications - the template ones provided by VS are a good start, but I haven't found any good, large, examples of best practice yet.Riffle
To try and understand the situation better. I have this in first line of Application_Start(): 'if (!WebSecurity.Initialized) { System.Data.Entity.Database.SetInitializer(new MyAppMVC4.Models.SampleData()); }' I also have this, within InitializeSimpleMembershipAttribute.cs: if (!WebSecurity.Initialized) { WebSecurity.InitializeDatabaseConnection("MyAppMVC4DBContext", "Users", "UserId", "Email", autoCreateTables: true); } What you're saying is, they conflict & need moving to the same place in the code thats executed only once?Orose
I think there's a gap in the market there - I would certainly pay $5? $20? or more for a comprehensive clear sample app....Orose
@user2254951. Your first line looks odd to me, I've just never done it like that, or linked it to WebSecurity (see Place to put Database.SetInitializer and shotgun surgery). Quite separately I use my EnsureInitialize in both those places without any "if WebSec...Init.." tests to allow for various deployment scenarios.Riffle
Thanks - I also found this post very useful: blog.longle.net/2012/09/25/…Orose
Thanks so much for this. I could get my site to run and seed locally, but I kept running into the issue when publishing to Azure. I still need to fully grok the concepts, but Saturday afternoon saved! :)Apterous
B
13

Add this code to Global.asax.cs. This will makes sure that your database is always Initialized before any other executions. Also make sure its the first registration in Application_Start()

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

Get rid of Filters/InitializeSimpleMembershipAttribute.cs or just comment the code inside, in case you would like to go back to it.

Remove [InitializeSimpleMembership] at the top of AccountController.cs

Also if you haven't already enabled migrations, i would encourage you do so. That way, you can do your seeds in Configuration.cs created inside the Migration folder when you run Enable-Migrations

Bimestrial answered 13/5, 2013 at 2:36 Comment(0)
F
2

If it is already initialized then make sure your first call:

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

// The rest of the code

This way you'll be sure that you don't repeat the initialization process.

Forewent answered 12/5, 2013 at 18:21 Comment(2)
This doesn't seem to help - see above. FYI, my SampleData.cs seed method derives from 'dropcreatedatabasealways): public class SampleData : DropCreateDatabaseAlways<myContext>Orose
I tried wrapping LazyLoader (see AccountController.cs above) in this - didn't help.Orose
H
0

Do you also have the InitializeSimpleMembership attribute on your AccountController class? (this is the default). If so, you are initializing twice.

Hausmann answered 12/5, 2013 at 18:38 Comment(10)
"You must call the "WebSecurity.InitializeDatabaseConnection" method before you call any other method of the "WebSecurity" class. This call should be placed in an _AppStart.cshtml file in the root of your site."Orose
@user2254951 - you're absolutely sure this is the only place where the InitalizeDatabaseConnection method is? You're sure you don't have the attribute in more than one place?Hausmann
No, as I say above - If I remove [IntializeSimpleMembership] in AccountController.cs - I get the above error (which implies it hasn't been initialized at allOrose
@user2254951 - No? You're not sure?Hausmann
@user2254951 - you need to provide the entire source code of you InitializeSimpleMembershipAttribute class, by only providing a small part we can't see what else you might be doing.Hausmann
@user2254951 - there's something going on here that you're not showing us.. You have to be calling this twice somehow.Hausmann
@user2254951 - you don't have an _AppStart.cshtml file do you? Does that have an InitializeDatabaseConnection call in it? Do you have <add key="enableSimpleMembership" value="true" /> in your web.config?Hausmann
I don't have any code in _AppStart.cshtml, only layout info (a hang over from old WebMatrix I think?) - and no web.config reference eitherOrose
I think I've narrowed it down - it seems to work if I comment these lines out: [code]( // Create Admin user if (!WebSecurity.ConfirmAccount("[email protected]")) { //WebSecurity.CreateUserAndAccount("admin", "pass", new { email = "[email protected]" }); WebSecurity.CreateUserAndAccount("[email protected]", "pass"); } // Create admin role if not exist if (!Roles.RoleExists("Administrator")) { Roles.CreateRole("Administrator"); Roles.AddUserToRole("[email protected]", "Administrator"); } ) Is something wrong with them? If so whats best way to seed admins?Orose
formatted code: I think I've narrowed it down - it seems to work if I comment these lines out: ' // Create Admin user if (!WebSecurity.ConfirmAccount("[email protected]")) { //WebSecurity.CreateUserAndAccount("admin", "pass", new { email = "[email protected]" }); WebSecurity.CreateUserAndAccount("[email protected]", "pass"); } // Create admin role if not exist if (!Roles.RoleExists("Administrator")) { Roles.CreateRole("Administrator"); Roles.AddUserToRole("[email protected]", "Administrator"); } 'Orose
M
0

To make sure that the WebSecurity.InitializeDatabaseConnection is not called twice just use the WebSecurity.Initialized to check if it was already called. This blog post provides detailed instructions on seeding and customizing SimpleMembership. There is a series in this blog on using SimpleMembership and I would also recommend looking at decoupling SimpleMembership from your ASP.NET MVC Application. You can get the complete source code for these examples here.

Moreen answered 13/5, 2013 at 13:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.