Я использую asp.net mvc 2 с C # и DataAnnotations.
Ситуация такова: у меня есть пользовательский элемент управления, который строго типизирован для класса модели. Этот элемент управления отображается в представлении несколько раз, но с различными значениями свойств, такими как заголовки (например, все вопросы: вопрос 1, вопрос 2, вопрос 3 и т. Д.). Я могу написать собственный класс проверки, который проверяет этот объект в целом, но проблема в том, что я не могу получить конкретные теги Html.ValidationMessage (...) для отображения. Ошибки проверки отображаются только в сводке вверху представления, и я хочу, чтобы они отображались как вверху, так и рядом с конкретным элементом управления, который не прошел проверку.
Я пытался создать пользовательские классы проверки, подобные приведенным ниже, для проверки по каждому свойству, но проблема в том, что мне нужны два значения модели: Рейтинг и Заголовок. Фактические проверки бизнеса выполняются по рейтингу, но свойство Заголовок указывает пользователю, какой элемент управления на экране неверен.
[AttributeUsage(AttributeTargets.Property)]
public class RatingValidation : ValidationAttribute
{
public string Heading { get; private set; }
private readonly string ErrorRatingInvalid = "Rating for {0} is invalid. Rating is required and must be between 1 and 5.";
public override string FormatErrorMessage(string name)
{
String errorMsg = ErrorRatingInvalid;
return String.Format(CultureInfo.CurrentUICulture,
errorMsg,
Heading);
}
public override bool IsValid(object value)
{
bool isValidResult = true;
if (value == null)
return false;
//Trying to do something like the following. This doesn't work because the
//attribute is applied to a property so only that property value is passed.
//In this case, the type of myRatingObject would likely be the same as the
//property validated.
var myRatingObject = TypeDescriptor.GetProperties(value);
this.Heading = myRatingObject.Heading;
if( myRatingObject.Rating < 1 || myRatingObject.Rating > 5)
isValidResult = false;
return isValidResult;
}
}
Пример класса моей модели:
public class MyModel
{
public MyModel()
{
//this.IsEditable = true;
}
public String Heading { get; set; }
[RatingValidation()]
public int Rating { get; set; }
}
Мысли
РЕДАКТИРОВАТЬ 1
Я хотел написать еще немного кода, чтобы лучше объяснить, почему создание отдельных валидаторов классов в этом случае не будет решением.
Напомним, что вся эта проверка входит в настраиваемое частичное представление со строгой типизацией. Точнее говоря, это шаблон редактирования. Основная модель будет иметь список модели того же типа, что и это частичное представление. В целях примера я пересмотрел некоторые из приведенного выше кода, чтобы лучше это проиллюстрировать. Я оставляю код выше этого редактирования нетронутым, поэтому обсуждение, ведущее к этому редактированию, имеет смысл и позволяет увидеть, где начался разговор и в каком направлении он шел.
Простите за код, так как демонстрационное приложение для тестирования это довольно многословно. Надеюсь, это лучше объясняет мою проблему.
[RatingsValidationAttribute()]
public class PersonRating
{
public String Heading { get; set; }
public int Index { get; set; }
public int Rating { get; set; }
}
public class Person
{
public String FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public List<PersonRating> Ratings { get; set; }
}
В моем основном представлении у меня есть:
<%= Html.EditorFor(m => m.Ratings) %>
Элемент управления Ratings View выглядит следующим образом:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ModelBindingResearch.Models.PersonRating>" %>
<%= Html.HiddenFor(m=>m.Heading) %>
<%= Html.HiddenFor(m=>m.Index) %>
<%= Html.DisplayTextFor(model => model.Heading) %>
:
<%= Html.TextBoxFor(model => model.Rating) %>
<%= Html.ValidationMessageFor(model => model.Rating, "*")%>
<%= Html.ValidationMessageFor(m=>m) %>
<br />
Класс атрибута проверки:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class RatingsValidationAttribute : ValidationAttribute
{
public RatingsValidationAttribute()
{
}
public int Rating { get; private set; }
public string Heading { get; set; }
private readonly string ErrorRatingInvalid = "Rating for {0} is invalid. Rating is required and must be between 1 and 5.";
public override string FormatErrorMessage(string name)
{
String errorMsg = ErrorRatingInvalid;
return String.Format(CultureInfo.CurrentUICulture,
errorMsg,
Heading);
}
public override bool IsValid(object value)
{
bool isValidResult = true;
PersonRating personRating = (value as PersonRating);
try
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
Heading = personRating.Heading;
if (personRating.Rating < 1 || //Rating must be b/t 1 & 5
personRating.Rating > 5)
{
isValidResult = false;
}
}
catch (Exception e)
{
//log error
}
return isValidResult;
}
}
Моя тестовая модель связующего. Это еще ничего не делает. Это просто исследовательский. Обратите внимание, что переменная o содержит полную объектную модель и все значения. Этот код сильно заимствует из: ASP.NET MVC - Пользовательское сообщение проверки для типов значений
public class TestModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
Object o = base.BindModel(controllerContext, bindingContext);
Object obj = bindingContext.Model;
Person p = (Person)o;
bindingContext.ModelState.AddModelError("FirstName", "Custom exception thrown during binding for firstname.");
return o;
}
protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, object value)
{
base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
String propertyName = propertyDescriptor.Name;
Object propertyValue = value;
}
private bool IsFormatException(Exception e)
{
if (e == null)
return false;
else if (e is FormatException)
return true;
else
return IsFormatException(e.InnerException);
}
}
My Home Controller (только методы, которые я добавил для представления Create):
public ActionResult Create()
{
return View(getPerson());
}
[HttpPost]
public ActionResult Create(Person p)
{
return View(p);
}
private Person getPerson()
{
Person p = new Person();
Address a = new Address();
PersonRating pr1 = new PersonRating();
PersonRating pr2 = new PersonRating();
PersonRating pr3 = new PersonRating();
pr1.Heading = "Initiative";
pr1.Rating = 5;
pr1.Index = 1;
pr2.Heading = "Punctuality";
pr2.Rating = 5;
pr1.Index = 2;
pr3.Heading = "Technical Knowledge";
pr3.Rating = 5;
pr3.Index = 3;
a.Street = "555 Somewhere Dr";
a.City = "City";
a.State = "AL";
p.FirstName = "Jason";
p.LastName = "Rhevax";
p.Age = 30;
p.PersonAddress = a;
p.Ratings.Add(pr1);
p.Ratings.Add(pr2);
p.Ratings.Add(pr3);
return p;
}
Наконец, Create.aspx полностью:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ModelBindingResearch.Models.Person>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Create
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Create</h2>
<% using (Html.BeginForm()) {%>
<%= Html.ValidationSummary() %>
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
<%= Html.LabelFor(model => model.FirstName) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(model => model.FirstName) %>
<%= Html.ValidationMessageFor(model => model.FirstName, "*") %>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model.LastName) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(model => model.LastName) %>
<%= Html.ValidationMessageFor(model => model.LastName) %>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model.Age) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(model => model.Age) %>
<%= Html.ValidationMessageFor(model => model.Age) %>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model.PersonAddress.Street) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(model => model.PersonAddress.Street)%>
<%= Html.ValidationMessageFor(model => model.PersonAddress.Street)%>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model.PersonAddress.City) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(model => model.PersonAddress.City)%>
<%= Html.ValidationMessageFor(model => model.PersonAddress.City)%>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model.PersonAddress.State) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(model => model.PersonAddress.State)%>
<%= Html.ValidationMessageFor(model => model.PersonAddress.State)%>
</div>
<div>
<%= Html.EditorFor(m => m.Ratings) %>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
<div>
<%= Html.ActionLink("Back to List", "Index") %>
</div>
</asp:Content>
Наконец, я зарегистрировал связыватель модели в методе application_start global.aspx через:
ModelBinders.Binders.Add(typeof(Person), new TestModelBinder());
Итак, проверка ниже работает, но вот проблема. Сообщение проверки, отображаемое классом RatingsValidationAttribute, является правильным, но я хочу, чтобы это сообщение отображалось только в верхней части страницы. На элементе управления я просто хочу, чтобы текстовое поле «Рейтинги» отображало «*». Вы заметите, что следующее
<%= Html.ValidationMessageFor(model => model.Rating, "*")%>
не отображает звездочку. Я полагаю, это потому, что атрибут проверяется на уровне класса. При проверке ModelState в моём модуле связывания ключ, который на самом деле содержит сообщение об ошибке, похож на: «Ratings [0]», что означает отображение этого сообщения, я должен использовать следующее:
<%= Html.ValidationMessageFor(model => model)%>
Во-первых, это безобразно. Во-вторых, будет проверено несколько проверок для каждого частичного элемента управления, поэтому я хочу, чтобы ошибка в сводке уточняла, в каком заголовке произошла ошибка, и просто используйте звездочки «*» для обозначения элементов управления, в которых есть ошибка.
Я прошу прощения за длину редактирования. Я пытаюсь обрабатывать вопросы, которые я задаю при переполнении стека, таким образом, что практически нет сомнений в том, какое было разрешение. Не стесняйтесь просить больше кода (в случае, если я забыл опубликовать что-то) или любые другие вопросы.
РЕДАКТИРОВАТЬ 3
Общее примечание о создании атрибута проверки на уровне класса: если проверка завершается неудачно и в ModelState добавляется ошибка, имя поля - это имя класса.Поскольку мой проверяемый класс представляет собой список из 3 объектов, которые содержатся в списке в представлении Model, ключи ModelState выглядят примерно так: Model.ListName [index].
Есть ли способ указать ключ, связанный с сообщением об ошибке, для пользовательского класса проверки, который оценивается на уровне класса?Для настраиваемых атрибутов проверки на уровне полей это не проблема.
РЕДАКТИРОВАТЬ 4
В этом сообщении блога http://blog.ceredir.com/index.php/2010/08/10/mvc2-cross-field-validation/, рассматриваются проблемы этого вопроса.,Моя единственная проблема заключается в том, что мне требуется создать 4 класса для каждой проверки, которую я хочу выполнить, что довольно много.Тем не менее, это первый рабочий пример, который показывает способ применения атрибута проверки к полю / свойству и доступа ко всей модели для этого класса.
Теперь моя единственная оставшаяся проблема - попытка перехватить некоторые позадивалидация сцен, которую фреймворк делает для вас.Я говорю о том, что если у вас есть поле int в вашей форме и вы не указываете значение, платформа добавляет сообщение проверки, например:
Поле _____ обязательно для заполнения.(где ______ - имя поля int).
Это также делает нечто подобное, если вы предоставляете текстовое значение для поля int:
Значение 'asdf' недопустимо для ______,
Из-за моей ситуации, когда эти проверки выполняются на модели, которая повторяется несколько раз, мне нужно изменить эти сообщения, чтобы отобразить свойство Heading объекта в сообщениях выше.Приведенные выше сообщения могут измениться следующим образом:
Поле __________ является обязательным.-> Поле {0} для {1} является обязательным.
Значение 'asdf' недопустимо для (имя поля).-> Значение {0} для {1} недопустимо для {2}.-> Значение рейтинга «asdf» недействительно для технических знаний.
Надеюсь, это имело смысл.Я думаю, что я перевела весь этот вопрос в бессмысленный характер.Я мог бы вернуться и попытаться как-то перефразировать.Было бы полезно, если бы я мог получить какую-то обратную связь.