Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get AngularJS working with the ServiceStack FallbackRoute attribute to support HTML5 pushstate Urls?

I am building a client/server solution, using an AngularJS Single Page App as the client component and a Self-Host ServiceStack RESTful API as the server component. A single Visual Studio Console Application Project holds HTML and JavaScript files for the AngularJS component, along with C# classes for bootstrapping the ServiceStack AppHost (I have devolved Interface and Service responsibilities to separate Visual Studio Projects).

I have set all HTML and JavaScript files to have a 'Build Action' of 'None' and a 'Copy to Output Directory' of 'Copy if newer'.

Everything is working very well as long as I am prepared to put up with having a '#' in my site URLs. I would like to eliminate this by using HTML5 pushstate URLs.

Effectively this means I need to persuade ServiceStack to serve up my default Single Page App HTML shell page whenever a non-existent route is requested. There is now a FallbackRoute attribute available in ServiceStack which appears to have been added exactly for this purpose.

However, I am unsure how to use it. I have found people asking similar questions here, here and here. But the answers given were all before the new FallbackRoute attribute arrived.

Essentially, I am looking for a simple, yet complete example of how to use the FallbackRoute attribute to ensure any requests to non-existent routes are redirected to a single static HTML page.

like image 340
Holf Avatar asked Aug 19 '13 19:08

Holf


2 Answers

The RazorRockstars.Web has an implementation. I'll modify it to use a wildcard path and a default view:

[FallbackRoute("/{Path*}")]
public class Fallback
{
    public string Path { get; set; }
    public string PathInfo { get; set; }
}

public class RockstarsService : Service
{
    [DefaultView("Index")]
    public object Any(Fallback request)
    {
        request.PathInfo = base.Request.PathInfo;
        return request;
    }
    // ...
}

Since this is a service it requires a View page (details here) rather than a content page.

In the RockStars example, I can't determine what view would be rendered for the FallBackResponse, but setting the view explicitly should be all you need.

The [DefaultView("Index")] attribute I added to the Any method maps the response to a Views/Index.cshtml file. The Index.cshtml file can be empty but for a template declaration, and the complete markup for your single page app can be in your template file (i.e. _Layout.cshtml)

Without Razor

Read the html into a string and return it, while setting the content type to "text/html" with an attribute, see wiki docs on service return types

public class RockstarsService : Service
{
    static string readContents;

    [AddHeader(ContentType = "text/html")]
    public string Any(Fallback request)
    {

        // check timestamp for changes for production use
        if (readContents == '') {
            using (StreamReader streamReader = new StreamReader(pathFromConfigFile, Encoding.UTF8))
            {
                 readContents = streamReader.ReadToEnd();
            }
        }
        return readContents;
    }
    // ...
}
like image 169
Pauli Price Avatar answered Oct 17 '22 09:10

Pauli Price


It turns out it is all very simple with the FallbackRoute functionality, once you work out how to use it properly:

[FallbackRoute("/{Path*}")]
    public class Fallback
    {
        public string Path { get; set; }
    }

    public class FallBackService : Service
    {
        public object Any(Fallback request)
        {
            return new HttpResult(new FileInfo("index.html")) {ContentType = "text/html"};
        }
    }

Once this is in place, I find 'index.html' is indeed getting served up whenever I try to hit a non-existent route.

Any static files, such as JavaScript and CSS resources, get served up as normal (as long as they have a 'Copy to Output Directory' setting of 'Copy if newer', of course).

This works like a charm with the HTML5 Push-state functionality in AngularJS.

like image 21
Holf Avatar answered Oct 17 '22 11:10

Holf