Using ASP.NET Membership in Silverlight

The Authentication support in RIA Services uses ASP.NET Membership, Roles, and Profile support by default. While this provides a lot of power, flexibility, and interoperability, it may also leave you wondering “How do I use ASP.NET Membership in Silverlight?” The answer (and I hope it doesn’t surprise you) is “Write a RIA Services DomainService.”

I want to start first by explaining the pattern I’ll use to create the service. Most developers do not start from a clean slate when writing an application. You’re likely to already have some mix of existing services, existing business logic, and existing data access layers. Not only is it easy to reuse these resources when writing a RIA Services application, it is encouraged. No need to rewrite something if it will continue to be useful in your new application. This approach shifts our challenge from figuring out how to rewrite a component to figuring out how to reuse it.

ASP.NET Membership falls squarely into this category. It encompasses business logic and data access in a useful and well-vetted API based on the MembershipUser type. Our first task in making Membership DomainService-friendly is to determine if we can represent the MembershipUser as an entity. There are two qualifications a type must have to be considered an entity. First, it must be public. Second, and often the trickier requirement when dealing with existing types, it must have a primary key marked with a KeyAttribute (from System.ComponentModel.DataAnnotations). MembershipUser is public and has a primary key (UserName and Email) so that is a good start. However, it is also a type we cannot modify. This prevents us from being able to apply the KeyAttribute or any other useful metadata.

There are typically two approaches to solve this problem. The first is to create a proxy type (there are a number of different names for this pattern; including DTOs and Views). An alternate solution is to provide metadata for the type using an approach apart from attributes and the CLR. There is a great sample here that specifies the key attribute using xml.

For this sample, we’ll follow the first approach and create a type similar to MembershipUser to expose from our DomainService. However, since we’re creating it solely for the service, we will be able to control the exact shape and metadata. The resulting type is MembershipServiceUser.

 public class MembershipServiceUser
{
    public string Comment { get; set; }
    [Editable(false)]
    public DateTime CreationDate { get; set; }
    [Key]
    [Editable(false, AllowInitialValue = true)]
    public string Email { get; set; }
    public bool IsApproved { get; set; }
    [Key]
    [Editable(false, AllowInitialValue = true)]
    public string UserName { get; set; }
    // ...

    public MembershipServiceUser() { }
    public MembershipServiceUser(MembershipUser user)
    {
        this.FromMembershipUser(user);
    }

    public void FromMembershipUser(MembershipUser user)
    {
        this.Comment = user.Comment;
        this.CreationDate = user.CreationDate;
        this.Email = user.Email;
        this.IsApproved = user.IsApproved;
        this.UserName = user.UserName;
        // ...
    }

    public MembershipUser ToMembershipUser()
    {
        MembershipUser user = Membership.GetUser(this.UserName);

        if (user.Comment != this.Comment) user.Comment = this.Comment;
        if (user.IsApproved != this.IsApproved) user.IsApproved = this.IsApproved;
        // ...

        return user;
    }
}

There are a few things to notice about the MembershipServiceUser as a proxy type. First, all the properties allow both get and set, but additional metadata has been added to the properties to define how they can be used. Second, there are conversion method to move from business logic type to domain service type and back. Finally, MembershipServiceUser only exposes the subset of the properties found on MembershipUser that we want to be available on the client.

Now that we have an entity type that can be exposed from a DomainService, we’re ready to look at making the Membership API available on the client as well. For this, we’ll create a MembershipService that extends the base DomainService type.

 [EnableClientAccess(RequiresSecureEndpoint = false
  /* This should be set to true before the application is deployed */)]
public class MembershipService : DomainService
{
    [RequiresRole("Administrator")]
    public IEnumerable<MembershipServiceUser> GetUsers()
    {
        return Membership.GetAllUsers().Cast<MembershipUser>().Select(
            u => new MembershipServiceUser(u));
    }

    [Invoke(HasSideEffects = true)]
    public void CreateUser(MembershipServiceUser user, string password)
    {
        Membership.CreateUser(user.UserName, password, user.Email);
    }

    [RequiresRole("Administrator")]
    public void DeleteUser(MembershipServiceUser user)
    {
        Membership.DeleteUser(user.UserName);
    }

    [RequiresRole("Administrator")]
    public void UpdateUser(MembershipServiceUser user)
    {
        Membership.UpdateUser(user.ToMembershipUser());
    }

    [RequiresRole("Administrator")]
    public void ResetPassword(MembershipServiceUser user)
    {
        user.ToMembershipUser().ResetPassword();
    }
     // ...
}

Again, there are a few things worth taking a look at. First, this service will eventually need to require a secure endpoint. Every service that accepts a password or other critical user information should be secured. Second, every method (except CreateUser) requires the user to be an “Administrator”. This is advisable since we don’t want just anybody to be able to delete users. Once again we’ve only exposed a subset of the API and we can add or remove methods from the MembershipService based on what we would like to do on the client.

At this point, Membership is available for use from the client. The API is slightly different since we’ve followed standard RIA Services conventions, but all the power is still there. In addition, we’ve made Membership available using a general pattern that can be applied to many kinds of existing services and other business logic.

The full source for the MembershipService is available as part of the Authorization Sample.It’s only a few more methods, but it might give you an idea of how the pattern grows.