Используем сервис Gravatar

Вячеслав Гринин, June 27, 2011

Название Gravatar переводится как “глобально распознаваемые аватары“, и предназначен для хранения и глобального доступа к аватаркам пользователя и его персональным данным.

Все URL-ы в системе gravatar имеют одну общую ключевую деталь – хеш адрсеса электропочты пользователя. Именно этот хеш уникально идентифицирует пользователя в рамках сервиса gravatar. Хеш формируется очень просто. Для этого нужно взять e-mail, убедиться, что в нем отсутствуют начальные и конечные пробелы, привести его в нижний регистр, и взять MD5-хеш полученной строки.

На языке PHP этот код будет выглядеть так:

    $email = "vgrinin@gmail.com";
    echo md5(strtolower(trim($email)));

А на C# – вот так:

string email = "vgrinin@gmail.com";
byte[] hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(
  email.Trim().ToLower()));
StringBuilder hashString = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
  hashString.Append(hash[i].ToString("X2"));
}
Console.WriteLine(hashString.ToString().ToLower());

Гм… получилось несколько длиннее, чем на PHP.
(more…)

Практикуемся в ADO.NET Entity Framework. Model First.

Вячеслав Гринин, June 6, 2011

Сразу оговорюсь, какое отношение имеет цикл статей про Entity Framework к веб-программированию. В дальнейшем я собираюсь использовать полученную модель для создания веб-приложения на базе технологии ASP.NET MVC 3, которая очень хорошо сочетается с Entity Framework.

Прежде всего – вам нужно иметь установленную Visual Studio 2010 (кажется 2008 тоже подойдет) и установленный пакет ADO.NET Entity Framework 4.1 найдите его по ссылке или в поиске на microsoft.com. Теперь нам становятся доступны все возможности ADO.NET Entity Framework. Замечу, что четвертая версия отличается от более ранних, так что, если у вас установлена более ранняя версия, то не гарантирую, что у вас будет работать тот код, что я привел в статье.

Итак, создаем обычное консольное приложение. Присваиваем ему имя test1. Добавляем в проект модель данных.

Add -> New Item... -> ADO.NET Entity Data Model

, называем ее MyEFModel.edmx.

Среда разработки предлагает нам два варианта создания модели: генерация из базы данных и пустую модель. В этой статье мы собираемся рассмотреть принцип Model First, то есть начинать разработку мы будет с создания модели данных, из которой впоследствии будет сгенерирована схема данных (таблицы и связи в базе данных). А это значит, что мы выберем вариант создания пустой модели (Empty Model). После этого перед нами откроется пустое поле дизайнера модели данных. Если дизайнер не открылся, то сделайте двойной клик на модели MyEFModel.edmx в Solution Explorer’е.
(more…)

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.

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

Реализация чата на ASP.NET с использованием Long Polling

Вячеслав Гринин, March 13, 2010

Бродя по просторам интернета, на одном из сайтов я увидел простой чат. Просмотрев страницу в FireBug я понял, что никакими апплетами там и не пахнет, а значит чат реализован на простом JavaScript.

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

И встал передо мной вопрос – как они это сделали? Как они обеспечили такую высокую скорость отдачи свежих данных? Ведь веб-сервер на то и сервер, что может лишь отвечать на запросы пользователя, но уж никак не самостоятельно инициировать запросы.

Непродолжительный поиск по интернетам дал мне ответ. И ответ этот “Long Polling“, а встречается еще и другое название технологии – Comet.

Итак, как же это работает?

По сути, это все тот же старый добрый AJAX, и использование объекта XmlHttpRequest. С одной большой разницей, заключенной на серверной стороне. Разница состоит вот в чем. Клиент посылает XHR-запрос и длительное время ожидает ответа сервера. А “длительное время” отклика обеспечивает сам сервер, который не сразу отдает данные клиенту, а лишь только тогда, когда у него в очереди появляются свежие данные, то есть когда ему действительно есть что сказать.

Клиент, дождавшись ответа, обрабатывает его (например, отображает сообщение на странице), а затем, без промедления, снова запрашивает сервер. И снова ждет.

Если же подав запрос клиент так и не дождался ответа из-за таймаута, то он снова запрашивает сервер. А откуда этот таймаут? А просто серверу нам нечего ответить, вот он и не отдает клиенту результат запроса. Ждет. Но не дождавшись, разрывает соединение по таймауту. Такая вот несложная схема.

Это и называется Long Polling, то есть – длительный опрос.

