-
Notifications
You must be signed in to change notification settings - Fork 713
ASP.NET Core 2.0: Versioning causes routes to return 404 when UseMvc is behind a MapWhen #194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I'm having this same issue on a NET Core 2.0 app. Our API is using json rpc AND REST, so we currently use a .Map('/api') inside startup's Configure method to map requests to /api to use mvc. Any ideas on how to resolve this? |
The registration order can definitely matter. As it currently stands, the API versioning route handler injects itself into the pipeline as the last handler. This must be the case to ensure proper behavior. Unfortunately, it's possible for the selection process to be hit multiple times. To ensure all possible candidates have been considered (which was a different issue in 1.0.x), API versioning needs to go last. I've had some discussions with the ASP.NET team on how to address this, but nothing is likely to change in until ASP.NET Core 2.1+. Do either of you have a repro or configuration that you can share? There was a similar issue with JavaScript SPA handlers a few months back that we were able to get working. I suspect that the setup should be similar. |
I've attached a simple project that demonstrates the issue. Comment out the MapWhen and just use UseMvc() and it works. Swap the comments and use MapWhen instead and it 404s. You won't even get the constraint warning if you miss using the version query parameter. |
Great! Thanks. I'll have a look. |
OK - I made a few minor tweaks and things work the way I expect. Hopefully, it's the way you expect. First, let me say that I do not believe that you can make the route templates overlap with this configuration. If that's what you were hoping for, it may not be possible. It will certainly take a lot more investigation if that's the case and I can't promise the results will be positive. Here's what I changed:
Looks like this: [ApiVersion("1.0")]
[Route("api/[controller]")]
public class TestController : Controller { /* omitted */ }
// ... in Startup.cs
app.UseMvc();
app.MapWhen(
c => !c.Request.Path.Value.StartsWith("/api"),
b => b.Run(async c => await c.Response.WriteAsync("This is not MVC!"))); Here's the results:
Is this not what you were hoping to achieve? This setup is closely similar to what I documented in the known limitations for ASP.NET Core Routing, but perhaps you hadn't seen that. Let me know if I'm missing something here. Thanks. |
It looks like you have Mvc as the default and the non-Mvc as the fallback. Due to some requirements out of my control, I have to prevent some calls from going to Mvc in the first place based on conditions that do not involve the path. To simplify my requirements, just think of a custom header that must exist and be a specific value before it should route to Mvc. If this header says "Mvc", then Mvc will run, if it says "other" than another chain will be run. If it is neither, or doesn't exist, then the default should be a 404. If this header does not exist or is not equal to Mvc, then Mvc should not run even if some of the url's would match. That is why I had the UseMvc call inside the MapWhen. tldr; I have two different http webservices running in asp.net core and I need to be able to route them differently based on business logic. Either or, not both. Having a fallback as your example shows would not meet my requirements. |
I see. That makes more sense. For my own edification, is there some reason why you wouldn't just create your own custom middleware to handle this scenario in put it in front of MVC in the pipeline? It seems like your middleware would make a simple decision based on the header:
I'm working off the knowledge in your posts and example. I didn't see anything like this in the repro. I'm not sure exactly how complex your scenario is, but I suspect that app.UseLegacyRouting();
app.UseMvc(); Would be immensely clearer in intention and use for your application. Uploading your entire application isn't practical and it is often difficult to strip things down for a repro, but if you're able to expand your current repro to include this new information, I'm sure we can get something working. |
Maybe I simplified too much, let me explain what I'm trying to do. The application hosts two different web services. In the config, you can set the endpoints that will be used for each service. By default, http://:8080 and https://:8443 goes to our proprietary product, whereas http://:9090 and https://:9443 goes to Mvc. In the older version of the application, there was a http listener for each http service so they were separated at the http listener level. With aspnet core, there is a single listener that is configured to listen on all endpoints. I then used MapWhen to direct http:8080 & https:8443 to our product's middleware; and http:9090 & https:9443 to Mvc. As I understood, this created two separate pipelines. http:8080 and https:8443 cannot be passed to Mvc and vice-versa for the other endpoints. Any ideas on how I can do this while still using api versioning? |
A few questions:
As far as I know, there the request handler pipeline is divorced from the HTTP listener. This means that you should be able to have a middleware that redirects to a pipeline based on the incoming request or even use I'll see if I can't setup a sample application this way. |
Isn't MapWhen redirecting the pipeline? How is that different than what I'm doing? It'll still breaks api versioning. |
Thanks for clarifying. The pipeline is a Chain of Responsibility so you can think of it like this:
I'll see if I can't emulate your scenario. |
There may be other solutions, but I have a solution that should work for you. Your scenario is rather unique. To make it work, it seems the best option is to not call I setup the server like this: public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseKestrel( options =>
{
options.Listen( IPAddress.Loopback, 8080 );
options.Listen( IPAddress.Loopback, 9090 );
} )
.Build(); and then I configure the application like this: public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
IApiVersionRoutePolicy routePolicy )
{
if ( env.IsDevelopment() )
{
app.UseDeveloperExceptionPage();
}
app.MapWhen(
c => c.Request.Host.Port == 8080,
a => a.UseMvc().UseRouter( b => b.Routes.Add( routePolicy ) ) );
app.MapWhen(
c => c.Request.Host.Port == 9090,
b => b.Run( async c => await c.Response.WriteAsync( "This is not MVC!" ) ) );
} This will produced the following results:
Hopefully this gives you the skeleton you need to build your application. I've attached the working revision that I used. |
Did this solution work for you? |
Yes, appears to indeed work. Sorry for not trying it earlier, I was re-tasked to something else. |
No problem. Glad it worked. Let me know if you need anything else. |
After testing awhile, it looks like that if I place the UseMvc call behind a MapWhen, then all Mvc routes return a 404 with no errors.
If I comment out the MapWhen and place a UseMvc instead, the routes work.
Now, if I use MapWhen but comment out the AddApiVersioning, routes also work.
The text was updated successfully, but these errors were encountered: