#asp.net-mvc-3 #razor
#asp.net-mvc-3 #razor
Вопрос:
У меня есть представление MVC3, доступное только для чтения, которое содержит таблицу, отображающую свойства элемента.
Для многих свойств элемента мы отслеживаем изменения, внесенные поставщиком в элемент. Так, например, поставщик может обновить свойство с именем «Цвет» со значения «Синий» на «Красный». В этом представлении в таблице перечислены все свойства, отслеживаемые в строке таблицы, со столбцом, показывающим «Старое значение» и «Новое значение». В следующем столбце либо отображается статус текущего изменения (ожидает утверждения, Одобрено или Отклонено). Однако для пользователей с правами администратора столбец будет содержать ссылки («Одобрить», «Отклонить» или «Сбросить до ожидающего утверждения»).
Мой код разметки и Razor для этого очень повторяющийся и выходит из-под контроля. Я хотел бы создать HtmlHelper для этого или, возможно, частичный просмотр, который я могу использовать для перемещения всего кода, а затем использовать его для каждого свойства элемента.
Вот пример кода, используемого для одного свойства. Этот код повторяется еще для примерно 10 свойств.
Я использую некоторые jquery и ajax для выполнения действий. Например, когда изменение отклоняется, пользователь должен ввести причину отклонения изменения.
<tr id="rowId-color">
<td>@Html.LabelFor(model => model.Color)</td>
<td>@Html.DisplayFor(model => model.Color)</td>
@if (Model.ChangeLog != null amp;amp; Model.ChangeLog.Item("Color") != null) {
var change = Model.ChangeLog.Item("Color");
var changeStatus = (ItemEnumerations.ItemChangeStatuses)change.ItemChangeStatusID;
<td>@change.OldValueDisplay</td>
<td id="tdstatusId-@change.ItemChangeID">
@if (changeStatus == ItemEnumerations.ItemChangeStatuses.AwaitingApproval amp;amp; User.IsInRole("TVAPMgr")) {
@Ajax.ActionLink("Approve", "Approve", new { itemChangeID = change.ItemChangeID }, new AjaxOptions { HttpMethod = "POST", Confirm = "Approve this change?", OnSuccess = "actionCompleted" })
@Html.Raw("|")
<a href="#dialog" name="reject" data-id="@change.ItemChangeID" >Reject</a>
}
else if ((changeStatus == ItemEnumerations.ItemChangeStatuses.Rejected || changeStatus == ItemEnumerations.ItemChangeStatuses.Approved) amp;amp; User.IsInRole("TVAPMgr")) {
@Ajax.ActionLink("Reset to Awaiting Approval", "Reset", new { itemChangeID = change.ItemChangeID }, new AjaxOptions { HttpMethod = "POST", Confirm = "Reset this change to Awaiting Approval?", OnSuccess = "actionCompleted" })
}
else {
@changeStatus.ToDisplayString()
}
</td>
<td id="tdreasonId-@change.ItemChangeID">@Html.DisplayFor(m => m.ChangeLog.Item(change.ItemChangeID).RejectedReason)</td>
}
else {
<td colspan="3">No Change</td>
}
</tr>
Ответ №1:
Это действительно больше похоже на DisplayTemplate для ItemChangeModel
типа, таким образом, вы можете просто сделать:
<tr id="rowId-color">
<td>@Html.LabelFor(model => model.Color)</td>
<td>@Html.DisplayFor(model => model.Color)</td>
@Html.DisplayFor(m => m.ChangeLog.Item("Color"))
</tr>
Для каждой ячейки журнала изменений и шаблона отображения это похоже на мини-представление с типизированной моделью ItemChangeModel
. Таким образом, ваш файл просмотра должен выглядеть следующим образом:
@model ItemChangeModel
@if(Model != null) {
<td>@Html.DisplayFor(m => m.OldValueDisplay)</td>
<td id="tdstatusId-@Model.ItemChangeID">
@switch((ItemEnumerations.ItemChangeStatuses) Model.ItemChangeStatusID) {
case ItemEnumerations.ItemChangeStatuses.AwaitingApproval:
if(User.IsInRole("TVAPMgr")) {
@Ajax.ActionLink("Approve", "Approve", new { itemChangeID = change.ItemChangeID }, new AjaxOptions { HttpMethod = "POST", Confirm = "Approve this change?", OnSuccess = "actionCompleted" })
@Html.Raw("|")
<a href="#dialog" name="reject" data-id="@change.ItemChangeID" >Reject</a>
}
break;
case ItemEnumerations.ItemChangeStatuses.Rejected:
case ItemEnumerations.ItemChangeStatuses.Approved:
if(User.IsInRole("TVAPMgr")) {
@Ajax.ActionLink("Reset to Awaiting Approval", "Reset", new { itemChangeID = change.ItemChangeID }, new AjaxOptions { HttpMethod = "POST", Confirm = "Reset this change to Awaiting Approval?", OnSuccess = "actionCompleted" })
} else {
@changeStatus.ToDisplayString()
}
@break;
}
</td>
<td id="tdreasonId-@change.ItemChangeID">@Html.DisplayFor(m => m.RejectedReason) </td>
} else {
<td colspan="3">No Change</td>
}
(Сложно закодировать в окне редактора, для этого может потребоваться некоторая очистка, но я думаю, вы поймете идею)
Вы добавляете этот шаблон отображения (с именем файла ItemChangeModel.cshtml) в папку Views Shared DisplayTemplates, и он будет использоваться всякий раз, когда для этого типа выполняется вызов DisplayFor.
В комментариях было отмечено, что вы не можете использовать метод в DisplayFor, но вы можете изменить его на индексированное свойство:
public class ChangeLog
{
public ItemChangeModel this[string key] { get { return Item("Color"); } }
}
Затем используйте:
@Html.DisplayFor(m => m.ChangeLog["Color"])
Комментарии:
1. Если вы хотите стать еще более причудливым, вы можете добавить DisplayTemplate с именем ChangeHistory, который может повторять свойства и делать это также более автоматизированным способом
ModelMetadata.Properties
.2. Я думаю, что ваш ответ сработает. Предложение комментария было бы затруднительным, поскольку в каждом из свойств есть некоторые различия, но в интересующем меня разделе достаточно сходств. Не могли бы вы добавить немного кода к своему ответу. Тип, возвращаемый из журнала изменений. Элемент имеет тип ItemChangeModel
3.
m => m.ChangeLog.Item("Color")
недопустимое выражение, которое принял бы строго типизированный помощник DisplayFor. Он содержит вызов функции, и это не поддерживается.4. Вы уверены, что в MVC3 он обрабатывает вызовы и более сложный доступ к элементам, в частности, доступ к индексу с одним параметром… нужно было бы собрать небольшой образец, чтобы попробовать, думаю, я смогу это сделать.
5. Дмитрий прав, вы не можете использовать метод для вызова DisplayFor, но вы можете так же легко использовать
@Html.Display
или изменитьItem
свойство, чтобы оно было индексируемым свойством, а не методом.
Ответ №2:
Вы не показали и не объяснили, как выглядят ваши модели домена и представления, но я подозреваю, что то, что вы используете здесь, не является подходящей моделью представления для этого конкретного требования представления. Лучшей моделью представления была бы модель, имеющая список свойств для утверждения, который будет показан в таблице.
В любом случае, одним из возможных подходов является написание пользовательского помощника HTML, чтобы ваш вид выглядел следующим образом:
<tr id="rowId-color">
@Html.DisplayFor(x => x.Color)
@Html.ChangeLogFor(x => x.Color)
</tr>
...
и помощник может быть чем-то вроде:
public static class HtmlExtensions
{
public static IHtmlString ChangeLogFor<TProperty>(
this HtmlHelper<MyViewModel> html,
Expression<Func<MyViewModel, TProperty>> ex
)
{
var model = html.ViewData.Model;
var itemName = ((MemberExpression)ex.Body).Member.Name;
var change = model.ChangeLog.Item(itemName);
if (change == null)
{
return new HtmlString("<td colspan="3">No Change</td>");
}
var isUserTVAPMgr = html.ViewContext.HttpContext.User.IsInRole("TVAPMgr");
var changeStatus = (ItemChangeStatuses)change.ItemChangeStatusID;
var sb = new StringBuilder();
sb.AppendFormat("<td>{0}</td>", html.Encode(change.OldValueDisplay));
sb.AppendFormat("<td id="tdstatusId-{0}">", change.ItemChangeID);
var ajax = new AjaxHelper<MyViewModel>(html.ViewContext, html.ViewDataContainer);
if (changeStatus == ItemChangeStatuses.AwaitingApproval amp;amp; isUserTVAPMgr)
{
sb.Append(
ajax.ActionLink(
"Approve",
"Approve",
new {
itemChangeID = change.ItemChangeID
},
new AjaxOptions {
HttpMethod = "POST",
Confirm = "Approve this change?",
OnSuccess = "actionCompleted"
}).ToHtmlString()
);
sb.Append("|");
sb.AppendFormat("<a href="#dialog" name="reject" data-id="{0}">Reject</a>", change.ItemChangeID);
}
else if ((changeStatus == ItemChangeStatuses.Rejected || changeStatus == ItemChangeStatuses.Approved) amp;amp; isUserTVAPMgr)
{
sb.Append(
ajax.ActionLink(
"Reset to Awaiting Approval",
"Reset",
new {
itemChangeID = change.ItemChangeID
},
new AjaxOptions {
HttpMethod = "POST",
Confirm = "Reset this change to Awaiting Approval?",
OnSuccess = "actionCompleted"
}
).ToHtmlString()
);
}
else
{
sb.Append(changeStatus.ToDisplayString());
}
sb.AppendLine("</td>");
sb.AppendFormat(
"<td id="tdreasonId-{0}">{1}</td>",
change.ItemChangeID,
html.Encode(model.ChangeLog.Item(change.ItemChangeID).RejectedReason)
);
return new HtmlString(sb.ToString());
}
}
Лучшим подходом было бы повторно адаптировать вашу модель представления к требованиям этого представления и просто использовать шаблоны отображения.
Комментарии:
1. Я использовал ваш код почти дословно, и он работает для большинства свойств. Однако отслеживание изменений использует имена свойств в качестве ключей, однако они не всегда совпадают со свойствами этой модели. Например, модель имеет свойство Material, но при изменении используется ключ MaterialID . Есть ли аннотация данных, которую я могу использовать для их сопоставления? Я думал об использовании атрибута KeyAttribute в системе. ComponentModel. Примечания к данным.
2. @JeffReddy, да, вы могли бы использовать аннотации данных для этого случая. Их значения будут считываться из метаданных модели внутри помощника.
3. Да, это то, на что я надеялся. Уже некоторое время гуглил это. Пытаюсь выяснить, какие аннотации к данным использовать и как получить значения из метаданных, а также как вообще получить доступ к метаданным от моего помощника.