AJAX при помощи DataContractJsonSerializer и ashx-handler
В продолжение разговора про AJAX хочу написать несколько статей, описывающих создание AJAX-приложений с применением различных подходов. А подходы эти в принципе могут отличаться в следующих ключевых моментах:
- Тип клиентской JS-библиотеки
- Тип серверного ответчика
- Способ сериализации данных
- Тип передаваемых данных
Как видим, здесь есть где развернуться. Расскажу подробнее о каждом из пунктов.
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.
Запускайте, комментируйте, спрашивайте.
В тему:
Что то слишком мудрено… И по-моему расчитано на блогера чем на вебмастера
bleach online, November 23, 2010 1:33 pmСкажу так. Тема для меня новая но говорю вам спасибо потому что давно хлтел изучить этот AJAX но никак небыло времени, ну а сейчас уже приспичило и надо быстренько изучать. Спасибо за пост !
collaps, February 11, 2012 3:48 pmВот блин никогда не понимал зачем некоторые люди прикрепляют обломки своего проекта и называют это исходники. Неужели так трудно заархивировать работающее решение?
Ярослав, August 6, 2013 2:09 pmПоясняю. Куски кода нужны для объяснения его работы. Для скачивания готового проекта в архиве в конце статьи есть и всегда была отдельная ссылка.
Вячеслав Гринин, August 6, 2013 11:26 pm