Adding User Management to your business application

RIA “Business application” template adds login screen and logic to use Membership and role management.You just need to alter your web.cofig and create aspnet database.The configuration has already been cover in previous articles. In this article i will create Userdata metatdata class (for MembershipUser) and display all users in the system in DataGrid.User can also update some flags like IsApproved and IsLocked from this screen.User can also select single or multiple users from grid and click on delete to delete users from Membership. Users is not allowed to use IsLocked flag to lock account however he can use this checkbox to unlock the locked accounts.In this article we will learn how we can bind data grid to our custom entity,Update the data on web service when ever user changes anything in client side.Add custom validation to ensure that user is allowed to set IsLocked=false; and is not allowed to set IsLocked = true;

1. To your web project add class UserData to Modles which will be used hold metadata for MembershipUser.

public class UserData
{  [Display(Order = 1, Name = "User Name")]
    [Key]
    public string UserName { get; set; }
    [ReadOnly(true)]
    [Display(Order = 2, Name = "Email")]
    public string Email { get; set; }
    [ReadOnly(true)]
    [Display(Order = 3, Name = "Created On")]
    public DateTime CreationDate { get; set; }
    [ReadOnly(true)]
    [Display(Order = 4, Name = "Last Login")]
    public DateTime LastLoginDate { get; set; }
    [ReadOnly(true)]
    [Display(Order = 5, Name = "Is Online")]
    public bool IsOnline { get; set; }
    [Display(Order = 6, Name = "Is Approved")]
    [Editable(true)]
    public bool IsApproved { get; set; }
    [Display(Order = 7, Name = "Is Locked")]
    [Editable(true)]
    [CustomValidation(typeof(Shared.Security.IsLockedOutValidator), "IsLockedOutValidValue")]
    public bool IsLockedOut { get; set; }
    [ReadOnly(true)]
    [Display(Order = 8, Name = "Last Lockout")]
    public DateTime LastLockoutDate
    {
        get;
        set;
    }
    [ReadOnly(true)]
    [Display(Order = 9, Name = "Last Activity")]
    public DateTime LastActivityDate { get; set; }
}

 Note that properties IsApproved and IsLocked are editable and rest of all properties are readonly.Also custom validator class IsLockedOutValidator is used to validate that user is allowed to set IsLocked = false; and is not allowed to set IsLocked =true;

2. Now again to you web project in shared folder add the class IsLockedOutValidator and name the file as IsLockedOutValidator.shared.cs.This will ensure that IsLockedOutValidator class is exposed to client for validations.Add following code to your class IsLockedOutValidator

public class IsLockedOutValidator
{
    public static ValidationResult IsLockedOutValidValue(bool value, ValidationContext context)
    {
        if (value == true)
        {
            return new ValidationResult("You can not lock user accounts ! Please use IsApproved to disable users.");
        }
        else
        {
            return ValidationResult.Success;
        }
    }
}

 3. Now you need to add following methods to you Domainservice class

    public IEnumerable<Models.Security.UserData> GetUsers()
    {
        List<Models.Security.UserData> Users = new List<Models.Security.UserData>();
        foreach (MembershipUser u in Membership.GetAllUsers())
        {
            Users.Add(new Models.Security.UserData
            {
                UserName = u.UserName,
                CreationDate = u.CreationDate,
                Email = u.Email,
                IsApproved = u.IsApproved,
                IsLockedOut = u.IsLockedOut,
                IsOnline = u.IsOnline,
                LastActivityDate = u.LastActivityDate,
                LastLockoutDate = u.LastLockoutDate,
                LastLoginDate = u.LastLoginDate,
            });
        }
        return Users;
    }
   public void DeleteUserData(UserData data)
    {
        Membership.DeleteUser(data.UserName);
    }
   public void UpdateUserData(UserData data)
    {
        //We need to only ensure that two properties are editable and values can be changed.
        MembershipUser u = Membership.GetUser(data.UserName);
        if (data.IsLockedOut == false && u.IsLockedOut)
            u.UnlockUser();
        u.IsApproved = data.IsApproved;
        Membership.UpdateUser(u);
    }

 Please note that if your entity name is UserData then method name used for addition,deletion,of update operation should be Update[EntityName],Delete[EntityName] i.e UpdateUserData,DeleteUserData .. etc.

OK to this stage we have create our class to expose users registered under membership. We have exposed methods to update and delete users from membership. We have also added our custom validation shared class.

Now let’s talk about silverlight client.

To your xaml file add namespace “xmlns:dataGrid=”clr-namespace:System.Windows.Controls;assembly = System.Windows.Controls.Data” (you need to add appropriate ref. Before doing this)

<dataGrid:DataGrid x:Name="grdUsers"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserSortColumns="True"
AutoGenerateColumns="True"
BorderBrush="Gray" />

To code behind file add

SecurityContext _context = new SecurityContext(); //Assuming this is your web service context

In constructor of code behind

    this.Loaded += new RoutedEventHandler(Configurations_Loaded);
    grdUsers.RowEditEnded += new EventHandler<DataGridRowEditEndedEventArgs>(grdUsers_RowEditEnded);
   void grdUsers_RowEditEnded(object sender, DataGridRowEditEndedEventArgs e)
    {
        _context.SubmitChanges();
    }
   void Configurations_Loaded(object sender, RoutedEventArgs e)
    {
        grdUsers.ItemsSource = _context.UserDatas;
        _context.Load(_context.GetUsersQuery());
    }

Add button “DeleteUser(s)” and on click event add following code

  private void DeleteUser(object sender, RoutedEventArgs e)
    {
        IEnumerable<UserData> list = grdUsers.SelectedItems.Cast<UserData>();
        List<UserData> alist = new List<UserData>();
        alist.AddRange(list);
        foreach (UserData u in alist)
        {
            _context.UserDatas.Remove(u);
        }
        _context.SubmitChanges();
    }

Note that we are removing or changing _context.UserDatas and just using _context.SubmitChnages();This is call the appropriate UpdateUserData or DeleteUserData on web service.

RIA WCF Configuration (Finally Resolved):

Finally after 3 days and nights i was able to find the solution for hosting RIA services on shared hosting environment and without changing anything on IIS. Yes it is possible…

Why : RIA framework dynamically creates WCF service (Domain services) and add endpoints to the service.It first check if endpoint does’nt exist then create it,and it checks for 3 endpoints (http,soap and binary).After creating end points it adds authentication schema to end points.It picks IIS authentication schema’s and tries to apply on end points and failed to apply.

If we could create desired end points in web.config RIA framework will not create or do anything with endpoints and it works succesfully ..

You just need to follow the simple steps mentioned below :

1. Add following code to you web.config to solve issue “This collection already contains an address with scheme http..”

<serviceHostingEnvironment aspNetCompatibilityEnabled="true">
  <baseAddressPrefixFilters>
    <add prefix="http://www.yoursite.com"/>
  </baseAddressPrefixFilters>
</serviceHostingEnvironment>

Note: Your service can be only accessed by url mentioned in above settings. As configured above you can’t access your service via http://yoursite.com.
You could also use factory code to host WCF (see below) to resolve this error however alone with that you need to create svc files for each domain service.

2.Add AspNetCompatibilityRequirementsMode attribute to your RIA Domain services classes
Eg .Attrubtes added to AuthenticationService class under services folder

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class AuthenticationService : AuthenticationBase<User> { }

RIA framework dynamically apply these attributes after creating end points.Since we are now bypassing endpoint creation , we need to manually apply these attributes.

3. For each RIA domain service add following to you configuration file.
Eg. Is shown for AuthenticationService and UserRegistrationService
Where SparkExams is my custom namespace.

<services>
  <service name="SparkExams.Web.AuthenticationService"
  behaviorConfiguration="RIAServiceBehavior">
    <endpoint address="" binding="wsHttpBinding"
    contract="SparkExams.Web.AuthenticationService" />
    <endpoint address="/soap"
    binding="basicHttpBinding"
    contract="SparkExams.Web.AuthenticationService" />
    <endpoint address="/binary"
    binding="customBinding"
    bindingConfiguration="BinaryHttpBinding"
    contract="SparkExams.Web.AuthenticationService" />
  </service>
  <service name="SparkExams.Web.UserRegistrationService"
  behaviorConfiguration="RIAServiceBehavior">
    <endpoint address=""
    binding="wsHttpBinding"
    contract="SparkExams.Web.UserRegistrationService" />
    <endpoint address="/soap"
    binding="basicHttpBinding"
    contract="SparkExams.Web.UserRegistrationService" />
    <endpoint address="/binary"
    binding="customBinding" bindingConfiguration="BinaryHttpBinding"
    contract="SparkExams.Web.UserRegistrationService" />
  </service>

Please note that RIA adds 3 endpoints and if any of these endpoints are missing from web.config it will throw “IIS specified authentication schemes ‘Basic, Anonymous’…” error.
Add following behaviours and bindings to your web.config

<behaviors>
  <serviceBehaviors>
    <behavior name="RIAServiceBehavior">
      <serviceMetadata httpGetEnabled="true" />
      <serviceDebug includeExceptionDetailInFaults="false" />
    </behavior>
  </serviceBehaviors>
</behaviors>
<bindings>
  <customBinding>
    <binding name="BinaryHttpBinding">
      <binaryMessageEncoding />
      <httpTransport />
    </binding>
  </customBinding>
</bindings>

Test you wcf end points using WCF client test tool (Test client for Windows Communication Foundation services.) WcfTestClient.exe : Go to VS 2008 Console and type WcfTestClient.exe.

Note that there is no need to host you service,or change IIS settings by ISP.
Update : While working on SL project i have noticed that SL is not able to recieve faults/exceptions thrown by RIA domain service.Please follow article to fix the issue “Silverlight Faults in SL 3.0 with RIA Domain Services – Fix 2 – Must read

Finally code and live demo application link are here..

Demo Application link
/rajnish/RiaTest/

 Custom service link
/rajnish/RiaTest/DeployTest-Web-services-CustomService.svc

Link to binay files for above links
/rajnish/uploads/code/RiaTest/Binary.zip

