Предположим, в веб-приложении есть некий класс. При этом необходимо чтобы данные с формы заполняли только часть его свойств. Как поступить в этом случае? Давайте рассмотрим несколько подходов для решения этой проблемы.
Исходная Модель
Перед тем как начать, посмотрим на Модель для примеров. Пусть это будет статья в блоге:
public class Article
{
public string Title { get; set; }
public string Content { get; set; }
public bool IsPublished { get; set; }
}
По логике приложения решение о публикации принимает только модератор, а не сами авторы. Таким образом, требуется при передаче ими данных на сервер не заполнять свойство isPublished. Разумеется, необходимо учесть, что недобросовестный пользователь может подставить значение в запрос. Поэтому только на отсутствие соответствующего поля ввода полагаться нельзя.
Техника с неявной связью
Данная техника использует атрибут ASP.NET MVC Bind для параметра Действия. Он позволяет определить, какие свойства должны быть заполнены или проигнорированы. Например, для описанного выше сценария это будет выглядеть так:
[HttpPost]
public ViewResult Edit([Bind(Include = "Title,Content")] Article article)
{
...
}
Здесь Bind указывает заполнять только два свойства: Title и Content. Но, в зависимости от задачи, можно перечислить игнорируемые свойства:
[HttpPost]
public ViewResult Edit([Bind(Exclude = "IsPublished")] Article article)
{
...
}
В любом из вариантов, даже если злоумышленник вручную поставит в запрос значение IsPublished, оно не будет передано в экземпляр Модели.
И еще один вариант, который эквивалентен использованию Bind с указанием Include:
[HttpPost]
public ViewResult Edit()
{
var article = new Article();
this.TryUpdateModel(article, includeProperties: new[] { "Title", "Content" });
...
}
Необходимо отметить явный минус этого подхода: имена свойств указаны в текстовом виде. А значит, что никаких ошибок на этапе компиляции, при их неправильном написании, не будет. Автоматически отследить подобные ситуации можно только с помощью модульного тестирования (unit testing).
Техника со сильным связыванием
В этом случае требуется определить интерфейс, который описывает изменяемые свойства:
public interface IEditableArticle
{
string Title { get; set; }
string Content { get; set; }
}
Разумеется, Модель также должна его реализовывать:
public class Article : IEditableArticle
{
public string Title { get; set; }
public string Content { get; set; }
public bool IsPublished { get; set; }
}
Тогда Действие будет выглядеть следующим образом:
[HttpPost]
public ActionResult Edit()
{
var article = new Article();
this.TryUpdateModel<IEditableArticle>(article);
...
}
Недостатком данной техники является необходимость наследования Моделью интерфейса, что не всегда желательно и возможно.
Архитектурный подход
Средние и большие решения, как правило, ��спользуют многослойную архитектуру. В такой ситуации, все ASP.NET MVC приложение может рассматриваться как один из элементов слоя UI. При этом, наверное самым удачным вариантом является создание своей внутренней модели для пользовательского ввода:
public class ArticleViewModel
{
public string Title { get; set; }
public string Content { get; set; }
}
Именно она будет использоваться в Представлении и как входной параметр Действия. В дальнейшем же, при передаче данных в другие слои, преобразовываться в требуемый ими тип. И если это необходимо делать в разных местах приложения, то можно написать следующий метод-расширение:
public static class ArticleViewModelExtensions
{
public static Article ToServiceTypeArticle(this ArticleViewModel articleViewModel)
{
return new Article() {
Title = articleViewModel.Title,
Content = articleViewModel.Content,
IsPublished = false
};
}
}
Соответственно, Действие примет вид:
[HttpPost]
public ViewResult Edit(ArticleViewModel article)
{
...
this.SomeArticleService.Update(article.ToServiceTypeArticle());
...
}
Из сложностей архитектурного подхода можно отметить необходимость согласования атрибутов основной Модели и Модели для пользовательского ввода.
Запрет на изменение свойства
Необходимо упомянуть еще об одном способе. Он доступен с 3 версии ASP.NET MVC и подходит, если значение свойства никогда не должно быть заполнено данными из Представления. В этом случае достаточно отметить его атрибутом ReadOnly:
public class Article
{
public string Title { get; set; }
public string Content { get; set; }
[ReadOnly(true)]
public bool IsPublished { get; set; }
}
В завершение стоит напомнить одну прописную истину: никогда не доверяйте вводимым данным.