ASP.NET MVC: простенькая задачка

Программирование

Tagged Under : ,

Итак, у вас есть Action контроллера, в котором содержится следующий фрагмент кода:

ViewBag.Test = "test message";
ViewBag.Alert = "alert message";
ViewData["Alert"] = "alert message 2";

ViewBag.Func = new Func<string, string>(x => {
    ViewBag.Test = x;
    x = ViewBag.Alert;
    ViewBag.Alert = (string)ViewData["Alert"];
    return x;
});

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

@ViewBag.Func("x")
@ViewData["Test"]
@ViewBag.Alert

Какие строки мы увидим на странице и в какой последовательности? Сначала ответьте себе устно, а потом проверьте запустив.

Простейшая реализация long polling в WCF-сервисах на примере текстовой трансляции

Программирование

Tagged Under : , , , , ,

Собственно, про long polling я узнал от своего коллеги Ильи Кубаря. Long polling весьма простое в реализации решение для осуществления реалтайм обновлений на клиентской стороне, например, в чатах или например в текстовых трансляциях футбольных матчей.
Суть метода состоит в следующем:

  • с клиента посылается на сервер AJAX-запрос;
  • на серверной части если данные обновились, то отправляется ответ, если нет – ответ не отправляется до тех пор пока нет обновлений;
  • на клиенте и на сервере выставляется таймаут.

Long polling по сравнению с polling’ом имеет значительные плюсы:

  • данные действительно обновляются моментально
  • уменьшается кол-во запросов с клиента к серверу (не страшно, что они дольше выполняются)

Итак, рассмотрим в качестве простейшего примера прототип текстовой трансляции совместив на одной странице список сообщений и их добавление (сделаем прототип страницы управления так сказать), а также дополнительно посмотрим как сделать wcf-сервис так сказать RESTful-сервисом.
Создадим WCF Service Application и переименуем дефолтный интерфейс к ILongPollingService со следующим содержанием:

[ServiceContract(SessionMode = SessionMode.NotAllowed)] // сессия нам не нужна
public interface ILongPollingService
{
    [OperationContract]
    [WebGet(ResponseFormat = WebMessageFormat.Json)] //метод доступен при обращении по http
    List<Message> PushData(string msg); //новое сообщение от ведущего трансляции

    [OperationContract]
    [WebGet(ResponseFormat = WebMessageFormat.Json)] //метод доступен при обращении по http
    List<Message> GetData(string ID); // ID - это идентификатор последнего сообщения полученного клиентом
}

Далее опишем класс реализующий интерфейс ILongPollingService:

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] //нужно дя поддержки REST'а
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Single, InstanceContextMode = InstanceContextMode.PerCall)]
public class Service : ILongPollingService
{
    static List<Message> list = new List<Message>(); //здесь храним сообщения трансляции

    DateTime end; // время когда нужно выдать ответ

    public List<Message> PushData(string msg) //метод добавляющий сообщения
    {
        string id = Guid.NewGuid().ToString(); //создаём идентификатор
        list.Add(new Message { ID = id, MessageBody = msg }); // добавляем сообщение в список
        return list;
    }

    public List<Message> GetData(string ID) //метод, возвращающий текст трансляции
    {
        if (list.Count > 0)
            if (list[list.Count - 1].ID == ID) //если у клиента то же сообщение, что и последнее в списке добавленных, ждём
            {
                end = DateTime.Now.AddMinutes(2); //через две минуты отдаём то, что есть, чтобы если что, очередь запросов двигалась
                Wait(ID); // ждём
            }
        return list;
    }

    private void Wait(string ID)
    {
        while (list[list.Count - 1].ID == ID && DateTime.Now < end)
        {
            System.Threading.Thread.Sleep(1);
        };
    }
}

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

[DataContract]
public class Message
{
    [DataMember]
    public string ID
    {
        get;
        set;
    }

    [DataMember]
    public string MessageBody
    {
        get;
        set;
    }
}

Для поддержки REST нужно внести кое-какие настройки в кофиг, например:

<system.serviceModel>
    <services>
      <service name="LongPollingService.Service" behaviorConfiguration="LongPollingService.ServiceBehavior">
        <endpoint address="/json" binding="webHttpBinding" contract="LongPollingService.ILongPollingService" behaviorConfiguration="LongPollingServiceJsonBehavior"/>
      </service>
    </services>
    <bindings>
    </bindings>
    <behaviors>
      <endpointBehaviors>
        <behavior name="LongPollingServiceJsonBehavior">
          <webHttp/>
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="LongPollingService.ServiceBehavior">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>

И в разметку LongPollingService.svc добавить Factory:

<%@ ServiceHost Language="C#" Debug="false" Service="LongPollingService.Service" CodeBehind="LongPollingService.svc.cs" Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory" %>

Этих простых шагов достаточно для добавления поддержки REST.

Ну. а код страницы базируется на паре js-функций и очень прост:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="LongPollingService.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>
    <script src="jquery-1.6.4.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        var id = '';

        $(document).ready(function () {
            getmsgs();
        });

        function getmsgs() {
            $.ajax({
                async: true,
                url: '/LongPollingService.svc/json/GetData',
                data: 'ID=' + id,
                type: 'GET',
                dataType: 'json',
                timeout: 130000, //ставим времени больше двух минут, а то будут Aborted-ответы, а нам нужны 200
                success: function (data) {
                    var str = '';
                    for (var index = 0; index < data.length; index++) {
                        str += data[index].MessageBody + '<br/>';
                        id = data[index].ID;
                    }
                    $("#msgs").html(str);
                },
                complete: getmsgs
            });
        }

        function send() {
            $.ajax({
                async: true,
                url: '/LongPollingService.svc/json/PushData',
                data: 'msg=' + $("#txt").val(),
                type: 'GET',
                dataType: 'json',
                success: function (data) {
                    var str = '';
                    for (var index = 0; index < data.length; index++) {
                        str += data[index].MessageBody + '<br/>';
                        id = data[index].ID;
                    }
                    $("#msgs").html(str);
                }
            });
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <div id="msgs">
    </div>
    <input type="text" id="txt" />
    <input type="button" value="send" onclick="send(); return false;" />
    </div>
    </form>
</body>
</html>

CodeBehind абсолютно пуст, поэтому его не привожу.

Исходный код примера можно скачать.

P.S.: возможно в WCF есть встроенная поддержка long polling, но замарачиваться на этот счёт я не стал.

Определение мобильных устройств на ASP.NET-сайтах

Программирование

Tagged Under : , , ,

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

Принцип работы данной библиотеки прост: при запуске приложения, происходит замена стандартного BrowserCapabilitiesProvider на MobileCapabilitiesProvider (делается это через HttpModule). В момент инициализации из файла с устройствами wurfl.xml наполняется коллекция устройств, их свойств и хендлеров им сопоставленных, учитывая, что файл весьма немаленький (~16Мб) весь процесс может занимать значительное время от 5 до 15 секунд (во время данного процесса значительное время отнимают регулярки: очень много вызовов), но в последующем всё отрабатывает очень быстро по сравнению со стандартным механизмом.

Собственно, подключение нового механизма очень хорошо описано на сайте библиотеки, но всё же опишу основные шаги для случая, когда не надо перенаправлять пользователя и отправлять данные о неопределённом устройстве:
1. Добавляем секцию и её описание в web.config

<sectionGroup name="fiftyOne">
  <section name="log" type="FiftyOne.Foundation.Mobile.Configuration.LogSection, FiftyOne.Foundation" requirePermission="false" allowDefinition="Everywhere" restartOnExternalChanges="false" allowExeDefinition="MachineToApplication"/>
  <section name="redirect" type="FiftyOne.Foundation.Mobile.Configuration.RedirectSection, FiftyOne.Foundation" requirePermission="false" allowDefinition="Everywhere" restartOnExternalChanges="false" allowExeDefinition="MachineToApplication"/>
  <section name="wurfl" type="FiftyOne.Foundation.Mobile.Detection.Wurfl.Configuration.WurflSection, FiftyOne.Foundation" requirePermission="false" allowDefinition="Everywhere" restartOnExternalChanges="false" allowExeDefinition="MachineToApplication"/>
</sectionGroup>

<skip />

<fiftyOne>
<log logFile="~/App_Data/Log.txt"
     logLevel="Info"/>
<wurfl wurflFilePath="~/App_Data/wurfl.xml.gz"
       useActualDeviceRoot="false">      
  <wurflPatches>
    <add name="browser_definitions"
           filePath="~/App_Data/web_browsers_patch.xml"
           enabled="true"/>
  </wurflPatches>
</wurfl>
</fiftyOne>

Описание элементов можно посмотреть здесь.
2. Добавляем модуль:

<httpModules>
  <add name="Detector" type="FiftyOne.Foundation.Mobile.Detection.DetectorModule, FiftyOne.Foundation"/>
</httpModules>

<modules>
  <remove name="Detector"/>
  <add name="Detector" type="FiftyOne.Foundation.Mobile.Detection.DetectorModule, FiftyOne.Foundation"/>
</modules>

3. В App_Data кладём wurfl.xml.gz и web_browsers_patch.xml (он нужен если у browser_definitions параметр enabled = true, если нет, то он не понадобится)

4. Кладём сборку FiftyOne.Foundation 4 в bin.

Этого вполне достаточно, чтобы подменить стандартный механизм. После этого мобильные устройства начнут определяться быстрее и точнее, в том числе будут определяться модели устройств, которые не определялись ранее. Например, для user agent’а: Mozilla/5.0 (SymbianOS/9.1; U; [en-us]) AppleWebKit/413 (KHTML, like Gecko) Safari/413 ранее выводилось Unknown, после подключения FiftyOne.Foundation определяется устройство Nokia и модель N73, а также что пользователь зашёл с мобильного устройства.

На последок хочу заметить, что пока заметил только один заметный баг: для user agent’а Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) (что согласно UserAgent Switcher’у соответствует гугл боту) свойство Request.Browser.Crawler возвращает false, что неправильно. Всё возвращается на свои места с отключением browser_definitions (enabled = false) при этом мобильные устройства всё также отлично определяются.

