Uploading file with other model data, validate model & file before uploading by using DataAnnotation is a tricky part because there is no FileFor or any similar control in MVC. So very first question comes in mind, is that possible to use DataAnnotation with file control? Yes, it is very possible. Second question, it this possible with client side validation, even the answer is yes.

We know that we use html control input type = "file" to choose and upload any kind of file. When we need to create a text box we use TextBoxFor and it renders input type = "text", so if we will use TextBoxFor and add attribute type="file" then it will work as file upload control, is not it?

// Normal HTML file control
<input type="file" id="image" name="image">
// Normal Text Box HTML Control
<input type="file" id="image" name="image">
// MVC: we will use this
@Html.TextBoxFor(model=>model.MyImage, new { @type = "file"} )
// It will renders for us
<input type="file" id="MyImage" name="MyImage">

This is the trick we will use to render the file control and post file to control with model data. It will be able to validate inout client side as well as server side. Do a title bit google and you will notice most of the people uses html control directly, yes, that will work if you will give the same name as the model property then it will use the model validation.

We are using Code First method so let's see our Products Table:

  • ProductId
  • ProductName
  • Price
  • CategoryId
  • Image
  • Thumb

I am planning to store image into folder in two different sizes, 1. big image and 2. thumbnail. In database we will store the path of these images. For model we will use a separate model which will accept

  • ProductName
  • Price
  • CategoryId
  • Uploading

Let's create our model, so what would be the data type for ImageUpload? we will use HttpPostedFileBase, rest of the code are normal

 public class ProductModel
{
public Int32 ProductId { get; set; } [Required]
[Display(Name = "Product Name")]
public String ProductName { get; set; } [Required]
[Display(Name = "Price")]
public Decimal Price { get; set; } [Required]
[Display(Name = "Category")]
public Int32 CategoryId { get; set; } [Required]
[DataType(DataType.Upload)]
[Display(Name = "Choose File")]
public HttpPostedFileBase ImageUpload { get; set; }
}

Create get controller: we will use ViewBag.Categories to bind the dropdown from Category table

// GET: Products/Create
public ActionResult Create()
{
// To Bind the category drop down in search section
ViewBag.Categories = db.Categories.Where(x => x.IsActive == true);
var model = new ProductModel();
return View(model);
}

Right click in side the Create action method and create view for it. Add following html into it

@model Advance.Learning.WebApp.Models.ProductModel

