PDA

View Full Version : [CLOSED] Async Store with AJAX Proxy (ASHX) does not update store on client side after successful request



anup
Oct 14, 2012, 10:07 PM
Hi,

In the Examples Explorer is this neat example of autosync:
http://examples2.ext.net/#/GridPanel/Update/AutoSave/

If I edit it locally to force the updated data to be completely different (simulating some scenario where your update causes server to create new values for whatever reason), then those new values (which the user did not type) is correctly rendered to the screen once the store update response is successfully returned.

For reference all I did in the HandleChanges method in the above example is changed the following to update the "Last" property:



if (e.Action == StoreAction.Update)
{
foreach (TestPerson updated in persons)
{
updated.Last += " blah blah"; // new line
this.UpdatePerson(updated);
e.ResponseRecords.Add(updated);
}
}


This means if I update the Last field (or any other field in that row) the Last field will always have " blah blah" added to the end from the server. That is great.

So, now I took the same example and used an AJAX proxy instead to an ASHX to do the same thing but this time it doesn't work the same way (the response from the server seems to be ignored)

Here is some sample code.

1) TestPerson.cs



public class TestPerson
{
public int? Id { get; set; }
public string Email { get; set; }
public string First { get; set; }
public string Last { get; set; }

public static List<TestPerson> GetPeople
{
get
{
return new List<TestPerson>
{
new TestPerson{Id=1, Email="fred@flintstone.com", First="Fred", Last="Flintstone"},
new TestPerson{Id=2, Email="wilma@flintstone.com", First="Wilma", Last="Flintstone"},
new TestPerson{Id=3, Email="pebbles@flintstone.com", First="Pebbles", Last="Flintstone"},
new TestPerson{Id=4, Email="barney@rubble.com", First="Barney", Last="Rubble"},
new TestPerson{Id=5, Email="betty@rubble.com", First="Betty", Last="Rubble"},
new TestPerson{Id=6, Email="bambam@rubble.com", First="BamBam", Last="Rubble"}
};
}
}
}


2. Cut down ASPX



<%@ Page Language="C#" %>

<!DOCTYPE html>
<html>
<head runat="server">
<title>Grid with AutoSave - Ext.NET Examples</title>
</head>
<body>
<form runat="server">
<ext:ResourceManager runat="server" />

<h1>Grid with AutoSave</h1>

<p>An Error has been simulated on the server-side: Attempting to update a record having ODD-numbered id will generate this errror. This error can be handled by listening to the "exception" event upon your Store.</p>

<ext:Store ID="Store1" runat="server" AutoSync="true">
<Model>
<ext:Model runat="server" IDProperty="Id" Name="Person">
<Fields>
<ext:ModelField Name="Id" Type="Int" UseNull="true" />
<ext:ModelField Name="Email" />
<ext:ModelField Name="First" />
<ext:ModelField Name="Last" />
</Fields>
</ext:Model>
</Model>
<Proxy>
<ext:AjaxProxy Url="TestPersons/Get.ashx">
<Reader>
<ext:JsonReader Root="Data" TotalProperty="TotalRecords" />
</Reader>
<Writer>
<ext:JsonWriter Root="Data" Encode="true" WriteAllFields="false" />
</Writer>
<API Update="TestPersons/Update.ashx" />
</ext:AjaxProxy>
</Proxy>
</ext:Store>

<ext:GridPanel
ID="GridPanel1"
runat="server"
Icon="Table"
Frame="true"
Title="Users"
Height="400"
Width="500"
StoreID="Store1"
StyleSpec="margin-top: 10px">
<ColumnModel>
<Columns>
<ext:Column runat="server" Text="ID" Width="40" DataIndex="Id" />

<ext:Column runat="server" Text="Email" Flex="1" DataIndex="Email">
<Editor>
<ext:TextField runat="server" />
</Editor>
</ext:Column>

<ext:Column runat="server" Text="First" Flex="1" DataIndex="First">
<Editor>
<ext:TextField runat="server" />
</Editor>
</ext:Column>

<ext:Column runat="server" Text="Last" Flex="1" DataIndex="Last">
<Editor>
<ext:TextField runat="server" />
</Editor>
</ext:Column>

<ext:CommandColumn runat="server" Width="70">
<Commands>
<ext:GridCommand Text="Reject" ToolTip-Text="Reject row changes" CommandName="reject" Icon="ArrowUndo" />
</Commands>
<PrepareToolbar Handler="toolbar.items.get(0).setVisible(record.dirty);" />
<Listeners>
<Command Handler="record.reject();" />
</Listeners>
</ext:CommandColumn>
</Columns>
</ColumnModel>

<Plugins>
<ext:CellEditing runat="server" />
</Plugins>
</ext:GridPanel>
</form>
</body>
</html>


3. Get ASHX:



public class Get : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "application/json";

var data = TestPerson.GetPeople;
var paging = new Paging<TestPerson>(data, data.Count);

context.Response.Write(JSON.Serialize(paging));
}

public bool IsReusable
{
get { return false; }
}
}


4. Update.ashx



public class Update : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
var response = new StoreResponseData();
var dataHandler = new StoreDataHandler(context);

List<Dictionary<string, string>> data = dataHandler.ObjectData<Dictionary<string, string>>();

foreach (Dictionary<string, string> list in data)
{
try
{
// normally update system with list values

// as example only:
if (list.ContainsKey("Last"))
{
list["Last"] += " blah blah blah";
}

// return the data as response
response.Data = JSON.Serialize(data);
}
catch (Exception e)
{
// logging, etc.
response.Success = false;
response.Message = e.Message;
}
}

response.Return();
}

public bool IsReusable
{
get { return false; }
}
}


Notice in the update I am using Dictionary<string, string> rather than strongly typed TestPerson - this is because I have set WriteAllFields="false" in the JsonWriter - using true seems to have the same result.

The main difference I notice is in the JSON format of the response. The ExamplesExplorer, which uses the "OnBeforeStoreChanged" event on the Store, and code-behind produces this kind of JSON response:


{serviceResponse:{success:true,data:{data:[{"Id":4,"Email":"barney@rubble.com","First":"Barney","Last":"Rubbles blah blah"}]}}}

Whereas with the ASHX approach I get this:


{data:[{"Id":"5","Email":"betty@rubble.com","First":"Betty","Last":"Rubbles blah blah blah"}]}

I could not see how to get my response data to match the "OnBeforeStoreChanged" version - at least not just using the StoreResponseData class in Update.ashx? Have I missed something (probably obvious!) ?

Daniil
Oct 15, 2012, 8:51 AM
Hi Anup,

You set

<ext:JsonReader Root="Data" TotalProperty="TotalRecords" />
and use the Paging class in the Get handler. That is OK.

But this Reader is also used to parse a sync response. You use the StoreResponseData class to generate a sync response. But StoreResponseData uses lowercase "data" for serialized. So, the Reader with Root="data" just doesn't retrieve any records for a response.

For example, using this

context.Response.Write(JSON.Serialize(new
{
Data = JSON.Serialize(data)
}));
in the Update handler will cause it working.

Otherwise, you can change the Root to "data" and don't use the Paging class in the Get handler to avoid uppercase "Data".

Well, I can state two points here.

1. We, probably, have to use the same "data" or "Data" for all our store response types, i.e. Paging, StoreResponseData, StoreResult (seems, I didn't miss anything).

2. Vladimir recently said that Sencha implemented a feature that allows to put in a response some meta data that will affect on parsing. For example, you can pass some root for the Reader in a response and the Reader will use this root to parse this response. So, I think, we should extend our StoreResponseData, StoreResult and, maybe, Paging as well, with an additional property called, for example, Meta. Maybe, Vladimir is already working on this functionality.

anup
Oct 15, 2012, 10:41 AM
Hi,

Thanks for the suggestions. Using the serialization code in Get.ashx AND changing the Reader's root property to "data" worked (changed Writer's root property as well to "data" just for consistency). By the way, I found I had to set the anonymous Data property to lowercase "data" in your serialization code suggestion too - otherwise read would not work (on the initial page load).

While it now works, I think my main problem with the solution, potentially, is that realistically I would want to use paged data (in realistic cases - I probably cut down the sample too much).

So I would probably also have to set the TotalRecords property manually as well?

In essence I guess I am really creating almost the same thing as a Paging<T> without actually being able to use Paging<T>? Only in this scenario...

So maybe option 1) that you mention (about same "data" or "Data" everywhere) is what I would need in this case so I can continue using Paging<T>?

Daniil
Oct 15, 2012, 11:05 AM
We just discussed it with Vladimir and decided to get lowercase "data" during serialization a Paging class instance.

So, you (and others) will need to change "Data" to "data" for the Reader Root.

And, in a web service case return paging.SerializationObject (a new property) instead of just a Paging instance. If return just a paging instance, it will be still serialized with Data/TotalRecords.

By the way, regarding TotalRecords. It will be serialized as "total" now. It is default value of Reader TotalProperty. So, no need to set this property for the Reader.

Thank you for the question that led us to apply these changes. It adds the consistency in Ext.NET.

Daniil
Oct 15, 2012, 11:26 AM
Vladimir committed the change, revision #4412. Please report if will face to any troubles with it.

anup
Oct 15, 2012, 9:33 PM
Thanks. The update seems to work for my first example in this post (once I make the changes - like "data" and using .SerializationObject).

Now the really odd thing is another example almost identical does not seem to work - I have tried it in the same project where the above example works, as well as another project. I've probably been staring at this too long but I can't seem to see why it is not working when the first one does - ASHX handlers for Get and Update are virtually identical, and the Store's Proxy definition is the same... I am sure I have missed something?

In the Update ashx I have forced the Company and Price to be amended when received, so I expect them to reflect on the grid once synced, but they do not update...

ASPX:



<%@ Page Language="C#" %>
<!DOCTYPE html>
<html>
<head runat="server">
<title>Simple Grid - Ext.NET Examples</title>

<style type="text/css">
body { padding:10px; }
</style>
</head>
<body>
<ext:ResourceManager runat="server" />

<ext:Store ID="Store1" runat="server" AutoSync="true">
<Model>
<ext:Model runat="server" IDProperty="Id">
<Fields>
<ext:ModelField Name="Company" Mapping="Name" />
<ext:ModelField Name="Price" Type="Float" />
</Fields>
</ext:Model>
</Model>
<Proxy>
<ext:AjaxProxy Url="FinancialData.ashx">
<Reader>
<ext:JsonReader Root="data" />
</Reader>
<Writer>
<ext:JsonWriter Root="data" Encode="true" WriteAllFields="false" />
</Writer>
<API Update="FinancialDataUpdate.ashx" />
</ext:AjaxProxy>
</Proxy>
</ext:Store>

<ext:GridPanel runat="server" Title="Simple Grid" Width="500" Height="290" StoreID="Store1">
<ColumnModel>
<Columns>
<ext:Column Text="Id" DataIndex="Id" Width="25" />
<ext:Column Text="Company" DataIndex="Company" Flex="1">
<Editor>
<ext:TextField runat="server" />
</Editor>
</ext:Column>
<ext:NumberColumn Text="Price" DataIndex="Price" Width="75">
<Renderer Format="UsMoney" />
<Editor>
<ext:NumberField runat="server" />
</Editor>
</ext:NumberColumn>
</Columns>
</ColumnModel>
<Plugins>
<ext:CellEditing runat="server" />
</Plugins>
</ext:GridPanel>
</body>
</html>


C# - Sample data class:



public class CompanyData
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }

public CompanyData(int id, string name, double price)
{
Id = id;
Name = name;
Price = price;
}

public static List<CompanyData> GetData()
{
return new List<CompanyData>
{
new CompanyData(1, "3m Co", 71.72),
new CompanyData(2, "Alcoa Inc", 29.01),
new CompanyData(3, "Altria Group Inc", 83.81),
new CompanyData(4, "American Express Company", 52.55),
new CompanyData(5, "American International Group, Inc.", 64.13),
new CompanyData(6, "AT&T Inc.", 31.61)
};
}
}


3. ASHX for the Get:



public class FinancialData : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "application/json";

var data = CompanyData.GetData();
var pagedData = new Paging<CompanyData>(data, data.Count);

context.Response.Write(JSON.Serialize(pagedData.Se rializationObject));
}

public bool IsReusable
{
get { return false; }
}
}


4. ASHX for the Update:



public class FinancialDataUpdate : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
var response = new StoreResponseData();
var dataHandler = new StoreDataHandler(context);

List<Dictionary<string, string>> data = dataHandler.ObjectData<Dictionary<string, string>>();

foreach (Dictionary<string, string> list in data)
{
try
{
// update system with list values

// as example only:
if (list.ContainsKey("Price"))
{
list["Price"] = "10.00";
}
if (list.ContainsKey("Company"))
{
list["Company"] += " blah blah blah";
}

// return the data as response
response.Data = JSON.Serialize(data);
}
catch (Exception e)
{
// logging, etc.
response.Success = false;
response.Message = e.Message;
}
}

response.Return();
}

public bool IsReusable
{
get { return false; }
}
}


Can you spot it? I can't believe this can be a bug because in principle it seems quite similar to the earlier example which now works!

Vladimir
Oct 15, 2012, 10:08 PM
First, SerializationObject is not required if you use JSON.Serialize.
Paging class contains instructions for Newtonsoft.Json how to properly serialize, thefore just use


JSON.Serialize(pagedData)


or


pagedData.Serialize()


About the issue,
- Company field. You use Mapping="Name" therefore in sync response you have to use Name instead Company


if (list.ContainsKey("Company"))
{
list["Name"] = list["Company"].ToString() + " blah blah blah";
list.Remove("Company");
}


- Price field, price is floart but you set string. Change dictionary to <string, object> and set a number


if (list.ContainsKey("Price"))
{
list["Price"] = 10;
}

anup
Oct 16, 2012, 12:09 AM
Many thanks Vladimir. It worked - I missed the Name/Company mapping and the data type (was staring too hard I think!).

About the serialization - yes, originally I tried serializing just the Paging object, but I realized the JSON had double the traffic:

It had a structure like this:



{
data: [],
total: 0,
serializationObject: { data: [], total: 0 }
}


(I've removed the data from the above but you can imagine if many rows and columns, that is a lot of duplication)

Hence if I just used serializationObject I got the correct structure. Maybe serializationObject should not be marked as serializable in which case the more elegant solution is serializing Paging or using the .Serialize() method that you mentioned?

Thanks!

Vladimir
Oct 16, 2012, 12:37 AM
Thanks, I changed SerializationObject to method to avoid serialization in any case

anup
Oct 16, 2012, 1:05 AM
Many thanks for such a quick response. Retested and looks fixed to me. You can mark this as Closed.