ASP.NET MVC 4, The “WebSecurity.InitializeDatabaseConnection” method can be called only once

后端 未结 5 1591
迷失自我
迷失自我 2020-12-19 07:00

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

I use this connection string in the web.config:



        
相关标签:
5条回答
  • 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.

    0 讨论(0)
  • 2020-12-19 07:32

    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

    0 讨论(0)
  • 2020-12-19 07:32

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

    0 讨论(0)
  • 2020-12-19 07:33

    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.

    0 讨论(0)
  • 2020-12-19 07:49

    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.

    0 讨论(0)
提交回复
热议问题