вторник, ноября 25, 2008

JSON в ASP.NET

исходные коды
В
предыдущей теме
Callback in ASP.NET я рассматривал, как выполнить callback со стороны клиента на сервер, так сказать, сходить за данными. Здесь я рассказываю, как можно выполнить сериализацию объекта некоего класса со стороны сервера на клиента и использовать его в гораздо более удобной и в ООП-ориентированной форме;



В рассматриваемом примере на странице создается список объектов класса Customer, декларация класса следующая:



[Serializable]
public class CustomerInfo
{
private string firstName;
public string FirstName
{
get
{
return firstName;
}
set
{
if (firstName == value)
return;
firstName = value;
}
}


private string lastName;
public string LastName
{
get
{
return lastName;
}
set
{
if (lastName == value)
return;
lastName = value;
}
}

private string company;
public string Company
{
get
{
return company;
}
set
{
if (company == value)
return;
company = value;
}
}

[NonSerialized]
private string testNonSerialized = "NonSerialized string";

}



мы пометили класс как сериализуемый атрибутом Serializable, все поля простых типов данных данного класса могут быть сериализованы, за исключением тех полей, которые мы можем отметить атрибутом NonSerialized (забегая на будущее, иногда в имеющемся классе, который мы хотим использовать на стороне клиента, могут использоваться типы данных и поля, которые мы или просто не сможем использовать на стороне клиента или же нам просто данные поля не нужны)




Заполнение данными происходит следующим образом:

///
/// databinding & controls initialization
///

private void LoadData()
{
// db request and customers collection initialization
customers.Add(new CustomerInfo
{
FirstName = "John",
LastName = "Smith",
Company = "ITS"
});

customers.Add(new CustomerInfo
{
FirstName = "Alise",
LastName = "Young",
Company = "IT Research"
});

customers.Add(new CustomerInfo
{
FirstName = "Alex",
LastName = "Gullini",
Company = "AD Analitics"
});
}



Сама страница предоставляется следующую функциональность - строит таблицу с отображением текстовых полей для редактирования firstName, lastName, company и кнопку, при нажатии на которую мы запрашиваем следующий объект для редактирования и так далее по кругу - сохранение изменений в прилагаемом примере не реализовано, только получение и отображение;



Внешний вид страницы





Сама разметка страницы следующая:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="JSON._Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title></title>
<style type="text/css">
td { border:solid 1px #eaeaea; }
</style>

<script language="javascript" type="text/javascript">
var customerID = 0;

function onClickHandler() {

serverCall('get,' + customerID);
customerID++;
if (customerID > 2) {
customerID = 0;
}
}

function onSuccessfullHandler(responseFromServer) {
var customer = Sys.Serialization.JavaScriptSerializer.deserialize(responseFromServer);
if (customer != null) {
// binding
var txtFirstName = $get('txtFirstName');
var txtLastName = $get('txtLastName');
var txtCompany = $get('txtCompany');

txtFirstName.value = customer.firstName;
txtLastName.value = customer.lastName;
txtCompany.value = customer.company;
}
else
customerID = 0;
}

</script>
</head>
<body>
<form id="frmMain" runat="server">
<asp:ScriptManager ID="scriptManager" runat="server"></asp:ScriptManager>
<div>
<table style="border:solid 1px grey;">
<tr>
<td>
First name:
</td>
<td>
<input type="text" id="txtFirstName" />
</td>
</tr>

<tr>
<td>
Last name:
</td>
<td>
<input type="text" id="txtLastName" />
</td>
</tr>

<tr>
<td>
Company:
</td>
<td>
<input type="text" id="txtCompany" />
</td>
</tr>

<tr>
<td colspan="2">
<input type="button" id="btnGetNextCustomer" onclick="onClickHandler();" value="Next customer"/>
</td>
</tr>
</table>
</div>
</form>
</body>
</html>





Как видим, на странице используются обычные html-контролы, при нажатии на кнопку btnGetNextCustomer вызывается функция onClickHandler, которая и выполняет всю работу;


onClickHandler делает вызов функции serverCall, которую мы зарегестрировали в обработчике страницы Page_Load так как было рассказано в предыдущем моем топике, в частности, в той ее части, которая касается реализации интерфейса ICallbackEventHandler;



Страница работает следующим образом - на стороне клиента объявлена переменная customerID - это простой порядковый номер, которые передается в функцию обратного вызова serverCall, и затем этот номер увеличивается для запроса следующей записи и так далее по кругу - при достижении максимальной величины (3) он сбрасывается обратно в ноль;

При получении значения на стороне сервера в методе RaiseCallbackEvent(string eventArgument) мы сохраняем запрошенный номер во внутреннем поле currentCustomerID:




public void RaiseCallbackEvent(string eventArgument)
{
string[] argsFromClient = eventArgument.Split(',');

if (argsFromClient.Length >= 2)
{
switch (argsFromClient[0])
{
case "get":
int customerID;
if (int.TryParse(argsFromClient[1], out customerID))
{
this.currentCustomerID = customerID;
}

break;
default:
break;
}
}
}


Затем, при формировании ответа клиенту, мы сериализуем запрошенный объект Customer следующим образом:



public string GetCallbackResult()
{
string result = "{}";

if (this.currentCustomerID < this.Customers.Count)
{
CustomerInfo customer = this.Customers[this.currentCustomerID];
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(CustomerInfo));

using (MemoryStream ms = new MemoryStream())
{
ser.WriteObject(ms, customer);
result = Encoding.Default.GetString(ms.ToArray());
}
}

return result;
}


Используем класс DataContractJsonSerializer, который объявлен в пространстве имен System.Runtime.Serialization.Json и находится в сборке System.ServiceModel.Web.dll - ее нужно подключить к проекту;


Результат сериализации в строку result вы можете увидеть на следующем скриншоте - по сути дела, данная строка представляет обычное отображение javascript-объекта в строку, если мы вызываетм метод toString() - сериализация объекта в javascript достаточно простая, сохраняются просто пары ключ-значение, что логично, так как любой класс в javascript - это просто словарь.

Ответ со стороны сервера






Далее, сериализованная строка уходит на клиента, где обрабатывается в нашем сallback обработчике onSuccessfullHandler следующим образом:

function onSuccessfullHandler(responseFromServer) {
var customer = Sys.Serialization.JavaScriptSerializer.deserialize(responseFromServer);
if (customer != null) {
// binding
var txtFirstName = $get('txtFirstName');
var txtLastName = $get('txtLastName');
var txtCompany = $get('txtCompany');

txtFirstName.value = customer.firstName;
txtLastName.value = customer.lastName;
txtCompany.value = customer.company;
}
else
customerID = 0;
}





Ответ со стороны сервера




В данном случае мы используем клиентский класс Sys.Serialization.JavaScriptSerializer и его метод deserialize из библиотеки ajax - обратите внимание, для того, чтобы классы из пространства имен Sys и прочих были доступны, на странице должен быть доступен ScriptManager. Также обратите внимание, что структура самого десериализованного объекта customer полностью совпадает с его серверным описанием, таким образом, мы можем использовать объявления ранее private полей из серверного объявления класса за исключением тех, которые были помечены атрибутом NonSerialized;



Десериализованный объект customer на клиенте






Таким образом, написав класс на стороне сервера, мы в удобной форме можем использовать его на стороне клиента. В данном примере не была рассмотрена обратная десериализация со стороны клиента на сервер - но не думаю что это уже будет представлять серьезную проблему, никаких нюансов там не должно быть


ps: на самом деле, сам не так давно открыл для себя и начал использовать сериализацию серверных классов на сторону клиента, пока проме плюсов ничего не получил

Комментариев нет: