首页 > 代码库 > 七天学会ASP.NET MVC (六)——线程问题、异常处理、自己定义URL

七天学会ASP.NET MVC (六)——线程问题、异常处理、自己定义URL

技术分享

本节又带了一些经常使用的。却非常难理解的问题,本节从文件上传功能的实现引出了线程使用,介绍了线程饥饿的解决方法,异常处理方法,了解RouteTable自己定义路径 。

系列文章

七天学会ASP.NET MVC (一)——深入理解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 将通过client提供上传文件的訪问入口。

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=http://www.mamicode.com/"/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中不须要验证?

在该选项中加入client和server端验证须要读者自行加入的,下面是加入验证的提示:

  • server端验证可使用Data Annotations。
  • client验证可利用client的数据解释和运行jQuery的验证。

    必须手动设置自己定义数据属性,由于并没有将Htmlhelper 方法设置为文件输入。

  • client验证可编写JavaScript 代码,通过点击button来实现。这种方法并非非常难。由于文件输入是由输入控件完毕,值能够在JavaScript中获取及验证 。

什么是 HttpPostedFileBase?

HttpPostedFileBase将通过client提供文件上传的訪问入口,Model Binder 会在Post请求期间更新 FileUploadViewModel类中的全部属性值。

我们在FileUploadViewModel内部仅仅有一个属性,Model Binder会通过client设置它实现文件上传。

是否会提供多文件的输入控件?

是,有两种方法能够实现:

1. 创建多文件输入控件,每一个控件有唯一的名称,FileUploadViewModel类会为每一个控件创建 HttpPostedFileBase类型的属性。每一个属性名称应该与控件名称匹配。

2. 创建多文件输入控件,每一个控件有同样的名称。创建类型的List列表,取代创建多个HttpPostedFileBase类型的属性。

enctype="multipart/form-data" 是用来做什么的?

该属性指定了post 数据的编码类型。默认属性值是”application/x-www-form-urlencoded“

例1—登录窗口会给server发送下面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 请求会被发送到server。

   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方法

该功能通过两个keyword就可实现:“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。

理一下思路:

  • 当上传button被点击时,新请求会被发送到server。
  • Webserver从线程池中产生Worker线程 。并分配给server请求。
  • worker线程会使Action 方法运行
  • Worker方法在 Task.Factory.StartNew方法的辅助下,开启异步操作
  • 使用asynckeyword将Action 方法标记为异步方法。由此会保证异步操作一旦开启,Worker 线程就会释放。
  • 使用awaitkeyword也可标记异步操作,能够保证异步操作完毕时才干够继续运行下面的代码。
  • 一旦异步操作在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. 运行,查找异常,点击上传button,选择已建立的样本数据,选择上传。

技术分享

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名称的keyword。

Action Method 名称

为了获取action 方法名称,{action}是标识action 方法的keyword。

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=http://www.mamicode.com/"/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”为结束的请求。将不会有不论什么路径载入的操作。请求将直接定位到物理资源。

总结

本节内容中讲述的线程问题是我们在MVC开发过程中经常遇到的,所以希望大家深入学习。

同一时候在进行MVC开发时,不要忘记借助开发工具来帮助开发过程。 ComponentOne Studio ASP.NET MVC 是一款针对 MVC 平台的控件包,它与 Visual Studio 无缝集成,全然与 MVC6 和 ASP.NET 5.0 兼容,将大幅提高工作效率。

6天的MVC 学习已经完毕了,希望大家能够将所讲的知识充分理解,充分吸收。第7章我们会使用MVC,JQUery 和Ajax创建简单的页面应用。欢迎大家持续关注!

原文链接:http://www.codeproject.com/Articles/1002109/Learn-MVC-Project-in-days-Day-6

七天学会ASP.NET MVC (六)——线程问题、异常处理、自己定义URL