在ASP.NET MVC中使用IIS级别的URL Rewrite
原文 在ASP.NET MVC中使用IIS级别的URL Rewrite
大约一年半前,我在博客上写过一系列关于URL Rewrite的文章(2、3、4),把ASP.NET平台上进行URL Rewrit的方式和各自地特点进行了较为详细的描述。应该来说,已经讲的非常具体,可以应对90%的情况。其实IIS Rewrite的原理非常容易理解,进行一些简单的变化和推断之后,便可以得出一些问题的原因和解决方案。现在我们就来看一个真实案例:在ASP.NET MVC中使用IIS级别的URL Rewrite。
在当时的文章中我谈到,URL Rewrite分有IIS级别和ASP.NET两种级别,并且各有各的特点和限制。在ASP.NET MVC中我们常用的方式是ASP.NET级别的URL Routing,它的作用是从URL中捕获数据并交给程序使用(当然还有“构造”的功能,稍候再谈)。因此,在ASP.NET MVC中我们往往不需要使用ASP.NET级别的URL Rewrite。而如今使用IIS级别的URL Rewrite,也正是因为有某些特殊问题无法回避才“不得已而为之”的。
以下涉及到的URL都以http://51programming.com为例,这个域名已经被我泛解析为127.0.0.1,如果您需要的话可以用它来做实验。
在许多年前,一个URL的Path就是普通的路径,而动态的参数,如查询路径,是通过Query String提供的,例如:
http://51programming.com/products?keywords=helloworld
为了避免混淆,在这里我们先来澄清一些概念。什么是URL,什么是Path,而什么是QueryString。例如在上面的地址,这三者分别是:
- URL:http://51programming.com/products?keywords=helloworld
- Path:http://51programming.com/products
- Query String:keywords=helloworld
后来SEO兴起之后,有人说这样的“动态地址”不利于搜索引擎中的权重优化,因此建议把关键字作为Path的一部分。于是就出现了这样的URL:
http://51programming.com/products/helloworld
这么看来问题并不大,但是您要知道,关键字往往是由用户输入的,可能会输入特殊字符。例如,如果用户输入了“200%”作为关键字,则两种形式下的URL就分别是:
http://51programming.com/products?keywords=200%25
http://51programming.com/products/200%25
如果您尝试一下便可以知道,第一个URL可以正常访问,而第二个URL便会引发Bad Request异常:

