I recently had the same problem where i needed to serve a single page app (react in my case) and some api controllers from the same self hosted OWIN pipeline. The accepted answer works great to serve the client files, but it fails if your SPA uses client side routing.
If your SPA needs client side routing (i used react-router-dom), you need to redirect requests that don't map to files or api controllers to your index.html
file. To do this, you can add a custom middleware at the end of your pipeline and use the SendFileAsync()
method from Microsoft.Owin.StaticFiles to return index.html
:
public class OwinPipeline
{
public void Configuration(IAppBuilder appBuilder)
{
// SPA files from /wwwroot
var opts = new FileServerOptions
{
EnableDefaultFiles = true,
FileSystem = new PhysicalFileSystem("./wwwroot"),
};
appBuilder.UseFileServer(opts);
// Web API
var config = new HttpConfiguration
{
IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always
};
config.MapHttpAttributeRoutes();
appBuilder.UseWebApi(config);
// Use index.html if not a file or controller route
appBuilder.Use(async (context, _) =>
{
// If no middleware handled the request, send index.html.
// this way the client side router can handle the route.
await context.Response.SendFileAsync("./wwwroot/index.html");
});
}
}
There are two important points here:
- The middleware needs to be the last one in your pipeline. This way it only gets called when no other middleware matched the request.
- Don't call
await next()
in your last middleware as this would change the response code to a 404. This is a default behavior of the OWIN pipeline.
Note:
- I placed my
index.html
and all other SPA files in a wwwroot
folder in my project.
- When you use a relativ path in
SendFileAsync()
, it uses Directory.GetCurrentDirectory()
as base path to locate your file. If your web filesystem root differs from that, it throws a FileNotFound exception. In this case it might be necessary to provide the absolute path to your index.html
file.