At the moment we're using a default.cshtml view in the root of ServiceStack to serve our AngularJS single-page app.
What I'd like to do is enable support for html5 pushstate (so no hash in the URL), but the only examples I've found so far involve a dependency on MVC with a wildcard route, and pushing the ServiceStack infrastructure to a /api subroute.
We can't take the MVC dependency, so I think we need for accept:text/html requests we need to accept any url and serve up our root application. I'd be happy to remove the default HtmlFormat extension or override it (we could still use it's JsonReport content type it we needed to).
How can I best approach this?
The Order of Operations wiki page shows the number of different hooks you can tap into to inject your own customized behavior as well as the order which they are run.
Hi-jacking requests with RawHttpHandlers
You can by-pass ServiceStack completely by adding a Config.RawHttpHandlers
to return a IHttpHandler
on requests you want to hi-jack, e.g this is how the built-in mini profiler hi-jacks all requests for files that start with ssr-
and returns the physical file:
config.RawHttpHandlers.Add((IHttpRequest request) => {
var file = GetFileNameWithoutExtension(request.PathInfo);
return file != null && file.StartsWith("ssr-")
? new MiniProfilerHandler()
: null;
}
Providing a fallback handler for non-matching routes
If you want to provide a default handler for non-matching route you can register a CatchAllHandlers in AppHost.Configure()
or in a plugin with:
appHost.CatchAllHandlers.Add((string method, string pathInfo, string filepath) =>
{
return ShouldProvideDefaultPage(pathInfo)
? new RazorHandler("/defaultpage.cshtml")
: null;
});
Using a wildcard to accept any url in a service
You could create a dummy service and simply return the same single view, e.g:
[Route("/app/{PathInfo*}")]
public class App {
public string PathInfo { get; set; }
}
public class MyService : Service
{
public object Any(App request)
{
return request;
}
}
With the wild card this service will return the view e.g. /View/App.cshtml
on any route starting with /app
, e.g:
- /app
- /app/hello
- /app/hello/my/name/is?foo=bar
Partial page support
Since partial reloads is related to pushstate I'll also mention the built-in support ServiceStack has for partial reloads.
ServiceStack Docs is an example demo that uses pushstate on browsers that support it, otherwise it falls back to use full-page reloads with browsers that don't.
You can ask for a partial page with ?format=text.bare
param, e.g.
- Full page: http://www.servicestack.net/docs/markdown/about
- Partial page: http://www.servicestack.net/docs/markdown/about?format=html.bare
- Partial page markdown: http://www.servicestack.net/docs/markdown/about?format=text.bare
Although this uses Markdown Razor. In the latest ServiceStack.Razor support you can access a partial page with just: ?format=bare
Expanding on my comment. This is what I ended up with trying to host an application in /app while also supporting the virtual file system.
host.CatchAllHandlers.Add((string method, string pathInfo, string filepath) =>
{
if (!Regex.IsMatch(pathInfo, "^/app([/?]|$)"))
return null;
// Serve valid requests as is
var vFile = HostContext.ResolveVirtualFile(pathInfo, null);
if (vFile != null)
return null;
var vDir = HostContext.ResolveVirtualDirectory(pathInfo, null);
if (vDir != null && vDir.GetDefaultDocument() != null)
return null;
// Fallback to default document
var vDef = HostContext.ResolveVirtualDirectory("/app/", null).GetDefaultDocument();
return new CustomResponseHandler((req, res) =>
new HttpResult(vDef.OpenRead(), MimeTypes.GetMimeType(vDef.Name)));
});