views:

3993

answers:

4

I can't seem to find a good blog post that shows how to bind a model to a view without the magic strings "ViewData" (using a strongly-typed view is the approach I'm trying to take)

Does anyone know what I need to alter in the below to bind this directly to my model?

View

<%@ Page Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage(Of IEnumerable (Of MvcApplication1.Category))" %>
<asp:Content ID="aboutContent" ContentPlaceHolderID="MainContent" runat="server">
    <%=Html.DropDownList("CategoryList")%>
</asp:Content>

Controller

Function Index() As ActionResult
    Dim list As New List(Of Category)
    list.Add(New Category With {.CategoryID = 1, .CategoryName = "Test1"})
    list.Add(New Category With {.CategoryID = 2, .CategoryName = "Test2"})

    Return View()
End Function

EDIT

The final solution in VB is shown below, thanks for the big response!

Controller

Function Index() As ActionResult
    Dim id As Integer = 1
    Dim ProductObject As Product = mProductService.GetProductById(id)

    Return View(ProductObject)
End Function

View

<%=Html.DropDownList("Category", New SelectList(Model.Categories, "CategoryID", "CategoryName"))%>

Product Class (w/ a IEnumeralbe property for the categories)

Public Class Product
    Public Sub New()

    End Sub


    Private mProductID As Integer
    Public Property ProductID() As Integer
        Get
            Return mProductID
        End Get
        Set(ByVal value As Integer)
            mProductID = value
        End Set
    End Property

    ReadOnly Property Categories() As IEnumerable(Of Category)
        Get
            Dim list As New List(Of Category)
            list.Add(New Category With {.CategoryID = 1, .CategoryName = "Test1"})
            list.Add(New Category With {.CategoryID = 2, .CategoryName = "Test2"})

            Return list
        End Get
    End Property

End Class
A: 

You could create your own helper method overload for Html.DropDownList that would directly map the model to the DropDownList helper.

That being said, you should really ask your self how much domain specific logic your view should contain. ViewData helps to separate your model and domain logic from your view.

Example:

I know this is not an "overload" because felt that since this will still need a name, you would want another method that just takes a string for the name of the drop down but automatically binds the model to the control (assuming it is an IEnumerable). This is an example of the HtmlHelper extension method.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;

namespace MvcApplication3.Helpers
{
    public static class ViewHelpers
    {
        public static string ModelDropDownList(this HtmlHelper htmlHelper, string name)
        {
            var model = htmlHelper.ViewData.Model as IEnumerable<SelectListItem>;
            if(model == null)
                return string.Empty;

            return htmlHelper.DropDownList(name, model);
        }
    }
}

Here is how you would call this in your view

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<SelectListItem>>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <%= Html.ModelDropDownList("Test") %>
</asp:Content>
Adam Carr
Fair enough, can I then mix ViewData("list") with a model because I do have times where I have a strongly typed view to populate a table /etc but I need to populate a dropdownlist. Would I be able to do both in the same view?
Toran Billups
Yes because this would simply be an overload to simply your API but the standard method signature would still exist.
Adam Carr
Excellent! I might take this approach because I'm yet to write a custom helper method overload (although this would be something worth doing as I'm thinking less and less about webforms these days)
Toran Billups
Just to clarify my point on ViewData and your comments about "separation of my domain from the view" - if I really need this I will create a view model instead. This helps to eliminate the wasted LOC to do the left /right ViewData("Name") = object.Name IMHO
Toran Billups
how do I get my view to be able to find the custom helper?
shogun
+4  A: 

The dropdown list helper takes an IEnumerable<SelectListItem>, not an IEnumerable<Category>. Typically what you do is have your page have a particular model. The model includes a property for the selected value from the dropdown, not the collection. You supply the collection to select from via ViewData. You could have a view-only model that includes both the properties and the collection(s) to select from, but that might mean a proliferation of classes. There's some debate as to whether proliferating classes or magic strings are worse design.

My take on your code would be something like:

<%@ Page Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage(Of Foo)" %>
<asp:Content ID="aboutContent" ContentPlaceHolderID="MainContent" runat="server">
    <%=Html.DropDownList("Category", ViewData("CategoryList"))%>
</asp:Content>

Function Index() As ActionResult
    Dim list As New List(Of SelectListItem)
    list.Add(New SelectListItem  With {.Value = 1, .Text = "Test1"})
    list.Add(New SelectListItem With {.Value = 2, .Text = "Test2"})
    ViewData("CategoryList") = list
    Return View()
End Function

where Foo is a class that contains a property Category of type int.

If you wanted to do a strongly-typed view, then have Foo have a property Categories of type SelectListItem, then change:

<%=Html.DropDownList("Category", ViewData("CategoryList"))%>

to

<%=Html.DropDownList("Category", Foo.Categories )%>

and

    ViewData("CategoryList") = list
    Return View()

to

    Dim foo as New Foo
    foo.Categories = list
    Return View(foo)
tvanfosson
I agree. This is actually how I would do it but gave my answer as a direct example of what I felt like he was asking to do. Good comment and example text.
Adam Carr
A: 

If you want to go the Strongly Typed method, there is a SelectList object to which you can pass an IEnumerable. You have to specify the Text Field and Value Field properties of T but this SelectList object can be allocated directly to the Html.DropDownList helper:

c#
IList<Category> categories = new IList<Category>()
    {
        new Category() { ID = 1, Name = "Category 1" },
        new Category { ID = 2, Name = "Category 2" }
    };

SelectList categoryDropDown = new SelectList(categories, "ID", "Name");

ViewData["categoryDropDown"] = categoryDropDown;

then do:

c#
<%=Html.DropDownList("CategoryID", ViewData("categoryDropDown"))%>

That way you can pass in an IList of categories from anywhere (Service layer, controller wide method).

I Clark