Но от слов к делу. Сейчас мы изготовим серверную и клиентскую часть простого интернет-чата, построенного на описанной технологии. Мы подробно рассмотрим работу всего серверного и клиентского кода и даже в конце статьи получим ссылку на вполне работоспособный продукт. Сразу скажу, у него есть некоторые ограничения. Вы, например, не сможете его использовать в режиме сильной нагрузки, то есть наличии одновременно большого количества клиентов. В нем нет схемы очистки “потерянных” соединений. Это лишь работоспособный учебный пример. Все недостающие аспекты кода предлагаю вам на самостоятельную доработку.

Итак… Без особых размышлений приведу здесь листинг кода и объясню принцип его действия.

using System;
using System.Collections.Generic;

// Класс описывающий одно сообщение от клиента и
// метод его сериализации
public class CometMessage
{
 public string UserName;
 public string Message;
 public string Serialize()
 {
 return "{'user': '" + UserName + "', 'message': '" + Message + "'}";
 }
}

// Собственно, серверная часть
public static class CometServer
{
 // вспомогательный объект для блокировки ресурсов
 // многопоточного приложения
 private static Object _lock = new Object();

 // Список, хранящий состояние всех подключенных клиентов
 private static List<CometAsyncState> _clientStateList =
   new List<CometAsyncState>();

 // Возвращаем сообщение каждому подключенному клиенту
 public static void PushMessage(CometMessage message)
 {
 lock (_lock)
 {
 // Пробегаем по списку всех подключенных клиентов
 foreach (CometAsyncState clientState in _clientStateList)
 {
 if (clientState.CurrentContext.Session != null)
 {
 // И пишем в выходной поток текущее сообщение
 clientState.CurrentContext.Response.Write(message.Serialize());
 // После чего завершаем запрос - вот именно после этого результаты
 // запроса пойдут ко всем подключенным клиентам
 clientState.CompleteRequest();
 }
 }
 }
 }

 // Срабатывает кажды раз при запуске клиентом запроса Long poll
 // так как при этом HttpContext клиента изменяется, то надо обновить
 // все изменившиеся данные клиента в списке, идентифицируемом по
 // гуиду, который у клиента в течение работы остается постоянным
 public static void UpdateClient(CometAsyncState state, String guid)
 {
 lock (_lock)
 {
 // ищем клиента в списке по его гуиду
 CometAsyncState clientState = _clientStateList.Find(s => s.ClientGuid
   == guid);
 if (clientState != null)
 {
 // и если он нашелся, то обновляем все его параметры
 clientState.CurrentContext = state.CurrentContext;
 clientState.ExtraData = state.ExtraData;
 clientState.AsyncCallback = state.AsyncCallback;
 }
 }
 }

 // Регистрация клиента
 public static void RegicterClient(CometAsyncState state)
 {
 lock (_lock)
 {
 // Присваиваем гуид и добавляем в список
 state.ClientGuid = Guid.NewGuid().ToString("N");
 _clientStateList.Add(state);
 }
 }

 // Разрегистрация клиента
 public static void UnregisterClient(CometAsyncState state)
 {
 lock (_lock)
 {
 // Просто удаляем его из списка
 _clientStateList.Remove(state);
 }
 }
}

Итак:

6-14 строки – инкапсулируют класс сообщения от пользователя, как видим, там еще есть метод, преобразующий объект в JSON-строку. Вообще, по-хорошему, здесь надо пользоваться нормальным JSON-сериализатором, а не городить строку вручную.

Далее идет описание класса CometServer в котором и живет вся серверная логика LongPolling-технологии.

Заметим, что в переменной _clientStateList (строка 24) хранятся все подключенные на данный момент к серверу клиенты.

Метод PushMessage (строка 24) пробегает по всему списку клиентов и пишет в выходной поток каждого переданное ему в качестве аргумента сообщение, естественно, предварительно сериализовав его. После чего для каждого клиента в обязательном порядке вызывается метод CompleteRequest(), который завершает асинхронный запрос, передавая его результаты клиенту.

Метод UpdateClient (строка 51) по полученному им гуиду клиента, находит клиента в списке clientStateList и обновляет все его параметры, такие как HttpContext-например. Этим мы обеспечиваем всегда актуальное состояние списка клиентов, после каждого их реконнекта.

Метод RegicterClient (строка 69) /опечатка вышла в его имени, сами исправите при необходимости:)/ добавляет нового клиента в очередь.

Метод UnregisterClient (строка 80) соответственно – удаляет клиента из очереди. Что вполне законно. Зачем уведомлять клиента о новых сообщениях в чате, если он давно покинул чат?

Дальше идет описание класса CometAsyncState. Зачем мы его создали? Да чтобы хранить такие параметры клиента, как CurrentContext, AsyncCallback и ClientGuid. Ну то есть, чтобы отдельный поток, порожденный в пуле потоков и отвечающий за одного клиента, мог собственно функционировать и взаимодействовать с клиентом. Этот класс, по большому счету, всего лишь набор заглушек для методов интерфейса IAsyncResult плюс метод CompleteRequest(), который вызывает callback-функцию при завершении потока.

using System;
using System.Threading;
using System.Web;

public class CometAsyncState : IAsyncResult
{
    // Чтобы было где хранить все это и был создан
    // класс-наследник от IAsyncResult
    public HttpContext CurrentContext;
    public AsyncCallback AsyncCallback;
    public object ExtraData;
    public string ClientGuid;
    private Boolean _isCompleted;

    // Конструктор
    public CometAsyncState(HttpContext context,
      AsyncCallback callback, object data)
    {
        CurrentContext = context;
        AsyncCallback = callback;
        ExtraData = data;
        _isCompleted = false;
    }

    // Завершим запрос
    public void CompleteRequest()
    {
        // При завершении запроса просто выставим флаг
        // что он завершен
        // и вызовем callback
        _isCompleted = true;
        if (AsyncCallback != null)
        {
            AsyncCallback(this);
        }
    }

    #region IAsyncResult Members
    // И снова видим набор заглушек для интерфейса IAsyncResult

    public Boolean CompletedSynchronously
    {
        get
        {
            return false;
        }
    }

    public bool IsCompleted
    {
        get
        {
            return _isCompleted;
        }
    }

    public object AsyncState
    {
        get
        {
            return ExtraData;
        }
    }

    public WaitHandle AsyncWaitHandle
    {
        get
        {
            return new ManualResetEvent(false);
        }
    }
    #endregion
}

Осталась малость – клиентский JavaScript. Ниже я приведу его с моими подробными комментариями в самом коде и не буду его так детально разжевывать. Потому что весь он разжеван в начале статьи, где я описывал принцип работы LongPolling.

var clientGuid

$(document).ready(function() {
 // Подключаемся после загрузки страницы,
 // запускаем первый long polling
 Connect();
});

$(window).unload(function() {
 // При выгрузке страницы - запрашиваем сервер об отключении
 // клиента для экономии ресурсов
 Disconnect();
});

// Посылает lonp poll - запрос серверу
function SendRequest() {
 var url = './comet.ashx?guid=' + clientGuid;
 $.ajax({
 type: "POST",
 url: url,
 // Если запрос завершился успехом, значит сервер сообщил
 // о новых событиях - обрабатываем их
 success: ProcessResponse,
 // При ошибке (например таймауте), снова рекурсивно
 // посылаем запрос обеспечивая тем самым непрерывный
 // процесс прослушки серверных событий
 error: SendRequest
 });
}

// Регистрируемся на сервере
function Connect() {
 var url = './comet.ashx?cmd=register';
 $.ajax({
 type: "POST",
 url: url,
 success: OnConnected,
 error: ConnectionRefused
 });
}

// Разрегистрируемся на сервере
function Disconnect() {
 var url = './comet.ashx?cmd=unregister';
 $.ajax({
 type: "POST",
 url: url
 });
}

// Обработка сообщений, принятых с сервера
function ProcessResponse(transport) {
 eval('var d=' + transport + ';');
 document.getElementById("content").innerHTML +=
    ' <strong>' + d.user + '</strong> : "' + d.message + '"<br/>';
 // После отображения результатов запроса -
 // снова циклично делаем запрос.
 SendRequest();
}

// После регистрации на сервере сохраняем наш guid и
// посылаем первый long poll запрос на сервер
function OnConnected(transport) {
 clientGuid = transport;
 SendRequest();
}

// Если подключиться не удалось, то ждем три мекунды
// и опять пробуем подключиться
function ConnectionRefused() {
 $("#content").html("не удалось подключиться к серверу.
    Попробуем через 3 секунды...");
 setTimeout(Connect(), 3000);
}

// Отправка сообщения на сервер
function clickSendMessage() {
 var userName = document.getElementById("userName").value;
 var message = document.getElementById("message").value;
 var url = './comet.ashx?cmd=send&message=' + message + '&user=' + userName;
 $.ajax({
 type: "POST",
 url: url
 });
}

Ну и разумеется, код серверного хэндлера comet.ashx, который обрабатывает команды клиентского скрипта. Скажу сразу, хэндлер этот асинхронный, а потому унаследован не от IHttpHandler, а от IHttpAsyncHandler

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

using System;
using System.Web;
using System.Threading;

public class CometAsyncHandler : IHttpAsyncHandler,
  System.Web.SessionState.IRequiresSessionState
{
    #region IHttpAsyncHandler Members

    public IAsyncResult BeginProcessRequest(HttpContext ctx,
      AsyncCallback cb, Object obj)
    {
        // Готовим объект для передачи его в QueueUserWorkItem
        CometAsyncState currentAsyncState =
          new CometAsyncState(ctx, cb, obj);

        // Добавляем в тредпул новый ждущий поток
        ThreadPool.QueueUserWorkItem(new WaitCallback(RequestWorker),
           currentAsyncState);

        return currentAsyncState;
    }

    public void EndProcessRequest(IAsyncResult ar)
    {
    }

    #endregion

    #region IHttpHandler Members
    // IHttpHandler Members - просто пустые заглушки,
    // так как нам не требуется реализация синхронных методов

    public bool IsReusable
    {
        get
        {
            return true;
        }
    }

    public void ProcessRequest(HttpContext context)
    {
    }

    #endregion

    // Основная функция рабочего потока
    private void RequestWorker(Object obj)
    {
        // obj - второй параметр
        // при вызове ThreadPool.QueueUserWorkItem()
        CometAsyncState state = obj as CometAsyncState;

        string command =
          state.CurrentContext.Request.QueryString["cmd"];
        string guid =
          state.CurrentContext.Request.QueryString["guid"];

        switch (command)
        {
            case "register":
                // Регистрируем клиента в очереди сообщений
                CometServer.RegicterClient(state);
                state.CurrentContext.Response.Write(
                  state.ClientGuid.ToString());
                state.CompleteRequest();
                break;
            case "unregister":
                // Удаляем клиента из очереди сообщений
                CometServer.UnregisterClient(state);
                state.CompleteRequest();
                break;
            case "send":
                // Отсылка сообщения
                string message =
                  state.CurrentContext.Request.QueryString["message"];
                string userName =
                  state.CurrentContext.Request.QueryString["user"];
                CometServer.PushMessage(new CometMessage() {
                  Message = message, UserName = userName });
                state.CompleteRequest();
                break;
            default:
                // При реконнекте клиента
                if (guid != null)
                {
                    CometServer.UpdateClient(state, guid);
                }
                break;

        }
    }
}

Основная логика программы кроется в методе RequestWorker (строки 51-95), который в зависимости от полученной с клиента команды, выполняет либо регистрацию/разрегистрацию клиента, либо отправку сообщения. Либо обновление данных о клиенте при его реконнекте.

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

Вот и все. Надеюсь, что статья оказалось полезной для вас.

Upd:
Оказалось, что при существовании какой-либо информации в сессии LongPolling-чат перестает корректно работать, обрывая соединения, и не обеспечивая доставку сообщений конечным пользователям. Решение проблемы описано в статье HTTPHandler : IRequiresSessionState halts execution of pages, а если кратко – следует наследовать CometAsyncHandler от System.Web.SessionState.IReadOnlySessionState, а не от System.Web.SessionState.IRequiresSessionState. Спасибо пользвателю Алеша за предоставленную информацию.

Разбор XML-файлов в .NET

Вячеслав Гринин, February 27, 2010

Заметил я вот какую странную вещь – мало я пишу про ASP.NET, несмотря на то, что сам я в основном программирую именно под ASP.NET. И решил я начинать исправлять эту ситуацию потихоньку. И теперь в нашем блоге будет появляться все больше статей, посвященных ASP.NET, да и вообще технологии .NET. И сегодня будет именно такая статья. Посвящена она работе с XML-файлами.

Вы уже наверное заметили, что я люблю парсить XML-файлы. Да просто формат этот очень уж удобный для передачи и хранения различных данных. Взять вот хотя бы тот же gismeteo-информер, который я уже успел распарсить несколькими различными методами. И все эти методы касались языка JavaScript. А теперь попробуем распарсить его и в .NET. Но давайте уж возьмем не сам по себе информер, а кое-какие другие XML-данные с сайта gismeteo.

Исходные данные: Mozilla с установленным FireBug, открытая в нем страница http://informer.gismeteo.ru/getcode/xml.php. Видите внизу три списка, соответственно: стран, регионов и городов? Так вот, когда Вы в нем выбираете страну, то в списки регионов и городов загружаются регионы и города выбранной страны. При этом происходит запрос на сервер gismeteo вот такого вида: http://informer.gismeteo.ru/getcode/index.php. При этом, в POST-параметрах запроса передаются параметры: a1=156 (для регионов), a2n=156 (для городов). 156 – это код России в справочниках gismeteo.

А назад мы получаем XML-файл вот такого вида (здесь я приведу лишь кусок файла):

<cities>
<city value="11845" old_id="99855">Абаза</city>
<city value="4723" old_id="29865">Абакан</city>
<city value="4659" old_id="29485">Абан</city>
<city value="4546" old_id="28581">Абатский</city>
<city value="4607" old_id="28815">Абдулино</city>
<city value="11657" old_id="99666">Абинск</city>
<city value="12874" old_id="89157">Автуры</city>
<city value="3967" old_id="23383">Агата</city>
</cities>

Итак захотел я распарсить именно этот файл. Скажу даже зачем мне это нужно. А вот нужно мне по названию города на русском языке получить соответствующий XML-документ с данными о погоде в этом городе. А XML этот лежит по вот такому пути: http://informer.gismeteo.ru/xml/99855_1.xml , где 99855 – это аттрибут old_id нужного мне города. Понятно, что разобранный файл я сохраню затем в базу данных, откуда и буду выбирать айдишники нужных мне городов. Наверное, можно было попросить эту базу у gismeteo, но я так и не нашел на их сайте работающих e-mail’ов. :) Да и не ищем мы легких путей. Будем самостоятельно парсить их базу .

Итак, вот готовый код для разбора подобного рода XML-файла:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.XPath;
using System.IO;

namespace ParseXML
{
class Program
{
static void Main(string[] args)
{
// Собственно XML
string xml = @"
<cities>
<city value=""11845"" old_id=""99855"">Абаза</city>
<city value=""4723"" old_id=""29865"">Абакан</city>
<city value=""4659"" old_id=""29485"">Абан</city>
<city value=""4546"" old_id=""28581"">Абатский</city>
<city value=""4607"" old_id=""28815"">Абдулино</city>
<city value=""11657"" old_id=""99666"">Абинск</city>
<city value=""12874"" old_id=""89157"">Автуры</city>
<city value=""3967"" old_id=""23383"">Агата</city>
</cities>";
// ЗАгружаем строку в XPathDocument
XPathDocument xPathDoc = new XPathDocument(new StringReader(xml));
XPathNavigator xPathNav = xPathDoc.CreateNavigator();
// Выбираем нужные узлы
XPathNodeIterator xPathIter = xPathNav.Select("/cities/city");
// Пробегаем по всем узлам
foreach (XPathNavigator nav in xPathIter)
{
// Метод GetAttribute извлекает аттрибут узла
int id = Int32.Parse(nav.GetAttribute("value", ""));
int old_id = Int32.Parse(nav.GetAttribute("old_id", ""));
// Свойство Value узла дает нам содержимое XML-узла, то есть строку,
// которая содержится между открывающим и закрывающим XML-тегами
Console.WriteLine("{0} \t id={1}, \t http://informer.gismeteo.ru/xml/{2}_1.xml",
nav.Value, id, old_id);
}
Console.ReadLine();
}
}
}

Вот и все. Думаю, что код этот очень простой и в дополнительном комментировании не нуждается.

Кстати, скачать бесплатно мобильные игры java Вы можете на сайте mobi4uk4a.ru. Я скачал себе набор пасьянсов, теперь есть чем заняться пока еду в метро.

Нам и нашим четвероногим друзьям нужна квалифицированная ветеринарная помощь, ведь только в этом случае можно быть уверенным, что Ваш домашний любимец будет здоров и проживет долгую жизнь рядом с Вами. Не стоит надеяться на “авось”, если Ваш питомец заболел, и ни в коем случае нельзя заниматься самолечением – сразу идите в ветеринарную клинику “Биоконтроль”

На днях нашел замечательный ресурс посвященный фрилансу. Софт, книги, статьи для вебмастера – здесь есть все необходимое для того, чтобы Ваша работа в сфере web доставляла Вам истинное удовольствие.

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