No selected items in the ListBoxFor should show validation error in asp.net mvc

2.8k views Asked by At

I show the Delete View in a partial/dialog. Initially no items are selected. When the user clicks the ok button the Delete Post action and its parameter selectedIds is NULL. Thats ok, but how can I show the user an error message?

When I reach the code "return PartialView" then I get an Exception in the View, that my Model.DisplayList ist empty.

I know I seem to mix 2 problems here but I guess one could solve the other...

Delete Actions:

[HttpGet]
        public ActionResult Delete()
        {
            var templates = _templateDataProvider.GetTemplates();
            var listViewModel = new ListViewModel<Template>();
            listViewModel.DisplayList = templates;
            return PartialView(listViewModel);
        }

        [HttpPost]
        public ActionResult Delete(int[] selectedIds)
        {
            if (selectedIds == null)
            {
                ModelState.AddModelError("Name", "Nothing selected");
            }

            if (ModelState.IsValid)
            {
                return Json(new { success = true });
            }

            return PartialView();
        }

Delete View:

@model ITMS.ViewModels.ListViewModel<ITMS.Models.Template>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
@using (Html.BeginForm("Delete", "Template"))
{ 
    @Html.ValidationSummary(false)      
    @Html.ListBoxFor(x => x.SelectedIds, new SelectList(Model.DisplayList, "Id", "Name"),new { @class = "select"} )
}

When I return this:

return PartialView(new ListViewModel<Template> { DisplayList = _templateDataProvider.GetTemplates() });

then it works, but I do not want to access the database again for this.

1

There are 1 answers

9
Darin Dimitrov On BEST ANSWER

In your Delete POST action when there is an exception and you redisplay the partial view you are not passing any model to this view. And yet your view needs a DisplayList property in order to load the list box. So make sure you are passing a model tha same way you did in your GET action:

var templates = _templateDataProvider.GetTemplates();
var listViewModel = new ListViewModel<Template>();
listViewModel.DisplayList = templates;
return PartialView(listViewModel);

Another possibility is to directly have the Delete action take the view model:

[HttpPost]
public ActionResult Delete(ListViewModel<Template> model)
{
    if (ModelState.IsValid)
    {
        return Json(new { success = true });
    }

    // Make sure you rebind the DisplayList property
    model.DisplayList = _templateDataProvider.GetTemplates();
    return PartialView(model);
}

Now as far as the validation is concerned you could decorate your SelectedIds property on the view model with for example the Required attribute:

[Required]
public IEnumerable<Template> SelectedIds { get; set; }

UPDATE:

A performance concern has been expressed in the comments section about accessing the database to load the list again in the POST action. To avoid this performance hit the values could be cached and reused.

Like this:

public IEnumerable<Template> GetTemplates()
{
    var templates = HttpContext.Cache["templates"] as IEnumerable<Template>;
    if (templates == null)
    {
        templates = _templateDataProvider.GetTemplates();
        HttpContext.Cache["templates"] = templates;
    }
    return templates;
}

and then:

public ActionResult Delete()
{
    var listViewModel = new ListViewModel<Template>();
    listViewModel.DisplayList = GetTemplates();
    return PartialView(listViewModel);
}

[HttpPost]
public ActionResult Delete(ListViewModel<Template> model)
{
    if (ModelState.IsValid)
    {
        return Json(new { success = true });
    }

    model.DisplayList = GetTemplates();
    return PartialView(model);
}