Published articles on other web sites*

Published articles on other web sites*

Introducing System.Web.Providers - ASP.NET Universal Providers for Session, Membership, Roles and User Profile on SQL Compact and SQL Azure

Crazy random logo of evocative clipart combining the .NET Logo and some universal powerplugs into an unofficial logoI 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.
Install-Package System.Web.Providers

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.
<configuration>
<connectionStrings>
<add name="DefaultConnection" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\aspnet.mdf;Initial Catalog=aspnet;Integrated Security=True;User Instance=True;MultipleActiveResultSets=True" providerName="System.Data.SqlClient"/>
</connectionStrings>
<system.web>
<profile defaultProvider="DefaultProfileProvider" >
<providers>
<add name="DefaultProfileProvider" type="System.Web.Providers.DefaultProfileProvider" connectionStringName="DefaultConnection" applicationName="/"/>
</providers>
</profile>
<membership defaultProvider="DefaultMembershipProvider">
<providers>
<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="/" />
</providers>
</membership>
<roleManager defaultProvider="DefaultRoleProvider">
<providers>
<add name="DefaultRoleProvider" type="System.Web.Providers.DefaultRoleProvider" connectionStringName="DefaultConnection" applicationName="/" />
</providers>
</roleManager>
<sessionState mode="Custom" customProvider="DefaultSessionProvider">
<providers>
<add name="DefaultSessionProvider" type="System.Web.Providers.DefaultSessionStateProvider" connectionStringName="DefaultConnection" applicationName="/"/>
</providers>
</sessionState>
</system.web>
</configuration>


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:


<connectionStrings>
<add name="Sql_CE" connectionString="Data Source=|DataDirectory|\MyWebSite.sdf;"
providerName="System.Data.SqlServerCe.4.0"/>
</connectionStrings>


If you want to use SQL Azure, change the connection string like this example (wrapped for readability):


<connectionStrings>
<add name="Sql_Azure"
connectionString="data source=myDNSName;Initial Catalog=myDatabase;
User ID=myUserName;Password=myPassword;
Encrypt=true;Trusted_Connection=false;
MultipleActiveResultSets=True"
providerName="System.Data.SqlClient"/>
<connectionStrings>


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 "System.Web.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."


Making a new user


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).


Selecting ASP.NET Configuration site from the Project Menu


Then from the Security tab, under Roles, I'll create a new Role:


image


Then I'll find the Scott User and add him to the Administrator Role:


Giving the Scott User 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.


Welcome scott (Administrator)


I'll add a small line where I ask "User.IsInRole()" like this:


@if(Request.IsAuthenticated) {
<text>Welcome <strong>@User.Identity.Name</strong>
@(User.IsInRole("Administrator") ? "(Administrator)" : String.Empty)
[ @Html.ActionLink("Log Off", "LogOff", "Account") ]</text>
}
else {
@:[ @Html.ActionLink("Log On", "LogOn", "Account") ]
}

<br />
@if (ViewBag.Profile != null) {
<text>Hey, your birthday is @ViewBag.Profile.Birthdate.ToString("d")! Congrats.</text>
}


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 <profile> so the system know the derived Profile class I want used when I ask for one:


<profile defaultProvider="DefaultProfileProvider" inherits="MvcApplication46.Models.MyCustomProfile">
...


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.


..
</providers>
<properties>
<add allowAnonymous="false" defaultValue="" name="Birthdate" readOnly="false" serializeAs="String" type="System.DateTime" />
</properties>


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.


Using IIS Manager to edit User Profile schema


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.
© 2011 Scott Hanselman. All rights reserved.

No comments:

Post a Comment

Related Posts Plugin for WordPress, Blogger...