Hi,
In some special cases you will need a list of textboxes (to deal with n - n associations) whose id is not know before runtime. Something like this : http://screencast.com/t/YjIxNjUyNmU
In that particular sample I'm looking to associate a count to some of my 'templates'.
in ASP.Net MVC 1 I coded a Dictionary ModelBinder to have a clean and intuitive HTML. It allowed things like this :
// loop on the templates
foreach(ITemplate template in templates)
{
// get the value as text
int val;
content.TryGetValue(template.Id, out val);
var value = ((val > 0) ? val.ToString() : string.Empty);
// compute the element name (for dictionary binding)
string id = "cbts_{0}".FormatMe(template.Id);
%>
<input type="text" id="<%= id %>" name="<%= id %>" value="<%= value %>" />
<label for="<%= id %>"><%= template.Name %></label>
<br />
Here is the code of the binder :
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
IDictionary<int, int> retour = new Dictionary<int, int>();
// get the values
var values = bindingContext.ValueProvider;
// get the model name
string modelname = bindingContext.ModelName + '_';
int skip = modelname.Length;
// loop on the keys
foreach(string keyStr in values.Keys)
{
// if an element has been identified
if(keyStr.StartsWith(modelname))
{
// get that key
int key;
if(Int32.TryParse(keyStr.Substring(skip), out key))
{
int value;
if(Int32.TryParse(values[keyStr].AttemptedValue, out value))
retour.Add(key, value);
}
}
}
return retour;
}
When passing to ASP.Net MVC 2, the problem is the ValueProvider is not a dictionary anymore. There is no way to loop through the values to parse them the way I did.
And I did not find any other way to do so (If you know one, tell me).
I finally switched to the 'standard' way of binding the dictionary, but the HTML is ugly, counterintuitive (using counters to loop over a non-indexed collection ??) and all the values are required, unlike the behavior I need (and that worked perfectly in ASP.Net MVC 1).
It looks like this :
int counter= 0;
// loop on the templates
foreach(ITemplate template in templates)
{
// get the value as text
int val;
content.TryGetValue(template.Id, out val);
var value = ((val > 0) ? val.ToString() : string.Empty);
// compute the element name (for dictionary binding)
string id = "cbts_{0}".FormatMe(template.Id);
string dictKey = "cbts[{0}].Key".FormatMe(counter);
string dictValue = "cbts[{0}].Value".FormatMe(counter++);
%>
<input type="hidden" name="<%= dictKey %>" value="<%= template.Id %>" />
<input type="text" id="<%= id %>" name="<%= dictValue %>" value="<%= value %>" />
<label for="<%= id %>"><%= template.Name %></label>
<br />
In the controller I need to trick the ModelState
to avoid 'a value is required' errors :
public ActionResult Save(int? id, Dictionary<int, int> cbts)
{
// clear all errors from the modelstate
foreach(var value in this.ModelState.Values)
value.Errors.Clear();
This is too tricky. I will soon need to use this kind of binding many times, and maybe have some tiers developer work on the application.
Question :
- Do you know a way to make it better ?
What I need, IMO, is a ModelBinder for dictionary that allow a better html and that does no consider that all values are required.