C# Corner

ServiceStack and Razor Forms

ServiceStack moves to a complete Web application framework with support for Razor forms.

A few months ago, I wrote an article about using ServiceStack to breathe life into old ASP.NET Web Forms applications. Using ServiceStack, you could easily add REST support to Web Forms applications without having to deal with the headaches of running a combined Web Forms + MVC site. This time, let's look at ServiceStack Razor forms support -- making ServiceStack a complete Web application framework.

If you're not familiar with ServiceStack, I encourage you to quickly review my other article. It will give you a good overview of the DTO approach used by ServiceStack to expose services. This will help you understand the service I'll be creating in this article.

Getting Started
Because the Razor view engine used by ServiceStack is the Microsoft .NET Framework 4, the sample code will be a .NET 4 Web application, but I'll still use the free version of ServiceStack, version 3.9.71. The methods presented in this article will work for both 3.9.71, as well as the newer 4.x commercial version of ServiceStack.

To get started, I create a new ASP.NET Application in Visual Studio. When prompted to select the template, I use the Empty template. This will create a basic ASP.NET Web application, but there's no support for Web Forms (ASPX) or MVC (Razor).

Next, I install the ServiceStack.Razor NuGet package. This will automatically pull in all of its dependent packages. As you recall in my last article on using ServiceStack, I need to use the NuGet Package Manager Console to install a specific version of ServiceStack (the free one):

Install-Package ServiceStack.Razor -Version 3.9.71 -DependencyVersion Highest

ServiceStack needs an entry point. This is the "AppHost" class and this is where I'll add support for using Razor forms from ServiceStack. I create a new AppHost.cs class file and add the following code:

public class AppHost : AppHostBase
{
  public AppHost() : base("ServiceStackAndHTML", typeof(AppHost).Assembly)
  {
  }

  public override void Configure(Container container)
  {
    Plugins.Add(new RazorFormat());
    SetConfig(new EndpointHostConfig { DebugMode = true });
  }
}

In the EndpointHostConfig, I hardcoded DebugMode to true for this sample (normally, it's inferred by ServiceStack). By putting ServiceStack in debug mode, a background file system watcher will automatically restart the AppDomain for the Web site whenever a view or layout is changed. This will make for a faster refresh when I hop back to the browser to view new pages.

This AppHost class needs to be initialized just once, so I'll add a Global.asax file and plug my AppHost into the Application_Start handler:

public class Global : System.Web.HttpApplication
{
  protected void Application_Start(object sender, EventArgs e)
  {
    new AppHost().Init();
  }
}

Finally, I need to make some changes to web.config. First, like last time, I need to add a handler so all requests to this application are handled by ServiceStack. I add a new system.webServer section:

<system.webServer>
  <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>

Second, when I installed ServiceStack.Razor, changes were made to my web.config that point to an old version of the Microsoft System.Web.WebPages.Razor assembly, version 1.0. I don't have version 1.0 on my machine (it's probably an old MVC 3 distribution), but I do have version 2.0, so I just updated the assembly version number in web.config:

<add assembly="System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, 
  PublicKeyToken=31BF3856AD364E35" />

That's all I need! My ServiceStack-based Web site is ready to serve up services, as well as Razor view pages. And, as a bonus, it supports Markdown, too (and more on that later)!

Serving Up HTML
To see how easy it is to start creating Razor forms, I use Visual Studio and add a new MVC 5 View Page (Razor). I called the page "default.cshtml" and I put it right in the root directory of the Web site (no Controller needed). This inserts a very basic Razor form:

@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title></title>
</head>
<body>
  <div>
    
  </div>
</body>
</html>

This should look familiar to anyone that has done MVC work with Razor. Notice how I have control of setting the Layout property? I can even put in straight C# code -- just like in MVC. Because this is a Razor form and ServiceStack supports Razor, there's nothing more I need to do. But, to make sure everything is working, I'll add a little server-side Razor code. Inside the <div> in the <body> I'll add a call to get the current date/time:

Server Time: @DateTime.Now

Now I hit F5, my browser launches and I'm looking at the server's time. I didn't need to create an MVC controller and a route to get to the page. ServiceStack's Razor support allows direct access to Razor pages and it even strips off the extension to give you nice URLs -- and will also strip off any documents that are called "default." So, for my example where I created default.cshtml in the root directory, my URL in the browser shows:

http://localhost:61174/

If I rename "default.cshtml" to "servertime.cshtml," the page is available via:

http://localhost:61174/servertime

This is a huge time saver for simple pages that don't need the overhead of an MVC controller just to display some HTML. And I also don't need to set up any custom routes to get these pretty URLs.

Markdown Support!
Markdown is a super-simple format that allows content to be expressed in simple text, but still allows that text to be converted into nice-looking HTML. StackOverflow and GitHub are just a few examples where Markdown is used heavily to maintain user-submitted content. For a quick overview of Markdown and its formatting, see the StackOverflow editing help page.

The ServiceStack HTML rendering supports Markdown files directly. Inside Visual Studio, I add a new text file called "ShowMeContent.md". I enter the following text into this file:

Section 1
==============
This is section 1.

Section 1.1
--------------
And here is a sub-section.

This part is **very** important to understand.

On its own, this text is very simple and could easily be used in a plain-text e-mail. But I'm serving this up from a Web site, so when I go back to my browser and visit this page, I'll get HTML (notice again how I don't need to include the ".md" extension and I automatically have a pretty URL):

http://localhost:61174/showmecontent

I don't want to go into all of the details of Markdown, but if you view the rendered HTML, you'll see how "Section 1" got wrapper in an <h1> tag and "Section 1.1" is wrapped in an <h2> tag. The "**" surround the word "very" caused it to be rendered as bold (a single "*" will render it as italics).

ServiceStack has even combined the power of Razor forms with the ease of Markdown into a view engine called Markdown Razor. This gives you the ability to do all sorts of server-side code in Razor sections, but define the presentation in Markdown. Here's a simple example. Note that Markdown Razor does not support multi-line statements so I can't wrap the code in a @{ … } block:

@var goodMovies = new [] { "Star Wars: The Force Awakens", "Star Wars: The Empire Strikes Back" };
@var poorMovies = new [] { "Star Wars: The Phantom Menace" };

Good Movies
==============
@foreach var title in goodMovies {
- @title
}

Poor Movies
==============
@foreach var title in poorMovies {
- @title
}

I place this in a file called "mymovielist.md" and then go to my browser to view it in HTML:

http://localhost:61174/mymovielist 

Without any styling, it's pretty boring, but I can go back later and add a stylesheet to fix that. If I want to use this data for a plain-text e-mail, I could add a "format" URL argument and tell ServiceStack I want the bare text version of this Markdown page:

http://localhost:61174/mymovielist?format=text.bare

Finally, ServiceStack lets you mix and match view engines in a single page. I can go back to my servertime.cshtml page and add a new <div> to display my movie list:

<div style="background-color: #ccc">
  @Html.Partial("mymovielist")
</div>

You'll see I didn't need to include the ".md" extension. ServiceStack.Razor will first look for "mymovielist.cshtml" and, if not found, will look for "mymovielist.md."

ServiceStack Services and HTML
Instead of a simple page that lists my movies, let's assume I have a real movie listing service that's exposed with ServiceStack. For this article, I'll create a very simple service that stores the list in-memory. The data for each movie will include its name and a flag indicating if it's a poor movie:

public class Movie
{
  public string Title { get; set; }
  public bool IsPoor { get; set; }
}

Like any ServiceStack service, I need to define a request and response DTO. For right now, I'm only implementing the query (GET) functionality, but I'll add "Title" and "IsPoor" properties to my request so this object can be used later for adds and deletes:

[Route("/movielist")]
public class MoviesRequest
{
  public string Title { get; set; }
  public bool? IsPoor { get; set; }
}

In my response DTO, I'll take a list of movies and split them into the Good movies and the Poor movies. Note the use of a parameterless constructor, as well as the [DataContract] and [DataMember] attributes in Listing 1. This allows my service to be exposed to SOAP clients, as well (this is all built-in functionality of ServiceStack).

Listing 1: Response DTO for Movies Service
[DataContract]
public class MoviesResponse
{
  private readonly Movie[] goodMovies;
  private readonly Movie[] poorMovies;

  public MoviesResponse(IEnumerable<Movie> allMovies)
  {
    this.goodMovies = allMovies.Where(m => !m.IsPoor).ToArray();
    this.poorMovies = allMovies.Where(m => m.IsPoor).ToArray();
  }

  public MoviesResponse()
    : this(Enumerable.Empty<Movie>())
  {
  }

  [DataMember]
  public Movie[] GoodMovies
  {
    get { return goodMovies; }
  }

  [DataMember]
  public Movie[] PoorMovies
  {
    get { return poorMovies; }
  }
}

Now I create the service to return my list of movies. I use a static constructor to initialize the in-memory listing of movies, as shown in Listing 2.

Listing 2: Simple MovieService Code
public class MovieService : Service
{
  private static List<Movie> movies = new List<Movie>();

  static MovieService()
  {
    movies.AddRange(new[]
    {
      new Movie {Title = "Star Wars: The Force Awakens", IsPoor = false},
      new Movie {Title = "Star Wars: The Empire Strikes Back", IsPoor = false},
      new Movie {Title = "Star Wars: The Phantom Menace", IsPoor = true}
    }
      );
  }

  public MoviesResponse Get(MoviesRequest request)
  {
    return new MoviesResponse(movies);
  }
}

Once I compile this application, any client making a GET request to my "/movielist" endpoint will get a response with the list of movies based on the Content-Type requested. To test this, I enter the URL in my browser, but tell ServiceStack I want the format to be JSON because that's what I'm used to seeing when I debug REST services:

http://localhost:61174/movielist?format=json

I could leave off the "format" argument and ServiceStack will give me a basic HTML page with the data in a tabular format. Instead, I'll take advantage of ServiceStack's Razor support to return a custom Markdown page.

While I showed earlier that ServiceStack supports direct viewing of Razor pages without the need for a controller, it still uses the "/Views" and "/Views/Shared" folders when looking for views for a ServiceStack service. The resolution order used in the search is:

  1. If the service has the "DefaultView" attribute applied to it, ServiceStack will look for that view name in the "/Views" folder and then "/Views/Shared" for a view (looking for both a .cshtml view and then an .md view).
  2. A view with the same name as the Response DTO is searched for in "/Views" and then "/Views/Shared" (again, both .cshtml and .md).
  3. A view with the same name as the Request DTO is searched for in "/Views" and then "/Views/Shared" (again, both .cshtml and .md).

So I create a "Views" folder in the root of my application and create an empty Markdown file with the name "MoviesResponse.md" (same name as my response DTO). To utilize Markdown Razor, I can inherit from MarkdowViewBase<T> where T is my response object type (Note: If I was creating a regular Razor page (.cshtml), I would inherit from ViewPage<T>):

@inherits ServiceStack.Markdown.MarkdownViewBase<ServiceStackAndHTML.DTO.MoviesResponse>

This types the "Model" property inside my Markdown Razor page as my MoviesResponse DTO. Now I can take the Markdown I used earlier from "mymovielist.md" and make a few changes to pull the list of good and poor movies from the model (my service's response DTO). Here's the complete MoviesResponse.md:

@inherits ServiceStack.Markdown.MarkdownViewBase<ServiceStackAndHTML.DTO.MoviesResponse>

Good Movies
==============
@foreach var movie in Model.GoodMovies {
- @movie.Title
}

Poor Movies
==============
@foreach var movie in Model.PoorMovies {
- @movie.Title
}

I can now put http://localhost:61174/movielist in my browser and get my custom HTML.

Add and Delete Functionality
To make this service really useful, I'll implement the ability to add new movies, as well as remove movies from my list. This is as easy as adding a "Post" (add) and a "Delete" (remove) method to my service, as you can see in Listing 3.

Listing 3: Add and Delete Functionality of MovieService
public MoviesResponse Post(MoviesRequest request)
{
  movies.Add(new Movie
  {
    Title = request.Title, 
    IsPoor = request.IsPoor.Value
  });
  return new MoviesResponse(movies);
}

public MoviesResponse Delete(MoviesRequest request)
{
  movies = movies.Where(m => !(m.Title == request.Title && m.IsPoor == request.IsPoor))
    .ToList();

  return new MoviesResponse(movies);
}

To test this, I'll open up Postman (bit.ly/1YYNrkb), my favorite Chrome add-in for API testing. I'll construct a request to my service to add another movie:

  • The HTTP method will be POST
  • The URL will point to my service: http://localhost:61174/movielist
  • I'll set the Content-Type header to "application/json"
  • The body will be JSON and contain:
    {"Title":"The Godfather, Part II","IsPoor":false}

I click Postman's Send button and my response is an updated list of movies. If I go back to my browser and refresh the movielist page, I'll see the new movie. Now I can go back to Postman, leave the body the same and change the verb to DELETE. I press Send, go back and refresh the “movielist” page, and I get an updated list of movies that has the new movie removed.

Of course, we don't want our users using Postman to access this service. Download the sample code for this article and you'll see how I created a complete single-page app against this service (http://localhost:61174/movies). I also took the time to create a Layout page to really showcase how easy it is to move your Razor knowledge to ServiceStack.

Wrapping Up
This article covered just the basics of using HTML from within ServiceStack. There's so much more I didn't have time to cover:

  • The ServiceStack Razor pages have built-in access to the ServiceStack IOC container (funq).
  • ServiceStack's ability to be run from a Windows Service means you could easily create a Web-based configuration page for your Windows services.
  • Custom error pages.
  • Cascading Layout templates.
  • Dynamic view model support (easily grab QueryString, FormData, Cookie values without needing to define a model).

If you'd like to see any of these topics covered, feel free to drop me an e-mail.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube