Introducing System.Web.Providers - ASP.NET Universal Providers for Session, Membership, Roles and User Profile on SQL Compact and SQL Azure
UPDATE #2: Note that the NuGet package has changed its name from System.Web.Providers to Microsoft.AspNet.Providers.
UPDATE: Note that in MVC 4 and ASP.NET 4 and 4.5 the default hash is now HMACSHA256
I always like to remind folks of the equation ASP.NET > (ASP.NET MVC + ASP.NET WebForms). The whole "base of the pyramid" of ASP.NET has lots of things you can use in you applications. Some of these useful bits are Session State, Membership (Users), Roles, Profile data and the provider model that underlies it. Using these isn't for everyone but they are very useful for most applications, even ones as large as the ASP.NET site itself.
Today the Web Platform and Tools team (WPT) is releasing an Alpha of the ASP.NET Universal Providers that will extend Session, Membership, Roles and Profile support to SQL Compact Edition and SQL Azure. Other than supporting additional storage options, the providers work like the existing SQL-based providers.
Today these are being released via a NuGet Package, but it's very likely that these Universal Providers will be the default in the next version of ASP.NET.
To enable the providers, the NuGet package adds configuration entries in the web.config file. The configuration for these providers is the same as the existing SqlMembershipProvider
class, but the type
parameter is set to the type of the new providers, as shown in the following table:
SQL Provider Types | Equivalent Type for Universal Providers |
---|---|
System.Web.Security.SqlMembershipProvider |
System.Web.Providers.DefaultMembershipProvider |
System.Web.Profile.SqlProfileProvider |
System.Web.Providers.DefaultProfileProvider |
System.Web.Security.SqlRoleProvider |
System.Web.Providers.DefaultRoleProvider |
(Built in provider) |
System.Web.Providers.DefaultSessionStateProvider |
If you install these, the NuGet package will swap your defaultProviders in your web.config. You can certainly pick and choose the settings for each as well. Here we're changing Profile, Membership, RoleManager and SessionState. The latter is nice as it better allows your session-state-using Azure apps to scale with SQL Azure as the backend storage.
Using these Universal "Default Profile Providers" means all you have to do is set the right connection string and your applications that use these services will work with SQL Server (plus Express), SQL Server Compact and SQL Azure with no code changes from you.
enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false"
maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10"
applicationName="/" />
Selecting a Data Store
By default, the NuGet package sets the connection string to use a SQL Server Express database (wrapped here for readability):
"Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\aspnetdb.mdf;
Initial Catalog=aspnet;Integrated Security=True;
User Instance=True;MultipleActiveResultSets=True" providerName="System.Data.SqlClient"
If you want to use SQL Server Compact, change the connection string as shown in the following example:
If you want to use SQL Azure, change the connection string like this example (wrapped for readability):
providerName="System.Data.SqlClient"/>
Even though this release is primarily about extending support to all versions of SQL Server, I realize that y'all might not even know about what these things do, so I thought I'd spend a little time explaining. I notice also that there's some confusion on StackOverflow and other sites on how to use Membership and Profile and the like on ASP.NET MVC, so I'll use that for the examples.
Example of Membership, Roles and Profile in ASP.NET MVC (with the Universal Providers)
I'll fire up VS and File | New Project on a new ASP.NET MVC 3 Project. Then I'll right click on References and select Add | Library Reference. The NuGet package id is "Microsoft.AspNet.Providers." After this package is installed, I can also install SQL Compact Edition via NuGet if I like and set the connection string to SQL Compact as shown above.
Remember, this is a very functional Alpha, but there may be bugs (report them!) so it might be updated a few times before the next version of ASP.NET is released.
First, I'll run my app and click Register and make a new user named "Scott."
Adding Roles to a User
Next, from Visual Studio Project menu, I visit ASP.NET Configuration. (I could also write my own admin section and do this programmatically, if I liked).
Then from the Security tab, under Roles, I'll create a new Role:
Then I'll find the Scott User and add him to the Administrator Role:
I want to show something if a user is an Administrator. I'll add a little chunk of code to the default website's _LogOnPartial. cshtml so we'll see [Administrator] next to their name of they are one.
I'll add a small line where I ask "User.IsInRole()" like this:
@if(Request.IsAuthenticated) {
Welcome @User.Identity.Name
@(User.IsInRole("Administrator") ? "(Administrator)" : String.Empty)
[ @Html.ActionLink("Log Off", "LogOff", "Account") ]
}
else {
@:[ @Html.ActionLink("Log On", "LogOn", "Account") ]
}
@if (ViewBag.Profile != null) {
Hey, your birthday is @ViewBag.Profile.Birthdate.ToString("d")! Congrats.
}
So now I have some Roles I can assign to users and check against. I can set whatever roles I want, like Bronze, Silver, Gold, etc.
Adding Profile Information to Users
Let's say I want Users to have a Birthday and I want that to be part of the User Profile. I can just use the Profile object and ask for things via string like this:
DateTime? birthday2 = HttpContext.Profile["Birthdate"] as DateTime?; //alternative syntax
However, perhaps I'd rather have a stronger typed syntax for my profile.
NOTE: I've already brought up the issue that User hangs off Controller in MVC 3 but Profile is simply missing. Perhaps that will be fixed in MVC 4. I believe it was a oversight. You shouldn't be digging around in HttpContext if you want your code testable
I'll make a small CustomProfile object like this that extends ProfileBase:
public class MyCustomProfile : ProfileBase
{
public DateTime? Birthdate {
get { return this["Birthdate"] as DateTime?; }
set { this["Birthdate"] = value; }
}
}
Alternatively, I could put the "getting" of the profile in the custom class in a static, or I could use Dependency Injection. It depends on how you want to get to it.
public static MyCustomProfile GetUserProfile(string username)
{
return Create(username) as MyCustomProfile;
}
public static MyCustomProfile GetUserProfile()
{
return Create(Membership.GetUser().UserName) as MyCustomProfile;
}
Then in web.config, I'll update the
...
For older website projects, I can add properties in the web.config like this. There are attributes I can use like SettingsAllowAnonymous for custom derive classes in code.
..
Or I can even use IIS7's administration interface to edit the profile details in the web.config. You can have all kinds of profile properties, group them and it's all handled for you.
If I like, I can ask for the User's Profile (I've got it set for only authenticated users), and set a default as well. I save explicitly, but there is an auto-save option also.
if (User.Identity.IsAuthenticated)
{
var customProfile = HttpContext.Profile as MyCustomProfile;
DateTime? birthday = customProfile.Birthdate; //Because I made a strongly typed derived class
if (!birthday.HasValue) {
customProfile.Birthdate = new DateTime(1965, 1, 14); //cause that's everyone's birthday, right?
customProfile.Save(); //or set autosave if you like.
}
ViewBag.Profile = customProfile; //So the View can use it
}
At the very end I put the Profile in the ViewBag so it can be accessed from the View. I could also have added just the things I want to a larger ViewModel. Then I can use it later with some sanity checks:
@if (ViewBag.Profile != null) {
@:Hey, your birthday is @ViewBag.Profile.Birthdate.ToString("d")! Congrats.
}
Expect to see more cloud-ready things like this in the coming months that'll better position your apps, new and old, to move up to Azure or even down to SQL Compact. Hope this helps. Enjoy.
Related Links
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
Is this code going to be open source?
sorry in advance for the double post
Shawn - Lemme ask.
fschwiet - Sure, just download the NuGet manually and unzip it. It's right there.
Are there plans to support other providers and databases? EF Code First support?
what I am thinking is may be it is time to have 'provider crypt' for custom encrypt per-site.
Current asp.net way doing it require administrator.
how about on share hosting? most the time we do our implementation of the provider.
And I am think this happen on desktop environment too.
Like on EF, we must inherit the context to do de-crypt connection string or add de-crypt connection-string on context each time we instance the context.
why we can have provider-like way on framework? (crypt, logger, trace, (may be notify provider(just like mobile -- implementation can be for web(form/mvc) and desktop(winform/xaml)) for example) it is just an idea, for most this we common thing we did.
so, team EF, asp.net or other teams can use it and we developer can consume and focus on our main task.
Keep amaze us :) thanks.
I'll also echo Domenic's point - I don't even use providers anymore b/c they are a pain for testability.
To that end, even Controller.User (IIdentity) is a mixed bag as it can easily stymie testability of the controller action. I'd rather see an IIdentityValueProvider similar to the HeaderValueProvider created by Donn Felker. Perhaps to that end, a MembershipUserValueProvider and a ProfileValueProvider would be a better choice than Controller.MembershipUser and Controller.Profile properties.
builder.Register(c => HttpContext.Current.User.Identity).As<IIdentity>().HttpRequestScoped();
Getting back to the membership provider... I see they've duplicated the internal class SecUtility for parameter validatoin (and related string resources). Did they put the source up anywhere? Its kind of sad anyone wanting to do a membership provider with matching parameter validation has to redo that code.
Thank you,
Do you know how they got around the issue of SQL Azure not being able to do timed jobs, and thus, it couldn't self-invalidate expired sessions? I've implemented it with two other suggested methods using TableStorage: http://www.intertech.com/Blog/post/Session-State-in-Windows-Azure.aspx and paying extra to use a worker role to call an SP in SQLAzure: http://blogs.msdn.com/b/sqlazure/archive/2010/08/04/10046103.aspx but both felt a little hack-ish.
I'd love to find out how they did it and if it 'just works' like it does in SQL Server.
Thanks!
--Aaron
Ignat - You mean, like taking an existing site running on IIS and Compact and upgrading it to Azure?
redney = Um, ok, an expression, then.
So I'm guessing that they're doing something to clear expired sessions every time any user requests a session or membership object? Any performance implications of all that? I've seen others suggest custom writing a clean up call every page load (in a site Master page) to avoid paying for a worker role instance, but that's a real hack (see 4th comment down: http://blogs.msdn.com/b/sqlazure/archive/2010/08/04/10046103.aspx)
Also, is there a quick way to set up those table on Azure like there is on a full SQL Server ala aspnet_regsql.exe? I've been running that local on Express and using SQL Azure Migration Wizard (http://sqlazuremw.codeplex.com/) to move it to the cloud.
Same from Express to Azure ( with pure Tables, please , not SqlAzure...)
--Aaron
A null was returned after calling the 'get_ProviderFactory' method on a store provider instance of type 'System.Data.Odbc.OdbcConnection'. The store provider might not be functioning correctly.
Which I suspect is because I'm using Web Forms and not MVC. Any help would be appreciated.
To answer to the question about scripts above, if you specify a new db name in the connection string, the both the database and the membership tables will be created for you.
Level 1: Auto authenticate using active directory or display a login id and password for a database authentication?
-Hem
We plan to have an update in the future (Beta?) that will also support Azure Storage and the File System providing even more flexibility for our customers.
Next some of the feedback that we receive about the existing ASP.NET providers is that they are hard to use with you own database schema if you want to control the profile table for example. We are working to address this as well but the main goal of this release was to quickly provide a set of providers that support our existing infrastructure and work well in Azure.
----------------------
Configuration Error
Description: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately.
Parser Error Message: This method cannot be called during the application's pre-start initialization stage.
Source Error:
Line 31: <clear/>
Line 32: <add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="ApplicationServices" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/"/>
Line 33: <add name="DefaultMembershipProvider" type="System.Web.Providers.DefaultMembershipProvider" connectionStringName="DefaultConnection" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/"/>
Line 34: </providers>
Line 35: </membership>
Source File: c:\xxx\web.config Line: 33
Both may have group/role membership, if configured, and I'd like to have common code to query this.
(I won't even mention the possiblility of on-site deployment models with AD Windows authentication)
Could not load type 'System.Web.Providers.DefaultMembershipProvider' from assembly 'System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
Greate Job.
config.Add("connectionStringName", "DefaultConnection");
member.Initialize("DefaultMembershipProvider",config);
Please, please, please give this some consideration, having profile items associated against the user shouldn't take such a massive amount of code to STILL end up putting them in the same constrained, non queryable, state that they have been in for sooooo long...
thanks for listening to yet another gripe....
btw, loving .net MVC3, nuGet and everything that has come out recently and understand you can't cover everything (like Profile object still being in HttpContext !!!)
I would really like to use these to access the underlying data source but have yet to do so.
I have been having difficulty using ObjectContext and DbContext and would like to know if there is some way to have a context made without having to alter my connection string.
Thanks.
HI,
The session provider of ASP.NET Univarsal Providers is interesting approach, but a performance decreases to remove its expired sessions.
We made custom session provider inherit from System.Web.Providers.DefaultSessionStateProvider, and override some method.
The custom session provider(we made!) added index to an 'Expires' column, and remove expired sessions collectively in ExecuteNonQuery. Because of that, we improve a performance of the custom session provider.
Could you improve the official Universal Provider by refering to our session provider code?
see also: http://tinyurl.com/3tbgys5
Any suggestion for a fix?
The error message is: "Unable to find the requested .Net Framework Data Provider. It may not be installed. "
I had to set the "HashAlgorithmType Attribute" in web.config like this:
<membership hashAlgorithmType="SHA1">
<providers>....</providers>
</membership>
It also works with hashAlgorithmType="HMACSHA256".
More tips on: http://www.codeproject.com/KB/aspnet/LoginControlError.aspx
Just a note that in your examples of changing the connection strings, you use the names Sql_CE and Sql_Azure but if you want them to work with the default names added by the System.Web.Providers package, you should keep the connection string name as DefaultConnection and just alter the other conneciton string properties accordingly. Otherwise, you'd have to update the connectionStringName property of the provider entries to Sql_CE or Sql_Azure, etc. I know this is pretty obvious but might throw off some people.
Anyone else having this issue? And idea's how to fix it, workarround?
Error: "The database ...App_Data\ASPNETDB.MDF cannot be opened because it is version 661. This server supports version 612 and earlier. ..."
Any ideas how to get this to work on boxes that don't have the necessary dependencies?
Thanks.
http://anderly.com/2011/08/11/using-simplemembership-in-mvc3/comment-page-1/#comment-2797
"NOTE: I've already brought up the issue that User hangs off Controller in MVC 3 but Profile is simply missing. Perhaps that will be fixed in MVC 4. I believe it was a oversight. You shouldn't be digging around in HttpContext if you want your code testable"
Profile does indeed hang off of Controller in MVC 4.
But I have search all over the internet to resolve the situation below :
How to Handle the initialization when Simplemebership and context share the same database and the same context ? (code first)
I have a Database and I want to store the simplemembership information and my domain model tables too. But how to Initialize them at the same time and seeding them ?
This seems to be impossible
Thanks
Here is a link with full integration with SimpleMembershipProvider and SimpleRoleProvider.
http://blog.longle.net/2012/09/25/seeding-users-and-roles-with-mvc4-simplemembershipprovider-simpleroleprovider-ef5-codefirst-and-custom-user-properties/
How are the DB Tables created when a new user is created? I need this function for an application I wrote and I'd like to update it for the new framework.
Thanks.
Comments are closed.