C# Corner

Shine Up Those Older Web Forms Applications with ServiceStack

Forget post backs! Easily add REST services to enhance your legacy Web Forms applications.

So I've got an ASP.NET Web Forms application that needs maintaining. And it's still running the Microsoft .NET Framework 3.5. The size, complexity and availability of external components from third-party vendors make an upgrade to the .NET Framework 4 too risky. Am I stuck dealing with server-side controls and post backs? Heck, no! Thanks to ServiceStack, I can easily enhance that application.

What Is ServiceStack?
I don't want to rehash the materials from the ServiceStack Web site, so I'll just say that ServiceStack is a simple, clean and all-around great framework for creating services in the .NET Framework. While version 4.0 is a commercial release, the 3.x code is still open source and totally functional.

Why use ServiceStack instead of the ASP.NET MVC or ASP.NET Web API frameworks? Well, I still need to support the .NET 3.5 site so the Web API is out (it requires at least the .NET 4 runtime), and MVC hasn't supported the .NET Framework 3.5 since version 2 (current version of ASP.NET MVC is version 5). So not only does ServiceStack still support the .NET Framework 3.5, it's pretty easy for me to plug in to an existing Web Forms application.

Why not set up a brand-new Web site? Well, depending on infrastructure, that can be more complicated. And, in this particular case, I had an existing application with some features I wanted to take advantage of (authentication, application information stored in the ASP.NET session and so on). Creating a whole new Web site seemed a bit overkill.

This article will give you an overview of the integration points for a Web Forms application that utilizes ServiceStack. It is not an extensive overview or even an introduction to ServiceStack. You can get more details on ServiceStack features by visiting the Web site. (If you'd like to see more coverage of ServiceStack, drop me an e-mail.)

Getting Started
Thanks to NuGet, adding ServiceStack to my existing Web Forms application was easy, but did come with one important wrinkle: Starting with NuGet version 2.8 (released January 2014), dependency resolution changed from favoring the highest available version to favoring the lowest available version. To be fair, this is a more sound strategy (see the NuGet 2.8 Release Notes for more details), but it causes problems with the way ServiceStack is packaged. However, by using the Package Manager console, I can change the dependency resolution to the way it used to be (using the highest available version number):

PM> Install-Package ServiceStack -Version 3.9.71 -DependencyVersion Highest

This installs all required binaries and references for ServiceStack into my Web Forms application.

Configuration
ServiceStack is plugged in to my existing application via an HTTP Handler. To get this configured, I need to add a section to my web.config, as shown in Listing 1.

Listing 1: Web.config with ServiceStack HTTP Handler
<location path="api">
  <!--Required for IIS 6.0-->
  <system.web>
    <httpHandlers>
      <add path="*" type="ServiceStack.WebHost.Endpoints.ServiceStackHttpHandlerFactory, 
        ServiceStack" verb="*" />
    </httpHandlers>
  </system.web>

  <!--Required for IIS 7.0-->
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true" />
    <validation validateIntegratedModeConfiguration="false" />
    <handlers>
      <add path="*" name="ServiceStack.Factory" 
        type="ServiceStack.WebHost.Endpoints.ServiceStackHttpHandlerFactory, ServiceStack" 
        verb="*" preCondition="integratedMode" resourceType="Unspecified" allowPathInfo="true" />
    </handlers>
  </system.webServer>
</location>

There's not really a physical "api" directory in my Web site. I'm just telling IIS that any requests in the "api" path of my URL will be processed by a special handler -- in this case, ServiceStack. This allows me to "segment" my ServiceStack services in a central location.

Sample Service
For this article, I'll expose a very simple REST service that accepts a name and returns the name in uppercase. ServiceStack uses the Data Transfer Object (DTO) pattern. Therefore, I need to create both a request DTO and a response DTO:

public class HelloRequest
{
  public string Name { get; set; }
}

public class HelloResponse
{
  public string Result { get; set; }
}

Finally, I'll define a very simple service using these DTOs:

public class BigNameService : Service
{
  public object Get(HelloRequest request)
  {
    if (String.IsNullOrEmpty(request.Name))
      request.Name = "stranger";

    return new HelloResponse { Result = "Hello, " + request.Name.ToUpper() };
  }
}

The next thing I need to do is create an "AppHost" for my ServiceStack services. This is where ServiceStack is initialized:

public class AppHost : AppHostBase
{
  public AppHost()
    : base("C# Corner Sample", typeof(BigNameService).Assembly)
  {
    SetConfig(new EndpointHostConfig { ServiceStackHandlerFactoryPath = "api" });

    Routes.Add<HelloRequest>("/hello");
    Routes.Add<HelloRequest>("/hello/{Name*}");
  }
}

Notice how I set the ServiceStackHandlerFactoryPath to "api". This matches the location setting I configured in web.config. If you decide to put your ServiceStack services somewhere else, make sure you change both settings. I also configured the routes that will handle my service requests. Even though the routes start with a "/", they're still assumed to be found under the "api" section of my URI because that's how I configured ServiceStack. The full URI for my service is http://<mydomain>/api/hello. Also, the second route tells ServiceStack that anything entered in the URL after "/hello" should be exposed to my service as the "Name" parameter -- and my HelloRequest DTO just happens to have a Name property so it will be populated automatically!

My AppHost class is instantiated and initialized inside the Global.asax Application_Start method:

void Application_Start(object sender, EventArgs e)
{
  // Existing code not repeated...
  new AppHost().Init();
}

At this point, my service is complete! I can drop a little bit of JavaScript and HTML into my main ASPX page as a test (see Listing 2).

Listing 2: Testing the BigNameService with JavaScript and HTML
Enter a name: <input type="text" id="name"/> <input type="button" 
  id="showHello" value="Greeting"/>

<script type="text/javascript">
  $(function () {
    $('#showHello').click(function () {
      $.ajax({
        url: '/api/hello/' + $('#name').val(),
        dataType: 'json',
        success: function(data) {
          alert(data.Result);
        }
      });
    });
  })
</script>

Now that I have the basics wired up, it's time to get some real work done!

Session Access
Many Web Forms applications take advantage of the ASP.NET built-in Session object for storing information within the context of a user's session. ServiceStack bypasses this implementation and has its own session management. This custom session management is optimized for maximum performance and provides more features in the ServiceStack framework.

But I'm enhancing an existing Web Forms application that has taken advantage of the ASP.NET Session object and I'd like to be able to utilize this inside my ServiceStack services. Because the ASP.NET Session is part of the ASP.NET framework and not something specific to Web Forms, you would think I could get access to it from within a ServiceStack service:

var session = HttpContext.Current.Session;

When I tried this, I noticed that the Session object was null. Why?

ASP.NET defines a marker interface for its Session management. A marker interface is an interface that has no methods. By implementing the interface, a class is "marked" as supporting some arbitrary feature or function. In the case of ASP.NET session management, there's an interface called "IRequiresSessionState." When ASP.NET is processing a request, it checks if the HTTP handler processing the request implements the IRequiresSessionState interface. If so, it sets up the current HttpContext to have access to the Session object.

Because the folks at ServiceStack didn't want to use the ASP.NET Session object, the HTTP Handler exposed by ServiceStack doesn't implement the IRequiresSessionState interface and, therefore, I don't have access to the ASP.NET Session.

Thankfully, ServiceStack is flexible and allows me to add support for this. When I added information to my web.config to support ServiceStack, the handler added was actually a factory (ServiceStack.HttpHandlerFactory -- an implementation of System.Web.IHttpHandlerFactory). I can implement my own factory, which simply wraps the functionality of the existing handler inside a class that implements IRequiresSessionState!

First, I start with a new IHttpHandler implementation (note the use of the IRequiresSessionState interface). This implementation will do nothing but forward requests to the original IHttpHandler it received in the constructor (see Listing 3).

Listing 3: Forwarding Requests to the Original IHttpHandler
public class SessionStateHandlerWrapper : IHttpHandler, IRequiresSessionState
{
  private readonly IHttpHandler handler;

  public SessionStateHandlerWrapper(IHttpHandler handler)
  {
    this.handler = handler;
  }

  public void ProcessRequest(HttpContext context)
  {
    handler.ProcessRequest(context);
  }

  public bool IsReusable
  {
    get { return handler.IsReusable; }
  }
}

Next, I need to create a new IHttpHandlerFactory that returns my wrapper instead of the default ServiceStack handler, which you can see in Listing 4.

Listing 4: New IHttpHandlerFactory
public class SessionStateHandlerFactory : IHttpHandlerFactory
{
  private readonly static IHttpHandlerFactory ssFactory = new HttpHandlerFactory();

  public IHttpHandler GetHandler(HttpContext context, string requestType, string url, 
    string pathTranslated)
  {
    var ssHandler = ssFactory.GetHandler(context, requestType, url, pathTranslated);
    if (ssHandler == null)
      return null;

    return new SessionStateHandlerWrapper(ssHandler);
  }

  public void ReleaseHandler(IHttpHandler handler)
  {
    ssFactory.ReleaseHandler(handler);
  }
}

Finally, I update my web.config to use my handler factory instead of the ServiceStack version:

<handlers>
  <add path="*" name="ServiceStack.Factory" 
    type="WebFormsAndServiceStack.App_Start.SessionStateHandlerFactory"
    verb="*" preCondition="integratedMode" resourceType="Unspecified" allowPathInfo="true" />
</handlers>

Once this is in place, I'll have full read/write access to the ASP.NET Session object that will be shared between my legacy Web Forms application and my ServiceStack services. All I have to do is access the session from the current HttpContext. What I ultimately do is create a subclass of the ServiceStack Service class (the base class for the REST services I'll be creating) and expose the ASP.NET session as a virtual property -- this makes unit testing my services easier:

public abstract class SessionStateService : Service
{
  public virtual HttpSessionState AspSession
  {
    get { return HttpContext.Current.Session; }
  }
}

Now, if I have the following code inside my old Web Forms project:

Session["currentDepartment"] = "Accounting";

I can access the currentDepartment session value from my ServiceStack service by doing:

var department = AspSession["currentDepartment"].ToString();

Authentication
The next integration point I wanted to tackle was authentication. The application I was enhancing used forms authentication for security. A database was set up with the user IDs and passwords of the users who could access the system. If a user were to try and visit a page and wasn't authenticated, they would automatically get redirected to a login page. Likewise, I don't want my REST services to be available to just anyone. I only want authenticated users to be able to access these services.

By default, ServiceStack comes with a number of authentication providers: Basic Authentication, HTTP Digest, Twitter OAuth, OpenId (Google, Yahoo and so on) and more. At the time of this writing, it did not have a provider that would integrate with ASP.NET forms authentication. However, it did make the authentication system extensible.

The CredentialsAuthProvider is a good base class for a custom authentication mechanism. For my purposes (securing some REST services), I didn't need the full functionality of new user registration, login page redirection, forgotten password e-mails and so on. All of that is done by other providers. For securing my REST services, I just need to know if the current user accessing the service is authorized. So I need to create my own auth provider, as shown in Listing 5.

Listing 5: Creating My Own Auth Provider
public class ServiceStackFormsAuth : CredentialsAuthProvider
{
  public ServiceStackFormsAuth()
  {
  }

  public ServiceStackFormsAuth(IResourceManager appSettings)
    : base(appSettings)
  {
  }

  public ServiceStackFormsAuth(IResourceManager appSettings, string authRealm,
                               string oAuthProvider)
    : base(appSettings, authRealm, oAuthProvider)
  {
  }
}

This actually gets me 95 percent of the way there. The last thing I need to do is override the implementation of IsAuthorized and return a Boolean as to whether the current user is authorized. I thought this was going to be easy. After all, when a user is authenticated through forms authentication, the current HttpContext User.Identity.IsAuthenticated property returns true. If the user isn't authenticated, the IsAuthenticated property returns false. Therefore, I simply coded:

public override bool IsAuthorized(IAuthSession session, IOAuthTokens tokens, Auth request = null)
{
  return HttpContext.Current.User.Identity.IsAuthenticated;
}

But when I started testing this service as an unauthenticated user, I wasn't getting an HTTP status 401 (Unauthorized). Instead, I was getting a 302 redirect to my login page! After some investigation, I found out that, by default, the forms authentication implementation in ASP.NET automatically intercepts 401 response codes and turns them into 302 redirects to the configured login page. In all my years of using forms authentication, I never knew that feature existed. Luckily, there's a flag I can set to turn off that redirect:

public override bool IsAuthorized(IAuthSession session, IOAuthTokens tokens, Auth request = null)
{
  HttpContext.Current.Response.SuppressFormsAuthenticationRedirect = true;
  return HttpContext.Current.User.Identity.IsAuthenticated;
}

Now if a user tries to access one of my secure REST endpoints and they haven't been authenticated through the main Web application, the response will be a 401 (Unauthorized) status code.

Service Implementation
Now that I have my forms authentication provider, I need to plug it into ServiceStack and I need to mark any required methods (or the entire service) with the [Authenticate] attribute.

I start off by modifying AppHost. There's a Configure method that can be overridden to provide additional configuration:

public override void Configure(Container container)
{
  var appSettings = new AppSettings();

  Plugins.Add(new AuthFeature(() => new AuthUserSession(),
  new IAuthProvider[] {
    new ServiceStackFormsAuth(appSettings), 
    }));
}

Next, I need to add the [Authenticate] attribute to my service. I can add this attribute to individual methods in my service or I can add it to the entire service. For this example, I'll just add it to the service:

[Authenticate]
public class BigNameService : SessionStateService
{
    // Original code omitted for brevity
}

Now, all requests to my BigNameService will require that the current user is authenticated.

Get the Code, Get to Work
The sample code in this article contains a starter ASP.NET Web Forms application with the BigNameService implemented via ServiceStack. If you load the code into Visual Studio and run it, you'll get an error if you try and click the "Greeting" button and haven't been authenticated. Click on the "Register" link and set up a username and password. Now try the "Greeting" button again. The call will succeed. Click the Logout link to log out and try the button again -- you'll see the error because you're no longer authenticated.

I hope I've shown you how easy it can be to breathe some new life into an older Web Forms application. With ServiceStack, you just need a few extra DLLs and a couple of tweaks to your web.config and you've got the ability to use some newer Web technologies like REST, AJAX, Knockout.js and more to add new life to Web Forms applications.

comments powered by Disqus

Featured

  • AI for GitHub Collaboration? Maybe Not So Much

    No doubt GitHub Copilot has been a boon for developers, but AI might not be the best tool for collaboration, according to developers weighing in on a recent social media post from the GitHub team.

  • Visual Studio 2022 Getting VS Code 'Command Palette' Equivalent

    As any Visual Studio Code user knows, the editor's command palette is a powerful tool for getting things done quickly, without having to navigate through menus and dialogs. Now, we learn how an equivalent is coming for Microsoft's flagship Visual Studio IDE, invoked by the same familiar Ctrl+Shift+P keyboard shortcut.

  • .NET 9 Preview 3: 'I've Been Waiting 9 Years for This API!'

    Microsoft's third preview of .NET 9 sees a lot of minor tweaks and fixes with no earth-shaking new functionality, but little things can be important to individual developers.

  • Data Anomaly Detection Using a Neural Autoencoder with C#

    Dr. James McCaffrey of Microsoft Research tackles the process of examining a set of source data to find data items that are different in some way from the majority of the source items.

  • What's New for Python, Java in Visual Studio Code

    Microsoft announced March 2024 updates to its Python and Java extensions for Visual Studio Code, the open source-based, cross-platform code editor that has repeatedly been named the No. 1 tool in major development surveys.

Subscribe on YouTube