OAuth + ASP.NET (Часть 5): авторизация через Odnoklassniki по протоколу OAuth 2.0

Программирование, Проекты

Tagged Under : , , , , , ,

Итак, на очереди авторизация через одноклассники: я не поленился и всё-таки зарегистрировал тестовое приложение. Вопреки ожиданиям всё оказалось гораздо проще.
Итак, метод контроллера, перебрасывающий пользователя на одноклассники для авторизации и для получения кода (используется разработанная ранее библиотека для авторизации через OAuth 1.0 и 2.0 AOAuthNET):

public ActionResult odnoklassniki()
{
    OAuth2 odnoklassniki = new OAuth2("id вашего приложения", "секретный ключ приложения", "http://www.odnoklassniki.ru/oauth/authorize", "http://api.odnoklassniki.ru/oauth/token.do", "http://twitter.kosfiz.net/auth/odnoklassniki/");
    odnoklassniki.GetAuthCode(new Dictionary<string, string>() { { "scope", "" } });
    return View();
}

Метод контроллера, получающий токен и после данные о пользователе:

public ActionResult odnoklassniki(string code)
{
    if (!string.IsNullOrEmpty(code))
    {
        OAuth2 odnoklassniki = new OAuth2("id вашего приложения", "секретный ключ приложения", "http://www.odnoklassniki.ru/oauth/authorize", "http://api.odnoklassniki.ru/oauth/token.do", "http://twitter.kosfiz.net/auth/odnoklassniki/");
        odnoklassniki.Code = code;
        OAuth2Token token = odnoklassniki.GetAccessToken(new Dictionary<string, string> { { "client_secret", "секретный ключ приложения" } }, OAuth2.AccessTokenType.JsonDictionary);
        if (token != null)
        {
            if (token.dictionary_token != null)
            {
                Dictionary<string, string> dict = new Dictionary<string, string>();
                dict.Add("client_id", "id приложения");
                dict.Add("application_key", "публичный ключ");
                dict.Add("method", "users.getCurrentUser");
                Response.Write(OAuth2UserData.GetOdnoklassnikiUserData(token.dictionary_token["access_token"], "секретный ключ", dict));
            }
        }
    }
    return View();
}

