BrunoLM
BrunoLM

Reputation: 100361

How to create custom Html.ControlFor?

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.

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?

Upvotes: 1

Views: 450

Answers (3)

BrunoLM
BrunoLM

Reputation: 100361

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.

Upvotes: 0

Priyank
Priyank

Reputation: 10623

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

Upvotes: 0

Andy
Andy

Reputation: 8562

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.

Upvotes: 1

Related Questions