I haven't done a programming article in quite a while. The Sql Server Code Generator article continues to get a decent amount of hits so I thought it would be nice to give again and post some more code from one of my current projects.

A bit of background before I get started. The web site I am working on will require access from a variety of clients. The current list includes web browsers on all OSes and dedicated applications on Windows Phone 7 (via Silverlight), iOS 4(iPhone/iPad/iTouch) and Android. What I wanted to provide to these platforms was a unified method of authenticating against the backend services. Did I mention that I need to get this up and running as quickly as possible. My only other real goal was to build it in such a way that piece of it could be factored out for scalability.

Since I am using ASP.Net to create the web site, I immediately looked to the provider model for help. ASP.Net comes with a pretty robust Sql Server based user database. Will it scale to a million users?  I'm not sure but considering that I don't have the first user, I figured it was a good place to start.

The first problem I have with the ASP.Net SqlMembershipProvider is that it requires the web site to have access to SQL Server which precludes the use of a web farm in a DMZ without opening Sql Server ports and exposing my internal architecture. That wasn't going to cut it for my needs. After Googling for solutions, I didn't come up with much so I architected my own. Here's a short image of what that architecture looks like.

Architecture Layout

Windows Communications Foundation to the rescue. Even better, ASP.Net 4.0 implements AuthenticationService and RoleService in a dll for precisely what I want... Or so I thought. Turns out System.Web.ApplicationServices.AuthenticationService and RoleService only implement a fraction of the SqlMembershipProvider and SqlRoleProvider methods. More on this later but suffice to say, I wasn't able to use these services out of the box like I had hoped. So I resorted to Plan B which was to wrap SqlMembershipProvider and SqlRoleProvider with a class that provides OperationContracts on the overriden methods. The dll on the other side would simply reference these remote methods. See code below:

[ServiceContract]
public classMembershipService : SqlMembershipProvider
{
    private MembershipProvider provider = Membership.Provider;
    [OperationContract]
    public override String ApplicationName
    {
        get { return provider.ApplicationName; }
        set { provider.ApplicationName = value; }
    }
    [OperationContract]
    public override MembershipUser GetUser(string username, bool userIsOnline)
    {
        return provider.GetUser(username, userIsOnline);
    }

    [OperationContract]
    public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
    {
        return provider.GetUser(providerUserKey, userIsOnline);
    }

    ...
}

 

 There’s three things wrong with the above code.

  1. WCF doesn’t understand the notion of overloaded method calls.  Every call name must be explicitly named.  Thankfully, WCF solves this problem with the OperationContract “Name” attribute.
  2. Properties can’t be used in WCF.  We need to translate them to methods and then translate them back again on the client side.
  3. ASP.Net requires that the attribute AspNetCompatibilityRequirements is applied to the class.

Here’s the proper code:
 

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceContract]
public classMembershipService : SqlMembershipProvider
{
    private MembershipProvider provider = Membership.Provider;
    public override String ApplicationName
    {
        get{ return provider.ApplicationName; }
        set{ provider.ApplicationName = value; }
    }
    [OperationContract]
    public string GetApplicationName()
    {
        return this.ApplicationName;
    }
    [OperationContract]
    public void SetApplicationName(String applicationname)
    {
        this.ApplicationName = applicationname;
    }
    [OperationContract(Name="GetUserByUserName")]
    public override MembershipUser GetUser(string username, bool userIsOnline)
    {
        return provider.GetUser(username, userIsOnline);
    }
    ...
}

All of the other methods of MembershipProvider translate properly across the web service boundary so let’s take a look at the custom membership provider, WCFMembershipProvider, that we will use on the DMZ web farm to gain access to membership.
 

public class WCFMembershipProvider : MembershipProvider
{
    private MembershipService.MembershipServiceClient m_proxy = null;

    public WCFMembershipProvider()
    {
        m_proxy = new MembershipService.MembershipServiceClient();
    }

    public String Uri
    {
        get { return m_proxy.Endpoint.Address.Uri.ToString(); }
        set { m_proxy.Endpoint.Address = new System.ServiceModel.EndpointAddress(value); }
    }

    public override string ApplicationName
    {
        get { return m_proxy.GetApplicationName(); }
        set { m_proxy.SetApplicationName(value); }
    }

    public override MembershipUser GetUser(string username, bool userIsOnline)
    {
        return m_proxy.GetUserByUserName(username, userIsOnline);
    }

    public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
    {
        return m_proxy.GetUserByUserKey(providerUserKey, userIsOnline); ;
    }

    ...
}

There’s a couple of interesting points to make here.  The first is to point out how I used the methods GetApplicationName() and SetApplicationName(string) to marshal the property MembershipProvider.ApplicationName.  It’s pretty straight forward and easy to implement.  What confused me the most was the CreateUser method and any other method with an “out” parameter.  Visual Studio 2010 seems to reorder the parameters when generating the service client.  All “out” parameters come first when it is clearly visible that they are last in the OperationContract method declaration.  I couldn’t figure out why this was the case so I just moved the parameters and went on.

The only other gotcha for me, was the system.serviceModel definitions in the app.config of the DLL need to be copied into the web.config of the calling web site.  In the end, here is what my client web.config looked like.
 

<?xmlversion="1.0"?>
<!--
 
For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=169433
 
-->

<
configuration>
  <
connectionStrings>
  </
connectionStrings>

  <
system.web>
    <
compilationdebug="true" targetFramework="4.0" />

    <
authenticationmode="Forms">
      <
formsloginUrl="~/Account/Login.aspx" timeout="2880" />
    </
authentication>

    <
membershipdefaultProvider="WCFMembershipProvider">
      <
providers>
        <
clear/>
        <
addname="WCFMembershipProvider"
             type="EPI.Web.WCFApplicationServicesProviders.WCFMembershipProvider"
             Uri="http://localhost:3308/MembershipService.svc"
             enablePasswordRetrieval="false" enablePasswordReset="true"
             requiresQuestionAndAnswer="false" requiresUniqueEmail="false"
             maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6"
             minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10"
             applicationName="/" />
      </
providers>
    </
membership>

    <
roleManagerenabled="true" defaultProvider="WCFRoleProvider">
      <
providers>
        <
clear/>
        <
addname="WCFRoleProvider"
             type="EPI.Web.WCFApplicationServicesProviders.WCFRoleProvider"
             Uri="http://localhost:3308/RoleService.svc"
             applicationName="/" />
        <!--
<add name="AspNetWindowsTokenRoleProvider"
            type="System.Web.Security.WindowsTokenRoleProvider" applicationName="/" />
-->
      </
providers>
    </
roleManager>

  </
system.web>

  <
system.webServer>
     <
modulesrunAllManagedModulesForAllRequests="true" />
  </
system.webServer>

  <
system.serviceModel>
    <
bindings>
      <
wsHttpBinding>
        <
bindingname="MembershipProviderServicewsHttp" closeTimeout="00:01:00"
            openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
            bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
            maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
            messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
            allowCookies="false">
          <
readerQuotasmaxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
              maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          <
reliableSessionordered="true" inactivityTimeout="00:10:00"
              enabled="false" />
          <
securitymode="Message">
            <
transportclientCredentialType="Windows" proxyCredentialType="None"
                realm="" />
            <
messageclientCredentialType="Windows" negotiateServiceCredential="true"
                algorithmSuite="Default" />
          </
security>
        </
binding>
        <
bindingname="RoleProviderServicewsHttp" closeTimeout="00:01:00"
            openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
            bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
            maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
            messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
            allowCookies="false">
          <
readerQuotasmaxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
              maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          <
reliableSessionordered="true" inactivityTimeout="00:10:00"
              enabled="false" />
          <
securitymode="Message">
            <
transportclientCredentialType="Windows" proxyCredentialType="None"
                realm="" />
            <
messageclientCredentialType="Windows" negotiateServiceCredential="true"
                algorithmSuite="Default" />
          </
security>
        </
binding>
      </
wsHttpBinding>
    </
bindings>
    <
client>
      <
endpointaddress="http://localhost:3308/MembershipService.svc"
          binding="wsHttpBinding" bindingConfiguration="MembershipProviderServicewsHttp"
          contract="MembershipService.MembershipService" name="MembershipProviderServicewsHttp">
        <
identity>
          <
userPrincipalNamevalue="usernameremoved" />
        </
identity>
      </
endpoint>
      <
endpointaddress="http://localhost:3308/RoleService.svc" binding="wsHttpBinding"
          bindingConfiguration="RoleProviderServicewsHttp" contract="RoleService.RoleService"
          name="RoleProviderServicewsHttp">
        <
identity>
          <
userPrincipalNamevalue="usernameremoved" />
        </
identity>
      </
endpoint>
    </
client>
  </
system.serviceModel>
</
configuration>
 

That pretty much does it.  One of the interesting ideas I can think of for this code is that you can essentially use the provider in the DLL as the basis for the System.Web.ApplicationServices DLL and instantly have a front facing WCF service that derives from the architecture in this article.  Granted, it's a couple of web service calls deep to authenticate but it's scalable, securable and easily made reliable.  You can download the source for this project here.  I hope you find the code useful and please comment away on the implementation, architecture or design.  I’m always open to feedback.