Для получения данных о пользователе пришлось доработать класс OAuth2UserData и добавить следующие методы:

public static string GetOdnoklassnikiUserData(string access_token, string secret_key, Dictionary<string,string> dict)
{
    string sig = GetSig(GetMD5(access_token + secret_key), dict);
    dict.Add("access_token", access_token);
    dict.Add("sig", sig);
    return GetUserData(ODNOKLASSNIKI_ME_URL, dict);
}

private static string GetMD5(string key)
{
    MD5 md5 = MD5.Create();
    byte[] hash = md5.ComputeHash(Encoding.UTF8.GetBytes(key));
    return string.Concat(hash.Select(x => x.ToString("x2")).ToList());
}

Пример авторизации через одноклассники с помощью библиотеки AOAuthNET

Исходный код библиотеки AOAuthNET

OAuth + ASP.NET (Часть 4): авторизация через Windows Live ID по протоколу OAuth 2.0

Программирование, Проекты

Tagged Under : , , , , , ,

В рамках проверки своей библиотеки и её расширения решил заняться интеграцией с Windows Live ID. Совсем недавно live не поддерживал OAuth, но время течёт и всё меняется. Теперь если вы рассчитываете привлечь на сайт аудиторию в том числе и c live-аккаунтами не затрудняя их очередной регистрацией, то возможно использовать OAuth для авторизации пользователя на своём сайте и получения данных о нём.

Ниже я приведу пример как это сделать с помощью библиотеки AOAuthNET для ASP.NET MVC сайта, для классического ASP.NET отличий будет немного.

Итак, метод контроллера, отрабатывающий по нажатию пользователя на кнопке войти

[HttpGet]
public ActionResult live()
{
    OAuth2 live = new OAuth2("id приложения", "секретный ключ", "https://oauth.live.com/authorize", "https://oauth.live.com/token", "на эту страницу будет совершён редирект с передачей кода");
    live.GetAuthCode(new Dictionary<string, string>() { { "display", "page" }, { "scope", "wl.basic" } });
    return View();
}

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

public ActionResult live(string code)
{
    if (!string.IsNullOrEmpty(code))
    {
        OAuth2 live = new OAuth2("id приложения", "секретный ключ", "https://oauth.live.com/authorize", "https://oauth.live.com/token", "на эту страницу будет совершён редирект с передачей кода");
        live.Code = code;
        OAuth2Token token = live.GetAccessToken(new Dictionary<string, string> { { "client_secret", "секретный ключ" }}, OAuth2.AccessTokenType.JsonDictionary);
        if (token != null)
        {
            if (token.dictionary_token != null)
            {
                Response.Write(OAuth2UserData.GetLiveUserData(token.dictionary_token["access_token"]));
            }
        }
    }
    return View();
}

Соответственно, я добавил метод GetLiveUserData, который получает информацию о пользователе (имя, фамилия и т.д.):

public static string GetLiveUserData(string access_token)
{
    return GetUserData(LIVE_ME_URL, new Dictionary<string, string>() { {LIVE_ACCESS_TOKEN, access_token} });
}

Всё просто, о методе GetUserData я писал ранее в первых частях цикла.

Пример работы библиотеки AOAuthNET с windows live id

Библиотека AOAuthNET и исходники

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

