1

I have a class called Entity

public class Entity
{
    public string Name { get; set; }

    public Location Place { get; set; }
}

and one class called Location

public class Location
{
    public string Country { get; set; }

    public string State { get; set; }

    public string City { get; set; }
}

One Entity contains a Location, so I want to generate 3 dropdowns for Location.

  • Country
  • State
  • City

I could do it manually like

@Html.DropDownListFor(o => o.Country, new [] { new SelectListItem() { Text = "United States", Value="US" } })
<br />
@Html.DropDownListFor(o => o.State, new [] { new SelectListItem() { Text = "Some State", Value="SS" } })
<br />
@Html.DropDownListFor(o => o.City, new[] { new SelectListItem() { Text = "Some city", Value = "City" } })

But I have several places on my website that will need the exact same 3 dropdowns, like Restaurant, Hotel and other classes that also have a Location. I've tried to make a partial view that starts a new form, but I get an exception:

The model item passed into the dictionary is of type 'TestMVC3Razor.Controllers.Entity', but this dictionary requires a model item of type 'TestMVC3Razor.Controllers.Location', with this code:

@model TestMVC3Razor.Controllers.Entity
@using (Html.BeginForm())
{
    @Html.Partial("LocationSelector", Model.Place)
    <br />
    <input type="submit" value="Submit" />
}

And the partial view is

@model TestMVC3Razor.Controllers.Location
@using (Html.BeginForm())
{
    @Html.DropDownListFor(o => o.Country, new [] { new SelectListItem() { Text = "United States", Value="US" } })
    <br />
    @Html.DropDownListFor(o => o.State, new [] { new SelectListItem() { Text = "Some State", Value="SS" } })
    <br />
    @Html.DropDownListFor(o => o.City, new[] { new SelectListItem() { Text = "Some city", Value = "City" } })
}

This obviously shouldn't work, but I want to do something like it, a helper like this would be perfect

@Html.LocationSelectFor(o => o.Location)

But how do I do this? I need to generate 3 dropdowns and when I post to an action I need to get the object with bidden values.

public ActionResult(Location loc)
{
    var x = String.Format("{0}, {1} - {2}", loc.City, loc.Country, loc.State);
}

How can I make this helper to create 3 dropdowns and bind values when I post?

BrunoLM
  • 97,872
  • 84
  • 296
  • 452
  • I don't have time to go in to *how* to do this right now, but it may be worth creating a custom Editor that accepts a complex "location" value which contains the three strings, and then use the UIHint Attribute, you can still use "EditorFor(...)", but editor for will use the editor the UIHint is given instead of the default editor. Or you can name the Editor view the same as the complex Location model. – cwharris May 05 '11 at 23:47

3 Answers3

1

Just create your own extension off of HtmlHelper:

public static HtmlHelperExtensions {
  public static MvcString LocationSelectFor<TModel, TProperty>(this HtmlHelper<TModel> helper, System.Linq.Expressions.Expression<Func<TModel,TProperty>> expression) {
    // examine expression and build html
  }
}

The trick is looking at the expression. This blog post should get you started: http://geekswithblogs.net/Madman/archive/2008/06/27/faster-reflection-using-expression-trees.aspx

Alternately, you can create an EditorTemplate for your Location class. Just google for asp.net mvc editortemplate. http://www.codecapers.com/post/Display-and-Editor-Templates-in-ASPNET-MVC-2.aspx

Personally I would stick with EditorTemplates, as you can change the view without the need to recompile typically.

Andy
  • 8,432
  • 6
  • 38
  • 76
0

You can follow this example to use expression and expression body

Get Custom Attributes from Lambda Property Expression

Or just use string expression and manipulate that as here http://www.joelscode.com/post/Use-MVC-Templates-with-Dynamic-Members-with-custom-HtmlHelper-Extensions.aspx

Community
  • 1
  • 1
Priyank
  • 10,503
  • 2
  • 27
  • 25
0

Following xixonia little hints I got what I needed.

@Html.EditorFor(o => o.Place, "LocationSelector",
    new CreateLocation{ Country = "US", State = "A", City = "Y" })

And I have a template under

Views
|- Shared
   |- EditorTemplates

LocationSelector.cshtml

@model TestMVC3Razor.Controllers.CreateLocation
@using TestMVC3Razor.Controllers
@Html.DropDownListFor(o => o.Country, Model.CountryList)
<br />
@Html.DropDownListFor(o => o.State, Model.StateList)
<br />
@Html.DropDownListFor(o => o.City, Model.CityList)

And then I made

public class CreateEntity
{
    [Required]
    public string Name { get; set; }

    public CreateLocation Place { get; set; }
}

public class CreateLocation
{
    public CreateLocation(Location location = null)
    {
        if (location != null)
        {
            Country = location.Country;
            State = location.State;
            City = location.City;
        }
    }

    public string Country { get; set; }

    public string State { get; set; }

    public string City { get; set; }

    public IEnumerable<SelectListItem> CountryList
    {
        get
        {
            var list = new[]
            {
                new SelectListItem() { Text = "US", Value = "US" },
                new SelectListItem() { Text = "BR", Value = "BR" },
                new SelectListItem() { Text = "ES", Value = "ES" },
            };
            var selected = list.FirstOrDefault(o => o.Value == Country);
            if (selected != null)
            {
                selected.Selected = true;
            }
            return list;
        }
    }
    public IEnumerable<SelectListItem> StateList
    {
        get
        {
            var list = new[]
            {
                new SelectListItem() { Text = "A", Value = "A" },
                new SelectListItem() { Text = "B", Value = "B" },
                new SelectListItem() { Text = "C", Value = "C" },
            };
            var selected = list.FirstOrDefault(o => o.Value == State);
            if (selected != null)
            {
                selected.Selected = true;
            }
            return list;
        }
    }
    public IEnumerable<SelectListItem> CityList
    {
        get
        {
            var list = new[]
            {
                new SelectListItem() { Text = "X", Value = "X" },
                new SelectListItem() { Text = "Y", Value = "Y" },
                new SelectListItem() { Text = "Z", Value = "Z" },
            };
            var selected = list.FirstOrDefault(o => o.Value == City);
            if (selected != null)
            {
                selected.Selected = true;
            }
            return list;
        }
    }
}

And my controller

public class HomeController : Controller
{
    public ActionResult Index()
    {
        // can load data for edit
        return View(new CreateEntity { Place = new CreateLocation(TempData["Location"] as Location) });
    }

    [HttpPost]
    public ActionResult Index(Entity ent)
    {
        var loc = ent.Place;
        var x = String.Format("{0} {1} {2}", loc.Country, loc.State, loc.City);
        ViewBag.Result = x; // display selected values

        TempData["Location"] = loc;
        return Index();
    }
}

I don't know if it is the best solution, but at least I can call

@Html.EditorFor(o => o.Place, "LocationSelector", obj)

from any place and have a default place selector on my website.

BrunoLM
  • 97,872
  • 84
  • 296
  • 452