This is a migrated thread and some comments may be shown as answers.

Posting Json for Update, Create, & Delete actions.

9 Answers 2573 Views
Grid
This is a migrated thread and some comments may be shown as answers.
Matt
Top achievements
Rank 1
Matt asked on 18 Mar 2020, 06:38 PM

I currently have a kendo grid defined using Razor with read & update actions. It seems that, by default, the update action sends data as a url encoded string (application/x-www-form-urlencoded). Is there a way to set the datasource's update/create/delete actions to post json data instead. 

I ask because our grid rows contain nested objects used in business logic and we've found that the model binding process is really slow on a url encoded string, and very fast when we send the data as a json string (tested in a seperate ajax call).

9 Answers, 1 is accepted

Sort by
0
Aleksandar
Telerik team
answered on 20 Mar 2020, 03:06 PM

Hello Matt,

You could configure the Grid for Ajax binding.  When configured for Ajax binding, the Grid for ASP.NET MVC makes Ajax requests when doing paging, sorting, filtering, grouping, or when saving data. A runnable example is available in the demo section for the Grid. You could also check the demo for Popup Editing.

Let me know if you have additional questions.

Regards,
Aleksandar
Progress Telerik

Get quickly onboarded and successful with your Telerik UI for ASP.NET MVC with the dedicated Virtual Classroom technical training, available to all active customers.
0
Matt
Top achievements
Rank 1
answered on 20 Mar 2020, 05:25 PM

Hey Aleksandar, thanks for your reply! 

We do currently have the grid bound ajax. When we execute a read request the data is sent down from the server as json. However when the Update action is called the data is posted to the server as a form encoded url. We would like to have the data posted as a json encoded string, in a similar fashion as if the following javascript code had been called (but with only updated rows)

var gridData = {models : $("#Grid").getKendoGrid().dataSource.data()};
var json = JSON.stringify(gridData);
$.ajax({
        type: 'POST',
        url: "/ControllerName/UpdateUrl",
        data: json,
        contentType: 'application/json',
    });

 

Thanks!

 

 

0
Aleksandar
Telerik team
answered on 24 Mar 2020, 01:00 PM

Hi Matt,

In this scenario, you would have to convert to Custom DataSource as shown in this demo and customize the parameterMap function. This function is used to convert the request parameters in a format that is suitable for the remote service. The Grid configuration would be similar to the below:

.Transport(t=> {
... t.Update(u => u.Action("UpdateAction", "MyController").DataType("json").Type(HttpVerbs.Post)); t.ParameterMap("parameterMap");
... })

 

function parameterMap(data, operation) {
        if (operation === "update") {
            returnJSON.stringify(data);
            } 
   ...
}

 

I hope this helps.

Regards,
Aleksandar
Progress Telerik

Progress is here for your business, like always. Read more about the measures we are taking to ensure business continuity and help fight the COVID-19 pandemic.
Our thoughts here at Progress are with those affected by the outbreak.
0
Matt
Top achievements
Rank 1
answered on 24 Mar 2020, 02:14 PM

Thanks for the reply, so I've implemented the change you mentioned, and now it seems that when my grid calls read I get back json data, but it doesn't bind correctly to the grid (I get one empty row).

Chrome debugger shows the data coming back, but when I call sender.dataSource.data() in the data bound event of the grid I get one object that has null values for all of my defined model fields, and the following Parameters. Data containing all of my rows, Total, an integer count of all of the rows, and then two null fields Aggregate Results and Errors. 

On the server in my controller I am calling .ToDataSourceResult. Is that not supported in a custom datasource? Or do I have to manually map back somehow?

I'm attatching my data source configuration below: 

DataSource:

