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
.
- 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?
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.
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
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.
精彩评论