Рендеринг List<Model> в форме и отправка на контроллер

Я рендеринг рекурсивной древовидной структуры с использованием ViewComponent, и я борюсь с использованием Html.HiddenFor а также Html.CheckBoxFor в виде.

Это ViewModel:

public class ViewModelProductCategory
{
    public int Id { get; set; }
    public int? ParentId { get; set; }
    public string Title { get; set; }
    public int SortOrder { get; set; }
    public bool Checked { get; set; }
    public ViewModelProductCategory ParentCategory { get; set; }
    public IEnumerable<ViewModelProductCategory> Children { get; set; }
    public IEnumerable<ViewModelProduct> Products { get; set; }
}

ViewComponent вызывается из главной View-страницы следующим образом:

@await Component.InvokeAsync("SelectCategories",
new
{
    parentId = 0,
    productId = Model.Id // This is the current product Id from the main View
})

... и это метод Invoke ViewComponent:

public async Task<IViewComponentResult> InvokeAsync(int? parentId, int productId)
{
    List<ViewModelProductCategory> VM = new List<ViewModelProductCategory>();
    if (parentId == 0)
    {
        VM = _mapper.Map<List<ViewModelProductCategory>>
            (await _context.ProductCategories.Include(c => c.Children)
            .Where(x => x.ParentId == null).OrderBy(o => o.SortOrder).ToListAsync());
    }
    else
    {
        VM = _mapper.Map<List<ViewModelProductCategory>>
            (await _context.ProductCategories.Include(c => c.Children)
            .Where(x => x.ParentId == parentId).OrderBy(o => o.SortOrder).ToListAsync());
    }
    foreach (var item in VM)
    {
        // The Checked-value is not stored in the database, but is set here based
        // on the occurances of ProductId in the navigation property Products.Id
        // I'm not entirely confident that this statement checks the correct checkboxes...
        item.Checked = item.Products.Any(c => c.Id == productId);
    }
    ViewData["productId"] = productId;
    return View(VM);
}

Это Default.cshtml ViewComponent:

@model List<MyStore.Models.ViewModels.ViewModelProductCategory>
<ul style="list-style:none;padding-left:0px;">
    @if (Model != null)
    {
        int ProductId = (ViewData["productId"] != null)
            ? int.Parse(ViewData["productId"].ToString())
            : 0;
        @for (int i = 0; i < Model.Count(); i++)
        {
            <li style="margin-top:4px;padding:0px;">
                @if (Model[i].Children.Count() == 0)// Prevent products from being placed in a category with child categories
                                                    // by not rendering a checkbox next to it
                {
                    @Html.HiddenFor(model => Model[i].Id)
                    @Html.CheckBoxFor(model => Model[i].Checked)
                }
                @Html.LabelFor(model => Model[i].Id, Model[i].Title)
                <ul>
                    @*Let's recurse!*@
                    @await Component.InvokeAsync("SelectCategories",
                    new
                    {
                        parentId = Model[i].Id,
                        productId = ProductId
                    })
                </ul>
            </li>
        }
    }
</ul>

Который выводит этот HTML (это часть):

<input id="z0__Id" name="[0].Id" type="hidden" value="1003" />
<input checked="checked" id="z0__Checked" name="[0].Checked" type="checkbox" value="true" />
<label for="z0__Id">Up to 20&quot;</label>

<input data-val="true" data-val-required="The Id field is required." id="z1__Id" name="[1].Id" type="hidden" value="1004" />
<input checked="checked" data-val="true" data-val-required="The Checked field is required." id="z1__Checked" name="[1].Checked" type="checkbox" value="true" />
<label for="z1__Id">21&quot; - 40&quot;</label>

<input data-val="true" data-val-required="The Id field is required." id="z2__Id" name="[2].Id" type="hidden" value="1005" />
<input checked="checked" data-val="true" data-val-required="The Checked field is required." id="z2__Checked" name="[2].Checked" type="checkbox" value="true" />
<label for="z2__Id">41&quot; - 55&quot;</label>

<!-- ...and so on ... -->

Я столкнулся с (по крайней мере) двумя проблемами здесь:

  1. Заявление item.Products.Any(c => c.Id == productId); следует оценить true если значение локальной переменной productId находится в свойстве навигации ProductsЭто означает, что этот конкретный продукт связан с этой конкретной категорией (через item.Products), но теперь он проверяет все родственные категории в родительской категории.

  2. Я думаю, что HTML-вывод не является правильным, по крайней мере, для name-имущество: <input checked="checked" id="z0__Checked" name="[0].Checked" type="checkbox" value="true" />, Может ли это работать, будучи названным [0].Checked?

Я не написал контроллер POST-edit метод еще, поэтому я не включаю его в этот вопрос. В нем я должен сделать некоторые преобразования из одной модели в другую, и некоторые другие вещи. Если я могу просто заставить форму правильно отображаться, я могу привязать ее к контроллеру.

1 ответ

Решение

Да, я ожидаю, что отображаемые имена html, вероятно, недопустимы для привязки модели. Вы должны добавить родительскую модель, чтобы содержать список этого класса:

public class ViewModelX
{
    public List<ViewModelProductCategory> Categories = new List<ViewModelProductCategory>();
}

Затем используйте его в вашем представлении и контроллере:

@model ViewModelX

@Html.HiddenFor(model => model.Categories[i].Id)
@Html.CheckBoxFor(model => model.Categories[i].Checked)
// etc

_mapper.Map<ViewModelX> ...

Это будет отображаться в HTML:

name="Categories[0].Id"
Другие вопросы по тегам