MVC - Model binding to collection

Discussion in 'ASP .Net' started by RichB, May 18, 2009.

  1. RichB

    RichB Guest

    I am slowly getting to grips with the default model binding within the MVC
    framework, but within the model I am looking at now the model will not bind
    for a collection.

    I have read Scott Hanselman's blog post:
    http://www.hanselman.com/blog/ASPNE...dingToArraysListsCollectionsDictionaries.aspx

    I have then managed to replicate it for relatively simple examples where a
    Model object contains a collection of simple objects. This when passed to the
    view is represented in the view with HTML input field as follows:

    <%= Html.TextBox("object.Collection[index].PropertyName")%>

    This maps back to recreate the object including the collection Properties
    when the form is posted back.

    I am however having difficulty in getting the mapping to work in a more
    complex example:

    <% foreach (var contact in Model.Venue.VenueDetail.ContactLink.ContactDatas)
    {%>
    <p>
    <label
    for="venue.VenueDetail.ContactLink.ContactDatas[<%=Model.Venue.VenueDetail.ContactLink.ContactDatas.IndexOf(contact)%>].Data">Indoor or Outdoor:</label>
    <%=
    Html.TextArea("contact["+Model.Venue.VenueDetail.ContactLink.ContactDatas.IndexOf(contact)+"].Data")%>
    <%=
    Html.ValidationMessage("venue.VenueDetail.ContactLink.ContactDatas["+Model.Venue.VenueDetail.ContactLink.ContactDatas.IndexOf(contact)+"].Data", "*")%>
    </p>
    <%} %>

    This appears to create the correct html, i.e. the index starts at 0, and
    follows sequentially. (I have checked many times that this is the correct
    path to the object/property (intellisense provides the following path:
    Model.Venue.VenueDetail.ContactLink.ContactDatas[0].Data).

    When submitting the form it passes back the following in the post:

    [reduced for
    brevity].....venue.VenueDetail.ContactLink.ContactDatas%5B0%5D.Data=fred&venue.VenueDetail.ContactLink.ContactDatas%5B1%5D.Data=wilma

    However, whilst the ContactDatas object is created, it is not populated with
    any ContactData objects.

    Should the default model mapper map to models 3 deep, and if so is there any
    obvious error in what I have stated above.

    If not, then how should I best provide mappings for my ContactData objects?

    Thanks,
    Richard
    RichB, May 18, 2009
    #1
    1. Advertising

  2. I got a headache looking at what you posted, but if I understand the gist of
    it, you are wondering how to get an individual item from a list? If so, you
    have to simplify. You should not have to pass a godawfully long string of
    HTTP encoded crap to get info on a single item. If you want to drill down n
    number of steps in a hierarchy, create a page to display that item so you
    can get it with a simple:

    http://mysite/item3/1

    type of URL. You will have to deconstruct your hierarchy a bit to get here,
    but linking back with a huge query string will likely end up with a very
    kludgy and buggy application.

    Hope this helps.

    --
    Gregory A. Beamer
    MVP; MCP: +I, SE, SD, DBA

    *************************************************
    | Think outside the box! |
    *************************************************


    "RichB" <> wrote in message
    news:...
    > I am slowly getting to grips with the default model binding within the MVC
    > framework, but within the model I am looking at now the model will not
    > bind
    > for a collection.
    >
    > I have read Scott Hanselman's blog post:
    > http://www.hanselman.com/blog/ASPNE...dingToArraysListsCollectionsDictionaries.aspx
    >
    > I have then managed to replicate it for relatively simple examples where a
    > Model object contains a collection of simple objects. This when passed to
    > the
    > view is represented in the view with HTML input field as follows:
    >
    > <%= Html.TextBox("object.Collection[index].PropertyName")%>
    >
    > This maps back to recreate the object including the collection Properties
    > when the form is posted back.
    >
    > I am however having difficulty in getting the mapping to work in a more
    > complex example:
    >
    > <% foreach (var contact in
    > Model.Venue.VenueDetail.ContactLink.ContactDatas)
    > {%>
    > <p>
    > <label
    > for="venue.VenueDetail.ContactLink.ContactDatas[<%=Model.Venue.VenueDetail.ContactLink.ContactDatas.IndexOf(contact)%>].Data">Indoor
    > or Outdoor:</label>
    > <%=
    > Html.TextArea("contact["+Model.Venue.VenueDetail.ContactLink.ContactDatas.IndexOf(contact)+"].Data")%>
    > <%=
    > Html.ValidationMessage("venue.VenueDetail.ContactLink.ContactDatas["+Model.Venue.VenueDetail.ContactLink.ContactDatas.IndexOf(contact)+"].Data",
    > "*")%>
    > </p>
    > <%} %>
    >
    > This appears to create the correct html, i.e. the index starts at 0, and
    > follows sequentially. (I have checked many times that this is the correct
    > path to the object/property (intellisense provides the following path:
    > Model.Venue.VenueDetail.ContactLink.ContactDatas[0].Data).
    >
    > When submitting the form it passes back the following in the post:
    >
    > [reduced for
    > brevity].....venue.VenueDetail.ContactLink.ContactDatas%5B0%5D.Data=fred&venue.VenueDetail.ContactLink.ContactDatas%5B1%5D.Data=wilma
    >
    > However, whilst the ContactDatas object is created, it is not populated
    > with
    > any ContactData objects.
    >
    > Should the default model mapper map to models 3 deep, and if so is there
    > any
    > obvious error in what I have stated above.
    >
    > If not, then how should I best provide mappings for my ContactData
    > objects?
    >
    > Thanks,
    > Richard
    Cowboy \(Gregory A. Beamer\), May 18, 2009
    #2
    1. Advertising

  3. RichB

    RichB Guest

    Sorry,
    perhaps I should be clearer, I am wanting to create an object which has a
    collection. So a Venue has some details part of which are a series of
    contact details (phone, email etc). I am creating the view without problem,
    but my expectation is that I can create a textbox with a name which will
    enable the defaultmodelbinder to map back the post variables to the model.

    I'm sure that my model could be a little simpler, although I would still
    have Contact Data as a collection and it is complicated a little because of
    the linqtosql mapping. I guess that my question boils down to whether the
    defaultModelBindershould be able to map to a model of this complexity or
    whether I need to do some work to simplify it or if there is a more
    appropriate way to perform the mapping.

    Thanks,
    Richard




    "Cowboy (Gregory A. Beamer)" <> wrote in
    message news:OJJd$...
    >I got a headache looking at what you posted, but if I understand the gist
    >of it, you are wondering how to get an individual item from a list? If so,
    >you have to simplify. You should not have to pass a godawfully long string
    >of HTTP encoded crap to get info on a single item. If you want to drill
    >down n number of steps in a hierarchy, create a page to display that item
    >so you can get it with a simple:
    >
    > http://mysite/item3/1
    >
    > type of URL. You will have to deconstruct your hierarchy a bit to get
    > here, but linking back with a huge query string will likely end up with a
    > very kludgy and buggy application.
    >
    > Hope this helps.
    >
    > --
    > Gregory A. Beamer
    > MVP; MCP: +I, SE, SD, DBA
    >
    > *************************************************
    > | Think outside the box! |
    > *************************************************
    >
    >
    > "RichB" <> wrote in message
    > news:...
    >> I am slowly getting to grips with the default model binding within the
    >> MVC
    >> framework, but within the model I am looking at now the model will not
    >> bind
    >> for a collection.
    >>
    >> I have read Scott Hanselman's blog post:
    >> http://www.hanselman.com/blog/ASPNE...dingToArraysListsCollectionsDictionaries.aspx
    >>
    >> I have then managed to replicate it for relatively simple examples where
    >> a
    >> Model object contains a collection of simple objects. This when passed to
    >> the
    >> view is represented in the view with HTML input field as follows:
    >>
    >> <%= Html.TextBox("object.Collection[index].PropertyName")%>
    >>
    >> This maps back to recreate the object including the collection Properties
    >> when the form is posted back.
    >>
    >> I am however having difficulty in getting the mapping to work in a more
    >> complex example:
    >>
    >> <% foreach (var contact in
    >> Model.Venue.VenueDetail.ContactLink.ContactDatas)
    >> {%>
    >> <p>
    >> <label
    >> for="venue.VenueDetail.ContactLink.ContactDatas[<%=Model.Venue.VenueDetail.ContactLink.ContactDatas.IndexOf(contact)%>].Data">Indoor
    >> or Outdoor:</label>
    >> <%=
    >> Html.TextArea("contact["+Model.Venue.VenueDetail.ContactLink.ContactDatas.IndexOf(contact)+"].Data")%>
    >> <%=
    >> Html.ValidationMessage("venue.VenueDetail.ContactLink.ContactDatas["+Model.Venue.VenueDetail.ContactLink.ContactDatas.IndexOf(contact)+"].Data",
    >> "*")%>
    >> </p>
    >> <%} %>
    >>
    >> This appears to create the correct html, i.e. the index starts at 0, and
    >> follows sequentially. (I have checked many times that this is the
    >> correct
    >> path to the object/property (intellisense provides the following path:
    >> Model.Venue.VenueDetail.ContactLink.ContactDatas[0].Data).
    >>
    >> When submitting the form it passes back the following in the post:
    >>
    >> [reduced for
    >> brevity].....venue.VenueDetail.ContactLink.ContactDatas%5B0%5D.Data=fred&venue.VenueDetail.ContactLink.ContactDatas%5B1%5D.Data=wilma
    >>
    >> However, whilst the ContactDatas object is created, it is not populated
    >> with
    >> any ContactData objects.
    >>
    >> Should the default model mapper map to models 3 deep, and if so is there
    >> any
    >> obvious error in what I have stated above.
    >>
    >> If not, then how should I best provide mappings for my ContactData
    >> objects?
    >>
    >> Thanks,
    >> Richard

    >
    RichB, May 18, 2009
    #3
  4. Hi Richard,

    >I'm sure that my model could be a little simpler, although I would still
    >have Contact Data as a collection and it is complicated a little because

    of
    the linqtosql mapping. I guess that my question boils down to whether the
    >defaultModelBindershould be able to map to a model of this complexity or
    >whether I need to do some work to simplify it or if there is a more
    >appropriate way to perform the mapping.


    Hi Richard,
    I think the model updating should work. According to your code the problem
    is probably on this line:
    <%=
    Html.TextArea("contact["+Model.Venue.VenueDetail.ContactLink.ContactDatas.In
    dexOf(contact)+"].Data")%>

    Here the "contact" is the local variable:
    <% foreach (var contact in Model.Venue.VenueDetail.ContactLink.ContactDatas)

    However, to get the mapping work we should specify the name of <input> in
    this way:
    ModelClassname.CollectionPropertyName[index].PropertyName.....

    I've wrote a sample that shows how to do that. Please check out the
    following code (create a new ASP.NET MVC Web Application named
    MvcApplication3 and paste the following code into it):

    Model (create a new class in the Models folder):

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;

    namespace MvcApplication3.Models
    {
    public class MyData
    {
    public List<MyCustomer> Customers { get; set; }
    }
    public class MyCustomer {
    public TestClassDepth1 Depth1 { get; set; }
    }
    public class TestClassDepth1 {
    public TestClassDepth2 Depth2{get;set;}
    }
    public class TestClassDepth2
    {
    public TestClassDepth3 Depth3 { get; set; }
    }
    public class TestClassDepth3 {
    public TestClassDepth4 Depth4 { get; set; }
    }
    public class TestClassDepth4 {
    public string Name { get; set; }
    }
    }


    Controller (HomeController.cs):

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using MvcApplication3.Models;

    namespace MvcApplication3.Controllers
    {
    [HandleError]
    public class HomeController : Controller
    {
    public ActionResult Index()
    {
    ViewData["Message"] = "Welcome to ASP.NET MVC!";

    return View();
    }

    public ActionResult About()
    {
    ViewResult vr = View(
    new MyData()
    {
    Customers =
    new List<MyCustomer>()
    {
    new MyCustomer()
    { Depth1=new TestClassDepth1()
    { Depth2=new TestClassDepth2(){
    Depth3=new TestClassDepth3(){
    Depth4=new TestClassDepth4(){
    Name="Allen Chen"}
    }
    }
    }
    },
    new MyCustomer()
    { Depth1=new TestClassDepth1()
    { Depth2=new TestClassDepth2(){
    Depth3=new TestClassDepth3(){
    Depth4=new TestClassDepth4(){
    Name="Peter wang"}
    }
    }
    }
    },
    new MyCustomer()
    { Depth1=new TestClassDepth1()
    { Depth2=new TestClassDepth2(){
    Depth3=new TestClassDepth3(){
    Depth4=new TestClassDepth4(){
    Name="Mike Sun"}
    }
    }
    }
    }

    }
    });


    return vr;
    }
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Test(FormCollection formValues)
    {
    MyData md = new MyData();
    UpdateModel(md, "MyData", formValues.ToValueProvider());

    //Set a breakpoint here to check
    //md.Customers[1].Depth1.Depth2.Depth3.Depth4.Name
    //md.Customers[2].Depth1.Depth2.Depth3.Depth4.Name
    //md.Customers[3].Depth1.Depth2.Depth3.Depth4.Name

    ViewResult vr = View("About", md);
    return vr;
    }
    }
    }

    About.aspx:

    <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
    Inherits="System.Web.Mvc.ViewPage" %>

    <asp:Content ID="aboutTitle" ContentPlaceHolderID="TitleContent"
    runat="server">
    About Us
    </asp:Content>

    <asp:Content ID="aboutContent" ContentPlaceHolderID="MainContent"
    runat="server">
    <h2>About</h2>

    <form action="Test" method="post" >
    <%for (int i = 0; i <
    ((MvcApplication3.Models.MyData)ViewData.Model).Customers.Count; i++)
    { %>
    <%=Html.TextBox("MyData.Customers[" + i +
    "].Depth1.Depth2.Depth3.Depth4.Name",

    ((MvcApplication3.Models.MyData)ViewData.Model).Customers.Depth1.Depth2.D
    epth3.Depth4.Name)%>

    <%} %>
    <input type="submit" value="Post" />
    </form>


    </asp:Content>

    To test, set a breakpoint in the Test method where the comments locate,
    start debugging, click the "About" button to see the view. You'll see data
    is filled in the textbox. You can change the input if you like. Then click
    "Post" button to break into the Test method. Check the values indicated by
    the comments in the watch window. You'll see they are all updated.

    Please have a try and let me know if it works. If you have additional
    questions please feel free to ask.

    Regards,
    Allen Chen
    Microsoft Online Support

    Delighting our customers is our #1 priority. We welcome your comments and
    suggestions about how we can improve the support we provide to you. Please
    feel free to let my manager know what you think of the level of service
    provided. You can send feedback directly to my manager at:
    .

    ==================================================
    Get notification to my posts through email? Please refer to
    http://msdn.microsoft.com/en-us/subscriptions/aa948868.aspx#notifications.

    Note: MSDN Managed Newsgroup support offering is for non-urgent issues
    where an initial response from the community or a Microsoft Support
    Engineer within 2 business day is acceptable. Please note that each follow
    up response may take approximately 2 business days as the support
    professional working with you may need further investigation to reach the
    most efficient resolution. The offering is not appropriate for situations
    that require urgent, real-time or phone-based interactions. Issues of this
    nature are best handled working with a dedicated Microsoft Support Engineer
    by contacting Microsoft Customer Support Services (CSS) at
    http://msdn.microsoft.com/en-us/subscriptions/aa948874.aspx
    ==================================================
    This posting is provided "AS IS" with no warranties, and confers no rights.
    Allen Chen [MSFT], May 19, 2009
    #4
  5. RichB

    RichB Guest

    Thanks Allen,

    Yes, your code works fine, which I guess proves that deep models can be
    built by the model binder.

    I did make an error in my original post. I had been playing around with
    different references, including using the local reference which was posted.
    If I use the following, then the model still does not get rebuilt, although
    the ContactDatas object is built, just it's contents are null:

    Html.TextArea("venue.VenueDetail.ContactLink.ContactDatas["+Model.Venue.VenueDetail.ContactLink.ContactDatas.IndexOf(contact)+"].Data")

    Any ideas why this might not be working?

    What is the best way to simplify the model within the model itself, or
    within the controller by providing a formviewmodel (as per the nerdinners
    application). I am stuck with the LinktoSQL produced classes, The only real
    dead wood is the ContactLink Class, which is just a relic of the database
    structure. I could combine Venue and VenueDetails in one big class, though
    feel that actually they are better as seperate classes.

    Thanks,
    Richard


    "Allen Chen [MSFT]" <> wrote in message
    news:D...
    > Hi Richard,
    >
    >>I'm sure that my model could be a little simpler, although I would still
    >>have Contact Data as a collection and it is complicated a little because

    > of
    > the linqtosql mapping. I guess that my question boils down to whether the
    >>defaultModelBindershould be able to map to a model of this complexity or
    >>whether I need to do some work to simplify it or if there is a more
    >>appropriate way to perform the mapping.

    >
    > Hi Richard,
    > I think the model updating should work. According to your code the problem
    > is probably on this line:
    > <%=
    > Html.TextArea("contact["+Model.Venue.VenueDetail.ContactLink.ContactDatas.In
    > dexOf(contact)+"].Data")%>
    >
    > Here the "contact" is the local variable:
    > <% foreach (var contact in
    > Model.Venue.VenueDetail.ContactLink.ContactDatas)
    >
    > However, to get the mapping work we should specify the name of <input> in
    > this way:
    > ModelClassname.CollectionPropertyName[index].PropertyName.....
    >
    > I've wrote a sample that shows how to do that. Please check out the
    > following code (create a new ASP.NET MVC Web Application named
    > MvcApplication3 and paste the following code into it):
    >
    > Model (create a new class in the Models folder):
    >
    > using System;
    > using System.Collections.Generic;
    > using System.Linq;
    > using System.Web;
    >
    > namespace MvcApplication3.Models
    > {
    > public class MyData
    > {
    > public List<MyCustomer> Customers { get; set; }
    > }
    > public class MyCustomer {
    > public TestClassDepth1 Depth1 { get; set; }
    > }
    > public class TestClassDepth1 {
    > public TestClassDepth2 Depth2{get;set;}
    > }
    > public class TestClassDepth2
    > {
    > public TestClassDepth3 Depth3 { get; set; }
    > }
    > public class TestClassDepth3 {
    > public TestClassDepth4 Depth4 { get; set; }
    > }
    > public class TestClassDepth4 {
    > public string Name { get; set; }
    > }
    > }
    >
    >
    > Controller (HomeController.cs):
    >
    > using System;
    > using System.Collections.Generic;
    > using System.Linq;
    > using System.Web;
    > using System.Web.Mvc;
    > using MvcApplication3.Models;
    >
    > namespace MvcApplication3.Controllers
    > {
    > [HandleError]
    > public class HomeController : Controller
    > {
    > public ActionResult Index()
    > {
    > ViewData["Message"] = "Welcome to ASP.NET MVC!";
    >
    > return View();
    > }
    >
    > public ActionResult About()
    > {
    > ViewResult vr = View(
    > new MyData()
    > {
    > Customers =
    > new List<MyCustomer>()
    > {
    > new MyCustomer()
    > { Depth1=new TestClassDepth1()
    > { Depth2=new TestClassDepth2(){
    > Depth3=new TestClassDepth3(){
    > Depth4=new TestClassDepth4(){
    > Name="Allen Chen"}
    > }
    > }
    > }
    > },
    > new MyCustomer()
    > { Depth1=new TestClassDepth1()
    > { Depth2=new TestClassDepth2(){
    > Depth3=new TestClassDepth3(){
    > Depth4=new TestClassDepth4(){
    > Name="Peter wang"}
    > }
    > }
    > }
    > },
    > new MyCustomer()
    > { Depth1=new TestClassDepth1()
    > { Depth2=new TestClassDepth2(){
    > Depth3=new TestClassDepth3(){
    > Depth4=new TestClassDepth4(){
    > Name="Mike Sun"}
    > }
    > }
    > }
    > }
    >
    > }
    > });
    >
    >
    > return vr;
    > }
    > [AcceptVerbs(HttpVerbs.Post)]
    > public ActionResult Test(FormCollection formValues)
    > {
    > MyData md = new MyData();
    > UpdateModel(md, "MyData", formValues.ToValueProvider());
    >
    > //Set a breakpoint here to check
    > //md.Customers[1].Depth1.Depth2.Depth3.Depth4.Name
    > //md.Customers[2].Depth1.Depth2.Depth3.Depth4.Name
    > //md.Customers[3].Depth1.Depth2.Depth3.Depth4.Name
    >
    > ViewResult vr = View("About", md);
    > return vr;
    > }
    > }
    > }
    >
    > About.aspx:
    >
    > <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
    > Inherits="System.Web.Mvc.ViewPage" %>
    >
    > <asp:Content ID="aboutTitle" ContentPlaceHolderID="TitleContent"
    > runat="server">
    > About Us
    > </asp:Content>
    >
    > <asp:Content ID="aboutContent" ContentPlaceHolderID="MainContent"
    > runat="server">
    > <h2>About</h2>
    >
    > <form action="Test" method="post" >
    > <%for (int i = 0; i <
    > ((MvcApplication3.Models.MyData)ViewData.Model).Customers.Count; i++)
    > { %>
    > <%=Html.TextBox("MyData.Customers[" + i +
    > "].Depth1.Depth2.Depth3.Depth4.Name",
    >
    > ((MvcApplication3.Models.MyData)ViewData.Model).Customers.Depth1.Depth2.D
    > epth3.Depth4.Name)%>
    >
    > <%} %>
    > <input type="submit" value="Post" />
    > </form>
    >
    >
    > </asp:Content>
    >
    > To test, set a breakpoint in the Test method where the comments locate,
    > start debugging, click the "About" button to see the view. You'll see data
    > is filled in the textbox. You can change the input if you like. Then click
    > "Post" button to break into the Test method. Check the values indicated by
    > the comments in the watch window. You'll see they are all updated.
    >
    > Please have a try and let me know if it works. If you have additional
    > questions please feel free to ask.
    >
    > Regards,
    > Allen Chen
    > Microsoft Online Support
    >
    > Delighting our customers is our #1 priority. We welcome your comments and
    > suggestions about how we can improve the support we provide to you. Please
    > feel free to let my manager know what you think of the level of service
    > provided. You can send feedback directly to my manager at:
    > .
    >
    > ==================================================
    > Get notification to my posts through email? Please refer to
    > http://msdn.microsoft.com/en-us/subscriptions/aa948868.aspx#notifications.
    >
    > Note: MSDN Managed Newsgroup support offering is for non-urgent issues
    > where an initial response from the community or a Microsoft Support
    > Engineer within 2 business day is acceptable. Please note that each follow
    > up response may take approximately 2 business days as the support
    > professional working with you may need further investigation to reach the
    > most efficient resolution. The offering is not appropriate for situations
    > that require urgent, real-time or phone-based interactions. Issues of this
    > nature are best handled working with a dedicated Microsoft Support
    > Engineer
    > by contacting Microsoft Customer Support Services (CSS) at
    > http://msdn.microsoft.com/en-us/subscriptions/aa948874.aspx
    > ==================================================
    > This posting is provided "AS IS" with no warranties, and confers no
    > rights.
    >
    >
    >
    RichB, May 19, 2009
    #5
  6. RichB

    RichB Guest

    Allen,

    I've actually found an answer which works for this on a StackOverflow
    posting. I'm still not quite sure why my code didn't work, other than that
    the model was too complex for the DefaultModelBinder.

    The solution is to add a second Parameter with a Bind attribute Prefix as
    follows:
    [Bind(Prefix = "venue.VenueDetail.ContactLink.ContactDatas")]
    EntitySet<ContactData> contactData

    Then obviously set the ContactDatas = contactData.

    I need to look at exactly how Prefix is used, but it does work in populating
    the ContactDatas object.

    With respect to the complexity of my model I would appreciate some advice on
    whether it is advisable to be simplifying the model produced by the
    LinqtoSQL and if so the best approach to doing this.

    Thanks,
    Richard
    RichB, May 20, 2009
    #6
  7. Hi Richard,

    >I've actually found an answer which works for this on a StackOverflow
    >posting. I'm still not quite sure why my code didn't work, other than that
    >the model was too complex for the DefaultModelBinder.


    Thanks for your update. I tried again and it still works. Below is the
    updated code, which I believe is more close to your scenario:

    Model:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;

    namespace MvcApplication3.Models
    {
    public class MyData
    {

    public MyDataDepth1 MyDataDepth1 { get; set; }
    }

    public class MyDataDepth1
    {
    public MyDataDepth2 MyDataDepth2 { get; set; }
    }
    public class MyDataDepth2
    {
    public List<MyCustomer> Customers { get; set; }
    }

    public class MyCustomer {
    public TestClassDepth1 Depth1 { get; set; }
    }
    public class TestClassDepth1 {
    public TestClassDepth2 Depth2{get;set;}
    }
    public class TestClassDepth2
    {
    public TestClassDepth3 Depth3 { get; set; }

    }
    public class TestClassDepth3 {
    public TestClassDepth4 Depth4 { get; set; }
    }

    public class TestClassDepth4 {
    public string Name { get; set; }
    }
    }


    Controller:
    public ActionResult About()
    {


    ViewResult vr = View(
    new MyData()
    {
    MyDataDepth1 = new MyDataDepth1()
    {
    MyDataDepth2 = new MyDataDepth2()
    {
    Customers =
    new List<MyCustomer>()
    {
    new MyCustomer()
    { Depth1=new TestClassDepth1()
    { Depth2=new TestClassDepth2(){
    Depth3=new TestClassDepth3(){
    Depth4=new TestClassDepth4(){
    Name="Allen Chen"}
    }
    }
    }
    },
    new MyCustomer()
    { Depth1=new TestClassDepth1()
    { Depth2=new TestClassDepth2(){
    Depth3=new TestClassDepth3(){
    Depth4=new TestClassDepth4(){
    Name="Peter wang"}
    }
    }
    }
    },
    new MyCustomer()
    { Depth1=new TestClassDepth1()
    { Depth2=new TestClassDepth2(){
    Depth3=new TestClassDepth3(){
    Depth4=new TestClassDepth4(){
    Name="Mike Sun"}
    }
    }
    }
    }

    }
    }
    }
    });


    return vr;
    }
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Test(FormCollection formValues)
    {
    MyData md = new MyData();

    UpdateModel(md,"MyData", formValues.ToValueProvider());

    ViewResult vr = View("About", md);
    return vr;
    }

    View:

    <%for (int i = 0; i <
    ((MvcApplication3.Models.MyData)ViewData.Model).MyDataDepth1.MyDataDepth2.Cu
    stomers.Count; i++)
    { %>

    <%=Html.TextBox("MyData.MyDataDepth1.MyDataDepth2.Customers[" + i +
    "].Depth1.Depth2.Depth3.Depth4.Name",

    ((MvcApplication3.Models.MyData)ViewData.Model).MyDataDepth1.MyDataDepth2.Cu
    stomers.Depth1.Depth2.Depth3.Depth4.Name)%>




    <%} %>


    To know the reason why your code cannot work could you tell me what are
    venue, VenueDetail, ContactLink and ContactDatas (especially venue, it's in
    lower case, is it still a variable?) ? You can send a demo project to me:
    . Please update here after sending the project in case
    I missed that email.


    Regards,
    Allen Chen
    Microsoft Online Support
    Allen Chen [MSFT], May 21, 2009
    #7
  8. RichB

    RichB Guest

    Thanks Allen,

    I have tried your code and once again it works appropriately. I have sent
    you an email with a stripped down version of my project which fails in the
    same way on trying to create a venue.


    I notice that your Post method processes a FormCollection. Mine processes a
    Venue venue as per the Nerddinner example. I have tried following your
    FormCollection approach in my project and received the same result.

    I am happy that using [Bind (Prefix=...)] does solve the issue, and I sent
    the example to you just out of my interest and hopefully yours.

    If establishing the reason for the failure requires extensive work, then
    please feel free to let me know and discard.

    Richard
    RichB, May 21, 2009
    #8
  9. Are you using ViewPage or ViewPage<T>?

    If you use ViewPage<T>, you have a more explicit way of defining your model
    (ie, T) so you can drill through more explicitly.

    --
    Gregory A. Beamer
    MVP; MCP: +I, SE, SD, DBA

    Blog: http://gregorybeamer.spaces.live.com
    Twitter: @gbworld

    *************************************************
    | Think outside the box! |
    *************************************************


    "RichB" <> wrote in message
    news:#iCVv1#...
    > Sorry,
    > perhaps I should be clearer, I am wanting to create an object which has a
    > collection. So a Venue has some details part of which are a series of
    > contact details (phone, email etc). I am creating the view without
    > problem, but my expectation is that I can create a textbox with a name
    > which will enable the defaultmodelbinder to map back the post variables to
    > the model.
    >
    > I'm sure that my model could be a little simpler, although I would still
    > have Contact Data as a collection and it is complicated a little because
    > of the linqtosql mapping. I guess that my question boils down to whether
    > the defaultModelBindershould be able to map to a model of this complexity
    > or whether I need to do some work to simplify it or if there is a more
    > appropriate way to perform the mapping.
    >
    > Thanks,
    > Richard
    >
    >
    >
    >
    > "Cowboy (Gregory A. Beamer)" <> wrote in
    > message news:OJJd$...
    >>I got a headache looking at what you posted, but if I understand the gist
    >>of it, you are wondering how to get an individual item from a list? If so,
    >>you have to simplify. You should not have to pass a godawfully long string
    >>of HTTP encoded crap to get info on a single item. If you want to drill
    >>down n number of steps in a hierarchy, create a page to display that item
    >>so you can get it with a simple:
    >>
    >> http://mysite/item3/1
    >>
    >> type of URL. You will have to deconstruct your hierarchy a bit to get
    >> here, but linking back with a huge query string will likely end up with a
    >> very kludgy and buggy application.
    >>
    >> Hope this helps.
    >>
    >> --
    >> Gregory A. Beamer
    >> MVP; MCP: +I, SE, SD, DBA
    >>
    >> *************************************************
    >> | Think outside the box! |
    >> *************************************************
    >>
    >>
    >> "RichB" <> wrote in message
    >> news:...
    >>> I am slowly getting to grips with the default model binding within the
    >>> MVC
    >>> framework, but within the model I am looking at now the model will not
    >>> bind
    >>> for a collection.
    >>>
    >>> I have read Scott Hanselman's blog post:
    >>> http://www.hanselman.com/blog/ASPNE...dingToArraysListsCollectionsDictionaries.aspx
    >>>
    >>> I have then managed to replicate it for relatively simple examples where
    >>> a
    >>> Model object contains a collection of simple objects. This when passed
    >>> to the
    >>> view is represented in the view with HTML input field as follows:
    >>>
    >>> <%= Html.TextBox("object.Collection[index].PropertyName")%>
    >>>
    >>> This maps back to recreate the object including the collection
    >>> Properties
    >>> when the form is posted back.
    >>>
    >>> I am however having difficulty in getting the mapping to work in a more
    >>> complex example:
    >>>
    >>> <% foreach (var contact in
    >>> Model.Venue.VenueDetail.ContactLink.ContactDatas)
    >>> {%>
    >>> <p>
    >>> <label
    >>> for="venue.VenueDetail.ContactLink.ContactDatas[<%=Model.Venue.VenueDetail.ContactLink.ContactDatas.IndexOf(contact)%>].Data">Indoor
    >>> or Outdoor:</label>
    >>> <%=
    >>> Html.TextArea("contact["+Model.Venue.VenueDetail.ContactLink.ContactDatas.IndexOf(contact)+"].Data")%>
    >>> <%=
    >>> Html.ValidationMessage("venue.VenueDetail.ContactLink.ContactDatas["+Model.Venue.VenueDetail.ContactLink.ContactDatas.IndexOf(contact)+"].Data",
    >>> "*")%>
    >>> </p>
    >>> <%} %>
    >>>
    >>> This appears to create the correct html, i.e. the index starts at 0, and
    >>> follows sequentially. (I have checked many times that this is the
    >>> correct
    >>> path to the object/property (intellisense provides the following path:
    >>> Model.Venue.VenueDetail.ContactLink.ContactDatas[0].Data).
    >>>
    >>> When submitting the form it passes back the following in the post:
    >>>
    >>> [reduced for
    >>> brevity].....venue.VenueDetail.ContactLink.ContactDatas%5B0%5D.Data=fred&venue.VenueDetail.ContactLink.ContactDatas%5B1%5D.Data=wilma
    >>>
    >>> However, whilst the ContactDatas object is created, it is not populated
    >>> with
    >>> any ContactData objects.
    >>>
    >>> Should the default model mapper map to models 3 deep, and if so is there
    >>> any
    >>> obvious error in what I have stated above.
    >>>
    >>> If not, then how should I best provide mappings for my ContactData
    >>> objects?
    >>>
    >>> Thanks,
    >>> Richard

    >>

    >
    >
    Cowboy \(Gregory A. Beamer\), May 21, 2009
    #9
  10. RichB

    Guest

    I am having the same issue, Richard. I believe it has to do with a
    collection being an EntitySet<T> rather than a List<T>.

    Allen, does your example from a few days ago work when you change the
    MyData.Customers type from List<MyCustomer> to
    EntitySet<MyCustomer> ??

    I keep running into LOTS of posts around the 'nets describing problems
    with this same type of situation, but so far, not too many solutions
    (unless you consider writing your own model binder to be a viable
    solution!)

    Thanks!
    , May 22, 2009
    #10
  11. RichB

    Guest

    Well, shortly after I posted my reply, I seem to have found a
    workaround that is working in my situation. In my case, I have a
    Company entity that has one to many relationship to Contacts (my Linq-
    to-Sql EntitySet).

    Since it seems that when I change my code from EntitySet<Contact> to
    List<Contact>, the MVC default model binder starts working as expected
    (even though the LTS isn't now), I figured I would provide an
    alternate, "aliased" property to MVC that is of type List<Contact>,
    and sure enough, this seems to work.

    In my Company entity class:

    // This is what LINQ-to-SQL will use:
    private EntitySet<Contact> _Contacts = new EntitySet<Contact>();
    [Association(Storage="_Contacts", OtherKey="CompanyID", ThisKey="ID")]
    public EntitySet<Contact> Contacts
    {
    get { return _Contacts; }
    set { _Contacts.Assign(value); }
    }

    // This is what MVC default model binder (and my View) will use:
    public List<Contact> MvcContacts
    {
    get { return _Contacts.ToList<Contact>(); }
    set { _Contacts.AddRange(value); }
    }

    So now, in my View, I have the following:

    <label>First Name*
    <%= Html.TextBox("Company.MvcContacts[" + i + "].FirstName") %>
    </label>
    <label>Last Name*
    <%= Html.TextBox("Company.MvcContacts[" + i + "].LastName") %>
    </label>


    Seems to work like a charm!

    Best of luck!
    -Mike
    , May 22, 2009
    #11
  12. Hi Richard,

    >I have tried your code and once again it works appropriately. I have sent
    >you an email with a stripped down version of my project which fails in the
    >same way on trying to create a venue.


    >I notice that your Post method processes a FormCollection. Mine processes

    a
    >Venue venue as per the Nerddinner example. I have tried following your
    >FormCollection approach in my project and received the same result.


    >I am happy that using [Bind (Prefix=...)] does solve the issue, and I sent
    >the example to you just out of my interest and hopefully yours.


    I've reproduced this issue. The cause of this problem is, in the set of
    ContactDatas property, the auto generated code by Linq to SQL is:

    _ContactDatas.Assign(value);

    However, in the model updating stage the above set accessor will be
    invoked. The value passed to it is exactly the same object as the
    _ContactDatas. Therefore it will cause _ContactDatas clear itself, as the
    same result of this test:

    EntitySet<ContactData> test = new EntitySet<ContactData>();
    test.Add(new ContactData());
    test.Assign(test);

    //test is empty now

    The reason of above behavior is, in the EntitySet<T>.Assign method, it
    clears all the items of the caller of this method (the test variable in the
    above code) and then loop through the collection that is tend to be
    assigned to the caller collection. Unfortunately it's been cleared out so
    there's no item to add.

    You can try this workaround if you like:

    Controller:
    public ActionResult Create(FormCollection formValues
    //Include Bind and success
    //, [Bind(Prefix="venue.VenueDetail.ContactLink.ContactDatas")]
    EntitySet<ContactData> cont
    )
    {

    Venue venue = new Venue();
    UpdateModel(venue, "Venue", formValues.ToValueProvider());

    //check venue.VenueDetail.ContactLink.ContactDatas[0].Data));
    return RedirectToAction("Create");
    }

    View:

    <%=Html.TextBox("Venue.VenueDetail.ContactLink.ContactDatas[0].Data")%>

    Modle:

    [Association(Name = "ContactLink_ContactData", Storage =
    "_ContactDatas", ThisKey = "ContactLinkId", OtherKey = "ContactLinkId")]
    public EntitySet<ContactData> ContactDatas
    {
    get
    {
    return this._ContactDatas;
    }
    set
    {


    /////////////



    //to work around, add this:
    _ContactDatas = new EntitySet<ContactData>();
    _ContactDatas.Assign(value);
    }
    }


    I personally don't think this behavior of Assign method is good. It'd be
    better to leave the caller collection not changed if the parameter of the
    method is the same object as the caller. You're suggested to submit a
    feedback in our connect site to inform our develop team of this issue.
    They'll investigate it and hope they could provide revised version in the
    next release.

    https://connect.microsoft.com/VisualStudio/Feedback?wa=wsignin1.0

    Regards,
    Allen Chen
    Microsoft Online Support
    Allen Chen [MSFT], May 22, 2009
    #12
  13. Hi Richard,

    >I notice that your Post method processes a FormCollection. Mine processes

    a
    >Venue venue as per the Nerddinner example. I have tried following your
    >FormCollection approach in my project and received the same result.


    >I am happy that using [Bind (Prefix=...)] does solve the issue, and I sent
    >the example to you just out of my interest and hopefully yours.


    >If establishing the reason for the failure requires extensive work, then
    >please feel free to let me know and discard.


    >Have you tried my workaround? Can it work?


    Regards,
    Allen Chen
    Microsoft Online Support
    Allen Chen [MSFT], May 26, 2009
    #13
  14. RichB

    RichB Guest

    Hi Allen,

    Sorry I've been tied up all of last week and not had chance to try our work
    around. I shall probably get to do it tomorrow sometime and will post back
    then.

    Richard


    "Allen Chen [MSFT]" <> wrote in message
    news:...
    > Hi Richard,
    >
    >>I notice that your Post method processes a FormCollection. Mine processes

    > a
    >>Venue venue as per the Nerddinner example. I have tried following your
    >>FormCollection approach in my project and received the same result.

    >
    >>I am happy that using [Bind (Prefix=...)] does solve the issue, and I sent
    >>the example to you just out of my interest and hopefully yours.

    >
    >>If establishing the reason for the failure requires extensive work, then
    >>please feel free to let me know and discard.

    >
    >>Have you tried my workaround? Can it work?

    >
    > Regards,
    > Allen Chen
    > Microsoft Online Support
    >
    RichB, May 27, 2009
    #14
  15. RichB

    RichB Guest

    Thanks Allen, yes your workaround did work as expected, as does the [Bind
    (Prefex = ..)] solution.

    I can't say that I fully understand why the [Bind (Prefix=..)] solution
    works where default binding fails, but For my purposes I am just happy that
    I have solutions which I can apply.

    I have provided feedback on the link you provided, hopefully in the correct
    and understandable way.

    Many thanks, for your help.

    Richard


    "Allen Chen [MSFT]" <> wrote in message
    news:...
    > Hi Richard,
    >
    >>I have tried your code and once again it works appropriately. I have sent
    >>you an email with a stripped down version of my project which fails in the
    >>same way on trying to create a venue.

    >
    >>I notice that your Post method processes a FormCollection. Mine processes

    > a
    >>Venue venue as per the Nerddinner example. I have tried following your
    >>FormCollection approach in my project and received the same result.

    >
    >>I am happy that using [Bind (Prefix=...)] does solve the issue, and I sent
    >>the example to you just out of my interest and hopefully yours.

    >
    > I've reproduced this issue. The cause of this problem is, in the set of
    > ContactDatas property, the auto generated code by Linq to SQL is:
    >
    > _ContactDatas.Assign(value);
    >
    > However, in the model updating stage the above set accessor will be
    > invoked. The value passed to it is exactly the same object as the
    > _ContactDatas. Therefore it will cause _ContactDatas clear itself, as the
    > same result of this test:
    >
    > EntitySet<ContactData> test = new EntitySet<ContactData>();
    > test.Add(new ContactData());
    > test.Assign(test);
    >
    > //test is empty now
    >
    > The reason of above behavior is, in the EntitySet<T>.Assign method, it
    > clears all the items of the caller of this method (the test variable in
    > the
    > above code) and then loop through the collection that is tend to be
    > assigned to the caller collection. Unfortunately it's been cleared out so
    > there's no item to add.
    >
    > You can try this workaround if you like:
    >
    > Controller:
    > public ActionResult Create(FormCollection formValues
    > //Include Bind and success
    > //, [Bind(Prefix="venue.VenueDetail.ContactLink.ContactDatas")]
    > EntitySet<ContactData> cont
    > )
    > {
    >
    > Venue venue = new Venue();
    > UpdateModel(venue, "Venue", formValues.ToValueProvider());
    >
    > //check venue.VenueDetail.ContactLink.ContactDatas[0].Data));
    > return RedirectToAction("Create");
    > }
    >
    > View:
    >
    > <%=Html.TextBox("Venue.VenueDetail.ContactLink.ContactDatas[0].Data")%>
    >
    > Modle:
    >
    > [Association(Name = "ContactLink_ContactData", Storage =
    > "_ContactDatas", ThisKey = "ContactLinkId", OtherKey = "ContactLinkId")]
    > public EntitySet<ContactData> ContactDatas
    > {
    > get
    > {
    > return this._ContactDatas;
    > }
    > set
    > {
    >
    >
    > /////////////
    >
    >
    >
    > //to work around, add this:
    > _ContactDatas = new EntitySet<ContactData>();
    > _ContactDatas.Assign(value);
    > }
    > }
    >
    >
    > I personally don't think this behavior of Assign method is good. It'd be
    > better to leave the caller collection not changed if the parameter of the
    > method is the same object as the caller. You're suggested to submit a
    > feedback in our connect site to inform our develop team of this issue.
    > They'll investigate it and hope they could provide revised version in the
    > next release.
    >
    > https://connect.microsoft.com/VisualStudio/Feedback?wa=wsignin1.0
    >
    > Regards,
    > Allen Chen
    > Microsoft Online Support
    >
    RichB, May 28, 2009
    #15
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Øyvind Isaksen
    Replies:
    1
    Views:
    946
    Øyvind Isaksen
    May 18, 2007
  2. rmn190
    Replies:
    2
    Views:
    2,333
    Arne Vajhøj
    Jan 10, 2008
  3. Paulo
    Replies:
    3
    Views:
    463
    Peter Bromberg [C# MVP]
    Dec 4, 2008
  4. RichB
    Replies:
    2
    Views:
    1,210
    RichB
    Jun 4, 2009
  5. Crazy Cat
    Replies:
    1
    Views:
    672
    schepp
    Sep 3, 2009
Loading...

Share This Page