.DataSource(dataSource => dataSource.Custom()
        .Group(g => g.Add(c => c.LocationName))
        .Batch(true)
        .ServerAggregates(false).ServerFiltering(false).ServerGrouping(false).ServerPaging(false).ServerSorting(false)
        .PageSize(100)
        .Events(e => e.Error("errorEvent"))
        .Transport(transport =>
        {
            transport.Read(read =>
            {
                read.Action("Read", "CustomPriceEntry")
                        .Data("priceEntryReadData")
                        .Type(HttpVerbs.Get);
            });
            transport.Update(update =>
            {
                update.Action("SaveProgress", "CustomPriceEntry")
                        .Data("priceEntryReadData")
                        .DataType("json")
                        .Type(HttpVerbs.Post);
                 
            });
            transport.ParameterMap("parameterMap");
        })
        .Schema(schema => schema.Model(model =>
        {
            model.Id(id => id.QuoteConfigurationMappingId);
            model.Field(field => field.SupplierCounterPartyName).Editable(false);
            model.Field(field => field.CarrierCounterPartyName).Editable(false);
            model.Field(field => field.InternalCounterPartyName).Editable(false);
            model.Field(field => field.ExternalCounterPartyName).Editable(false);
            model.Field(field => field.NextEffective).Editable(false);
            model.Field(field => field.QuoteConfigurationMappingId).Editable(false);
            model.Field(field => field.LocationName).Editable(false);
            model.Field(field => field.CommodityName).Editable(false);
            model.Field(field => field.HasAlert).Editable(false);
            model.Field(field => field.ProductName).Editable(false);
            model.Field(field => field.SpreadAmount).Editable(true);
            model.Field(field => field.IsSpread).Editable(true);
            model.Field(field => field.TimeZoneId).Editable(false);
            model.Field(field => field.CurrentCost).Editable(false);
            model.Field(field => field.LatestDate).Editable(false);
            model.Field(field => field.ProposedPrice).Editable(true);
            model.Field(field => field.LatestDailyPostedPrice).Editable(false);
            model.Field(field => field.LatestDailyAdjustment).Editable(false);
            model.Field(field => field.LatestDailyPostingTimeString).Editable(false);
            model.Field(field => field.LatestIntraDayPrice).Editable(false);
            model.Field(field => field.LatestIntraDayAdjustment).Editable(false);
            model.Field(field => field.LatestIntraDayPostingTimeString).Editable(false);
        })
 
))

 

Parameter Map Function: 

function parameterMap(data, operation) {
    if (operation === "update") {
        return JSON.stringify(data);
    }
    return data;
}

 

 

 

 

 

0
Aleksandar
Telerik team
answered on 26 Mar 2020, 01:25 PM

Hi Matt,

The ToDataSourceResult method used in the Controller action method wraps the data passed to the view in a Data object. In the scenario when CustomDataSource is used you would need to update the Schema to specify which part of the response contains the data:

.Schema(schema => {
    schema.Model(.....);
    schema.Data("Data");
schema.Total("Total");
})

If you are passing back Aggregate results, Total and Errors you would need to configure those options in the Schema as well.

Regards,
Aleksandar
Progress Telerik

Progress is here for your business, like always. Read more about the measures we are taking to ensure business continuity and help fight the COVID-19 pandemic.
Our thoughts here at Progress are with those affected by the outbreak.
0
Matt
Top achievements
Rank 1
answered on 07 Apr 2020, 07:34 PM

Ok, so I added those lines and now the grid is binding to data correctly. Now I'm running into the case where when I stringify the data in the parameterMap function the list of models allong with any additional data I pass doesn't bind on the server. 

Looking in chrome debugger it seems that my call to the update function is still sending "application/x-www-form-urlencoded" as the content type even though the DataType on the update function is set to "json" (see above). Is this incorrect? I tried setting the DataType to application/json but that also did not work.

0
Aleksandar
Telerik team
answered on 09 Apr 2020, 12:44 PM

Hi Matt,

With batch updates enabled you will need to add the Bind Attribute to specify the prefix that should be used to bind your complex object:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Update([DataSourceRequest] DataSourceRequest request, [Bind(Prefix = "models")]IEnumerable<MyViewModel> updatedModels)
{
...
}

Check this demo for additional details on batch updating.

Let me know if this suggestion helps. If not please share the controller configuration of the update method?

Regards,
Aleksandar
Progress Telerik

Progress is here for your business, like always. Read more about the measures we are taking to ensure business continuity and help fight the COVID-19 pandemic.
Our thoughts here at Progress are with those affected by the outbreak.
0
Matt
Top achievements
Rank 1
answered on 09 Apr 2020, 03:48 PM

Hey, sorry for the lack of clarity in my part. 

So this is what the update method signature looks like: 

SaveProgress([DataSourceRequest] DataSourceRequest request, [Bind(Prefix = "models")]IEnumerable<PriceEntryRowBase> rows, int configurationId)

 

and what's actually happening is that we're not binding any of the parameters, which results in an error because MVC can't find the configurationId parameter. 

If I make configurationId nullable then I get null values for both the models parameter and the configurationId parameter. 

I wanted to make sure that the data I was sending was correct, so I modified my parameter mapping function to store off its reults when I call the grid's normal SYNC method

var parameterMapResults;
function parameterMap(data, operation) {
    if (operation === "update") {
        parameterMapResults = JSON.stringify( {
                 models:data.models,
                 configurationId: data.configurationId
         });
        return parameterMapResults;
 
    }
    return data;
}

 

After calling save and getting an error, I can then call the following code that sends the same set of mapped parameters but via explicit ajax. When I do this everything binds correctly.

 

$.ajax({
       type: 'POST',
       url: "@Url.Action("SaveProgress", "CustomPriceEntry")",
       data: parameterMapResults,
       contentType: 'application/json',
});

 

What I've noticed, and I think this is the issue, is that the content type on the ajax call made via the grid sync method is still set to application/x-www-form-urlencoded (reported in the network tab of Chrome's dev), even though I have explicitly set the update method to send json (see my previously posted transport section). The actual json content does appear in the document body, because if I manually open up the input stream belonging to the .Net HttpRequest in my controller using something like the following, the data is all there.

public static IDictionary<string, object> RequestBodyParameters(this HttpRequestBase request)
{
    string json = request.GetDocumentContents();
    if (!json.IsNullOrWhiteSpace())
    {
        return JsonConvert.DeserializeObject<ExpandoObject>(json) as IDictionary<string, object>;
    }
 
    return new Dictionary<string, object>();
}

 

I think that, because of how the content type is set, MVC's model binder looks for url encoded parameters in form data instead of in the posted body, so nothing binds.

Am I missing something in my transport definition? Or is there a bug in my version (Kendo UI v2018.3.1017)? 

 

Thanks!

 

 

 

0
Aleksandar
Telerik team
answered on 13 Apr 2020, 10:15 AM

Hello Matt,

Thank you for the additional details. I noticed I have misled you in one of my previous responses and I sincerely apologize for that. The DataType configuration specifies the result expected from the server, but not the type that would be sent. Thus your observation is indeed correct - by default the content type is set to "application/x-www-form-urlencoded". To configure the content-type HTTP header sent to the server use the .ContentType() configuration option:

transport.Update(update =>
            {
                update.Action("SaveProgress", "CustomPriceEntry")
                        .Data("priceEntryReadData")
                        .ContentType("application/json")
                        .DataType("json")
                        .Type(HttpVerbs.Post);
                 
            });

Setting the ContentType to "application/json" should resolve the issue.

Regards,
Aleksandar
Progress Telerik

Progress is here for your business, like always. Read more about the measures we are taking to ensure business continuity and help fight the COVID-19 pandemic.
Our thoughts here at Progress are with those affected by the outbreak.
Tags
Grid
Asked by
Matt
Top achievements
Rank 1
Answers by
Aleksandar
Telerik team
Matt
Top achievements
Rank 1
Share this question
or