Adding an Admin user to an ASP.NET MVC 4 application using a single drop-in file

I'm working on an ASP.NET MVC 4 tutorial and wanted to set it up so just dropping a file in App_Start would create a user named "Owner" and assign them to the "Administrator" role (more explanation at the end if you're interested).

There are reasons why this wouldn't fit into most application scenarios:

  • It's not efficient, as it checks for (and creates, if necessary) the user every time the app starts up
  • The username, password, and role name are hardcoded in the app (although they could be pulled from config)
  • Automatically creating an administrative account in code (without user interaction) could lead to obvious security issues if the user isn't informed

However, with some modifications it might be more broadly useful - e.g. creating a test user with limited privileges, ensuring a required account isn't accidentally deleted, or - as in my case - setting up an account for demonstration or tutorial purposes.

Challenge #1: Running on startup without requiring the user to install or configure anything

I wanted to see if this could be done just by having the user drop a file into the App_Start folder and go. No copying code into Global.asax.cs, no installing addition NuGet packages, etc. That may not be the best approach - perhaps a NuGet package with a dependency on WebActivator would be better - but I wanted to see if this was possible and see if it offered the best experience.

Fortunately ASP.NET 4 and later provide a PreApplicationStartMethod attribute which allows you to register a method which will run when the application starts up. You drop this attribute in your application and give it two parameters: a method name and the type that contains it. I created a static class named PreApplicationTasks with a static method named, then dropped this attribute in it:

[assembly: PreApplicationStartMethod(typeof(PreApplicationTasks), "Initializer")] 

That's it. One small gotcha: the namespace can be a problem with assembly attributes. I decided my class didn't need a namespace.

Challenge #2: Only one PreApplicationStartMethod per assembly

In .NET 4, the PreApplicationStartMethod is marked as AllMultiple=false, so you can only have one PreApplicationStartMethod per assembly. This was fixed in .NET 4.5, as noted by Jon Skeet, so you can have as many PreApplicationStartMethods as you want (allowing you to keep your users waiting for the application to start indefinitely!).

The WebActivator NuGet package solves the multiple instance problem if you're in .NET 4 - it registers as a PreApplicationStartMethod, then calls any methods you've indicated using [assembly: WebActivator.PreApplicationStartMethod(type, method)]. David Ebbo blogged about that here:  Light up your NuGets with startup code and WebActivator.

In my scenario (bootstrapping a beginner level tutorial) I decided not to worry about this and stick with PreApplicationStartMethod.

Challenge #3: PreApplicationStartMethod kicks in before configuration has been read

This is by design, as Phil explains. It allows you to make changes that need to happen very early in the pipeline, well before Application_Start. That's fine in some cases, but it caused me problems when trying to add users, since the Membership Provider configuration hadn't yet been read - I got an exception stating that "Default Membership Provider could not be found."

PreApplicationStartMethod fires before config is loaded

The solution here is to run code that requires configuration in a PostApplicationStart method. But how to do that?

Challenge #4: Getting PostApplicationStartMethod without requiring WebActivator

The WebActivator NuGet package, among other things, provides a PostApplicationStartMethod attribute. That's generally how I'd recommend running code that needs to happen after Application_Start:

[assembly: WebActivator.PostApplicationStartMethod(typeof(TestLibrary.MyStartupCode), "CallMeAfterAppStart")]

This works well, but I wanted to see if this would be possible without WebActivator. Hmm.

Well, wait a minute - WebActivator works in .NET 4, so clearly it's registering and calling PostApplicationStartup tasks somehow. Off to the source code! Sure enough, there's even a handy comment in ActivationManager.cs which shows where PostApplicationStartup tasks are being registered:

public static void Run()
{
    if (!_hasInited)
    {
        RunPreStartMethods();

        // Register our module to handle any Post Start methods. But outside of ASP.NET, just run them now
        if (HostingEnvironment.IsHosted)
        {
            Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(StartMethodCallingModule));
        }
        else
        {
            RunPostStartMethods();
        }

        _hasInited = true;
    }
}

Excellent. Hey, that DynamicModuleUtility seems familiar... Sure enough, K. Scott Allen mentioned it on his blog last year. This is really slick - a PreApplicationStartMethod can register a new HttpModule in code. Modules are run right after application startup, so that's a perfect time to do any startup stuff that requires configuration to be read. As K. Scott says, it's this easy:

using System;
using System.Web;
using Microsoft.Web.Infrastructure.DynamicModuleHelper;

[assembly:PreApplicationStartMethod(typeof(MyAppStart), "Start")]

public class CoolModule : IHttpModule
{
    // implementation not important 
    // imagine something cool here
}

public static class MyAppStart
{
    public static void Start()
    {
        DynamicModuleUtility.RegisterModule(typeof(CoolModule));
    }
}

Challenge #5: Cooperating with SimpleMembership

The ASP.NET MVC Internet template includes SimpleMembership. SimpleMembership is a big improvement over traditional ASP.NET Membership. For one thing, rather than forcing a database schema, it can work with your database schema. In the MVC 4 Internet template case, it uses Entity Framework Code First to define the user model. SimpleMembership bootstrap includes a call to InitializeDatabaseConnection, and I want to play nice with that.

There's a new [InitializeSimpleMembership] attribute on the AccountController, which calls \Filters\InitializeSimpleMembershipAttribute.cs::OnActionExecuting(). That comment in that method that says "Ensure ASP.NET Simple Membership is initialized only once per app start" which sounds like good advice. I figured the best thing would be to call that directly:

new Mvc4SampleApplication.Filters.InitializeSimpleMembershipAttribute().OnActionExecuting(null);

I'm not 100% happy with this - in fact, it's my least favorite part of this solution. There are two problems - first, directly calling a method on a filter, while legal, seems odd. Worse, though, the Filter lives in the application's namespace, which means that this code no longer works well as a generic drop-in.

The simplest workaround would be to duplicate the relevant SimpleMembership initialization code into my startup code, but I'd rather not. I'm interested in your suggestions here.

Challenge #6: Module Init methods are called more than once

When debugging, I noticed (and remembered) that the Init method may be called more than once per page request - it's run once per instance in the app pool, and an individual page request can cause multiple resource requests to the server. While SimpleMembership does have internal checks to prevent duplicate user or role entries, I'd rather not cause or handle those exceptions. So here's the standard single-use lock in the Module's init method:

void IHttpModule.Init(HttpApplication context)
{
    lock (lockObject)
    {
        if (!initialized)
        {
            //Do stuff
        }
        initialized = true;
    }
}

Putting it all together

With all of that out of the way, here's the code I came up with:

The Verdict: Is this a good thing?

Maybe.

I think you'll agree that the journey was undoubtedly worthwhile, as it took us through some of the finer points of hooking into application startup, integrating with membership, and understanding why the WebActivator NuGet package is so useful

Will I use this in the tutorial? I'm leaning towards no - I think a NuGet package with a dependency on WebActivator might work better:

  • It's a little more clear what's going on
  • Installing a NuGet package might be a little less error prone than copying a file
  • A novice user could uninstall the package when complete
  • It's a good introduction to NuGet, which is a good thing for beginners to see
  • This code either requires either duplicating a little code from that filter or modifying the file to use the namespace

Honestly I'm undecided at this point, but I'm glad that I can weigh the options.

If you're interested: Why are you doing this?

I'm updating the MVC Music Store tutorial to ASP.NET MVC 4, taking advantage of a lot of new ASP.NET MVC 4 features and trying to simplify areas that are giving people trouble. One change that addresses both needs us using the new OAuth support for membership as much as possible - it's a great new feature from an application perspective, and we get a fair amount of beginners struggling with setting up membership on a variety of database and development setups, which is a distraction from the focus of the tutorial - learning ASP.NET MVC.

Side note: Thanks to some great help from Rick Anderson, we had a draft of the tutorial that was looking pretty good earlier this summer, but there were enough changes in ASP.NET MVC 4 all the way up to RTM that there's still some work to be done. It's high priority and should be out very soon.

The one issue I ran into with OAuth is that we still need an Administrative user who can edit the store's inventory. I thought about a number of solutions for that - making the first user to register the admin, or the first user to use the username "Administrator" is assigned to the Administrator role - but they both ended up requiring extra code; also, I worried that people would use that code without understanding it or thinking about whether it was a good fit.

20 Comments

  • Out of curiosity, why do you prefer running startup code in WebActivator PostApplicationStartMethod rather than global.asax Application_Start for general purpose (non-nuget) initialization code?

  • You should add a check for !initialized outside your lock too. It saves the overhead of creating the lock when it isn't needed - i.e. most of the time!

    if (!initialized)
    {
    lock (lockObject)
    {
    if (!initialized)
    {
    new InitializeSimpleMembershipAttribute().OnActionExecuting(null);

    if (!WebSecurity.UserExists(_username))
    WebSecurity.CreateUserAndAccount(_username, _password);

    if (!Roles.RoleExists(_role))
    Roles.CreateRole(_role);

    if (!Roles.IsUserInRole(_username, _role))
    Roles.AddUserToRole(_username, _role);
    }
    initialized = true;
    }
    }

  • Instead of directly calling InitializeSimpleMembershiptAttribute.cs::OnActionExecuting(), you can add the following line within the "RegisterGlobalFilters" method in your App_Start\FilterConfig.cs file:

    filters.Add(new InitializationSimpleMembershipAttribute());

  • I do trust all the ideas you've introduced to your post. They're very convincing and can definitely
    work. Still, the posts are very brief for newbies. May you
    please extend them a bit from next time? Thanks for the post.

  • Hello there! I simply would like to give you a big thumbs up for the excellent info you have got right
    here on this post. I will be coming back to your web site for more
    soon.

  • We stumbled over here coming from a different
    website and thought I might check things out.

    I like what I see so now i am following you. Look forward to
    looking into your web page again.

  • Heya just wanted to give you a quick heads up and let you know a few of the
    pictures aren't loading properly. I'm not sure why but I think its a
    linking issue. I've tried it in two different web browsers and both show the same outcome.

  • Howdy, I believe your site could possibly be having
    web browser compatibility problems. When I take a look at your site in Safari, it looks fine however, if opening
    in Internet Explorer, it's got some overlapping issues. I simply wanted to give you a quick heads up! Apart from that, wonderful site!

  • Hi there! This article couldn't be written any better! Looking through this post reminds me of my previous roommate! He continually kept talking about this. I am going to forward this article to him. Fairly certain he's going to have a great read.
    Thanks for sharing!

  • I feel this is one of the so much significant information for me.

    And i'm glad studying your article. But want to observation on some general things, The website style is ideal, the articles is truly great : D. Good task, cheers

  • That organization involves Transpokies receiving accomplished versions
    of PC games from publishers, and then optimizing it to run on the individual to bear down in,
    come apart foeman organisation and twit everyone then this is .

  • Pokies for play purposes a smorgasbord of different games, and getting
    caught up in the appeal is what the Pokies owners desire you to do.
    Now, I recognize that I can run out and buy literally the United States before they came to work
    on at the Adept.

  • The write-up features proven beneficial to me personally.
    It’s extremely helpful and you really are certainly really knowledgeable
    in this region. You have got opened our eyes to different thoughts about this particular matter using interesting
    and sound content material.

  • I'm really enjoying the theme/design of your weblog. Do you ever run into any internet browser compatibility issues? A couple of my blog readers have complained about my blog not working correctly in Explorer but looks great in Firefox. Do you have any advice to help fix this issue?

  • You actually make it seem so easy with your presentation but I find this topic to be actually something that I think I would never understand.
    It seems too complicated and very broad for me.

    I'm looking forward for your next post, I'll try to get the
    hang of it!

  • When some one searches for his essential thing, therefore
    he/she desires to be available that in detail, so that thing is maintained over here.

  • You cannot use the area at any time for personal use, even if the use is just a
    day or two of the year. This time include the expenses or the reasons that forced the
    budget to break. If you drive a lower when compared with
    average volume of miles each year, less than 12,000, you can pay much less expensive, often 15% much less.

  • My partner and I stumbled over here by a different web address
    and thought I may as well check things out. I like what I see so now i'm following you. Look forward to finding out about your web page again.

  • Quality articles or reviews is the key to be a focus for the users to visit the web page, that's what this web site is providing.

  • For instance, you can add a number of Master Pages into one Web page and create an embedded You - Tube
    with full-screen mode. For content management in Drupal vs Joomla, consider these
    options:. 3rd Does all SEO work can be done by one person, or not.

Comments have been disabled for this content.