Привязка к коллекции CheckboxFor

Я пытаюсь реализовать экран разрешений, в котором пользователю может быть дано определенное разрешение на конкретном экране. Для этого я создаю коллекцию Checkboxfor, привязанную к коллекции свойств bool. Но когда я отправляю форму, я получаю либо все свойства bool true, либо все false, в зависимости от того, инициализировал ли я эти свойства как true или false в конструкторе viewmodel.

Вот код для ViewModel:

Подход I:

public class MyViewModel
{
    public MyModel Model { get; set; }        

    public IEnumerable<ScreenType> Screens { get; set; }
    public IEnumerable<SecurityType> SecurityTypes { get; set; }
    public List<PermissionType> Permissions { get; set; }        

    public MyViewModel()
    {
        LoadScreens();
        LoadSecurityTypes();
        LoadPermissions();
    }

    public void LoadPermissions()
    {
        Permissions = new List<PermissionType>();

        foreach (var screen in Screens)
        {
            foreach (var securityType in SecurityTypes)
            {
                Permissions.Add(
                    new PermissionType
                    {
                        PermissionId= Guid.NewGuid(),
                        ScreenId= screen.Id,
                        SecurityId = securityType.Id,
                        IsAllowed = false
                    });
            }
        }
    }    
}

Подход II

public class MyViewModel
{
    public MyModel Model { get; set; }        

    public IEnumerable<ScreenType> Screens { get; set; }
    public IEnumerable<SecurityType> SecurityTypes { get; set; }
    public List<bool> AllowedList { get; set; }        

    public MyViewModel()
    {
        LoadScreens();
        LoadSecurityTypes();
        LoadPermissions();
    }

    public void LoadPermissions()
    {
        AllowedList = new List<bool>();

        foreach (var form in Forms)
        {
            foreach (var security in Securities)
            {
                AllowedList.Add(false);
            }
        }
    }    
}

Вот код моего представления:

Подход I:

    @using (Ajax.BeginForm("Create", "Role", null, new AjaxOptions { UpdateTargetId = "addStatus", InsertionMode = InsertionMode.Replace, OnSuccess = "onFormPostSuccess" }, new { @id = "AddForm" }))
    {  
        <div>
            <span><label>Screen</label></span>
            @foreach (var security in Model.SecurityTypes)
            { 
                <span><label>@security.Name</label></span>                    
            }
            <br />
            @foreach (var screen in Model.Screens)
            {
                <span>@screen.Name</span>
                foreach (var security in Model.SecurityTypes)
                { 
                    <span>@Html.CheckBoxFor(m => m.Permissions.Where(s => s.SecurityId == security.Id && s.ScreenId == screen.Id).Single().IsAllowed, new { @id = HtmlHelper.GenerateIdFromName("Create" + screen.Name + security.Name) })</span>                   
                }
                <br />
            }
        </div>
        <div>
            <span>
                <input type="image" src="/content/images/submit_button.png" value="submit" />
            </span> 
        </div>
        <div>
            <span id="addStatus" class="submitStatus"></span>
        </div>
    }

Подход II:

 @using (Ajax.BeginForm("Create", "Role", null, new AjaxOptions { UpdateTargetId = "addStatus", InsertionMode = InsertionMode.Replace, OnSuccess = "onFormPostSuccess" }, new { @id = "AddForm" }))
    {  
        <div>
            <span><label>Screen</label></span>
            @foreach (var security in Model.SecurityTypes)
            { 
                <span><label>@security.Name</label></span>                    
            }
            <br />
            @foreach (int i=0; i < Model.Screens.Count(); i++)
            {
                <span>@Model.Screens.ElementAt(i).Name</span>
                foreach (int j=0; j<Model.SecurityTypes.Count(); j++)
                { 
                    @* The index 5*i+j is because I have 5 security types *@ 
                    <span>@Html.CheckBoxFor(Model.AllowedList[5*i+j], new { @id = HtmlHelper.GenerateIdFromName("Create" + @Model.Screens.ElementAt(i).Name + @Model.SecurityTypes.ElementAt(j).Name) })</span>                   
                }
                <br />
            }
        </div>
        <div>
            <span>
                <input type="image" src="/content/images/submit_button.png" value="submit" />
            </span> 
        </div>
        <div>
            <span id="addStatus" class="submitStatus"></span>
        </div>
    }

Вот код для метода действия Create в Controller:

    [Authorize]
    [HttpPost]
    public JsonResult Create(MyViewModel viewModel)
    {   
        if ( ModelState.IsValid)
        {                

            if (service.AddRole(viewModel))
            {                    
                return Json("Role Added !");
            }
            return Json("Role exists !");
        }
        return Json("Please correct errors");
    }

Когда я проверяю viewModel в методе Create, все свойства IsAllowed имеют значение false. Как были инициализированы в конструкторе модели представления. Нет эффекта от установки / снятия флажков в представлении.

Я что-то пропустил?

1 ответ

Решение

HTML-помощники со строгой типизацией, такие как CheckBox, работают только с простыми выражениями в качестве первого аргумента (доступ к индексу свойств и массивов не более). Следующее выражение совершенно не соответствует возможностям этого помощника:

m => m.Permissions.Where(s => s.SecurityId == security.Id && s.ScreenId == screen.Id).Single().IsAllowed

Я бы порекомендовал вам использовать реальную модель представления и выполнять это на уровне вашего контроллера при отображении между вашей моделью домена и моделью представления.


ОБНОВИТЬ:

Очевидно, мой ответ о моделях представления был недостаточно ясен, поэтому позвольте мне показать, что я имею в виду под моделями представления.

Итак, начнем с определения моделей нашего представления, которые потребуются для реализации необходимой логики для этого представления:

public class CreateViewModel
{
    public IEnumerable<ScreenViewModel> Screens { get; set; }
}

public class ShowCreateViewModel: CreateViewModel
{
    public IEnumerable<SecurityTypeViewModel> SecurityTypes { get; set; }
}

public class ScreenViewModel
{
    public string ScreenName { get; set; }
    [HiddenInput(DisplayValue = false)]
    public int ScreenId { get; set; }
    public IEnumerable<SecurityTypeViewModel> SecurityTypes { get; set; }
}

public class SecurityTypeViewModel
{
    public string SecurityName { get; set; }
    [HiddenInput(DisplayValue = false)]
    public int SecurityTypeId { get; set; }
    public bool IsAllowed { get; set; }
}

Тогда у нас может быть действие контроллера, которое позаботится о получении моделей доменов из репозитория или чего-то подобного и сопоставлении с моделями представлений:

public class HomeController : Controller
{
    public ActionResult Create()
    {
        // The information will obviously come from a domain model that 
        // we map to a view model, but for the simplicity of the answer
        // I am hardcoding the values here
        var securityTypes = new[]
        {
            new SecurityTypeViewModel { SecurityTypeId = 1, SecurityName = "security 1" },
            new SecurityTypeViewModel { SecurityTypeId = 2, SecurityName = "security 2" },
            new SecurityTypeViewModel { SecurityTypeId = 3, SecurityName = "security 3" },
        };

        // The information will obviously come from a domain model that 
        // we map to a view model, but for the simplicity of the answer
        // I am hardcoding the values here
        return View(new ShowCreateViewModel
        {
            SecurityTypes = securityTypes,
            Screens = new[]
            {
                new ScreenViewModel 
                {
                    ScreenId = 1,
                    ScreenName = "Screen 1",
                    SecurityTypes = securityTypes
                },
                new ScreenViewModel 
                {
                    ScreenId = 2,
                    ScreenName = "Screen 2",
                    SecurityTypes = securityTypes
                },
            }
        });
    }

    [HttpPost]
    public ActionResult Create(CreateViewModel model)
    {
        // The view model passed here will contain all the necessary information
        // for us to be able to perform the actual Save: 
        // a list of the screen ids along with a list of the selected permission ids

        return Content(
            "Thank you for selecting the following allowed permissions:<br/>" + 
            string.Join("<br/>", model.Screens.Select(s => string.Format(
                "screen id: {0}, permission ids: {1}", 
                s.ScreenId, 
                string.Join(",", s.SecurityTypes.Where(st => st.IsAllowed).Select(st => st.SecurityTypeId))
            )))
        );
    }
}

и теперь осталось определить представление и соответствующие шаблоны редактора / дисплея.

Начнем с основного вида (~/Views/Home/Create.cshtml):

@model ShowCreateViewModel

<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.js")" type="text/javascript"></script>

@using (Ajax.BeginForm("Create", "Home", null, new AjaxOptions { UpdateTargetId = "addStatus", InsertionMode = InsertionMode.Replace, OnSuccess = "onFormPostSuccess" }, new { @id = "AddForm" }))
{  
    <table>
        <thead>
            <tr>
                <th>Screen/Security type</th>
                @Html.DisplayFor(x => x.SecurityTypes)
            </tr>
        </thead>
        <tbody>
            @Html.EditorFor(x => x.Screens)
        </tbody>
    </table>    

    <div>
        <input type="submit" value="submit" />
    </div>

    <div id="addStatus" class="submitStatus"></div>
}

Далее у нас есть шаблон редактора для ScreenViewModel модель (~/Views/Shared/EditorTemplates/ScreenViewModel.cshtml):

@model ScreenViewModel
<tr>
    <td>
        @Html.DisplayFor(x => x.ScreenName)
        @Html.EditorFor(x => x.ScreenId)
    </td>
    @Html.EditorFor(x => x.SecurityTypes)
</tr>

Затем шаблон редактора для SecurityTypeViewModel модель (~/Views/Shared/EditorTemplates/SecurityTypeViewModel.cshtml):

@model SecurityTypeViewModel
<td>
    @Html.CheckBoxFor(x => x.IsAllowed)
    @Html.EditorFor(x => x.SecurityTypeId)
</td>

И, наконец, шаблон отображения для SecurityTypeViewModel модель (~/Views/Shared/DisplayTemplates/SecurityTypeViewModel.cshtml):

@model SecurityTypeViewModel
<th>
    @Html.DisplayFor(x => x.SecurityName)
</th>

И это в значительной степени это:

Я оставил для вас сопоставление между вашими фактическими моделями доменов и моделями представлений, определенными здесь.

Другие вопросы по тегам