AJAX при помощи DataContractJsonSerializer и ashx-handler

Вячеслав Гринин, November 2, 2010

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

  1. Тип клиентской JS-библиотеки
  2. Тип серверного ответчика
  3. Способ сериализации данных
  4. Тип передаваемых данных

Как видим, здесь есть где развернуться. Расскажу подробнее о каждом из пунктов.

1. Тип клиентской JS-библиотеки.

Это тот скрипт, который работает в браузере пользователя. Какую бы библиотеку Вы не использовали, в недрах ее для асинхронного обмена данными в любом случае будет использоваться объект XmlHTTPRequest. Я лично пользовался такими библиотеками как: ExtJS, jQuery, prototype.js, ATLAS. Сложно сравнивать эти библиотеки, все они хороши, каждая по своему.

2. Тип серверного ответчика.

Прежде всего серверный ответчик характеризует язык серверной реализации, это может быть PHP, ASP.NET, Java, Ruby, Python и много других страшных слов. 🙂 В каждом языке есть свои типы серверных ответчиков. Говоря про ASP.NET, я могу выделить WebServices (*.asmx), Generic Handlers (*.ashx).

3. Способ сериализации данных.

Их можно сериализовать “вручную”, то есть простой конкатенацией строк, можно использовать встроенные в серверную платформу объекты, например DataContractJsonSerializer, который имеется в ASP.NET.

4. Тип передаваемых данных.

Данные можно передавать в собственном формате, можно в виде JSON-строки, XML-строки, или же передавая строку с готовой HTML-разметкой. Вариантов масса, выбор зависит от контекста задачи.

В данной статье я собираюсь рассмотреть вариант релизации AJAX-обмена на основе компонентов:

1. Клиентская библиотека – jQuery,

2. Серверный ответчик – Generic Handler (*.ashx),

3. Способ сериализации – DataContractJsonSerializer,

4. Тип данных – HTML-разметка, упакованная в JSON-строку.

Чтобы не создавать бесполезный код, заставим наше приложение отображать на странице структуру некоторой папки, и позволяющей даже ходить по вложенным в нее папкам и скачивать любые файлы. Для простоты рассмотрения AJAX-обмена, вся заявленная функциональность будет вынесена в отдельные классы и оставлена без моих комментариев, а вот все что касается AJAX будет мною подробно рассмотрено и прокомментировано.

И опять все по пунктам:

1. Мы используем клиентскую библиотеку jQuery. Скачать ее последнюю версию можно на официальном сайте – ищите в поисковиках. Помимо многочисленных полезных функций, есть в ней и функции для работы с AJAX. Я же приведу готовый скрипт, который делает запрос к серверу, и получив ответ от него, отрисовывает результат на странице:

var NetworkDirectoryControl = {
 LoadDirectory: function (path) {
 var url = 'NetworkDirectoryHandler.ashx?root=' + path;
 $.ajax({
 type: "POST",
 url: url,
 success: NetworkDirectoryControl.OnLoadDirectorySuccess,
 error: NetworkDirectoryControl.OnLoadDirectoryError
 });
 },
 OnLoadDirectorySuccess: function (transport) {
 var data = $.parseJSON(transport);
 document.getElementById("NetworkDirectoryControl.ListContainer").innerHTML = data.Control;
 },
 OnLoadDirectoryError: function () {
 }
};

Функция NetworkDirectoryControl.LoadDirectory(path) просто делает асинхронный POST-запрос к серверному ответчику NetworkDirectoryHandler.ashx передавая ему в качестве GET-параметра переменную root.

При успешном выполнении запроса вызывается обратная функция NetworkDirectoryControl.OnLoadDirectorySuccess(transport), у которой аргумент transport содержит JSON-строку, возвращенную сервером. При помощи $.parseJSON мы преобразуем строку в JS-объект, после чего отображаем поле объекта Control в элементе DIV ID=”NetworkDirectoryControl.ListContainer”.

2. Серверный ответчик

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

using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.IO;
using System.Text;

