简体   繁体   中英

Configuring Web API with URL Rewrite installed on IIS

We have a Web Forms application that uses ASP.Net URL Rewrite 2.0 . The following rewrite rule is configured to enable access to certain pages without specifying the .ashx extension:

<rule name="RewriteASHX">
    <match url="(customers|orders|products.*)"/>
    <conditions logicalGrouping="MatchAll">
      <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true"/>
      <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true"/>
    </conditions>
    <action type="Rewrite" url="{R:1}.ashx"/>
</rule>

Using Web API, we want to gradually replace our Web Forms-based API pages with Web API controllers. With URL Rewrite installed, only controllers matching the above rule will route correctly to the Web API controller. (Adding exclusions to the URL Rewrite rules seems to not let the request make it to the controller at all, giving a 404 result.)

The Web API controllers are set up in the following manner:

public class CustomersController : ApiController
{
    [Route("api/v2/customers")]
    [ResponseType(typeof(CustomerCollection))]
    public IHttpActionResult Get()
    {
        ...
    }

    [Route("api/v2/customers/{identifier}")]
    [ResponseType(typeof(Customer))]
    public IHttpActionResult Get(string identifier)
    {
        ...
    }
}

As described above, we have only been able to get Web API requests to route if they match a URL Rewrite condition.

The parameterless api/v2/customers in CustomerController works because it matches <match url="(customers|orders|products.*)"/> , but api/v2/customers/C123 fails because it does not match the rewrite condition.

What is the best way to configure URL Rewrite to allow Web API requests to route properly?

The URL Rewrite module basically has 2 different functions:

  1. To rewrite an incoming URL to make it "look" like another URL to the web server
  2. To redirect an incoming URL to another URL

Since .NET Routing was introduced (which is for ASP.NET, ASP.NET MVC and Web API), URL rewriting is largely unnecessary. Routing is far better. With routing, the incoming request is sent exactly to the destination controller method. With rewriting, the HTTP context is rewritten to fool ASP.NET to send the request somewhere else, which can cause issues for things that rely on HTTP context values to work.

Redirecting on the other hand, is still something quite useful to do with URL Rewrite. Especially when it comes to legacy URLs from an old application you are upgrading. To make sure all of the old URLs work (and without damaging search engine rankings) use 301 redirects. 301 redirects can be done in MVC as well as in URL Rewrite.


So, my suggestion is to switch to routing from URL Rewrite for all of your "rewrites", which are no longer really necessary because of routing. You can setup a configuration with routing that is similar to your rewrites to reach your .ashx pages.

routes.MapPageRoute(
    routeName: "RouteASHX",
    routeUrl: "{ashxPage}", 
    physicalFile: "~/{ashxPage}.ashx",
    checkPhysicalUrlAccess: false,
    defaults: new RouteValueDictionary(),
    constraints: new RouteValueDictionary(new { ashxPage = @"customers|orders|products.*" }));

we have only been able to get Web API requests to route if they match a URL Rewrite condition (eg, the parameterless api/v2/customers in CustomerController works because it matches <match url="(customers|orders|products.*)"/> , but api/v2/customers/C123 fails because it does not match the rewrite condition).

This is another unrelated issue, that is due to using attribute routing with similar paths and undefined order. An issue with attribute routing is the fact that .NET Attributes by definition have an undefined order. Routing, on the other hand is setup so the first match always wins . These 2 goals are contradictory, and that is where you have been bitten. See Attribute vs Conventional Routing for details.

Fortunately, it isn't hard to fix. There is an Order property that you can set to manually override the arbitrary order in which .NET attributes are applied, putting the more specific route before the less specific.

public class CustomersController : ApiController
{
    [Route("api/v2/customers", Order = 2)]
    [ResponseType(typeof(CustomerCollection))]
    public IHttpActionResult Get()
    {
        ...
    }

    [Route("api/v2/customers/{identifier}", Order = 1)]
    [ResponseType(typeof(Customer))]
    public IHttpActionResult Get(string identifier)
    {
        ...
    }
}

Applying this configuration will make your Web API work without the need for URL rewriting.

If you are sure your Web API project will only handle incoming requests to "api/v2/...", then you can write a new rule at top to break,

<rule name="break" stopProcessing="true">
 <match url="^api/v2/(.*)" />
 <action type="None" />
</rule>

More details can be found in my blog post .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM