如何将 Web Api 控制器优先于 IHttpHandler?
How to prioritize Web Api Controllers over IHttpHandler?
我有一个遗留项目,它有一个实现 class 的单个 IHttpHandler,它使用巨大的 switch 语句等路由所有请求。我正在尝试使用 ApiControllers 引入属性路由,但是第一个总是有优先权。是否可以配置系统(代码或 IIS),以便 Web Api 控制器优先于我实现 class 的单个 IHttpHandler?在 IIS 中,我将我的 AttributeRouting 放在第一位,然后是所有的 aspx,但仍然没有首先处理 Web Api 控制器。无论我做什么(将它们放在同一个项目下)。不想单独介绍项目了
编辑: 有一个 IHttpModule 根据 api/ 之后的内容决定将其路由到特定的 ashx 文件。其中之一就是所描述的..
编辑 2: 更具体地说:如果 uri 没有过滤的内容列表 [file,message,属性 ...] 它被路由至 Resource.aspx
所以 api/file、api/message、api/property 将从其他 .ashx 文件处理 - 否则流量会转到 Resource.ashx...
因此,具有 api/endpoint1、api/endpoint2、api/endpoint3 的请求
都会去Resource.aspx。问题是如何将 api/endpoint3 路由到下面描述的 API 控制器。
谢谢
简化的代码架构:
//SolutionName/Api/MyModule.cs (Legacy Code)
//this routes based on what is after api/ to Resource.ashx or other ashx files
public class MyModule : IHttpModule {
//if url doesn't contain [file,message,property ...] route to Resource.ashx
}
//SolutionName/API/Resource.ashx (Legacy Code)
//this is hit at any request solutionname/api/anything
public class DefaultHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context) {
String APIBranch = parse(context);
switch(APIBranch)
{
case "endpoint1": methodOne(); break;
case "endpoint2": methodTwo(); break;
[...]
default: throw Exception(); break;
}
}
}
//SolutionName/API/App_Start/AttributeRoutingHttpConfig.cs
public static class AttributeRoutingHttpConfig
{
public static void RegisterRoutes(HttpRouteCollection routes)
{
// See http://github.com/mccalltd/AttributeRouting/wiki for more options.
// To debug routes locally using the built in ASP.NET development server, go to /routes.axd
routes.MapHttpAttributeRoutes();
}
public static void Start()
{
RegisterRoutes(GlobalConfiguration.Configuration.Routes);
}
}
//SolutionName/API/Controllers/MyController.cs
//this should have been hit for a GET on solutionname/api/endpoint3/id
[RoutePrefix("endpoint3")]
public class MyController : ApiController
{
private IModelDao modelDao;
MyController(IModelDao modelDao){
this.modelDao = modelDao;
}
[Route("{id}")]
[HttpGet]
public Model GetSomething(int id)
{
Model model = modelDao.GetSomething(id);
return model;
}
}
我找到了解决这个问题的两个方法。第一个是通过插入检查 Web API 路由系统是否可以处理请求来修改重写 url 的模块。第二个是向应用程序添加另一个模块,它将使用 HttpContext.RemapHandler()
.
将请求定向到 Web API 处理程序
代码如下:
第一个解决方案。
如果你的模块是这样的:
public class MyModule: IHttpModule
{
public void Dispose(){}
public void Init(HttpApplication context)
{
context.BeginRequest += (object Sender, EventArgs e) =>
{
HttpContext httpContext = HttpContext.Current;
string currentUrl = httpContext.Request.Url.LocalPath.ToLower();
if (currentUrl.StartsWith("/api/endpoint0") ||
currentUrl.StartsWith("/api/endpoint1") ||
currentUrl.StartsWith("/api/endpoint2"))
{
httpContext.RewritePath("/api/resource.ashx");
}
};
}
}
那么你需要这样改:
public void Init(HttpApplication context)
{
context.BeginRequest += (object Sender, EventArgs e) =>
{
HttpContext httpContext = HttpContext.Current;
var httpRequestMessage = new HttpRequestMessage(
new HttpMethod(httpContext.Request.HttpMethod),
httpContext.Request.Url);
IHttpRouteData httpRouteData =
GlobalConfiguration.Configuration.Routes.GetRouteData(httpRequestMessage);
if (httpRouteData != null) //enough if WebApiConfig.Register is empty
return;
string currentUrl = httpContext.Request.Url.LocalPath.ToLower();
if (currentUrl.StartsWith("/api/endpoint0") ||
currentUrl.StartsWith("/api/endpoint1") ||
currentUrl.StartsWith("/api/endpoint2"))
{
httpContext.RewritePath("/api/resource.ashx");
}
};
}
第二种解法。
重新映射处理程序的模块:
public class RemappingModule: IHttpModule
{
public void Dispose() { }
public void Init(HttpApplication context)
{
context.PostResolveRequestCache += (src, args) =>
{
HttpContext httpContext = HttpContext.Current;
string currentUrl = httpContext.Request.FilePath;
if (!string.IsNullOrEmpty(httpContext.Request.QueryString.ToString()))
currentUrl += "?" + httpContext.Request.QueryString;
//checking if url was rewritten
if (httpContext.Request.RawUrl != currentUrl)
{
//getting original url
string url = string.Format("{0}://{1}{2}",
httpContext.Request.Url.Scheme,
httpContext.Request.Url.Authority,
httpContext.Request.RawUrl);
var httpRequestMessage = new HttpRequestMessage(
new HttpMethod(httpContext.Request.HttpMethod), url);
//checking if Web API routing system can find route for specified url
IHttpRouteData httpRouteData =
GlobalConfiguration.Configuration.Routes.GetRouteData(httpRequestMessage);
if (httpRouteData != null)
{
//to be honest, I found out by experiments, that
//context route data should be filled that way
var routeData = httpContext.Request.RequestContext.RouteData;
foreach (var value in httpRouteData.Values)
routeData.Values.Add(value.Key, value.Value);
//rewriting back url
httpContext.RewritePath(httpContext.Request.RawUrl);
//remapping to Web API handler
httpContext.RemapHandler(
new HttpControllerHandler(httpContext.Request.RequestContext.RouteData));
}
}
};
}
}
这些解决方案在方法 WebApiConfig.Register
为空时有效,但如果存在带有模板 "api/{controller}"
的路由,则任何包含两个以 "api" 开头的段的路径都将通过检查,即使如果没有具有指定名称的控制器,并且您的模块可以为此路径执行一些用户操作。在这种情况下,例如,您可以使用 this answer 中的方法来检查控制器是否存在。
此外,Web API 路由系统将接受路由,即使找到的控制器不处理当前 http 方法的请求。您可以使用 RouteFactoryAttribute
和 HttpMethodConstraint
的后代来避免这种情况。
UPD 在此控制器上测试:
[RoutePrefix("api/endpoint1")]
public class DefaultController : ApiController
{
[Route("{value:int}")]
public string Get(int value)
{
return "TestController.Get: value=" + value;
}
}
[RoutePrefix("api/endpoint2")]
public class Endpoint2Controller : ApiController
{
[Route("segment/segment")]
public string Post()
{
return "Endpoint2:Post";
}
}
我有一个遗留项目,它有一个实现 class 的单个 IHttpHandler,它使用巨大的 switch 语句等路由所有请求。我正在尝试使用 ApiControllers 引入属性路由,但是第一个总是有优先权。是否可以配置系统(代码或 IIS),以便 Web Api 控制器优先于我实现 class 的单个 IHttpHandler?在 IIS 中,我将我的 AttributeRouting 放在第一位,然后是所有的 aspx,但仍然没有首先处理 Web Api 控制器。无论我做什么(将它们放在同一个项目下)。不想单独介绍项目了
编辑: 有一个 IHttpModule 根据 api/ 之后的内容决定将其路由到特定的 ashx 文件。其中之一就是所描述的..
编辑 2: 更具体地说:如果 uri 没有过滤的内容列表 [file,message,属性 ...] 它被路由至 Resource.aspx
所以 api/file、api/message、api/property 将从其他 .ashx 文件处理 - 否则流量会转到 Resource.ashx...
因此,具有 api/endpoint1、api/endpoint2、api/endpoint3 的请求 都会去Resource.aspx。问题是如何将 api/endpoint3 路由到下面描述的 API 控制器。 谢谢
简化的代码架构:
//SolutionName/Api/MyModule.cs (Legacy Code)
//this routes based on what is after api/ to Resource.ashx or other ashx files
public class MyModule : IHttpModule {
//if url doesn't contain [file,message,property ...] route to Resource.ashx
}
//SolutionName/API/Resource.ashx (Legacy Code)
//this is hit at any request solutionname/api/anything
public class DefaultHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context) {
String APIBranch = parse(context);
switch(APIBranch)
{
case "endpoint1": methodOne(); break;
case "endpoint2": methodTwo(); break;
[...]
default: throw Exception(); break;
}
}
}
//SolutionName/API/App_Start/AttributeRoutingHttpConfig.cs
public static class AttributeRoutingHttpConfig
{
public static void RegisterRoutes(HttpRouteCollection routes)
{
// See http://github.com/mccalltd/AttributeRouting/wiki for more options.
// To debug routes locally using the built in ASP.NET development server, go to /routes.axd
routes.MapHttpAttributeRoutes();
}
public static void Start()
{
RegisterRoutes(GlobalConfiguration.Configuration.Routes);
}
}
//SolutionName/API/Controllers/MyController.cs
//this should have been hit for a GET on solutionname/api/endpoint3/id
[RoutePrefix("endpoint3")]
public class MyController : ApiController
{
private IModelDao modelDao;
MyController(IModelDao modelDao){
this.modelDao = modelDao;
}
[Route("{id}")]
[HttpGet]
public Model GetSomething(int id)
{
Model model = modelDao.GetSomething(id);
return model;
}
}
我找到了解决这个问题的两个方法。第一个是通过插入检查 Web API 路由系统是否可以处理请求来修改重写 url 的模块。第二个是向应用程序添加另一个模块,它将使用 HttpContext.RemapHandler()
.
代码如下:
第一个解决方案。
如果你的模块是这样的:
public class MyModule: IHttpModule
{
public void Dispose(){}
public void Init(HttpApplication context)
{
context.BeginRequest += (object Sender, EventArgs e) =>
{
HttpContext httpContext = HttpContext.Current;
string currentUrl = httpContext.Request.Url.LocalPath.ToLower();
if (currentUrl.StartsWith("/api/endpoint0") ||
currentUrl.StartsWith("/api/endpoint1") ||
currentUrl.StartsWith("/api/endpoint2"))
{
httpContext.RewritePath("/api/resource.ashx");
}
};
}
}
那么你需要这样改:
public void Init(HttpApplication context)
{
context.BeginRequest += (object Sender, EventArgs e) =>
{
HttpContext httpContext = HttpContext.Current;
var httpRequestMessage = new HttpRequestMessage(
new HttpMethod(httpContext.Request.HttpMethod),
httpContext.Request.Url);
IHttpRouteData httpRouteData =
GlobalConfiguration.Configuration.Routes.GetRouteData(httpRequestMessage);
if (httpRouteData != null) //enough if WebApiConfig.Register is empty
return;
string currentUrl = httpContext.Request.Url.LocalPath.ToLower();
if (currentUrl.StartsWith("/api/endpoint0") ||
currentUrl.StartsWith("/api/endpoint1") ||
currentUrl.StartsWith("/api/endpoint2"))
{
httpContext.RewritePath("/api/resource.ashx");
}
};
}
第二种解法。
重新映射处理程序的模块:
public class RemappingModule: IHttpModule
{
public void Dispose() { }
public void Init(HttpApplication context)
{
context.PostResolveRequestCache += (src, args) =>
{
HttpContext httpContext = HttpContext.Current;
string currentUrl = httpContext.Request.FilePath;
if (!string.IsNullOrEmpty(httpContext.Request.QueryString.ToString()))
currentUrl += "?" + httpContext.Request.QueryString;
//checking if url was rewritten
if (httpContext.Request.RawUrl != currentUrl)
{
//getting original url
string url = string.Format("{0}://{1}{2}",
httpContext.Request.Url.Scheme,
httpContext.Request.Url.Authority,
httpContext.Request.RawUrl);
var httpRequestMessage = new HttpRequestMessage(
new HttpMethod(httpContext.Request.HttpMethod), url);
//checking if Web API routing system can find route for specified url
IHttpRouteData httpRouteData =
GlobalConfiguration.Configuration.Routes.GetRouteData(httpRequestMessage);
if (httpRouteData != null)
{
//to be honest, I found out by experiments, that
//context route data should be filled that way
var routeData = httpContext.Request.RequestContext.RouteData;
foreach (var value in httpRouteData.Values)
routeData.Values.Add(value.Key, value.Value);
//rewriting back url
httpContext.RewritePath(httpContext.Request.RawUrl);
//remapping to Web API handler
httpContext.RemapHandler(
new HttpControllerHandler(httpContext.Request.RequestContext.RouteData));
}
}
};
}
}
这些解决方案在方法 WebApiConfig.Register
为空时有效,但如果存在带有模板 "api/{controller}"
的路由,则任何包含两个以 "api" 开头的段的路径都将通过检查,即使如果没有具有指定名称的控制器,并且您的模块可以为此路径执行一些用户操作。在这种情况下,例如,您可以使用 this answer 中的方法来检查控制器是否存在。
此外,Web API 路由系统将接受路由,即使找到的控制器不处理当前 http 方法的请求。您可以使用 RouteFactoryAttribute
和 HttpMethodConstraint
的后代来避免这种情况。
UPD 在此控制器上测试:
[RoutePrefix("api/endpoint1")]
public class DefaultController : ApiController
{
[Route("{value:int}")]
public string Get(int value)
{
return "TestController.Get: value=" + value;
}
}
[RoutePrefix("api/endpoint2")]
public class Endpoint2Controller : ApiController
{
[Route("segment/segment")]
public string Post()
{
return "Endpoint2:Post";
}
}