这是因为URL的Path部分出现了特殊字符,而这种字符只能出现在Query String中。
看到这个画面,您还意识到了什么信息?在定位问题的原因,以及设法解决问题的时候,首先要明确的是到底是哪里出现了问题。例如看到这个画面,您应该清楚地意识到一点:这是ASP.NET抛出的异常,换句话说,IIS并没有把它当作是非法的URL,它还是老老实实地将URL交给ASP.NET ISAPI处理。因此,我们便可以动用IIS级别的URL Rewrite,在进入ASP.NET执行引擎之前,就把URL替换成可接受的形式:
RewriteRule ^/products/([^\?]*)\?(.+) /products?$2&keywords=$1 [I,L,U]
RewriteRule ^/products/([^\?]*) /products?keywords=$1 [I,L,U]
第一行应对的是带有Query String的情况,而第二行则是没有Query String的情况。这里用到的组件是IIRF(Ionic's Isapi Rewrite Filter),这是一款开源产品,一年半前的文章里我推荐的也是这个,现在它已经有了升级。它的功能便是在进入ASP.NET ISAPI之前,就将URL重写为其他形式:

原本在第3步会出现的Bad Request,由于已经在第2步被URL Rewrite成合法的形式。因此剩余的处理也就没有任何问题了。
这些内容在一年半前的文章内已经提过,不过现在既然有了ASP.NET MVC,则事情又变得更为复杂。因为ASP.NET Routing除了“匹配”URL的功能之外,还担负着“组装”URL的职责。因此,让ASP.NET Routing能够识别出Rewrite后的URL不难,但是如何同时让它又可以“组装”出Rewrite前的URL,这就需要一些小技巧了。例如以下的Route配置只能识别出URL输入(/products?keywords=xxx)但不能组装出我们需要的URL(/products/xxx):
routes.MapRoute(
"Product.List",
"products",
new { controller = "Product", action = "List" });
因此,我们必须这么做:
routes.MapRoute(
"Product.List",
"products/{*keywords}",
new { controller = "Product", action = "List", keywords = "" });
请注意我们让keywords匹配Path后端全部内容,而由于我们又提供了keywords的默认值,因此即使是“/products”这样的Path输入,也能正确匹配到这条Route规则——只不过此时的Route Value中的keywords字段已经不是用户输入的内容了(因为用户输入的/products/xxx,已经被重写为/products?keywords=xxx)。换句话说,如果有如下的Action,那么它的keywords参数则永远是空字符串:
public ActionResult List(string keywords) { ... }
幸好,ASP.NET MVC中存在Model Binder机制,我们可以编写一个Model Binder来指定这个参数的获取位置:
public class FromQueryBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
return controllerContext.HttpContext.Request.QueryString[bindingContext.ModelName];
}
}
再将其运用到List的keywords参数上去:
public ActionResult List(
[ModelBinder(typeof(FromQueryBinder))]string keywords)
由于参数名是keywords,因此bindingContext.ModelName也是keywords,于是从Query String中便可以取到我们需要的内容了。至于在进行URL生成的时候,我们还是可以之间一样添加一个keywords字段到Route Value中去,于是在我们先前配置的Route规则中便会组装成合适的Path了(即/products/xxx)。
在这个例子中,我们让keywords匹配Path后端全部内容,但是如果是Path中间某一段需要有特殊字符怎么办呢?其实也一样,只是在进行URL Rewrite的时候,需要在最终重写的时候填写一个“假”的值就可以了,如这样的Route规则:
routes.MapRoute(
"Product.List",
"products/{keywords}/page",
new { controller = "Product", action = "List" });
而IIS级别的URL Rewrite重写的规则就可以是:
RewriteRule ^/products/([^/]*)/(.*) /products/useless-segement/$2?keywords=$1 [I,L,U]
这样,如果用户输入/products/xxx/2就会被重写成/products/useless-token/2?keywords=xxx——事实上,在第一个示例中我们也可以这么做,只是我“不习惯”增加一个伪造的值而已。
以上解决方案可以在IIS 6与IIS 7的Classic Mode中正常使用,只可惜在IIS 7的Intergrated Mode中,可能是由于ASP.NET接管了IIS的部分逻辑,因此会很早抛出“IIS级别”,而不是“ASP.NET级别”的Bad Request异常。如果您遇到了这种方式,就必须通过以下三个步骤来摆脱这个麻烦的问题了:
- 设置AllowRestrictedChars:KB820129(让IIS 7接受特殊字符)
- 设置VerificationCompatibility:KB826437中除了“安装.NET 1.1 SP1”以外的步骤(让ASP.NET接受特殊字符)
- 将ASP.NET页面的ValidateRequest设为False
其实您只要经过了这三步修改,对于目前这个案例,即使不用IIS级别的URL Rewrite应该也没有问题了。
在ASP.NET MVC中使用IIS级别的URL Rewrite的更多相关文章
- asp.net mvc 中 一种简单的 URL 重写
asp.net mvc 中 一种简单的 URL 重写 Intro 在项目中想增加一个公告的功能,但是又不想直接用默认带的那种路由,感觉好low逼,想弄成那种伪静态化的路由 (别问我为什么不直接静态化, ...
- windows server 证书的颁发与IIS证书的使用 Dapper入门使用,代替你的DbSQLhelper Asp.Net MVC中Action跳转(转载)
windows server 证书的颁发与IIS证书的使用 最近工作业务要是用服务器证书验证,在这里记录下一. 1.添加服务器角色 [证书服务] 2.一路下一步直到证书服务安装完成; 3.选择圈选 ...
- ASP.NET MVC 中应用Windows服务以及Webservice服务开发分布式定时器
ASP.NET MVC 中应用Windows服务以及Webservice服务开发分布式定时器一:闲谈一下:1.现在任务跟踪管理系统已经开发快要结束了,抽一点时间来写一下,想一想自己就有成就感啊!! ...
- ASP.NET MVC中使用异步控制器
线程池 一直想把项目改写成异步,但是ASP.NETMVC3下写的过于繁琐,.NET 4.5与ASP.NET MVC下代码写起来就比较简单了, MS好像也一直喜欢这样搞,每一个成熟的东西,都要演变好几个 ...
- Asp.net mvc 中的 Controller 的激活
Controller 激活是指根据路由系统解析出来的 Controller 的名称创建 控制器(Controller)的过程,这里的控制器泛指实现了 IController 接口的类型 激活过程中的核 ...
- Asp.net mvc 中View 的呈现(二)
[toc] 上一节介绍了 Asp.net mvc 中除 ViewResult 外的所有的 ActionResult,这一节介绍 ViewResult. ViewResultBase ViewResul ...
- ASP.NET MVC中注册Global.asax的Application_Error事件处理全局异常
在ASP.NET MVC中,通过应用程序生命周期中的Application_Error事件可以捕获到网站引发的所有未处理异常.本文作为学习笔记,记录了使用Global.asax文件的Applicati ...
- ASP.NET MVC中的Session设置
最近在ASP.NET MVC项目中碰到这样的情况:在一个controller中设置了Session,但在另一个controller的构造函数中无法获取该Session,会报"System.N ...
- 在 ASP.NET MVC 中使用异步控制器
线程池 一直想把项目改写成异步,但是ASP.NETMVC3下写的过于繁琐,.NET 4.5与ASP.NET MVC下代码写起来就比较简单了, MS好像也一直喜欢这样搞,每一个成熟的东西,都要演变好几个 ...
随机推荐
- 【007】【JVM——内存分配和恢复策略】
内存分配与收回策略 JVM的自己主动内存管理要自己主动化地解决两个问题:对象分配内存以及回收分配给对象的内存.回收内存前几篇已经讲了.如今说内存分配.对象的内存分配一般分配在堆内存中,也可能经过 ...
- Spring.Net控制翻转、依赖注入、面向切面编程
Spring.Net快速入门:控制翻转.依赖注入.面向切面编程 Spring.Net主要功能: 1.IoC:控制翻转(Inversion of Control) 理解成抽象工厂翻转控制:就是创建对象 ...
- 策略模式Strategy——坐什么车回家?
1.存在的问题和模型 :2014年6月 学校:廊坊师范 家:石家庄 人物:学生 又快到期末考试了.回家的节奏也奔上日程,无聊之余就想想这次回家的事儿. 对我来说回 ...
- 使用JavaScript检测浏览器
假设你真的需要检测浏览器的类型,使用JavaScript非常easy达到. View Demo Download Source from GitHub JavaScript有一个navigator的标 ...
- SqlServer 添加列并赋值
有个需求,需要给某张表添加一列并且赋值,分解需求,一共分两部走: 添加列 赋值 两个功能都不难,很快实现. --add column alter table Med_Summary_Template ...
- Quartz.net开源作业调度
Quartz.net开源作业调度框架使用详解 前言 quartz.net作业调度框架是伟大组织OpenSymphony开发的quartz scheduler项目的.net延伸移植版本.支持 cron- ...
- nginx源代码分析--读请求主体(1)
首先,读取请求体已进入HTTP要求11相,我们需要做的请求正文部分处理一些模块,所以这个模块需要注册功能在这个阶段,在阅读功能要求的身体ngx_http_read_client_request_bod ...
- android利用jdk制作签名
Apk签名首先要有一个keystore的签名用的文件. keystore是由jdk自带的工具keytool生成的.详细生成方式參考一下: 開始->执行->cmd->cd 到你安装的j ...
- nisi 脚本示例
只是简单的copy文件和添加快捷方式,安装和卸载时对程序是否运行进行检测 ;-------------------------------- ;Include Modern UI !include & ...
- SDUT OJ 2463 学校password你必须学会科学计划
#include<iostream> #include<string.h> #include<stdio.h> #define N 10010 #define M ...