這里演示如何在MVC WEB應用程序如何上傳圖片到數據庫以及如何在WEB頁面上顯示圖片。數據庫表對應整個Model類,不單圖片數據一個字段,我們從數據表的定義開始:
CREATE TABLE [dbo].[Products] ( [ProductID] INT IDENTITY (1, 1) NOT NULL, [Name] NVARCHAR (MAX) NOT NULL, [Description] NVARCHAR (MAX) NOT NULL, [Price] DECIMAL (18, 2) NOT NULL, [Category] NVARCHAR (MAX) NOT NULL, [ImageData] VARBINARY (MAX) NULL, [ImageMimeType] NVARCHAR (MAX) NULL, CONSTRAINT [PK_dbo.Products] PRIMARY KEY CLUSTERED ([ProductID] ASC) );
保存圖片的字段為ImageData,類型VARBINARY,字段ImageMimeType保存圖片的類型。我們使用Entity framework處理c#對象到數據庫的操作,我們不需要編寫代碼通過SQL代碼操作數據庫,Entity framework負責數據庫的操作。Entity framework支持Model first和Code-first兩種方式,Code-first先編寫c# model類,然后綁定到數據庫,相關的內容可以參見http://msdn.microsoft.com/en-us/data/jj200620。
從Model類開始:
public class Product { [HiddenInput(DisplayValue = false)] public int ProductID { get; set; } [Required(ErrorMessage = "Please enter a product name")] public string Name { get; set; } [DataType(DataType.MultilineText)] [Required(ErrorMessage = "Please enter a description")] public string Description { get; set; } [Required] [Range(0.01, double.MaxValue, ErrorMessage = "Please enter a positive price")] public decimal Price { get; set; } [Required(ErrorMessage = "Please specify a category")] public string Category { get; set; } public byte[] ImageData { get; set; } [HiddenInput(DisplayValue = false)] public string ImageMimeType { get; set; } }
Entity framework可以在VS中使用nuget package manager安裝,安裝完entity framework我們創建Entity framework的會話類將Model和數據庫聯系起來:
using System.Data.Entity; namespace SportsStore.Domain.Concrete { public class EFDbContext : DbContext { public DbSet<Product> Products { get; set; } } }
我們還需要告訴Entity framework使用哪個數據庫,在web.config添加相應的連接字符串:
... <connectionStrings> <add name="EFDbContext" connectionString="Data Source=(localdb)\v11.0;Initial Catalog=SportsStore;Integrated Security=True" providerName="System.Data.SqlClient"/> </connectionStrings> ...
接下來創建一個使用EFDbContext操作Model的輔助類:
public interface IProductRepository { IQueryable<Product> Products { get; } void SaveProduct(Product product); Product DeleteProduct(int productID); } public class EFProductRepository : IProductRepository { private EFDbContext context = new EFDbContext(); public IQueryable<Product> Products { get { return context.Products; } } public void SaveProduct(Product product) { if (product.ProductID == 0) { context.Products.Add(product); } else { Product dbEntry = context.Products.Find(product.ProductID); if (dbEntry != null) { dbEntry.Name = product.Name; dbEntry.Description = product.Description; dbEntry.Price = product.Price; dbEntry.Category = product.Category; dbEntry.ImageData = product.ImageData; dbEntry.ImageMimeType = product.ImageMimeType; } } context.SaveChanges(); } public Product DeleteProduct(int productID) { Product dbEntry = context.Products.Find(productID); if (dbEntry != null) { context.Products.Remove(dbEntry); context.SaveChanges(); } return dbEntry; } }
定義IProductRepository接口是方便后續使用Dependency injection從接口獲取實現類EFProductRepository的實例,這里就不列出具體如何實現。EFProductRepository使用EFDbContext完成添加、保存對象到數據庫以及從數據庫刪除對象。完成數據模型的操作,下面定義控制器的方法:
public class AdminController : Controller { private IProductRepository repository; public AdminController(IProductRepository repo) { repository = repo; } public ViewResult Index() { return View(repository.Products); } public ViewResult Edit(int productId) { Product product = repository.Products .FirstOrDefault(p => p.ProductID == productId); return View(product); } [HttpPost] public ActionResult Edit(Product product, HttpPostedFileBase image) { if (ModelState.IsValid) { if (image != null) { product.ImageMimeType = image.ContentType; product.ImageData = new byte[image.ContentLength]; image.InputStream.Read(product.ImageData, 0, image.ContentLength); } repository.SaveProduct(product); TempData["message"] = string.Format("{0} has been saved", product.Name); return RedirectToAction("Index"); } else { // there is something wrong with the data values return View(product); } } public ViewResult Create() { return View("Edit", new Product()); } [HttpPost] public ActionResult Delete(int productId) { Product deletedProduct = repository.DeleteProduct(productId); if (deletedProduct != null) { TempData["message"] = string.Format("{0} was deleted", deletedProduct.Name); } return RedirectToAction("Index"); } }
這里兩個Edit ation,第一個顯示編輯上傳頁面,帶HttpPost特性的用於處理編輯頁面提交回傳,提交的image數據為HttpPostedFileBase類型,我們從中取出圖像文件的數據保存到Model類的ImageData屬性,ContentType則記錄到ImageMimeType屬性。對應的Edit視圖:
@model SportsStore.Domain.Entities.Product @{ ViewBag.Title = "Admin: Edit " + @Model.Name; Layout = "~/Views/Shared/_AdminLayout.cshtml"; } <h1>Edit @Model.Name</h1> @using (Html.BeginForm("Edit", "Admin", FormMethod.Post, new { enctype = "multipart/form-data" })) { @Html.EditorForModel() <div class="editor-label">Image</div> <div class="editor-field"> @if (Model.ImageData == null) { @:None } else { <img width="150" height="150" src="@Url.Action("GetImage", "Product", new { Model.ProductID })" /> } <div>Upload new image: <input type="file" name="Image" /></div> </div> <input type="submit" value="Save" /> @Html.ActionLink("Cancel and return to List", "Index") }
這里指定表單的enctype=multipart/form-data,缺少這個屬性表單提交的數據中會只有圖片文件的名稱而不包含圖片文件的數據。圖片顯示img單元的src指向一個從product的action生成的網址,我們還需要實現這個方法:
... public FileContentResult GetImage(int productId) { Product prod = repository.Products.FirstOrDefault(p => p.ProductID == productId); if (prod != null) { return File(prod.ImageData, prod.ImageMimeType); } else { return null; } } ...
這里從圖片文件數據和mime類型返回一個FileContentResult。
這就是實現上傳圖片到數據庫的完整過程,實際的應用中我們還需要限制文件大小,通過文件后綴名或者ContentType檢查是否是有效的圖片文件。
以上內容摘自《Apress Pro ASP.NET MVC 4》第四版,詳見原版 http://www.apress.com/9781430242369。