[转载][翻译]ASP.NET MVC 3 开发的20个秘诀(十一)[20 Recipes for Programming MVC 3]:通过表单上传文件 – O2DS – 博客园.
议题
同意用户将文件上传并保存到你的网站。
解决方案
通过HttpPostedFileBase实现将文件上传并保存到磁盘。
讨论
在下面这个示例中,我们将在之前创建的添加和编辑书籍的视图中添加上传一个缩略图的控件。开始,必须修改Books和Create视图表单的“enctype”以及在表单中用缩略图控件替换文本输入框。以下是修改后的视图文件:
@model MvcApplication4.Models.Book @{ ViewBag.Title = "Create"; } <h2>Create</h2> <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script> <script src=" @Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script> @using (Html.BeginForm("Create", "Books", FormMethod.Post, new { enctype = "multipart/form-data" })) { @Html.ValidationSummary(true) <fieldset> <legend>Book</legend> <div class="editor-label"> @Html.LabelFor(model => model.Title) </div> <div class="editor-field"> @Html.EditorFor(model => model.Title) @Html.ValidationMessageFor(model => model.Title) </div> <div class="editor-label"> @Html.LabelFor(model => model.Isbn) </div> <div class="editor-field"> @Html.EditorFor(model => model.Isbn) @Html.ValidationMessageFor(model => model.Isbn) </div> <div class="editor-label"> @Html.LabelFor(model => model.Summary) </div> <div class="editor-field"> @Html.EditorFor(model => model.Summary) @Html.ValidationMessageFor(model => model.Summary) </div> <div class="editor-label"> @Html.LabelFor(model => model.Author) </div> <div class="editor-field"> @Html.EditorFor(model => model.Author) @Html.ValidationMessageFor(model => model.Author) </div> <div class="editor-label"> @Html.LabelFor(model => model.Thumbnail) </div> <div class="editor-field"> <input type="file" name="file" /> @Html.ValidationMessageFor(model => model.Thumbnail) </div> <div class="editor-label"> @Html.LabelFor(model => model.Price) </div> <div class="editor-field"> @Html.EditorFor(model => model.Price) @Html.ValidationMessageFor(model => model.Price) </div> <div class="editor-label"> @Html.LabelFor(model => model.Published) </div> <div class="editor-field"> @Html.EditorFor(model => model.Published) @Html.ValidationMessageFor(model => model.Published) </div> <p> <input type="submit" value="Create" /> </p> </fieldset> } <div> @Html.ActionLink("Back to List", "Index") </div>
书籍的编辑视图也必须以同样的方式进行修改,与之前不同在这个表单上添加了一个隐藏字段(用于传递旧的缩略图信息)。这将会通知BookController在上传新文件之后将旧文件删除:
@model MvcApplication4.Models.Book @{ ViewBag.Title = "Edit"; } <h2>Edit</h2> <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script> <script src=" @Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script> @using (Html.BeginForm("Edit", "Books", FormMethod.Post, new { enctype = "multipart/form-data" })) { @Html.ValidationSummary(true) <fieldset> <legend>Book</legend> @Html.HiddenFor(model => model.ID) <div class="editor-label"> @Html.LabelFor(model => model.Title) </div> <div class="editor-field"> @Html.EditorFor(model => model.Title) @Html.ValidationMessageFor(model => model.Title) </div> <div class="editor-label"> @Html.LabelFor(model => model.Isbn) </div> <div class="editor-field"> @Html.EditorFor(model => model.Isbn) @Html.ValidationMessageFor(model => model.Isbn) </div> <div class="editor-label"> @Html.LabelFor(model => model.Summary) </div> <div class="editor-field"> @Html.EditorFor(model => model.Summary) @Html.ValidationMessageFor(model => model.Summary) </div> <div class="editor-label"> @Html.LabelFor(model => model.Author) </div> <div class="editor-field"> @Html.EditorFor(model => model.Author) @Html.ValidationMessageFor(model => model.Author) </div> <div class="editor-label"> @Html.LabelFor(model => model.Thumbnail) </div> <div class="editor-field"> <input type="file" name="file" /> @Html.HiddenFor(model => model.Thumbnail) @Html.ValidationMessageFor(model => model.Thumbnail) </div> <div class="editor-label"> @Html.LabelFor(model => model.Price) </div> <div class="editor-field"> @Html.EditorFor(model => model.Price) @Html.ValidationMessageFor(model => model.Price) </div> <div class="editor-label"> @Html.LabelFor(model => model.Published) </div> <div class="editor-field"> @Html.EditorFor(model => model.Published) @Html.ValidationMessageFor(model => model.Published) </div> <p> <input type="submit" value="Save" /> </p> </fieldset> } <div> @Html.ActionLink("Back to List", "Index") </div>
因为在BooksController中Create和Edit方法都会使用保存上传文件的方法,为了避免写重复的代码,我们需要创建一个新类,右键单击“Utils”文件夹选择“添加”→“类”并创建“FileUpload”类。
这个类将会负责两个重要的功能:保存文件和删除文件。在下面的示例中,FileUpload类将会接收一个HttpPostedFile类型的变量,并将其保存到服务器指定路径。另一个功能则相反,当服务器接收到新的文件时,它负责按照传回的名称将文件从服务器上删掉:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.IO; namespace MvcApplication4.Utils { public static class FileUpload { public static char DirSeparator = System.IO.Path.DirectorySeparatorChar; public static string FilesPath = "Content" + DirSeparator + "Uploads" + DirSeparator; public static string UploadFile(HttpPostedFileBase file) { // 确认接收到文件 if (null == file) return ""; // 确认接收到的文件非空 if (!(file.ContentLength > 0)) return ""; string fileName = file.FileName; string fileExt = Path.GetExtension(file.FileName); // 确认接受到的文件的类型 if (null == fileExt) return ""; // 检测当前要存储的文件夹是否存在 if (!Directory.Exists(FilesPath)) { // 不存在则创建一个 Directory.CreateDirectory(FilesPath); } // 设置完整保存路径 string path = FilesPath + DirSeparator + fileName; // 保存文件 file.SaveAs(Path.GetFullPath(path)); // 返回文件名 return fileName; } public static void DeleteFile(string fileName) { // 确认文件名合法 if (fileName.Length == 0) return; // 设置要删除的文件的完整路径 string path = FilesPath + DirSeparator + fileName; // 检测文件是否存在 if (File.Exists(Path.GetFullPath(path))) { // 删除文件 File.Delete(Path.GetFullPath(path)); } } } }
为了避免在BooksController类中再次进行实例化,这个类和方法都定义为静态的。类顶部的常量定义了文件将会保存的位置,请修改这些变量指定文件在你的站点的存储位置。在UploadFile方法中,检测如果上传文件要保存到目录不存在,则通过System.IO.Directory类的CreateDirectory方法创建。删除方法也有一个类似的检查,以确保通过File.Delete方法删除的文件是存在的。如果不执行这个检测,那么在删除一个不存在的文件时,方法将会抛出一个错误。
最后需要修改BooksController,在下面这个示例中,做了三处修改:
- 修改Create方法调用UploadFile方法;
- 修改Edit方法,先调用DeleteFile方法,然后再调用UploadFile方法;
- 修改DeleteConfirmed方法,调用DeleteFile方法删除文件后,将书籍从数据中删除。
using System; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Linq; using System.Linq.Dynamic; using System.Web; using System.Web.Mvc; using MvcApplication4.Models; using MvcApplication4.Utils; using PagedList; namespace MvcApplication4.Controllers { public class BooksController : Controller { private BookDBContext db = new BookDBContext(); … // // GET: /Books/Create public ActionResult Create() { return View(); } // // POST: /Books/Create [HttpPost] public ActionResult Create(Book book, HttpPostedFileBase file) { if (ModelState.IsValid) { // Upload our file book.Thumbnail = FileUpload.UploadFile(file); db.Books.Add(book); db.SaveChanges(); return RedirectToAction("Index"); } return View(book); } // // GET: /Books/Edit/5 public ActionResult Edit(int id) { Book book = db.Books.Find(id); return View(book); } // // POST: /Books/Edit/5 [HttpPost] public ActionResult Edit(Book book, HttpPostedFileBase file) { if (ModelState.IsValid) { // Delete old file FileUpload.DeleteFile(book.Thumbnail); // Upload our file book.Thumbnail = FileUpload.UploadFile(file); db.Entry(book).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } return View(book); } // // GET: /Books/Delete/5 public ActionResult Delete(int id) { Book book = db.Books.Find(id); return View(book); } // // POST: /Books/Delete/5 [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { Book book = db.Books.Find(id); // Delete old file FileUpload.DeleteFile(book.Thumbnail); db.Books.Remove(book); db.SaveChanges(); return RedirectToAction("Index"); } ... } }
参考