asp.netjsonvb.netangularjsmodelbinder

.Net Model binding a dataset via JSON post


I am having a heck of a time with this one. I must be missing something.

I am posting a complex model via Angular/JSON to a .NET controller. When posting it back to the server the DefaultModelBinder only partially binds to the model (the simple values such as int (ID) and string (title) are fine). The "ListItems" dataset seems to be ignored as well as the "ProductsList" datatable which are part of the ProjectDetails model. From what I am reading, for complex objects the DefaultModelBinder takes a second pass at the JSON recursively and then maps the objects that it can find. I have tried a number of solutions that I found through searching StackOverflow but to no avail. I think I have just lost my perspective at this point. Here is what I have. Any help would be greatly appreciated.

The Model

    Public Class ProjectDetails

    Private _ListItems As DataSet
    <JsonProperty("ListItems")> _
    Public Property ListItems() As DataSet
        Get
            Return _ListItems
        End Get
        Set(ByVal value As DataSet)
            _ListItems = value
        End Set
    End Property

    Private _ProductsList As DataTable
    Public Property ProductsList As DataTable
        Get
            Return _ProductsList
        End Get
        Set(value As DataTable)
            _ProductsList = value
        End Set
    End Property

    Private _imageID As Int32 = 0
    Public Property imageID() As Int32
        Get
            Return _imageID
        End Get
        Set(ByVal value As Int32)
            _imageID = value
        End Set
    End Property

    Private _Title As String = String.Empty
    Public Property Title() As String
        Get
            Return _Title
        End Get
        Set(ByVal value As String)
            _Title = value
        End Set
    End Property
    End Class

The Action Controller

    <Authorize()> _
    <AcceptVerbs(HttpVerbs.Post)> _
    <ValidateInput(False)> _
    Async Function ProjectUpdate(ByVal d As ProjectDetails) As Task(Of JsonResult)
    'send results to database here
    Save(d)
    return json(true)
    End Function

Header

    POST http://localhost:51110/projectUpdate HTTP/1.1
    Host: localhost:51110
    Connection: keep-alive
    Content-Length: 10119
    Accept: application/json, text/plain, */*
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36
    Content-Type: application/json;charset=UTF-8
    Accept-Encoding: gzip, deflate
    Accept-Language: en-US,en;q=0.8

JSON data posted

    {"ListItems":

    {"ProjectStatusCodes":[{"id":1,"ProjectStatusTx":"Stage1 - Under Construction"},{"id":3,"ProjectStatusTx":"Stage3 - Project Released"},{"id":4,"ProjectStatusTx":"Project Closed"}],

    "ProductsList":[
    {"id":2336,"Name":"Product1","Description":"This is a description","$$hashKey":"00K"},
    {"id":2337,"Name":"Product2","Description":"This is a second description","$$hashKey":"00M"}],

    "imageID":345,
    "Title":"books", }
    }

AngularJS code

    $scope.update = function (formData) {
    $http({
        method: 'POST',
        url: '/projectbuilder/projectUpdate',
       contentType: 'application/json; charset=utf-8',
        data: JSON.stringify($scope.formData)
    }).success(function (data, status, headers, config) {
        $scope.codeStatus = status;
    }).error(function (data, status, headers, config) {
        $scope.codeStatus = status || "Request failed";

    });

HTML template with form values

    <div ng-repeat="ProductsList in formData.ListItems.ProductsList">
        <div>
          <div>
            <div>
               <input value="{{ProductsList.Title}}" name="ProductsList.Title" ID="ProductsList.Title" ng-model="ProductsList.Title" >
               <br />
               <input value="{{ProductsList.ImageID}}" name="ProductsList.ImageID" ID="ProductsList.ImageID" ng-model="ProductsList.ImageID" >
            </div>
         </div>
    </div>

Behavior The JSON post works fine to the controller and it binds the ImageID and Title. The ListItems dataset contains no tables and the ProductsList datatable in the ListItems dataset does not exist.

NOTE: The ProjectStatusCodes data in the JSON is not mapped back to the model as it was used only for display purposes on the form.


Solution

  • OK. I figured it out. It seems as if JSON.NET does a great job of serializing the object when sending making sure that it is fully JSON compliant. The bad news is that it removes most of the things that make a datatable de-serializable when sending it back to the .NET application.

    What I ended up doing was catching the JSON string on postback and accessing the serialized datatable data separately using the JSON.NET fragment serialization which you can find here. http://www.newtonsoft.com/json/help/html/SerializingJSONFragments.htm I left the rest of the object "as is" and grabbed the datatable from the JSON string and did a de-serialization manually with this technique. The example in the article was very informative.

    CODE TO READ THE JSON STRING

        Dim req As Stream = Request.InputStream
        req.Seek(0, System.IO.SeekOrigin.Begin)
        Dim jsonstring As String = New StreamReader(req).ReadToEnd()
    

    CODE TO PARSE IT OUT AND DESERIALIZE

        Dim ProductsList As JObject = JObject.Parse(jsonstring)
        Dim results As IList(Of JToken) = ProductsList("ListItems")("ProductsList").Children().ToList()
    
        Console.Write(results)
    

    Not terribly elegant but it works.