OAuth + ASP.NET (Часть 3): авторизация через vkontakte по протоколу OAuth 2.0

Программирование

Tagged Under : , , ,

Буквально вчера узнал, что вконтакте начал открытое тестирование авторизации по протоколу OAuth 2.0 тем самым открыв лёгкий доступ к авторизации через серверный код. Вспомнив, что некогда я писал библиотеку AOAuth.NET для авторизации посредством протоколов OAuth 1.0 и 2.0 я решил интегрировать авторизацию вконтакте в тестовый сайт с помощью данной бибилотеки и при необходимости добавить в неё нужный функционал.

Во-первых, в классе Oauth2 пришлось добавить перегрузки метода GetAuthCode, чтобы он мог принимать дополнительные набор параметров, понадобилось это из-за обязательности параметра display в первом запросе к странице http://api.vkontakte.ru/oauth/authorize для получения кода.

public void GetAuthCode(Dictionary<string, string> additional)
{
    GetAuthCode(_AuthUrl, additional);
}

public void GetAuthCode(string auth_url, Dictionary<string,string> additional)
{
    Dictionary<string, string> parameters = new Dictionary<string, string>();
    parameters.Add(OAUTH_CLIENT_ID, _ClientId);
    parameters.Add(OAUTH_RESPONSE_TYPE, OAUTH_RESPONSE_TYPE_CODE);
    if (!string.IsNullOrEmpty(_RedirectURI))
        parameters.Add(OAUTH_REDIRECT_URI, _RedirectURI);

    if (additional != null)
        foreach (var item in additional)
            parameters.Add(item.Key, item.Value);

    string url = string.Format("{0}{1}", auth_url, OAuthCommonUtils.getParametersString(parameters));
    OAuthCommonUtils.Redirect(url);
}

Собственно, метод контроллера у меня получился аналогичным представленным в предыдущей статье:

[HttpGet]
public ActionResult VK()
{
    OAuth2 vk = new OAuth2("app_id", "secret", "http://api.vkontakte.ru/oauth/authorize", "https://api.vkontakte.ru/oauth/access_token", "http://twitter.kosfiz.net/auth/vk/");
    vk.GetAuthCode(new Dictionary<string, string>() { {"display", "popup"} });
    return View();
}

app_id – id вашего приложения (сайта) в вконтакте,
secret – секретный ключ для вашего приложения можно как и id найти на странице редактирования приложения.

Далее в контроллере принимающем код с вконтакта пишем следующий метод:

public ActionResult vk(string code)
{
    if (!string.IsNullOrEmpty(code))
    {
        OAuth2 vk = new OAuth2("app_id", "secret", "http://api.vkontakte.ru/oauth/authorize", "https://api.vkontakte.ru/oauth/access_token", "http://twitter.kosfiz.net/auth/vk/");
        vk.Code = code;
        OAuth2Token token = vk.GetAccessToken(new Dictionary<string, string> { { "client_secret", "secret" } }, OAuth2.AccessTokenType.JsonDictionary);
        if (token != null)
        {
            if (token.dictionary_token!=null)
            {
            Response.Write(OAuth2UserData.GetVKUserData(token.dictionary_token["access_token"], new Dictionary<string, string> { { "uid", token.dictionary_token["user_id"] } }));
            }
        }
    }
    return View();
}

По сравнению опять же с прошлой статьёй изменился тип парсинга токена, появился OAuth2.AccessTokenType.JsonDictionary и, соответственно, изменился метод GetAccessToken

public OAuth2Token GetAccessToken(string code, Dictionary<string,string> additional, AccessTokenType att)
{
    Dictionary<string, string> parameters = new Dictionary<string, string>();
    parameters.Add(OAUTH_GRANT_TYPE, OAUTH_GRANT_TYPE_VALUE);
    parameters.Add(OAUTH_CODE, code);
    parameters.Add(OAUTH_CLIENT_ID, _ClientId);
    if (!string.IsNullOrEmpty(_RedirectURI))
        parameters.Add(OAUTH_REDIRECT_URI, _RedirectURI);
    if (additional != null)
        foreach (var item in additional)
            parameters.Add(item.Key, item.Value);

    string raw_token = OAuthCommonUtils.sendPostRequest(_TokenUrl, OAuthCommonUtils.getParametersString(parameters).Remove(0,1));
    OAuth2Token token = null;
    switch(att)
    {
        case AccessTokenType.JsonDictionary:
            token = new OAuth2Token();
            JavaScriptSerializer s = new JavaScriptSerializer();
            token.dictionary_token = s.Deserialize<Dictionary<string,string>>(raw_token);
            break;
        case AccessTokenType.Raw:
            token = new OAuth2Token();
            token.raw_token = raw_token;
            break;
        case AccessTokenType.Dictionary:
            token = new OAuth2Token();
            string[] p = raw_token.Split('&');
            foreach (string item in p)
            {
                string[] key_value = item.Split('=');
                token.dictionary_token.Add(key_value[0], key_value[1]);
            }
            break;
        case AccessTokenType.OAuth2Token:
            try
            {
                JavaScriptSerializer serializer = new JavaScriptSerializer();
                token = serializer.Deserialize<OAuth2Token>(raw_token);
                token.raw_token = raw_token;
            }
            catch (Exception exc)
            {
            }
            break;
    }
   
    return token;
}

public enum AccessTokenType
{
    Raw,
    Dictionary,
    JsonDictionary,
    OAuth2Token
}

Ну, и в концовке я добавил следующий метод GetVKUserData

const string VK_ME_URL = "https://api.vkontakte.ru/method/getProfiles";
public static string GetVKUserData(string access_token, Dictionary<string, string> parameters)
{
        parameters.Add("access_token", access_token);
    return GetUserData(VK_ME_URL, parameters);
}

Этого метода достаточно, чтобы имея access_token получить имя и фамилию авторизовавшегося пользователя.

Попробовать можно тут – окно браузера может уменьшаться, я тестировал display = popup
Отсюда можно скачать исходники AOAuth.NET

Оптимизация ASP.NET сайта: кешируем статические ресурсы

Программирование

Tagged Under : , , , ,

Оптимизация, конечно, обширная тема и кеширование находится далеко не на первом месте, но кеширование статических ресурсов на мой взгляд вполне адекватный шаг на пути снижения загрузки на сервер:

  • снижается кол-во запросов к серверу
  • скорость загрузки страницы возрастает

Оба пункта безусловно важны.

Настроить кеширование статики можно через web.config. Например, нужно закешировать статику на 10 дней, тогда можно поступить следующим образом: добавить в раздел system.webServer следующее

<staticContent>
    <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="10.00:00:00" />
</staticContent>

Можно использовать в качестве cacheControlMode параметр UseExpires,

<clientCache cacheControlMode="UseExpires" httpExpires="Sun, 03 Apr 2011 13:15:29 GMT"/>

но тогда для него задаётся конкретное время, что весьма неудобно: придётся периодически обновлять web.config. Плюс ко всему заголовок expires, генерируемый в этом случае, считается устаревшим. Преимущество UseMaxAge в том, что можно указывать относительное время.

Если ресурсы закешированы с помощью max-age или expires, то браузер не будет посылать запросов к серверу (конечно, если в нём не выключено кеширование), а чем меньше запросов, тем меньше нагрузка на IIS и, как следствие, тем быстрее он отвечает на действительно важные запросы.

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

<location path="папка1/папка2">
<system.webServer>
  <staticContent>
    <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="0.00:00:00" />
  </staticContent>
</system.webServer>
</location>

В этом случае браузер будет посылать на сервер заголовок If-Modified-Since, IIS будет сравнивать время последнего изменения статического файла с содержимым заголовка и, если они равны, выдавать в ответ статус код 304, что означает, что файл не изменялся. Т.е. в таком случае запросы будут посылаться браузером к серверу и ресурс будет обновляться в случае изменения.

В случае использования max-age и expires есть весьма неочевидный нюанс: кол-во запросов к серверу за статическими ресурсами будет напрямую зависить от времени, на которое они кешируются. Для статических ресурсов, которые не планируется изменять в ближайшем будущем я бы рекомендовал выставлять интервал в 365 дней.

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

OAuth + ASP.NET (Часть 2): авторизация через facebook, yandex, mail.ru по протоколу OAuth 2.0

Программирование, Проекты

Tagged Under : , , , , , ,

В первой статье, посвящённой авторизации через популярные сервисы с использованием связки OAuth + ASP.NET, я написал небольшой класс для работы по протоколу OAuth 1.0 и вспомогательный класс. Но сейчас начинает активно внедряться вторая версия протокола, которая имеет весьма приличные отличия. Вторую версию, т.е. OAuth 2.0 уже поддерживают facebook, yandex, mail.ru и даже одноклассники. Именно поэтому я продолжил работу над библиотекой AOAuthNET (Another OAuthNET) и решил реализовать класс для авторизации по протоколу OAuth 2.0.

Прежде всего нужно ознакомиться с описания протокола в наших сервисах: mail.ru, yandex, facebook.

В этот раз всё же кратко опишу последовательность действий при авторизации:
1. Перенаправляем пользователя на страницу авторизации, там он подтвержает доступ, сообщая при этом сервису id нашего приложения и то как мы хотим авторизоваться (через js, через запросы между серверами или через обычное приложение);
2. После подтверждения нас переадресуют на страницу либо указанную при редиректе пользователя либо в настройках нашего приложения. На эту страницу получаем параметр code;
3. Берём полученный код и запрашиваем сервис по адресу получения маркера доступа сообщив например секретный ключ, код, id приложения, тип подтверждения и обратный адрес, если требуется. Если всё хорошо, то в ответе получим маркер доступа;
4. Запрашиваем данные о пользователе и радуемся жизни.

Всё это нашло отражение в классе OAuth2 Читать дальше »

OAuth + ASP.NET (Часть 1): авторизуемся через twitter, google и yahoo по протоколу OAuth 1.0

Программирование, Проекты

Tagged Under : , , , , , , , ,

Собственно, понадобилось сделать авторизацию через аккаунты различных популярных сервисов: twitter, facebook, mail.ru, google, yandex и по возможности ещё каких-нибудь. Так как я являюсь ярым сторонником подхода: если есть достаточно времени, то лучше сделать самому, – я решил реализовать это самостоятельно: изобрести велосипед в очередной раз.

Функционал будет минимальным: пройти авторизацию, получить маркер доступа (access_token) и идентификатор пользователя, ник, опционально что-нибудь ещё, например, email.

Итак, начиная с этой статьи, посвященной авторизации через twitter, google и yahoo (взят для дополнительного примера), используя и постигая OAuth 1.0, я попробую начать серию статей об OAuth-авторизации (в том числе и версии протокола 2.0 с отдельным классом и примерами для mail.ru, yandex’а, фейсбука и т.д.) через различные сервисы, поддерживающие её. При этом результатом будет небольшая библиотека с описанной функциональностью.
Читать дальше »

HtmlAgilityPack: фильтруем html в сообщениях и страницах

Программирование

Tagged Under : , , , ,

О HtmlAgilityPack недавно писали, кстати, тут.

Я же применял библиотеку в работе дважды:

  • При работе с Search Server’ом;
  • При реализации комментирования на одном из проектов.

Причем впервые где-то полгода назад, и по наводке Steve Sanderson‘а.

Тогда передо мной стояла задача «скормить» Search Server’у только желаемый контент со страниц. Дело в том, что его поисковый бот весьма далёк от совершенства и зачастую случается так, что нужно приложить усилия (показывать боту только нужное содержимое), чтобы выдаваемые им результаты стали вполне добротными.
Читать дальше »