MVC2 EditorTemplate for DropDownList - asp.net

I've spent the majority of the past week knee deep in the new templating functionality baked into MVC2. I had a hard time trying to get a DropDownList template working. The biggest problem I've been working to solve is how to get the source data for the drop down list to the template. I saw a lot of examples where you can put the source data in the ViewData dictionary (ViewData["DropDownSourceValuesKey"]) then retrieve them in the template itself (var sourceValues = ViewData["DropDownSourceValuesKey"];) This works, but I did not like having a silly string as the lynch pin for making this work.
Below is an approach I've come up with and wanted to get opinions on this approach:
here are my design goals:
The view model should contain the source data for the drop down list
Limit Silly Strings
Not use ViewData dictionary
Controller is responsible for filling the property with the source data for the drop down list
Here's my View Model:
public class CustomerViewModel
{
[ScaffoldColumn(false)]
public String CustomerCode{ get; set; }
[UIHint("DropDownList")]
[DropDownList(DropDownListTargetProperty = "CustomerCode"]
[DisplayName("Customer Code")]
public IEnumerable<SelectListItem> CustomerCodeList { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
public String PhoneNumber { get; set; }
public String Address1 { get; set; }
public String Address2 { get; set; }
public String City { get; set; }
public String State { get; set; }
public String Zip { get; set; }
}
My View Model has a CustomerCode property which is a value that the user selects from a list of values. I have a CustomerCodeList property that is a list of possible CustomerCode values and is the source for a drop down list. I've created a DropDownList attribute with a DropDownListTargetProperty. DropDownListTargetProperty points to the property which will be populated based on the user selection from the generated drop down (in this case, the CustomerCode property).
Notice that the CustomerCode property has [ScaffoldColumn(false)] which forces the generator to skip the field in the generated output.
My DropDownList.ascx file will generate a dropdown list form element with the source data from the CustomerCodeList property. The generated dropdown list will use the value of the DropDownListTargetProperty from the DropDownList attribute as the Id and the Name attributes of the Select form element. So the generated code will look like this:
<select id="CustomerCode" name="CustomerCode">
<option>...
</select>
This works out great because when the form is submitted, MVC will populate the target property with the selected value from the drop down list because the name of the generated dropdown list IS the target property. I kinda visualize it as the CustomerCodeList property is an extension of sorts of the CustomerCode property. I've coupled the source data to the property.
Here's my code for the controller:
public ActionResult Create()
{
//retrieve CustomerCodes from a datasource of your choosing
List<CustomerCode> customerCodeList = modelService.GetCustomerCodeList();
CustomerViewModel viewModel= new CustomerViewModel();
viewModel.CustomerCodeList = customerCodeList.Select(s => new SelectListItem() { Text = s.CustomerCode, Value = s.CustomerCode, Selected = (s.CustomerCode == viewModel.CustomerCode) }).AsEnumerable();
return View(viewModel);
}
Here's my code for the DropDownListAttribute:
namespace AutoForm.Attributes
{
public class DropDownListAttribute : Attribute
{
public String DropDownListTargetProperty { get; set; }
}
}
Here's my code for the template (DropDownList.ascx):
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<SelectListItem>>" %>
<%# Import Namespace="AutoForm.Attributes"%>
<script runat="server">
DropDownListAttribute GetDropDownListAttribute()
{
var dropDownListAttribute = new DropDownListAttribute();
if (ViewData.ModelMetadata.AdditionalValues.ContainsKey("DropDownListAttribute"))
{
dropDownListAttribute = (DropDownListAttribute)ViewData.ModelMetadata.AdditionalValues["DropDownListAttribute"];
}
return dropDownListAttribute;
}
</script>
<% DropDownListAttribute attribute = GetDropDownListAttribute();%>
<select id="<%= attribute.DropDownListTargetProperty %>" name="<%= attribute.DropDownListTargetProperty %>">
<% foreach(SelectListItem item in ViewData.Model)
{%>
<% if (item.Selected == true) {%>
<option value="<%= item.Value %>" selected="true"><%= item.Text %></option>
<% } %>
<% else {%>
<option value="<%= item.Value %>"><%= item.Text %></option>
<% } %>
<% } %>
</select>
I tried using the Html.DropDownList helper, but it would not allow me to change the Id and Name attributes of the generated Select element.
NOTE: you have to override the CreateMetadata method of the DataAnnotationsModelMetadataProvider for the DropDownListAttribute. Here's the code for that:
public class MetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
var additionalValues = attributes.OfType<DropDownListAttribute>().FirstOrDefault();
if (additionalValues != null)
{
metadata.AdditionalValues.Add("DropDownListAttribute", additionalValues);
}
return metadata;
}
}
Then you have to make a call to the new MetadataProvider in Application_Start of Global.asax.cs:
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ModelMetadataProviders.Current = new MetadataProvider();
}
Well, I hope this makes sense and I hope this approach may save you some time. I'd like some feedback on this approach please. Is there a better approach?

I think I found a solution to make it work when using Html.EditorForModel(); When using EditorForModel(), MVC uses Object.ascx to loop through all properties of the model and calls the corresponding template for each property in the model. ASP.Net MVC out of the box has Object.ascx in code, but you can create your own Object.ascx. Just create an EditorTemplates subfolder in your Shared View folder. Create an Object.ascx file there. (read this post for more information: http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-3-default-templates.html)
Here's my Object.ascx:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%# Import Namespace="WebAppSolutions.Helpers" %>
<% if (ViewData.TemplateInfo.TemplateDepth > 1) { %>
<%= ViewData.ModelMetadata.SimpleDisplayText%>
<% }
else { %>
<% foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm))) { %>
<% var htmlFieldName = Html.HtmlFieldNameFor(prop.PropertyName);%>
<% if (prop.HideSurroundingHtml) { %>
<%= Html.Editor(htmlFieldName)%>
<% }
else { %>
<div id="<%= htmlFieldName %>Container" class="editor-field">
<% if (!String.IsNullOrEmpty(Html.Label(prop.PropertyName).ToHtmlString())) { %>
<%= Html.Label(prop.PropertyName, Html.HtmlDisplayName(prop.PropertyName), prop.IsRequired)%>
<% } %>
<%= Html.Editor(prop.PropertyName, "", htmlFieldName)%>
<%= Html.ValidationMessage(prop.PropertyName, "*") %>
</div>
<% } %>
<% } %>
<% } %>
I have some custome code in my WebAppSolutions.Helpers for HtmlFieldNameFor and HtmlDisplayName. These helpers retrieve data from attributes applied to properties in the view model.
public static String HtmlFieldNameFor<TModel>(this HtmlHelper<TModel> html, String propertyName)
{
ModelMetadata modelMetaData = GetModelMetaData(html, propertyName);
return GetHtmlFieldName(modelMetaData, propertyName);
}
public static String HtmlDisplayName<TModel>(this HtmlHelper<TModel> html, String propertyName)
{
ModelMetadata modelMetaData = GetModelMetaData(html, propertyName);
return modelMetaData.DisplayName ?? propertyName;
}
private static ModelMetadata GetModelMetaData<TModel>(HtmlHelper<TModel> html, String propertyName)
{
ModelMetadata modelMetaData = ModelMetadata.FromStringExpression(propertyName, html.ViewData);
return modelMetaData;
}
private static String GetHtmlFieldName(ModelMetadata modelMetaData, string defaultHtmlFieldName)
{
PropertyExtendedMetaDataAttribute propertyExtendedMetaDataAttribute = GetPropertyExtendedMetaDataAttribute(modelMetaData);
return propertyExtendedMetaDataAttribute.HtmlFieldName ?? defaultHtmlFieldName;
}
The key to getting this to work using EditorModelFor() is this (should be line 20 or so in Object.ascx above):
<%= Html.Editor(prop.PropertyName, "", htmlFieldName)%>
prop.PropertyName is the property in the ViewModel containing the list of data that will become the DropDownList. htmlFieldName is the name of the property that's hidden that the DropDownList property is replacing. Make sense?
I hope this helps you.

Perfect. This is what I'm looking for. Thanks!
But your example model is simple model. How about a complex viewmodel like
public class MaintainServicePackageViewModel
{
public IEnumerable<ServicePackageWithOwnerName> ServicePackageWithOwnerName { get; set; }
public ServicePackageWithOwnerName CurrentServicePackage { get; set; }
public IEnumerable<ServiceWithPackageName> ServiceInPackage { get; set; }
}
public class ServicePackageWithOwnerName : ServicePackage
{
[UIHint("DropDownList")]
[DropDownList(DropDownListTargetProperty = "Owner")]
[DisplayNameLocalized(typeof(Resources.Globalization), "OwnerName")]
public IEnumerable<SelectListItem> OwnerName { get; set; }
}
The OwnerName is set to a dropdownlist, but it is not a direct element of the viewmodel instead it's a child element of ServicePackageWithOwnerName which is the element of the viewmodel. In such condition, there's no way to set the OwnerName value in the controller, how to fix this? Appreciate!
Regards
Jack

This is my approach from this post in Code Project:
One Editor Template for all DropDownLists in ASP.Net MVC

Related

ASP.NET MVC3 ModelBinding Issue - Getting values of dropdowns into the model?

I’ve lost more of my hair trying to solve this problem, so I really was hoping you could help?
I have a Telerik MVC grid:
And I have a custom EditorTemplate for Address (Address.ascx)
My ImporterDetails model:
public class ImporterViewModel : IEnumerableViewModel
{
public int ImporterId { get; set; }
[Required]
[DisplayName("Importer Name *")]
public string ImporterName { get; set; }
[Required]
[DisplayName("Importer Address *")]
public Address ImporterAddress { get; set; }
public static ImporterViewModel CreateImporter()
{
return new ImporterViewModel
{
ImporterName = Guid.NewGuid().ToString().Substring(0, 5),
ImporterAddress = Address.CreateDummyAddress(),
};
}
}
And the AddressViewModel:
[Bind(Exclude = "State, Country")]
public class Address
{
public int AddressId { get; set; }
[DisplayName("Address Line 1")]
[Required]
public string Line1 { get; set; }
[DisplayName("Address Line 2")]
public string Line2 { get; set; }
[DisplayName("Postcode")]
[Required]
[RegularExpression(RegexConstants.AUSTRALIAN_POSTCODE_PATTERN, ErrorMessage = "Invalid post code")]
public string Postcode { get; set; }
[DisplayName("State")]
public State State { get; set; }
[DisplayName("Suburb")]
public string Suburb { get; set; }
[DisplayName("Country")]
public Country Country { get; set; }
[Required]
public int CountryId { get; set; }
[Required]
public int StateId { get; set; }
/// <summary>
/// Creates a new dummy instance of Address
/// </summary>
/// <returns></returns>
public static Address CreateDummyAddress()
{
return new Address
{
Country = ServiceLocatorFactory.GetCodeServiceLocator<Country>().Get(x => x.CodeValue.ToLower() == "canada"),
State = ServiceLocatorFactory.GetCodeServiceLocator<State>().Get(x => x.CodeValue.ToLower() == "nsw"),
Line1 = Guid.NewGuid().ToString().Substring(0, 15),
Line2 = Guid.NewGuid().ToString().Substring(0, 15),
Suburb = "Dandenong",
Postcode = "2606",
};
}
public string AddressStrings
{
get
{
return ToString();
}
}
public override string ToString()
{
// create a blank StringBuilder
var sb = new StringBuilder();
// add the first address line
sb.Append(string.Format("{0}, ", Line1));
// add the second address line
sb.Append(string.Format("{0}, ", Line2));
sb.Append(string.Format("{0}, ", Suburb));
sb.Append(string.Format("{0} {1}, ", State == null ? string.Empty : State.Description, Postcode));
sb.Append(string.Format("{0}", Country == null ? string.Empty : Country.Description));
// and then return it as a single (formatted) string
return sb.ToString();
}
}
You’ll notice I’ve excluded State and Country because if I don’t, when I call a TryUpdateModel(importer) – I get the dreaded parameterless constructor exception. My question is:
How do I go about getting the right id of the State and Country (or in general, any dropdown) in my action this way?
For completeness’ sake:
Address.ascx
<div class="formElementGroupVertical">
<%: Html.LabelFor(m => m.Line1) %>
<%: Html.EditorFor(m => m.Line1) %>
<%: Html.ValidationMessageFor(m => m.Line1) %>
</div>
<div class="formElementGroupVertical">
<%: Html.LabelFor(m => m.Line2) %>
<%: Html.EditorFor(m => m.Line2) %>
<%: Html.ValidationMessageFor(m => m.Line2) %>
</div>
<div class="formElementGroupVertical">
<%: Html.LabelFor(m => m.Suburb) %>
<%: Html.EditorFor(m => m.Suburb)%>
<%: Html.ValidationMessageFor(m => m.Suburb)%>
</div>
<div class="formElementGroupVertical">
<%: Html.LabelFor(m => m.State) %>
<%: Html.EditorFor(m => m.State) %>
</div>
<div class="formElementGroupVertical">
<%: Html.LabelFor(m => m.Postcode) %>
<%: Html.EditorFor(m => m.Postcode)%>
<%: Html.ValidationMessageFor(m => m.Postcode)%>
</div>
<div class="formElementGroupVertical">
<%: Html.LabelFor(m => m.Country) %>
<%: Html.EditorFor(m => m.Country) %>
</div>
Country:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Web.Common.Models.Country>" %>
<%# Import Namespace="Web.Common.Models" %>
<%# Import Namespace="Web.Common.Service" %>
<%: Html.DropDownListFor(m => m.CodeId, new SelectList(ServiceLocatorFactory.GetCodeServiceLocator<Country>().GetAll(), "CodeId", "Description"), "Please Select")%>
And state is Identical to Country except the obvious.
Any help?
Short Answer:
CountryId is not being populated because the DropDownlistFor is 'For' country => country.CodeId.
To get CountryId, you'd actually need to point the dropdown list to it:
Html.DropDownListFor(m => m.CountryId, new SelectList(ServiceLocatorFactory.GetCodeServiceLocator<Country>().GetAll(), "CodeId", "Description"), "Please Select")%>
Slightly longer answer:
The easiest way to get a value of a dropdown is bind DropDownListFor to a property on your viewmodel that stores the id. Then in your controller you'd generate (e.g. via the database) the object from that id and attach it to whatever model as per your business requirements.
It's troublesome but straight-forward. There's currently no automatic way to modelbind full objects via dropdowns AFAIK.
In your case, your viewmodel would have:
public class AddressViewModel
{
public int SelectedCountryId { get; set; }
}
then use the DropDownFor() in this way:
<%: Html.DropDownListFor(m => m.SelectedCountryId, new SelectList(ServiceLocatorFactory.GetCodeServiceLocator<Country>().GetAll(), "CodeId", "Description"), "Please Select")%>
And then in your Action (in pseudo-code):
public ViewResult Save(AddressViewModel addressVM)
{
var address = new Address() { Country = countriesStore.ById(addressVM.SelectedCountryId) };
address.Save();
...
}
This way also means that you'd need to use a view model instead of the domain model for your views. Your example seems to be using the domain model so you might want to look into changing this. Once you start using view models you'll also need something like Automapper to facilitate mapping your view models to domain models.
Okay folks, I seem to have a temporary workaround to my problem, and would love to know your feedback, and possibly any other alternatives that may make this solution more "robust".
In my action, I check the Request.Form and lookup the dropdown specified by the PropertyName of the complex type. So the syntax looks like this:
PropertyName.PropertyName.Value field being returned by the dropdown. Using this, I am then able to look up the repository if required to get the instance of the ComplexType.
My Action is pasted below for reference:
[AcceptVerbs(HttpVerbs.Post)]
[GridAction]
public ActionResult Update(int id)
{
// obtain the instance of the importer we wish to update
var importer = serviceLocator.Get(i => i.ImporterId == id);
// the address object doesn't bind the State and Country dropdowns so we must manually read these
var stateCodeId = Request.Form["ImporterAddress.State.CodeId"];
var countryCodeId = Request.Form["ImporterAddress.Country.CodeId"];
//Perform model binding (fill the properties and validate the model)
if (TryUpdateModel(importer))
{
// parse the Id fields of the selected values of the dropdowns that we've just read from the request objects
importer.ImporterAddress.StateId = int.Parse(stateCodeId);
importer.ImporterAddress.CountryId = int.Parse(countryCodeId);
// and convert them to their specific code objects
importer.ImporterAddress.State =
ServiceLocatorFactory.GetCodeServiceLocator<State>().Get(s => s.CodeId == int.Parse(stateCodeId));
importer.ImporterAddress.Country =
ServiceLocatorFactory.GetCodeServiceLocator<Country>().Get(s => s.CodeId == int.Parse(countryCodeId));
// now save the updated model
serviceLocator.Update(importer);
}
// rebind the grid
return PartialView(new GridModel(serviceLocator.GetAll().ToList()));
}

