Accessing the ASP.NET Authentication, Profile and Role Service in Silverlight

In ASP.NET 2.0, we introduced a very powerful set of application services in ASP.NET (Membership, Roles and profile).  In 3.5 we created a client library for accessing them from Ajax and .NET Clients and exposed them via WCF web services.    For more information on the base level ASP.NET appservices that this walk through is based on, please see Stefan Schackow excellent book Professional ASP.NET 2.0 Security, Membership, and Role Management.

In this tutorial I will walk you through how to access the WCF application services from a directly from the Silverlight client.  This works super well if you have a site that is already using the ASP.NET application services and you just need to access them from a Silverlight client.    (Special thanks to Helen for a good chunk of this implantation)

Here is what I plan to show:

1. Login\Logout
2. Save personalization settings
3. Enable custom UI based on a user's role (for example, manager or employee)
4. A custom log-in control to make the UI a bit cleaner

image

You can download the completed sample solution

 

 

Part I: Login\Logout

In VS, do File\New select the Silverlight solution.  Let's call it "ApplicationServicesDemo".

image

We will need both the client side Silverlight project and the ASP.NET serverside project. 

image

 

Let's configure our system with the test users.  To do this we will use the ASP.NET Configuration Manager.  In VS, under the Website menu, select "ASP.NET Configuration". Use this application to add a couple of users.  I created two employees:

ID:manager
password:manager!
and
ID:employee
password:employee!

image

 

To expose the ASP.NET Authentication system, let's add a new WCF service.  Because we are just going to point this at the default one that ships with ASP.NET, we don't need any code behind, so the easiest thing to do is to add a new Text File.  In the ASP.NET website, Add New Item, select Text File  and call it "AuthenticationService.svc"

image

Add this one line as the contents of the file.  This wires it up to the implementation that ships as part of ASP.NET.

 <%@ ServiceHost Language="C#" Service="System.Web.ApplicationServices.AuthenticationService" %>

Now in Web.config, we need to add the WCF magic to turn the service on.

   <system.serviceModel>
    <services>
      <!-- this enables the WCF AuthenticationService endpoint -->
      <service name="System.Web.ApplicationServices.AuthenticationService"
               behaviorConfiguration="AuthenticationServiceTypeBehaviors">
        <endpoint contract="System.Web.ApplicationServices.AuthenticationService"
                  binding="basicHttpBinding" bindingConfiguration="userHttp"
                  bindingNamespace="https://asp.net/ApplicationServices/v200"/>
      </service>

    </services>
    <bindings>
      <basicHttpBinding>
        <binding name="userHttp">
          <!-- this is for demo only. Https/Transport security is recommended -->
          <security mode="None"/>
        </binding>
      </basicHttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="AuthenticationServiceTypeBehaviors">
          <serviceMetadata httpGetEnabled="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <!-- this is needed since this service is only supported with HTTP protocol -->
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
  </system.serviceModel>

Now, still in Web.config, we need to enable forms authentication.  Under the <system.web> change the authentication mode from "Windows" to "Forms".

 <authentication mode="Forms" />

 

One last change to web.config, we need to enable authentication to be exposed via the web service.This is done by adding a System.Web.Extensions section.

   <system.web.extensions>
    <scripting>
      <webServices>
        <authenticationService enabled="true" requireSSL="false"/>
      </webServices>
    </scripting>
  </system.web.extensions>

 

Now, to consume this authentication service in Silverlight, let's open the page.xaml file and add some initial UI. Just buttons to log "employee" and "manager"  in and a textblock to show some status. 

     <Grid x:Name="LayoutRoot" Background="White">
        <StackPanel>
            <Button x:Name="employeeLogIn" 
                    Width="100" Height="50" 
                    Content="Log In Employee" 
                    Click="employeeLogIn_Click"></Button>
            <Button x:Name="managerLogIn" 
                    Width="100" Height="50" 
                    Content="Log In Manager" 
                    Click="managerLogIn_Click"></Button>
            <TextBlock x:Name="statusText"></TextBlock>
        </StackPanel>
    </Grid>

Now, let's add a reference to the service we just created

Right click on the Silverlight project and select Add Service Reference

image

Click Discover and set the namespace to "AuthenticationService"

image

If you get an error at this point, it is likely something wrong with your AuthenticationService.svc or the web config, go back and double check those. 

 

Now, let's write a little code to call that service to log us in.  First add the right using statement

 using ApplicationServicesDemo.AuthenticationServices;

Then, in employeeLogIn_Click method write the code to call the service to log the employee in.  For now, we will hard code the name in password, but by the end we will be prompting the user to get this data.

First we create a the web services client class, then we call the login method asynchronously.  Remember all network calls in Silverlight are async, otherwise we'd lock up the whole browser.  Finally we sign up for the callback.

 private void employeeLogIn_Click(object sender, RoutedEventArgs e)
{
    AuthenticationServiceClient client = new AuthenticationServiceClient();
    client.LoginAsync("employee", "employee!", "", true, "employee");
    client.LoginCompleted += new EventHandler<LoginCompletedEventArgs>(client_LoginCompleted);
}

In the callback, for now, let's just set our status.

 void client_LoginCompleted(object sender, LoginCompletedEventArgs e)
{
    if (e.Error != null) statusText.Text = e.Error.ToString();
    else statusText.Text = e.UserState + " logged In result:" + e.Result;

 }

Run it!  You should see a good status.  Try changing the password and ID, and see the status change to false.  It is working.

image

Now do the same thing for manager and you are set!

 

 private void managerLogIn_Click(object sender, RoutedEventArgs e)
{
    AuthenticationServiceClient client = new AuthenticationServiceClient();
    client.LoginCompleted += new EventHandler<LoginCompletedEventArgs>(client_LoginCompleted);
    client.LoginAsync("manager", "manager!", "", true, "manager");
}

Part 2: Save Personalization Settings

In this part I will show how to leverage the ASP.NET profile system to store and retrieve data tied to a particular user.  The beautiful thing about this is there is no explicit database configuration required.

First, let's go back to the ASP.NET server project and add the ProfileService.svc.  The easiest way to do this might be to copy the AuthenticationService.svc change the file name and then change the contents as show below. 

 <%@ ServiceHost Language="C#" Service="System.Web.ApplicationServices.ProfileService" %>

Now, we need to enable the service via WCF configuration in web.config.  This looks just like the config for the prfofile service.  In the system.serviceModel\services section add a new node.

       <!-- this enables the WCF ProfileService endpoint -->
      <service name="System.Web.ApplicationServices.ProfileService"
               behaviorConfiguration="ProfileServiceTypeBehaviors">
        <endpoint contract="System.Web.ApplicationServices.ProfileService"
                  binding="basicHttpBinding" bindingConfiguration="userHttp"
                  bindingNamespace="https://asp.net/ApplicationServices/v200"/>
      </service>

and in the system.serviceModel\behaviors\serviceBehaviors add a new node

         <behavior name="ProfileServiceTypeBehaviors">
          <serviceMetadata httpGetEnabled="true"/>
        </behavior>

Now we need to configure the profile system.  Again, in web.config add a section listing all the profile properties.  In the System.web section, add the following node.

     <profile>
      <properties>
        <add name="Color" type="string" defaultValue="Red" />
      </properties>
    </profile>

 

Now we need to enable profile service to be accessed from the webservice. To do this, add the following node to the system.web.extensions\scripting\webservices section.

         <profileService enabled="true" 
                        readAccessProperties="Color" 
                        writeAccessProperties="Color"/>

Now, let's go to the Silverlight client and add a reference to this service.  This is the same as we did authentication service. Add Service Reference, Discover and select the profileService. 

image

Now, we need to add a little UI to the page.xaml in order to give us a way to view and edit the personalized setting.

             <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
                <TextBlock>Favorite Color: </TextBlock>
                <TextBox x:Name="colorNameBox" Width="100" Height="25"></TextBox>
                <Button x:Name="submitButton" Width="50" Height="25" 
                        Content="submit" Click="submitButton_Click">
                </Button>
            </StackPanel>

Now, let's extend loginComplete to retrieve all the profile properties for the user that just logged in.   Again, we need to do that asynchronously so we don't block the browser.

 void client_LoginCompleted(object sender, LoginCompletedEventArgs e)
{
    if (e.Error != null) statusText.Text = e.Error.ToString();
    else
    {
        statusText.Text = e.UserState + " logged In result:" + e.Result;
        ProfileServiceClient client = new ProfileServiceClient();
        client.GetAllPropertiesForCurrentUserAsync(false);
        client.GetAllPropertiesForCurrentUserCompleted += new EventHandler<GetAllPropertiesForCurrentUserCompletedEventArgs>(client_GetAllPropertiesForCurrentUserCompleted);
    }
}

When we get the property values back, we just set the LayoutRoot to have that background.

 void client_GetAllPropertiesForCurrentUserCompleted(object sender, GetAllPropertiesForCurrentUserCompletedEventArgs e)
{
    if (e.Error == null)
    {
        colorNameBox.Text = e.Result["Color"];
        ChangeBackgroundColor(e.Result["Color"]);
    }
}
 private void ChangeBackgroundColor(string colorName)
{
    SolidColorBrush brush = new SolidColorBrush();
    switch (colorName.ToLower())
    {
        case "black":
            brush.Color = Colors.Black;
            break;
        case "blue":
            brush.Color = Colors.Blue;
            break;
        case "brown":
            brush.Color = Colors.Brown;
            break;
        case "green":
            brush.Color = Colors.Green;
            break;
        case "orange":
            brush.Color = Colors.Orange;
            break;
        case "purple":
            brush.Color = Colors.Purple;
            break;
        case "yellow":
            brush.Color = Colors.Yellow;
            break;
        case "red":
            brush.Color = Colors.Red;
            break;
        case "white":
        default:
            brush.Color = Colors.White;
            break;
    }
    LayoutRoot.Background = brush;
}

Finally, when the submit button is pressed we need to set the value on the server.. for completeness, I show waiting until the result comes back from the server before setting the background locally. 

 private void submitButton_Click(object sender, RoutedEventArgs e)
{
    ProfileServiceClient client = new ProfileServiceClient();
    Dictionary<string, object> properites = new Dictionary<string,object>();
    properites.Add("Color",colorNameBox.Text);
    client.SetPropertiesForCurrentUserAsync(properites, false, properites);
    client.SetPropertiesForCurrentUserCompleted += new EventHandler<SetPropertiesForCurrentUserCompletedEventArgs>(client_SetPropertiesForCurrentUserCompleted);
}

void client_SetPropertiesForCurrentUserCompleted(object sender, SetPropertiesForCurrentUserCompletedEventArgs e)
{
    Dictionary<string, object> properites = e.UserState as Dictionary<string, object>;
    ChangeBackgroundColor((string)properites["Color"]);
}

 

The end result looks good!  Notice the employee and the manager can each have different values for color. 

image

image

 

Part 3. Enable Custom UI Based on a User's Role

In this section, we want to customize the UI to display differently based on the users role. 

To start off, let's go back to the websites management tool (WebSite\ASP.NET Configuration) and setup a "Management" role and add our "manager" user in that role.

image

image

Now we follow the same three step pattern as all the other services. 

First, we add the service, this time called RoleService.svc with the following contents

 <%@ ServiceHost Language="C#" Service="System.Web.ApplicationServices.RoleService" %>

