среда, ноября 26, 2008

На тему финансовго кризиса

Похоже, финансовый кризис коснулся даже википедию:



Wiki


ps: причем если посмотреть финансовые отчеты за 2008 года (http://wikimediafoundation.org/wiki/Finance_report -> http://upload.wikimedia.org/wikipedia/foundation/4/4c/Wikimedia_20072008_fs.pdf), можно увидеть нехилые такие затраты на зарплату, путешествия :) и прочее, причем с учетом предыдущих пожертвований все равно остаются в плюсе, а денег все равно хотят :)

вторник, ноября 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: на самом деле, сам не так давно открыл для себя и начал использовать сериализацию серверных классов на сторону клиента, пока проме плюсов ничего не получил

понедельник, ноября 24, 2008

Как выполнить callback со стороны клиента на сервер в ASP.NET

исходные коды
Н
е рассматривая update panel и не используя закрытые методы класса PageRequestManager из библиотеки ajax, я выделяю для себя способы:

  1. Page methods


    Тут все достаточно просто; достаточно реализовать в web-форме статический серверный метод страницы, пометить его атрибутом WebMethodAttribute, разместить в разметке ScriptManager control, установить у него свойство EnablePageMethods и требуемый метод будет доступен через сгенерированный на стороне клиента объект PageMethods;



    В прилагаемом примере в странице PageMethods.aspx я реализовал следующий метод:




    [WebMethod(true)]
    public static string PageMethodTest(string arg)
    {
    return "this is a string from server";
    }


    На стороне клиента разместил ScriptManager следующим образом:


    <asp:scriptmanager runat="server" id="scriptManager" enablepagemethods="true">

    </asp:scriptmanager>

    И зарегестрировал серверную кнопку:



    <asp:button id="btnPageMethodInvocation"
    runat="server"
    text="Page method invocation"
    onclientclick="btnPageMethodInvocation_clientHandler();return false;">

    </asp:button>

    При клике по кнопке будет выполнен клиентский сценарий btnPageMethodInvocation_clientHandler, после чего будет возврат значения false, что говорит о необходимости разовать цепочку вызовов клиентских сценариев, одним из которыхъ является возврат формы (собственно сам postback), а именно его мы и хотим избежать; Таким образом, при клике на форму у нас должен выполниться только сценарий btnPageMethodInvocation_clientHandler;


    Сам сценарий будет следующим:

    function btnPageMethodInvocation_clientHandler() {
    PageMethods.PageMethodTest("test", onSuccessfullHandler);
    }










    Как видите, происходит обращение к глобальному клиенскому объекту PageMethods, и вызывается метод, который мы описали на серверной стороне; Сигнатура данного клиентского метода будет отличаться только наличием дополнительных параметров, которые указывают на javascript функции успешного и неуспешного вызова серверного метода, для того чтобы собственно говоря и реализовать сам принцип асинхронности – сделав вызов PageMethods.PageMethodTest, выполнение сценария не прерывается, а результат будет передан в onSuccessfullHandler функцию с единственным параметром result в случае успешного выполнение callback, как показано ниже:


    function onSuccessfullHandler(result) {
    alert(result);
    }


    Сигнатура самого метода следующая:

    Сигнатура метода


    Здесь я просто показываю значение параметра result, значение которого мы получаем со стороны сервера;


    Иными словами, все выполнение происходит следующим образом:
    ScriptManager, проанализировав codebehind класс страницы и обнаружив статик метод с атрибутом WebMethod, сгенерировал объект PageMethods и соотвествующий по сигнатуре клиенский метод, которые, собственно и реализует асинхронный вызов одноименного серверного метода, передав значение со стороны клиента; Серверный метод делает возврат значения (в нашем примере мы возвращаем простой тип данных, о том как делать возврат сложных типов данных с применением json сериализации/десериализации я расскажу в следующем посте), которое в процессе выполнения callback будет передано в клиентский сценарий, который мы указали onSuccessfullHandler;

    Подводя итог, цепочка вызовов будет следующей:

    function btnPageMethodInvocation_clientHandler()
    PageMethods.PageMethodTest("test", onSuccessfullHandler)
    public static string PageMethodTest(string arg)
    function onSuccessfullHandler(result)


    Результат вызова нашей страницы будет слеюущим:


    Результат вызова



    Данный вариант реализации асинхронного вызова очень удобен на мой взгляд, если бы не огромный минус – а именно, невозможность его использования в User-контролах;

  2. Script Service


    Второй вариант лишен описанного выше минуса; Смысл заключается в следующем – реализуется web-сервис, code behind класс сервиса помечается атрибутом ScriptService, а методы, которые мы хотим использовать на стороне клиента – атрибутом ScriptMethod; В разметке самой страницы обязательно присуствие ScriptManager с указанием того ScriptService, методы которого мы хотим получить и использовать на стороне клиента;



    В нашем случае я сделал следующий web-service:



    namespace Callbacks.ScriptServices
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [ScriptService()]
    public class ScriptService : System.Web.Services.WebService
    {

    [WebMethod]
    [ScriptMethod]
    public string ScriptServiceTest(string arg)
    {
    return "this is a string from script service method";
    }
    }



    Создал страницу ScriptServiceCallPage.aspx, код разметки которой выглядит следующим образом:



    <title></title>
    <script language="javascript" type="text/javascript"
    function btnPageMethodInvocation_clientHandler() {
    Callbacks.ScriptServices.ScriptService.ScriptServiceTest("test",
    onSuccessfullHandler);
    }

    function onSuccessfullHandler(result) {
    alert(result);
    }
    </script>


    <form id="form1" runat="server">
    <asp:scriptmanager runat="server" id="scriptManager" enablepagemethods="true">
    <services>
    <asp:servicereference path="~/ScriptServices/ScriptService.asmx">
    </asp:servicereference>
    </services>
    <div>
    <asp:button id="btnPageMethodInvocation"
    runat="server" text="Script service method invocation"
    onclientclick="btnPageMethodInvocation_clientHandler();
    return false;">
    </asp:button></div>
    </asp:scriptmanager>



    </form>




    Отличие от предыдущего примера с использованием сгенерированного объекта PageMethods в том, что мы указываем конкретный ScriptService в ScriptManager, который генерирует соотвествующий javascript класс с методами, сигнатура который соотвествует серверным методам + добавляет параметры для указания javascript функций в случае успешного или неудачного выполнения асинхронного вызова; Сгенерированный объект, который мы можем использовать на стороне клиента, будет иметь имя, полностью совпадающего с указанным script service, т.е. включая c# namespace и имя класса – в нашем случае это Callbacks.ScriptServices.ScriptService; В остальном принцип работы с script service ничем не отличается от PageMethods; Достоинство данного способа состоит в том, что часто используемые операции, вызов которых требуется на клиенте, сосредоточены в одном месте, и доступны как из классов форм, так и user controls;






  3. Третий способ, который лично мне больше всего нравится и на мой взгляд наиболее гибок и прозрачнее для меня, является явная реализация интерфейса IcallbackEventHandler user контролом или классом страницы; Данный способ также описан у Дино Эспозито, автора множества книг-бестселлеров по технологиям AJAX и ASP.NET;



    В нашем примере я создал страницу ICallbackEventHandlerImplementor.aspx, у code behind класса реализовал интерфейс ICallbackEventHandler как показано ниже:


    public partial class ICallbackEventHandlerImplementor : System.Web.UI.Page, ICallbackEventHandler
    {
    #region ICallbackEventHandler Members

    public string GetCallbackResult()
    {
    string resultFromServer = "this is a string from ICallbackEventHandler implementor";
    return resultFromServer;

    }

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

    if (argsFromClient.Length >= 2)
    {
    switch (argsFromClient[0])
    {
    case "add":
    break;
    default:
    break;
    }
    }
    }

    #endregion
    }


    Метод RaiseCallbackEvent будет вызываться со стороны клиентаи принимаеть едиственный строковый параметр, значение которого мы сформируем на стороне клиента; Метод GetCallbackResult будет использоваться для формирования результата, который мы вернем обратно клиентской стороне;

    Итак, что же будет являться началом асинхронной операции – а началом асинхронной операции со стороны клиента будет являться вызов функции WebForm_DoCallback, сигнатура да и собственно название которого не стоит запоминать и использовать напрямую, так они могуто измениться в следующих версиях; Вместо этого сам код для вызова мы можем получить, используя класс ClientScriptManager через свойство ClientScript текущей страницы, используй метод GetCallbackEventReference; Данный метод генерирует вызов метода WebForm_DoCallback на стороне клиента, который мы можем использовать в нашем клиентском сценарии; В нашем примере я хочу сгенерировать клиенский сценарий serverCall, который бы принимал единственное строковое значение, отправлял бы его на выполнение серверу и собственно на этом бы его функции бы заканчивались:



    Т.е. что-то типа такого:


    function serverCall(arg)
    {
    WebForm_DoCallback(…arg)
    }


    Как это сделать, показано в реализации обработчика Page_Load нашей рассматриваемой страницы:

    protected void Page_Load(object sender, EventArgs e)
    {
    if (!IsPostBack)
    {
    string webFormDoCallbackScript = this.ClientScript.GetCallbackEventReference(this, "arg", "onSuccessfullHandler", null, true);
    string serverCallScript = "function serverCall(arg){" + webFormDoCallbackScript + ";\n}\n";

    if (!this.ClientScript.IsClientScriptBlockRegistered("serverCallScript"))
    {
    this.ClientScript.RegisterClientScriptBlock(this.GetType(), "serverCallScript", serverCallScript, true);
    }

    }
    }






    Что здесь происходит? Сначала я получаю в виде строки сам вызов WebForm_DoCallback, причем указываю, как будет называться его единственный аргумент, а также имя той javascript функции, в которую будет передан результат со стороны сервера по завершении асинхронного вызова; затем я формирую функцию враппер наз функцией асинхронного вызова – а именно нашу функцию serverCall – после запуска страницы в сгенерированном исходном тексте вы увидите следующий скрипт:




    <script type="text/javascript">
    //<![CDATA[
    function serverCall(arg){WebForm_DoCallback('__Page',arg,onSuccessfullHandler,null,null,true);
    }
    //]]>
    </script>



    Теперь же можно использовать нашу функцию-враппер serverCall со стороны клиента, как показано в разметке страницы:




    <head id="Head1" runat="server">
    <title></title>
    <script language="javascript" type="text/javascript">
    function btnPageMethodInvocation_clientHandler() {
    serverCall('add,' + 10);
    }

    function onSuccessfullHandler(result) {
    alert(result);
    }
    </script>
    </head>
    <body>

    <form id="form1" runat="server">
    <div>
    <asp:Button ID="btnPageMethodInvocation"
    runat="server"
    Text="ICallbackEventHandlerImplementor sample"
    OnClientClick="btnPageMethodInvocation_clientHandler();return false;" />
    </div>
    </form>
    </body>
    </html>


    Сама разметка, как видите, практически не отличается от предыдущих страниц, за исключением того, что здесь не требуется ScriptManager; Ограничение в количестве параметров, передаваемых в функцию асинхронного вызова WebForm_DoCallback, не ограничивает однако нас от формирования такой строки, значение которой мы можем рассматривать как несколько параметров, что собственно и продемонстрировано в примере:


    На клиенте мы передаем serverCall('add,' + 10); строку “Add, 10”, на стороне сервера делаем сплит строки и дальше уже анализируем полученные значения для выполнения какого либо действия на стороне сервера незаметно от клиента;

    Лично мне последний способ нравится больше в виду того, что я получаю больший контроль над самим асинхронным вызовом, чем в первых двух описанных;



    ps: Существуют и другие способы асинхронных вызовов, например,в том же самом DevExpress есть даже специальный серверный контрол, в котором указывается javascript handler на получение результата со стороны сервера и серверный обработчик, который и выполняет анализ данных со стороны клиента;









пятница, ноября 21, 2008

No keyboard detected! Press F1 to Resume

говорят что боян

понедельник, ноября 17, 2008

Маленький Дюшес

Давно не решался на такой шаг, но все же решился (не по своей воле :))) ) , с чем себя и поздравляю...


Итак, в нашем доме поселился страшный зверь:


Дуэша

ps: зовут Duane, можно просто Дуэша, или Душка, или Дюша :-D... Тигру 2 месяца, всеяден, уже умеет ходить на горшок, постоянно требует внимания, пережил первое посещение ванных процедур, громко урчит от удовольствия, не пьет молоко :-O, хозяев пока не презирает (это временно)....