咨詢電話:023-6276-4481
熱門(mén)文章
電 話:023-6276-4481
郵箱:broiling@qq.com
地址:重慶市南岸區(qū)亞太商谷6幢25-2
1. 創(chuàng)建 FileUploadViewModel
在ViewModels文件夾下新建類(lèi)“FileUploadViewModel”,如下:
1: public class FileUploadViewModel: BaseViewModel
2: {
3: public HttpPostedFileBase fileUpload {get; set ;}
4: }
HttpPostedFileBase 將通過(guò)客戶端提供上傳文件的訪問(wèn)入口。
2. 創(chuàng)建 BulkUploadController 和Index action 方法
新建 controller“BulkUploadController”,并實(shí)現(xiàn)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會(huì)確保頁(yè)眉和頁(yè)腳數(shù)據(jù)能夠正確傳遞到ViewModel中,AdminFilter限制非管理員用戶的訪問(wèn)。
3.創(chuàng)建上傳View
創(chuàng)建以上Action方法的View。View名稱應(yīng)為 index.cshtml,且存放在“~/Views/BulkUpload”文件夾下。
4. 設(shè)計(jì)上傳View
在View中輸入以下內(nèi)容:
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: }
如上,F(xiàn)ileUploadViewModel中屬性名稱與 input[type="file"]的名稱類(lèi)似,都稱為“fileUpload”。我們?cè)贛odel Binder中已經(jīng)講述了名稱屬性的重要性,注意:在表單標(biāo)簽中,有一個(gè)額外的屬性是加密的,會(huì)在實(shí)驗(yàn)結(jié)尾處講解。
5. 創(chuàng)建業(yè)務(wù)層上傳方法
在 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. 創(chuàng)建Upload Action 方法
創(chuàng)建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會(huì)綁定到Upload action方法中,限制非管理員用戶的訪問(wèn)。
7. 創(chuàng)建BulkUpload鏈接
打開(kāi) “Views/Employee”文件夾下的 AddNewLink.cshtml 文件,輸入BulkUpload鏈接,如下:
<a href="/Employee/AddNew">Add New</a> <a href="/BulkUpload/Index">BulkUpload</a>
8.運(yùn)行
8.1 創(chuàng)建一個(gè)樣本文件來(lái)測(cè)試,如圖所示
8.2 運(yùn)行,點(diǎn)擊BulkUpload鏈接
選擇文件并點(diǎn)擊確認(rèn)
為什么在實(shí)驗(yàn)27中不需要驗(yàn)證?
在該選項(xiàng)中添加客戶端和服務(wù)器端驗(yàn)證需要讀者自行添加的,以下是添加驗(yàn)證的提示:
服務(wù)器端驗(yàn)證可使用Data Annotations。
客戶端驗(yàn)證可利用客戶端的數(shù)據(jù)解釋和執(zhí)行jQuery的驗(yàn)證。必須手動(dòng)設(shè)置自定義數(shù)據(jù)屬性,因?yàn)椴](méi)有將Htmlhelper 方法設(shè)置為文件輸入。
客戶端驗(yàn)證可編寫(xiě)JavaScript 代碼,通過(guò)點(diǎn)擊按鈕來(lái)實(shí)現(xiàn)。這個(gè)方法并不是很難,由于文件輸入是由輸入控件完成,值可以在JavaScript中獲取及驗(yàn)證 。
什么是 HttpPostedFileBase?
HttpPostedFileBase將通過(guò)客戶端提供文件上傳的訪問(wèn)入口,Model Binder 會(huì)在Post請(qǐng)求期間更新 FileUploadViewModel類(lèi)中的所有屬性值。我們?cè)贔ileUploadViewModel內(nèi)部只有一個(gè)屬性,Model Binder會(huì)通過(guò)客戶端設(shè)置它實(shí)現(xiàn)文件上傳。
是否會(huì)提供多文件的輸入控件?
是,有兩種方法可以實(shí)現(xiàn):
1. 創(chuàng)建多文件輸入控件,每個(gè)控件有唯一的名稱,F(xiàn)ileUploadViewModel類(lèi)會(huì)為每個(gè)控件創(chuàng)建 HttpPostedFileBase類(lèi)型的屬性,每個(gè)屬性名稱應(yīng)該與控件名稱匹配。
2. 創(chuàng)建多文件輸入控件,每個(gè)控件有相同的名稱,創(chuàng)建類(lèi)型的List列表,代替創(chuàng)建多個(gè)HttpPostedFileBase類(lèi)型的屬性。
enctype="multipart/form-data" 是用來(lái)做什么的?
該屬性指定了post 數(shù)據(jù)的編碼類(lèi)型,默認(rèn)屬性值是”application/x-www-form-urlencoded“
例1—登錄窗體會(huì)給服務(wù)器發(fā)送以下Post 請(qǐng)求
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
所有輸入值會(huì)被作為發(fā)送的值的一部分,以”key/value“的形式發(fā)送。
當(dāng) enctype="multipart/form-data" 屬性被加入Form標(biāo)簽中,以下post 請(qǐng)求會(huì)被發(fā)送到服務(wù)器。
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--
如上所示,F(xiàn)orm會(huì)在多部分post發(fā)送,每部分都是被分界線分割的,每部分包含單值。
如果form標(biāo)簽包含文件輸入控件的話,enctype必須被設(shè)置為”multipart/form-data“。
為什么有時(shí)候需要設(shè)置 encType 為 “multipart/form-data”,而有時(shí)候不需要設(shè)置?
當(dāng)encType 設(shè)置為”multipart/form-data“,將會(huì)實(shí)現(xiàn)Post數(shù)據(jù)和上傳文件的功能,當(dāng)然也會(huì)增加請(qǐng)求的size 增加,請(qǐng)求size 越大意味著性能越低。因此得出的最佳實(shí)踐經(jīng)驗(yàn)需要設(shè)置為默認(rèn)的”application/x-www-form-urlencoded“。
為什么在實(shí)驗(yàn)27中創(chuàng)建ViewModel?
在View中已經(jīng)有一個(gè)控件了,我們需要通過(guò)直接添加 HttpPostedFileBase類(lèi)型的參數(shù),并命名為”fileUpload“實(shí)現(xiàn)相同的結(jié)果,從而替代創(chuàng)建獨(dú)立的ViewModel。
1: public ActionResult Upload(HttpPostedFileBase fileUpload)
2: {
3: }
創(chuàng)建 ViewModel是最好的方法,Controller應(yīng)該以 ViewModel的形式給View發(fā)送數(shù)據(jù),且數(shù)據(jù)必須來(lái)自Controller。
是否存在疑慮,當(dāng)發(fā)送請(qǐng)求時(shí),如何獲取響應(yīng)?
眾人皆知的編程規(guī)則,程序中任何事件都是由線程執(zhí)行的,請(qǐng)求事件也是。
Asp.net framework 維護(hù)線程池,每次當(dāng)請(qǐng)求發(fā)送到webserver時(shí),會(huì)從線程池中分配空閑的線程處理此請(qǐng)求。這種線程被稱為worker線程。
當(dāng)請(qǐng)求處理完成,該線程無(wú)法服務(wù)其他請(qǐng)求時(shí),worker 線程會(huì)被阻塞?,F(xiàn)在我們來(lái)了解什么是線程饑餓,如果一個(gè)應(yīng)用程序接收到很多請(qǐng)求,且處理每個(gè)請(qǐng)求都非常耗時(shí)。在這種情況下,我們就必須指定一個(gè)點(diǎn)來(lái)結(jié)束請(qǐng)求,當(dāng)有新的請(qǐng)求進(jìn)入狀態(tài)時(shí),沒(méi)有worker 線程可使用,這種現(xiàn)象稱為線程饑餓。
在我們的示例程序中只包含2個(gè)員工記錄,而在實(shí)際使用情況下,會(huì)包含成千上萬(wàn)的記錄,這就意味著將耗費(fèi)大量的時(shí)間來(lái)處理請(qǐng)求。這種情況就可能導(dǎo)致線程饑餓.
線程饑餓的解決方法:
截至現(xiàn)在我們討論的請(qǐng)求類(lèi)型都是同步請(qǐng)求。如果使用異步請(qǐng)求來(lái)代替同步請(qǐng)求,那么線程饑餓的問(wèn)題就得到解決了。
異步請(qǐng)求的情況下,會(huì)分配worker線程來(lái)服務(wù)請(qǐng)求。
worker 線程初始化異步操作,并返回到線程池服務(wù)其他請(qǐng)求。異步操作可使用CLR 線程來(lái)繼續(xù)執(zhí)行。
存在的問(wèn)題就是,CLR 線程無(wú)法返回響應(yīng),一旦它完成了異步操作,它會(huì)通知Asp.net。
Webserver 再次獲取一個(gè)worker線程來(lái)處理剩余的請(qǐng)求,并返回響應(yīng)。
上述使用場(chǎng)景中,會(huì)獲取兩次worker 線程,這兩次獲取的線程可能相同,也可能會(huì)不同。
文件讀取是I/O操作,不需要使用worker 線程處理。因此最好將同步請(qǐng)求轉(zhuǎn)換為異步。
同步請(qǐng)求的響應(yīng)時(shí)間能提升嗎?
不可以,響應(yīng)時(shí)間是相同的,線程會(huì)被釋放來(lái)服務(wù)其他請(qǐng)求。
在Asp.net MVC中會(huì)通過(guò)將同步Action方法轉(zhuǎn)換為異步Action方法,將同步請(qǐng)求轉(zhuǎn)換為異步請(qǐng)求。
1. 創(chuàng)建異步控制器
在控制器中將基類(lèi) UploadController修改為 AsynController。
1: {
2: public class BulkUploadController : AsyncController
3: {
2. 轉(zhuǎn)換同步Action方法
該功能通過(guò)兩個(gè)關(guān)鍵字就可實(shí)現(xiàn):“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();
上一篇 下一篇