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

简介:

总结

实验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”为结束的请求,将不会有任何路径加载的操作,请求将直接定位到物理资源。




本文转自 powertoolsteam 51CTO博客,原文链接:http://blog.51cto.com/powertoolsteam/1674037,如需转载请自行联系原作者
相关文章
|
6月前
|
开发框架 JSON .NET
ASP.NET Core 自定义配置警告信息
自定义配置警告信息需要在 startup 类中的 ConfigureService 方法中进行配置示例: // 注册 控制器服务 services.AddControllers(configure: setup => { setup.ReturnHttpNotAcceptable = true; ...
43 0
|
3月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
41 0
|
1月前
|
开发框架 前端开发 .NET
C# .NET面试系列六:ASP.NET MVC
<h2>ASP.NET MVC #### 1. MVC 中的 TempData\ViewBag\ViewData 区别? 在ASP.NET MVC中,TempData、ViewBag 和 ViewData 都是用于在控制器和视图之间传递数据的机制,但它们有一些区别。 <b>TempData:</b> 1、生命周期 ```c# TempData 的生命周期是短暂的,数据只在当前请求和下一次请求之间有效。一旦数据被读取,它就会被标记为已读,下一次请求时就会被清除。 ``` 2、用途 ```c# 主要用于在两个动作之间传递数据,例如在一个动作中设置 TempData,然后在重定向到另
99 5
|
6月前
|
Java
已解决Java.net.MalformedURLException异常的有效方法java.net.MalformedURLException: no protocol异常处理
已解决Java.net.MalformedURLException异常的有效方法java.net.MalformedURLException: no protocol异常处理
353 0
|
4月前
钉钉接收事件订阅的url可以加自定义参数吗?
钉钉接收事件订阅的url可以加自定义参数吗?
34 0
|
4月前
|
XML API 数据库
七天.NET 8操作SQLite入门到实战 - 第六天后端班级管理相关接口完善和Swagger自定义配置
七天.NET 8操作SQLite入门到实战 - 第六天后端班级管理相关接口完善和Swagger自定义配置
|
5月前
|
Windows
基于.Net Core实现自定义皮肤WidForm窗口
基于.Net Core实现自定义皮肤WidForm窗口
49 0
|
5月前
|
开发框架 自然语言处理 前端开发
基于ASP.NET MVC开发的、开源的个人博客系统
基于ASP.NET MVC开发的、开源的个人博客系统
52 0
|
6月前
|
开发框架 中间件 .NET
ASP.NET CORE 自定义中间件
ASP.NET CORE 自定义中间件
43 0
|
6月前
|
前端开发
给 SAP Fiori Launchpad 配置自定义 url
给 SAP Fiori Launchpad 配置自定义 url
80 0