/// <summary>
/// Handler which returns folder markup or file content stream due to requested path
/// </summary>
public class NetworkDirectoryHandler : IHttpHandler
{
 public void ProcessRequest(HttpContext context)
 {
 string RequestedPath = NetworkDirectoryControlUtil.DecodeStringForJScript(context.Request.QueryString["root"]);
 if (RequestedPath == null)
 {
 RequestedPath = @"Root:\";
 }

 if (RequestedPath.EndsWith(@"\"))
 {
 // Process directory request
 context.Response.ContentType = "text/plain";
 NetworkDirectoryControlJSONResult jsonResult = new NetworkDirectoryControlJSONResult();
 jsonResult.Control = NetworkDirectoryControlUtil.RenderControl(NetworkDirectoryControlUtil.RootDirectory, RequestedPath);
 string response = NetworkDirectoryControlUtil.SerializeToJSON<NetworkDirectoryControlJSONResult>(jsonResult);
 context.Response.Write(response);
 }
 else
 {
 // Process file request
 string FileName = RequestedPath.Remove(0, RequestedPath.LastIndexOf(@"\") + 1);
 context.Response.ContentType = "application/octet-stream";
 context.Response.AddHeader("Content-Disposition", @"attachment; filename=""" + FileName + @"""");
 context.Response.ContentEncoding = System.Text.Encoding.UTF8;
 context.Response.Charset = "UTF-8";
 try
 {
 context.Response.WriteFile(NetworkDirectoryControlUtil.GetAbsolutePath(NetworkDirectoryControlUtil.RootDirectory, RequestedPath));
 }
 catch
 {
 // In case of error - do nothing
 }
 }
 }

 public bool IsReusable
 {
 get
 {
 return false;
 }
 }

}

Метод ProcessRequest объекта NetworkDirectoryHandler срабатывает всякий раз при обращении к нему. В качестве аргумента он получает свой HTTP-Context, в котором хранятся все полученные GET и POST параметры, обработав их, мы можем в объект Response записать выходные данные обработчика.

В нашем случае мы получаем переменную root, преобразуем ее из формата Base64 методом NetworkDirectoryControlUtil.DecodeStringForJScript, по наличию в конце строки слэша мы определяем был этот запрос к папке или к файлу, и затем, в соответствии с этим либо отрисовываем контрол и отсылаем назад разметку, либо читаем запрошенный файл и пишем его в поток Response.

3. Способ сериализации

public static string SerializeToJSON<T>(T InputObject)
 {
 DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
 MemoryStream stream = new MemoryStream();
 serializer.WriteObject(stream, InputObject);
 stream.Seek(0, SeekOrigin.Begin);
 StreamReader reader = new StreamReader(stream);
 return reader.ReadToEnd();
 }

Сериализатор DataContractJsonSerializer работает на основе контрактов. Контрактом является заголовочное описание класса, описанного атрибутом [DataContract], и некоторые члены которого помечены атрибутом [DataMember]. Контрактный JSON-сериализатор работает только с такими классами и сериализует только те их члены, которые помечены атрибутом. Остальные члены игнорируются.

[DataContract]
public class NetworkDirectoryControlJSONResult
{
 [DataMember]
 public string Control;
}

4. В качестве типа данных мы выбрали HTML-разметку, упакованную в JSON-строку. JSON-сериализацию мы уже рассмотрели, осталось узнать, как отрисовывать контролы вне страничного контекста, то есть “вручную”.

public static string RenderControl(string RootDirectory, string RelativeDirectory)
 {
 NetworkDirectoryControl control = new NetworkDirectoryControl();
 ((INetworkDirectoryControl)control).RootDirectory = RootDirectory;
 ((INetworkDirectoryControl)control).RelativeDirectory = RelativeDirectory;
 Page page = new Page();
 page.Controls.Add(control);
 StringWriter sw = new StringWriter();
 HtmlTextWriter htw = new HtmlTextWriter(sw);
 HttpContext.Current.Server.Execute(page, htw, false);
 return sw.ToString();
 }

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

А поэтому мы создаем страницу Page, добавляем в нее уже созданный готовый контрол, вызовом метода Server.Execute запускаем жизненный цикл страницы, и только после этого получаем готовую разметку контрола. Вот так, как в примере выше.

Здесь я выкладываю ссылку на готовый работоспособный проект в Visual Studio 2010. Весь AJAX-функционал проекта кроется в файлах: NetworkDirectoryHandler.ashx, jquery-1.4.1.min.js, NetworkDirectory.js и NetworkDirectoryClasses.cs.

Запускайте, комментируйте, спрашивайте.

В тему:

4комментария

Что то слишком мудрено… И по-моему расчитано на блогера чем на вебмастера

bleach online, November 23, 2010 1:33 pm Reply

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

collaps, February 11, 2012 3:48 pm Reply

Вот блин никогда не понимал зачем некоторые люди прикрепляют обломки своего проекта и называют это исходники. Неужели так трудно заархивировать работающее решение?

Ярослав, August 6, 2013 2:09 pm Reply

Поясняю. Куски кода нужны для объяснения его работы. Для скачивания готового проекта в архиве в конце статьи есть и всегда была отдельная ссылка.

Вячеслав Гринин, August 6, 2013 11:26 pm Reply
Ваше имя
Ваш email*
Ваш сайт
Текст вашего комментария:

Поиск по блогу:
Подписаться:
Популярные:
Облако тегов:
Разное:
Счетчик: