tags:

views:

539

answers:

8

(I'm new to MVC).

In my application I don't have a model in the sense of a class with properties. Oh no it's much simpler: my users basically fill in a bunch of string values for various keys setup elsewhere in the system (the keys are arbitrary and not known ahead of time, thus no pre-coded class)1.

My "model" thus is just:

Dictionary<string, string>

Pretty simple.

As I understand it model binding, html helpers, model state, validation summaries all rely on reflection of an arbitrary class' properties. But can they just use the key/values in my dictionary instead?

For example, can I have:

<label for="Name">Name:</label>
<%= Html.TextBox("Name") %>
<%= Html.ValidationMessage("Name", "*") %>

and:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Dictionary<string, string> valuesEntered)
{            
    // ...
}

and have MVC use the "Name" key/value as found in my Dictionary<string, string> "model" to do all its behind-the-scenes magic? (MVC 1.0 preferrably, but please shed some light if this is better addressed in 2.0 as I'd still like to know)?


1: sounds silly I'm sure. It's for a reporting app, where the "keys" are the report parameter names and the "values" are the values the report will run with.

A: 

True, a model can be as simple as a collection of name/value pairs. You can go down that route but i would suggest at least wrapping your Dictionary in a class and using properties or getters at the very least to allow yourself some flexibility down the road.

And yes, you can bind collections but only in list/grid/combobox-type scenarios. E.g. UI controls that are designed to hold collections. You can't really use reflection to arbitrarily bind to a given value within a collection.

EDIT: I don't think a label/text box is the right UI for your scenario. Try a grid or a list.

Paul Sasik
Unless C# provides dynamic accessors, I cannot use properties since I don't know what to name them. They are dynamic by nature. That is kind of the crux of the problem.
JPot
I don't think a label/text box is the right UI for your scenario. Try a grid or a list.
Paul Sasik
@Paul Sasik: ok then, what do I do inside each iteration of the key/value pairs? I render a textbox whose name matches my key, and whose value MVC can (hopefully) pluck out of the dictionary or ModelState, right?
JPot
If you do want to render a text box you'll have to do so dynamically. e.g. Add the control (text box and label) to the form programmatically as you loop through your collection.
Paul Sasik
@Paul Sasik: yes thank you, I will ensure to do so. They question remains, if I leave out the value of the TextBox, can I get the MVC framework to pluck it out of my dictionary?
JPot
Not really. The route i'm suggesting goes away from a framework-supported binding method to something you manage manually. E.g. You would load the values into the view yourself, and save any changes from the view to the model as well...
Paul Sasik
A: 

I would suggest creating a model for a report name-value pair and then use a method such as that outlined in this (see the BeginCollectionItem html helper discussion) to achieve binding a collection of these.

Validation could then be inserted into the name-value pair model as required depending on the rules for the particular instance.

Simon Fox
One question that article raises for me: can `TextBoxFor(x => x["foo"])` work? I believe the `XxxFor()` helpers only work on *property accessors* though...
JPot
Thats why I suggest you use a collection of a concrete model such as KeyValuePairModel (i.e. IEnumerable<KeyValuePairModel>) which has Key and Value properties which you can bind to/reflect.
Simon Fox
TextBoxFor(x => x["foo"]) won't work because MVC will not be able to create meaningfull id for the input html tag that is produced by the TextBoxFor helper. Exception "InvalidOperationException: Templates can be used only with field and property accessor expressions" is thrown instead.
PanJanek
@PanJanek: you are correct, but there's no reason that should be the case. In the case of `TextBoxFor(x => x["foo"])` a meaningful id could be "foo".
Roatin Marth
@Roatin: Right, but what if the model object apart from indexer has a property named "foo"? Then the "foo" id would be ambiguous. It would have to be something like "[foo]", but i belive it is not a good idea to store so much information in html control id attribute.
PanJanek
@PanJanek: you are right that "foo" alone could conflict. But now that you mention it there is precedent for an id like "[foo]". MVC will generate an input element name like "[0]" for items in a collection. It could very well do the same for string indexed properties.
Roatin Marth
@Roatin: Now that you mention it I remember that i used that collection binding in one project and it works fine. Theoretically MVC could treat string indexers the same way as integer indexers, but maybe there is a reason why this feature is not included in MVC2 - string indexing values could contain spaces, special charaters ([, ", ') and could be very long (there is no length limit in fact). IDs produced from such indexing string could be potential source of bugs and messy html.
PanJanek
Why did this get downvoted?
Simon Fox
A: 

According to ASP.NET MVC architecture you will need to do the following steps:

  1. Create your own binder (a suitable example I found here)

    public class DefaultDictionaryBinder : DefaultModelBinder  
    {  
        //...  
    }
    
  2. Substitute default binder in Application_Start

    ModelBinders.Binders.DefaultBinder = new DefaultDictionaryBinder();
    
  3. Create action filter

    public class DictionaryFilter : ActionFilterAttribute  
    {  
        public override void OnActionExecuting(ActionExecutingContext filterContext)  
        {  
            // Process filterContext.HttpContext.Request.InputStream here  
        }    
        //...  
    }
    
  4. Now apply the filter to your action

    [DictionaryFilter]  
    [AcceptVerbs(HttpVerbs.Post)]  
    public ActionResult Create(Dictionary<string, string> valuesEntered)  
    {  
        //...  
    }  
    
  5. Enjoy your work :-)

ILog
A: 

Creating the custom model binder is a good suggestion, however, I see one big problem with using a Dictionary to supply the values: How does the Dictionary-based model get the keys? According to your comment, you plan to give your controls the same names as your keys. So, if you already know the names of your keys, why go through the trouble of creating a Dictionary?

You can accomplish the same result by creating a single ViewModel object that holds all of the parameters you will use for your reports as properties. Populate the values you need, ignore the ones you don't.

For example:

public class ReportParameterViewModel
{
    public DateTime OrderRangeBegin { get; set; }
    public DateTime OrderRangeEnd { get; set; }
    public string Department { get; set; }
    public int Customer_Number { get; set; }
    public double Commission_Rate { get; set; }
    public int Page_Start { get; set; }
    public int Page_End { get; set; }
    public bool IsExecutiveVersion { get; set; }
    //
    // ...and so on...
    //
}

Make sure all of your controller action methods responsible for report generation can accept an instance of ReportParameterViewModel and make sure all of your report views inherit from ReportParameterViewModel. The dictionary idea just sounds like more work than is necessary, given the fact that the views for your reports are likely to be static.

Neil T.
Thanks for the response. The example was only meant to be trivial. I actually *don't* know the keys, at all. The model is actually more like `IEnumerable<ParameterValue>`, where `ParameterValue` is basically `KeyValuePair` and thus, a dictionary.
JPot
A: 

Another alternative might be looping over the dictionary to generate the fields, using the key as the field name. In this case, however the label might have to be pulled from another location.

Prior to posting the form use jquery.form.js to combine the data being posted back into a json string representation for a dictionary and then use the JavaScriptSerializer to convert the json strin into an dictionary. Maybe, there is even support for doing this automatically.

For the validation you could also switch to client side validation, for example using jquery.validate.js and build the java scipt automatically.

Obalix
+2  A: 

The default model binder in MVC 1.0 can bind to a dictionary as long as it uses the magic form field identifiers 'dictionaryName[index].key' and 'dictionaryName[index].value', where dictionaryName is the name of your dictionary parameter, and index is a 0-based sequential number. Typically the 'key' value will be a hidden field, and the 'value' value is your text field. In your example:

<%= Html.Hidden("valuesEntered[0].key", "Name") %>
<%= Html.TextBox("valuesEntered[0].value") %>
<%= Html.ValidationMessage("valuesEntered[0].value", "*") %>

As I understand it, binding to dictionaries is different in MVC 2.

Sam
A: 

Goood Luck! I had the same issue, and i'm working on it now

A: 

Had the same problem

Who upvoted this? Useless "answer." -1.
Aaronaught
Not an answer, use the comment system next time
Ryu