@{
ViewBag.Title = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
} <h1>New Product</h1>
<hr />
@using (Html.BeginForm("Create", "Products",
FormMethod.Post, new { enctype = "multipart/form-data" }))
{
@Html.AntiForgeryToken() <div class="form-horizontal"> <div class="form-group">
@Html.LabelFor(m => m.ProductName, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.ProductName,
new { @class = "form-control", @placeholder = "Enter product name" })
@Html.ValidationMessageFor(m => m.ProductName, "", new { @class = "text-danger" })
</div>
</div> <div class="form-group">
@Html.LabelFor(m => m.Price, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.Price,
new { @class = "form-control", @placeholder = "Enter product price" })
@Html.ValidationMessageFor(m => m.Price, "", new { @class = "text-danger" })
</div>
</div> <div class="form-group">
@Html.LabelFor(m => m.CategoryId, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownListFor(m => m.CategoryId,
new SelectList(ViewBag.Categories, "CategoryId", "CategoryName"),
"Choose Category", new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.CategoryId, "", new { @class = "text-danger" })
</div>
</div> <div class="form-group">
@Html.LabelFor(m => m.ImageUpload, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.ImageUpload, new { type = "file", @class = "form-control" })
@Html.ValidationMessageFor(m => m.ImageUpload, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-success" />
@Html.ActionLink("Cancel", "Index", null, new { @class = "btn btn-warning" })
</div>
</div>
</div>
}
<div>
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}

To post the selected file to control we need to add the attribute enctype

// Either we can directly use the html tag form
<form action="/Products/Create"
enctype="multipart/form-data"
method="post"
novalidate="novalidate"> // Same can be render by following code, which I used in view
@using (Html.BeginForm("Create", "Products", FormMethod.Post,
new { enctype = "multipart/form-data" }))

How I tried to use the file upload control

<div class="form-group">
@Html.LabelFor(m => m.ImageUpload, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.ImageUpload, new { type = "file", @class = "form-control" })
@Html.ValidationMessageFor(m => m.ImageUpload, "", new { @class = "text-danger" })
</div>
</div>

ow to bind the Category drop down:

@Html.DropDownListFor(m => m.CategoryId,
new SelectList(ViewBag.Categories, "CategoryId", "CategoryName"),
"Choose Category",
new { @class = "form-control" })

We are set to save the product record with image, so we need a folder to save the images in our application, create a folder ProductImages on the root of application and add Create post method in products controller

 [HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ProductModel model)
{
var imageTypes = new string[]{
"image/gif",
"image/jpeg",
"image/pjpeg",
"image/png"
};
if (model.ImageUpload == null || model.ImageUpload.ContentLength == )
{
ModelState.AddModelError("ImageUpload", "This field is required");
}
else if (!imageTypes.Contains(model.ImageUpload.ContentType))
{
ModelState.AddModelError("ImageUpload", "Please choose either a GIF, JPG or PNG image.");
} if (ModelState.IsValid)
{
var product = new Product();
product.ProductName = model.ProductName;
product.Price = model.Price;
product.CategoryId = model.CategoryId; // Save image to folder and get path
var imageName = String.Format("{0:yyyyMMdd-HHmmssfff}", DateTime.Now);
var extension = System.IO.Path.GetExtension(model.ImageUpload.FileName).ToLower();
using (var img = System.Drawing.Image.FromStream(model.ImageUpload.InputStream))
{
product.Image = String.Format("/ProductImages/{0}{1}", imageName, extension);
product.Thumb = String.Format("/ProductImages/{0}_thumb{1}", imageName, extension); // Save thumbnail size image, 100 x 100
SaveToFolder(img, extension, new Size(, ), product.Thumb); // Save large size image, 600 x 600
SaveToFolder(img, extension, new Size(, ), product.Image);
} db.Products.Add(product);
db.SaveChanges();
return RedirectToAction("Index");
} // If any error return back to the page
ViewBag.Categories = db.Categories.Where(x => x.IsActive == true);
return View(model);
}
  • First create an array of image type to support
  • Check if file is available
  • Check if file type is in supported types
  • check if mode is valid: ModelState.IsValid
  • Create a file name from datetime with milliseconds, even we can use GUID
  • SaveToFolder method to save files in folder see who I resize image

    private void SaveToFolder(Image img, string extension, Size newSize, string pathToSave) { // Get new resolution Size imgSize = NewImageSize(img.Size, newSize); using (System.Drawing.Image newImg = new Bitmap(img, imgSize.Width, imgSize.Height)) { newImg.Save(Server.MapPath(pathToSave), img.RawFormat); } }

Run the application and try to add a new product, I found everything working.

Edit Record: To edit the record we cannot use the ProductModel because in it ImageUpload is mandatory and it is not necessary to change the image in every edit, might be we only want to update only price or category, so we will use our Product table model for edit. To update the image we will create a separate page.

// GET: Products/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Product product = db.Products.Find(id);
if (product == null)
{
return HttpNotFound();
} ViewBag.Categories = db.Categories.Where(x => x.IsActive == true);
return View(product);
} [HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "ProductId,ProductName,Price,CategoryId")] Product model)
{
if (ModelState.IsValid)
{
var product = db.Products.Find(model.ProductId);
if (product == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
product.ProductName = model.ProductName;
product.Price = model.Price;
product.CategoryId = model.CategoryId;
db.SaveChanges();
return RedirectToAction("Index");
} ViewBag.Categories = db.Categories.Where(x => x.IsActive == true);
return View(model);
}

In get method we used db.Products.Find(id), the find mehtod takes one parameter, primary key to find the record.

In post method we find the product on the basis of primary key (product Id) and update the values from model (passed values) and update the database. Here is the complete view Html

@model Advance.Learning.WebApp.Models.Product

@{
ViewBag.Title = "Edit Product";
Layout = "~/Views/Shared/_Layout.cshtml";
} <h1>Edit Product</h1>
<hr /> @using (Html.BeginForm())
{
@Html.AntiForgeryToken() <div class="form-horizontal"> @Html.HiddenFor(m => m.ProductId) <div class="form-group">
@Html.LabelFor(m => m.ProductName, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.ProductName, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.ProductName, "", new { @class = "text-danger" })
</div>
</div> <div class="form-group">
@Html.LabelFor(m => m.Price, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.Price, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Price, "", new { @class = "text-danger" })
</div>
</div> <div class="form-group">
@Html.LabelFor(m => m.CategoryId, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownListFor(m => m.CategoryId,
new SelectList(ViewBag.Categories, "CategoryId", "CategoryName"),
"Choose Category", new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.CategoryId, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<img src="@Model.Thumb" alt="@Model.ProductName image" class="img-thumbnail" />
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-success" />
@Html.ActionLink("Cancel", "Index", null, new { @class = "btn btn-warning" })
</div>
</div>
</div>
} @section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}

Detail Page: Simplest page with all the controls disabled and only one button to go back to listing page, action method and html

// GET: Products/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Product product = db.Products.Find(id);
if (product == null)
{
return HttpNotFound();
}
ViewBag.Category = db.Categories.Find(product.CategoryId).CategoryName;
return View(product);
} // View @model Advance.Learning.WebApp.Models.Product
@{
ViewBag.Title = "Details";
Layout = "~/Views/Shared/_Layout.cshtml";
} <h2>Product Details</h2>
<hr />
<div>
<div class="form-horizontal">
<div class="form-group">
@Html.LabelFor(m => m.ProductName, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.ProductName,
new { @class = "form-control", @disabled = "disabled" })
</div>
</div> <div class="form-group">
@Html.LabelFor(m => m.Price, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.Price,
new { @class = "form-control", @disabled = "disabled" })
</div>
</div> <div class="form-group">
@Html.LabelFor(m => m.Price, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.TextBoxFor(m=>m.CategoryId, ((String)ViewBag.Category),
new { @class = "form-control", @disabled = "disabled" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<img src="@Model.Thumb" alt="@Model.ProductName image" class="img-thumbnail" />
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
@Html.ActionLink("Cancel", "Index", null, new { @class = "btn btn-warning" })
</div>
</div>
</div>
</div>

Delete Record: Before delete show the detail of the product so user can delete record with confident

// GET: Products/Delete/5
public ActionResult Delete(int? id)
{
if (id == null)
return new HttpStatusCodeResult(HttpStatusCode.BadRequest); Product product = db.Products.Find(id);
if (product == null)
return HttpNotFound(); ViewBag.Category = db.Categories.Find(product.CategoryId).CategoryName;
return View(product);
} // POST: Products/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Product product = db.Products.Find(id);
db.Products.Remove(product);
db.SaveChanges();
return RedirectToAction("Index");
} // VIEW HTML
@model Advance.Learning.WebApp.Models.Product @{
ViewBag.Title = "Delete";
Layout = "~/Views/Shared/_Layout.cshtml";
} <h2>Delete Product</h2> <h3 class="text-danger">Are you sure you want to delete this product?</h3> <div class="form-horizontal">
<div class="form-group">
@Html.LabelFor(m => m.ProductName, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.ProductName,
new { @class = "form-control", @disabled = "disabled" })
</div>
</div> <div class="form-group">
@Html.LabelFor(m => m.Price, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.Price,
new { @class = "form-control", @disabled = "disabled" })
</div>
</div> <div class="form-group">
@Html.LabelFor(m => m.Price, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.CategoryId, ((String)ViewBag.Category),
new { @class = "form-control", @disabled = "disabled" })
</div>
</div> <div class="form-group">
<div class="col-md-offset-2 col-md-10">
<img src="@Model.Thumb" alt="@Model.ProductName image" class="img-thumbnail" />
</div>
</div> @using (Html.BeginForm())
{
@Html.AntiForgeryToken() <div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Delete" class="btn btn-danger" />
@Html.ActionLink("Cancel", "Index", null, new { @class = "btn btn-warning" })
</div>
</div>
}
</div>

If you want to use separate image upload page we can use very similar to create page because it has everything to update the image and update record. To update we need record Id which is already in ProductModel so will be easy to use it.

本文来自:http://www.advancesharp.com/blog/1142/asp-net-mvc-upload-file-with-record-validation-step-6

ASP.Net MVC upload file with record & validation - Step 6的更多相关文章

  1. Asp.net mvc 3 file uploads using the fileapi

    Asp.net mvc 3 file uploads using the fileapi I was recently given the task of adding upload progress ...

  2. asp.net mvc return file result

    asp.net mvc返回文件: public ActionResult ExportReflection(string accessToken) { var reflections = GetCms ...

  3. [转]ASP.NET MVC 2: Model Validation

    本文转自:http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx?CommentPo ...

  4. 【转】ASP.NET MVC教程

    转自:http://www.cnblogs.com/QLeelulu/category/123326.html ASP.NET MVC的最佳实践与性能优化的文章 摘要: 就一些文章链接,就不多废话了. ...

  5. ASP.NET没有魔法——ASP.NET MVC 模型验证

    在前面的文章中介绍了用户的注册及登录功能,在注册用户时可以通过代码的形式限制用户名及密码的格式,如果不符合要求那么就无法完成操作,如下图: 该功能的原理是Identity基于的Entity Frame ...

  6. Detailed ASP.NET MVC Pipeline

    Posted By : Shailendra Chauhan, 27 Jan 2014 P.NET MVC is an open source framework built on the top o ...

  7. ASP.NET MVC轻教程 Step By Step 13——页面布局

    一般在一个网站中页面会使用相同的结构和元素,如果每个页面都要重复添加这些元素,不仅繁琐更会给我们后期维护带来大麻烦.所以我们采用网页模板之类的技术,将固定不变的元素放入模板,同时留下一些占位符供页面各 ...

  8. ASP.NET MVC 学习7、为Model Class的字段添加验证属性(validation attribuate)

    Adding Validation to the Model ,在Model中添加数据验证 参考:http://www.asp.net/mvc/tutorials/mvc-4/getting-star ...

  9. <转>ASP.NET学习笔记之MVC 3 数据验证 Model Validation 详解

    MVC 3 数据验证 Model Validation 详解  再附加一些比较好的验证详解:(以下均为引用) 1.asp.net mvc3 的数据验证(一) - zhangkai2237 - 博客园 ...

随机推荐

  1. java8 函数式接口——Function/Predict/Supplier/Consumer

    Function 我们知道Java8的最大特性就是函数式接口.所有标注了@FunctionalInterface注解的接口都是函数式接口,具体来说,所有标注了该注解的接口都将能用在lambda表达式上 ...

  2. ThinkPHP 5.1 基础知识

    ==========================================//模板中的默认标题{$title|default='默认标题'}========================= ...

  3. Codeforces Round #482 (Div. 2)

    D. Kuro and GCD and XOR and SUM 字典树真好玩... 牛老板提供的思路:建1e5个 字典树,每个数插入到以它的因子为根所在的字典树中,这样就实现了整除,当然gcd(k, ...

  4. Java原来如此-反射机制

    在Java运行时环境中,对于任意一个类,能知道这个类有哪些属性和方法.对于任意一个对象,能调用它的任意一个方法.这种动态获取类的信息以及动态调用对象的方法的功能来自于Java语言的反射(Reflect ...

  5. 微信小程序踩坑之一【weui-wxss-master单选按钮图标修改思路】

    小程序原生所带的weui框架做小程序UI实在太方便了,但是他的一些细微变化也是让开发中碰到不少头疼的问题 一直以来单选多选的美化都是设计师重点表达的地方之一 而weui-wxss-master中的单选 ...

  6. GitHub 上受欢迎的 Android UI Library 整理(一)

    抽屉菜单 https://github.com/mikepenz/MaterialDrawer ★7337 - 安卓抽屉效果实现方案https://github.com/Yalantis/Side-M ...

  7. 2012-2013 ACM-ICPC, NEERC, Central Subregional Contest

    A Hanoi Tower 递归 题意: 大家都很熟悉汉诺塔的递归程序,现在给你一个组合,询问你这个组合是否会出现在汉诺塔的递归过程中. 题解: 将汉诺塔的递归程序反过来思考,考虑当前最大的那个盘,我 ...

  8. java基础第三篇

    6.Java 中的容器(重点) a.变量:变量是一个容器,它存储的单个值 //int i=3; 1.局部变量:定义在方法中,没有默认值 2.成员变量:定义在类中方法外,这个事物的属性(特征)定义为成员 ...

  9. HDU5618 Jam's problem again

    CDQ分治模板题 #include<cstdio> #include<cctype> #include<algorithm> #include<cstring ...

  10. 6.【nuxt起步】-完成一个静态的页面

    1.接下来新建/component/maincontent.vue 把这些html代码copy到maincontent.vue 发现格式比较难看,就格式化一下 2.插件安装 beautify,安装后重 ...