ASP.NET MVC 4 - 上传图片到数据库
这里演示如何在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。