热门文章
电 话:023-6276-4481
邮箱:broiling@qq.com
地址:重庆市南岸区亚太商谷6幢25-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 separated19: 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中不需要验证?
在该选项中添加客户端和服务器端验证需要读者自行添加的,以下是添加验证的提示:
服务器端验证可使用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 线程处理。因此最好将同步请求转换为异步。
同步请求的响应时间能提升吗?
不可以,响应时间是相同的,线程会被释放来服务其他请求。
在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();
上一篇
下一篇