views:

69

answers:

4

Okay, I'm not trying to do anything super complex, I have searched, and searched, and roamed every blog and forum I can find. I am about to tear my hair out because no one just outright shows it working.

I want to use an IDictionary in my Model. Nevermind the reason why, that's irrelevant. I just want to do something very simple, and understand what is going on.

Can someone please help me figure out the HTML I should be putting in the View to make this work right? Please? I have tried Html.TextBoxFor and it comes back null on the postback. I have tried manually using the index of the dictionary item, and it comes back null. I am really getting frustrated, and all I keep seeing is redirection to blogs that don't answer the question.

Controller

    public ActionResult DictionaryView()
    {
        var model = new Dictionary<string, int>
        {
            { "First", 0 },
            { "Second", 0 },
            { "Third", 0 },
            { "Fourth", 0 }
        };

        return View(model);
    }

    [HttpPost]
    public ActionResult DictionaryView(Dictionary<string, int> dictionary)
    {
        return null;
    }

HTML

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<System.Collections.Generic.IDictionary<string,int>>" %>

<asp:Content ContentPlaceHolderID="MainContent" runat="server">

    <h2>Dictionary</h2>
<% using (Html.BeginForm()) {%>
        <%: Html.ValidationSummary(true) %>
    <% for (int i = 0; i < Model.Count; i++) { %>
        **// This is there I am stuck!** 
    <% } %>
            <p>
                <input type="submit" value="Submit" />
            </p>
    <% } %>
</asp:Content>
+3  A: 

http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx

Example

<%
    int i = 0;
    foreach (KeyValuePair<string, int> pair in Model)
    { %>
    <p>Key: <%= Html.TextBox(String.Format("[{0}].key", i), pair.Key)%></p>
    <p>Value: <%= Html.TextBox(String.Format("[{0}].value", i), pair.Value)%></p>
<%
    i++;
    }
%>

Edit: This should work

Phil Brown
I've seen this blog, I did not gather anything from it that was remotely helpful. It shows already written HTML based on pre-known data. Even typing in his examples verbatim didn't work. This blog doesn't show any Views, only pseudo-models. All of the HTML he shows is simply what is generated, not what the View should look like to get said results.
Stacey
Unfortunately that doesn't work. It's a dictionary, not an Array, so using 'i' as an indexer doesn't actually work. I've also tried using a foreach and using the Key as the Indexer, and it yields no working results either. All of the fields come out with the same values.
Stacey
Changing [i] to ElementAt(i) did yield a working view, but then the postback gives a null Model.
Stacey
The postback is a different story. Did you capture the `[Post]`?
rockinthesixstring
Yes, I examine the post, and it is null. The problem is that it generates every field with an Id/Name of "Key" or "Value". It doesn't create fields with unique ids (hence the blog post not being of use. It presumes the Ids correlate to the dictionary)
Stacey
do you think that your postback is giving a null Model because you've got `return null` in the `[HttpPost]` section? What are you expecting to be returned in your Postback?
rockinthesixstring
I'm putting a breakpoint at the Method entry, and examining the parameter. No point in returning anything if the model is null from the entry point.
Stacey
I'm not sure what you're talking about. There aren't any hidden fields in the HTML.
Stacey
Edited my answer to work (tested too). Whilst the model binding part is definitely built-in, creating a view for generic dictionaries is not, mainly due to the inability to fetch or set keys at specific indexes. Because of this, you can't use the strongly-typed helpers which is unfortunate.
Phil Brown
Yes! That works! I didn't consider using the old Html Helpers. I forgot that you could set the values manually through them. Thank you! They really need to examine this, because all of the things you read up on make it sound like this should work out of the box, but it very clearly doesn't.
Stacey
+1  A: 

Can you use a ViewModel (POCO) instead of a Dictionary? That way your model will have something you can select.

EDIT


ok. So my chops aren't perfect without intellisense... sorry. But this is my general idea

Public Function DictionaryView() As ActionResult

    Dim model = New System.Collections.Generic.List(Of MyDictionary)
    model.Add(new MyDictionary("First", 0))
    model.Add(new MyDictionary("Second", 0))
    model.Add(new MyDictionary("Third", 0))
    model.Add(new MyDictionary("Forth", 0))

    Return View(model)
End Function


''# This would be your view model.
Public Class MyDictionary
    Public Property TheString
    Public Property TheInt
End Class

Then in your view you can use

model.TheString  
model.TheInt
rockinthesixstring
No, the assignment is specific that it must be a Dictionary. I cannot use a wrapper.
Stacey
assignment as in homework?
rockinthesixstring
also, you do know that in real life you would far benefit from a ViewModel over a Dictionary? It makes more sense to look at in your View.
rockinthesixstring
Yes, as in homework. The practicality of the usage isn't really the point. The point is to understand the model binder and make it work with the non-indexed type. (otherwise I would simply wrap it and use EditorTemplates). Judging from everything else I keep reading, this is supposed to be 'very easy' to do, but yet no one has posted an actual working example of it for some reason. Blog after Blog keeps pointing out how it's built into MVC 2.0 but no one seems to show it in action.
Stacey
Ok, I understand. @Phil's answer is correct in this scenario.
rockinthesixstring
See my comment under @Phil's answer regarding the `null` on Postback.
rockinthesixstring
I'm also trying to avoid breaking the convention of the Html Helpers by hacking the IDs manually. It's supposed to be 'natively possible' without a bunch of magic strings.
Stacey
+1  A: 

Stacey,

I don't believe the ModelBinder (the default one anyway), will be able to figure out what you're trying to do, hence the IDictionary param being null.

Your items will be there in the Request.Params collection. For example:

Request.Params["First"]

will return whatever you entered in the first text box. From there, you can pluck them out by hand.

If you want to make this work without using a ViewModel as @rockinthesixstring suggested, you are most likely going to have to write a custom ModelBinder that would do what you want.

If you need an example of this, let me know and I'll dig up the custom model binder stuff.

BTW, a working view can look like this:

foreach (var pair in Model)
{
    Response.Write(Html.TextBox(pair.Key, pair.Value));
} 
CubanX
I've looked at the custom ModelBinder idea, too. But what I don't understand is why do all of the blogs say that this is innately built into MVC 2.0, yet it very clearly isn't? I've even found samples of Custom Model Binders that did this in MVC 1.0 that don't work in 2.0, and they say it's because it's built into 2.0. For instance, http://forums.asp.net/p/1529895/3706154.aspx
Stacey
Can you show me a blog where they say this works with the default ModelBinder? The link you have there is pointing to a custom ModelBinder that worked in v1 and is broken in v2.
CubanX
That's exactly my point. If you read the reply, they show that 2.0 has it updated to work with the Default ModelBinder.
Stacey
Hmm, seems you are correct. Let me mess around with the code some more and see if I can get it to wire up,
CubanX
+1  A: 

You are missing the concept of how to have a separate counter so you have an index and also iterate through the dictionary with a foreach loop:

<% using (Html.BeginForm()) {%>
        <%: Html.ValidationSummary(true) %>
        <% int counter = 0;
           foreach (var kv in Model) { %>
            <input type="text" name="dictionary[<%= counter %>].Key" value="<%= kv.Key %>" />
            <input type="text" name="dictionary[<%= counter %>].Value" value="<%= kv.Value %>" />
        <%
           counter++;
           } %>
        <p>
            <input type="submit" value="Submit" />
        </p>
<% } %>

Just tested this now, my Controller is a CCP of yours.

And FYI I simply read that blog post to get here. You just had to match your html elements to exactly what Scott posted.

jfar
Yes, it's just one of those things where everything is so obscure that it doesn't really click well. This also works. I'm probably going to write my own Html Helper that does the entire process, inevitably.
Stacey
Obscure? Well you're still in school.. Reading documentation and fighting through frustrating moments without getting flustered is a good professional skill to have.
jfar