Issue Using Grid with ViewModel

6 posts, 0 answers
  1. Stephen
    Stephen avatar
    158 posts
    Member since:
    Jan 2009

    Posted 20 Mar 2013 Link to this post

    I am still new to MVC and still trying to get a grasp on the best way to do things.  I realize at some point I'll probably need to switch to using Repositories or something but for now I have my EF Models and I am trying to use ViewModels in my Controllers.

    I am trying to display a Grid and perform basic CRUD operations.  I am using an EditorTemplate for the popup mode.  Basically the Grid is supposed to  display a set of items (Beamlines).  One of the fields within a Beamline is an AllocationPanelID.  The Beamline stores the AllocationPanelID but the full list of AllocationPanels is in a separate model.  So my understanding of ViewModels is that I can combine data from multiple models into one View Model.  This is where I am having trouble.  I define that AllocationPanel IEnumerable<SelectListItem> in the View Model and connect it in the Controller but when it gets to it in the
    View it has NULL as if it is never really getting hooked up to the data.

    View Model: (shortened for breviety)
    public class BeamlineViewModel
    {
        public decimal ID { get; set; }
        public string Description { get; set; }
        public Nullable<decimal> SortOrder { get; set; }
        public string InsertionDevice { get; set; }
        public Nullable<decimal> AllocationPanelID { get; set; }
        public string Status { get; set; }
     
        public IEnumerable<SelectListItem> AllocationPanels { get; set; }
     
        public IEnumerable<SelectListItem> StatusTypes = new List<SelectListItem>
        {
            new SelectListItem {Value = "A", Text = "Available"},
            new SelectListItem {Value = "C", Text = "Construction and Commissioning"},
            new SelectListItem {Value = "D", Text = "Diagnostic and Instrumentation"},
            new SelectListItem {Value = "O", Text = "Operational"},
            new SelectListItem {Value = "U", Text = "Unused Port"}
        };
    }

    Controller: (only showing Index, Edit, and related methods)
    public ActionResult Index()
    {
        return View(GetBeamlines());
    }
     
    public ActionResult Edit(int id)
    {
        using (MyEntities context = new MyEntities())
        {
            return View(context.Beamlines.Find(id));
        }
    }
     
    private static IEnumerable<BeamlineViewModel> GetBeamlines()
    {
        var context = new MyEntities();
        return context.Beamlines.Select(b => new BeamlineViewModel
        {
            ID = b.ID,
            Description = b.Description,
            SortOrder = b.Sort_Order,
            InsertionDevice = b.Insertion_Device,
            AllocationPanelID = b.Allocation_Panel_ID,
            Status = b.Status
        });
    }
     
    public ActionResult GetAllocationPanels()
    {
        using (MyEntities context = new MyEntities())
        {
            var allocationPanels = context.Allocation_Panels.ToList();
            var model = new BeamlineViewModel
            {
                AllocationPanels = allocationPanels.Select(m => new SelectListItem { Value = m.ID.ToString(), Text = m.Description })
            };
            return View(model);
        }
    }

    View:
    @model IEnumerable<MyProject.ViewModels.BeamlineViewModel>
     
    @{
        ViewBag.Title = "Beamlines";
    }
     
    <h2>Beamlines</h2>
     
    @(Html.Kendo().Grid(Model)
        .Name("gvBeamlines"
        .Columns(columns =>
        {
            columns.Command(command => { command.Edit(); }).Width(50);
            columns.Bound(o => o.Description).Width(100);
            columns.Bound(o => o.InsertionDevice).Title("Insertion Device");
            columns.Bound(o => o.Status);
            columns.Bound(o => o.EnergyRange).Title("Energy Range");
            columns.Command(command => { command.Destroy(); }).Width(50);
        })
        .ToolBar(toolbar => toolbar.Create())
        .Editable(editable => editable.Mode(GridEditMode.PopUp).TemplateName("Beamline").Window(window => window.HtmlAttributes(new { @style = "width:700px;" })))
        .Pageable()
        .Sortable()
        .DataSource(dataSource => dataSource
            .Server()
            .Model(model => model.Id(o => o.ID))
            .Create(create => create.Action("Create", "Beamlines"))
            .Read(read => read.Action("Index", "Beamlines"))
            .Update(update => update.Action("Edit", "Beamlines"))
            .Destroy(destroy => destroy.Action("Delete", "Beamlines"))
        )
    )

    Editor Template: (shortened for breviety)
    @model MyProject.ViewModels.BeamlineViewModel
     
    @Html.HiddenFor(model => model.ID)
     
    <div class="editor-label">
        @Html.Label("Beamline")
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.Description)
        @Html.ValidationMessageFor(model => model.Description)
    </div>
     
    <div class="editor-label">
        @Html.Label("Status")
    </div>
    <div class="editor-field">
        @Html.DropDownListFor(model => model.Status, new SelectList(Model.StatusTypes, "Value", "Text"), "(Select One)")
        @Html.ValidationMessageFor(model => model.Status)
    </div>
     
    <div class="editor-label">
        @Html.Label("Sort Order")
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.SortOrder)
        @Html.ValidationMessageFor(model => model.SortOrder)
    </div>
     
    <div class="editor-label">
        @Html.Label("Insertion Device Beamline")
    </div>
    <div class="editor-field">
        @Html.RadioButtonFor(model => model.InsertionDevice, "Y")
        @Html.Label("Yes")
        @Html.RadioButtonFor(model => model.InsertionDevice, "N")
        @Html.Label("No")
        @Html.ValidationMessageFor(model => model.InsertionDevice)
    </div>
     
    <div class="editor-label">
        @Html.Label("Allocation Panel")
    </div>
    <div class="editor-field">
        @Html.DropDownListFor(model => model.AllocationPanelID, new SelectList(Model.AllocationPanels, "Value", "Text"), "(Select One)")
        @Html.ValidationMessageFor(model => model.AllocationPanelID)
    </div>

    A lot of this code I got from different places and have manipulated to get it working.  The GetAllocationPanels() method seems to hook the AllocationPanels IEnumerable that is defined in the View Model but I never actually call the GetAllocationPanels() method or see where to call it.

    As the code currently exists, the Grid loads and when I try to edit a Beamline I get an error on the second to last line of the Editor Template, the DropDownListFor for the AllocationPanels.  It gives me a ArgumentNullException.  Value cannot be null. Parameter name: items.

    I have also read that it might be better to have a View Model for the Beamline which would just contain the fields making up a Beamline, and then a second View Model for the Beamline listing which would have an instantiation for the first View Model and then also the other items like the AllocationPanels.  I have tried that but could never seem to figure out how to type the View and Editor Template and also what should go in the Controller methods.

    Any help is greatly appreciated.

    The trouble is.
  2. Daniel
    Admin
    Daniel avatar
    2220 posts

    Posted 22 Mar 2013 Link to this post

    Hello Stephen,

    In order to pass the data to the editor template you could either:

    1. Populate the data to the ViewBag/ViewData in the Index action method:
      public ActionResult Index()
      {
          using (MyEntities context = new MyEntities())
          {
              var allocationPanels = context.Allocation_Panels.ToList();
              ViewData["panels"] = allocationPanels.Select(m => new SelectListItem { Value = m.ID.ToString(), Text = m.Description });
          }
          return View(GetBeamlines());
      }
      @Html.DropDownListFor(model => model.AllocationPanelID, IEnumerable<SelectListItem>)ViewData["panels"], "(Select One)")
    2. Include the data in the model like you are doing for the "StatusTypes". 

    With the first approach you will retrieve the data only once and only one copy will be kept. The only advantage of the second approach is that the collection will be strongly typed.

    Regards,
    Daniel
    the Telerik team
    Join us on our journey to create the world's most complete HTML 5 UI Framework - download Kendo UI now!
  3. Stephen
    Stephen avatar
    158 posts
    Member since:
    Jan 2009

    Posted 22 Mar 2013 Link to this post

    Thank you for the response!

    I would prefer the second method where it is in the Model but the other one I am doing that with that you referenced (StatusTypes) is a "hardcoded" drop down list that I am defining in the View Model.  How would I define the AllocationPanels in the View Model since I need to get its info from the Database/EF Entity?

    Also, I tried the first method you suggested just to try it out and I got an error in the DropDownListFor...it didn't like the IEnumerable type definition...

    Just an aside with that first method, setting the ViewData in the Index method will work even though that method is only returning data from the method it is calling?  I guess ViewData is like a session?  As you can see I am new to this and still trying to figure it all out.  If I were to instantiate the AllocationPanels variable that I declared in the ViewModel in the code block within the Index method in the Controller does that do anything if I don't pass it back to the View?
  4. Daniel
    Admin
    Daniel avatar
    2220 posts

    Posted 26 Mar 2013 Link to this post

    Hello again Stephen,

    You could add the collection to the model in the GetBeamlines method e.g.
    private static IEnumerable<BeamlineViewModel> GetBeamlines()
    {
        var context = new MyEntities();
        var allocationPanels = context.Allocation_Panels.ToList().Select(m => new SelectListItem { Value = m.ID.ToString(), Text = m.Description });
         
        return context.Beamlines.Select(b => new BeamlineViewModel
        {
            ID = b.ID,
            Description = b.Description,
            SortOrder = b.Sort_Order,
            InsertionDevice = b.Insertion_Device,
            AllocationPanelID = b.Allocation_Panel_ID,
            Status = b.Status,
            AllocationPanels = allocationPanels
        });
    }

    I am not sure what is causing the problem when using the ViewData. Could you share the code that you tried? If it is the same as in my previous reply, could you provide more information about the error?

    Regarding your question about the ViewData - it is not the same as the Session and it is only available for the current request. The reason why it will work for this scenario is that the Grid will call the Index action in order to render its data again.

    Regards,
    Daniel
    the Telerik team
    Join us on our journey to create the world's most complete HTML 5 UI Framework - download Kendo UI now!
  5. Stephen
    Stephen avatar
    158 posts
    Member since:
    Jan 2009

    Posted 03 Apr 2013 Link to this post

    Hi again Daniel,

    Thanks for all of your help so far.  I am trying to stick with the model solution instead of the ViewData but I am now getting a new error.  I used the code from your last response to set the Allocation Panels in the GetBeamlines method but when I try to edit a row of the grid it throws the following error:

    Unable to create a constant value of type 'System.Web.Mvc.SelectListItem'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.

    Does this have to do with the declaration of AllocationPanels in my View Model?
  6. Daniel
    Admin
    Daniel avatar
    2220 posts

    Posted 05 Apr 2013 Link to this post

    Hi Stephen,

    I am sorry for misleading you. The query is performed in the database and Entity Framework cannot  translate the expression. In order to pass strongly typed data in this case, I can suggest to load the "Allocation_Panels" with the "Beamline" model and then convert the data to a SelectList in the editor template. I attached a sample project which uses similar approach.
    An alternative is to call the ToList method before Select, but this can lead to performance issue with large data sets.

    Kind regards,
    Daniel
    the Telerik team
    Join us on our journey to create the world's most complete HTML 5 UI Framework - download Kendo UI now!
Back to Top