天天看點

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。