Binding List<MyObject> to a repeater

I have a complicated class which is something like:
public class Person
{
public int Pid;
IList<Address> Addressess;
public Name Name;
public Name PartnerName;
Person(int id)
{
Addressess = new List<Address>();
}
}
public class Address
{
public string HouseName;
public string street;
public string country;
public string universe;
public string galaxy;
}
public class Name
{
public string Firstname;
public string Lastname;
public string Fullname { get { return Firstname + " " + Lastname; } }
}
So, now, when I bind the repeater like so:
rpPeople.DataSource = PeopleNearYou; //this is a List<Person>();
and in the actual repeater, I want to show the details. To access, say, Pid, all I need to do is:
<%# Eval("Pid") %>
Now, I can't figure out how to access the full name in repeater
<%# Eval("Fullname") %> //error, fullname not found
Also, I want to display only First Address only and I can't do that
<%# Eval("Address").First().Universe %> //red, glarring error. can't figure out how
So, how would I display these stuff please?
Many thanks.
This will be so much easier if you grab required class members when you bind the repeater.
rpPeople.DataSource = PeopleNearYou.Select(r => new
{
Pid = r.Pid,
Universe = r.Addressess.First().Universe,
Fullname = r.Name.Fullname
}
Now all you need to do in your repeater is:
<%# Eval("Universe") %>
<%# Eval("Fullname") %>
If I get in to complicated situations like this I always use the ItemDataBound event as you can get much more control. For example, in your situation I would create a label in the item template, bind the ItemDataBound to code similar to this...
void rpt1_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
((Label)e.Item.FindControl("lblFullName")).Text = ((Person)e.Item.DataItem).FullName;
}
You'll need a check on e.Item.Type too if you have header/footer rows.

