The ASP.NET MVC 4 Internet template adds some new, very useful
features which are built on top of SimpleMembership. These changes add
some great features, like a much simpler and extensible membership API
and support for OAuth. However, the new account management features
require SimpleMembership and won't work against existing ASP.NET
Membership Providers. I'll start with a summary of top things you need
to know, then dig into a lot more detail.
Summary:
- SimpleMembership has been designed as a replacement for the previous ASP.NET Role and Membership provider system
- SimpleMembership
solves common problems developers ran into with the Membership provider
system and was designed for modern user / membership / storage needs
- SimpleMembership integrates with the previous membership system, but you can't use a MembershipProvider with SimpleMembership
- The
new ASP.NET MVC 4 Internet application template AccountController
requires SimpleMembership and is not compatible with previous
MembershipProviders
- You can continue to use existing
ASP.NET Role and Membership providers in ASP.NET 4.5 and ASP.NET MVC 4 -
just not with the ASP.NET MVC 4 AccountController
- The existing ASP.NET Role and Membership provider system remains supported, as it is part of the ASP.NET core
- ASP.NET 4.5 Web Forms does not use SimpleMembership; it implements OAuth on top of ASP.NET Membership
- The ASP.NET Web Site Administration Tool (WSAT) is not compatible with SimpleMembership
The
following is the result of a few conversations with Erik Porter (PM for
ASP.NET MVC) to make sure I had some the overall details straight,
combined with a lot of time digging around in ILSpy and Visual Studio's
assembly browsing tools.
SimpleMembership: The future of membership for ASP.NET
The
ASP.NET Membership system
was introduced with ASP.NET 2.0 back in 2005. It was designed to solve
common site membership requirements at the time, which generally
involved username / password based registration and profile storage in
SQL Server. It was designed with a few extensibility mechanisms -
notably a provider system (which allowed you override some specifics
like backing storage) and the ability to store additional profile
information (although the additional profile information was packed
into a single column which usually required access through the API).
While it's sometimes frustrating to work with, it's held up for seven
years - probably since it handles the main use case (username / password
based membership in a SQL Server database) smoothly and can be adapted
to most other needs (again, often frustrating, but it can work).
The
ASP.NET Web Pages and WebMatrix efforts allowed the team an opportunity
to take a new look at a lot of things - e.g. the Razor syntax started
with ASP.NET Web Pages, not ASP.NET MVC. The ASP.NET Web Pages team
designed SimpleMembership to (wait for it) simplify the task of dealing
with membership. As Matthew Osborn said in his post
Using SimpleMembership With ASP.NET WebPages:
With
the introduction of ASP.NET WebPages and the WebMatrix stack our team
has really be focusing on making things simpler for the developer. Based
on a lot of customer feedback one of the areas that we wanted to
improve was the built in security in ASP.NET. So with this release we
took that time to create a new built in (and default for ASP.NET
WebPages) security provider. I say provider because the new stuff is
still built on the existing ASP.NET framework. So what do we call this
new hotness that we have created? Well, none other than SimpleMembership. SimpleMembership is an umbrella term for both SimpleMembership and SimpleRoles.
Part of simplifying membership involved fixing some common problems with ASP.NET Membership.
Problems with ASP.NET Membership
ASP.NET Membership was very obviously designed around a set of assumptions:
- Users and user information would most likely be stored in a full SQL Server database or in Active Directory
- User
and profile information would be optimized around a set of common
attributes (UserName, Password, IsApproved, CreationDate, Comment, Role
membership...) and other user profile information would be accessed
through a profile provider
Some problems fall out of these assumptions.
Requires Full SQL Server for default cases
The
default, and most fully featured providers ASP.NET Membership providers
(SQL Membership Provider, SQL Role Provider, SQL Profile Provider)
require full SQL Server. They depend on stored procedure support, and
they rely on SQL Server cache dependencies, they depend on agents for
clean up and maintenance. So the main SQL Server based providers don't
work well on SQL Server CE, won't work out of the box on SQL Azure, etc.
Note: Cory Fowler recently let me know about these Updated ASP.net scripts for use with Microsoft SQL Azure
which do support membership, personalization, profile, and roles. But
the fact that we need a support page with a set of separate SQL scripts
underscores the underlying problem.
Aha, you say! Jon's forgetting the Universal Providers, a.k.a. System.Web.Providers! Hold on a bit, we'll get to those...
Custom Membership Providers have to work with a SQL-Server-centric API
If
you want to work with another database or other membership storage
system, you need to to inherit from the provider base classes and
override a bunch of methods which are tightly focused on storing a
MembershipUser in a relational database. It can be done (and you can
often find pretty good ones that have already been written), but it's a
good amount of work and often leaves you with ugly code that has a bunch
of System.NotImplementedException fun since there are a lot of methods
that just don't apply.
Designed around a specific view of users, roles and profiles
The
existing providers are focused on traditional membership - a user has a
username and a password, some specific roles on the site (e.g.
administrator, premium user), and may have some additional "nice to
have" optional information that can be accessed via an API in your
application.
This doesn't fit well with some modern usage patterns:
- In OAuth and OpenID, the user doesn't have a password
- Often these kinds of scenarios map better to user claims or rights instead of monolithic user roles
- For
many sites, profile or other non-traditional information is very
important and needs to come from somewhere other than an API call that
maps to a database blob
What would work a lot better
here is a system in which you were able to define your users, rights,
and other attributes however you wanted and the membership system worked
with your model - not the other way around.
Requires specific schema, overflow in blob columns
I've
already mentioned this a few times, but it bears calling out separately
- ASP.NET Membership focuses on SQL Server storage, and that storage is
based on a very specific database schema.
Update:
This schema has been improved a lot with Universal Providers. The views
and stored procedures have been removed, and the tables are simplified.
Still,
the main issues are unchanged: you're not in control of the schema, and
any profile data is stored in property value blobs in the Profiles
table:
SimpleMembership as a better membership system
As you might have guessed, SimpleMembership was designed to address the above problems.
Works with your Schema
As Matthew Osborn explains in his
Using SimpleMembership With ASP.NET WebPages post, SimpleMembership is designed to integrate with your database schema:
All SimpleMembership
requires is that there are two columns on your users table so that we
can hook up to it – an “ID” column and a “username” column. The
important part here is that they can be named whatever you want. For
instance username doesn't have to be an alias it could be an email
column you just have to tell SimpleMembership to treat that as the “username” used to log in.
Matthew's
example shows using a very simple user table named Users (it could be
named anything) with a UserID and Username column, then a bunch of other
columns he wanted in his app.
Then we point SimpleMemberhip at that table with a one-liner:
WebSecurity.InitializeDatabaseFile("SecurityDemo.sdf", "Users", "UserID", "Username", true);
No other tables are needed, the table can be named anything we want,
and can have pretty much any schema we want as long as we've got an ID
and something that we can map to a username.
Broaden database support to the whole SQL Server family
While SimpleMembership is not database agnostic, it works
across the SQL Server family. It continues to support full SQL Server,
but it also works with SQL Azure, SQL Server CE, SQL Server Express, and
LocalDB. Everything's implemented as SQL calls rather than requiring stored procedures, views, agents, and change notifications.
Note that SimpleMembership still requires some flavor of SQL Server -
it won't work with MySQL, NoSQL databases, etc. You can take a look at
the code in WebMatrix.WebData.dll using a tool like ILSpy if you'd like
to see why - there are places where SQL Server specific SQL statements
are being executed, especially when creating and initializing tables. It
seems like you might be able to work with another database if you
created the tables separately, but I haven't tried it and it's not
supported at this point.
Note: I'm thinking it would be possible for SimpleMembership (or
something compatible) to run Entity Framework so it would work with any
database EF supports. That seems useful to me - thoughts?
Note: SimpleMembership has the same database support - anything
in the SQL Server family - that Universal Providers brings to the
ASP.NET Membership system.
UPDATE: Newer updates of Universal Providers - I believe starting with the 1.2 release on 8/16 - are now really database agnostic, so they'll work on any database that has an Entity Framework provider.
Easy to with Entity Framework Code First
The problem with with ASP.NET Membership's system for storing
additional account information is that it's the gate keeper. That means
you're stuck with its schema and accessing profile information through
its API.
SimpleMembership flips that around by allowing you to use any table
as a user store. That means you're in control of the user profile
information, and you can access it however you'd like - it's just data.
Let's look at a practical based on the AccountModel.cs class in an
ASP.NET MVC 4 Internet project. Here I'm adding a Birthday property to
the UserProfile class.
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public DateTime Birthday { get; set; }
}
Now if I want to access that information, I can just grab the account by username and read the value.
var context = new UsersContext();
var username = User.Identity.Name;
var user = context.UserProfiles.SingleOrDefault(u => u.UserName == username);
var birthday = user.Birthday;
So instead of thinking of SimpleMembership as a big membership API,
think of it as something that handles membership based on your user
database. In SimpleMembership, everything's keyed off a user row in a
table you define rather than a bunch of entries in membership tables
that were out of your control.
How SimpleMembership integrates with ASP.NET Membership
Okay, enough sales pitch (and hopefully background) on why things
have changed. How does this affect you? Let's start with a diagram to
show the relationship (note: I've simplified by removing a few classes
to show the important relationships):
So SimpleMembershipProvider is an implementaiton of an
ExtendedMembershipProvider, which inherits from MembershipProvider and
adds some other account / OAuth related things. Here's what
ExtendedMembershipProvider adds to MembershipProvider:
The important thing to take away here is that a
SimpleMembershipProvider is a MembershipProvider, but a
MembershipProvider is not a SimpleMembershipProvider.
This distinction is important in practice:
you cannot use an
existing MembershipProvider (including the Universal Providers found in
System.Web.Providers) with an API that requires a
SimpleMembershipProvider, including any of the calls in
WebMatrix.WebData.WebSecurity or
Microsoft.Web.WebPages.OAuth.OAuthWebSecurity.
However, that's as far as it goes. Membership Providers still work if
you're accessing them through the standard Membership API, and all of
the core stuff - including the AuthorizeAttribute, role enforcement,
etc. - will work just fine and without any change.
Let's look at how that affects you in terms of the new templates.
Membership in the ASP.NET MVC 4 project templates
ASP.NET MVC 4 offers six Project Templates:
- Empty - Really empty, just the assemblies, folder structure and a tiny bit of basic configuration.
- Basic - Like Empty, but with a bit of UI preconfigured (css / images / bundling).
- Internet - This has both a Home and Account
controller and associated views. The Account Controller supports
registration and login via either local accounts and via OAuth / OpenID
providers.
- Intranet - Like the Internet template, but it's preconfigured for Windows Authentication.
- Mobile - This is preconfigured using jQuery Mobile and is intended for mobile-only sites.
- Web API - This is preconfigured for a service backend built on ASP.NET Web API.
Out of these templates, only one (the Internet template) uses SimpleMembership.
ASP.NET MVC 4 Basic template
The Basic template has configuration in place to use ASP.NET
Membership with the Universal Providers. You can see that configuration
in the ASP.NET MVC 4 Basic template's web.config:
This means that it's business as usual for the Basic template as far as ASP.NET Membership works.
ASP.NET MVC 4 Internet template
The Internet template has a few things set up to bootstrap SimpleMembership:
- \Models\AccountModels.cs defines a basic user account and includes data annotations to define keys and such
- \Filters\InitializeSimpleMembershipAttribute.cs
creates the membership database using the above model, then calls
WebSecurity.InitializeDatabaseConnection which verifies that the
underlying tables are in place and marks initialization as complete (for
the application's lifetime)
- \Controllers\AccountController.cs makes heavy use of OAuthWebSecurity (for OAuth account registration / login / management) and WebSecurity. WebSecurity provides account management services for ASP.NET MVC (and Web Pages)
WebSecurity can work with any ExtendedMembershipProvider. There's one
in the box (SimpleMembershipProvider) but you can write your own. Since
a standard MembershipProvider is not an ExtendedMembershipProvider,
WebSecurity will throw exceptions if the default membership provider is a
MembershipProvider rather than an ExtendedMembershipProvider.
Practical example:
- Create a new ASP.NET MVC 4 application using the Internet application template
- Install the Microsoft ASP.NET Universal Providers for LocalDB NuGet package
- Run the application, click on Register, add a username and password, and click submit
You'll get the following execption in AccountController.cs::Register:
To call this method, the "Membership.Provider" property must be an instance of "ExtendedMembershipProvider".
This occurs because the ASP.NET Universal Providers packages include a
web.config transform that will update your web.config to add the
Universal Provider configuration I showed in the Basic template example
above. When WebSecurity tries to use the configured ASP.NET Membership
Provider, it checks if it can be cast to an ExtendedMembershipProvider
before doing anything else.
So, what do you do?
Options:
If you want to use the new AccountController, you'll either need to
use the SimpleMembershipProvider or another valid
ExtendedMembershipProvider. This is pretty straightforward.
If you want to use an existing ASP.NET Membership Provider in ASP.NET
MVC 4, you can't use the new AccountController. You can do a few
things:
- Replace the AccountController.cs and AccountModels.cs in an
ASP.NET MVC 4 Internet project with one from an ASP.NET MVC 3
application (you of course won't have OAuth support). Then, if you want,
you can go through and remove other things that were built around
SimpleMembership - the OAuth partial view, the NuGet packages (e.g. the
DotNetOpenAuthAuth package, etc.)
- Use an ASP.NET MVC 4 Internet application template and
add in a Universal Providers NuGet package. Then copy in the
AccountController and AccountModel classes.
- Create an ASP.NET MVC 3 project and upgrade it to ASP.NET MVC 4 using the steps shown in the ASP.NET MVC 4 release notes.
None of these are particularly elegant or simple. Maybe we (or just
me?) can do something to make this simpler - perhaps a NuGet package.
However, this should be an edge case - hopefully the cases where you'd
need to create a new ASP.NET but use legacy ASP.NET Membership Providers
should be pretty rare. Please let me (or,
preferably the team) know if that's an incorrect assumption.
Membership in the ASP.NET 4.5 project template
ASP.NET 4.5 Web Forms took a different approach which builds off
ASP.NET Membership. Instead of using the WebMatrix security assemblies,
Web Forms uses Microsoft.AspNet.Membership.OpenAuth assembly. I'm no
expert on this, but from a bit of time in ILSpy and Visual Studio's
(very pretty) dependency graphs, this uses a Membership Adapter to save
OAuth data into an EF managed database while still running on top of
ASP.NET Membership.
Note: There may be a way to use this in ASP.NET MVC 4, although it would probably take some plumbing work to hook it up.
How does this fit in with Universal Providers (System.Web.Providers)?
Just to summarize:
- Universal Providers are intended for cases where you have an
existing ASP.NET Membership Provider and you want to use it with another
SQL Server database backend (other than SQL Server). It doesn't require
agents to handle expired session cleanup and other background tasks, it
piggybacks these tasks on other calls.
- Universal Providers are not really, strictly speaking, universal -
at least to my way of thinking. They only work with databases in the
SQL Server family.
- Universal Providers do not work with Simple Membership.
- The Universal Providers packages include some web config transforms which you would normally want when you're using them.
What about the Web Site Administration Tool?
Visual Studio includes tooling to launch the
Web Site Administration Tool (WSAT) to configure users and roles in your application.
WSAT is built to work with ASP.NET Membership, and is not compatible with Simple Membership. There are two main options there:
- Use the WebSecurity and OAuthWebSecurity API to manage the users and roles
- Create a web admin using the above APIs
- Since SimpleMembership runs on top of your database, you can
update your users as you would any other data - via EF or even in direct
database edits (in development, of course)