Thursday, December 27, 2012

Self-hosting ServiceStack serving razor'd HTML

In this walkthrough, I'll show you how to setup a self-hosting(i.e. this doesn't use ASP.NET nor ASP.NET MVC) ServiceStack that serves HTML. The HTML can be made dynamic through razor

21 steps


1. Start Visual Studio in Administrator mode

2. Create a new Console Application project, name your project BillionaireServiceStackRazorSelfHosting

3. Change the project's Target framework to .NET Framework 4 (from the default .NET Framework 4 Client Profile)

4. Add ServiceStack.Razor to your project via Nuget

5. Replace your app.config with this content:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
      <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
      <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral," requirePermission="false" />
    </sectionGroup>
  </configSections>
  <system.web>
    <httpHandlers>
      <add path="*" type="ServiceStack.WebHost.Endpoints.ServiceStackHttpHandlerFactory, ServiceStack" verb="*" />
    </httpHandlers>
    <compilation debug="true">
      <assemblies>
        <add assembly="System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
      </assemblies>
      <buildProviders>
        <add extension=".cshtml" type="ServiceStack.Razor.CSharpRazorBuildProvider, ServiceStack.Razor" />
      </buildProviders>
    </compilation>
  </system.web>
  <!-- Required for IIS 7.0 -->
  <system.webServer>
    <handlers>
      <add path="*" name="ServiceStack.Factory" type="ServiceStack.WebHost.Endpoints.ServiceStackHttpHandlerFactory, ServiceStack" verb="*" preCondition="integratedMode" resourceType="Unspecified" allowPathInfo="true" />
    </handlers>
  </system.webServer>
  <appSettings>
    <add key="webPages:Enabled" value="false" />
    <add key="webpages:Version" value="2.0.0.0"/>
  </appSettings>
  <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="ServiceStack.Razor.ViewPage">
      <namespaces>
        <add namespace="ServiceStack.Html" />
        <add namespace="ServiceStack.Razor" />
        <add namespace="ServiceStack.Text" />
        <add namespace="ServiceStack.OrmLite" />
        <add namespace="BillionaireServiceStackRazorSelfHosting" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>

6. Copy app.config to web.config, web.config enables intellisense for razor. The webpages:Version must be 2.0.0.0; if it is 1.0.0.0 razor intellisense will not work, saw the answer here: http://stackoverflow.com/questions/6133090/razor-intellisense-not-working-vs2010-sp1rel-mvc3-from-wpi-win7-x64/11780828#11780828


7. Create a new class named AppHost.cs in your project's root path:

//The entire C# code for the stand-alone RazorRockstars demo.
namespace BillionaireServiceStackRazorSelfHosting
{
    public class AppHost : ServiceStack.WebHost.Endpoints.AppHostHttpListenerBase
    {
     
        public AppHost() : base("Test Razor", typeof(AppHost).Assembly) { }

        public override void Configure(Funq.Container container)
        {
            ServiceStack.Logging.LogManager.LogFactory = new ServiceStack.Logging.Support.Logging.ConsoleLogFactory();

            Plugins.Add(new ServiceStack.Razor.RazorFormat());


            SetConfig(new ServiceStack.WebHost.Endpoints.EndpointHostConfig
            {
                CustomHttpHandlers = {
                    { System.Net.HttpStatusCode.NotFound, new ServiceStack.Razor.RazorHandler("/notfound") }
                }
            });
        }
    }    
}


8. Replace your Program.cs with this:

using System;


namespace BillionaireServiceStackRazorSelfHosting
{
    class Program
    {
        static void Main(string[] args)
        {
            ServiceStack.Logging.LogManager.LogFactory = new ServiceStack.Logging.Support.Logging.ConsoleLogFactory();

            var appHost = new AppHost();
            appHost.Init();
            appHost.Start("http://*:2012/");

            var proc = new System.Diagnostics.Process ();
            proc.StartInfo.UseShellExecute = true;
            proc.StartInfo.FileName = "http://localhost:2012/";
            proc.Start ();


            Console.WriteLine("\n\nListening on http://*:2012/..");
            Console.WriteLine("Type Ctrl+C to quit..");
            System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
        }
    }
}

9. If you'll compile your code now, you'll receive an error that you must add System.Web to your project:


10. Add System.Web to your project's Reference:


11. Your code shall compile now:


12. To test if things are working accordingly, run your code, you shall see the following:




13. Check http://localhost:2012 on your browser. You shall see the following:




14. Then create a new page named Great.cshtml on your site's root path


15. Put this code in Great.cshtml:

Teach your kid to start counting from zero:

@for (int i = 0; i < 10; i++)
{
    <p>@i</p>
}

16. If you'll run your code now, you'll receive an error:



17. You must set Great.cshtml's Copy to Output Directory property to Copy if newer


18. Your code will run now:



19. To use strongly-typed model for your razor page, use inherits directive. Create a model first, it must be public and its Copy to Output Directory property must be set to Copy if newer too

namespace BillionaireServiceStackRazorSelfHosting
{
    public class CountRange
    {
        public int StartNumber { get; set; }
        public int EndNumber { get; set; }
    }
}

20. Then change Great.cshtml to this:

@inherits ViewPage<CountRange>


Start: @Model.StartNumber <br />
End: @Model.EndNumber


@for (int i = Model.StartNumber; i <= Model.EndNumber; i++)
{
    <p>@i</p>
}

21. Run your code, and browse this url: http://localhost:2012/Great?StartNumber=8&EndNumber=12. This is the output:



That's it, web serving is never been this lightweight!


Another advantage of serving Razor this way(as opposed to ASP.NET MVC's Areas) is you can structure your web pages in the most logical way you wanted them be, you could place Razor pages on any directory and on any level, ServiceStack.Razor won't have a problem serving them.


ASP.NET MVC even with its Areas, feels very constraining. In fact, afaik, the main intent of ASP.NET MVC's Areas is not for stashing related controllers together; Area's main intent is to group the related controller+views(and perhaps plus some controller-specific models) so an application's module could be structured and conceived as one cohesive MVC unit. Perhaps Area's Controllers folder should be named Controller, i.e. in singular form ツ


The ServiceStack solution you've made on Visual Studio can be opened on MonoDevelop, it works out-of-the-box. I tried to open and run the same Visual Studio .sln on MonoDevelop on my main development OS (Mac OS X), It Just Works™


For more info on ServiceStack Razor head to http://razor.servicestack.net/


Happy Computing! ツ





A minor snag, though razor's intellisense is working by following the steps above, I can't figure out how to make the inherits directive be detected properly by Visual Studio. Consequence is, Visual Studio won't be able to give you intellisense for the Model; in fact, Visual Studio will put red squiggly line below the Model property. Things are still working though even Visual Studio put red squiggly line below them




Posted a question on stackoverflow, hope someone could shed some light on ServiceStack razor's intellisense on model: http://stackoverflow.com/questions/14043364/servicestack-razor-inherits-directive-has-no-intellisense


GitHub: https://github.com/MichaelBuen/DemoServiceStackRazor

9 comments:

  1. nice post! thanks for this blog! =)

    ReplyDelete
  2. superb quick start! thank you!
    (I'm new to many of used tools and technologies but I was able to try and experience they immediately)

    ReplyDelete
  3. Thanks for the concise, step by step directions. As a beginner, the breadth and depth of ServiceStack can be a bit overwhelming, especially as it tends to improve faster than the example and documentation.
    Hope the issue with Razor Intellisense gets solves though, it really hinders discoverability at the moment.

    ReplyDelete
  4. Unfortunately, when ran on Mono (3.0.x) it serves an empty (Content-Length: 0) page :(
    any ideas?

    ReplyDelete
    Replies
    1. Might be because of version 3 is still in beta status http://www.go-mono.com/mono-downloads/download.html

      I'll check this again in Mono version 3 as soon it becomes stable release. Have only tried this on Mono version 2 stable release

      Delete
    2. Thanks for the reply. I've switched temporarily to Mono 2 (2.10.8.1-1ubuntu2.2), but still the content-length is 0. Could you please publish the test project complete source so I can make sure it is not my mistake?

      BTW I've had ran mono 2 previously, but due to bug 10551 ( https://bugzilla.xamarin.com/show_bug.cgi?id=10551 ) I had to switch to 3 ...

      Delete
    3. Published it here https://github.com/MichaelBuen/DemoServiceStackRazor

      Delete
    4. Hi,
      thank you for publishing it. This version works. When I'm using assemblies (ServiceStack.* and System.Web.Razor provided in your repository, even my code works fine. But using the ServiceStack assemblies installed via Mono's NuGet doesn't work.

      Delete
  5. Hey, thanks for wonderful post. I am working template for F# with Servicestack. Even one is out http://visualstudiogallery.msdn.microsoft.com/278caff1-917a-4ac1-a552-e5a2ce0f6e1f. But I have little problem while creating another one specifically for asp.net host. Repo for all my project here and issue is alreay mention in issue tab. https://github.com/kunjee17/ServiceStackFSharp , It will be great if you can help me out with this issue. Thanks for wonderful post though.

    ReplyDelete