ASP.NET Web Forms 4.5 привязка модели, где модель содержит коллекцию
Я пытаюсь обновить старое приложение Web Forms, чтобы использовать новые возможности привязки к модели, добавленные в 4.5, аналогичные свойствам привязки MVC.
У меня возникли проблемы с созданием редактируемого FormView, который представляет собой единственную модель, содержащую простых членов плюс член, который представляет собой коллекцию других моделей. Мне нужно, чтобы пользователь мог редактировать простые свойства родительского объекта и свойства дочерней коллекции.
Проблема в том, что дочерняя коллекция (ProductChoice.Extras
) всегда равна null после привязки к модели, когда код пытается обновить модель.
Вот мои модели:
[Serializable]
public class ProductChoice
{
public ProductChoice()
{
Extras = new List<ProductChoiceExtra>();
}
public int Quantity { get; set; }
public int ProductId { get; set; }
public List<ProductChoiceExtra> Extras { get; set; }
}
[Serializable]
public class ProductChoiceExtra
{
public int ExtraProductId { get; set; }
public string ExtraName { get; set; }
public int ExtraQuantity { get; set; }
}
И мой код управления пользователя:
public partial class ProductDetails : System.Web.UI.UserControl
{
private Models.ProductChoice _productChoice;
protected void Page_Load(object sender, EventArgs e)
{
_productChoice = new Models.ProductChoice()
{
Quantity = 1,
ProductId = 1
};
_productChoice.Extras.Add(new Models.ProductChoiceExtra()
{
ExtraProductId = 101,
ExtraName = "coke",
ExtraQuantity = 1
});
_productChoice.Extras.Add(new Models.ProductChoiceExtra()
{
ExtraProductId = 104,
ExtraName = "sprite",
ExtraQuantity = 2
});
}
public Models.ProductChoice GetProduct()
{
return _productChoice;
}
public void UpdateProduct(Models.ProductChoice model)
{
/* model.Extras is always null here, it should contain two ProductChoiceExtra objects */
if (TryUpdateModel(_productChoice) == true)
{
}
}
}
Моя контрольная разметка:
<div id="selectOptions">
<asp:FormView runat="server" ID="fvProductSelection" DefaultMode="Edit"
ItemType="Models.ProductChoice"
SelectMethod="GetProduct"
UpdateMethod="UpdateProduct" >
<EditItemTemplate>
<asp:linkbutton id="UpdateButton" text="Update" commandname="Update" runat="server"/>
<asp:HiddenField runat="server" ID="ProductId" Value="<%# BindItem.ProductId %>" />
<asp:TextBox Text ="<%# BindItem.Quantity %>" ID="Quantity" runat="server" />
<asp:Repeater ID="Extras" ItemType="Models.ProductChoiceExtra" DataSource="<%# BindItem.Extras %>" runat="server">
<ItemTemplate>
<asp:HiddenField Value="<%# BindItem.ExtraProductId %>" ID="ExtraProductId" runat="server" />
<asp:Label Text="<%# BindItem.ExtraName %>" ID="Name" runat="server" />
<asp:TextBox Text="<%# BindItem.ExtraQuantity %>" ID="Quantity" runat="server" />
</ItemTemplate>
</asp:Repeater>
</EditItemTemplate>
</asp:FormView>
</div>
Я попытался сделать свойство Extras
a BindingList
, а не List
, но это не имело никакого значения, коллекция Extras
не связана в методе UpdateProduct
.
Ответы
Ответ 1
К сожалению, я точно не знаю, как это делается с веб-формами, поэтому я не уверен, как воспроизводить это с помощью ретранслятора, но в MVC для связывания модели требуется индекс для восстановления списка. Если мне придется угадать, как это делается в веб-формах, это будет нечто похожее на это:
<div id="selectOptions">
<asp:FormView runat="server" ID="fvProductSelection" DefaultMode="Edit"
ItemType="Models.ProductChoice"
SelectMethod="GetProduct"
UpdateMethod="UpdateProduct" >
<EditItemTemplate>
<asp:linkbutton id="UpdateButton" text="Update" commandname="Update" runat="server"/>
<asp:HiddenField runat="server" ID="ProductId" Value="<%# BindItem.ProductId %>" />
<asp:TextBox Text ="<%# BindItem.Quantity %>" ID="Quantity" runat="server" />
<% for (int i = 0; i < BindItem.Extras.Count; i++)
{ %>
<asp:HiddenField Value="<%# BindItem.Extras[i].ExtraProductId %>" ID="ExtraProductId" runat="server" />
<asp:Label Text="<%# BindItem.Extras[i].ExtraName %>" ID="Name" runat="server" />
<asp:TextBox Text="<%# BindItem.Extras[i].ExtraQuantity %>" ID="Quantity" runat="server" />
<% } %>
</EditItemTemplate>
</asp:FormView>
</div>
Примечание. Я заменил ретранслятор циклом for, который выполняет итерацию через коллекцию с индексом, используемым для доступа к каждому Экстра. Это похоже на то, как мне нужно делать то, что вы хотите в ASP.NET MVC. Индекс отправляется вместе с остальной частью веб-формы при отправке формы, что позволяет связующему устройству модели восстанавливать упорядоченный список объектов.
Надеюсь, это какая-то помощь и прости меня за любые ошибки, так как у меня нет проекта веб-форм, чтобы проверить это на данный момент.
Ответ 2
Копаем в System.Web.ModelBinding показывает, что CollectionModelBinder ожидает, что значения, переданные в FormValueProvider, будут в том же формате, что и для MVC, то есть: MyCollection [i]
public static string CreateIndexModelName(string parentName, string index)
{
if (parentName.Length != 0)
{
return (parentName + "[" + index + "]");
}
return ("[" + index + "]");
}
К сожалению, ваши имена элементов ретранслятора не будут соответствовать этим критериям.
Несмотря на то, что вы, безусловно, неортодоксальный, вы все равно можете достичь этого, написав текстовые поля без сервера, а затем давая им имя, начинающееся с вашего контейнера именования данных, а затем индекс. И благодаря "Request.Unvalidated" (также представленному в 4.5) у вас есть возможность привязывать данные к данным, даже если они не представлены элементами управления на стороне сервера.
Ответ 3
[Serializable]
public class ProductChoice
{
public int Quantity { get; set; }
public int ProductId { get; set; }
public ProductChoiceExtra Extras { get; set; }
}
[Serializable]
public class ProductChoiceExtra
{
public int ExtraProductId { get; set; }
public string ExtraName { get; set; }
public int ExtraQuantity { get; set; }
public List<ProductChoiceExtra> listProducts{get;set;}
}
Ответ 4
Вы должны указать шаблон редактирования элемента во внутреннем каталоге данных, поскольку свойства в шаблоне элемента не могут быть возвращены в модели, которая автоматически создается и передается методу обновления.
У меня не было времени попробовать, но это должно сработать...
<div id="selectOptions">
<asp:FormView runat="server" ID="fvProductSelection" DefaultMode="Edit"
ItemType="Models.ProductChoice"
SelectMethod="GetProduct"
UpdateMethod="UpdateProduct" >
<EditItemTemplate>
<asp:linkbutton id="UpdateButton" text="Update" commandname="Update" runat="server"/>
<asp:HiddenField runat="server" ID="ProductId" Value="<%# BindItem.ProductId %>" />
<asp:TextBox Text ="<%# BindItem.Quantity %>" ID="Quantity" runat="server" />
<asp:DataList ID="Extras" DataSource="<%# DataBinder.Eval(Container.DataItem, "Extras") %>" runat="server">
<EditItemTemplate>
<asp:HiddenField Value="<%# BindItem.ExtraProductId %>" ID="ExtraProductId" runat="server" />
<asp:Label Text="<%# BindItem.ExtraName %>" ID="Name" runat="server" />
<asp:TextBox Text="<%# BindItem.ExtraQuantity %>" ID="TextBox1" runat="server" />
</EditItemTemplate>
<ItemTemplate>
<asp:HiddenField Value="<%# BindItem.ExtraProductId %>" ID="ExtraProductId" runat="server" />
<asp:Label Text="<%# BindItem.ExtraName %>" ID="Name" runat="server" />
<asp:TextBox Text="<%# BindItem.ExtraQuantity %>" ID="Quantity" runat="server" />
</ItemTemplate>
</asp:Repeater>
</EditItemTemplate>
</asp:FormView>
</div>
Ответ 5
Передайте значения дополнительных значений в строке запроса, добавьте значения параметров в метод обновления. Используйте атрибуты данных в HTML 5, чтобы сохранить эти 3 значения в элементе вида формы.
Пример
UpdateMethod="UpdateProduct/104/coke/2"
или
UpdateMethod="UpdateProduct/?ExtraProductId=104&ExtraName=coke&ExtraQuantity=2"
для первого подхода вам нужно написать правило маршрутизации в Route Config.
Внутри пользовательского элемента управления, как показано ниже
public void UpdateProduct(Models.ProductChoice model, int ExtraProductId, string ExtraName, int ExtraQuantity)
{
/* model.Extras is always null here, it should contain two ProductChoiceExtra objects */
if (TryUpdateModel(_productChoice) == true)
{
model.Extras.Add(new Models.ProductChoiceExtra()
{
ExtraProductId = ExtraProductId,
ExtraName = ExtraName,
ExtraQuantity = ExtraQuantity
});
}
}