Comment intercepter 404 à l'aide du middleware Owin
Contexte
Laissez-moi d'abord vous expliquer le contexte. Je travaille sur un projet qui tente de marier un serveur d'arrière-plan qui utilise l'API Web configurée via OWIN - hébergé sur IIS maintenant, mais potentiellement d'autres hôtes pris en charge par OWIN dans le futur - à une interface utilisant AngularJS.
Le frontend AngularJS est un contenu entièrement statique. J'évite complètement les technologies côté serveur telles que MVC / Razor, WebForms, Bundles, tout ce qui a à voir avec le frontend et les actifs qu'il utilise, et je m'en remets plutôt aux dernières et meilleures techniques utilisant Node.js, Grunt / Gulp, etc. .pour gérer la compilation CSS, le regroupement, la minification, etc. schéma ci-dessous).
MyProject.sln
server
MyProject.Host
MyProject.Host.csproj
Startup.cs
(etc.)
frontend
MyProjectApp
app.js
index.html
MyProjectApp.njproj
(etc.)
Donc, en ce qui concerne le frontend, tout ce que j'ai à faire est de demander à mon hôte de servir mon contenu statique. Dans Express.js, c'est trivial. Avec OWIN, j'ai pu le faire facilement en utilisant le middleware Microsoft.Owin.StaticFiles , et cela fonctionne très bien (c'est très élégant).
Voici ma 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);
La prise
Fondamentalement, il héberge simplement tout frontend/MyProjectApp
comme s'il se trouvait à l'intérieur de la racine de MyProject.Host. Alors naturellement, si vous demandez un fichier qui n'existe pas, IIS génère une erreur 404.
Maintenant, comme il s'agit d'une application AngularJS et qu'elle prend en charge html5mode
, j'aurai des routes qui ne sont pas des fichiers physiques sur le serveur, mais qui sont gérées comme des routes dans l'application AngularJS. Si un utilisateur devait déposer sur un AngularJS (autre chose que index.html
ou un fichier qui existe physiquement, dans cet exemple), j'obtiendrais un 404 même si cette route pourrait être valide dans l'application AngularJS. Par conséquent, j'ai besoin de mon middleware OWIN pour renvoyer le index.html
fichier dans le cas où un fichier demandé n'existe pas, et laisser mon application AngularJS déterminer s'il s'agit vraiment d'un 404.
Si vous êtes familier avec les SPA et AngularJS, il s'agit d'une approche normale et simple. Si j'utilisais le routage MVC ou ASP.NET, je pourrais simplement définir la route par défaut vers un contrôleur MVC qui renvoie my index.html
, ou quelque chose du genre. Cependant, j'ai déjà déclaré que je n'utilisais pas MVC et que j'essayais de garder cela aussi simple et léger que possible.
Cet utilisateur a eu un dilemme similaire et l'a résolu avec la réécriture d'IIS. Dans mon cas, cela ne fonctionne pas car a) mon contenu n'existe pas physiquement là où le module d'URL de réécriture peut le trouver, donc il revient toujoursindex.html
et b) je veux quelque chose qui ne repose pas sur IIS, mais qui est géré dans Middleware OWIN afin qu'il puisse être utilisé de manière flexible.
TL; DNR moi, pour avoir crié à haute voix.
Simple, comment puis-je intercepter un 404 Not Found et renvoyer le contenu de (note: ne pas rediriger) mon FileServer
-servé en index.html
utilisant le middleware OWIN?
J'ai écrit ce petit composant middleware, mais je ne sais pas s'il est excessif, inefficace ou s'il y a d'autres pièges. Fondamentalement , il faut juste la même FileServerOptions
les FileServerMiddleware
utilisations, la plus importante étant la partie FileSystem
que nous utilisons. Il est placé avant le middleware susmentionné et effectue une vérification rapide pour voir si le chemin demandé existe. Sinon, le chemin de la requête est réécrit comme "index.html", et le StaticFileMiddleware normal prendra le relais à partir de là.
Évidemment, il pourrait être nettoyé pour être réutilisé, y compris un moyen de définir différents fichiers par défaut pour différents chemins racine (par exemple, tout ce qui manque à "/ feature1" devrait utiliser "/feature1/index.html", de même avec "/ feature2 "et" /feature2/default.html ", etc.).
Mais pour l'instant, cela fonctionne pour moi. Cela dépend évidemment de Microsoft.Owin.StaticFiles.
public class DefaultFileRewriterMiddleware : OwinMiddleware
{
private readonly FileServerOptions _options;
/// <summary>
/// Instantiates the middleware with an optional pointer to the next component.
/// </summary>
/// <param name="next"/>
/// <param name="options"></param>
public DefaultFileRewriterMiddleware(OwinMiddleware next, FileServerOptions options) : base(next)
{
_options = options;
}
#region Overrides of OwinMiddleware
/// <summary>
/// Process an individual request.
/// </summary>
/// <param name="context"/>
/// <returns/>
public override async Task Invoke(IOwinContext context)
{
IFileInfo fileInfo;
PathString subpath;
if (!TryMatchPath(context, _options.RequestPath, false, out subpath) ||
!_options.FileSystem.TryGetFileInfo(subpath.Value, out fileInfo))
{
context.Request.Path = new PathString(_options.RequestPath + "/index.html");
}
await Next.Invoke(context);
}
#endregion
internal static bool PathEndsInSlash(PathString path)
{
return path.Value.EndsWith("/", StringComparison.Ordinal);
}
internal static bool TryMatchPath(IOwinContext context, PathString matchUrl, bool forDirectory, out PathString subpath)
{
var path = context.Request.Path;
if (forDirectory && !PathEndsInSlash(path))
{
path += new PathString("/");
}
if (path.StartsWithSegments(matchUrl, out subpath))
{
return true;
}
return false;
}
}