четверг, 27 января 2011 г.

EasyHTTP – инструмент для удобного взаимодействия с веб-сервером

Работая над множеством интернет-проектов, я довольно часто сталкиваюсь с проблемой, когда приходится обмениваться данными с сервером не посредстовом вызова веб-сервисов, а непосредственно создавая и отправляя HtppRequest с прикрепленными параметрами и обрабатывая HttpResponse, приводя возвращенные данные к нужному объекту класса.

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

Можно продемонстрировать коротенький пример данного подхода: допустим мы имеем обычный RSS-клиент, но оперирующий данными, хранящимися на сервере. Наш клиент может получать список новых статей, добавлять новый RSS-канал, отмечать статьи как прочитанные.

Для удобного оперирования данными создадим соответвующие классы объектов приложения:

public class RssChanel
{
public int Id { get; set; }
public string Title { get; set; }
public string RssUrl { get; set; }
public string Category { get; set; }
}

public class SitePage
{
public int Id { get; set; }
public int RssChanelId { get; set; }
public RssChanel RssChanelSource { get; set; }
public string Title { get; set; }
public string Preview { get; set; }
public string Url { get; set; }
public DateTime CreateDate { get; set; }
public bool IsRead { get; set; }
}



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


/// <summary>
/// Summary description for RssService
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class RssService : System.Web.Services.WebService
{

[WebMethod]
public bool RssChanelProvider_Insert(RssChanel chanel)
{
return DataRepository.RssChanelProvider.Insert(chanel);
}

[WebMethod]
public bool RssChanelProvider_Update(RssChanel chanel)
{
return DataRepository.RssChanelProvider.Update(chanel);
}

[WebMethod]
public bool RssChanelProvider_Delete(int chanelId)
{
return DataRepository.RssChanelProvider.Delete(chanelId);
}

[WebMethod]
public SitePage[] SitePageProvider_GetAll(int pageLength)
{
return DataRepository.SitePageProvider.GetAll(pageLength).ToArray();
}

[WebMethod]
public bool SitePageProvider_Update(SitePage sitePage)
{
return DataRepository.SitePageProvider.Update();
}
}


Итак, теперь с помощью соответсвующих запросов мы можем достучаться до методов нашего веб-сервиса


image


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


image


image


Все. Далее всю сложную работу по созданию прокси-класов, wsdl-описания сервиса и многое другое берет на себя VisualStudio. Также в конфигурационный файл добавляется сексия управления каналом связи с сервером. Тонна рутинной работы была сделана за нас студией. Нам же остается лишь изменить тестовый URL-адрес сервера на реальный.


В итоге мы имеем удобную объектную модель для обмена данными с сервером.


image image


Следующий код будет абсолютно валидным и компилируемым:


static void Main(string[] args)
{
RssServiceSoapClient serviceClient = new RssServiceSoapClient();

SitePage[] sitePages = serviceClient.SitePageProvider_GetAll(20);

foreach (SitePage sitePage in sitePages)
{
sitePage.IsRead = true;
serviceClient.SitePageProvider_Update(sitePage);
}
}






Отлично. Если же наше веб-приложение постоянно развивается и объектная модель со временем может менятся – для клиента необходимо просто перегенерировать ServiceReference и новые свойства будут доступны в перегенерированных proxy-классах.


image


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


Хорошо, но что делать, если необходимо создать клиентское приложение, получающее и обрабатывающее данные не с нашего сервера, т.е. где мы не можем гарантировать, что обмен данными происходит через веб-сервисы.


К тому же мы не можетvгарантировать, что все ресурсы, из которых мы чарпаем данные, отдают нам их именно в том формате, в котором нам надо. Хотелось бы иметь какое-то универсальное средство, упрощающее монотонную работу с HTTP – Get,Post,Delete,Put, сериализация из JSON в объектную модель приложения, чтение данных из REST-full сервисов.


Анализируя библиотеки сторонных разработчиков, я нашел для себя наиболее удобную – EsayHTTP. Можно на первом коротеньком примере показать, в чем именно упрощается работа при использовании данной библиотеки:


var http = new HttpClient
{
Request = {Accept = HttpContentTypes.ApplicationJson}
};

var response = http.Get("http://domain.com/customer/25");

var customer = response.StaticBody<Customer>();



Буквально несколько строчек кода и сразу экономия нескольких часов рутинной работы: создания helper-классов для отправки запросов и получения ответов сервера, создание helper-классов для десеаризилицации ответов и многое другое.


В этом же примере мы задаем Accept Header возвращаемой страницы и конвертируем ответ в strogly-typed объект Customer.


Если же наша бизнес-модель не имеет точного типа объекта для десериализации ответа сервера, мы может проделать следующие манипуляции:


var http = new HttpClient
{
Request = {Accept = HttpContentTypes.ApplicationJson}
};

var response = http.Get("http://domain.com/customer/25");

dynamic customer = response.DynamicBody();

Console.WriteLine(customer.Name);
Console.WriteLine(customer.Email);



В этом случае мы хотим возвратить динамический тип, чтобы десериализовать ответ в динамический объект. Это дает нам возможность выбирать лишь необходимые нам значения полей из объекта, не создавая для этогого отдельные классы.


Если же нам просто надо скачать страницу или изображение и сохранить его в файл на локальном компьюетере:


var http = new HttpClient();
http.GetAsFile("http://ydobno.net/logo.png", @"C:\Logos\ydobno\logo.png");


Крайне просто также сделать POST-запрос на сервер:


var http = new HttpClient();

var customer = new Customer()
{
Name = "Joe Smith",
Email = "Joe@Gmail.com"
};

http.Post("http://domain.com/customer", customer, HttpContentTypes.ApplicationJson);



К тому же мы можем задавать тип кодировки передаваемых данных, в нашем случае данные на сервере принимаются в формате JSON.


Отлично. Благодаря данной библиотеке мы сразу получили мощный инструмент для взаимодействия с удаленным сервером, получили возможность обрабатывать и посылать запросы, не погружаясь в глубины HTTP, получили возможность вытягивать из ответного потока объекты, не погружаясь в глубины десеарилизации. Конечно же, данная библиотека не является панацеей от всех проблем и скорее всего придется делать свои классы для работы с сервером на уровне HTTP, но во многих стандартных вопросах смело можно положится именно на нее.

среда, 19 января 2011 г.

Выполнение методов веб сервиса в браузере

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

Примером может быть:

image

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

image

Чтобы добавить возможность вызывать методы и передавать параметры методам сразу в браузере необходимо всего лишь добавить следующие строки в конфигурационный файл веб-приложения в секцию <system.web>:

<webServices>     
<protocols>
<add name="HttpGet"/>
<add name="HttpPost"/>
</protocols>
</webServices>


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


image


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

четверг, 6 января 2011 г.

Контроль исключений ASP.NET с помощью модуля Elmah

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

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

image

Используя валидаторы ASP.NET на этапе постинга формы мы проверяем введенное значения на условия: не пустое, целочисленное, меньше 5. Если введенные пользователем значения не попадают под наши правила валидации, мы выдаем предупреждения:

Введите количество человек
Только целочисленные значение не более 5

Тем самым при постинге формы на сервер мы можем быть уверены, что данные проверены. Далее же мы делаем деление общей суммы на количество участников. Допустим мы забыли учесть, что и минимальный порог введенных значений должен начинаться с 1. В итоге, при вводе значения 0 в поле количества участников и отправки формы на сервер мы получим следующий ответ:

image

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

Если же исправление ошибки довольное длительная процедура, а необходимо какое-никакое решения прямо сейчас – можно на первое время перенапрявлять пользователя на custom error страницу, если произошло необработанное исключение. Тем самым со стороны пользователя остается мнение, что система держит все под контролем.

Чтобы добавить такую функциональность необходимо просто создать новую страницу (например error.aspx) и добавить следующую запись в конфигурационный файл web.config.

<customErrors mode="On" defaultRedirect="error.aspx">
</customErrors>


Теперь при постинге формы с нулевым значением количества участников мы получим следующий ответ сервера (для примера)


image


Решение вроде бы подходящее, но малоинформативное для администратора системы. Можно полагаться конечно на сознательных пользователей нашей системы, которые точно опишут совершенные ими шаги, укажут введенные в поля значения и отправят письмо по указанному адресу. Хотелось бы получасть уведомления о каждой критической ошибке приложения и независимо от того – произошло это при постинге формы, либо же в фоновом процессе рассылки писем пользователям, либо при ajax-запросе для обновления пользовательского интерфейса.


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


Для этого с помощью NuGet (расширение Visual Studio) добавим библиотеку Elmah (если расширение NuGet не установлено – рекомендую срочным образом исправить это :) так как очень много именно рутинной работы помогает решить столь незаметное для всего рабочего процесса приложение).


image


image




Итак, нажимая кнопку Install происходят следующие действия (замечу, что без NuGet эти действия необходимо было бы проделать вручную):



  • Закачивается последняя стабильная сборка Elmah библиотеки;

  • Библиотека добавляется в References вашего приложения (если тип проекта – WebSite – сборка добавляется в Bin директорию);

  • В конфигурационном файле регистрируется новая SectionGroup – elmah и регистрируются новые модули и хендлеры. В частности, регистрируется хендлер elmah.axd, который генерирует структурированный отчет об ошибках приложения;

Все. Несколько кликов и механизм доступа администратором ко всем ошибкам приложения реализован. Введем в нашу форму 0 и еще раз запостим нашу форму. Теперь перейдем на страницу elmah.axd в корне нашего приложения. Это зарегестрированный ранее хендлер, управляемый библиотекой Elmah.dll


image


Выбирая детали сообщения мы можем видеть полную информацию об ошибке: место, где было сгенерировано необработанное исключение; ссылку на оригинальную страницу ошибки; содержимое запроса и многое другое. Как видите, более полной информации об проблемном месте сложно себе представить.


Хорошо, идем дальше. Имея удобный интерфейс анализа возникших ошибок, мы может довольно быстро реагировать на работу целой системы, улучшая качество взаимодйствия пользователя с сайтом.


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


Примером может быть исключение, возникающее при попытке скомпроментировать систему на наличие SQL-inject уязвимости. Если наша система все же подвержена на некоторых страницах данной уязвимости, то ожидание в сутки пока администратор заметит попытки взлома, может привести к довольно значительной утечке данных. Посему для более полного контроля над проблемами системы нам хотелось бы в случае возникновения особого типа необработанных исключений, делать уведомления администратору на почтовый ящик для незамедлительного вмешательства в работу системы.


Для решения данной проблемы мы можем использовать возможности ранее подключенной библиотеки Elmah. Для этого в наш конфигурационный файл добавим в секцию <configuration> и в секцию <httpModules> следующие найтройки:


 


<elmah>
<errorMail from="test@test.com"
to="test@test.com"
subject="Application Exception"
async="false"
smtpPort="25"
smtpServer="***"
userName="***"
password="***">
</errorMail>
</elmah>
...
<httpModules>
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
<add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
</httpModules>


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


Для этого добавим в наше веб-приложение отдельный класс VeryImportantException, унаследованный от базового класса Exception. Мы будет автоматически генерировать исключения данного типа в тех местах, где необходимо срочное вмешательство администратора и добавим несколько методов, фильтрующих логику обработки исключений в зависимости от их типа. Итак, новый класс будет предельно простым:


public class VeryImportantException : Exception
{
public VeryImportantException(Exception ex) : base(ex.Message, ex)
{
}
}


В global.asax добавим следующие методы:


public void ErrorLog_Filtering(object sender, ExceptionFilterEventArgs e)
{
Exception exception = e.Exception as VeryImportantException;
if (exception != null)
{
ErrorLog.GetDefault(HttpContext.Current).Log(new Error(exception.InnerException));
e.Dismiss();
}
}

public void ErrorMail_Filtering(object sender, ExceptionFilterEventArgs e)
{
if (!(e.Exception is VeryImportantException))
e.Dismiss();
}


Данные методы помогают добавлять custom логику при обработке исключений библиотекой, вплоть до отмены обработки. В нашем же случае мы отменяем рассылку уведомлений на почту для всех исключений, кроме исключений типа VeryImportantException.


Итак, благодаря возможностям библиотеки Elmah, мы реализовали все необходимые нашему приложению требования относительно логирования ошибок, вплоть до рассылки на почту особо опасных приложению исключений. Теперь, используя следующий синтаксис, мы можем контролировать блоки кода на наличие необработанных исключений, на которые в первую очередь нужно обратить внимание администратора:


int price = int.Parse(lblPrice.Text);
int count = int.Parse(txtPersonCount.Text);

try
{
double calcPrice = price / count;
}
catch (Exception ex)
{
ErrorSignal.FromContext(HttpContext.Current).Raise(new VeryImportantException(ex));
}


Теперь мы можем переловить опасное исключение и сообщить администратору на почту.


image