Привязка данных(binding) является достаточно удобным средством ASP.NET MVC. Удобно оно в первую очередь тем, что позволяет скрыть реализацию преобразования данных между данными из модели и данными HTTP запроса.
В своих проектах я часто сталкиваюсь с необходимостью сохранять различные данные типа blob и image. В этой статье я бы хотел показать, как можно легко организовать и использовать привязку данных из модели, которые содержат различные изображения. Для примера я взял учебный проект MVC Music Store и решил его подправить — добавить возможность изменять изображение обложки музыкального альбома. При написании данной статьи, я использовал версию APS.NET MVC 3 и Razor.
В общем, привязка данных предназначена для загрузки и сохранения данных модели. Реализуем класс, который кастомизирует привязку данных по умолчанию.
В этой реализации мы переопределили основной метод BindModel, вызвали сначала базовую реализацию для привязки всех данных, а затем реализовали конвертацию и запись в поле данных изображения из поля формы HTTP запроса типа HttpPostedFileBase.
Так же, было бы неплохо создать свой атрибут:
Для создания вида нам также пригодится простой хелпер создания поля загрузки файла:
Теперь модифицируем основной проект.
В нашем проекте есть модель — класс Album. Добавим новое поле Image типа byte[]. Так же исключим это новое поле из стандартной привязки.
И не забудем добавить новое поле в базу.
Далее нам нужно каким-то образом декларировать нашу привязку. Это можно сделать, добавив атрибут в соответствующий Action, либо добавив привязку данных глобально.
К сожалению это не подойдет для метода Edit, где используется тип FormCollection
в аргументах. Тогда можно просто добавить новую привязку в коллекцию, например так:
Либо сделать это глобально, в файле Global.asax:
Для загрузки изображений, модифицируем вид и добавим поле в форму:
и изменим вызов хелпера формы, добавив новый HTML атрибут enctype = «multipart/form-data» для возможности аплодинга бинарных данных:
Вот примерно и все по поводу загрузки и сохранения.
Для отображения нам необходимо создать новый Action.
В этом примере, ограничимся использованием изображения png формата. Также нужно позаботиться о кешировании, указав соответствующий атрибут.
Для создания вида, нам пригодится простой хелпер:
А в самом виде добавим.
На этом примере я хотел показать, как можно быстро и легко организовать привязку данных в MVC. Как можно видеть, в результате использования привязки, код реализации вида, модели и контроллера получается достаточно простой и лаконичный. А самое главное то, что один раз реализовав привязку, мы можем использовать это где либо еще.
Что можно улучшить.
Добавить хелпер BeginForm — сокращенный вариант, для того, чтобы не писать имена контроллера и действия. Реализация Action для отображения достаточно примитивна. По-хорошему, нужно сохранять тип изображения и корректно его возвращать и реализовать валидацию банарных данных. А вместо хелперов, которые я привел в этой статье, более правильный метод это использовать шаблоны редактирования и просмотра. Можно также использовать такое мощное средство, как фильтры.
mvcmusicstore.codeplex.com
www.highoncoding.com/Articles/689_Uploading_and_Displaying_Files_Using_ASP_NET_MVC_Framework.aspx
www.hanselman.com/blog/SplittingDateTimeUnitTestingASPNETMVCCustomModelBinders.aspx
odetocode.com/Blogs/scott/archive/2009/04/27/6-tips-for-asp-net-mvc-model-binding.aspx
odetocode.com/blogs/scott/archive/2009/05/05/iterating-on-an-asp-net-mvc-model-binder.aspx
В своих проектах я часто сталкиваюсь с необходимостью сохранять различные данные типа blob и image. В этой статье я бы хотел показать, как можно легко организовать и использовать привязку данных из модели, которые содержат различные изображения. Для примера я взял учебный проект MVC Music Store и решил его подправить — добавить возможность изменять изображение обложки музыкального альбома. При написании данной статьи, я использовал версию APS.NET MVC 3 и Razor.
Реализация привязки
В общем, привязка данных предназначена для загрузки и сохранения данных модели. Реализуем класс, который кастомизирует привязку данных по умолчанию.
public class ImageModelBinder : DefaultModelBinder
{
private string _fieldName;
public ImageModelBinder(string fieldName)
{
_fieldName = fieldName;
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var obj = base.BindModel(controllerContext, bindingContext);
ValueProviderResult valueResult;
valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + "." + _fieldName);
if (valueResult == null)
{
valueResult = bindingContext.ValueProvider.GetValue(_fieldName);
}
if (valueResult != null)
{
HttpPostedFileBase file = (HttpPostedFileBase)valueResult.ConvertTo(typeof(HttpPostedFileBase));
if (file != null)
{
byte[] tempImage = new byte[file.ContentLength];
file.InputStream.Read(tempImage, 0, file.ContentLength);
PropertyInfo imagePoperty = bindingContext.ModelType.GetProperty(_fieldName);
imagePoperty.SetValue(obj, tempImage, null);
}
}
return obj;
}
}
В этой реализации мы переопределили основной метод BindModel, вызвали сначала базовую реализацию для привязки всех данных, а затем реализовали конвертацию и запись в поле данных изображения из поля формы HTTP запроса типа HttpPostedFileBase.
Так же, было бы неплохо создать свой атрибут:
public class ImageBindAttribute : CustomModelBinderAttribute
{
private IModelBinder _binder;
public ImageBindAttribute(string fieldName)
{
_binder = new ImageModelBinder(fieldName);
}
public override IModelBinder GetBinder() { return _binder; }
}
Для создания вида нам также пригодится простой хелпер создания поля загрузки файла:
public static IHtmlString ImageUpload(this HtmlHelper helper, string name)
{
return ImageUpload(helper, name, null);
}
public static IHtmlString ImageUpload(this HtmlHelper helper, string name, object htmlAttributes)
{
var tagBuilder = new TagBuilder("input");
tagBuilder.GenerateId(name);
UrlHelper urlHelper = new UrlHelper(helper.ViewContext.RequestContext);
tagBuilder.Attributes["name"] = name;
tagBuilder.Attributes["type"] = "file";
tagBuilder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
return MvcHtmlString.Create(tagBuilder.ToString());
}
Использование
Теперь модифицируем основной проект.
В нашем проекте есть модель — класс Album. Добавим новое поле Image типа byte[]. Так же исключим это новое поле из стандартной привязки.
[Bind(Exclude = "AlbumId, Image")]
public class Album
{
...
public string AlbumArtUrl { get; set; }
[ScaffoldColumn(false)]
public byte[] Image { get; set; }
...
}
И не забудем добавить новое поле в базу.
Далее нам нужно каким-то образом декларировать нашу привязку. Это можно сделать, добавив атрибут в соответствующий Action, либо добавив привязку данных глобально.
public ActionResult Create([ImageBind("Image")] Album album)
{
if (ModelState.IsValid)
{
storeDb.Albums.Add(album);
К сожалению это не подойдет для метода Edit, где используется тип FormCollection
в аргументах. Тогда можно просто добавить новую привязку в коллекцию, например так:
public ActionResult Edit(int id, FormCollection collection)
{
var album = storeDb.Albums.Find(id);
if (Binders[typeof(Album)] == null) Binders.Add(typeof(Album), new ImageModelBinder("Image"));
...
Либо сделать это глобально, в файле Global.asax:
ModelBinders.Binders.Add(typeof(Album), new ImageModelBinder("Image"));
Для загрузки изображений, модифицируем вид и добавим поле в форму:
<div class="editor-field">
@Html.ImageUpload("Image")
</div>
и изменим вызов хелпера формы, добавив новый HTML атрибут enctype = «multipart/form-data» для возможности аплодинга бинарных данных:
(Html.BeginForm("Edit", "StoreManager", FormMethod.Post, new { enctype = "multipart/form-data" }))
Вот примерно и все по поводу загрузки и сохранения.
Для отображения нам необходимо создать новый Action.
[OutputCache(Duration = 0)]
public ActionResult Image(int id)
{
var album = storeDb.Albums.Find(id);
return new FileStreamResult(new MemoryStream(album.Image), "image/png");
}
В этом примере, ограничимся использованием изображения png формата. Также нужно позаботиться о кешировании, указав соответствующий атрибут.
Для создания вида, нам пригодится простой хелпер:
public static IHtmlString Image(this HtmlHelper helper, string name, string id)
{
return Image(helper, name, id, null);
}
public static IHtmlString Image(this HtmlHelper helper, string name, string id, object htmlAttributes)
{
var tagBuilder = new TagBuilder("img");
UrlHelper urlHelper = new UrlHelper(helper.ViewContext.RequestContext);
tagBuilder.Attributes["src"] = urlHelper.Action(name, null, new { id = id });
tagBuilder.Attributes["alt"] = string.Format("{0} of {1}", name, id);
tagBuilder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
return MvcHtmlString.Create(tagBuilder.ToString());
}
А в самом виде добавим.
<div class="field">
@Html.Image("Image", @Model.AlbumId.ToString())
</div>
Заключение
На этом примере я хотел показать, как можно быстро и легко организовать привязку данных в MVC. Как можно видеть, в результате использования привязки, код реализации вида, модели и контроллера получается достаточно простой и лаконичный. А самое главное то, что один раз реализовав привязку, мы можем использовать это где либо еще.
Что можно улучшить.
Добавить хелпер BeginForm — сокращенный вариант, для того, чтобы не писать имена контроллера и действия. Реализация Action для отображения достаточно примитивна. По-хорошему, нужно сохранять тип изображения и корректно его возвращать и реализовать валидацию банарных данных. А вместо хелперов, которые я привел в этой статье, более правильный метод это использовать шаблоны редактирования и просмотра. Можно также использовать такое мощное средство, как фильтры.
Источники
mvcmusicstore.codeplex.com
www.highoncoding.com/Articles/689_Uploading_and_Displaying_Files_Using_ASP_NET_MVC_Framework.aspx
www.hanselman.com/blog/SplittingDateTimeUnitTestingASPNETMVCCustomModelBinders.aspx
odetocode.com/Blogs/scott/archive/2009/04/27/6-tips-for-asp-net-mvc-model-binding.aspx
odetocode.com/blogs/scott/archive/2009/05/05/iterating-on-an-asp-net-mvc-model-binder.aspx