Simon Martin
Simon Martin

Reputation: 4231

How can I avoid using a ViewBag in MVC4 to pass query values between controller and view

I've read that it's generally a bad practice to use ViewBags and also bad to mix ViewBag and ViewModels, but I'm using some ViewBags to pass sort order parameters between my controller and view and though I can create the properties on the ViewModel for the view I need the values before I can create the model in the first place, so can't see how to get around this.

Controller

public ActionResult Index(string sortOrder, string searchString, 
                          string currentFilter, int? page, 
                          bool? includeComplete)
{

 ViewBag.CurrentSort = sortOrder;
 ViewBag.NameSortParam = string.IsNullOrWhiteSpace(sortOrder) ? "Name desc" : "";
 ViewBag.DateSortParam = sortOrder == "Date" ? "Date desc" : "Date";

 if (Request.HttpMethod == "GET")
     searchString = currentFilter;
 else
     page = 1;

 ViewBag.CurrentFilter = searchString;
 bool showCompleted = (includeComplete == null || includeComplete == false)
                        ? false : true;
 ViewBag.IncludeCompleted = showCompleted;

 int pageNumber = (page ?? 1);

 var query = Session.QueryOver<ToDo>();

 if (!string.IsNullOrWhiteSpace(searchString))
     query = query.WhereRestrictionOn(td => td.TaskName)
                      .IsInsensitiveLike(string.Format("%{0}%", searchString));

 if (!showCompleted)
     query.And(td => td.IsComplete == false);

 switch (sortOrder)
 {
    case "Name desc":
      query = query.OrderBy(td => td.TaskName).Desc;
      break;
        case "Date":
      query = query.OrderBy(td => td.DueDate).Asc;
      break;
    case "Date desc":
      query = query.OrderBy(td => td.DueDate).Desc;
      break;
    default:
      query = query.OrderBy(td => td.TaskName).Asc;
      break;
 }

 var result = query.Fetch(p=>p.Priority).Eager
           .Fetch(s=>s.Staff).Eager
           .List();

 var viewModel = AutoMapper.Mapper.Map<IEnumerable<ToDo>, 
                     IEnumerable<IndexToDoViewModel>>(result)
                     .ToPagedList(pageNumber, PageSize);
 return View(viewModel);

}

Then in my view I'll use the ViewBag to pass values back to the controller, such as the sortOrder which I need to have before I can apply it in my NHibernate query that forms the bulk of the method, for example:

@Html.ActionLink("Name", "Index", new {sortOrder=ViewBag.NameSortParam, 
                                      includeComplete = ViewBag.IncludeCompleted})

My ViewModel

public class IndexToDoViewModel
{
    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:dd MMM yyyy}")]
    [DisplayName("Date Due")]
    public DateTime? DueDate { get; set; }

    public Guid Id { get; set; }

    [DisplayName("Is Complete")]
    public bool IsComplete { get; set; }

    [DataType(DataType.MultilineText)]
    public String Notes { get; set; }

    public string Priority { get; set; }

    public string Staff {get; set;}

    [DisplayName("Name")]
    public String TaskName { get; set; }

    // These would potentially replace my ViewBag?
    public string CurrentSort { get; set; }
    public string CurrentFilter { get; set; }
    public string NameSortParam { get; set; }
    public string DateSortParam { get; set; }
    public bool IncludeCompleted { get; set; }

}

Upvotes: 2

Views: 4329

Answers (2)

Guffa
Guffa

Reputation: 700202

Then in my view I'll use the ViewBag to pass values back to the controller

No, you are passing the values to the next controller. The values will (potentially) be used after a round trip to the browser, when a new instance of the controller will handle the request.

As you are not passing anything back to the current controller, there is no need to use the ViewBag. You can put the values in the model if you like.


Even if you were sending data from the view to the controller (which is very unusual), that would still be possible using the model. Example:

MyModel viewModel = new MyModel();
ActionResult result = View(viewModel);

// here you can access anything that the view would put in the model

return result;

Of course, you can't use any values from the view in the controller to create the model, as you have to create the model before the view. If you would need any data from the view to get data for it, you would put that as a method in the model instead of in the controller.

Upvotes: 3

Oliver
Oliver

Reputation: 9002

I would recommend creating a ViewModel for each of the views you create as it is most likely that further down the line you will need to pass more information to your view.

In this case, you would have a ViewModel that has a property for the paged list data and you can add another property for the SortOrder.

For example:

public class MyViewModel
{
    public IEnumerable<ToDo> TodoList { get; set; }
    public string SortOrder { get; set; }
}

See SO question: ViewModel Best Practices

Upvotes: 1

Related Questions