|
| 1 | +using System; |
| 2 | +using System.Collections.Generic; |
1 | 3 | using Microsoft.AspNet.Authorization; |
2 | 4 | using Microsoft.AspNet.Builder; |
3 | 5 | using Microsoft.AspNet.Hosting; |
| 6 | +using Microsoft.AspNet.Http; |
4 | 7 | using Microsoft.AspNet.Identity.EntityFramework; |
| 8 | +using Microsoft.AspNet.Routing; |
5 | 9 | using Microsoft.Data.Entity; |
6 | 10 | using Microsoft.Extensions.Configuration; |
7 | 11 | using Microsoft.Extensions.DependencyInjection; |
@@ -108,15 +112,74 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF |
108 | 112 | // Add MVC to the request pipeline. |
109 | 113 | app.UseMvc(routes => |
110 | 114 | { |
111 | | - routes.MapRoute( |
112 | | - name: "default", |
113 | | - template: "{controller=Home}/{action=Index}/{id?}"); |
114 | | - |
115 | | - routes.MapRoute("spa-fallback", "{*anything}", new { controller = "Home", action = "Index" }); |
| 115 | + // Matches any request that doesn't appear to have a filename extension (defined as 'having a dot in the last URI segment'). |
| 116 | + // This means you'll correctly get 404s for /some/dir/non-existent-image.png instead of returning the SPA HTML. |
| 117 | + // However, it means requests like /customers/isaac.newton will *not* be mapped into the SPA, so if you need to accept |
| 118 | + // URIs like that you'll need to match all URIs, e.g.: |
| 119 | + // routes.MapbackRoute("spa-fallback", "{*anything}", new { controller = "Home", action = "Index" }); |
| 120 | + // (which of course will match /customers/isaac.png too - maybe that is a real customer name, not a PNG image). |
| 121 | + routes.MapSpaFallbackRoute("spa-fallback", new { controller = "Home", action = "Index" }); |
116 | 122 |
|
117 | 123 | // Uncomment the following line to add a route for porting Web API 2 controllers. |
118 | 124 | // routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}"); |
119 | 125 | }); |
120 | 126 | } |
121 | 127 | } |
| 128 | + internal static class SpaRouteExtensions { |
| 129 | + private const string ClientRouteTokenName = "clientRoute"; |
| 130 | + |
| 131 | + public static void MapSpaFallbackRoute(this IRouteBuilder routeBuilder, string name, object defaults, object constraints = null, object dataTokens = null) { |
| 132 | + MapSpaFallbackRoute(routeBuilder, name, /* templatePrefix */ (string)null, defaults, constraints, dataTokens); |
| 133 | + } |
| 134 | + |
| 135 | + public static void MapSpaFallbackRoute(this IRouteBuilder routeBuilder, string name, string templatePrefix, object defaults, object constraints = null, object dataTokens = null) |
| 136 | + { |
| 137 | + var template = CreateRouteTemplate(templatePrefix); |
| 138 | + |
| 139 | + var constraintsDict = ObjectToDictionary(constraints); |
| 140 | + constraintsDict.Add(ClientRouteTokenName, new SpaRouteConstraint()); |
| 141 | + |
| 142 | + routeBuilder.MapRoute(name, template, defaults, constraintsDict, dataTokens); |
| 143 | + } |
| 144 | + |
| 145 | + private static string CreateRouteTemplate(string templatePrefix) |
| 146 | + { |
| 147 | + templatePrefix = templatePrefix ?? string.Empty; |
| 148 | + |
| 149 | + if (templatePrefix.Contains("?")) { |
| 150 | + // TODO: Consider supporting this. The {*clientRoute} part should be added immediately before the '?' |
| 151 | + throw new ArgumentException("SPA fallback route templates don't support querystrings"); |
| 152 | + } |
| 153 | + |
| 154 | + if (templatePrefix.Contains("#")) { |
| 155 | + throw new ArgumentException("SPA fallback route templates should not include # characters. The hash part of a URI does not get sent to the server."); |
| 156 | + } |
| 157 | + |
| 158 | + if (templatePrefix != string.Empty && !templatePrefix.EndsWith("/")) { |
| 159 | + templatePrefix += "/"; |
| 160 | + } |
| 161 | + |
| 162 | + return templatePrefix + $"{{*{ ClientRouteTokenName }}}"; |
| 163 | + } |
| 164 | + |
| 165 | + private static IDictionary<string, object> ObjectToDictionary(object value) |
| 166 | + { |
| 167 | + return value as IDictionary<string, object> ?? new RouteValueDictionary(value); |
| 168 | + } |
| 169 | + |
| 170 | + private class SpaRouteConstraint : IRouteConstraint |
| 171 | + { |
| 172 | + public bool Match(HttpContext httpContext, IRouter route, string routeKey, IDictionary<string, object> values, RouteDirection routeDirection) |
| 173 | + { |
| 174 | + var clientRouteValue = (values[ClientRouteTokenName] as string) ?? string.Empty; |
| 175 | + return !HasDotInLastSegment(clientRouteValue); |
| 176 | + } |
| 177 | + |
| 178 | + private bool HasDotInLastSegment(string uri) |
| 179 | + { |
| 180 | + var lastSegmentStartPos = uri.LastIndexOf('/'); |
| 181 | + return uri.IndexOf('.', lastSegmentStartPos + 1) >= 0; |
| 182 | + } |
| 183 | + } |
| 184 | + } |
122 | 185 | } |
0 commit comments