线程问题、异常处理、自定义URL
本节又带了一些常用的,却很难理解的问题,本节从文件上传功能的实现引出了线程使用,介绍了线程饥饿的解决方法,异常处理方法,了解RouteTable自定义路径 。
系列文章
七天学会ASP.NET MVC (一)——深入理解ASP.NET MVC
七天学会ASP.NET MVC (二)——ASP.NET MVC 数据传递
七天学会ASP.NET MVC (三)——ASP.Net MVC 数据处理
七天学会ASP.NET MVC (五)——Layout页面使用和用户角色管理
七天学会ASP.NET MVC (六)——线程问题、异常处理、自定义URL
目录
实验27——添加批量上传选项
关于实验27
实验27存在的问题
解决方法
实验28——解决线程饥饿问题
实验29——异常处理—显示自定义错误页面
关于实验29
理解实验29中的限制
实验30—异常处理—日志异常
关于实验30
理解RouteTable
理解Asp.net MVC 请求周期
实验31—实现用户友好URLs
关于实验31
总结
实验27——添加批量上传选项
在实验27中,我们将提供一个选项,供用户选择上传Employee记录文件(CSV格式)。
我们会学习以下知识:
1. 如何使用文件上传控件
2. 异步控制器
1. 创建 FileUploadViewModel
在ViewModels文件夹下新建类“FileUploadViewModel”,如下:
1: public class FileUploadViewModel: BaseViewModel
2: {
3: public HttpPostedFileBase fileUpload {get; set ;}
4: }
HttpPostedFileBase 将通过客户端提供上传文件的访问入口。
2. 创建 BulkUploadController 和Index action 方法
新建 controller“BulkUploadController”,并实现Index Action 方法,如下:
1: public class BulkUploadController : Controller
2: {
3: [HeaderFooterFilter]
4: [AdminFilter]
5: public ActionResult Index()
6: {
7: return View(new FileUploadViewModel());
8: }
9: }
Index方法与 HeaderFooterFilter 和 AdminFilter属性绑定。HeaderFooterFilter会确保页眉和页脚数据能够正确传递到ViewModel中,AdminFilter限制非管理员用户的访问。
3.创建上传View
创建以上Action方法的View。View名称应为 index.cshtml,且存放在“~/Views/BulkUpload”文件夹下。
4. 设计上传View
在View中输入以下内容:
1: @using WebApplication1.ViewModels
2: @model FileUploadViewModel
3: @{
4: Layout = "~/Views/Shared/MyLayout.cshtml";
5: }
6:
7: @section TitleSection{
8: Bulk Upload
9: }
10: @section ContentBody{
11: <div>
12: <a href="/Employee/Index">Back</a>
13: <form action="/BulkUpload/Upload" method="post" enctype="multipart/form-data">
14: Select File : <input type="file" name="fileUpload" value="" />
15: <input type="submit" name="name" value="Upload" />
16: </form>
17: </div>
18: }
如上,FileUploadViewModel中属性名称与 input[type="file"]的名称类似,都称为“fileUpload”。我们在Model Binder中已经讲述了名称属性的重要性,注意:在表单标签中,有一个额外的属性是加密的,会在实验结尾处讲解。
5. 创建业务层上传方法
在 EmployeeBusinessLayer中新建方法 UploadEmployees,如下:
1: public void UploadEmployees(List<Employee> employees)
2: {
3: SalesERPDAL salesDal = new SalesERPDAL();
4: salesDal.Employees.AddRange(employees);
5: salesDal.SaveChanges();
6: }<employee>
7: </employee>
6. 创建Upload Action 方法
创建Action 方法,并命名为 “BulkUploadController”,如下:
1: [AdminFilter]
2: public ActionResult Upload(FileUploadViewModel model)
3: {
4: List<Employee> employees = GetEmployees(model);
5: EmployeeBusinessLayer bal = new EmployeeBusinessLayer();
6: bal.UploadEmployees(employees);
7: return RedirectToAction("Index","Employee");
8: }
9:
10: private List<Employee> GetEmployees(FileUploadViewModel model)
11: {
12: List<Employee> employees = new List<Employee>();
13: StreamReader csvreader = new StreamReader(model.fileUpload.InputStream);
14: csvreader.ReadLine(); // Assuming first line is header
15: while (!csvreader.EndOfStream)
16: {
17: var line = csvreader.ReadLine();
18: var values = line.Split(‘,‘);//Values are comma separated
19: Employee e = new Employee();
20: e.FirstName = values[0];
21: e.LastName = values[1];
22: e.Salary = int.Parse(values[2]);
23: employees.Add(e);
24: }
25: return employees;
26: }
AdminFilter会绑定到Upload action方法中,限制非管理员用户的访问。
7. 创建BulkUpload链接
打开 “Views/Employee”文件夹下的 AddNewLink.cshtml 文件,输入BulkUpload链接,如下:
<a href="/Employee/AddNew">Add New</a> <a href="/BulkUpload/Index">BulkUpload</a>
8.运行
8.1 创建一个样本文件来测试,如图所示
8.2 运行,点击BulkUpload链接
选择文件并点击确认
关于实验 27
为什么在实验27中不需要验证?
在该选项中添加客户端和服务器端验证需要读者自行添加的,以下是添加验证的提示:
- 服务器端验证可使用Data Annotations。
- 客户端验证可利用客户端的数据解释和执行jQuery的验证。必须手动设置自定义数据属性,因为并没有将Htmlhelper 方法设置为文件输入。
- 客户端验证可编写JavaScript 代码,通过点击按钮来实现。这个方法并不是很难,由于文件输入是由输入控件完成,值可以在JavaScript中获取及验证 。
什么是 HttpPostedFileBase?
HttpPostedFileBase将通过客户端提供文件上传的访问入口,Model Binder 会在Post请求期间更新 FileUploadViewModel类中的所有属性值。我们在FileUploadViewModel内部只有一个属性,Model Binder会通过客户端设置它实现文件上传。
是否会提供多文件的输入控件?
是,有两种方法可以实现:
1. 创建多文件输入控件,每个控件有唯一的名称,FileUploadViewModel类会为每个控件创建 HttpPostedFileBase类型的属性,每个属性名称应该与控件名称匹配。
2. 创建多文件输入控件,每个控件有相同的名称,创建类型的List列表,代替创建多个HttpPostedFileBase类型的属性。
enctype="multipart/form-data" 是用来做什么的?
该属性指定了post 数据的编码类型,默认属性值是”application/x-www-form-urlencoded“
例1—登录窗体会给服务器发送以下Post 请求
1: POST /Authentication/DoLogin HTTP/1.1
2: Host: localhost:8870
3: Connection: keep-alive
4: Content-Length: 44
5: Content-Type: application/x-www-form-urlencoded
6: ...
7: ...
8: UserName=Admin&Passsword=Admin&BtnSubmi=Login
所有输入值会被作为发送的值的一部分,以”key/value“的形式发送。
当 enctype="multipart/form-data" 属性被加入Form标签中,以下post 请求会被发送到服务器。
1: POST /Authentication/DoLogin HTTP/1.1
2: Host: localhost:8870
3: Connection: keep-alive
4: Content-Length: 452
5: Content-Type: multipart/form-data; boundary=----WebKitFormBoundarywHxplIF8cR8KNjeJ
6: ...
7: ...
8: ------WebKitFormBoundary7hciuLuSNglCR8WC
9: Content-Disposition: form-data; name="UserName"
10:
11: Admin
12: ------WebKitFormBoundary7hciuLuSNglCR8WC
13: Content-Disposition: form-data; name="Password"
14:
15: Admin
16: ------WebKitFormBoundary7hciuLuSNglCR8WC
17: Content-Disposition: form-data; name="BtnSubmi"
18:
19: Login
20: ------WebKitFormBoundary7hciuLuSNglCR8WC--
如上所示,Form会在多部分post发送,每部分都是被分界线分割的,每部分包含单值。
如果form标签包含文件输入控件的话,enctype必须被设置为”multipart/form-data“。
为什么有时候需要设置 encType 为 “multipart/form-data”,而有时候不需要设置?
当encType 设置为”multipart/form-data“,将会实现Post数据和上传文件的功能,当然也会增加请求的size 增加,请求size 越大意味着性能越低。因此得出的最佳实践经验需要设置为默认的”application/x-www-form-urlencoded“。
为什么在实验27中创建ViewModel?
在View中已经有一个控件了,我们需要通过直接添加 HttpPostedFileBase类型的参数,并命名为”fileUpload“实现相同的结果,从而替代创建独立的ViewModel。
1: public ActionResult Upload(HttpPostedFileBase fileUpload)
2: {
3: }
创建 ViewModel是最好的方法,Controller应该以 ViewModel的形式给View发送数据,且数据必须来自Controller。
以上问题的解决方法
是否存在疑虑,当发送请求时,如何获取响应?
众人皆知的编程规则,程序中任何事件都是由线程执行的,请求事件也是。
Asp.net framework 维护线程池,每次当请求发送到webserver时,会从线程池中分配空闲的线程处理此请求。这种线程被称为worker线程。
当请求处理完成,该线程无法服务其他请求时,worker 线程会被阻塞。现在我们来了解什么是线程饥饿,如果一个应用程序接收到很多请求,且处理每个请求都非常耗时。在这种情况下,我们就必须指定一个点来结束请求,当有新的请求进入状态时,没有worker 线程可使用,这种现象称为线程饥饿。
在我们的示例程序中只包含2个员工记录,而在实际使用情况下,会包含成千上万的记录,这就意味着将耗费大量的时间来处理请求。这种情况就可能导致线程饥饿.
线程饥饿的解决方法:
截至现在我们讨论的请求类型都是同步请求。如果使用异步请求来代替同步请求,那么线程饥饿的问题就得到解决了。
- 异步请求的情况下,会分配worker线程来服务请求。
- worker 线程初始化异步操作,并返回到线程池服务其他请求。异步操作可使用CLR 线程来继续执行。
- 存在的问题就是,CLR 线程无法返回响应,一旦它完成了异步操作,它会通知Asp.net。
- Webserver 再次获取一个worker线程来处理剩余的请求,并返回响应。
上述使用场景中,会获取两次worker 线程,这两次获取的线程可能相同,也可能会不同。
文件读取是I/O操作,不需要使用worker 线程处理。因此最好将同步请求转换为异步。
同步请求的响应时间能提升吗?
不可以,响应时间是相同的,线程会被释放来服务其他请求。
实验28——解决线程饥饿问题
在Asp.net MVC中会通过将同步Action方法转换为异步Action方法,将同步请求转换为异步请求。
1. 创建异步控制器
在控制器中将基类 UploadController修改为 AsynController。
1: {
2: public class BulkUploadController : AsyncController
3: {
2. 转换同步Action方法
该功能通过两个关键字就可实现:“async “和” await”
1: [AdminFilter]
2: public async Task<ActionResult> Upload(FileUploadViewModel model)
3: {
4: int t1 = Thread.CurrentThread.ManagedThreadId;
5: List<Employee> employees = await Task.Factory.StartNew<List<Employee>>
6: (() => GetEmployees(model));
7: int t2 = Thread.CurrentThread.ManagedThreadId;
8: EmployeeBusinessLayer bal = new EmployeeBusinessLayer();
9: bal.UploadEmployees(employees);
10: return RedirectToAction("Index", "Employee");
11: }<actionresult><employee><list<employee>
12: </list<employee></employee></actionresult>
在action方法的开始或结束处,使用变量存储线程ID。
理一下思路:
- 当上传按钮被点击时,新请求会被发送到服务器。
- Webserver从线程池中产生Worker线程 ,并分配给服务器请求。
- worker线程会使Action 方法执行
- Worker方法在 Task.Factory.StartNew方法的辅助下,开启异步操作
- 使用async关键字将Action 方法标记为异步方法,由此会保证异步操作一旦开启,Worker 线程就会释放。
- 使用await关键字也可标记异步操作,能够保证异步操作完成时才能够继续执行下面的代码。
- 一旦异步操作在Action 方法中完成执行,必须执行worker线程。因此webserver将会新建一个空闲worker 线程,并用来服务剩下的请求,提供响应。
3. 测试运行
运行应用程序,并跳转到BulkUpload页面。会在代码中显示断点,输入样本文件,点击上传。
如图所示,在项目启动或关闭时有的线程ID是不同的。
实验29——异常处理—显示自定义错误页面
如果一个项目不考虑异常处理,那么可以说这个项目是不完整的。到目前为止,我们已经了解了MVC中的两个过滤器:Action filter和 Authorization filter。现在我们来学习第三个过滤器,异常过滤器(Exception Filters)。
什么是异常过滤器(Exception Filters)?
异常过滤器与其他过滤器的用法相同,可当作属性使用。使用异常过滤器的基本步骤:
1. 使它们可用
2. 将过滤器作为属性,应用到action 方法或控制器中。我们也可以在全局层次使用异常过滤器。
异常过滤器的作用是什么?,是否有自动执行的异常过滤器?
一旦action 方法中出现异常,异常过滤器就会控制程序的运行过程,开始内部自动写入运行的代码。MVC为我们提供了编写好的异常过滤器:HandeError。
当action方法中发生异常时,过滤器就会在“~/Views/[current controller]”或“~/Views/Shared”目录下查找到名称为”Error”的View,然后创建该View的ViewResult,并作为响应返回。
接下来我们会讲解一个Demo,帮助我们更好的理解异常过滤器的使用。
已经实现的上传文件功能,很有可能会发生输入文件格式错误。因此我们需要处理异常。
1. 创建含错误信息的样本文件,包含一些非法值,如图,Salary就是非法值。
2. 运行,查找异常,点击上传按钮,选择已建立的样本数据,选择上传。
3. 激活异常过滤器
当自定义异常被捕获时,异常过滤器变为可用。为了能够获得自定义异常,打开Web.config文件,在System.Web.Section下方添加自定义错误信息。
1: <system.web>
2: <customErrors mode="On"></customErrors>
4. 创建Error View
在“~/Views/Shared”文件夹下,会发现存在“Error.cshtml”文件,该文件是由MVC 模板提供的,如果没有自动创建,该文件也可以手动完成。
1: @{
2: Layout = null;
3: }
4:
5: <!DOCTYPE html>
6: <html>
7: <head>
8: <meta name="viewport" content="width=device-width" />
9: <title>Error</title>
10: </head>
11: <body>
12: <hgroup>
13: <h1>Error.</h1>
14: <h2>An error occurred while processing your request.</h2>
15: </hgroup>
16: </body>
17: </html>
5. 绑定异常过滤器
将过滤器绑定到action方法或controller上,不需要手动执行,打开 App_Start folder文件夹中的 FilterConfig.cs文件。在 RegisterGlobalFilters 方法中会看到 HandleError 过滤器已经以全局过滤器绑定成功。
1: public static void RegisterGlobalFilters(GlobalFilterCollection filters)
2: {
3: filters.Add(new HandleErrorAttribute());//ExceptionFilter
4: filters.Add(new AuthorizeAttribute());
5: }
如果需要删除全局过滤器,那么会将过滤器绑定到action 或controller层,但是不建议这么做,最好是在全局中应用如下:
1: [AdminFilter]
2: [HandleError]
3: public async Task<ActionResult> Upload(FileUploadViewModel model)
4: {<actionresult>
5: </actionresult>
6. 运行
7. 在View中显示错误信息
将Error View转换为HandleErrorInfo类的强类型View,并在View中显示错误信息。
1: @model HandleErrorInfo
2: @{
3: Layout = null;
4: }
5:
6: <!DOCTYPE html>
7: <html>
8: <head>
9: <meta name="viewport" content="width=device-width" />
10: <title>Error</title>
11: </head>
12: <body>
13: <hgroup>
14: <h1>Error.</h1>
15: <h2>An error occurred while processing your request.</h2>
16: </hgroup>
17: Error Message :@Model.Exception.Message<br />
18: Controller: @Model.ControllerName<br />
19: Action: @Model.ActionName
20: </body>
21: </html>
8. 运行测试
Handle error属性能够确保无论是否出现异常,自定义View都能够显示,但是它的能力在controller和action 方法中是受限的。不会处理“Resource not found”这类型的错误。
运行应用程序,输一些奇怪的URL
9. 创建 ErrorController控制器,并创建Index方法,代码如下:
1: public class ErrorController : Controller
2: {
3: // GET: Error
4: public ActionResult Index()
5: {
6: Exception e=new Exception("Invalid Controller or/and Action Name");
7: HandleErrorInfo eInfo = new HandleErrorInfo(e, "Unknown", "Unknown");
8: return View("Error", eInfo);
9: }
10: }
10. 在非法URL中显示自定义Error视图
可在 web.config中定义“Resource not found error”的设置,如下:
1: <system.web>
2: <customErrors mode="On">
3: <error statusCode="404" redirect="~/Error/Index"/>
4: </customErrors>
11. 使 ErrorController 全局可访问。
将AllowAnonymous属性应用到 ErrorController中,因为错误控制器和index方法不应该只绑定到认证用户,也很有可能用户在登录之前已经输入错误的URL。
1: [AllowAnonymous]
2: public class ErrorController : Controller
3: {
12. 运行
关于实验29
View的名称是否可以修改?
可以修改,不一定叫Error,也可以指定其他名字。如果Error View的名称改变了,当绑定HandleError过滤器时,必须制定View的名称。
1: [HandleError(View="MyError")]
2: Or
3: filters.Add(new HandleErrorAttribute()
4: {
5: View="MyError"
6: });
是否可以为不同的异常获取不同的Error View?
可以,在这种情况下,必须多次应用Handle error filter。
1: [HandleError(View="DivideError",ExceptionType=typeof(DivideByZeroException))]
2: [HandleError(View = "NotFiniteError", ExceptionType = typeof(NotFiniteNumberException))]
3: [HandleError]
4:
5: OR
6:
7: filters.Add(new HandleErrorAttribute()
8: {
9: ExceptionType = typeof(DivideByZeroException),
10: View = "DivideError"
11: });
12: filters.Add(new HandleErrorAttribute()
13: {
14: ExceptionType = typeof(NotFiniteNumberException),
15: View = "NotFiniteError"
16: });
17: filters.Add(new HandleErrorAttribute());
前两个Handle error filter都指定了异常,而最后一个更为常见更通用,会显示所有其他异常的Error View。
上述实验中并没有处理登录异常,我们会在实验30中讲解登录异常。
实验30——异常处理—登录异常
1. 创建 Logger 类
在根目录下,新建文件夹,命名为Logger。在Logger 文件夹下新建类 FileLogger
1: namespace WebApplication1.Logger
2: {
3: public class FileLogger
4: {
5: public void LogException(Exception e)
6: {
7: File.WriteAllLines("C://Error//" + DateTime.Now.ToString("dd-MM-yyyy mm hh ss")+".txt",
8: new string[]
9: {
10: "Message:"+e.Message,
11: "Stacktrace:"+e.StackTrace
12: });
13: }
14: }
15: }
2. 创建 EmployeeExceptionFilter 类
在 Filters文件夹下,新建 EmployeeExceptionFilter类
1: namespace WebApplication1.Filters
2: {
3: public class EmployeeExceptionFilter
4: {
5: }
6: }
3. 扩展 Handle Error实现登录异常处理
让 EmployeeExceptionFilter 继承 HandleErrorAttribute类,重写 OnException方法:
1: public class EmployeeExceptionFilter:HandleErrorAttribute
2: {
3: public override void OnException(ExceptionContext filterContext)
4: {
5: base.OnException(filterContext);
6: }
7: }
4. 定义 OnException 方法
在 OnException 方法中包含异常登录代码。
1: public override void OnException(ExceptionContext filterContext)
2: {
3: FileLogger logger = new FileLogger();
4: logger.LogException(filterContext.Exception);
5: base.OnException(filterContext);
6: }
5. 修改默认的异常过滤器
打开 FilterConfig.cs文件,删除 HandErrorAtrribute,添加上步中创建的。
1: public static void RegisterGlobalFilters(GlobalFilterCollection filters)
2: {
3: //filters.Add(new HandleErrorAttribute());//ExceptionFilter
4: filters.Add(new EmployeeExceptionFilter());
5: filters.Add(new AuthorizeAttribute());
6: }
6. 运行
会在C盘中创建“Error”文件夹,存放一些error文件。
关于实验30
当异常出现后,Error View 是如何返回响应的?
查看 OnException 方法的最后一行代码:
1: base.OnException(filterContext);
即基类的 OnException 方法执行并返回Error View 的ViewResult。
在 OnException 中,是否可以返回其他结果?
可以,代码如下:
1: public override void OnException(ExceptionContext filterContext)
2: {
3: FileLogger logger = new FileLogger();
4: logger.LogException(filterContext.Exception);
5: //base.OnException(filterContext);
6: filterContext.ExceptionHandled = true;
7: filterContext.Result = new ContentResult()
8: {
9: Content="Sorry for the Error"
10: };
11: }
当返回自定义响应时,做的第一件事情就是通知MVC 引擎,手动处理异常,因此不需要执行默认的操作,不会显示默认的错误页面。使用以下语句可完成:
1: filterContext.ExceptionHandled = true
Routing
到目前为止,我们已经解决了MVC的很多问题,但忽略了最基本的最重要的一个问题:当用户发送请求时,会发生什么?
最好的答案是“执行Action 方法”,但仍存在疑问:对于一个特定的URL请求,如何确定控制器和action 方法。在开始实验31之前,我们首先来解答上述问题,你可能会困惑为什么这个问题会放在最后来讲,因为了解内部结构之前,需要更好的了解MVC。
理解RouteTable
在Asp.net mvc中有RouteTable这个概念,是用来存储URL 路径的,简而言之,是保存已定义的应用程序的可能的URL pattern的集合。
默认情况下,路径是项目模板组成的一部分。可在 Global.asax 文件中检查到,在 Application_Start中会发现以下语句:
1: RouteConfig.RegisterRoutes(RouteTable.Routes);
App_Start文件夹下的 RouteConfig.cs文件,包含以下代码块:
1: namespace WebApplication1
2: {
3: public class RouteConfig
4: {
5: public static void RegisterRoutes(RouteCollection routes)
6: {
7: routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
8:
9: routes.MapRoute(
10: name: "Default",
11: url: "{controller}/{action}/{id}",
12: defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
13: );
14: }
15: }
16: }
RegisterRoutes方法已经包含了由routes.MapRoute 方法定义的默认的路径。已定义的路径会在请求周期中确定执行的是正确的控制器和action 方法。如果使用 route.MapRoute创建了多个路径,那么内部路径的定义就意味着创建Route对象。
MapRoute 方法也可与 RouteHandler 关联。
理解ASP.NET MVC 请求周期
在本节中我们只讲解请求周期中重要的知识点
1. UrlRoutingModule
当最终用户发送请求时,会通过UrlRoutingModule 对象传递,UrlRoutingModule 是HTTP 模块。
2. Routing
UrlRoutingModule 会从route table集合中获取首次匹配的Route 对象,为了能够匹配成功,请求URL会与route中定义的URL pattern 匹配。
当匹配的时候必须考虑以下规则:
- 数字参数的匹配(请求URL和URL pattern中的数字)
- URL pattern中的可选参数:
- 参数中定义的静态参数
3. 创建MVC Route Handler
一旦Route 对象被选中,UrlRoutingModule会获得 Route对象的 MvcRouteHandler对象。
4. 创建 RouteData 和 RequestContext
UrlRoutingModule使用Route对象创建RouteData,可用于创建RequestContext。RouteData封装了路径的信息如Controller名称,action名称以及route参数值。
Controller 名称
为了从URL 中获取Controller名称,需要按规则执行如在URL pattern中{Controller}是标识Controller名称的关键字。
Action Method 名称
为了获取action 方法名称,{action}是标识action 方法的关键字。
Route 参数
URL pattern能够获得以下值:
1.{controller}
2.{action}
3. 字符串,如 “MyCompany/{controller}/{action}”,“MyCompany”是字符串。
4. 其他,如“{controller}/{action}/{id}”,”id“是路径的参数。
例如:
Route pattern - > “{controller}/{action}/{id}”
请求 URL ->http://localhost:8870/BulkUpload/Upload/5
测试1
1: public class BulkUploadController : Controller
2: {
3: public ActionResult Upload (string id)
4: {
5: //value of id will be 5 -> string 5
6: ...
7: }
8: }
测试2
1: public class BulkUploadController : Controller
2: {
3: public ActionResult Upload (int id)
4: {
5: //value of id will be 5 -> int 5
6: ...
7: }
8: }
测试3
1: public class BulkUploadController : Controller
2: {
3: public ActionResult Upload (string MyId)
4: {
5: //value of MyId will be null
6: ...
7: }
8: }
5. 创建MVC Handler
MvcRouteHandler 会创建 MVCHandler的实例传递 RequestContext对象
6. 创建Controller实例
MVCHandler会根据 ControllerFactory的帮助创建Controller实例
7. 执行方法
MVCHandler调用Controller的执行方法,执行方法是由Controller的基类定义的。
8. 调用Action 方法
每个控制器都有与之关联的 ControllerActionInvoker对象。在执行方法中ControllerActionInvoker对象调用正确的action 方法。
9. 运行结果
Action方法会接收到用户输入,并准备好响应数据,然后通过返回语句返回执行结果,返回类型可能是ViewResult或其他。
实验31——实现对用户有好的URL
1. 重新定义 RegisterRoutes 方法
在RegisterRoutes 方法中包含 additional route
1: public static void RegisterRoutes(RouteCollection routes)
2: {
3: routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
4:
5: routes.MapRoute(
6: name: "Upload",
7: url: "Employee/BulkUpload",
8: defaults: new { controller = "BulkUpload", action = "Index" }
9: );
10:
11: routes.MapRoute(
12: name: "Default",
13: url: "{controller}/{action}/{id}",
14: defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
15: );
16: }
2. 修改URL 引用
打开“~/Views/Employee”文件下的 AddNewLink.cshtml ,修改BulkUpload 链接,如下:
1:
2: <a href="/Employee/BulkUpload">BulkUpload</a>
3. 运行测试
关于实验31
之前的URL 现在是否起作用?
是,仍然有用。BulkUploadController中的Index 方法可通过两个URL 访问。
1. ”http://localhost:8870/Employee/BulkUpload“
2. “http://localhost:8870/BulkUpload/Index”
Route 参数和Query 字符串有什么区别?
- Query 字符串本身是有大小限制的,而无法定义Route 参数的个数。
- 无法在Query 字符串值中添加限制,但是可以在Route 参数中添加限制。
- 可能会设置Route参数的默认值,而Query String不可能有默认值。
- Query 字符串可使URL 混乱,而Route参数可保持它有条理。
如何在Route 参数中使用限制?
可使用正则表达式。
如:
1: routes.MapRoute(
2: "MyRoute",
3: "Employee/{EmpId}",
4: new {controller=" Employee ", action="GetEmployeeById"},
5: new { EmpId = @"\d+" }
6: );
Action 方法:
1: public ActionResult GetEmployeeById(int EmpId)
2: {
3: ...
4: }
为了保证每个路径参数都能独立,因此参数名称必须与Route Parameter一致。
是否需要将action 方法中的参数名称与Route 参数名称保持一致?
Route Pattern 也许会包含一个或多个RouteParameter,为了区分每个参数,必须保证action 方法的参数名称与Route 参数名称相同。
定义路径的顺序重要吗?
有影响,在上面的实验中,我们定义了两个路径,一个是自定义的,一个是默认的。默认的是最先定义的,自定义路径是在之后定义的。
当用户输入“http://.../Employee/BulkUpload”地址后发送请求,UrlRoutingModule会搜索与请求URL 匹配的默认的route pattern ,它会将 Employee作为控制器的名称,“BulkUpload”作为action 方法名称。因此定义的顺序是非常重要的,更常用的路径应放在最后。
是否有什么简便的方法来定义Action 方法的URL pattern?
我们可使用基于 routing 的属性。
1. 基本的routing 属性可用
在 RegisterRoutes 方法中在 IgnoreRoute语句后输入代码如下:
1: routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
2:
3: routes.MapMvcAttributeRoutes();
4:
5: routes.MapRoute(
6: ...
2. 定义action 方法的 route pattern
1: [Route("Employee/List")]
2: public ActionResult Index()
3: {
3. 运行测试
routing 属性可定义route 参数,如下:
1: [Route("Employee/List/{id}")]
2: publicActionResult Index (string id) { ... }
IgnoreRoutes 的作用是什么?
当我们不想使用routing作为特别的扩展时,会使用IgnoreRoutes。作为MVC模板的一部分,在RegisterRoute 方法中下列语句是默认的:
1: routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
这就是说如果用户发送以“.axd”为结束的请求,将不会有任何路径加载的操作,请求将直接定位到物理资源。
总结
6天的MVC 学习已经完成了,希望大家能够将所讲的知识充分理解,充分吸收。第7章我们会使用MVC,JQUery 和Ajax创建简单的页面应用。欢迎大家持续关注!
原文链接:http://www.codeproject.com/Articles/1002109/Learn-MVC-Project-in-days-Day-6