Then add enable this service via WCF in web.config:

       <!-- this enables the WCF RoleService endpoint -->
      <service name="System.Web.ApplicationServices.RoleService"
               behaviorConfiguration="RoleServiceTypeBehaviors">
        <endpoint contract="System.Web.ApplicationServices.RoleService"
                  binding="basicHttpBinding" bindingConfiguration="userHttp"
                  bindingNamespace="https://asp.net/ApplicationServices/v200"/>
      </service>

 and
         <behavior name="RoleServiceTypeBehaviors">
          <serviceMetadata httpGetEnabled="true"/>
        </behavior>
      </serviceBehaviors>

Then we need to enable the roles service for this site.  In web.config, under system.web add:

 <roleManager enabled="true"/>

Final change to web.config, we expose it via web services in web.config under System.web.extensions\scripting\webServices add:

         <roleService enabled="true"/>

 

Now, we just need to consume this on the client.  Add Service Reference\Discover, select the role service

image

Now, let's add some UI tweaks.  In this case I am going to add an image that will change based on the role of the user logged in.   So to do that, I add a image tag to the page.xaml

             <Image x:Name="roleImage" Source="notLoggedIn.jpg" Width="200"></Image>

Then, I need to go in and add to what happens with the user logs in... We need to go and see what roles they are in.  So we add the following code to client_LoginCompleted(). 

 if (e.Result == true) { // if log in successful
    RoleServiceClient roleClient = new RoleServiceClient();
    roleClient.GetRolesForCurrentUserAsync();
    roleClient.GetRolesForCurrentUserCompleted += new EventHandler<GetRolesForCurrentUserCompletedEventArgs>(roleClient_GetRolesForCurrentUserCompleted);
}

and when the callback comes back, we chose the right image based on the roles of the user that logged in.

 void roleClient_GetRolesForCurrentUserCompleted(object sender, GetRolesForCurrentUserCompletedEventArgs e)
{
    if (e.Result.Contains("Management"))
    {
        roleImage.Source = new BitmapImage(new Uri("bossRole.jpg", UriKind.Relative));
    }
    else
    {
        roleImage.Source = new BitmapImage(new Uri("employee.jpg", UriKind.Relative));
    }
}
  

Now you can run it and see the results!

 

Not logged in

image

Management role:

image

Logged in, but not in a management role:

image

 

Part 4. A Custom log-in control

A developer on my team built a very cool little log in control that makes it easier to log in.  So let's replace the lame test buttons we have been using with this new log-in control.

Add the LoginControl project to your solution

image

Add a project reference from the silverlight project to the LogIn controls project

image

Add the xmlns:my tag for the login control

image

Then, remove the manager login and employee login buttons and replace them with

             <my:Login x:Name="loginControl" 
                      LoginClick="loginControl_LoginClick"
                      LogoutClick="loginControl_LogoutClick"
                      ></my:Login>

The implementing of loginClick is very easy.  It uses the login_Complete we have already written above.

 private void loginControl_LoginClick(object sender, RoutedEventArgs e)
{
    AuthenticationServiceClient client = new AuthenticationServiceClient();
    client.LoginCompleted += new EventHandler<LoginCompletedEventArgs>(client_LoginCompleted);
    client.LoginAsync(loginControl.UserName, loginControl.Password, "", true, loginControl.UserName);
}

Notice we also add logout for completion. 

 private void Login_LogoutClick(object sender, RoutedEventArgs e)
{
    ServiceReference1.AuthenticationServiceClient client = new ServiceReference1.AuthenticationServiceClient();
    client.LogoutAsync();
    client.LogoutCompleted += new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(client_LogoutCompleted);
}
 void client_LogoutCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
    loginControl.IsLoggedIn = false;
    statusText.Text = "Logged out";
    ChangeBackgroundColor("white");
    roleImage.Source = new BitmapImage(new Uri("notLoggedIn.jpg", UriKind.Relative));
    colorNameBox.Text = "";
    loginControl.ClearPassword();
}

 

Now we are done!  you can log in and out as any user.  have fun!

 

image

image

 

You can download the completed sample solution