Html5 pushstate Urls on ServiceStack

2019-02-10 15:45发布

问题:

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?

回答1:

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



回答2:

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)));
});