8

I'm trying to inject a custom middleware into my OWIN pipeline that wraps the StaticFileMiddleware available from MS in order to support HTML 5 mode in AngularJS. I've been following this guide: http://geekswithblogs.net/shaunxu/archive/2014/06/10/host-angularjs-html5mode-in-asp.net-vnext.aspx

From what I can gather of how this is supposed to work, my middleware is passing along requests to the static file middleware, and then if it can't resolve those requests (i.e., a request for an HTML 5 angular path, "/whatever"), it instead returns the base angular page so that a hard request for an HTML 5 path will work.

My problem is that the result of invoking the inner middleware always seems to be a 200 status code, even though in my browser I get a 404, which leaves me scratching my head. Here's my code for reference:

public static class AngularServerExtension
{
    public static IAppBuilder UseAngularServer(this IAppBuilder builder, string rootPath, string entryPath)
    {
        var options = new AngularServerOptions()
        {
            FileServerOptions = new FileServerOptions()
            {
                EnableDirectoryBrowsing = false,
                FileSystem = new PhysicalFileSystem(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, rootPath))
            },
            EntryPath = new PathString(entryPath)
        };

        builder.UseDefaultFiles(options.FileServerOptions.DefaultFilesOptions);
        return builder.Use(new Func<AppFunc, AppFunc>(next => new AngularServerMiddleware(next, options).Invoke));           
    }
}

public class AngularServerMiddleware
{
    private readonly AngularServerOptions _options;
    private readonly AppFunc _next;
    private readonly StaticFileMiddleware _innerMiddleware;

    public AngularServerMiddleware(AppFunc next, AngularServerOptions options)
    {
        _next = next;
        _options = options;

        _innerMiddleware = new StaticFileMiddleware(_next, options.FileServerOptions.StaticFileOptions);
    }

    public async Task Invoke(IDictionary<string, object> environment)
    {
        IOwinContext context = new OwinContext(environment);
        // try to resolve the request with default static file middleware
        await _innerMiddleware.Invoke(environment);
        Debug.WriteLine(context.Request.Path + ": " + context.Response.StatusCode);
        // *** Right here is where I would expect a 404 but I get a 200 when debugging,
        // even though my browser eventually returns a 404

        // route to root path if the status code is 404
        // and need support angular html5mode
        if (context.Response.StatusCode == 404 && _options.Html5Mode)
        {
            context.Request.Path = _options.EntryPath;
            await _innerMiddleware.Invoke(environment);
            Console.WriteLine(">> " + context.Request.Path + ": " + context.Response.StatusCode);
        }
    }
}
public class AngularServerOptions
{
    public FileServerOptions FileServerOptions { get; set; }

    public PathString EntryPath { get; set; }

    public bool Html5Mode
    {
        get
        {
            return EntryPath.HasValue;
        }
    }

    public AngularServerOptions()
    {
        FileServerOptions = new FileServerOptions();
        EntryPath = PathString.Empty;
    }
}
Joshua Barron
  • 1,532
  • 2
  • 26
  • 42

2 Answers2

17

From your question I am not sure whether you are using IIS or selfhost. If you are using IIS, there is a much cleaner/faster solution than messing up with owin middleware: You can use IIS rewrite engine, copy the following inside your web config.

<system.webServer>

<rewrite>
  <rules>
    <!--Redirect selected traffic to index -->
    <rule name="Index Rule" stopProcessing="true">
      <match url=".*" />
      <conditions logicalGrouping="MatchAll">
        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> 
        <add input="{REQUEST_URI}" matchType="Pattern" pattern="^/api/" negate="true" />
      </conditions>
      <action type="Rewrite" url="/index.html" />
    </rule>
  </rules>
</rewrite>
...
</system.webServer>

This line allows all files to be served normally:

<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> 

this line allows the api to be served normally

<add input="{REQUEST_URI}" matchType="Pattern" pattern="^/api/" negate="true" />

Everything else gets index.html

Jose Ch.
  • 3,856
  • 1
  • 20
  • 34
3

I didn't want to be tied to IIS, with the way asp.net core is moving forward. Here's how I got it to work using OWIN:

// catch all for html5/angular2 client routing urls that need to be redirected back to index.html
// for original, see: http://stackoverflow.com/questions/27036448/how-to-intercept-404-using-owin-middleware/30741479#30741479
app.Use(async (ctx, next) =>
{
    // execute the rest of the pipeline
    //  though really, we're last in this configuration
    //  but, this allows the other static file handlers
    //  and web api route handlers to fail
    await next();

    // double check that we have a 404
    //  we could also double check that we didn't request a file (with an extension of some sort)
    if (ctx.Response.StatusCode != 404)
    {
        return;
    }

    // we have a 404, serve our default index.html
    var middleware = new StaticFileMiddleware(
        env => next(), new StaticFileOptions
        {
            FileSystem = new PhysicalFileSystem("./wwwroot"),
            RequestPath = PathString.Empty
        });

    ctx.Request.Path = new PathString("/index.html");
    await middleware.Invoke(ctx.Environment);
});

I needed to call next() before I checked for the status code, because I assume the other middleware won't set the 404 until all middleware has a chance to handle it.

DISCLAIMER: I'm only starting to explore OWIN based hosting, so while this seems to work, there could be some non-best practices.

David Faivre
  • 2,302
  • 3
  • 23
  • 25