Binding View Model from a form post with inner complex types

Ok, i got a viewmodel as follows:
public class PageViewModel
{
public Item Item { get; set; }
...
public PageViewModel
{ }
public PageViewModel(Item itemWebPage)
{
...
}
}
I use a form to edit this Item using a route like:
/Controller/Edit/43
The controller uses this code:
[AcceptVerbs("GET")]
public new ActionResult Edit(int id)
{
...
PageViewModel pageViewModel = new PageViewModel(...);
return PartialView(pageViewModel);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, PageViewModel item)
{
if (ModelState.IsValid)
{
...
// Here, the item.Item.ID is null, I want it to map to the id of the route.
}
return PartialView("Edit", item);
}
What I want to achieve is the ID of the property: item.Item.ID to be bound to the ID from the (route)URL. However I can't get it to work. I tried using [Bind()] attribute.
I fixed it now using a hidden field in the form like so:
<%= Html.HiddenFor(model => model.Item.ID)%>
However this feels a bit icky. I'd like to know if there is a better cleaner way of doing this?
Personally I would set the ID parameter in the form declaration as follows:
<% using (Html.BeginForm("Edit", "YourController", FormMethod.Post, new { id = model.Item.ID })) { %>
...
<% } %>

ModelState always valid

I've got something seemingly very simple not working.
I have got a model
public class Name: Entity
{
[StringLength(10), Required]
public virtual string Title { get; set; }
}
public class Customer: Entity
{
public virtual Name Name { get; set; }
}
a view model
public class CustomerViweModel
{
public Customer Customer { get; set; }
}
a view
<% using(Html.BeginForm()) { %>
<%= Html.LabelFor(m => m.Customer.Name.Title)%>
<%= Html.TextBoxFor(m => m.Customer.Name.Title)%>
<button type="submit">Submit</button>
<% } %>
and a controller
[HttpPost]
public ActionResult Index([Bind(Prefix = "Customer")] Customer customer)
{
if(ModelState.IsValid)
Save
else
return View();
}
No matter what I enter as the title (null, or a string > 10 chars), ModelState.IsValid is always true. The Title field in the Customer object has a value, so the data is being passed around, but not being validated?
Any clues?
In your View I don't see any text box or a field allowing to send data to the controller, only a label. Properties will not be validated if they are not posted. Add a textbox, leave it blank and your model won't be valid any more:
<%= Html.TextBoxFor(m => m.Customer.Name.Title)%>
UPDATE:
Here's the code I've used:
Model:
public class Name
{
[StringLength(10), Required]
public virtual string Title { get; set; }
}
public class Customer
{
public virtual Name Name { get; set; }
}
public class CustomerViewModel
{
public Customer Customer { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index([Bind(Prefix = "Customer")]Customer cs)
{
return View(new CustomerViewModel
{
Customer = cs
});
}
}
View:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MyApp.Models.CustomerViewModel>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% using(Html.BeginForm()) { %>
<%= Html.LabelFor(m => m.Customer.Name.Title)%>
<%= Html.TextBoxFor(m => m.Customer.Name.Title)%>
<button type="submit">Submit</button>
<% } %>
</asp:Content>
When you submit this form a validation error is shown.
Remark1: I've omitted the Entity base class in the models as I don't how does it look.
Remark2: I've renamed the variable in the Index action to cs. I remember that there was some problems with this in ASP.NET MVC 1.0 when you had the prefix and the variable named the same but I am not sure whether this applies here and I think it was fixed.
Figured it out, it was becuase I'm referencing System.ComponentModel.DataAnnotations 3.6 instead of 3.5. From what I gather, 3.6 is for WCF RIA services only.

problems with ASP MVC + Html.DropDownList() using a ModelView Pattern

Recently I posted a question about the html helper dropdownlist and got it working (here). But now I have decided it was alot smarter to switch to ModelView Patterns so I have acces to strongly typed methods in my views etc. What I did was I made some adjustments to the code in my other topic in the following way:
VacatureFormViewModel:
public class VacaturesFormViewModel
{
public Vacatures Vacature { get; private set; }
public SelectList EducationLevels { get; private set; }
public SelectList Branches { get; private set; }
public SelectList CareerLevels { get; private set; }
Repository repository;
// Constructor
public VacaturesFormViewModel(Vacatures vacature)
{
this.Vacature = vacature;
this.repository = new Repository();
this.EducationLevels = new SelectList(repository.GetAllEducationLevels(),"ID","Name",vacature.EducationLevels);
this.Branches = new SelectList(repository.GetAllBranches(),"ID","Name",vacature.Branches);
this.CareerLevels = new SelectList(repository.GetAllCareerLevels(), "ID", "Name", vacature.CareerLevels);
}
}
BanenController:
//
// GET: /Banen/Create
public ActionResult Create()
{
Vacatures vacature = new Vacatures();
return View(new VacaturesFormViewModel(vacature));
}
//
// POST: /Banen/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Vacatures vacatureToAdd)
{
if (ModelState.IsValid)
{
try
{
// TODO: Add insert logic here
repository.AddToVacatures(vacatureToAdd);
repository.SaveChanges();
// Return to listing page if succesful
return RedirectToAction("Index");
}
catch (Exception e)
{
return View();
}
}
}
And my Create.aspx view (part of it):
<% using (Html.BeginForm()) {%>
<fieldset>
<legend>Fields</legend>
<p>
<label for="Title">Title:</label>
<%= Html.TextBox("Title", Model.Vacature.Title) %>
<%= Html.ValidationMessage("Title", "*") %>
</p>
<p>
<label for="Content">Content:</label>
<%= Html.TextArea("Content", Model.Vacature.Content) %>
<%= Html.ValidationMessage("Content", "*") %>
</p>
<p>
<label for="EducationLevels">EducationLevels:</label>
<%= Html.DropDownList("EducationLevels", Model.EducationLevels)%>
<%= Html.ValidationMessage("EducationLevels", "*") %>
</p>
<p>
<label for="CareerLevels">CareerLevels:</label>
<%= Html.DropDownList("CareerLevels", Model.CareerLevels)%>
<%= Html.ValidationMessage("CareerLevels", "*")%>
</p>
<p>
<label for="Branches">Branches:</label>
<%= Html.DropDownList("Branches", Model.Branches)%>
<%= Html.ValidationMessage("Branches", "*")%>
</p>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
For guiding I have used the NerdDinner tutorial by ScottGu and I have read various topics here.
My question is if it is possible to let MVC ASP set my careerlevel, educationlevel and branche (dropdownlists) automatically as it currently is returning an ID string which is not what I want. When I change the creation of the SelectList to:
this.CareerLevels = new SelectList(repository.GetAllCareerLevels(), vacature.CareerLevels);
So without the "ID" and "Name" it does not save either (I guess it is still returned as a string in the post method, and not the object itself) and next to this, it lists in the view as: vacature.EducationLevels etc. So not the Names but the object itself is listed.
Final question
So, in short, my question is if it is possible to use this approach to set my branche, educationallevel and careerlevel. So not automatically?
In which case I still have to use things like:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(FormCollection form)
{
Vacatures vacatureToAdd = new Vacatures();
// Retrieve the education level by its ID
if (!form["EducationLevels"].Equals(""))
{
Guid educationID = new Guid(form["EducationLevels"]);
vacatureToAdd.EducationLevels = repository.GetEducationLevelByID(educationID);
}
In my controller? Or are there other, smoother options.
Edited to use Guid:
With dropdownlists I use a slightly different approach. It might be possible to get your viewmodel working, but for me it is easier this way:
public class VacaturesFormViewModel
{
public IEnumerable<SelectListItem>EducationLevels{ get; set; }
public Guid EducationLevelID{ get; set; }
}
The EducationLevelID will have the selected id of your Dropdown.
This is the view:
<%= Html.DropDownList("EducationLevelID", Model.EducationLevels)%>
Controller
IEnumerable<SelectListItem> educationLevelList =
from level in GetLevelList()
select new SelectListItem
{
Text = level .Name,
Value = level.Uid.ToString()
};
model.EducationLevels = educationLevelList ;
I'm not for sure but I think you should create model binders. (David Hayden wrote an simple model binder)
You could bind educationID parameter automatically:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Guid? educationID, FormCollection form)
{
Vacatures vacatureToAdd = new Vacatures();
if (educationID != null)
{
vacatureToAdd.EducationLevels =
repository.GetEducationLevelByID(educationID.Value);
}

Resources