//Source code of application (Web.config has been changed in binary.zip)
/rajnish/uploads/code/RiaTest/Source.zip

If you are having any problem related to hosting Sl,copy binay.zip contents to your virtual directory on web server and modify web.config (bottom)
add prefix=”/rajnish/

If you have any problem with your code , then please download the binary.zip extract the contents and create virtual directory say RiaTest on your domain and copy the contents of Binary.zip (files like Default.aspx,DeployTestTestPage.aspx etc) to your web server.Change web.config “prefix section as mentioned above.Eg. on my webserver www.rajneeshnoonia.com i have created RiaTest virtual directory and copyied the contents of binary.zip into it.first step is to test your web service with URL like /rajnish/RiaTest/DeployTest-Web-services-CustomService.svc. if web service is ok you can launch your silverlight application.

Note: the site will not work if you try to launch with /rajnish/… rather it will work if you try /rajnish/

Configure .Net RIA Service – hosted on shared domain

1.Mark “copy to local” these 3 assembly in your web project:
1) System.Web.DomainService
2) System.Web.Ria
3) System.ComponentModel.DataAnnotations

This will deploy above mentioned assemblies in you bin folder

Test your web service via like
http://www.yoursite.com/virDir/namespace-Web-AuthenticationService.svc
where namespace.Web.AuthenticationService is your RIA domain service.
Internally at runtime RIA replaces . with –
Here you will get error as “This collection already contains an address with scheme http. There can be at most one address per scheme in this collection.”

2.Add following code to your web.config
<serviceHostingEnvironment aspNetCompatibilityEnabled=”true”>
<baseAddressPrefixFilters>
<add prefix=”http://www.yoursite.com”/&gt;
</baseAddressPrefixFilters>
</serviceHostingEnvironment>

You also need to change the service reference inside your Silverlight code to point to the service with an absolute address. Following is a sample that I used in my application:
3. open file Namespace.Web.g.cs in hidden folder Generated_Code under silverlight project.
Replace UriKind something like new Uri(“namespace-Web-UserRegistrationService.svc”, UriKind.Relative) to new Uri(“namespace-Web-UserRegistrationService.svc”, UriKind.Absolute)

Try to re run your service url you will now get error “IIS specified authentication schemes ‘Basic, Anonymous’, but the binding only supports specification of exactly one authentication scheme. Valid authentication schemes are Digest, Negotiate, NTLM, Basic, or Anonymous. Change the IIS settings so that only a single authentication scheme is used.”

4.Reduced your site’s authentication to “Anonymous” and everything worked great. The exact details on how you do this vary from ISP to ISP

Microsoft is still working on this and will soon (hopefully) fix the issue’s in further release of ria services.

Configuring RIA to use roleManager, membership and profile.

Create Silverlight Project “Business Navingation application” in Visual Studio 2010 Beta 2 and configure SQL express database to generate schema for rolemanager, membership and profile feature using command

Launch Visual Studio Command prompt (Run as administrator)
Type command
Aspnet_regsql.exe –E–S sqlinstance–A rmp
This will create sql express database aspnetdb

Visit links to see how to configure components mentioned above

Configure Role Manager
Configure Membership Manager
Configure profile Manager

Edit web.config file as
<configuration>
<!– Connection to database (also used by rolemanager,membership manager and profile manager) –>
<connectionStrings>
<add name=”SQLDataSource” connectionString=”Data Source= .SQLExpress;Initial Catalog=aspnetdb;Integrated Security=SSPI;” />
</connectionStrings>
……
<!– Configure Role Manager (See : http://msdn.microsoft.com/en-us/library/ms998314.aspx)–>
<roleManager enabled=”true” defaultProvider=”SqlDataRoleProvider”>
<providers> <clear />
<add name=”SqlDataRoleProvider”
type=”System.Web.Security.SqlRoleProvider”
connectionStringName=”SQLDataSource”
applicationName=” APPName ” />
</providers>
</roleManager>
<!– Configure Membership Manager (See : http://msdn.microsoft.com/en-us/library/ms998347.aspx)–>
<membership defaultProvider=”SqlDataMemberProvider” userIsOnlineTimeWindow=”1″>
<providers> <clear />
<add name=”SqlDataMemberProvider”
type=”System.Web.Security.SqlMembershipProvider”
connectionStringName=”SQLDataSource”
applicationName=” APPName “
enablePasswordRetrieval=”false”
enablePasswordReset=”true”
requiresQuestionAndAnswer=”true”
requiresUniqueEmail=”true”
passwordFormat=”Hashed” />
</providers>
</membership>

<!– Configure profile Manager (See : http://msdn.microsoft.com/en-us/library/014bec1k.aspx)–>
<profile defaultProvider=”SqlDataProfileProvider”>
<properties>
<add name=”FriendlyName”/>
</properties>
<providers> <clear />
<add name=”SqlDataProfileProvider” type=”System.Web.Profile.SqlProfileProvider”
connectionStringName=”SQLDataSource” applicationName=”APPName” />
</providers>
</profile>
……
</system.web>
…..

For more details please follow links mentioned in post