PDA

View Full Version : [CLOSED] Getting raw JSON data in MVC Controller Post



anup
Sep 03, 2014, 11:53 AM
Hi,

This follows on from this thread:
http://forums.ext.net/showthread.php?42721

That previous thread was about extending the TreeStore class to support rejectChanges() and commitChanges(), to allow the use of an AjaxProxy in a more elegant way. It also used a custom JsonWriter so that I could control the amount of data that gets sent back to the server when committing some changes (in my particular case, delete of tree nodes).

A follow up from that is a variation of the example use in that post. I switched from pointing my Destroy API to an ASHX to point to an MVC Controller instead. However, I was struggling to find the best way to get both the raw data and the ExtraParameters to come through. In the end I had to settle for the solution I mention below, so this post is asking if there is a cleaner way I missed out?

ASPX example code [same as previous thread but with a different Destroy API URL]:



<%@ Page Language="C#" %>
<%@ Register TagPrefix="cc1" Namespace="Ext.Net2.Tests.Trees.CustomWriter" Assembly="Ext.Net2.Tests" %>

<!DOCTYPE html>
<html>
<head runat="server">
<title>Title</title>

<ext:ResourcePlaceHolder Mode="ScriptFiles" />

<script>
// See http://forums.ext.net/showthread.php?42721 for reasons to extend/override some of these classes

// ################################################## ###################
// Custom Writer for category to control what data is sent to the server
// ################################################## ###################
Ext.define('Ext.ux.CategoryNodeWriter', {
extend: 'Ext.data.writer.Json',
alias: 'writer.categorynodewriter',

getRecordData: function(record, operation) {
console.log("getRecordData", record, record.getOwnerTree());
return operation.action === 'destroy' ? { 'id': record.get('id') } : this.callParent(arguments);
}
});

/**
* @author Johan Vandeplas
*/
Ext.define('My.overrides.data.NodeInterface', {
override: 'Ext.data.NodeInterface',
statics: {
getPrototypeBody: function () {
var me = this,
protBody = me.callParent(arguments),
removeFn = protBody.remove;


protBody.remove = function () {
var me = this;
me.removedFromNode = me.parentNode;
me.removedFromIdx = me.parentNode.indexOf(me);
removeFn.apply(this, arguments);
};
return protBody;
}
}
});

Ext.define('My.overrides.data.TreeStore', {
override: 'Ext.data.TreeStore',

/**
* removes node or an array of nodes from its parent.
* @param {Ext.data.NodeInterface/[Ext.data.NodeInterface]}nodes
* @param {Boolean} [destroy=false] True to destroy the node upon removal.
*/
remove: function (nodes, destroy) {
nodes = Ext.isArray(nodes) ? nodes : [nodes];
Ext.each(nodes, function (node) {
node.remove(destroy === true);
});
},

/**
* @returns {Array}
*/
getRejectRecords: function () {
return Ext.Array.push(this.getNewRecords(), this.getUpdatedRecords(), this.getRemovedRecords());
},

/**
* brings the store to previous state (rejects all uncommitted changes)
*/
rejectChanges: function () {
var me = this,
recs = me.getRejectRecords(),
len = recs.length,
i = 0,
rec;


for (; i < len; i++) {
rec = recs[i];
rec.reject();
if (rec.phantom) {
me.remove(rec);
}
}


// Restore removed records back to their original positions
recs = me.removed;
len = recs.length;
for (i = len - 1; i >= 0; i--) {
rec = recs[i];
rec.removedFromNode.insertChild(rec.removedFromIdx , rec);
delete rec.removedFromNode;
delete rec.removedFromIdx;
rec.reject();
}


// Since removals are cached in a simple array we can simply reset it here.
// Adds and updates are managed in the data MixedCollection and should already be current.
me.removed.length = 0;
},

/**
* Commits all Records with {@link #getModifiedRecords outstanding changes}. To handle updates for changes,
* subscribe to the Store's {@link #event-update update event}, and perform updating when the third parameter is
* Ext.data.Record.COMMIT.
*/
commitChanges : function(){
var me = this,
recs = me.getModifiedRecords(),
len = recs.length,
i = 0;

for (; i < len; i++){
recs[i].commit();
}

// Since removals are cached in a simple array we can simply reset it here.
// Adds and updates are managed in the data MixedCollection and should already be current.
me.removed.length = 0;
},
});
</script>
</head>
<body>
<ext:ResourceManager runat="server" />

<ext:TreePanel ID="TreePanel1" runat="server" Width="300" Height="200">
<TopBar>
<ext:Toolbar runat="server">
<Items>
<ext:Button Text="Delete" Icon="Delete">
<Listeners>
<Click Handler="
var tree = #{TreePanel1},
node = tree.getSelectionModel().getSelection()[0];

if (!node) return;
node.remove();
tree.getStore().sync({
failure: function(batch) {
tree.getStore().rejectChanges();
},
success: function() {
tree.getStore().commitChanges();
}
});
" />
</Listeners>
</ext:Button>
</Items>
</ext:Toolbar>
</TopBar>
<Root>
<ext:Node Text="Tasks" Expanded="true">
<Children>
<ext:Node Icon="Folder" Text="Delete this 0" Leaf="true">
<CustomAttributes>
<ext:ConfigItem Name="id" Value="categoryNode0" Mode="Value" />
<ext:ConfigItem Name="task" Value="Project: Shopping" Mode="Value" />
<ext:ConfigItem Name="user" Value="Tommy Maintz" Mode="Value" />
</CustomAttributes>
</ext:Node>
<ext:Node Icon="Folder" Text="Delete this 1" Leaf="true">
<CustomAttributes>
<ext:ConfigItem Name="id" Value="categoryNode1" Mode="Value" />
<ext:ConfigItem Name="task" Value="Project: Shopping" Mode="Value" />
<ext:ConfigItem Name="user" Value="Tommy Maintz" Mode="Value" />
</CustomAttributes>
</ext:Node>
<ext:Node Icon="Folder" Text="Delete this 2" Leaf="true">
<CustomAttributes>
<ext:ConfigItem Name="id" Value="categoryNode2" Mode="Value" />
<ext:ConfigItem Name="task" Value="Project: Shopping" Mode="Value" />
<ext:ConfigItem Name="user" Value="Tommy Maintz" Mode="Value" />
</CustomAttributes>
</ext:Node>
</Children>
</ext:Node>
</Root>
<Store>
<ext:TreeStore runat="server" AutoSync="false" ShowWarningOnFailure="false">
<Model>
<ext:Model runat="server">
<Fields>
<ext:ModelField Name="task" />
<ext:ModelField Name="user" />
</Fields>
</ext:Model>
</Model>
<Proxy>
<ext:AjaxProxy Url="TreeLoader.ashx">
<API Destroy="~/Tree/Delete" />
<Writer>
<cc1:CustomWriter />
</Writer>
<ExtraParams>
<ext:Parameter Name="example" Value="1" Mode="Raw" />
</ExtraParams>
</ext:AjaxProxy>
</Proxy>
</ext:TreeStore>
</Store>
</ext:TreePanel>
</body>
</html>


Controller:



public class TreeController : Controller
{
public ActionResult Delete(int example)
{
Request.InputStream.Seek(0, SeekOrigin.Begin);
string jsonData = new StreamReader(Request.InputStream).ReadToEnd();

// do stuff with jsonData

return this.Direct();
}
}


While the above works, is it the best way, or is there something cleaner? I tried using StoreDataHandler like this:



public class TreeController : Controller
{
public ActionResult Delete(StoreDataHandler handler)
{
return this.Direct();
}
}


But, in the debugger, I could not see either the "example" extra parameter in the handler, nor could I extract the post data (the JsonData property was empty). Did I miss something in the setup of the proxy or something else to get StoreDataHandler to work, or am I unable to use it in this situation?

Thanks!

anup
Sep 03, 2014, 12:58 PM
So it looks like I can improve this by doing this:



<Writer>
<cc1:CustomWriter Root="data" Encode="true" />
</Writer>


So it is not related to my CustomWriter (that just clutters the sample, sorry!) but ensuring I set both Root to "data" (looking at code, it can't be anything else) and setting Encode to true to ensure the JSON Data is sent as an HTTP post parameter, rather than the entire POST body being JSON.

When I do this, I can then make the controller like this:



public ActionResult Delete(StoreDataHandler handler, int example)
{
... etc...
}


And handler.JsonData will be set.

This is a bit cleaner, I think... (with the tradeoff that the raw HTTP POST is no longer pure JSON, but I think that is okay in this situation).

You can mark this as closed, unless you have further improvement suggestions :)

Daniil
Sep 03, 2014, 4:35 PM
Hi Anup,

It looks I don't see any room for improvements... Though, I will re-look tomorrow morning with a fresh head:)

Daniil
Sep 04, 2014, 4:04 PM
I've reviewed again and the answer is still the same. Root="data" and Encode="true" are good to use in your scenario.

anup
Sep 04, 2014, 4:25 PM
Thanks for taking the time to confirm. Much appreciated!