Background
First let me explain the background. I am working on a project that attempts to marry a backend server that uses Web API configured via OWIN- hosted on IIS now, but potentially other OWIN-supported hosts in the future- to a frontend using AngularJS.
The AngularJS frontend is entirely static content. I completely avoid server-side technologies such as MVC/Razor, WebForms, Bundles, anything that has to do with the frontend and the assets it uses, and defer instead to the latest and greatest techniques using Node.js, Grunt/Gulp, etc. to handle CSS compilation, bundling, minification, etc. For reasons I won't go into here, I keep the frontend and server projects in separate locations within the same project (rather than stick them all in the Host project directly (see crude diagram below).
MyProject.sln
server
MyProject.Host
MyProject.Host.csproj
Startup.cs
(etc.)
frontend
MyProjectApp
app.js
index.html
MyProjectApp.njproj
(etc.)
So as far as the frontend is concerned, all I need to do is get my Host to serve my static content. In Express.js, this is trivial. With OWIN, I was able to do this easily using Microsoft.Owin.StaticFiles middleware, and it works great (it's very slick).
Here is my OwinStartup
configuration:
string dir = AppDomain.CurrentDomain.RelativeSearchPath; // get executing path
string contentPath = Path.GetFullPath(Path.Combine(dir, @"../../../frontend/MyProjectApp")); // resolve nearby frontend project directory
app.UseFileServer(new FileServerOptions
{
EnableDefaultFiles = true,
FileSystem = new PhysicalFileSystem(contentPath),
RequestPath = new PathString(string.Empty) // starts at the root of the host
});
// ensure the above occur before map handler to prevent native static content handler
app.UseStageMarker(PipelineStage.MapHandler);
The Catch
Basically, it just hosts everything in frontend/MyProjectApp
as if it were right inside the root of MyProject.Host. So naturally, if you request a file that doesn't exist, IIS generates a 404 error.
Now, because this is an AngularJS app, and it supports html5mode
, I will have some routes that aren't physical files on the server, but are handled as routes in the AngularJS app. If a user were to drop onto an AngularJS (anything other than index.html
or a file that physically exists, in this example), I would get a 404 even though that route might be valid in the AngularJS app. Therefore, I need my OWIN middleware to return the index.html
file in the event a requested file does not exist, and let my AngularJS app figure out if it really is a 404.
If you're familiar with SPAs and AngularJS, this is a normal and straight-forward approach. If I were using MVC or ASP.NET routing, I could just set the default route to an MVC controller that returns my index.html
, or something along those lines. However, I've already stated I'm not using MVC and I'm trying to keep this as simple and lightweight as possible.
This user had a similar dilemma and solved it with IIS rewriting. In my case, it doesn't work because a) my content doesn't physically exist where the rewrite URL module can find it, so it always returns index.html
and b) I want something that doesn't rely on IIS, but is handled within OWIN middleware so it can be used flexibly.
TL;DNR me, for crying out loud.
Simple, how can I intercept a 404 Not Found and return the content of (note: not redirect) my FileServer
-served index.html
using OWIN middleware?
I wrote this little middleware component, but I don't know if it's overkill, inefficient, or if there are other pitfalls. Basically it just takes in the same
FileServerOptions
theFileServerMiddleware
uses, the most important part being theFileSystem
we're using. It is placed before the aforementioned middleware and does a quick check to see if the requested path exists. If not, the request path is rewritten as "index.html", and the normal StaticFileMiddleware will take over from there.Obviously it could stand to be cleaned up for reuse, including a way to define different default files for different root paths (e.g. anything requested from "/feature1" that is missing should use "/feature1/index.html", likewise with "/feature2" and "/feature2/default.html", etc.).
But for now, it this works for me. This has a dependency on Microsoft.Owin.StaticFiles, obviously.