{
    "version": "https://jsonfeed.org/version/1",
    "title": "Сергей Звездин",
    "description": "Блог о разработке программного обеспечения на C#, .NET, ASP.NET и Visual Studio",
    "home_page_url": "https://blog.zwezdin.com",
    "feed_url": "https://blog.zwezdin.com/feed.json",
    "icon": "https://blog.zwezdin.com/assets/cover/sergeyzwezdin.292a538a.jpg",
    "favicon": "https://blog.zwezdin.com/assets/favicon/favicon-194x194.png",
    "items": [
        {
            "id": "https://blog.zwezdin.com/2017/vs-2017-editorconfig/",
            "url": "https://blog.zwezdin.com/2017/vs-2017-editorconfig/",
            "title": "EditorConfig для Visual Studio 2017",
            "date_published": "2017-03-25T01:00:00.000Z",
            "content_html": "<p>Если над проектом работает команда из нескольких человек, то она неизбежно сталкивается с проблемой стиля кодирования. Кто-то скобки расставил по-своему, кому-то больше нравится <code>var</code> вместо указания явного типа, кому-то — наоборот. Чего уж там говорить про open-source проекты, где случайный прохожий может сделать баг-фикс и уйти. Всё это ведет к тому, что стиль кода в проекте сильно разнится в разных его частях.</p>\n<p><strong>EditorConfig</strong> — это проект, который помогает разработчикам соблюдать единый стиль кода в рамках всего проекта. Visual Studio 2017 — одна из множества сред разработки, которая имеет встроенную поддержку EditorConfig.</p>\n<h2>EditorConfig</h2><p>Проект <a href=\"http://editorconfig.org/\"><strong>EditorConfig</strong></a> можно разделить на две части — формат файла, описывающий правила оформления кода и плагины для среды разработки, которые помогают эти правила соблюдать.</p>\n<p>Практически все современные среды разработки имеют либо встроенные средства для поддержки EditorConfig, либо поддерживают EditorConfig за счёт устанавливаемых плагинов:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2017/vs-2017-editorconfig/835b3fa4/ides@2x.png\"  alt=\"EditorConfig для Visual Studio 2017\"/>\n\n\n\n\n<p>Чтобы добавить правила для стиля кода, достаточно положить файл <code>.editorconfig</code> в корневую папку вашего проекта. При этом можно изменять эти правила в отдельных подпапках — для этого создаем дополнительный файл <code>.editorconfig</code> в нужной подпапке. Т.е. правила EditorConfig применяются иерархически.</p>\n<p>Формат файла <code>.editorconfig</code> напоминает INI-файлы с немного расширванным синтаксисом.</p>\n<p>Для корневого файла <code>.editorconfig</code> в самом начале нужно добавить <code>root = true</code>. Символы <code>#</code> и <code>;</code> позволяют добавить комментарии в файл. Превоначальная заготовка выглядит так:</p>\n<pre class=code language-ini><code class=\"code__container language-ini\"># Правила EditorConfig для\n# описания стиля кодирования\n\nroot = true</code></pre>\n<p>Далее следует описание правил. Но перед этим необходимо определить типы файлов, к которым эти правила должны применяться. Типы файлов задаются в стиле INI-секции:</p>\n<pre class=code language-ini><code class=\"code__container language-ini\">root = true\n\n[*]\nindent_style = space\nindent_size = 2\n\n[*.cs]\nindent_size = 4</code></pre>\n<p>Как видно, правила для <em>всех</em> файлов задаются в секции <code>[*]</code>.</p>\n<p>Указать типы файлов можно при помощи специальных шаблонов, при написании которых действуют следующие правила:</p>\n<ul>\n<li><code>*</code> — означает множество любых символов, кроме <code>/</code></li>\n<li><code>**</code> — означает множество любых символов, включая <code>/</code></li>\n<li><code>?</code> — означает любой единственный символ</li>\n<li><code>[name]</code> — явно задает имя файла</li>\n<li><code>[!name]</code> — применяется ко всем файлам, кроме указанного</li>\n<li><code>&#123;s1,s2,s3&#125;</code> — применяется ко всем файлам, указанным в списке</li>\n<li><code>&#123;x..y&#125;</code> — позволяет задать целочисленный диапазон</li>\n</ul>\n<p>Вот некоторые примеры описания типа файлов:</p>\n<ul>\n<li><code>[*.cs]</code> — все файлы с расширением <code>cs</code></li>\n<li><code>[*.&#123;cs,vb&#125;]</code> — все файлы с расширением <code>cs</code> и <code>vb</code></li>\n<li><code>[Properties/AssemblyInfo.cs]</code> — файл <code>AssemblyInfo.cs</code> из папки <code>Properties</code></li>\n<li><code>[Model/**.js]</code> — все файлы <code>.js</code> из папки <code>Model</code>, включая подпапки</li>\n<li><code>[&#123;appsettings.json, appsettings.dev.json, appsettings.test.json&#125;]</code> — файлы из списка</li>\n</ul>\n<h2>Правила</h2><p>Для каждой секции нужно задать правила. Правила зависят от того, для какого языка программирования вы их пишете.</p>\n<p>Правила задаются в следующем формате:</p>\n<blockquote>\n<p><code>&lt;имя правила&gt;</code> = <code>&lt;значение&gt;</code></p>\n</blockquote>\n<p>Для <code>C#</code> также можно задавать степень важности правила:</p>\n<blockquote>\n<p><code>&lt;имя правила&gt;</code> = <code>&lt;значение&gt;</code> : <code>&lt;важность правила&gt;</code></p>\n</blockquote>\n<p>Задание уровня важности правила позволяет повлиять на поведение компилятора и среды разработки при нарушении правила. Уровень важности может принимать следующие значения:</p>\n<ul>\n<li><code>none</code> — нет никакой реакции, если правило было нарушено.</li>\n<li><code>suggestion</code> — при нарушении правила в момент сборки проекта появляется рекомендация во вкладке <code>Messages</code> в <code>Error List</code>.</li>\n<li><code>warning</code> — при нарушении правила в момент сборки проекта появляется ошибка во вкладке <code>Warnings</code> в <code>Error List</code>. В этом случае сборка останавливается, если установлена опция <code>Treat Warnings as Errors</code>.</li>\n<li><code>error</code> — при нарушении правила в момент сборки проекта появляется ошибка во вкладке <code>Errors</code> в <code>Error List</code> и сборка останавливается.</li>\n</ul>\n<p>Например, для правила:</p>\n<pre class=code language-ini><code class=\"code__container language-ini\">[*.cs]\ncsharp_style_var_for_built_in_types = false:warning</code></pre>\n<p>Мы увидим только предупреждение в момент сборки, если правило было нарушено.</p>\n<p>Но если правило изменить так:</p>\n<pre class=code language-ini><code class=\"code__container language-ini\">[*.cs]\ncsharp_style_var_for_built_in_types = false:error</code></pre>\n<p>То вы не сможете собрать проект, пока ошибка не будет исправлена.</p>\n<p>Список наиболее популярных правил можно найти <a href=\"https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties\">здесь</a>. Для .NET список правил <a href=\"https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#this_and_me\">здесь</a>. В качестве примера <code>.editorconfig</code> для .NET можно посмотреть <a href=\"https://github.com/dotnet/roslyn/blob/master/.editorconfig\">файл команды Roslyn</a>.</p>\n<p>Создадим правила для проекта .NET:</p>\n<pre class=code language-ini><code class=\"code__container language-ini\">root = true\n\n[*]\n# Использовать отсутпы для пробелов\nindent_style = space\n\n# Использовать 4 пробела для отступа\nindent_size = 4\n\n# Использовать UTF-8 для всех файлов\ncharset = utf-8\n\n# Запретить пробелы в конце строки\ntrim_trailing_whitespace = false\n\n# Запретить пустую строку в конце файла\ninsert_final_newline = false\n\n[*.cs]\n# Использовать явное указание простых типов\ncsharp_style_var_for_built_in_types = false:warning\n\n# Использовать var когда тип очевиден исходя\n# из правой части выражения\ncsharp_style_var_when_type_is_apparent = true:warning\n\n# Во всех остальный случаях также использовать var\ncsharp_style_var_elsewhere = true:warning\n\n# Новая строка перед открывающей скобкой\ncsharp_new_line_before_open_brace = true:error\n\n# Новая строка перед членами в анонимных типах\ncsharp_new_line_before_members_in_anonymous_types = true:warning</code></pre>\n<p>Если в <code>.editorconfig</code> не указано какое-либо из правил, то считается, что нужно использовать текущие настройки среды разработки.</p>\n<h2>Visual Studio</h2><p><strong>Visual Studio 2017</strong> имеет встроенную поддержку EditorConfig, т.е. вы просто можете добавить файл <code>.editorconfig</code> в проект и пользоваться этим инструментом. Для более ранних версий Visual Studio можно воспользоваться <a href=\"https://marketplace.visualstudio.com/items?itemName=EditorConfigTeam.EditorConfig\">расширением EditorConfig</a>.</p>\n<blockquote>\n<p><strong>Важно!</strong> Настройки <code>.editorconfig</code> имеют приоритет над настройками среды разработки.<br>Это означает, что если у вас в Visual Studio, например, установлен отступ в 4 символа, а в <code>.editorconfig</code> — два, то среда разработки будет считать, что актуальная настройка — два символа.</p>\n</blockquote>\n<p>При подключении <code>.editorconfig</code> к проекту Visual Studio автоматически подхватит эти настройки. Теперь если вы отформатируете документ при помощи меню <code>Edit</code> → <code>Advanced</code> → <code>Format Document</code> или <kbd><kbd>Ctrl</kbd> + <kbd>e</kbd>, <kbd>D</kbd></kbd>, то документ будет отформатирован в соответствии с правилами <code>.editorconfig</code>:</p>\n\n\n\n    \n\n\n\n\n<p>Если при сборке проекта какое-то правило нарушено, то вы увидите об этом ошибку:</p>",
            "tags": [
                "C#",
                ".NET",
                "Visual Studio"
            ],
            "banner_image": "https://blog.zwezdin.com/2017/vs-2017-editorconfig/c34cda2a/vs_2017-editorconfig@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2017/asp-net-core-logging-into-kibana/",
            "url": "https://blog.zwezdin.com/2017/asp-net-core-logging-into-kibana/",
            "title": "Подключаем Kibana для хранения логов ASP.NET Core",
            "date_published": "2017-03-19T06:00:00.000Z",
            "content_html": "<p>Логи — полезные инструмент для отладки и мониторинга приложений. Только просмотр текстовых логов — дело скучное и неинтересное. Поэтому изучают логи в исключительных ситуациях. Здесь на помощь приходят <strong>структурированные логи</strong> — когда записи хранятся не просто в виде текста, а в виде структурированных данных с разделением информации на отдельные поля. По таким логам можно настроить фильтры, поиск, при соблюдении определенных условий — отправлять уведомления и т.д.</p>\n<p>Инфраструктура логгирования <strong>ASP.NET Core</strong> поддерживает структурированные логи. Посмотрим как это можно использовать на примере ELK.</p>\n<h2>Elasticsearch и Kibana</h2><p>Один из вариантов хранения структурированных логов — хранение их в Elasticsearch. Преимущество этого подхода заключается в том, что для этого всё уже готово — вам не нужно допиливать код для хранения данных.</p>\n<p>ELK — это стек из трех продуктов: Elasticsearch, Logstash и Kibana:</p>\n<ul>\n<li><strong>Elasticsearch</strong> — хранилище данных логов, позволяет искать по большим объемам данных с приемлемой скоростью.</li>\n<li><strong>Kibana</strong> — удобный веб-интерфейс к хранилищу данных.</li>\n<li><strong>Logstash</strong> — агрегатор, который забирает логи с жесткого диска и отправляет их в Logstash.</li>\n</ul>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2017/asp-net-core-logging-into-kibana/a61527e6/kibana@2x.png\"  alt=\"Веб-интерфейс Kibana. Источник: https://www.elastic.co/blog\"/><figcaption>Веб-интерфейс Kibana.\n<cite>Источник: <a href=\"https://www.elastic.co/blog/beats-5-0-0-alpha1-released\" rel=\"author\" target=\"_blank\">https://www.elastic.co/blog</a></cite></figcaption></figure>\n\n\n\n\n<p>Чтобы попробовать ELK можно использовать Docker-образ <a href=\"https://hub.docker.com/r/sebp/elk/\">sebp/elk</a>:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">docker pull sebp/elk\ndocker run -p 5601:5601 -p 9200:9200 -p 5044:5044 -it --name elk sebp/elk</code></pre>\n<p>Подробнее о том, как запустить ELK внутри Docker можно почитать в <a href=\"http://elk-docker.readthedocs.io/\">документации</a>.</p>\n<p>После запуска Kibana будет доступна через порт <code>5601</code>, а Elasticsearch API — через <code>9200</code>.</p>\n<p>Для реального проекта Elasticsearch весьма требователен к оборудванию (в частности - к RAM). Поэтому хорошим вариантом может быть использование ELK как сервиса. К счастью, таких сервисов очень много, например — <a href=\"https://logit.io/\">раз</a>, <a href=\"https://logz.io/product/\">два</a>, <a href=\"https://sematext.com/logsene/\">три</a>.</p>\n<h2>Отправка в данных в Elasticsearch</h2><p>Если для отправки данных используется <strong>Logstash</strong>, то всё сводится к записи логов в файл и настройки Logstash для чтения этих файлов и отправки в Elasticsearch. Альтернативным способом может быть отправка данных напрямую в Elasticsearch, минуя Logstash. Рассмотрим этот способ.</p>\n<p>Стоит отметить, что в данном случае нам потребуется какая-нибудь библиотека для работы с Elasticsearch. Желательно также, чтобы эта библиотека была интегрирована с <a href=\"/2017/asp-net-core-logging/\">инфраструктурой логгирования ASP.NET Core</a>. С учетом этого, <a href=\"https://serilog.net/\">Serilog</a> может быть хорошим выбором: она интегрирована с ASP.NET Core и имеет провайдер для отправки логов в Elastisearch.</p>\n<p>Установим в проект пакет <code>Serilog</code>, а заодно <code>Serilog.Extensions.Logging</code> и <code>Serilog.Sinks.Elasticsearch</code>.</p>\n<p>Теперь необходимо добавить Serilog-логгер в DI-контейнер:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Startup\n&#123;\n  public void ConfigureServices(IServiceCollection services)\n  &#123;\n    services.AddSingleton&lt;Serilog.Core.Logger>(container => new LoggerConfiguration()\n      .ConfigureEnrichers(container)\n      .ConfigureElk(\"http://elk:9200\", Configuration.GetSection(\"Logging:Elk\"), container)\n      .ConfigureSelfLog(container)\n      .CreateLogger());\n  &#125;</code></pre>\n<p>И в список логгинг-провайдеров:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">  public void Configure(IApplicationBuilder app, IHostingEnvironment env,\n    ILoggerFactory loggerFactory)\n  &#123;\n    loggerFactory.AddSerilog(services.GetService&lt;Serilog.Core.Logger>());\n  &#125;\n&#125;</code></pre>\n<p>Обратите внимание, что при конфигурировании ELK указана конфигурационная секция <code>Logging:Elk</code>. В ней мы указываем параметры фильтров, о которых я писал <a href=\"/2017/asp-net-core-logging/\">ранее</a>.</p>\n<p>Для данного примера конфигурационный файл может выглядеть так:</p>\n<pre class=code language-json><code class=\"code__container language-json\">&#123;\n  \"Logging\": &#123;\n    \"Console\": &#123;\n      \"IncludeScopes\": false,\n      \"LogLevel\": &#123;\n        \"Default\": \"Debug\",\n        \"System\": \"Information\",\n        \"Microsoft\": \"Information\"\n      &#125;\n    &#125;,\n    \"Elk\": &#123;\n      \"IncludeScopes\": false,\n      \"LogLevel\": &#123;\n        \"MyApp.Services\": \"Information\",\n        \"MyApp.Infrastructure\": \"Information\"\n      &#125;\n    &#125;\n  &#125;\n&#125;</code></pre>\n<p>Теперь можно воспользоваться стандартным API для логгирования и создавать записи в в ELK:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class HomeController : Controller\n&#123;\n  private readonly ILogger _logger;\n\n  public HomeController(ILogger&lt;HomeController> logger)\n  &#123;\n    _logger = logger;\n  &#125;\n\n  public IActionResult Index()\n  &#123;\n    _logger.LogError(\"Some error occured\");\n    return View();\n  &#125;\n&#125;</code></pre>\n<p>Однако, гораздо интереснее в таком случае писать в лог структурированную информацию. Для этого немного изменим способ логгирования:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class HomeController : Controller\n&#123;\n  private readonly ILogger _logger;\n\n  public HomeController(ILogger&lt;HomeController> logger)\n  &#123;\n    _logger = logger;\n  &#125;\n\n  public IActionResult Index()\n  &#123;\n    var request = ...;\n    var config = ...;\n    _logger.LogError(\"Some error occured.&#123;User&#125;&#123;@Request&#125;&#123;@Config&#125;\",\n      User.Identity.Name, request, config);\n    return View();\n  &#125;\n&#125;</code></pre>\n<p>Теперь данные отправляются в ELK в структурированном виде.</p>\n<p>Если для <em>каждой</em> записи в лог требуется сохранять какие-то стандартные данные (например, данные HTTP-запроса, окружения и т.д.) можно воспользоваться специальными объектами Serilog — <code>Enricher</code>‘ами. О том, как их использовать можно почитать в <a href=\"https://github.com/serilog/serilog/wiki/Enrichment\">документации</a>.</p>",
            "tags": [
                "ASP.NET",
                ".NET Core",
                "Logging"
            ],
            "banner_image": "https://blog.zwezdin.com/2017/asp-net-core-logging-into-kibana/83262f2d/asp_net-core-logging-into-kibana@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2017/asp-net-core-custom-logging-provider/",
            "url": "https://blog.zwezdin.com/2017/asp-net-core-custom-logging-provider/",
            "title": "Создание собственного провайдера логгирования в ASP.NET Core",
            "date_published": "2017-03-12T06:00:00.000Z",
            "content_html": "<p><strong>ASP.NET Core</strong> предоставляет <a href=\"/2017/asp-net-core-logging/\">удобную абстракцию</a> для ведения логов. Благодаря этому работа с логами устроена одинаково, независимо от того, какой провайдер используется в приложении.</p>\n<p>Провайдеры в свою очередь обеспечивают инфраструктуру для хранения логов в том или ином хранилище. Базовые библиотеки уже имеют набор стандартных провайдеров для наиболее популярных хранилищ. Но что если требуется отправлять логи в собственное, нестандартное хранилище? Для этого можно реализовать <em>собственный провайдер</em>.</p>\n<p>Предположим на требуется отправлять информацию об ошибках из лога при помощи SMS через сторонний сервис.</p>\n<h2>Логгер</h2><p>Для начала нам требуется реализовать <em>логгер</em>. <strong>Логгер</strong> — это объект, который непосредственно реализует логику сохранения в конкретный тип хранилища. Он в курсе всех деталей и тонкостей именно этого хранилища.</p>\n<p>Для реализации собственного логгера, следует создать объект, реализующий интерфейс <code>ILogger</code>. Этот интерфейс имеет три метода:</p>\n<ul>\n<li><code>BeginScope()</code> — позволяет определить группу сообщений. Если логгер поддерживает scope, то здесь следует вернуть <code>IDisposable</code>.</li>\n<li><code>IsEnabled()</code> — возвращает информацию о том, доступен ли данный логгер для заданного уровня логгирования.</li>\n<li><code>Log()</code> — метод, непосредственно реализующий логику сохранения записи в лог.</li>\n</ul>\n<p>Первая реализация логгера может выглядеть так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class SmsLogger : ILogger\n&#123;\n  public SmsLogger()\n  &#123;\n  &#125;\n\n  public IDisposable BeginScope&lt;TState>(TState state)\n  &#123;\n    return null;\n  &#125;\n\n  public bool IsEnabled(LogLevel logLevel)\n  &#123;\n    return true;\n  &#125;\n\n  public void Log&lt;TState>(LogLevel logLevel, EventId eventId,\n    TState state, Exception exception, Func&lt;TState, Exception, string> formatter)\n  &#123;\n    if (IsEnabled(logLevel))\n    &#123;\n      var msg = formatter(state, exception);\n\n      // send sms here\n    &#125;\n  &#125;\n&#125;</code></pre>\n<p>В этой реализации отсутствует фильтрация, поэтому <code>IsEnabled()</code> всегда возвращает <code>true</code>. Также отправка SMS не поддерживает scope, поэтому <code>BeginScope()</code> возвращает <code>null</code>.</p>\n<p>Внутри метода <code>Log()</code> следует обратиться к внешнему сервису для отправки сообщения. Здесь можно использовать любой сторонний сервис и просто отправлять ему HTTP-запрос всякий раз, когда требуется логгирование.</p>\n<h2>Провайдер для логгера</h2><p>Чтобы подключить логгер к приложению требуется провайдер. Провайдер — это объект, который при необходимости создает логгер. Также провайдер занимается освобождением ресурсов. Для реализации провайдера нужно создать объект, реализующий интерфейс <code>ILoggerProvider</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class SmsLoggerProvider : ILoggerProvider\n&#123;\n  public SmsLoggerProvider()\n  &#123;\n  &#125;\n\n  public ILogger CreateLogger(string category)\n  &#123;\n    return new SmsLogger();\n  &#125;\n\n  public void Dispose()\n  &#123;\n  &#125;\n&#125;</code></pre>\n<h2>Подключаем провайдер к приложению</h2><p>Теперь чтобы подключить провайдер к приложению, достаточно добавить этот провайдер в коллекцию провайдеров:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Startup\n&#123;\n  public void ConfigureServices(IServiceCollection services)\n  &#123;\n  &#125;\n\n  public void Configure(IApplicationBuilder app, IHostingEnvironment env,\n    ILoggerFactory loggerFactory)\n  &#123;\n    loggerFactory.AddProvider(new SmsLoggerProvider());\n  &#125;\n&#125;</code></pre>\n<p>Можно добавить немного синтаксического сахара и сделать метод-расширение для добавления провайдера:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class SmsLoggerProviderExtensions\n&#123;\n  public static ILoggerFactory AddSms(this ILoggerFactory loggerFactory)\n  &#123;\n    loggerFactory?.AddProvider(new SmsLoggerProvider());\n    return loggerFactory;\n  &#125;\n&#125;</code></pre>\n<p>Тогда код добавления провайдера можно переписать следующим образом:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Startup\n&#123;\n  public void ConfigureServices(IServiceCollection services)\n  &#123;\n  &#125;\n\n  public void Configure(IApplicationBuilder app, IHostingEnvironment env,\n    ILoggerFactory loggerFactory)\n  &#123;\n    loggerFactory.AddSms();\n  &#125;\n&#125;</code></pre>\n<h2>Поддержка фильтров</h2><p>Фильтры <a href=\"/2017/asp-net-core-logging/\">позволяют</a> настроить какие именно записи будут попадать в лог для данного провайдера. Для нашего примера это особенно актуально — было бы глупо отправлять через SMS информацию о всех HTTP-запросах. Поэтому давайте добавим поддержку фильтров для нашего провайдера.</p>\n<p>В первую очередь добавим поддержку фильтров в реализацию логгера:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class SmsLogger : ILogger\n&#123;\n  private readonly string _category;\n\n  private readonly Func&lt;string, LogLevel, bool> _filter;\n\n  public SmsLogger(string category, Func&lt;string, LogLevel, bool> filter)\n  &#123;\n    _category = category;\n    _filter = filter;\n  &#125;\n\n  public IDisposable BeginScope&lt;TState>(TState state)\n  &#123;\n    return null;\n  &#125;\n\n  public bool IsEnabled(LogLevel logLevel)\n  &#123;\n    return _filter == null || _filter(_category, logLevel);\n  &#125;\n\n  public void Log&lt;TState>(LogLevel logLevel, EventId eventId,\n    TState state, Exception exception, Func&lt;TState, Exception, string> formatter)\n  &#123;\n    if (IsEnabled(logLevel))\n    &#123;\n      var msg = formatter(state, exception);\n      // send sms here\n    &#125;\n  &#125;\n&#125;</code></pre>\n<p>Как видно, код поменялся не очень существенно: конструктор теперь получает информацию о категории и функцию-фильтр. Эти данные используются в методе <code>IsEnabled()</code>.</p>\n<p>Для задания фильтра немного изменим реализацию провайдера:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class SmsLoggerProvider : ILoggerProvider\n&#123;\n  private readonly Func&lt;string, LogLevel, bool> _filter;\n\n  public SmsLoggerProvider(Func&lt;string, LogLevel, bool> filter)\n  &#123;\n    _filter = filter;\n  &#125;\n\n  public ILogger CreateLogger(string category)\n  &#123;\n    return new SmsLogger(category, _filter);\n  &#125;\n\n  public void Dispose()\n  &#123;\n  &#125;\n&#125;</code></pre>\n<p>И метода-расшрения для подключения провайдера:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class SmsLoggerProviderExtensions\n&#123;\n  public static ILoggerFactory AddSms(this ILoggerFactory loggerFactory,\n    Func&lt;string, LogLevel, bool> filter = null)\n  &#123;\n    loggerFactory?.AddProvider(new SmsLoggerProvider(filter));\n    return loggerFactory;\n  &#125;\n&#125;</code></pre>\n<p>Теперь при добавлении провайдера можно явно указать как именно нужно фильтровать сообщения, отправляемые через SMS:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Startup\n&#123;\n  public void ConfigureServices(IServiceCollection services)\n  &#123;\n  &#125;\n\n  public void Configure(IApplicationBuilder app, IHostingEnvironment env,\n    ILoggerFactory loggerFactory)\n  &#123;\n    loggerFactory.AddSms((category, level) =>\n    &#123;\n      if (category.Contains(\"MyApp\") &amp;&amp; level == LogLevel.Critical)\n        return true;\n\n      return false;\n    &#125;);\n  &#125;\n&#125;</code></pre>",
            "tags": [
                "ASP.NET",
                ".NET Core",
                "Logging"
            ],
            "banner_image": "https://blog.zwezdin.com/2017/asp-net-core-custom-logging-provider/b0a3c476/asp_net-core-custom-logging-provider@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2017/asp-net-core-logging/",
            "url": "https://blog.zwezdin.com/2017/asp-net-core-logging/",
            "title": "Логгирование в ASP.NET Core",
            "date_published": "2017-03-11T10:00:00.000Z",
            "content_html": "<p>Поскольку логгирование является неотъемлемой частью любого приложения, в <strong>ASP.NET Core</strong> появилась унифицированная инфраструктура для логгирования.</p>\n<p>Единый интерфейс для записи в лог позволяет приложениям работать с логом в едином стиле, независимо от того, какой провайдер используется.</p>\n<h2>Настройка инфраструктуры логгирования</h2><p>Прежде чем начать писать что-то в логи, нужно настроить провайдеров логов. Для этого в метод <code>Configure()</code> класса <code>Startup</code> инжектируется сервис <code>ILoggerFactory</code>. Используя этот сервис можно настроить провайдеров:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Startup\n&#123;\n  public void ConfigureServices(IServiceCollection services)\n  &#123;\n  &#125;\n\n  public void Configure(IApplicationBuilder app, IHostingEnvironment env,\n    ILoggerFactory loggerFactory)\n  &#123;\n    loggerFactory.AddDebug();\n\n    // ...\n  &#125;\n&#125;</code></pre>\n<p>В данном примере добавялется провайдер, который будет писать логи в консоль отладчика. Чтобы добавить этого провайдера, нужно установить пакет <code>Microsoft.Extensions.Logging.Debug</code>. Впрочем, он уже итак установлен в проекте, который создается в базовом шаблоне проекта ASP.NET MVC.</p>\n<p>Аналогичным образом добавляются и другие провайдеры. Microsoft предоставляет такие провайдеры как <code>Microsoft.Extensions.Logging.Console</code>, <code>Microsoft.Extensions.Logging.Debug</code>, <code>Microsoft.Extensions.Logging.TraceSource</code>, <code>Microsoft.Extensions.Logging.AzureAppServices</code>, <code>Microsoft.Extensions.Logging.EventSource</code>, <code>Microsoft.Extensions.Logging.EventLog</code>. Как нетрудно догадаться из названия, каждый из них имеет собственный источник для хранения логов.</p>\n<p>Можно использовать несколько провайдеров одновременно:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Startup\n&#123;\n  public void Configure(IApplicationBuilder app, IHostingEnvironment env,\n    ILoggerFactory loggerFactory)\n  &#123;\n    loggerFactory.AddDebug();\n    loggerFactory.AddConsole();\n\n    // ...\n  &#125;\n&#125;</code></pre>\n<h2>Запись в лог</h2><p>Для того, чтобы начать писать что-то в лог, следует инжектировать интерфейс <code>ILogger</code> в нужный компонент приложения. Причем в качестве generic-параметра мы можем указать категорию логгера. Например, получим доступ к логгеру в компоненте <code>HomeController</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class HomeController : Controller\n&#123;\n  private readonly ILogger _logger;\n\n  public HomeController(ILogger&lt;HomeController> logger)\n  &#123;\n    _logger = logger;\n  &#125;</code></pre>\n<p>После этого в рамках компонента можно пользоваться этим логгером и добавлять записи в лог. Для записи используются методы, соответствующие уровню важности сообщения:</p>\n<ul>\n<li><code>LogDebug()</code></li>\n<li><code>LogTrace()</code></li>\n<li><code>LogInformation()</code></li>\n<li><code>LogWarning()</code></li>\n<li><code>LogError()</code></li>\n<li><code>LogCritical()</code></li>\n</ul>\n<p>Попробуем записать что-нибудь в лог:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public IActionResult Index()\n&#123;\n  _logger.LogError(\"Some error occured\");\n  return View();\n&#125;</code></pre>\n<p>Теперь при срабатывании этого действия в консоли отладчика обязательно появится запись:</p>\n<blockquote>\n<p>WebApplication8.Controllers.HomeController:Error: Some error occured</p>\n</blockquote>\n<p>Если при настройке провайдеров мы настроили также вывод в консоль (<code>AddConsole()</code>), то эта же запись появится и в консоли (если мы запускаем приложение через Kestrel и консоль нам видна).</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2017/asp-net-core-logging/2ae0d79f/console-logger@2x.png\"  alt=\"Логгирование в ASP.NET Core\"/>\n\n\n\n\n<p>Записывать в лог можно не только обычные строки, но и структурированные данные. Для этого существуют специальные перегрузки методов <code>LogXXX()</code>. Запишем, например, в лог данные с идентификатором объекта:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public IActionResult Index()\n&#123;\n  _logger.LogError(\"Unable to delete object with &#123;ID&#125; key\", 25);\n  return View();\n&#125;</code></pre>\n<p>В логе появится строка следующего содержания:</p>\n<blockquote>\n<p>WebApplication8.Controllers.HomeController:Error: Unable to delete object with 25 key</p>\n</blockquote>\n<p>Если провайдер поддерживает структурированные данные, то <code>ID</code> будет доступно как отдельное поле. Более того, поле может быть не только простым типом, но и составным объектом. Но это — отдельная тема и здесь касаться этого не будем.</p>\n<h2>Scope</h2><p>Иногда бывает полезно объединить набор сообщений лога в группы. Это можно сделать используя метод <code>BeginScope()</code>, реализующий <code>IDisposable</code>. Поэтому можно использовать конструкцию <code>using</code> для группировки сообщений в логе:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public IActionResult Index()\n&#123;\n  using (_logger.BeginScope(\"Updating object &#123;ID&#125;\", 25))\n  &#123;\n    _logger.LogInformation(\"Checking object state\");\n\n    // ...\n\n    _logger.LogInformation(\"Updating object reference\");\n\n    // ...\n\n    _logger.LogError(\"Something went wrong\");\n  &#125;\n\n  return View();\n&#125;</code></pre>\n<p>Не все провайдеры поддерживают такую группировку. Однако, те, кто поддерживают будут иметь пометку <code>Updating object &#123;ID&#125;</code> для каждой записи внутри scope.</p>\n<p>Например, группировку поддерживает <code>Microsoft.Extensions.Logging.Console</code>. Для её использования это нужно указать явно при настройке провайдера:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Startup\n&#123;\n  public void Configure(IApplicationBuilder app, IHostingEnvironment env,\n    ILoggerFactory loggerFactory)\n  &#123;\n    loggerFactory.AddConsole(includeScopes: true);\n\n    // ...\n  &#125;\n&#125;</code></pre>\n<p>В таком случае для каждого сообщения внутри scope будет добавлена строка <code>Updating object &#123;ID&#125;</code>:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2017/asp-net-core-logging/29d3e723/logging-scope@2x.png\"  alt=\"Логгирование в ASP.NET Core\"/>\n\n\n\n\n<h2>Фильтрация логов</h2><p>В логи обычно записывается большой объем отладочной информации. Например, инфраструктура ASP.NET записывает туда информацию о каждом обработанном HTTP-запросе. Это может быть полезно во время отладки, но когда приложение работает на боевом сервере, эта информация не нужна. Более того, в большом потоке отладочной информации можно не увидеть действительно ценного — сообщения об ошибках или каких-либо нестандартных ситуациях. Поэтому для разных окружений настройки логгирования можно настроить по-разному.</p>\n<p>Все записи в логах разделяются по степени важности. Например, методы <code>LogDebug()</code> и <code>LogInformation()</code> записывают событие с какими-то отладочными данными. Их смысл в том, чтобы дать представление о процессах, которые происходят в приложении, но не более. С другой стороны <code>LogWarning()</code>, говорит о том, что в приложении произошло что-то, на что было бы неплохо обратить внимание. Наконец, <code>LogError()</code> или <code>LogCritical()</code> записывает информацию об ошибках — т.е. приложение повело себя не так, как планировалось, и на такие записи обязательно нужно обращать внимание.</p>\n<p>В ASP.NET Core можно задать минимальный уровень для фильтрации логов. Например, при отладке можно разрешить показывать все сообщения, включая <code>Trace</code>, <code>Debug</code> и <code>Information</code>. При этом на боевом сервере эти сообщения можно зафильтровать и отображать только ошибки.</p>\n<p>Это поведение задается при помощи специального параметра:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Startup\n&#123;\n  public void Configure(IApplicationBuilder app, IHostingEnvironment env,\n    ILoggerFactory loggerFactory)\n  &#123;\n    loggerFactory.AddDebug(minLevel: LogLevel.Error);\n    loggerFactory.AddConsole(minLevel: LogLevel.Warning);\n\n    // ...\n  &#125;\n&#125;</code></pre>\n<p>В этом случае всё, что менее критично, чем <code>Error</code> или <code>Warning</code> будет игнорироваться для этих провайдеров.</p>\n<p>Но не все категории записей в логе одинаково полезны. Например, в каких-то ситуациях нам могут быть неинтересны ошибки, которые пишутся в лог инфраструктурой ASP.NET, даже если для них задан уровень <code>Error</code>, но при этом очень важны записи, сделанные нашими собственными сервисами, причем для менее приоритетных уровней.</p>\n<p>Именно для этого и существует generic-параметр интерфейса <code>ILogger&lt;T&gt;</code>. Параметр задает категорию сообщений, которую можно использовать для фильтрации сообщений. В этом случае настройки могут отличаться от провайдера к провайдеру. Но общие принципы таковы, что для отдельных категорий сообщений мы указываем собственный уровень логгирования.</p>\n<p>Вот как например можно настроить провайдер для записи в консоль:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Startup\n&#123;\n  public void Configure(IApplicationBuilder app, IHostingEnvironment env,\n    ILoggerFactory loggerFactory)\n  &#123;\n    loggerFactory.AddConsole((category, level) =>\n    &#123;\n      if (category.Contains(\"MyApp\") &amp;&amp; level >= LogLevel.Information)\n        return true;\n      else if (category.Contains(\"Microsoft\") &amp;&amp; level >= LogLevel.Error)\n        return true;\n\n      return false;\n    &#125;);\n\n    // ...\n  &#125;\n&#125;</code></pre>\n<p>Другой способ — использовать <a href=\"/2016/configuring-aspnet-core-apps-with-ioptions/\">конфигурационный файл</a> для настройки уровней логгирования:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Startup\n&#123;\n  public void Configure(IApplicationBuilder app, IHostingEnvironment env,\n    ILoggerFactory loggerFactory)\n  &#123;\n    loggerFactory.AddConsole(Configuration.GetSection(\"Logging\"));\n\n    // ...\n  &#125;\n&#125;</code></pre>\n<p>Конфигурационный файл в этом случае может выглядеть так:</p>\n<pre class=code language-json><code class=\"code__container language-json\">&#123;\n  \"Logging\": &#123;\n    \"IncludeScopes\": false,\n    \"LogLevel\": &#123;\n      \"Default\": \"Warning\",\n      \"Microsoft\": \"Error\",\n      \"MyApp\": \"Information\"\n    &#125;\n  &#125;\n&#125;</code></pre>\n<p>Ещё один удобный способ настроить фильтры — это использовать пакет <code>Microsoft.Extensions.Logging.Filter</code>. После установки этого пакета для <code>ILoggerFactory</code>, появится метод-расширение <code>WithFilter()</code>, позволяющий настроить фильтры:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Startup\n&#123;\n  public void Configure(IApplicationBuilder app, IHostingEnvironment env,\n    ILoggerFactory loggerFactory)\n  &#123;\n    loggerFactory\n      .WithFilter(new FilterLoggerSettings\n      &#123;\n        &#123; \"Microsoft\", LogLevel.Error &#125;,\n        &#123; \"MyApp\", LogLevel.Information &#125;\n      &#125;)\n      .AddConsole()\n      .AddDebug();\n\n    // ...\n  &#125;\n&#125;</code></pre>\n<p>Такой способ удобен, когда нужно настроить фильтр одновременно для нескольких провайдеров. Также он позволяет задавать фильтры единым способом, даже когда провайдер не имеет параметров для настройки фильтров.</p>\n<h2>Существующие фреймворки</h2><p>Добавленный в ASP.NET Core встроенный мехнизм логгрования отлично интегрируется с большиством известных фреймворков. Если вы уже используете в приложение какой-то существующий фреймворк — не нужно удалять его из проекта. Скорее всего для него уже создан провайдер для интеграции с ASP.NET Core. Вот провайдеры для некоторых популярных фреймворков:</p>\n<ul>\n<li><a href=\"https://github.com/NLog/NLog.Extensions.Logging\">NLog</a></li>\n<li><a href=\"https://github.com/ashimoon/dotnetliberty.AspNet.log4net\">log4net</a></li>\n<li><a href=\"https://github.com/serilog/serilog-extensions-logging\">Serilog</a></li>\n</ul>",
            "tags": [
                "ASP.NET",
                ".NET Core",
                "Logging"
            ],
            "banner_image": "https://blog.zwezdin.com/2017/asp-net-core-logging/a8f6fea7/asp_net-core-logging@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2017/mindstorms-ev3-net-api/",
            "url": "https://blog.zwezdin.com/2017/mindstorms-ev3-net-api/",
            "title": "Работаем с Lego Mindstorms EV3 из .NET",
            "date_published": "2017-03-05T13:35:00.000Z",
            "content_html": "<p>Традиционно роботы, построенные на платформе <strong>Lego Mindstorms EV3</strong>, программируются с использованием графической среды LabVIEW. В этом случае программы запускаются на контроллере EV3 и робот работает автономно. Здесь я расскажу про альтернативный способ управления роботом — использование платформы .NET, запущенной на компьютере.</p>\n<p>Но прежде чем мы перейдем непосредственно к программированию, давайте рассмотрим случаи, когда это может быть полезно:</p>\n<ul>\n<li>Требуется удаленное управление роботом с ноутбука (например, по нажатию кнопок)</li>\n<li>Требуется собирать данные с контроллера EV3 и обрабатывать их на внешней системе (например, для IoT-систем)</li>\n<li>Любые другие ситуации, когда хочется написать алгоритм управления на .NET и запускать его с компьютера, подключенного к контроллеру EV3</li>\n</ul>\n<h2>LEGO MINDSTORMS EV3 API for .NET</h2><p>Управление контроллером EV3 из внешней системы осуществляется путем отправки команд в последовательный порт. Сам формат команд описан в <a href=\"https://le-www-live-s.legocdn.com/sc/media/files/ev3-developer-kit/lego%20mindstorms%20ev3%20communication%20developer%20kit-f691e7ad1e0c28a4cfb0835993d76ae3.pdf?la=en-us\">Communication Developer Kit</a>.</p>\n<p>Но реализация этого протокола вручную — дело скучное. Поэтому можно воспользоваться <a href=\"https://github.com/BrianPeek/legoev3\">готовой .NET-оберткой</a>, которую заботливо написал Brian Peek. Исходные коды этой библиотеки размещены на <a href=\"https://github.com/BrianPeek/legoev3\">Github</a>, а готовый к использованию пакет можно найти в <a href=\"https://nuget.org/packages/Lego.Ev3\">Nuget</a>.</p>\n<h2>Подключение к контроллеру EV3</h2><p>Для связи с контроллером EV3 используется класс <code>Brick</code>. При создании этого объекта в конструктор требуется передать реализацию интерфейса <code>ICommunication</code> — объект, описывающий способ подключения к контроллеру EV3. Доступны реализации <code>UsbCommunication</code>, <code>BluetoothCommunication</code> и <code>NetworkCommunication</code> (подключение через WiFi).</p>\n<p>Наиболее популярный способ подключения - через Bluetooth. Рассмотрим поподробнее этот способ подключения.</p>\n<p>Прежде чем мы сможем программно подключиться к контроллеру через Bluetooth, контроллер необходимо подключить к компьютеру, используя настройки операционной системы.</p>\n<p>После того, как контроллер подключен, идём в настройки Bluetooth и выбираем вкладку <code>COM-порты</code>. Находим наш контроллер, нам нужен <strong>исходящий</strong> порт. Его и будем указывать при создании объекта <code>BluetoothCommunication</code>.</p>\n<p>Код для подключения к контроллеру будет выглядеть так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public async Task Connect(ICommunication communication)\n&#123;\n    var communication = new BluetoothCommunication(\"COM9\");\n    var brick = _brick = new Brick(communication);\n    await _brick.ConnectAsync();\n&#125;</code></pre>\n<p>Опционально можно указать таймаут подключения к контроллеру:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">await _brick.ConnectAsync(TimeSpan.FromSeconds(5));</code></pre>\n<p>Подключение к блоку через USB или WiFi осуществляется аналогично, за тем исключением, что используются объекты <code>UsbCommunication</code> и <code>NetworkCommunication</code>.</p>\n<p>Все дальнейшие действия, выполняемые с контроллером, осуществляются через объект <code>Brick</code>.</p>\n<h2>Покрутим моторами</h2><p>Для выполнения команд на контроллере EV3 обратимся к свойству <code>DirectCommand</code> объекта <code>Brick</code>. Для начала попробуем запустить моторы.</p>\n<p>Предположим, что наш мотор подключен к порту A контроллера, тогда запуск этого мотора на мощности 50% будет выглядеть так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">await _brick.DirectCommand.TurnMotorAtPowerAsync(OutputPort.A, 50);</code></pre>\n<p>Есть и другие методы для управления мотором. Например, можно повернуть мотор на заданный угол, используя методы <code>StepMotorAtPowerAsync()</code> и <code>StepMotorAtSpeedAsync()</code>. Всего доступно несколько методов, которые являются вариациями на режимы включения моторов — по времени, скорости, мощности и т.д.</p>\n<p>Принудительная остановка осуществляется методом <code>StopMotorAsync()</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">await _brick.DirectCommand.StopMotorAsync(OutputPort.A, true);</code></pre>\n<p>Второй параметр указывает на использование тормоза. Если его установить в <code>false</code>, то мотор будет останавливаться «накатом».</p>\n<h2>Чтение значений с датчиков</h2><p>Контроллер EV3 имеет четыре порта для подключения сенсоров. Дополнительно к этому, моторы также имеют встроеные энкодеры, что позволяет использовать их как сенсоры. В итоге мы имеем 8 портов, с которых можно считывать значения.</p>\n<p>Доступ к портам для считывания значений можно получить через свойство <code>Ports</code> объекта <code>Brick</code>. <code>Ports</code> — это коллекция портов, доступных на контроллере. Поэтому для работы с конкретным портом нужно его выбрать. <code>InputPort.One</code>…<code>InputPort.Four</code> — это порты для датчиков, а <code>InputPort.A</code>…<code>InputPort.D</code> — это энкодеры моторов.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">var port1 = _brick.Ports[InputPort.One];</code></pre>\n<p>Датчики в EV3 могут работать в разных режимах. Например, датчик цвета EV3 можно использовать для измерения внешнего освещения, измерения отраженного света или для определения цвета. Поэтому, чтобы «сообщить» сенсору о том, как именно мы хотим его использовать, нужно задать его режим:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">_brick.Ports[InputPort.One].SetMode(ColorMode.Reflective);</code></pre>\n<p>Теперь, когда датчик подключен и режим его работы задан, можно считать из него данные. Получить можно «сырые» данные, обработанное значение и значение в процентах.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">float si = _brick.Ports[InputPort.One].SIValue;\nint raw = _brick.Ports[InputPort.One].RawValue;\nbyte percent = _brick.Ports[InputPort.One].PercentValue;</code></pre>\n<p>Свойство <code>SIValue</code> возвращает обработанные данные. Здесь все зависит от того, какой именно датчик используется и в каком режиме. Например, при измерении отраженного света мы получим значения от <code>0</code> до <code>100</code> в зависимости от интенсивности отраженного света (черный/белый).</p>\n<p>Свойство <code>RawValue</code> возвращает «сырое» значение, полученное с АЦП. Иногда удобнее использовать именно его для последующей обработки и использования. Кстати, в среде разработки EV3 тоже есть возможность получения «сырых» значений — для этого нужно воспользоваться блоком из синей панели.</p>\n<p>Если используемый датчик предполагает получение значений в процентах, то можно также воспользоваться свойством <code>PercentValue</code>.</p>\n<h2>Выполнение команд «пачкой»</h2><p>Предположим, в нашем распоряжении есть робот-тележка с двумя колесами и мы хотим развернуть его на месте. В этом случае два колеса должны вращаться в противоположном направлении. Если мы воспользуемся <code>DirectCommand</code> и последовательно отправим две команды контроллеру, между их выполнением может пройти некоторое время:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">await _brick.DirectCommand.TurnMotorAtPowerAsync(OutputPort.A, 50);\nawait _brick.DirectCommand.TurnMotorAtPowerAsync(OutputPort.B, -50);</code></pre>\n<p>В этом примере мы отправляем команду для вращения мотора A на скорости 50, после успешного окончания отправки этой команды, повторяем то же самое с мотором, подключенным к порту B. Проблема в том, что отправка команд происходит не моментально, поэтому моторы могут начать крутиться в разное время — пока передается команда для порта B, мотора A <em>уже</em> начнет крутиться.</p>\n<p>Если для нас критически важно заставить крутится моторы одновременно, можно отправлять команды контроллеру «пачкой». В этом случае следует воспользоваться свойством <code>BatchCommand</code> вместо <code>DirectCommand</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">_brick.BatchCommand.TurnMotorAtPower(OutputPort.A, 50);\n_brick.BatchCommand.TurnMotorAtPower(OutputPort.B, -50);\nawait _brick.BatchCommand.SendCommandAsync();</code></pre>\n<p>Теперь подготавливается сразу две команды, после чего они отправляются на контроллер одним пакетом. Контроллер, получив эти команды, начнет вращение моторов одновременно.</p>\n<h2>Что ещё можно сделать</h2><p>Кроме вращения моторов и считывания значений сенсоров, можно выполнять ещё ряд действий на контроллере EV3. Не буду подробно останаливаться на кадом из них, перечислю только список того, что можно сделать:</p>\n<ul>\n<li><code>CleanUIAsync()</code>, <code>DrawTextAsync()</code>, <code>DrawLineAsync()</code> и др. — манипуляция встроенным экраном контроллера EV3</li>\n<li><code>PlayToneAsync()</code> и <code>PlaySoundAsync()</code> — использование встроенного динамика для воспроизведения звуков</li>\n<li><code>WriteFileAsync()</code>, <code>CopyFileAsync()</code>, <code>DeleteFileAsync()</code> (из <code>SystemCommand</code>) — работа с файлами</li>\n</ul>\n<h2>Заключение</h2><p>Использование .NET для управления роботами Mindstorms EV3 хорошо демонстрирует как технологии «из разных миров» могут работать совместно. В качестве результата исследования EV3 API для .NET было создано небольшое приложение, которое позволяет управлять роботом EV3 с компьютера. К сожалению, аналогичные приложения существуют для NXT, а EV3 они обошли стороной. В то же время они полезны на сорвнованиях управляемых роботов, например в футболе роботов.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2017/mindstorms-ev3-net-api/3a11cf1c/ev3-remote-control@2x.png\"  alt=\"Работаем с Lego Mindstorms EV3 из .NET\"/>",
            "tags": [
                ".NET",
                "WPF",
                "LEGO Mindstorms"
            ],
            "banner_image": "https://blog.zwezdin.com/2017/mindstorms-ev3-net-api/23cc08c8/mindstorms_ev3-net-api@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2017/cors-asp-net-core/",
            "url": "https://blog.zwezdin.com/2017/cors-asp-net-core/",
            "title": "Использование CORS в ASP.NET Core",
            "date_published": "2017-02-26T13:00:00.000Z",
            "content_html": "<p>По умолчанию браузеры отклоняют AJAX-запросы к адресам, расположенных в других доменах. Но иногда такие запросы необходимы. Для того, чтобы это регулировать придумали CORS.</p>\n<p><strong>Cross Origin Resource Sharing</strong> (CORS) — стандарт W3C, который позволяет определить политики кросс-доменных AJAX-запросов. Подробнее про CORS можно почитать <a href=\"https://www.w3.org/TR/cors/#access-control-allow-origin-response-header\">здесь</a> и <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS\">здесь</a>.</p>\n<p>Как видно из спецификаций, управление политиками CORS происходит за счет дополнительных HTTP-запросов и HTTP-заголовков при осуществлении кросс-доменных запросов. В <strong>ASP.NET Core</strong> существует готовый пакет для настройки политик CORS, что упрощает работу с кросс-доменными запросами.</p>\n<h2>Установка пакета</h2><p>В первую очередь следует установить пакет <code>Microsoft.AspNetCore.Cors</code> — в нем находится все необходимое для настройки CORS.</p>\n<p>После установки пакета следует зарегистрировать сервис CORS в контейнере ASP.NET:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public void ConfigureServices(IServiceCollection services)\n&#123;\n  services.AddCors();\n&#125;</code></pre>\n<h2>Настройка источника</h2><p>Кроме добавления CORS в DI-контейнер, следует указать конвейеру ASP.NET Core о необходимости использования Middleware из пакета <code>Microsoft.AspNetCore.Cors</code>. Сделать это можно вызвав метод <code>UseCors()</code>. В качестве параметра передается выражение, настраивающее политику CORS. Зададим домены, которым можно работать с нашим ресурсом в рамках CORS:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)\n&#123;\n  app.UseCors(builder =>\n    builder.WithOrigins(\"https://blog.zwezdin.com/\")\n  );\n&#125;</code></pre>\n<p>Параметр <code>builder</code> — это объект <code>CorsPolicyBuilder</code>. С его помощью можно указать как именно должен работать CORS для нашего ресурса. Вся дальнейшая настройка CORS производится с его помощью.</p>\n<h2>Указание HTTP-методов и заголовков</h2><p>Если вам требуется более тонкая настройка CORS, то следует воспользоваться тем же <code>CorsPolicyBuilder</code>, но задать больше параметров. Например, если в рамках CORS-запросов вы захотите передавать HTTP-заголовки, отличные от <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS\">стандартных</a>, то такой запрос будет отклонен. Следующая конфигурация позволит работать с нестандартными заголовками, а также указать HTTP-методы, разрешенные для работы и набор хостов-источников:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)\n&#123;\n  app.UseCors(builder =>\n    builder.WithOrigins(\"https://blog.zwezdin.com/\",\n      \"https://google.com\")\n      .WithHeaders(\"X-Header1\", \"X-Header1\")\n      .WithMethods(\"GET\", \"POST\", \"PUT\")\n  );\n&#125;</code></pre>\n<p>Можно также отменить ограничительную политику и разрешить всё:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)\n&#123;\n  app.UseCors(builder =>\n    builder.AllowAnyOrigin()\n      .AllowAnyHeader()\n      .AllowAnyMethod()\n  );\n&#125;</code></pre>\n<p>Пользоваться, однако, такой методикой я бы посоветовал с осторожностью.</p>\n<h2>Определение политик</h2><p>Если предполагается, что отдельные части приложения должны иметь разное поведение для CORS-запросов, то можно воспользоваться политиками. Политики — это именованый набор правил CORS, аналогичный тому, как это описывалось выше. Описание политик производится в методе <code>AddCors()</code> при добавлении сервиса в DI-контейнер:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public void ConfigureServices(IServiceCollection services)\n&#123;\n  services.AddCors(cors =>\n  &#123;\n    cors.AddPolicy(\"Policy1\",\n      builder =>\n        builder.WithOrigins(\"https://blog.zwezdin.com/\")\n          .WithHeaders(\"X-Header1\", \"X-Header1\")\n          .WithMethods(\"GET\", \"POST\", \"PUT\"));\n\n    cors.AddPolicy(\"Policy2\",\n      builder =>\n        builder.AllowAnyOrigin()\n          .WithMethods(\"GET\"));\n\n    cors.AddPolicy(\"Policy3\",\n      builder =>\n        builder.AllowAnyOrigin()\n          .WithHeaders(\"X-Header1\")\n          .WithMethods(\"POST\"));\n  &#125;);\n&#125;</code></pre>\n<p>Здесь определяется три политики с именами <code>Policy1</code>, <code>Policy2</code> и <code>Policy3</code>. Далее есть несколько способов как эти политики можно применять.</p>\n<p>Можно указать имя политики вместо явной конфигурации при добавлении Middleware.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)\n&#123;\n  app.UseCors(\"Policy2\");\n&#125;</code></pre>\n<p>Такой подход позволяет иметь несколько предопределенных политик и переключать их, например, используя <a href=\"https://blog.zwezdin.com/2016/configuring-aspnet-core-apps-with-ioptions/\">конфигурационный файл</a>.</p>\n<p>Если в приложении используется ASP.NET MVC, то политики CORS можно применять к отдельным контроллерам или действиям:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[EnableCors(\"Policy2\")]\npublic class HomeController : Controller\n&#123;\n  [EnableCors(\"Policy1\")]\n  public IActionResult Index()\n  &#123;\n    return View();\n  &#125;\n\n  [EnableCors(\"Policy2\")]\n  public IActionResult About()\n  &#123;\n    return View();\n  &#125;\n&#125;</code></pre>\n<p>Если добавить фильтр <code>CorsAuthorizationFilterFactory</code> глобально, то заданная политика будет применяться ко всем контроллерам/действиям. При этом можно явно указать на какие действия эта политика распространяться не должна:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[EnableCors(\"Policy2\")]\npublic class HomeController : Controller\n&#123;\n  [EnableCors(\"Policy1\")]\n  public IActionResult Index()\n  &#123;\n    return View();\n  &#125;\n\n  [EnableCors(\"Policy2\")]\n  public IActionResult About()\n  &#123;\n    return View();\n  &#125;\n\n  [DisableCors]\n  public IActionResult Contact()\n  &#123;\n    return View();\n  &#125;\n&#125;</code></pre>",
            "tags": [
                "ASP.NET",
                ".NET Core"
            ],
            "banner_image": "https://blog.zwezdin.com/2017/cors-asp-net-core/bb76bf88/cors_asp-net-core@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2016/aspnet-core-custom-configurationsource/",
            "url": "https://blog.zwezdin.com/2016/aspnet-core-custom-configurationsource/",
            "title": "Создаем собственный провайдер конфигурации в ASP.NET Core",
            "date_published": "2016-10-09T06:00:00.000Z",
            "content_html": "<p><strong>ASP.NET Core</strong> использует новую модель конфигурирования на основе <code>IOptions&lt;&gt;</code>, о чем я <a href=\"/2016/configuring-aspnet-core-apps-with-ioptions/\">писал</a> ранее. Эта модель позволяет считывать конфигурационные параметры отдельными частями приложения. Но где эти данные размещены? Давайте разберемся с этим вопросом.</p>\n<h2><code>Startup.cs</code></h2><p>Для начала заглянем в файл <code>Startup.cs</code> — файл, с которого начинается запуск приложения. В стандартном шаблоне проекта <strong>ASP.NET Core</strong>, в конструкторе класса <code>Startup</code> создается объект <code>ConfigurationBuilder</code> и на его основе строится объект с конфигурационными данными. Этот объект сохраняется в локальное свойство и фактически хранит <em>словарь</em> конфигурационных параметров.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Startup\n&#123;\n  public Startup(IHostingEnvironment env)\n  &#123;\n    IConfigurationBuilder builder = new ConfigurationBuilder()\n        .SetBasePath(env.ContentRootPath)\n        .AddJsonFile(\"appsettings.json\", true, true);\n    Configuration = builder.Build();\n  &#125;\n\n  public IConfigurationRoot Configuration &#123; get; &#125;</code></pre>\n<p>Внутри класса <code>Startup</code> это свойство затем неоднократно используется для настройки отдельных частей приложения — об этом я писал в <a href=\"/2016/configuring-aspnet-core-apps-with-ioptions/\">предыдущей записи</a>.</p>\n<h2>Источники данных для конфигурации</h2><p>Из кода <code>Startup.cs</code> можно понять, что <code>ConfigurationBuilder</code>‘у передается файл <code>appsettings.json</code>, в котором и хранятся настройки приложения. Чем хорош механизм конфигурирования <strong>ASP.NET Core</strong> — это возможностью использовать одновременно несколько источников данных для конфигурации. Например, можно хранить конфигурацию в нескольких JSON-файлах:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public Startup(IHostingEnvironment env)\n&#123;\n  IConfigurationBuilder builder = new ConfigurationBuilder()\n      .SetBasePath(env.ContentRootPath)\n      .AddJsonFile(\"appsettings.json\", true, true)\n      .AddJsonFile(\"another-settings.json\", true, true);</code></pre>\n<p>Можно также использовать разные типы источников. Например, часть конфигурации хранить в <code>JSON</code>, а другую — в <code>XML</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public Startup(IHostingEnvironment env)\n&#123;\n  IConfigurationBuilder builder = new ConfigurationBuilder()\n      .SetBasePath(env.ContentRootPath)\n      .AddJsonFile(\"appsettings.json\", true, true)\n      .AddXmlFile(\"appsettings.xml\", true, true);</code></pre>\n<p>Из коробки <strong>ASP.NET Core</strong> предлагает несколько различных способов задания источников конфигурации:</p>\n<ul>\n<li><code>EnvironmentVariables</code> — извлекает конфигурационные параметры из переменных окружения операционной системы</li>\n<li><code>JSON</code>, <code>XML</code>, <code>Ini</code> — позволяет использовать файлы JSON, XML и INI для хранения конфигруационных параметров</li>\n<li><code>UserSecrets</code> — позволяет использовать <a href=\"https://docs.asp.net/en/latest/security/app-secrets.html\">специальное защищенное хранилище</a> для хранения конфигурационных параметров</li>\n</ul>\n<p>Базовый набор позволяет организовать гибкую систему конфигурации приложения <strong>ASP.NET Core</strong>. Но по-настоящему мощным этот инструмент становится за счет возможности его расширения. Другими словами, вы можете создать собственный источник данных для конфигурации приложения и использовать его вместе со встроенными механизмами.</p>\n<h2>Собственный источник конфигурации ASP.NET Core</h2><p>В качестве учебного примера, создадим источник данных, позволяющий хранить конфигурацию в файлах формата <a href=\"http://yaml.org/spec/1.1/\"><code>YAML</code></a>.</p>\n<p>Реализация собственного источника конфигурации начинается с создания класса, реализующего интерфейс <code>IConfigurationSource</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class YamlConfigurationSource : IConfigurationSource\n&#123;\n  public YamlConfigurationSource(string fileName)\n  &#123;\n    FileName = fileName;\n  &#125;\n\n  public string FileName &#123; get; &#125;\n\n  public IConfigurationProvider Build(IConfigurationBuilder builder)\n  &#123;\n    return new YamlConfigurationProvider(this);\n  &#125;\n&#125;</code></pre>\n<p>Основная задача этого класса заключается в получении исходных данных (например, <em>имени конфигруационного файла</em>) и создании объекта <code>ConfigurationProvider</code> для данного источника. Реализацией провайдера займемся чуть позже, а пока давайте сразу добавим методы расширения, чтобы можно было удобно использовать этот источник данных в <code>Startup.cs</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">  public static class YamlConfigurationSourceExtensions\n  &#123;\n    public static IConfigurationBuilder AddYamlFile(\n      this IConfigurationBuilder builder, string filename)\n    &#123;\n      return builder.Add(new YamlConfigurationSource(filename));\n    &#125;\n  &#125;</code></pre>\n<p>Для реализации провайдера нам потребуется доступ к файловой системе. Поэтому при создании провайдера передадим ссылку на него. Код <code>YamlConfigurationSource</code> изменится следующим образом:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class YamlConfigurationSource : IConfigurationSource\n&#123;\n  public YamlConfigurationSource(string fileName)\n  &#123;\n    FileName = fileName;\n  &#125;\n\n  public string FileName &#123; get; &#125;\n\n  public IConfigurationProvider Build(IConfigurationBuilder builder)\n  &#123;\n    return new YamlConfigurationProvider(this, builder.GetFileProvider());\n  &#125;\n&#125;</code></pre>\n<p>Осталось реализовать провайдер для доступа к <code>Yaml</code>. Для этого создадим класс-наследник <code>ConfigurationProvider</code>. Для загрузки данных из источника следует реализовать метод <code>Load</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class YamlConfigurationProvider : ConfigurationProvider\n&#123;\n  private readonly IFileProvider _fileProvider;\n  private readonly YamlConfigurationSource _source;\n\n  public YamlConfigurationProvider(YamlConfigurationSource source, IFileProvider fileProvider)\n  &#123;\n    _source = source;\n    _fileProvider = fileProvider;\n  &#125;\n\n  public override void Load()\n  &#123;\n  &#125;\n&#125;</code></pre>\n<p>Для работы с <code>Yaml</code>-файлами воспользуемся библиотекой <a href=\"https://github.com/aaubry/YamlDotNet\"><code>YamlDotNet</code></a>. Вся суть конфигурационного провайдера сводится к считыванию источника и сохранении конфигурационных значений в служебном словаре, доступном через свойство <code>Data</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class YamlConfigurationProvider : ConfigurationProvider\n&#123;\n  private readonly IFileProvider _fileProvider;\n  private readonly YamlConfigurationSource _source;\n\n  public YamlConfigurationProvider(YamlConfigurationSource source, IFileProvider fileProvider)\n  &#123;\n    _source = source;\n    _fileProvider = fileProvider;\n  &#125;\n\n  public override void Load()\n  &#123;\n    IFileInfo file = _fileProvider.GetFileInfo(_source.FileName);\n    using (Stream stream = file.CreateReadStream())\n    &#123;\n      using (TextReader reader = new StreamReader(stream))\n      &#123;\n        Dictionary&lt;object, object> document = new DeserializerBuilder()\n          .Build()\n          .Deserialize(reader) as Dictionary&lt;object, object>;\n\n        if (document != null)\n        &#123;\n          foreach (KeyValuePair&lt;object, object> item in document)\n          &#123;\n            Data[item.Key.ToString()] = item.Value.ToString();\n          &#125;\n        &#125;\n      &#125;\n    &#125;\n  &#125;\n&#125;</code></pre>\n<p>Провайдер готов. Осталось подключить его к приложению в <code>Startup.cs</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Startup\n&#123;\n  public Startup(IHostingEnvironment env)\n  &#123;\n    IConfigurationBuilder builder = new ConfigurationBuilder()\n        .SetBasePath(env.ContentRootPath)\n        .AddJsonFile(\"appsettings.json\", true, true)\n        .AddYamlFile(\"config.yml\");\n    Configuration = builder.Build();\n  &#125;</code></pre>\n<h2>Заключение</h2><p>Приведенный выше пример показывает как за несколько минут можно добавить собственный конфигурационный провайдер в приложение <strong>ASP.NET Core</strong>.</p>\n<p>Обратите внимание, что этот пример годится <em>только для учебных целей</em>. Например, в нем не реализована обработка вложенных свойств <code>YAML</code>. Кроме того, для реализации конфигурационных источников, которые хранят свои данные в файлах существует ещё одна абстракция - <code>FileConfigurationProvider</code> и <code>FileConfigurationSource</code>. Кроме всего прочего в них реализована возможность отслеживания изменений в конфигурационных файлах. Поэтому, если ваш источник конфигурации использует файлы, то собственный провайдер лучше создавать на основе <code>FileConfigurationProvider</code>.</p>",
            "tags": [
                "ASP.NET",
                ".NET Core"
            ],
            "banner_image": "https://blog.zwezdin.com/2016/aspnet-core-custom-configurationsource/40433076/aspnet_core-custom-configurationsource@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2016/chrome-dev-console/",
            "url": "https://blog.zwezdin.com/2016/chrome-dev-console/",
            "title": "Как использовать консоль Google Chrome на 100%",
            "date_published": "2016-09-03T02:00:00.000Z",
            "content_html": "<p>Большинство веб-разработчиков при отладке приложений используют <strong>Google Chrome</strong> — это уже ни для кого не секрет. Прежде всего в этом браузере привлекают продвинутые средства для разработчиков. Одним из элементов <em>devtool</em> является консоль, которая в свою очередь гораздо более продвинутая, чем думают о ней большинство разработчиков.</p>\n<p>Консоль <strong>Google Chrome</strong> умеет гораздо больше, чем <code>console.log()</code>. Я опишу <em>некоторые</em> возможности, которые сам применяю чаще всего.</p>\n<h2>Производные <code>console.log()</code></h2><p>Вызов <code>console.log()</code> позволяет отобразить в консоли сообщение.</p>\n<pre class=code language-javascript><code class=\"code__container language-javascript\">var e = &#123; x: 5 &#125;;\nconsole.log('Log message', e);</code></pre>\n<p>Вызовы <code>info()</code>, <code>warn()</code> и <code>error()</code> позволяют сделать то же самое, визуально выделив сообщение соответствующим стилем. Бывает очень полезно, когда в процессе отладки накапливается большое количество сообщений и трудно выделить какие-то ключевые сообщения.</p>\n<pre class=code language-javascript><code class=\"code__container language-javascript\">var e = &#123; x: 5 &#125;;\nconsole.log('Hello! I`m a message', e);\nconsole.info('Something happened...', e);\nconsole.warn('Wow! You defenitely should pay attention here!', e);\nconsole.error('Ooooooops....', e);</code></pre>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2016/chrome-dev-console/8e8eb469/console-log-info-warn-error@2x.png\"  alt=\"Результат работы console.info(), console.warn() и console.error().\"/><figcaption>Результат работы console.info(), console.warn() и console.error().</figcaption></figure>\n\n\n\n\n<p>Вторым параметром я передал переменную <code>e</code>, содержимое которой также отобразилось в консоли. Chrome позволяет передавать сколько угодно параметров, если вам требуется отобразить не только сообщение, но и состояние каких-либо переменных.</p>\n<h2>Таблицы</h2><p>По умолчанию консоль отображает содержимое объекта в виде дерева. Это очень удобно, т.к. вы можете скрывать/раскрывать нужные части объекта и отслеживать в них информацию. Неудобства возникают когда вам нужно отобразить, <em>например</em>, содержимое массива. В этом случае вы получите нечто подобное:</p>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2016/chrome-dev-console/b4567e1a/console-log-elements@2x.png\"  alt=\"Результат работы console.log() для массива элементов.\"/><figcaption>Результат работы console.log() для массива элементов.</figcaption></figure>\n\n\n\n\n<p>Вместо <code>console.log()</code> в этом случае удобнее использовать <code>console.table()</code>, вот как это выглядит:</p>\n<pre class=code language-javascript><code class=\"code__container language-javascript\">var elements = [\n  &#123; id: 1, x: 'Lorem', y: 'ipsum'&#125;,\n  &#123; id: 2, x: 'dolor', y: 'sit'&#125;,\n  &#123; id: 3, x: 'amet', y: 'consectetur'&#125;,\n  &#123; id: 4, x: 'adipiscing', y: 'elit'&#125;,\n  &#123; id: 5, x: 'sed', y: 'do'&#125;\n];\n\nconsole.table(elements);</code></pre>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2016/chrome-dev-console/04ea1b5e/console-table@2x.png\"  alt=\"Результат работы console.table().\"/><figcaption>Результат работы console.table().</figcaption></figure>\n\n\n\n\n<p>Как видно из скриншота, вызов <code>console.table()</code> отображает массив не только в виде таблицы, но и в виде дерева, как при вызове <code>console.log()</code>.</p>\n<h2>Группировка</h2><p>Когда сообщения в консоли идут потоком друг за другом бывает полезно объединять их в группы. Делается это путем вызова методов <code>console.group()</code> и <code>console.groupEnd()</code>.</p>\n<pre class=code language-javascript><code class=\"code__container language-javascript\">console.group('Message processing');\nconsole.log('Message processing started...')\nconsole.warn('Error during processing list element.');\nconsole.log('Done');\nconsole.groupEnd();\n\nconsole.group('Image processing');\nconsole.log('Image processing started...')\nconsole.info('Image size: 500Kb');\nconsole.log('Done');\nconsole.groupEnd();</code></pre>\n<p>Если вместо <code>console.group()</code> вызвать <code>console.groupCollapsed()</code>, то сообщения в консоли будут свернуты при отображении.</p>\n<pre class=code language-javascript><code class=\"code__container language-javascript\">console.group('Message processing');\nconsole.log('Message processing started...')\nconsole.warn('Error during processing list element.');\nconsole.log('Done');\nconsole.groupEnd();\n\nconsole.groupCollapsed('Some other stuff');\nconsole.log('Blah');\nconsole.info('Blah');\nconsole.warn('Blah');\nconsole.groupEnd();\n\nconsole.group('Image processing');\nconsole.log('Image processing started...')\nconsole.info('Image size: 500Kb');\nconsole.log('Done');\nconsole.groupEnd();</code></pre>\n\n\n\n    \n\n\n\n\n<h2>Проверка утверждений</h2><p>Ассерты — это распространенный метод проверки утверждения в разных языках программирования. Консоль <strong>Google Chrome</strong> также предоставляет такую возможность. Чтобы выполнить проверку, нужно вызвать <code>console.assert()</code> с двумя параметрами: первый — <em>проверяемое условие</em>, второй — <em>сообщение об ошибке</em>. Если проверяемое условие ложно, то в консоли появится сообщение об ошибке, иначе ничего не произойдет.</p>\n<pre class=code language-javascript><code class=\"code__container language-javascript\">console.assert(1000 > 10, 'It`s true, so nothing will happen at console.');\nconsole.assert(25 &lt; 10, '25 is not less than 10!');</code></pre>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2016/chrome-dev-console/562ce90d/console-assert@2x.png\"  alt=\"Результат работы console.assert().\"/><figcaption>Результат работы console.assert().</figcaption></figure>\n\n\n\n\n<h2>Подсчет количества вызовов</h2><p>Когда одно и то же сообщение появляется в консоли несколько раз, бывает удобно вести подсчет сколько раз это произошло. С этим отлично справляется <code>console.count()</code>.</p>\n<pre class=code language-javascript><code class=\"code__container language-javascript\">console.count('Something happened');\nconsole.count('Something happened');\nconsole.count('Something happened');\nconsole.count('Something happened');\nconsole.count('Something happened');\nconsole.count('Something happened');\nconsole.count('Something happened');\nconsole.count('Something happened');</code></pre>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2016/chrome-dev-console/699fa6e6/console-count@2x.png\"  alt=\"Результат работы console.count().\"/><figcaption>Результат работы console.count().</figcaption></figure>\n\n\n\n\n<h2>Отображение стека вызовов</h2><p>Когда при отладке приложения нужно посмотреть стек вызовов, часто используют точки останова и отладчик. Лично мне это неудобно, я предпочитаю отобразить стек в консоли. Я знаю, что для этого некоторые используют хак вроде такого:</p>\n<pre class=code language-javascript><code class=\"code__container language-javascript\">console.log(new Error().stack);</code></pre>\n<p>Но на самом деле в <strong>Chrome</strong> для этого есть специальный метод <code>console.trace()</code>.</p>\n<pre class=code language-javascript><code class=\"code__container language-javascript\">function f1() &#123;\n  console.trace('f1');\n&#125;\n\nfunction f2() &#123;\n  f1();\n&#125;\n\nfunction f3() &#123;\n  f2();\n&#125;\n\nf3();</code></pre>\n<p>В этом случае в консоли отобразится доступный стек вызовов. И никаких хаков :-)</p>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2016/chrome-dev-console/fe4d7aa3/console-trace@2x.png\"  alt=\"Результат работы console.trace().\"/><figcaption>Результат работы console.trace().</figcaption></figure>\n\n\n\n\n<h2>Форматирование</h2><p>Chrome позволяет использовать подстановки в выражениях аналогично тому, как это делается в <code>string.Format()</code> в <code>C#</code> или <code>println()</code> в <code>C</code>.</p>\n<pre class=code language-javascript><code class=\"code__container language-javascript\">console.log('Current value: %d', 500);</code></pre>\n<p>В этом примере <code>%d</code> означает, что здесь подставляется число. Список доступных значений:</p>\n<ul>\n<li><code>%s</code> — строка</li>\n<li><code>%i</code>, <code>%d</code> — целое число</li>\n<li><code>%f</code> — дробное число</li>\n<li><code>%o</code> — DOM-элемент</li>\n<li><code>%O</code> — JavaScript-объекта</li>\n</ul>\n<p>Отдельного внимания заслуживает <code>%c</code> — если указать его при вызове <code>console.log()</code>, то в параметрах можно передать набор CSS-стилей, которые будут использоваться при отображении.</p>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2016/chrome-dev-console/30ddbaf5/console-format@2x.png\"  alt=\"Возможности форматирования сообщения.\"/><figcaption>Возможности форматирования сообщения.</figcaption></figure>\n\n\n\n\n<h2>Бонус — <code>$0</code></h2><p>Если во вкладке <code>Elements</code> выделить какой-либо элемент в DOM, то к нему можно обратиться через <code>$0</code> в консоли. Это очень удобный способ, когда нужно быстро выполнить какие-нибудь манипуляции с DOM-элементом.</p>",
            "tags": [
                "Tips&Tricks",
                "Tools",
                "JavaScript",
                "Фронтенд"
            ],
            "banner_image": "https://blog.zwezdin.com/2016/chrome-dev-console/c6a99f9a/chrome_dev-console@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2016/configuring-aspnet-core-apps-with-ioptions/",
            "url": "https://blog.zwezdin.com/2016/configuring-aspnet-core-apps-with-ioptions/",
            "title": "Конфигурация ASP.NET Core приложения через IOptions",
            "date_published": "2016-08-28T04:00:00.000Z",
            "content_html": "<p>Долгое время в приложениях .NET для работы с файлами конфигурации использовался объект <a href=\"https://msdn.microsoft.com/en-US/library/system.configuration.configurationmanager(v=vs.110).aspx\"><code>ConfigurationManager</code></a>, а каждая секция в конфигурационном файле описывалась через <a href=\"https://msdn.microsoft.com/en-US/library/2tw134k3(v=vs.100).aspx\"><code>ConfigurationSection</code></a>. В <strong>ASP.NET Core</strong> подходы к работе с конфигурационными файлами изменились и на смену пришел интерфейс <code>IOptions</code>.</p>\n<h2>Интерфейс <code>IOptions</code></h2><p>ASP.NET Core позволяет описывать конфигурацию в виде простых .NET-объектов, освобождая нас от необходимости использовать дополнительные атрибуты и наследование (как это было с <code>ConfigurationSection</code>). Доступ к конфигурации в таком случае осуществляется путем внедрением зависимости <code>IOptions&lt;&gt;</code> в нужный компонент.</p>\n<p>Для пример создадим объект с нужными для нас свойствами конфигруации:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class OwnerOptions\n&#123;\n  public string Account &#123; get; set; &#125;\n\n  public string DisplayName &#123; get; set; &#125;\n\n  public string Email &#123; get; set; &#125;\n&#125;</code></pre>\n<p>Для получения доступа к конфигурации, например, из контроллера достаточно будет просто внедрить <code>IOptions&lt;&gt;</code> в контроллер:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class HomeController : Controller\n&#123;\n  private readonly IOptions&lt;OwnerOptions> _ownerOptions;\n\n  public HomeController(IOptions&lt;OwnerOptions> ownerOptions)\n  &#123;\n      _ownerOptions = ownerOptions;\n  &#125;</code></pre>\n<p>Доступ к свойствам этого объекта осуществляется через свойство <code>Value</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">  public IActionResult Index()\n  &#123;\n      return View(new\n                  &#123;\n                      Account = _ownerOptions.Value.Account\n                  &#125;);\n  &#125;</code></pre>\n<h2>Настройка объектов конфигурации</h2><p>Заполние содержимым <code>options</code>-объектов происходит в момент запуска приложения. В <code>startup.cs</code> для этого существует специальный метод <code>ConfigureServices()</code>.</p>\n<p>Чтобы сконфигурировать нужный объект следует воспользоваться методом <code>Configure&lt;&gt;()</code> интерфейса <code>IServiceCollection</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public void ConfigureServices(IServiceCollection services)\n&#123;\n  services.Configure&lt;OwnerOptions>(...);\n&#125;</code></pre>\n<p>В зависимости от ситуации настраивать объекты конфигурации можно разными способами. В каждом случае используется своя перегрузка метода <code>Configure()</code>.</p>\n<h3>Заполнение конфигурации непосредственно из кода</h3><p>Для заполнения значений из кода в метод <code>Configure()</code> следует передать <code>Action</code>, внутри которого задаются нужные значения:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class OwnerOptions\n&#123;\n  public string Account &#123; get; set; &#125;\n\n  public string DisplayName &#123; get; set; &#125;\n\n  public string Email &#123; get; set; &#125;\n&#125;\n\npublic class Startup\n&#123;\n  public void ConfigureServices(IServiceCollection services)\n  &#123;\n    services.Configure&lt;OwnerOptions>(options =>\n    &#123;\n      options.Account = \"local\";\n      options.DisplayName = \"Local account\";\n      options.Email = \"noreply@local.account\";\n    &#125;);</code></pre>\n<h3>Заполнение конфигурации из конфиг-файлов</h3><p>Для заполнения значений из конфигурационного файла в метод <code>Configure()</code> следует передать свойство <code>Configuration</code>, которое определено в классе <code>Startup</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class WebsiteOptions\n&#123;\n  public string Domain &#123; get; set; &#125;\n&#125;\n\npublic class Startup\n&#123;\n  public IConfigurationRoot Configuration &#123; get; &#125;\n\n  public void ConfigureServices(IServiceCollection services)\n  &#123;\n    services.Configure&lt;WebsiteOptions>(Configuration);</code></pre>\n<p>В этом случае значения для каждого свойства <code>options</code>-объекта будут сопоставляться ключам в конфигурационном файле. Поэтому в стандартный конфигурационный файл <code>appsettings.json</code> добавим свойство <code>Domain</code>, которое присутствует в <code>WebsiteOptions</code>.</p>\n<pre class=code language-json><code class=\"code__container language-json\">&#123;\n  \"Domain\": \"localhost\"\n&#125;</code></pre>\n<h3>Заполнение конфигурации из отдельной секции конфиг-файла</h3><p>Этот случай очень похож на предыдущий за исключением того, что <code>options</code>-объект заполняется данными нужной конфигурационной секции. В метод <code>Configure()</code> передается не весь объект <code>Configuration</code>, а только конкретная его секция:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class ConnectionStringOptions\n&#123;\n  public string DefaultConnection &#123; get; set; &#125;\n&#125;\n\npublic class Startup\n&#123;\n  public IConfigurationRoot Configuration &#123; get; &#125;\n\n  public void ConfigureServices(IServiceCollection services)\n  &#123;\n    services.Configure&lt;ConnectionStringOptions>(Configuration.GetSection(\"ConnectionStrings\"));</code></pre>\n<p>Файл <code>appsettings.json</code> будет иметь следующий вид:</p>\n<pre class=code language-json><code class=\"code__container language-json\">&#123;\n  \"ConnectionStrings\": &#123;\n    \"DefaultConnection\": \"Server=(localdb)\\\\mssql;Database=aspnet-webapp;Trusted_Connection=True;MultipleActiveResultSets=true\"\n  &#125;\n&#125;</code></pre>\n<h2>Заключение</h2><p>Интерфейс <code>IOptions</code> предоставляет простой и в то же время гибкий способ определения конфигурационных параметров приложения. Это — эволюция объекта <code>ConfigurationSection</code>, использовавшегося до текущего момента.</p>\n<p>Полная версия приложения, использующего одновременно три способа:</p>\n<p><code>startup.cs</code></p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Startup\n&#123;\n  public IConfigurationRoot Configuration &#123; get; &#125;\n\n  public void ConfigureServices(IServiceCollection services)\n  &#123;\n    services.Configure&lt;OwnerOptions>(options =>\n    &#123;\n        options.Account = \"local\";\n        options.DisplayName = \"Local account\";\n        options.Email = \"noreply@local.account\";\n    &#125;);\n\n    services.Configure&lt;WebsiteOptions>(Configuration);\n\n    services.Configure&lt;ConnectionStringOptions>(\n        Configuration.GetSection(\"ConnectionStrings\"));\n  &#125;</code></pre>\n<p><code>appsettings.json</code></p>\n<pre class=code language-json><code class=\"code__container language-json\">&#123;\n  \"ConnectionStrings\": &#123;\n    \"DefaultConnection\": \"Server=(localdb)\\\\mssql;Database=aspnet-webapp;Trusted_Connection=True;MultipleActiveResultSets=true\"\n  &#125;,\n  \"Domain\": \"localhost\"\n&#125;</code></pre>\n<p><code>HomeController.cs</code></p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class HomeController : Controller\n&#123;\n  private readonly IOptions&lt;OwnerOptions> _ownerOptions;\n  private readonly IOptions&lt;WebsiteOptions> _websiteOptions;\n  private readonly IOptions&lt;ConnectionStringOptions> _connectionStringOptions;\n\n  public HomeController(IOptions&lt;OwnerOptions> ownerOptions,\n                        IOptions&lt;WebsiteOptions> websiteOptions,\n                        IOptions&lt;ConnectionStringOptions> connectionStringOptions)\n  &#123;\n    _ownerOptions = ownerOptions;\n    _websiteOptions = websiteOptions;\n    _connectionStringOptions = connectionStringOptions;\n  &#125;\n\n  public IActionResult Index()\n  &#123;\n    return View(new IndexViewModel\n    &#123;\n        Owner = _ownerOptions.Value,\n        Website = _websiteOptions.Value,\n        ConnectionString = _connectionStringOptions.Value\n    &#125;);\n  &#125;</code></pre>",
            "tags": [
                "ASP.NET",
                ".NET Core"
            ],
            "banner_image": "https://blog.zwezdin.com/2016/configuring-aspnet-core-apps-with-ioptions/9e78f47b/configuring_aspnet-core-apps-with-ioptions@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2016/aspnet-core-iapplicationlifetime/",
            "url": "https://blog.zwezdin.com/2016/aspnet-core-iapplicationlifetime/",
            "title": "События отслеживания запуска и остановки приложения ASP.NET Core",
            "date_published": "2016-08-22T13:00:00.000Z",
            "content_html": "<p><code>IApplicationLifetime</code> — это новый интерфейс в <strong>ASP.NET Core</strong>. С его помощью можно корректно обрабатывать моменты запуска и завершения ASP.NET приложения.</p>\n<p>Интерфейс определен следующим образом:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">namespace Microsoft.AspNetCore.Hosting\n&#123;\n  public interface IApplicationLifetime\n  &#123;\n    CancellationToken ApplicationStarted &#123; get; &#125;\n\n    CancellationToken ApplicationStopping &#123; get; &#125;\n\n    CancellationToken ApplicationStopped &#123; get; &#125;\n\n    void StopApplication();\n  &#125;\n&#125;</code></pre>\n<p>Интерфейс по умолчанию пристутствует в DI-контейнере, поэтому его можно инжектировать в том месте, где это необходимо, например в классе <code>Startup</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Startup\n&#123;\n  public void Configure(IApplicationBuilder app,\n                        IHostingEnvironment env,\n                        IApplicationLifetime applicationLifetime,\n                        ILoggerFactory loggerFactory)\n  &#123;\n  &#125;\n&#125;</code></pre>\n<p>Или в контроллере:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class HomeController : Controller\n&#123;\n  public HomeController(IApplicationLifetime applicationLifetime)\n  &#123;\n  &#125;</code></pre>\n<p><code>IApplicationLifetime</code> содержит три свойства <code>CancellationToken</code>:</p>\n<ul>\n<li><code>ApplicationStarted</code> − срабатывает когда хост приложения полностью запущен.</li>\n<li><code>ApplicationStopping</code> − срабатывает когда хост приложения инициирует процедуру завершения. В этот момент запросы от пользователей могут всё ещё обрабатываться.</li>\n<li><code>ApplicationStopped</code> − срабатывает после завершения приложения. В этот момент все запросы от пользователей обработаны.</li>\n</ul>\n<p>Используя <code>CancellationToken</code> можно зарегистрировать обработчик:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Startup\n&#123;\n  public void Configure(IApplicationBuilder app,\n                        IHostingEnvironment env,\n                        IApplicationLifetime applicationLifetime,\n                        ILoggerFactory loggerFactory)\n  &#123;\n    applicationLifetime.ApplicationStarted.Register(OnApplicationStarted);\n    applicationLifetime.ApplicationStopping.Register(OnApplicationStopping);\n    applicationLifetime.ApplicationStopped.Register(OnApplicationStopped);\n  &#125;\n\n  protected void OnApplicationStarted()\n  &#123;\n  &#125;\n\n  protected void OnApplicationStopping()\n  &#123;\n  &#125;\n\n  protected void OnApplicationStopped()\n  &#123;\n  &#125;\n&#125;</code></pre>\n<p><code>ApplicationStopping</code> и <code>ApplicationStopped</code> блокируют дальнейшее завершение приложения до тех пор, пока не исполнен обработчик.</p>\n<p>Дополнительно <code>IApplicationLifetime</code> имеет метод <code>StopApplication()</code>. С его помощью можно инициировать процесс завершения приложения.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class HomeController : Controller\n&#123;\n  private readonly IApplicationLifetime _applicationLifetime;\n\n  public HomeController(IApplicationLifetime applicationLifetime)\n  &#123;\n    _applicationLifetime = applicationLifetime;\n  &#125;\n\n  public IActionResult Shutdown()\n  &#123;\n    _applicationLifetime.StopApplication();\n    return View();\n  &#125;</code></pre>",
            "tags": [
                "Tips&Tricks",
                "ASP.NET",
                ".NET Core"
            ],
            "banner_image": "https://blog.zwezdin.com/2016/aspnet-core-iapplicationlifetime/d5e4e0ce/aspnet_core-iapplicationlifetime@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2016/config-transformations-with-magic-chunks/",
            "url": "https://blog.zwezdin.com/2016/config-transformations-with-magic-chunks/",
            "title": "Трансформация XML- и JSON-конфигов при помощи Magic Chunks",
            "date_published": "2016-08-15T01:00:00.000Z",
            "content_html": "<p>Когда мы говорим о <strong>DevOps</strong> рано или поздно речь заходит о <em>трансформации конфигурационных файлов</em>. В мире .NET стандартом <em>де-факто</em> для конфигурационных файлов всегда был XML. А для трансформации XML-файлов был придуман <a href=\"https://msdn.microsoft.com/en-us/library/dd465326.aspx\">XML Document Transform</a>.</p>\n<p>Появление <strong>.NET Core</strong> сместило многие акценты в мире .NET-разработки, включая работу с <a href=\"https://docs.asp.net/en/latest/fundamentals/configuration.html\">файлами конфигурации</a>. XML, конечно же, поддерживается до сих пор. Однако, в качестве основного формата предлагается использовать <code>JSON</code>.</p>\n<p>В .NET Core предполагается, что у вашего приложения есть <a href=\"https://docs.asp.net/en/latest/fundamentals/environments.html\">несколько сред исполнения</a>. Для каждого случая мы можем создать отдельный конфигурационный файл и положить туда нужные нам значения. Пусть, например, у нас есть файл <code>appsettings.json</code> и две среды исполнения, тогда конфигурационные файлы будут иметь следующие имена:</p>\n<ul>\n<li><code>appsettings.Development.json</code> − конфигурация для среды <code>Development</code></li>\n<li><code>appsettings.Production.json</code> − конфигурация для среды <code>Production</code></li>\n</ul>\n<p>Такой подход решает задачу подмены значений в конфигурацинных файлах, но лично мне в этом не нравится как минимум два момента:</p>\n<ol>\n<li>Вы храните все конфигурационные файлы <code>appsettings.json</code>, <code>appsettings.Development.json</code>, <code>appsettings.Production.json</code> на каждом из серверов. Т.е. пароли и ключи для продакшн-ресурсов у вас будут также находится на серверах для тестирования.</li>\n<li>Иногда хочется вообще вынести все чувствительные данные (такие как пароли, ключи к базам данных, внешним ресурсам и т.д.) за пределы репозитория.</li>\n</ol>\n<p>По этим причинам, трансформация конфигурационных файлов была бы предпочтительнее подмены файлов. И если с трансформацией <code>XML</code> всё понятно, то для <code>JSON</code> я таких инструментов не нашел. Поэтому тихим июньским субботним утром было решено изобретать свой велосипед на эту тему. О том, что из этого получилось — <em>далее</em>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2016/config-transformations-with-magic-chunks/d2879a6e/many-people-still-invent-a-bike@2x.jpg\"  alt=\"Трансформация XML- и JSON-конфигов при помощи Magic Chunks\"/>\n\n\n\n\n<h2>Magic Chunks</h2><p>«<em>Каким образом можно универсально представить трансформации</em>» — именно этот вопрос встал в первую очередь. Чтобы не усложнять, в качестве рабочей идеи было принято представление трансформаций в виде последовательности «<em>ключ − значение</em>».</p>\n<p>Допустим у вас есть следующий конфигурационный файл:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;configuration>\n  &lt;system.web>\n    &lt;compilation debug=\"true\" targetFramework=\"4.5.1\" />\n    &lt;authentication mode=\"None\" />\n  &lt;/system.web>\n&lt;/configuration></code></pre>\n<p>Чтобы изменить отдельные его части, мы указываем путь внутри файла и задаем для него значение. Разделителем является <code>/</code>. Например:</p>\n<ul>\n<li><code>configuration/system.web/compilation/@debug</code> → <code>false</code></li>\n<li><code>configuration/system.web/authentication/@mode</code> → <code>Forms</code></li>\n</ul>\n<p>После трансформации файл конфигурации будет выглядеть так:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;configuration>\n  &lt;system.web>\n    &lt;compilation debug=\"false\" targetFramework=\"4.5.1\" />\n    &lt;authentication mode=\"Forms\" />\n  &lt;/system.web>\n&lt;/configuration></code></pre>\n<p>Приняв эту идею за основу, я сделал небольшую .NET-сборку, которая принимает на вход исходный файл и набор трансформаций, а на выходе выдает изменненый конфигурационный файл.</p>\n<p>Такой подход позволяет трансформировать конфигурационные файлы независимо от того, к какому типу они относятся. На данный момент поддерживаются — <a href=\"https://github.com/sergeyzwezdin/magic-chunks/wiki/XML\">XML</a>, <a href=\"https://github.com/sergeyzwezdin/magic-chunks/wiki/JSON\">JSON</a> и <a href=\"https://github.com/sergeyzwezdin/magic-chunks/wiki/YAML\">YAML</a>. А для того, чтобы это можно было максимально удобно интегрировать в конкретный процесс сборки, было создано несколько разных оберток.</p>\n<h3>MSBuild</h3><p>Чтобы запустить трансформацию конфигов внутри <code>MSBuild</code>-скрипта нужно загрузить <a href=\"https://github.com/sergeyzwezdin/magic-chunks/releases\">последнюю версию Magic Chunks</a> или установить её из <a href=\"https://www.nuget.org/packages/MagicChunks/\">Nuget</a>.</p>\n<p>После этого в ваш <code>msbuild</code>-файл следует внести несколько изменений:</p>\n<p><strong>Шаг 1:</strong> Импортировать <code>MagicChunks.targets</code> файл:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Import Project=\"tools\\MagicChunks.targets\" /></code></pre>\n<p><strong>Шаг 2:</strong> Определить коллекцию трансформаций. В <code>MSBuild</code> это можно сделать при помощи <code>ItemGroup</code>:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;ItemGroup>\n  &lt;TransformsXml Include=\"configuration/system.web/authentication/@mode\">\n    &lt;Value>Forms&lt;/Value>\n  &lt;/TransformsXml>\n  &lt;TransformsXml Include=\"configuration/system.web/httpRuntime\">\n    &lt;Value>125&lt;/Value>\n  &lt;/TransformsXml>\n  ...\n&lt;/ItemGroup></code></pre>\n<p><strong>Шаг 3:</strong> Запустить трансформацию:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;TransformConfig Path=\"Web.config\" Trasformations=\"@(TransformsXml)\" /></code></pre>\n<p>По умолчанию будет изменен исходный файл (в данном примере — <code>Web.config</code>). Если результат требуется сохранить в другом файле, то дополнительно следует задать атрибут <code>Target</code>:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;TransformConfig Path=\"Web.config\" Target=\"App.config\" Trasformations=\"@(TransformsXml)\" /></code></pre>\n<p>Пример запуска трансформаций при помощи <code>MSBuild</code> − <a href=\"https://github.com/sergeyzwezdin/magic-chunks/tree/master/samples/msbuild\">samples/msbuild</a></p>\n<h3>Powershell</h3><p><code>Powershell</code> — очень популярный инструмент для сборки проектов. Поэтому <code>Magic Chunks</code> имеет готовый модуль для <code>Powershell</code>. Потребуется загрузить <a href=\"https://github.com/sergeyzwezdin/magic-chunks/releases\">последнюю версию Magic Chunks</a> или установить её из <a href=\"https://www.nuget.org/packages/MagicChunks/\">Nuget</a>.</p>\n<p>После загрузки следует выполнить следующие шаги:</p>\n<p><strong>Шаг 1:</strong> Импортировать модуль <code>MagicChunks.psm1</code>:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">Import-Module \".\\MagicChunks.psm1\";</code></pre>\n<p><strong>Шаг 2:</strong> Запустить команду <code>Format-MagicChunks</code> с нужными параметрами:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">Format-MagicChunks -path \"$PSScriptRoot\\Web.config\" `\n    -target \"$PSScriptRoot\\Web.transformed.1.config\" -transformations @&#123;\n       \"configuration/system.web/authentication/@mode\" = \"Forms\";\n       \"configuration/system.web/httpRuntime\" = \"125\";\n&#125;</code></pre>\n<p>Пример запуска трансформаций при помощи <code>Powershell</code> − <a href=\"https://github.com/sergeyzwezdin/magic-chunks/tree/master/samples/powershell\">samples/powershell</a></p>\n<h3>PSake</h3><p><a href=\"https://github.com/psake/psake\">PSake</a> — надстройка над <code>Powershell</code>, позволяющая писать билд-скрипты чуть более удобно. Поэтому запуск трансформаций производится так же, как в <code>Powershell</code>. Код, приведенный выше актуален и в данном случае.</p>\n<p>Пример запуска трансформаций при помощи <code>PSake</code> − <a href=\"https://github.com/sergeyzwezdin/magic-chunks/tree/master/samples/psake\">samples/psake</a></p>\n<h3>Cake</h3><p><a href=\"http://cakebuild.net/\">Cake (C# Make)</a> — инструмент, позволяющий создавать билд-скрипты на языке <code>C#</code>. Чтобы подключить <code>Magic Chunks</code> к скрипту на <code>Cake</code> следует выполнить следующие шаги:</p>\n<p><strong>Шаг 1:</strong> Добавить ссылку на <code>Magic Chunks</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">#addin \"MagicChunks\"</code></pre>\n<p><strong>Шаг 2:</strong> Запустить трансформацию, используя алиас <code>TransformConfig</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">Task(\"Default\")\n    .Does(() => &#123;\n\n        TransformConfig(@\"Web.config\", \"Web.transformed.1.config\",\n          new TransformationCollection &#123;\n            &#123; \"configuration/system.web/authentication/@mode\", \"Forms\" &#125;,\n            &#123; \"configuration/system.web/httpRuntime\", \"125\" &#125;\n          &#125;);\n\n    &#125;);</code></pre>\n<p>Пример запуска трансформаций при помощи <code>Cake</code> − <a href=\"https://github.com/sergeyzwezdin/magic-chunks/tree/master/samples/cake\">samples/cake</a></p>\n<h3>Запуск из собственного .NET-кода</h3><p>Иногда может потребоваться произвести трансформацию из .NET-кода. Сделать это очень просто, т.к. <code>Magic Chunks</code> — это и есть сборка .NET:</p>\n<p><strong>Шаг 1:</strong> Устанавливаем <code>Magic Chunks</code> из Nuget:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">Install-Package MagicChunks</code></pre>\n<p><strong>Шаг 2:</strong> Создаем объект <code>MagicChunks.Core.Transformer</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">using MagicChunks.Core;\n\nvar transformer = new Transformer();</code></pre>\n<p><strong>Шаг 3:</strong> Выполняем трансформацию:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">string result = transformer.Transform(File.ReadAllText(@\"Web.config\"),\n  new TransformationCollection\n  &#123;\n    &#123;\"configuration/system.web/authentication/@mode\", \"Forms\"&#125;,\n    &#123;\"configuration/system.web/httpRuntime\", \"125\"&#125;\n  &#125;);</code></pre>\n<p>Пример запуска трансформаций внутри .NET-проекта − <a href=\"https://github.com/sergeyzwezdin/magic-chunks/tree/master/samples/netapp\">samples/netapp</a></p>\n<h3>Плагин для TFS/Visual Studio Online</h3><p>Для тех, кто используется <strong>Team Foundation Server</strong> или <strong>Visual Studio Online</strong> для сборки своего проекта, будет удобно использовать <a href=\"https://marketplace.visualstudio.com/items?itemName=sergeyzwezdin.magic-chunks\">готовый плагин</a> для трансформации. Сделать это весьма просто:</p>\n<p><strong>Шаг 1:</strong> Устанавливаем <a href=\"https://marketplace.visualstudio.com/items?itemName=sergeyzwezdin.magic-chunks\">плагин</a> из Visual Studio Marketplace.</p>\n<p><strong>Шаг 2:</strong> Добавляем новый шаг сборки:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2016/config-transformations-with-magic-chunks/f8b19342/vsts-step2@2x.png\"  alt=\"Трансформация XML- и JSON-конфигов при помощи Magic Chunks\"/>\n\n\n\n\n<p><strong>Шаг 3:</strong> Выбираем <code>Config transformation</code> в группе <code>Utility</code>:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2016/config-transformations-with-magic-chunks/95be08cd/vsts-step3@2x.png\"  alt=\"Трансформация XML- и JSON-конфигов при помощи Magic Chunks\"/>\n\n\n\n\n<p><strong>Шаг 4:”</strong> Указываем путь до файла, который нужно трансформировать, а также набор трансофрмаций в виде <code>JSON</code>-коллекции:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2016/config-transformations-with-magic-chunks/ca60c76b/vsts-step4@2x.png\"  alt=\"Трансформация XML- и JSON-конфигов при помощи Magic Chunks\"/>\n\n\n\n\n<p><strong>Шаг 5:</strong> Запускаем сборку.</p>\n<p>Поскольку TFS на данный момент не имеет элементов управления для работы с коллекциями «<em>ключ - значение</em>», трансформации представляются в виде <code>JSON</code>-коллекции:</p>\n<pre class=code language-json><code class=\"code__container language-json\">&#123;\n  \"configuration/system.web/compilation/@debug\": \"false\",\n  \"configuration/system.web/authentication/@mode\": \"Forms\"\n&#125;</code></pre>\n<p>В качестве значений можно использовать <a href=\"https://www.visualstudio.com/en-us/docs/build/define/variables\">переменные TFS</a>:</p>\n<pre class=code language-json><code class=\"code__container language-json\">&#123;\n  \"configuration/appSettings/add[@key='builtBy']/@value\": \"$(Build.QueuedBy)\"\n&#125;</code></pre>\n<p>Видео о том, как настроить трансформацию в TFS за 30 секунд:</p>",
            "tags": [
                "Tools",
                "ASP.NET",
                ".NET",
                "Powershell",
                "Team Foundation Server",
                "Visual Studio Team System",
                "Open Source",
                ".NET Core"
            ],
            "banner_image": "https://blog.zwezdin.com/2016/config-transformations-with-magic-chunks/69189442/config_transformations-with-magic-chunks@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2016/xamarin-now-is-free/",
            "url": "https://blog.zwezdin.com/2016/xamarin-now-is-free/",
            "title": "Xamarin стал бесплатным",
            "date_published": "2016-03-31T19:00:00.000Z",
            "content_html": "<p>В конце февраля Скотт Гатри <a href=\"http://weblogs.asp.net/scottgu/welcoming-the-xamarin-team-to-microsoft\">объявил</a> о покупке Xamarin, популярного фреймворка для создания кросс-платформенных мобильных приложений, корпорацией Microsoft. Никакой конкретики тогда не прозвучало и Скотт предложил подождать конференции <a href=\"https://build.microsoft.com/\">//Build</a>.</p>\n<p>Как и ожидалось, на конференции Microsoft озвучили много интересных новостей, одна из которых посвящена Xamarin. Microsoft официально объявили — Xamarin будет распространяться бесплатно и откроет код Xamarin SDK.</p>\n<p>Xamarin Studio Community — бесплатный вариант лицензирования Xamarin, когда библиотека поставляется вместе с бесплатной редакцией Visual Studio Community. В этой редакции разработчики могут создавать приложения в Visual Studio (для Windows) и Xamarin Studio (для Mac). В распоряжении имеются библиотеки Xamarin.Forms, Xamarin.iOS, Xamarin.Android. Для подписчиков MSDN существуют и более продвинутые версии.</p>\n<p>Для мобильных разработчиков это отличная новость. Подписчики MSDN теперь могут не тратить ежегодно весьма значительные суммы на покупку Xamarin. А для тех, у кого нет подписки MSDN теперь существует бесплатная редакция Xamarin, в которой больше нет ограничения на размер кода.</p>\n<p>Индустрия выиграет от такого поворота событий. Цена на Xamarin была сдерживающим фактором для распространения Xamarin. Даже удивительно почему Microsoft не сделала это года на два раньше.</p>",
            "tags": [
                "Мобильная разработка"
            ],
            "banner_image": "https://blog.zwezdin.com/2016/xamarin-now-is-free/c133c2af/xamarin_now-is-free@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2016/running-chakracore/",
            "url": "https://blog.zwezdin.com/2016/running-chakracore/",
            "title": "Запускаем JavaScript-код внутри .NET-приложения, используя ChakraCore",
            "date_published": "2016-03-26T13:00:00.000Z",
            "content_html": "<p>Бибилотеку <strong>ChakraCore</strong> можно использовать для встраивания в собственные приложения. Благодаря этому ваше приложение научится запускать произвольный Javascript-код.</p>\n<p>Код ChakraCore написан на C++. Поэтому не представляет особого труда импортировать эту библиотеку в собственные проекты C++. Можно использовать <code>PInvoke</code> и запускать этот код из среды .NET.</p>\n<h2>Подготовка проекта</h2><p>В первую очередь готовим библиотеку <code>ChakraCore.dll</code> — это и есть движок <strong>CharkCore</strong>. Проект распростарняется в виде исходного кода, поэтому его нужно загрузить себе и собрать.</p>\n<ol>\n<li>Загружаем <a href=\"https://github.com/Microsoft/ChakraCore\">исходный код ChakraCore</a>.</li>\n<li>В Visual Studio открываем проект <code>Build\\Chakra.Core.sln</code>.</li>\n<li>Выбираем платформу, на которой будет запускаться код - <code>x86</code>, <code>x64</code> или <code>ARM</code>. Если приложение будет запускаться на нескольких платформах, нужно собрать разные собрки.</li>\n<li>Собираем.</li>\n<li>В папке <code>Build\\VcBuild\\bin\\platform</code> забираем файл <code>ChakraCore.dll</code> - он потребуется нам на следующем шаге.</li>\n</ol>\n<h2>Исполнение кода JavaScript</h2><p><code>ChakraCore.dll</code> — это не .NET-сборка. Поэтому нам потребуются .NET-обертки для запуска кода. К нашему удобству такие обертки доступны среди примеров использования ChakraCore.</p>\n<ol>\n<li>Создаем новый .NET-проект.</li>\n<li>Добавляем к проекту <code>ChakraCore.dll</code>.</li>\n<li>Копируем в проект <a href=\"https://github.com/microsoft/Chakra-Samples/tree/master/ChakraCore%20Samples/Hello%20World/Windows/C%23/HelloWorld/Hosting\">обертки</a> для <code>ChakraCore.dll</code>.</li>\n</ol>\n<p>Для доступа к ChakraCore используется объект <code>JavaScriptRuntime</code>. После завершения работы, этот объект необходимо освободить, поэтому обернем его в <code>using</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">JavaScriptRuntime runtime;\nNative.JsCreateRuntime(JavaScriptRuntimeAttributes.None, null, out runtime);\n\nusing (runtime)\n&#123;\n    // ...\n&#125;</code></pre>\n<p>Определяем контекст исполнения JS-кода:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">JavaScriptSourceContext currentSourceContext = JavaScriptSourceContext.FromIntPtr(IntPtr.Zero);\nJavaScriptContext context;\nNative.JsCreateContext(runtime, out context);\nNative.JsSetCurrentContext</code></pre>\n<p>И запускаем:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">JavaScriptValue result;\nNative.JsRunScript(script, currentSourceContext++, \"\", out result);</code></pre>\n<p>Не забываем, что мы работаем с нативной библиотекой, поэтому данные нам вернуться в виде указателей на результат. Нехитрыми преобразованиями получаем из них .NET-объект:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">IntPtr resultPtr;\nUIntPtr stringLength;\nNative.JsStringToPointer(resultJSString, out resultPtr, out stringLength);\n\nNative.JsSetCurrentContext(JavaScriptContext.Invalid);\n\nstring result = Marshal.PtrToStringUni(resultPtr);</code></pre>\n<h2>Собираем результат</h2><p>В моем случае в качестве примера используется консольное приложение. При запуске приложения получаем от пользователя два числа и просим Javascript-функцию сложить их. Результат выводим в консоль.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">static void Main(string[] args)\n&#123;\n    int a = int.Parse(Console.ReadLine());\n    int b = int.Parse(Console.ReadLine());\n\n    string script = String.Format(\n        \"((a, b)=>&#123;\" +\n        \"return a+b;\" +\n        \"&#125;)(&#123;0&#125;, &#123;1&#125;)\", a, b);\n\n    string result = RunJavascript(script);\n\n    Console.WriteLine(result);\n    Console.Read();\n&#125;\n\nprivate static string RunJavascript(string script)\n&#123;\n    JavaScriptRuntime runtime;\n    Native.JsCreateRuntime(JavaScriptRuntimeAttributes.None, null, out runtime);\n\n    using (runtime)\n    &#123;\n        JavaScriptSourceContext currentSourceContext = JavaScriptSourceContext.FromIntPtr(IntPtr.Zero);\n\n        JavaScriptContext context;\n        Native.JsCreateContext(runtime, out context);\n\n        Native.JsSetCurrentContext(context);\n\n        JavaScriptValue result;\n        Native.JsRunScript(script, currentSourceContext++, \"\", out result);\n\n        JavaScriptValue resultJSString;\n        Native.JsConvertValueToString(result, out resultJSString);\n\n        IntPtr resultPtr;\n        UIntPtr stringLength;\n        Native.JsStringToPointer(resultJSString, out resultPtr, out stringLength);\n\n        Native.JsSetCurrentContext(JavaScriptContext.Invalid);\n\n        return Marshal.PtrToStringUni(resultPtr);\n    &#125;\n&#125;</code></pre>\n<p>Результат:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2016/running-chakracore/f0cfdd46/1@2x.png\"  alt=\"Запускаем JavaScript-код внутри .NET-приложения, используя ChakraCore\"/>",
            "tags": [
                ".NET",
                "JavaScript"
            ],
            "banner_image": "https://blog.zwezdin.com/2016/running-chakracore/9a4cb123/running_chakracore@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2016/web-app-deployment-with-git-kudu/",
            "url": "https://blog.zwezdin.com/2016/web-app-deployment-with-git-kudu/",
            "title": "Kudu: Деплой веб-проектов при помощи Git",
            "date_published": "2016-03-12T13:00:00.000Z",
            "content_html": "<p>Деплой приложения — неизбежный этап в жизненном цикле приложения. Так или иначе вы выполняете деплой, когда показываете приложение миру.</p>\n<p>В индустрии нет единого мнения об инструментах для развертывания. Рассмотрим некоторые из них:</p>\n<ol>\n<li><strong>FTP</strong> — один из древнейших способов деплоя приложения на сервер. Этот способ очень медленный. Кроме того, невозможно обеспечить атомарность обновления. А если в процессе обновления прервалось соединение, то обновиться только часть приложения. Какие будут последствия, остается только догадываться.</li>\n<li><strong>File share</strong> — во многом аналог предыдущего способа. В целом немного быстрее, но атомарность обновления также не гарантируется.</li>\n<li>Ctrl+C, Ctrl+V через <strong>Remote Desktop</strong> — да-да, до сих пор встречаю людей, которые подключаются к продакшн-серверу по RDP и просто копируют туда архив с файлами. Мы не будем всерьез рассматрвиать этот метод как одну из альтернатив :-)</li>\n<li><strong>WebDeploy</strong> — инструмент от Microsoft, набирающий популярность. Позволяет создавать пакеты, а затем атомарно деплоить их на сервер. Поддерживаются дополнительные плюшки в виде деплоя БД и других артефактов. Вполне рабочий вариант, за исключением того, что порой бывает весьма капризен. А неинформативные сообщения об ошибках сжирают часы на отладку процесса деплоя.</li>\n</ol>\n<p>Платные инструменты вроде <strong>Octopus Deploy</strong> — насколько мне известно работают вполне исправно и для крупных компаний это может быть вполне хорошим вариантом. Для тех, кто не может себе позволить платить сотни долларов только за деплой этот способ не подходит.</p>\n<p>Как процесс деплоя должен выглядеть в идеале:</p>\n<ol>\n<li>Собираем рабочую копию проекта.</li>\n<li>Атомарно отправляем изменения на сервер.</li>\n<li>Приложение обновляется.</li>\n</ol>\n<p>В идеале инструмент <em>не должен</em> требовать шаманского бубна при настройке, а если что-то пошло не так, то показывать понятные сообщения об ошибке.</p>\n<h2>Проект Kudu</h2><p>Проект <a href=\"https://github.com/projectkudu/kudu\">Kudu</a> появился как часть инфраструктуры Azure для деплоя <strong>Azure Websites</strong>, используя Git. Использовать в качестве транспорта для деплоя Git − известную большинству разработчиков систему контроля версий − отличная идея! Не нужно изучать новый синтаксис или структуру пакета, вы уже знакомы с этим.</p>\n<p>Kudu работает гениально просто:</p>\n<ol>\n<li>Вы устанавливаете консоль Kudu на ваш сервер. На самом деле это простое приложение, которое добавляется в IIS.</li>\n<li>Через веб-интерфейс добавляете новый проект.</li>\n<li>Kudu самостоятельно создает для этого проекта веб-сайт в IIS и все необходимые папки.</li>\n<li>Для каждого проекта вы получаете ссылку на Git-репозиторий.</li>\n<li>Для деплоя приложения вы пушите все изменения в этот репозиторий.</li>\n</ol>\n<p>Если ваше приложение находится в Azure Websites, все это для вас уже готово − вам лишь остается отправить изменения в репозиторий.</p>\n<h2>Настройка Kudu на локальном сервере</h2><p>Установка и настройка Kudu — процесс простой и безболезненный. Ниже несколько шагов, которые нужно для этого выполнить.</p>\n<h3>Шаг 1: Загружаем и устанавливаем Kudu</h3><p>Загружаем последнюю версию проекта с GitHub:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">git clone https://github.com/projectkudu/kudu</code></pre>\n<p>Запускаем Visual Studio и открываем проект <code>Kudu.sln</code>. Собираем. Можно также обойтись без Visual Studio — запустить <code>build.cmd</code> и скрипт соберет проект для вас.</p>\n<p><code>Kudu.Web</code> — это проект, который нужно разместить в IIS после сборки.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2016/web-app-deployment-with-git-kudu/4de61e80/1@2x.png\"  alt=\"Kudu: Деплой веб-проектов при помощи Git\"/>\n\n\n\n\n<h3>Шаг 2: Настраиваем права доступа</h3><p>Обратимся к созданному сайту через браузер — в нем пока нет ни одного приложения. На странице <code>Admin</code> можно увидеть расположение приложение Kudu, а также папку, в которую будут размещены все новые приложения.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2016/web-app-deployment-with-git-kudu/d064558f/2@2x.png\"  alt=\"Kudu: Деплой веб-проектов при помощи Git\"/>\n\n\n\n\n<p>По умолчанию папка для приложений — <code>app</code>. Можно изменить этот путь в настройках <code>Web.config</code> для <code>Kudu.Web</code>.</p>\n<p>Следует убедиться, что текущий пользователь в IIS имеет права на запись в эту папку. Иначе Kudu не сможет создавать и деплоить проекты.</p>\n<h3>Шаг 3: Создаем новый проект</h3><p>В Kudu Dashboard переходим на страницу <code>Create Application</code> и указываем имя приложения. Нажимаем <code>Create applciation</code>.</p>\n<p>При создании приложения Kudu создает в IIS два приложения — <code>kudu_имяприложения</code> и <code>kudu_service_имяприложения</code>.</p>\n<p>Первое — приложение, которое требуется деплоить. Второе — сервисное приложение. Оно отображает информацию о проекте, историю коммитов и другую служебную информацию. Оно же хостит git-репозиторий.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2016/web-app-deployment-with-git-kudu/fba0c8b9/3@2x.png\"  alt=\"Kudu: Деплой веб-проектов при помощи Git\"/>\n\n\n\n\n<h3>Шаг 4: Деплоим</h3><p>Клонируем репозиторий проекта:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">git clone http://localhost:45857/coolapp.git</code></pre>\n<p>Добавляем файлы в папку проекта и выполняем коммит:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">git commit -am “Initial commit”</code></pre>\n<p>Пушим изменения.</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">git push origin master</code></pre>\n<p>Проверяем результат:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2016/web-app-deployment-with-git-kudu/7f18006e/4@2x.png\"  alt=\"Kudu: Деплой веб-проектов при помощи Git\"/>\n\n\n\n\n<h3>Бонус</h3><p>Каждый коммит в репозиторий — это изменение состояние приложения. Как следствие, мы имеем историю всех деплоев приложения в виде понятной истории коммитов. В свою очередь это позволяет нам быстро откатиться на нужный шаг назад, если приложением возникли проблемы.</p>",
            "tags": [
                "ASP.NET",
                "DevOps",
                "Git"
            ],
            "banner_image": "https://blog.zwezdin.com/2016/web-app-deployment-with-git-kudu/998cf19b/web_app-deployment-with-git-kudu@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2016/dotnet-core/",
            "url": "https://blog.zwezdin.com/2016/dotnet-core/",
            "title": ".NET Core 1.0 и ASP.NET Core 1.0",
            "date_published": "2016-01-23T13:00:00.000Z",
            "content_html": "<p>В феврале прошлого года весь мир узнал о перспективах платформы ASP.NET. Новая версия ASP.NET впечатляет: кросс-платформенность, логичный рантайм на OWIN, модульность (прощай монструозный System.Web), интеграция с популярными инструментами разработки (Grunt, Gulp, NPM), встроенный DI и прочие вкусности. В дополнение к этому, было объявлено об открытии исходных кодов фреймворка. Тогда это называлось ASP.NET vNext, а затем ASP.NET 5. MVC получила версию 6.</p>\n<p>Коллеги, которые не очень тщательно следят за новостями постоянно путались: <em>« — Новый ASP.NET, который пятый. — Ты имеешь в виду MVC 6? — Да, шестая MVC, которая пятый ASP.NET»</em>.</p>\n<p>Между тем <strong>ASP.NET 5</strong> — это не просто очередная версия. Это — переосмысление всей платформы: поменялся API, способы хостинга приложения, рантайм. По характеру и объему изменений, этой версии, наверное, можно было бы давать номер 10 :-)</p>\n<p>Чтобы отразить это изменение, названия и номер версии новой платформы изменен:</p>\n<ul>\n<li><strong>ASP.NET 5</strong> → <strong>ASP.NET Core 1.0</strong></li>\n<li><strong>.NET Core 5</strong> → <strong>.NET Core 1.0</strong></li>\n<li><strong>Entity Framework 7</strong> → <strong>EF Core 1.0</strong></li>\n</ul>\n<p>Что же, теперь название более точно отражает суть.</p>",
            "tags": [
                "ASP.NET",
                ".NET",
                ".NET Core"
            ],
            "banner_image": "https://blog.zwezdin.com/2016/dotnet-core/0c20e680/dotnet_core@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2016/chakracore-is-open-sourced/",
            "url": "https://blog.zwezdin.com/2016/chakracore-is-open-sourced/",
            "title": "Открыт исходный код JavaScript-движка Microsoft Edge",
            "date_published": "2016-01-17T13:00:00.000Z",
            "content_html": "<p>Microsoft продолжает открывать код своих проектов. На этот раз в открытом доступе оказался код <strong>ChakraCore</strong> — Javascript-движка, используемого в новом браузере Microsoft Edge. Код проекта размещен на <a href=\"https://github.com/Microsoft/ChakraCore\">GitHub</a>. Это означает, что каждый разработчик теперь может внести свой вклад в проект.</p>\n<p>К проекту прилагается <a href=\"https://github.com/Microsoft/ChakraCore/wiki/Roadmap\">дорожная карта</a>, которая показывает как проект будет развиваться в ближайшем будущем. Одним из пунктов дорожной карты является запуск и поддержка кода Node.js, что намекает на то, что Microsoft посоревнуется с V8 в этой нише.</p>\n<p>Судя по истории коммитов, работа над проектом кипит и сообщество играет в нем важную роль. Остается только пожелать разработчиком удачи и терпения в этом нелегком деле.</p>",
            "tags": [
                "JavaScript",
                "Open Source"
            ],
            "banner_image": "https://blog.zwezdin.com/2016/chakracore-is-open-sourced/dbe94a53/chakracore_is-open-sourced@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2016/aspnet5-http-platform-handler/",
            "url": "https://blog.zwezdin.com/2016/aspnet5-http-platform-handler/",
            "title": "Модель хостинга в ASP.NET 5",
            "date_published": "2016-01-10T13:00:00.000Z",
            "content_html": "<p>В ASP.NET 5 существенно поменялась модель хостинга приложения внутри веб-сервера. В предыдущих версиях ASP.NET за хостинг приложения отвечали модули, которые являются частью IIS. В ASP.NET 5 эта часть вынесена из веб-сервера в отдельную библиотеку — <strong>HTTP Platform Handler</strong>.</p>\n<p>Приложение на ASP.NET 5 в корне содержит файл <code>web.config</code>, который указывает веб-серверу использовать HTTP Platform Handler:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;configuration>\n  &lt;system.webServer>\n    &lt;handlers>\n      &lt;add name=\"httpplatformhandler\" path=\"*\" verb=\"*\" modules=\"httpPlatformHandler\" resourceType=\"Unspecified\" />\n    &lt;/handlers>\n    &lt;httpPlatform processPath=\"..\\approot\\web.cmd\" arguments=\"\" stdoutLogEnabled=\"false\" stdoutLogFile=\"..\\logs\\stdout.log\" startupTimeLimit=\"3600\">&lt;/httpPlatform>\n  &lt;/system.webServer>\n&lt;/configuration></code></pre>\n<p>Такой подход предполагает наличие установленной библиотеки <strong>HTTP Platform Handler</strong> на сервере, где запускается приложение. Когда вы устанавливаете ASP.NET Tools на локальный компьютер, HTTP Platform Handler устанавливается вместе с ними и IIS корректно регаирует на этот способ хостинга.</p>\n<p>Однако, если вы перенесете это приложение на сервер, то обнаружите ошибку при запуске приложения. Все потому, что HTTP Platform Handler там не установлен. Вот две ссылки для загрузки установочного пакета HTTP Platform Handler:</p>\n<ul>\n<li><a href=\"http://go.microsoft.com/fwlink/?LinkId=690722\">HTTP Platform Handler (x86)</a></li>\n<li><a href=\"http://go.microsoft.com/fwlink/?LinkID=690721\">HTTP Platform Handler (x64)</a></li>\n</ul>\n<p>В <code>web.config</code> можно задать переменные окружения, которые будут использоваться при запуске приложения. Это значение можно использовать внутри приложения, чтобы менять поведение в процессе работы.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;httpPlatform processPath=\"..\\approot\\web.cmd\" arguments=\"\" stdoutLogEnabled=\"false\" stdoutLogFile=\"..\\logs\\stdout.log\" startupTimeLimit=\"3600\">\n  &lt;environmentVariables>\n    &lt;environmentVariable name=\"environment\" value=\"prod\" />\n  &lt;/environmentVariables>\n&lt;/httpPlatform></code></pre>",
            "tags": [
                "ASP.NET"
            ],
            "banner_image": "https://blog.zwezdin.com/2016/aspnet5-http-platform-handler/781ccfae/aspnet5_http-platform-handler@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2016/aspnet5-tag-helpers/",
            "url": "https://blog.zwezdin.com/2016/aspnet5-tag-helpers/",
            "title": "Создаем читабельную разметку при помощи Tag Helpers в ASP.NET 5",
            "date_published": "2016-01-03T13:00:00.000Z",
            "content_html": "<p>Razor — неотъемлемая часть ASP.NET. Он позволяет динамически генерировать HTML-разметку, используя конструкции языка C#. Но так ли уж хорош синтаксис Razor для описания сложных сценариев?</p>\n<p>Давайте взглянем на следующий код:</p>\n<pre class=code language-html><code class=\"code__container language-html\">@foreach (var item in items) &#123;\n  &lt;li @(item.IsActive ? \"class=\\\"active\\\"\" : string.Empty)>@item.name&lt;/li>\n&#125;</code></pre>\n<p>Видно, что конструкция для пометки элемента списка классом active нагромождает код и уменьшает его читаемость. Код ухудшается, если нужно управлять набором из нескольких классов:</p>\n<pre class=code language-html><code class=\"code__container language-html\">@foreach (var item in items) &#123;\n  var cl = new List&lt;string>();\n  if (item.IsActive)\n    cl.Add(\"active\");\n\n  if (item.IsFeatured)\n    cl.Add(\"featured\");\n\n  &lt;li @(cl.Any() ? \"class=\\\"\" + String.Join(\" \", cl.ToArray() + \"\\\"\" : string.Empty)>@item.name&lt;/li>\n&#125;</code></pre>\n<p>Можно использовать собственные хелперы, но это тоже не всегда лучшим образом отражается на читаемости кода.</p>\n<p>Для решения этой проблемы в ASP.NET 5 появились специальные объекты — <strong>Tag Helpers</strong>. Они позволяют вынести сложную логику из шаблона Razor в небольшие классы, а в разметке оставить только HTML-атрибуты. К примеру, приведенный выше код мог бы быть переписан вот так:</p>\n<pre class=code language-html><code class=\"code__container language-html\">@foreach (var item in items) &#123;\n  &lt;li asp-active=\"@item.IsActive\" asp-featured=\"@item.IsFeatured\">@item.name&lt;/li>\n&#125;</code></pre>\n<h2>Подключение собственного хелпера</h2><p>С ASP.NET уже поставляется базовый набор хелперов. Но вся сила тег хелперов в простоте их создания в конкретно вашей ситуации.</p>\n<p>Каждый хелпер — это небольшой объект, унаследованный от <code>TagHelper</code>. Также при помощи атрибута <code>HtmlTargetElement</code> мы должны указать в каких ситуациях применим этот тег хелпер.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[HtmlTargetElement(\"li\", Attributes = \"asp-active, asp-featured\")]\npublic class ItemTagHelper : TagHelper\n&#123;\n&#125;</code></pre>\n<p>Простейший тег-хелпер готов. Пока он ничего не делает, но к этому мы вернемся чуть позже. А пока давайте сообщим ASP.NET, что мы создали собственный хелпер.</p>\n<p>Для подключения тег-хелпера появилась директива <code>@addTagHelper</code>. Чтобы использовать тег хелпер, мы должны добавить его при помощи этой директивы в разметке страницы. Если мы хотим использовать этот хелпер на всех страницах приложения, то вызов <code>@addTagHelper</code> можно добавить в файл <code>_ViewImports.cshtml</code>:</p>\n<pre class=code language-html><code class=\"code__container language-html\">@addTagHelper \"*, WebApplication15\"</code></pre>\n<p>Таким вызовом мы добавляем все тег-хелперы из указанной сборки.</p>\n<h2>Получение значений атрибутов внутри хелпера</h2><p>Вернемся к реализации логики хелпера. Нам требуется получить значения атрибутов <code>asp-active</code> и <code>asp-featured</code>. Для этого добавим два свойства в класс хелпера и пометим их атрибутом <code>HtmlAttributeName</code> — это укажет на то, что эти свойства нужно заполнить значениями этих HTML-атрибутов.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[HtmlTargetElement(\"li\", Attributes = \"asp-active, asp-featured\")]\npublic class ItemTagHelper : TagHelper\n&#123;\n  [HtmlAttributeName(\"asp-active\")]\n  public bool Active &#123; get; set; &#125;\n\n  [HtmlAttributeName(\"asp-featured\")]\n  public bool Featured &#123; get; set; &#125;\n&#125;</code></pre>\n<h2>Обработка элемента</h2><p>Работа тег хелпера предполагает модификацию исходного HTML-элемента (иначе зачем нам вообще нужен хелпер). В нашем примере мы хотим превратить значение атрибутов <code>asp-active</code> и <code>asp-featured</code> в набор CSS-классов.</p>\n<p>Чтобы добавить логику работы хелпера, достаточно реализовать метод <code>Process</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[HtmlTargetElement(\"li\", Attributes = \"asp-active, asp-featured\")]\npublic class ItemTagHelper : TagHelper\n&#123;\n  [HtmlAttributeName(\"asp-active\")]\n  public bool Active &#123; get; set; &#125;\n\n  [HtmlAttributeName(\"asp-featured\")]\n  public bool Featured &#123; get; set; &#125;\n\n  public override void Process(TagHelperContext context, TagHelperOutput output)\n  &#123;\n  &#125;\n&#125;</code></pre>\n<p>Второй параметр (<code>TagHelperOutput</code>) позволяет модифицировать исходный тег. Добавим немного логики, получим такой код:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[HtmlTargetElement(\"li\", Attributes = \"asp-active, asp-featured\")]\npublic class ItemTagHelper : TagHelper\n&#123;\n  [HtmlAttributeName(\"asp-active\")]\n  public bool Active &#123; get; set; &#125;\n\n  [HtmlAttributeName(\"asp-featured\")]\n  public bool Featured &#123; get; set; &#125;\n\n  public override void Process(TagHelperContext context, TagHelperOutput output)\n  &#123;\n    TagBuilder builder = new TagBuilder(output.TagName);\n\n    if (Active)\n      builder.AddCssClass(\"active\");\n\n    if (Featured)\n      builder.AddCssClass(\"featured\");\n\n    output.MergeAttributes(builder);\n  &#125;\n&#125;</code></pre>\n<p>Теперь можем поменять код разметки.</p>\n<p>Было:</p>\n<pre class=code language-html><code class=\"code__container language-html\">@foreach (var item in items) &#123;\n  var cl = new List&lt;string>();\n  if (item.IsActive)\n    cl.Add(\"active\");\n\n  if (item.IsFeatured)\n    cl.Add(\"featured\");\n\n  &lt;li string.empty)=\"\" :=\"\" &amp;quot;\\&amp;quot;&amp;quot;=\"\" +=\"\" cl.toarray()=\"\" &amp;quot;,=\"\" string.join(&amp;quot;=\"\" &amp;quot;class=\"\\&amp;quot;&amp;quot;\" ?=\"\" @(cl.any()=\"\">@item.name\n&#125;&lt;/li>&lt;/string></code></pre>\n<p>Стало:</p>\n<pre class=code language-html><code class=\"code__container language-html\">@foreach (var item in items) &#123;\n  &lt;li asp-featured=\"&amp;quot;@item.IsFeatured&amp;quot;\" asp-active=\"&amp;quot;@item.IsActive&amp;quot;\">@item.name\n&#125;&lt;/li></code></pre>\n<h2>Асинхронность</h2><p>В зависимости от ситуации, может потребоваться использовать паттерн <code>async</code>/<code>await</code> внутри метода <code>Process</code>. Для этих случаев можно реализовать метод <code>ProcessAsync()</code> вместо <code>Process()</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)\n&#123;\n  // ...\n&#125;</code></pre>\n<h2>Использовать или нет?</h2><p>С точки зрения чистоты разметки тег-хелперы выглядят привлекательно. Многие части разметки сразу так и хочется вынести в тег хелперы. Но подойти к этому вопросу стоит осознанно, иначе проект рискует превратиться в кашу из тег-хелперов.</p>\n<p>Я бы посоветовал выделить наиболее встречающиеся блоки и обернуть их в тег-хелперы, а специфичные случаи оставить в разметке. Вот хорошие кандидаты на вынесение в тег хелперы:</p>\n<ul>\n<li>атрибуты валидации</li>\n<li>генерация URL для SPA-приложений</li>\n<li>управление стилями элемента (указание CSS-класса)</li>\n<li>заготовки элементов формы (label + input)</li>\n<li>заготовки часто повторяющихся конструкций из CSS-фреймворков</li>\n<li>и т.д.</li>\n</ul>",
            "tags": [
                "ASP.NET"
            ],
            "banner_image": "https://blog.zwezdin.com/2016/aspnet5-tag-helpers/c883c884/aspnet5_tag-helpers@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2015/vs-code-is-open-sourced/",
            "url": "https://blog.zwezdin.com/2015/vs-code-is-open-sourced/",
            "title": "Visual Studio Code теперь в Open Source",
            "date_published": "2015-11-22T13:00:00.000Z",
            "content_html": "<p>В ходе виртуальной конференции <a href=\"https://channel9.msdn.com/Events/Visual-Studio/Connect-event-2015/\">Microsoft Connect(); // 2015</a> объявлено об открытии исходных кодов кросс-платформенного редактора кода Visual Studio Code.</p>\n<p><strong>VS Code</strong> существует в редакциях для Windows, Mac OSX и Linux. В его основе лежит идея о простом редакторе кода с минимальным функционалом. Редактор позволяет работать с кодом ASP.NET 5, JavaScript, C++, F#, Powershell, Python и др. Приятно, что как и у старшей версии, в VS Code поддерживается Intellisense.</p>\n<p>Другой приятной особеннотью VS Code является встроенная поддержка git. Вы можете выполнять простейшие операции с репозитореим не покидая среды разработки.</p>\n<p>Таким образом, VS Code имеет все шансы занять свое место на рынке. Особенно в том случае, когда всей мощи полной Visual Studio не требуется.</p>",
            "tags": [
                "Tools",
                "Visual Studio",
                "Visual Studio Code",
                "Open Source"
            ],
            "banner_image": "https://blog.zwezdin.com/2015/vs-code-is-open-sourced/d64fe3fd/vs_code-is-open-sourced@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2015/tfs-on-premise-to-vs-online/",
            "url": "https://blog.zwezdin.com/2015/tfs-on-premise-to-vs-online/",
            "title": "Миграция TFS On-Premise в Visual Studio Online",
            "date_published": "2015-04-04T13:00:00.000Z",
            "content_html": "<p>Для команд, использующих собственный TFS перспектива использования сервиса Visual Studio Online выглядит очень заманчиво — не нужно обслуживать сервера, делать бэкапы и т.д. Но сервис Visual Studio Online существует относительно недавно и у многих проекты живут в собственных TFS. В этом случае можно выполнить миграцию данных.</p>\n<p>Для миграции можно использовать утилиту от компании <strong>OpsHub</strong>. Пройдем последовательно шаги, необходимые для миграции проекта.</p>\n<ol>\n<li>Создаем новый аккаунт в VS Online.</li>\n<li>Создаем проект, который собираемся мигрировать. Имя проекта должно совпадать с именем проекта в старом TFS.</li>\n<li>Создаем в VS Online всех пользователей, которые присутствовали в старом TFS.</li>\n<li>Пользователь, который будет выполнять миграцию должен входить в группу <code>Project Collection Service Accounts</code>. Если текущий пользователь не входит в эту группу, нужно его добавить туда (<code>Control Panel</code> → <code>Project</code> → <code>Security</code> → <code>Users</code>).</li>\n</ol>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2015/tfs-on-premise-to-vs-online/75837e9b/1@2x.png\"  alt=\"Миграция TFS On-Premise в Visual Studio Online\"/>\n\n\n\n\n<h2>Миграция</h2><p><strong>Шаг 1:</strong> Утилита OpsHub неспособна мигрировать проект, в котором есть какая-либо кастомизация. Поэтому перед миграцией следует “очистить” исходный проект от каких-либо собственных дополнений.</p>\n<p><strong>Шаг 2:</strong> Устанавливаем и запускаем утилиту.</p>\n<p><strong>Шаг 3:</strong> Прежде всего следует предоставить информацию об исходном TFS и аккаунте в VS Online. Потребуется URL и имя пользователя.</p>\n<p><strong>Шаг 4:</strong> Следующим шагом будет создание миграции. Каждый проект переносится в рамках миграции. Для этого необходимо выбрать кнопку <code>New Migration</code>, указать исходный и целевой сервер и нажать <code>Next</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2015/tfs-on-premise-to-vs-online/409b807a/2@2x.png\"  alt=\"Миграция TFS On-Premise в Visual Studio Online\"/>\n\n\n\n\n<p><strong>Шаг 5:</strong> Следующим шагом следует указать что именно вы хотите мигрировать — содержимое бэклога, исходный код или то и другое.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2015/tfs-on-premise-to-vs-online/cb64b1f5/3@2x.png\"  alt=\"Миграция TFS On-Premise в Visual Studio Online\"/>\n\n\n\n\n<p><strong>Шаг 6:</strong> Следующий этап - выбор проекта для миграции. Здесь можно выбрать несколько проектов одновременно, но я бы не рекомендовал этот путь. На этом этапе пустой проект в VS Online с тем же именем уже должен быть создан.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2015/tfs-on-premise-to-vs-online/58f6d218/4@2x.png\"  alt=\"Миграция TFS On-Premise в Visual Studio Online\"/>\n\n\n\n\n<p><strong>Шаг 7:</strong> Следующим шагом вам нужно сопоставить пользователей старого TFS и пользователей VS Online. Если у вас большая команда, это, пожалуй, будет самым неприятным этапом. Тем не менее, пропустить этот шаг нельзя.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2015/tfs-on-premise-to-vs-online/593e3fb7/5@2x.png\"  alt=\"Миграция TFS On-Premise в Visual Studio Online\"/>\n\n\n\n\n<p><strong>Шаг 8:</strong> После этого утилита миграции проведет полную проверку параметров миграции. Обычно этот процесс занимает несколько минут.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2015/tfs-on-premise-to-vs-online/485a150d/6@2x.png\"  alt=\"Миграция TFS On-Premise в Visual Studio Online\"/>\n\n\n\n\n<p><strong>Шаг 9:</strong> Последним шагом вам будет предложено начать миграцию. Соглашаясь с этим вы запускаете процесс миграции. По опыту он достаточно ресурсозатратен и не быстр, поэтому наберитесь терпения.</p>\n<p>Если в процессе что-то пошло не так, то всегда можно удалить проект из VS Online и начать проект заново.</p>\n<p>К сожалению, этот способ не работает с большими проектами, где большие бэклоги и кодовые базы. В нашем случае утилита просто съедала весь память и CPU и отказывалась работать. В таких, более сложных случаях, придется придумывать какие-то обходные пути. Ну а для небольших проектов этот способ вполне подойдет.</p>",
            "tags": [
                "Team Foundation Server",
                "Visual Studio Team System"
            ],
            "banner_image": "https://blog.zwezdin.com/2015/tfs-on-premise-to-vs-online/d37f4963/tfs_on-premise-to-vs-online@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2015/application-insights-events-export/",
            "url": "https://blog.zwezdin.com/2015/application-insights-events-export/",
            "title": "Экспорт событий Application Insights",
            "date_published": "2015-03-30T13:00:00.000Z",
            "content_html": "<p><strong>Application Insights</strong> предоставляет очень неплохие инструменты аналитики. Но нельзя исключать ситуаций, когда требуется подключить дополнительные инструменты для анализа различного рода событий. Для этих ситуаций в Application Insights существует функция непрерывного экспорта событий.</p>\n<p>На данный момент непрерывный экспорт возможен только в Azure Storage. Поэтому перед началом настройки экспорта нужно создать <strong>Azure Storage</strong> и контейнер внутри него. После создания хранилища, нужно перейти в панель управления Application Inisights, выбрать раздел <code>Continous Export</code> и нажать кнопку <code>Add</code>:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2015/application-insights-events-export/124edb68/1@2x.png\"  alt=\"Экспорт событий Application Insights\"/>\n\n\n\n\n<p>После выбора хранилища, необходимо выбрать тип событий, которые должны экспортироваться:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2015/application-insights-events-export/094a8d10/2@2x.png\"  alt=\"Экспорт событий Application Insights\"/>\n\n\n\n\n<p>На этом настройка непрерывного экспорта завершена. С этого момента Application Insights будет экспортировать выбранный тип событий в указанное хранилище. Данные будут сохраняться в формате JSON. Пример экспортированного файла:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2015/application-insights-events-export/78c00a5c/3@2x.png\"  alt=\"Экспорт событий Application Insights\"/>",
            "tags": [
                "ASP.NET",
                "Application Insights"
            ],
            "banner_image": "https://blog.zwezdin.com/2015/application-insights-events-export/f939ddb2/application_insights-events-export@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2015/application-insights-events-tracking/",
            "url": "https://blog.zwezdin.com/2015/application-insights-events-tracking/",
            "title": "Отслеживание событий при помощи сервиса Application Insights",
            "date_published": "2015-03-18T13:00:00.000Z",
            "content_html": "<p>Одна из полезных возможностей <strong>Application Insights</strong> — это отслеживание событий, которые происходят в приложение во время работы. На основании этих данных можно сделать выводы об использовании приложения, какие фичи пользователи используют, а какие нет. Отдельно можно провести анализ ошибок, возникающих при работе.</p>\n<h2>Пользовательские события</h2><p>Пользовательские события позволяют определить ключевые точки приложения и анализировать статистику их использования. Например, в интернет-магазине можно добавить событие добавления в корзину. В системе документооборота такими событиями могут быть создание и изменение документа. Позже, по этим событиям можно получить статистику по времени.</p>\n<p>Для работы с пользовательскими событиями, кроме подключения AI к проекту, требуется добавить немного кода в приложение. Для взаимодействия с сервисом используется объект <code>TelemetryClient</code>. Отправка события регистрация пользователя в приложении может выглядеть так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">TelemetryClient telemetry = new TelemetryClient();\ntelemetry.TrackEvent(\"UserSignup\");</code></pre>\n<p>Если вместе с событием требуется учитывать какие-то дополнительные данные, то в метод <code>TrackEvent</code> можно передать дополнительные параметры:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">TelemetryClient telemetry = new TelemetryClient();\ntelemetry.TrackEvent(\"UserSignup\",new Dictionary&lt;string, string>\n&#123;\n    &#123;\"Role\", \"Observer\"&#125;,\n    &#123;\"Company\", \"A&amp;B\"&#125;\n&#125;);</code></pre>\n<p>Аналитика по событиям выглядит следующим образом:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2015/application-insights-events-tracking/d6285cac/1@2x.png\"  alt=\"Отслеживание событий при помощи сервиса Application Insights\"/>\n\n\n\n\n<p>Можно получить дополнительные проекции данных, используя фильтры:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2015/application-insights-events-tracking/da7ae2e9/2@2x.png\"  alt=\"Отслеживание событий при помощи сервиса Application Insights\"/>\n\n\n\n\n<p>Клик по каждому событию также покажет подробную информацию по выбранному событию.</p>\n<p>Как и в случае с анализом работоспособности приложения, здесь можно настроить уведомления, которые будут отправляться при достижении пороговых значений. Например, если количество добавлений в корзину за день меньше, чем какое-то выбранное значение, то вы получите уведомление по email.</p>\n<h2>Логи</h2><p>Другой замечательной возможностью AI является возможность хранения и обработки логов. Безусловно многие хранят логи на своих серверах в файлах специального формата. Но я думаю не ошибусь, если скажу, что процесс чтения и анализа лог-файлов не доставляет особого удовольствия. Все потому, что в большинстве случаев это простые текстовые файлы, содержащие огромный объем данных.</p>\n<p><strong>Application Insights</strong> ведет журнал внутренних событий - HTTP-запрос, просмотр страницы и т.п. При возникновении нестандартных ситуаций в приложении можно также отправить эту информацию в AI. Для этого используется тот же самый объект <code>TelemetryClient</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">try\n&#123;\n  // ...\n&#125;\ncatch (Exception ex)\n&#123;\n  TelemetryClient telemetry =\n          new TelemetryClient();\n  telemetry.TrackException(ex);\n\n  throw;\n&#125;</code></pre>\n<p>Информация об исключении передается в метод <code>TrackException</code> и далее отправляется на сервис. Есть перегруженная версия этого метода, которая вместе с исключение позволяет отправить на сервер дополнительные данные (свойства и метрики, критичность исключения и т.д.):</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">try\n&#123;\n    // ...\n&#125;\ncatch (Exception ex)\n&#123;\n  TelemetryClient telemetry = new TelemetryClient();\n  telemetry.TrackException(new ExceptionTelemetry(ex)\n  &#123;\n    Properties =\n    &#123;\n        &#123;\"Role\", \"Observer\"&#125;,\n        &#123;\"Company\", \"A&amp;B\"&#125;\n    &#125;,\n    Metrics =\n    &#123;\n        &#123;\"Balance\", 150&#125;,\n    &#125;,\n    SeverityLevel = SeverityLevel.Critical\n  &#125;);\n\n  throw;\n&#125;</code></pre>\n<p>Позже по этим данным можно строить дополнительные проекции данных и подвергать их анализу.</p>\n<p>Если в приложении уже присутствует система ведения логов, то можно подключить AI к ней. На данный момент существуют адаптеры для:</p>\n<ul>\n<li><a href=\"http://www.nuget.org/packages/Microsoft.ApplicationInsights.TraceListener\">System.Diagnostics.Trace</a></li>\n<li><a href=\"http://www.nuget.org/packages/Microsoft.ApplicationInsights.NLogTarget\">NLog</a></li>\n<li><a href=\"http://www.nuget.org/packages/Microsoft.ApplicationInsights.Log4NetAppender\">Log4Net</a></li>\n</ul>\n<p>Отчет по содержимому логов выглядит так:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2015/application-insights-events-tracking/77fb2a66/3@2x.png\"  alt=\"Отслеживание событий при помощи сервиса Application Insights\"/>\n\n\n\n\n<p>В верхней части показывается общая статистика и тренды. По каждому событию можно кликнуть и получить полную информацию. Что удобно, AI умеет систематизировать события и по выбранному событию отобразить запросы, так или иначе связанные с этим событием.</p>\n<p>Таким образом, данные аспекты работы приложения всегда остаются под вашим контролем.</p>",
            "tags": [
                "ASP.NET",
                "Application Insights"
            ],
            "banner_image": "https://blog.zwezdin.com/2015/application-insights-events-tracking/34c438fb/application_insights-events-tracking@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2015/application-insights-performance/",
            "url": "https://blog.zwezdin.com/2015/application-insights-performance/",
            "title": "Измерение производительности и доступности приложения при помощи Application Insights",
            "date_published": "2015-03-12T13:00:00.000Z",
            "content_html": "<p>В <a href=\"/2015/application-insights-introduction/\">предыдущем посте</a> было описано что такое <strong>Application Insights</strong> и какие задачи решает эта платформа. На этот раз взглянем на Application Insights со стороны мониторинга работоспособности приложения.</p>\n<p>Будем считать, что приложение работает если: <em>1)</em> оно корректно отвечает на ключевые HTTP-запросы; <em>2)</em> делает это за приемлемое время. Т.е. нам нужно убедиться отвечает ли приложение вообще, и если да, то насколько быстро отрабатывает запросы. Причем этот мониторинг должен выполняться на постоянной основе, через определенные интервалы.</p>\n<p>Основой для тестирования доступности и производительности явлются веб-тесты. Веб-тест может быть простым (простой HTTP-запрос по указанному адресу и проверка ответа) и записанным по сценарию (зайти на страницу, перейти на другую, ввести данные и т.п.). Записанные веб-тесты — это <strong>Web Performance Test</strong>, который можно создать или записать в Visual Studio. В результате получается файл <code>.webtest</code>, который нужно будет загрузить в Application Insights.</p>\n<p>Для создания веб-теста в AI, нужно выбрать раздел <code>Availability</code> и нажать кнопку <code>Add web test</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2015/application-insights-performance/95933078/1@2x.png\"  alt=\"Измерение производительности и доступности приложения при помощи Application Insights\"/>\n\n\n\n\n<p>При создании теста следует указать тип теста, задать нужные параметры и указать с каких локаций он будет запускаться.</p>\n<p>Сразу после создания сервис запустит эти тесты. На общей панели управления будет отображаться сводка о наиболее медленных запросах:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2015/application-insights-performance/91670f5e/2@2x.png\"  alt=\"Измерение производительности и доступности приложения при помощи Application Insights\"/>\n\n\n\n\n<p>Клик по этой панели откроет раздел, где можно посмотреть детальную аналитику по каждому запросу, времени выполнения и трендам:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2015/application-insights-performance/4d55554d/3@2x.png\"  alt=\"Измерение производительности и доступности приложения при помощи Application Insights\"/>\n\n\n\n\n<p>Кликнув по конкретному адресу можно посмотреть детали конкретного запроса.</p>\n<p>Скорее всего, для своего приложения вы имеете ориентиры насколько быстро оно должно отвечать на HTTP-запросы. Эти пороговые значения можно указать при настройке веб-теста. В таком случае при превышении этого порога вы получите уведомление на email. Уведомления так же будут отправляться, если веб-тест завершился неудачей (например, страница по нужному адресу не открылась).</p>\n<p>Отчет под доступности приложения также очень наглядно показывает состояние приложения:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2015/application-insights-performance/aae57d70/4@2x.png\"  alt=\"Измерение производительности и доступности приложения при помощи Application Insights\"/>\n\n\n\n\n<p>Подобная аналитика с одной стороны очень проста для восприятия, а с другой является очень мощным инструментом, дающим наглядную картину того, насколько успешно работает приложение.</p>",
            "tags": [
                "ASP.NET",
                "Application Insights"
            ],
            "banner_image": "https://blog.zwezdin.com/2015/application-insights-performance/0637562b/application_insights-performance@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2015/application-insights-introduction/",
            "url": "https://blog.zwezdin.com/2015/application-insights-introduction/",
            "title": "Что такое Application Insights и как этот сервис может помочь разработчику",
            "date_published": "2015-03-01T13:00:00.000Z",
            "content_html": "<p>Мы, разработчики, очень заботимся о коде, пишем тесты, проводим рефакторинг. Но когда дело касается переноса приложения на реальный сервер, не очень то и заботимся об этом - перенесли, проверили, что работает и забыли. Однако именно в этот момент могут появиться проблемы самого разного характера. Сервисы мониторинга позволяют заполнить этот пробел в жизненном цикле приложений и осуществлять постоянный контроль за работоспособностью приложения. Одним из таких сервисов является <strong>Application Insights</strong>.</p>\n<p>Сервис <strong>Application Insights</strong> впервые появился как часть <strong>Visual Studio Online</strong>. На данный момент этот сервис является частью облачной платформы Azure со всеми вытекающими последствиями. Сервис позволяет мониторить показатели используемости, доступности и производительности приложения. Но обо все по-порядку.</p>\n<p>Application Insights работает с приложениями ASP.NET. Приложения Windows Phone и Windows Store также можно мониторить. Совсем недавно была <a href=\"http://azure.microsoft.com/blog/2015/03/09/application-insights-java-sdk/\">заявлена</a> поддержка Java. Кроме того, Application Insights можно подключить к приложениям на платформе Node.js, PHP, Python, Ruby, Android и iOS, SDK для каждой из этих платформ находятся в открытом доступе на <a href=\"https://github.com/Microsoft/AppInsights-Home\">github</a>.</p>\n<p>Для подключения Application Insights к проекту потребуется Visual Studio 2013 Update 3 или новее. Для этого заходим на <a href=\"https://portal.azure.com/\">портал Azure</a> и создаем новый сервис Application Insights. После Application Insights можно подключать к проекту. Для этого достаточно правой кнопкой кликнуть на проекте и выбрать пункт меню <code>Add Application Insights Telemetry</code>:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2015/application-insights-introduction/e9c51815/1@2x.png\"  alt=\"Что такое Application Insights и как этот сервис может помочь разработчику\"/>\n\n\n\n\n<p>После этого будет предложено войти на портал Azure и выбрать сервис, который будет связан с этим приложением. После этого в проект будет добавлен файл <code>ApplicationInsights.config</code> (содержащий идентификатор сервиса AI в облаке). Кроме того, в проект устанавливаются нужные пакеты из Nuget и в <code>web.config</code> добавляются Http-моудли для отслеживания активности. Альтернативный способ - это воспользоваться <a href=\"http://go.microsoft.com/fwlink/?linkid=506648&clcid=0x409\">специальной утилитой</a> на сервере, тогда код проекта изменять не потребуется. Всё, что делает эта утилита - добавляет нужные библиотеки и меняет конфигурационный файл. Так что в некотором смысле оба способа идентичны.</p>\n<p>Теперь можно публиковать и запускать приложение. Данные начнут собираться и отправляться на сервис. Через несколько минут после этого можно начать смотреть отчеты.</p>\n<h2>Примеры отчетов</h2><p>Количество пользователей, сессий, запросов, статистика по странам:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2015/application-insights-introduction/e6cf4980/2@2x.png\"  alt=\"Что такое Application Insights и как этот сервис может помочь разработчику\"/>\n\n\n\n\n<p>Отслеживание медленных запросов:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2015/application-insights-introduction/b80b3308/3@2x.png\"  alt=\"Что такое Application Insights и как этот сервис может помочь разработчику\"/>\n\n\n\n\n<p>Отслеживание исключений:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2015/application-insights-introduction/b3745496/4@2x.png\"  alt=\"Что такое Application Insights и как этот сервис может помочь разработчику\"/>",
            "tags": [
                "ASP.NET",
                "Application Insights"
            ],
            "banner_image": "https://blog.zwezdin.com/2015/application-insights-introduction/27f4260b/application_insights-introduction@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2014/hangfire/",
            "url": "https://blog.zwezdin.com/2014/hangfire/",
            "title": "Выполнение кода в фоне в ASP.NET при помощи Hangfire",
            "date_published": "2014-12-21T13:00:00.000Z",
            "content_html": "<p>Веб-приложения - это не только, обработка HTTP-запросов. Часто приложению приходится выполнять какую-то работу в фоне - например, списывать деньги со счета по расписанию или выполнять отправку email-сообщения, не задерживая при этом ответ пользователю.</p>\n<p>Решают эту задачу все по-разному - кто-то создает свою службу Windows, кто-то запускает код по расписанию с помощью планировщика Windows. Есть ещё один прекрасный способ - библиотека <strong>Hangfire</strong>.</p>\n<p>В основе идеи <strong>Hangfire</strong> лежит ключевое <em>“fire-and-forget”</em>, что означает запустить код на выполнение и забыть - далее он выполнится в фоне без вашего участия. При этом вам не потребуются дополнительные инфраструктурные вмешательства (такие как Windows Service), что существенно упростит процесс развертывания. Hangfire подключается к приложению путем установки пакета из Nuget.</p>\n<h2>Подключение Hangfire к приложению</h2><p>Для подключения Hangfire к приложению нужно установить пакет Hangfire из Nuget:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">Install-Package HangFire</code></pre>\n<p>Данный пакет содержит базовую инфраструктуру Hangfire и хранилище для SQL Server. Hangfire использует внешнее хранилище данных для очередей для того, чтобы фоновые задачи не удалялись при перезапуске приложения. По умолчанию в качестве хранилища используется SQL Server, в платной Pro-версии есть возможность работать с <a href=\"http://odinserj.net/2014/11/15/hangfire-pro/\">Redis</a>. Также я сделал свой <a href=\"https://github.com/sergeyzwezdin/Hangfire.Mongo\">провайдер для Mongo</a>.</p>\n<p>Для запуска Hangfire используется инфраструктура OWIN (отличное решение, с учетом задела на будущее), для конфигурации нужно выполнить метод <code>UseHangfire</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public void Configuration(IAppBuilder app)\n&#123;\n  app.UseHangfire(config =>\n  &#123;\n    config.UseSqlServerStorage(\"&lt;connection string or its name>\");\n    config.UseServer();\n  &#125;);\n&#125;</code></pre>\n<p>Метод <code>UseHangfire</code> принимает в качестве аргумента метод для конфигурирования Hangfire. Здесь нужно обязательно использовать какой-либо из типов источников данных. Провайдер для SQL Server автоматически создаст нужные таблице в вашей базе данных, поэтому дополнительных действий тут не требуется - нужно только указать строку подключения SQL Server:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public void Configuration(IAppBuilder app)\n&#123;\n  app.UseHangfire(config =>\n  &#123;\n    config.UseSqlServerStorage(\n        @\"Data Source=.\\SQLEXPRESS;Initial Catalog=TestApplication;Integrated Security=True\");\n    config.UseServer();\n  &#125;);\n&#125;</code></pre>\n<p>Необходимые таблицы будут созданы при первом запуске приложения:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/hangfire/bb3dc1e0/1@2x.png\"  alt=\"Выполнение кода в фоне в ASP.NET при помощи Hangfire\"/>\n\n\n\n\n<p>Аналогично работают провайдеры для Redis и Mongo — нужные коллекции создаются автоматически.</p>\n<p>Этого достаточно для того, чтобы Hangfire заработал. Можно запустить приложение и вам будет доступен Dashboard от Hangifre по адресу <code>/hangfire</code>. Там содержаться все задачи — выполненные, запланированные, ошибочные и т.д.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/hangfire/e6379da8/2@2x.png\"  alt=\"Выполнение кода в фоне в ASP.NET при помощи Hangfire\"/>\n\n\n\n\n<p>Если требуется закрыть паролем этот Dashboard, то существует отдельный пакет - <code>Hangfire.Dashboard.Authorization</code>, который поддерживает встроенную аутентификацию ASP.NET, а также Basic Authentication. Не забывайте настроить HTTPS для вашего веб-приложения.</p>\n<p>Такой конфигурации достаточно, чтобы потестировать Hangfire, но для продакшн-среды нужны некоторые дополнительные действия. Что именно нужно сделать зависит от типа хостинга вашего приложения, но в целом идея заключается в том, чтобы убедиться, что ASP.NET приложение не переходит в спящий режим. Подробнее это описано в <a href=\"http://docs.hangfire.io/en/latest/deployment-to-production/making-aspnet-app-always-running.html\">официальной документации</a>.</p>\n<h2>Запуск задач при помощи Hangfire</h2><p>Hangfire имеет возможность запуска разных типов задач:</p>\n<ul>\n<li>простая задача в фоне, которая запустится как можно скорее (“fire-and-forget”)</li>\n<li>задача, выполняемая с задержкой (например, через 1.5 часа после постановки в очередь)</li>\n<li>задача, выполняемая периодически по заданному расписанию</li>\n</ul>\n<p>При запуске задачи в Hangfire она помещается в очередь (в хранилище появляется соответствующая запись) и далее выполняется в фоне.</p>\n<p>Для примера, создадим приложение, отправляющее email-ы. Почему это нужно делать в фоне? Дело в том, что скорость отправки сообщения зависит от конкретного SMTP-сервера. Эта задержка увеличивается, если отправлять несколько сообщений. Например, у меня были случаи, когда по запросу от пользователя нужно было рассылать более 10 сообщений - в этом случае задержка становится ощутимой. В случае с Hangfire мы просто ставим задачи по отправке email-сообщений в очередь и больше не задерживаем пользователя. Поэтому предпочтительнее обрабатывать такие задачи именно в фоне.</p>\n<p>Создадим простейший сервис для отправки сообщения:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class EmailService\n&#123;\n  public static void Send(string from, string to, string subject, string body)\n  &#123;\n    using (var smtp = new SmtpClient())\n    &#123;\n      // ...\n\n      smtp.Send(from, to, subject, body);\n    &#125;\n  &#125;\n&#125;</code></pre>\n<p>Чтобы поставить задачу на отправку сообщения в очередь следует воспользоваться объектом <code>BackgroundJob</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public ActionResult Send()\n&#123;\n  BackgroundJob.Enqueue(() =>\n    EmailService.Send(\"from@email.com\", \"to@email.com\", \"Subject\", \"Text\"));\n\n  return View();\n&#125;</code></pre>\n<p>Как только задачи будут поставлены в очередь, это будет видно в Hangfire Dashboard:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/hangfire/0768dc88/3@2x.png\"  alt=\"Выполнение кода в фоне в ASP.NET при помощи Hangfire\"/>\n\n\n\n\n<p>Здесь же можно отслеживать успешные и неудачные попытки выполнения заданий, отслеживать задания по расписанию и визульно видеть объем задач, выполняемых в фоне.</p>",
            "tags": [
                "ASP.NET"
            ],
            "banner_image": "https://blog.zwezdin.com/2014/hangfire/7b73235e/hangfire@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2014/deploy-website-with-webdeploy/",
            "url": "https://blog.zwezdin.com/2014/deploy-website-with-webdeploy/",
            "title": "Настраиваем WebDeploy для развертывания приложений ASP.NET",
            "date_published": "2014-10-07T12:00:00.000Z",
            "content_html": "<p>Рано или поздно возникает необходимость развертывания приложения на сервер. Исторически многие делают это при помощи FTP/SFTP. Однако, этот способ развертывания нельзя назвать удобным. Мало того, что развертывание в этом случае занимает много времени, оно к тому же не является атомарным. Как же быть? Правильный ответ - <strong>WebDeploy</strong>.</p>\n<p><strong>WebDeploy</strong> позволяет собирать пакеты и атомарно развертывать их на сервере - из Visual Studio или используя автоматизированные скрипты. Давайте последовательно пройдем шаги по настройке WebDeploy на сервере.</p>\n<h2>Установка пакета Web Deploy</h2><p>Для установки потребуется скачать установочный пакет и запустить его на сервере. Альтернативным способом является установка Web Deploy через <code>Web Platform Installer</code>. В последнем случае для установки нужно выбрать пакет <code>Web Deploy 3.5</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/deploy-website-with-webdeploy/42a321bb/1@2x.png\"  alt=\"Настраиваем WebDeploy для развертывания приложений ASP.NET\"/>\n\n\n\n\n<h2>Настройка публикации</h2><p>После успешной установки Web Deploy в консолии IIS должны появится разделы <code>Management Service</code> и <code>Management Service Delegation</code>. Для корректной работы Web Deploy в разделе <code>Management Service</code> следует разрешить удаленные подключения:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/deploy-website-with-webdeploy/662eecdf/2@2x.png\"  alt=\"Настраиваем WebDeploy для развертывания приложений ASP.NET\"/>\n\n\n\n\n<p>Здесь же можно указать SSL-сертификат, если у вас такой имеется.</p>\n<p>Если вы все сделали правильно, то в контекстном меню каждого сайта должен появиться пункт меню <code>Deploy</code>:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/deploy-website-with-webdeploy/601c8f7e/3@2x.png\"  alt=\"Настраиваем WebDeploy для развертывания приложений ASP.NET\"/>\n\n\n\n\n<p>Необходимо выбрать пункт меню <code>Configure Web Deploy Publishing</code>, чтобы разрешить публикацию для этого сайта, указать имя пользователя, от имени которого будут публиковаться пакеты, URL и нажать кнопку <code>Setup</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/deploy-website-with-webdeploy/f7bc75af/4@2x.png\"  alt=\"Настраиваем WebDeploy для развертывания приложений ASP.NET\"/>\n\n\n\n\n<p><strong>Внимание:</strong> Важно, чтобы пользователь, которого вы указываете при настройке Web Deploy имел права на запись в папку веб-сайта.</p>\n<p>Теперь всё готово для публикации приложения.</p>\n<h2>Публикация</h2><p>Публиковать приложение можно двумя способами — напрямую из Visual Studio и используя автоматизированные билд-скрипты.</p>\n<p>Для публикации из Visual Studio следует выбрать веб-сайт в Solution Explorer и в контекстом меню выбрать пункт <code>Publish</code>. В появившемся диалоге нужно заполнить требуемые поля и запустить публикацию:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/deploy-website-with-webdeploy/8efc7778/5@2x.png\"  alt=\"Настраиваем WebDeploy для развертывания приложений ASP.NET\"/>\n\n\n\n\n<p>Для публикации через msbuild-скрипт, можно использовать следующий подход: сначала мы собираем пакет для публикации (который к тому же будет является артефактом сборки), а затем публикуем его.</p>\n<p>Подготовка пакета:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Target Name=\"PrepareWebDeployPackage\">\n  &lt;MSBuild Projects=\"WebApplication.csproj\"\n           Targets=\"Package\"\n           Properties=\"PackageLocation=WebApplicationPackage.zip\"/>\n&lt;/Target></code></pre>\n<p>Публикация пакета:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Target Name=\"PublishWebDeployPackage\">\n    &lt;Exec Command=\"WebDeploy\\msdeploy.exe -source:package='WebApplicationPackage.zip' -dest:auto,computerName='..',userName='..',password='..',authtype='NTLM',includeAcls='False' -verb:sync -allowuntrusted -setParam:\"IIS Web Application Name\"=\"WebApp1\"\"/>\n&lt;/Target></code></pre>\n<p>Как видно, при публикации требуется указать адрес для публикации, имя пользователя и пароль, а также имя сайта на сервере.</p>\n<h2>Разрешение проблем</h2><p>По опыту могу сказать, что при настройке публикации могут возникать проблемы. Если вы убедились, что файрвол не блокирует ваши подключения и сервис WebDeploy установлен корректно, то можно <a href=\"https://technet.microsoft.com/en-us/library/ff729439(v=ws.10).aspx\">включить отслеживание запросов</a> и понять в чем причина.</p>",
            "tags": [
                "ASP.NET",
                "DevOps"
            ],
            "banner_image": "https://blog.zwezdin.com/2014/deploy-website-with-webdeploy/6547a72c/deploy_website-with-webdeploy@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2014/fronttalks-2014/",
            "url": "https://blog.zwezdin.com/2014/fronttalks-2014/",
            "title": "FrontTalks 2014 в Екатеринбурге (фотоотчет)",
            "date_published": "2014-09-22T12:00:00.000Z",
            "content_html": "<p>19 сентября 2014 г. в Екатеринбурге прошла конференция FrontTalks 2014. Если не считать препати UWDC’2014, то это первая конференция FrontTalks, которую мне удалось посетить. Мероприятие проходило в бизнес-центре “Палладиум”, в самом центре Екатеринбурга.</p>\n<p><strong>FrontTalks</strong> посвящено фронтенд разработке во всех её проявлениях. Программа была запланирована на весь день и содержала 9 докладов. Докладчики приехали из Москвы, Новосибирска, Санкт-Петербурга, Екатеринбурга, Челябинск, Минска, Осло и Симферополя. География участников включала Екатеринбург, Челябинск, Пермь, Ижевск и, возможно, какие-то другие города. Общее количество участников – что-то около 240 человек.</p>\n<p>Мы приехали к началу регистрации и все уже были на месте, полным ходом шло приготовление. Место проведения мне очень понравилось – просторный зал и хорошая слышимость докладчика. Кроме того, благодаря форме зала создавалась какая-то особая атмосфера, в которой чувствуешь себя ещё более комфортно.</p>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2014/fronttalks-2014/d5ea4373/1@2x.jpg\"  alt=\"Общий вид зала — Фото: Сергей Звездин\"/><figcaption>Общий вид зала <cite>— Фото: Сергей Звездин</cite></figcaption></figure>\n\n\n\n\n<p>Доклады шли в один поток, с 5-10 минутными перерывами и одним большим перерывом на обед. В перерывах народ активно общался с докладчиками и между собой. Большим плюсом было наличие кофе и воды, которые были доступны буквально в соседнем зале. Мелочь, а приятно.</p>\n<p>Поскольку каждый доклад был интересен по-своему, пробегусь вкратце по каждому из них. Открыл мероприятие <strong>Олег Мохов</strong>, который является главным организатором и движущей силой конференции FrontTalks. Приветствие было не очень долгим, после чего мы непосредственно перешли к докладам.</p>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2014/fronttalks-2014/d284b419/2@2x.jpg\"  alt=\"Олег Мохов — открытие конференции — Фото: Сергей Звездин\"/><figcaption>Олег Мохов — открытие конференции <cite>— Фото: Сергей Звездин</cite></figcaption></figure>\n\n\n\n\n<p>Первый доклад представил <strong>Роман Сальников</strong> из 2GIS, который был полностью посвящен инструментам разработчика в Chrome. Весь доклад прошел под девизом “Вы можете и дальше мучиться, а можете потратить немного времени и намного упростить себе жизнь”. Я полностью согласен с этим утверждением. Очень часто бывает так, что время потраченное на более глубокое изучение инструментов потом окупится в процессе работы. Роман показал несколько приемов как можно отлаживать код в Chrome Dev Tools – начиная с работы с консолью, заканчивая профилированием CPU и GPU.</p>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2014/fronttalks-2014/1a3b9acd/3@2x.jpg\"  alt=\"Роман Сальников про инструменты разработчика в Chrome — Фото: Сергей Звездин\"/><figcaption>Роман Сальников про инструменты разработчика в Chrome <cite>— Фото: Сергей Звездин</cite></figcaption></figure>\n\n\n\n\n<p>Следующим выступил <strong>Алексей Иванов</strong> из JetStyle, он рассказал о пре- и пост-процессорах CSS. Основная мысль заключалась в том, что препроцессоры – это отличная штука. Но некоторые задачи с препроцессорами решать неудобно. Признаюсь, в самом начале я достаточно скептически отнесся к этому утверждению. Но Алексей очень хорошо показал, что это действительно может быть полезно и да, иногда это удобнее препроцессоров. В докладе были упомянуты SASS, LESS и Stylus, после чего Алексей рассказал историю создания Rework и postcss. А ещё у Алексея отличное чувство юмора – “<em>Что бы ни писали программисты на руби, у них в итоге всё равно получается руби</em>“ – это шутка про создание SASS.</p>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2014/fronttalks-2014/1b8c567c/4@2x.jpg\"  alt=\"Алексей Иванов о пре- и пост-процессорах CSS — Фото: Сергей Звездин\"/><figcaption>Алексей Иванов о пре- и пост-процессорах CSS <cite>— Фото: Сергей Звездин</cite></figcaption></figure>\n\n\n\n\n<p>А вот следующий доклад <strong>Алексея Бахирева</strong> впечатлил меньше остальных. Темой здесь была разработка для мобильных устройств, для разной аудитории и форм-факторов. Были затронуты такие штуки как j2me, андроид и даже Smart TV. Алексей рассказывал о разработке в разрезе своих задач, но некоторые советы мне показались весьма сомнительными. Собственно, это подтвердили и другие участники, задавая свои вопросы в конце доклада.</p>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2014/fronttalks-2014/1fa3770c/5@2x.jpg\"  alt=\"Алексей Бахирев про разработку для мобильных устройств — Фото: Сергей Звездин\"/><figcaption>Алексей Бахирев про разработку для мобильных устройств <cite>— Фото: Сергей Звездин</cite></figcaption></figure>\n\n\n\n\n<p>Программа мероприятия продолжилась после обеда и на этот раз выступил <strong>Роман Дворнов</strong> из Ostrovok.ru. Тема доклада Романа – “Баба-Яга против!”. Доклад получился очень веселый и позитивный. Но проблематика, которая здесь обсуждалась довольно серьезная и затрагивает она вопросы внедрения новых технологий на фронтенде, обновление стандартов, куда все это ведет и чем это грозит. После выступления Романа, другие докладчики ещё не раз ссылались на Бабу Ягу из этого доклада. Да, всех задело за живое :-)</p>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2014/fronttalks-2014/5266542b/6@2x.jpg\"  alt=\"Роман Дворнов — &#34;Баба-Яга против!&#34; — Фото: Сергей Звездин\"/><figcaption>Роман Дворнов — \"Баба-Яга против!\" <cite>— Фото: Сергей Звездин</cite></figcaption></figure>\n\n\n\n\n<p>Следующим на сцене появился <strong>Арсений Форштретер</strong>, который рассказал о применении flexbox при разработке современных сайтов. В самом начале Арсений показал как это вообще работает (для тех, кто не в курсе), а потом перешел к тому, насколько может упроститься код в нестандартных сценариях. Докладчик попытался убедить всех, что flexbox можно использовать уже сегодня и что оно работает во многих современных браузерах (а там где не работает показал как сделать фолбеки). Честно говоря, я думаю, что применять flexbox пока ещё рано и нужно немного подождать.</p>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2014/fronttalks-2014/235b6900/7@2x.jpg\"  alt=\"Арсений Форштретер о применении flexbox — Фото: Сергей Звездин\"/><figcaption>Арсений Форштретер о применении flexbox <cite>— Фото: Сергей Звездин</cite></figcaption></figure>\n\n\n\n\n<p>Продолжил серию выступлений <strong>Володя Кузнецов</strong> с темой про SVG. Тема очень актуальная с условиях когда диапазон размеров экранов очень велик. Как и ожидалось, SVG до сих пор имеет ряд проблем с совместимостью, но Владимир показал различные уловки, как эти проблемы можно обойти. Также были продемонстрированы возможности анимирования, как всей сцены, так и отдельных её частей. Доклад получился интересный и полезный с практической точки зрения. Думаю, многие возьмут на вооружение то, о чем говорилось в докладе.</p>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2014/fronttalks-2014/c7362f74/8@2x.jpg\"  alt=\"Владимир Кузнецов про SVG — Фото: Сергей Звездин\"/><figcaption>Владимир Кузнецов про SVG <cite>— Фото: Сергей Звездин</cite></figcaption></figure>\n\n\n\n\n<p>Тему SVG продолжил развивать <strong>Дмитрий Дудин</strong> в своем докладе “Как сделать Инстаграм в браузере”. Доклад был про то, как использовать эффекты и фильтры для фото на клиенте. Было предложено несколько способов, но остановился Дмитрий именно на SVG-фильтрах. Доклад получился очень живой, полный различных примеров и сопровождающийся полезными ссылками на библиотеки, которые позволяют упростить жизнь при работе с изображениями.</p>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2014/fronttalks-2014/5268f4de/9@2x.jpg\"  alt=\"Дмитрий Дудин про инстаграм в браузере — Фото: Сергей Звездин\"/><figcaption>Дмитрий Дудин про инстаграм в браузере <cite>— Фото: Сергей Звездин</cite></figcaption></figure>\n\n\n\n\n<p>Отличный доклад подготовил <strong>Вадим Макеев</strong>, человек очень известный в мире веб-технологий. На этот раз Вадим рассказывал об использовании изображений на страницах и рассказал почему тег <code>&lt;img&gt;</code> уже не удовлетворяет современным требованиям. Была показана хронология как это развивалось и в завершение мы увидели отличную демонстрацию того, как в скором будущем мы будем вставлять изображения на страницу. Основная идея в этом направлении – это адаптация изображения под тип устройства и загрузка с сервера только той версии картинки, которая наиболее оптимально подходит под текущие условия. Тема очень интересная, но нужно, чтобы её поддержали все браузеры. Тем не менее, то, что мы увидели очень впечатляет и надеюсь скоро мы сможем этим пользоваться.</p>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2014/fronttalks-2014/28f8e327/10@2x.jpg\"  alt=\"Вадим Макеев про &amp;lt;picture&amp;gt; — Фото: Сергей Звездин\"/><figcaption>Вадим Макеев про <code>&lt;picture&gt;</code> <cite>— Фото: Сергей Звездин</cite></figcaption></figure>\n\n\n\n\n<p>Последним в программе стоял доклад от <strong>Вадима Макишвили</strong> с загадочным названием “36”. Мне будет очень трудно передать все, о чем Вадим говорил в ходе своего повествования. Если говорить в целом, то доклад был о том, что испытывает человек, который перешагнул возраст “36”. О том, что это очень сложный период, и что в этот момент можно наломать дров. Доклад был воспринят на ура и все слушали, затаив дыхание. Эта тема заставляет задуматься над тем, что ты делаешь и зачем это делаешь, а также о том, что будет завтра и что с этим делать.</p>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2014/fronttalks-2014/c792b07d/11@2x.jpg\"  alt=\"Вадим Макишвили - &#34;36&#34; — Фото: Сергей Звездин\"/><figcaption>Вадим Макишвили - \"36\" <cite>— Фото: Сергей Звездин</cite></figcaption></figure>\n\n\n\n\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2014/fronttalks-2014/97d1f5a3/12@2x.jpg\"  alt=\"Вадим Макишвили - &#34;36&#34; — Фото: Сергей Звездин\"/><figcaption>Вадим Макишвили - \"36\" <cite>— Фото: Сергей Звездин</cite></figcaption></figure>\n\n\n\n\n<p>Всегда, оказываясь на новом мероприятии, я стараюсь ответить на вопрос “<em>поеду ли я сюда еще раз?</em>“. Что касается FrontTalks, то мне мероприятие очень понравилось – и по формату, и по содержательной части. Так что в случае с FrontTalks – однозначно, приеду еще.</p>\n<p>Спасибо организаторам и докладчикам за интересный день!</p>",
            "tags": [
                "Мероприятия",
                "FrontTalks"
            ],
            "banner_image": "https://blog.zwezdin.com/2014/fronttalks-2014/2c7f8a02/fronttalks_2014@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2014/webapi-client/",
            "url": "https://blog.zwezdin.com/2014/webapi-client/",
            "title": "Клиент для WebAPI",
            "date_published": "2014-09-22T12:00:00.000Z",
            "content_html": "<p>Уже много сказано о том, как создавать свои API на основе <strong>ASP.NET WebAPI</strong> и существенно меньше о том, как работать с этими сервисами со стороны клиента. Это и понятно, ведь часто потребителями этих сервисов выступает клиентский JavaScript, который умеет работать с JSON “из коробки”. Тем не менее, случается и так, что клиентом должно быть .NET-приложение.</p>\n<p>Удивительно, но несколько раз я встречал в коде, когда .NET-приложение взаимодействует с сервером при помощи HttpClient, а потом пытается вручную разбирать JSON/XML, пытаться проверять тип данных и т.п. Такой подход ведет к усложнению коду и ошибкам. Вместо этого можно воспользоваться уже <a href=\"https://www.nuget.org/packages/Microsoft.AspNet.WebApi.Client\">готовой библиотекой</a>, в которой это уже реализовано.</p>\n<p>Создаем приложение, добавляем <code>HttpClient</code>, загружаем содержимое ответа с сервера:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">using (var http = new HttpClient())\n&#123;\n  var result = await http.GetAsync(new Uri(\"http://localhost:40139/api/users/get\"));\n  result.EnsureSuccessStatusCode();\n\n  // ...\n&#125;</code></pre>\n<p>Устанавливаем <code>Microsoft.AspNet.WebApi.Client</code>:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">Install-Package Microsoft.AspNet.WebApi.Client</code></pre>\n<p>Добавляем объект, описывающий сущность, возвращаемую сервером:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class User\n&#123;\n  public string Name &#123; get; set; &#125;\n  public string Email &#123; get; set; &#125;\n&#125;</code></pre>\n<p>После получения ответа с сервера, распакуем его непосредственно в эти объекты:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">using (var http = new HttpClient())\n&#123;\n  var result = await http.GetAsync(new Uri(\"http://localhost:40139/api/users/get\"));\n  result.EnsureSuccessStatusCode();\n\n  var users = await result.Content.ReadAsAsync&lt;IEnumerable&lt;User>>();\n\n  foreach (var user in users)\n  &#123;\n    // ...\n  &#125;\n&#125;</code></pre>\n<p>Как видно, мы не выполняем никаких действий по парсингу и преобразованию ответа.</p>\n<p>Если предполагается, что сервер может вернуть результат в разных форматах, можно определить список форматировщиков, которые будут использоваться при распозновании ответа:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">var users = await result.Content.ReadAsAsync&lt;IEnumerable&lt;User>>(\n  new MediaTypeFormatter[]\n  &#123;\n      new JsonMediaTypeFormatter(),\n      new XmlMediaTypeFormatter()\n  &#125;);</code></pre>\n<p>Можно не только получать данные с сервера, но и отправлять их обратно:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">using (var http = new HttpClient())\n&#123;\n  var result = await http.PostAsync(\n    \"http://localhost:40139/api/users/create\",\n    new User &#123; Name = \"User1\", Email = \"user1@mail.com\" &#125;,\n    new JsonMediaTypeFormatter());\n\n  result.EnsureSuccessStatusCode();\n&#125;</code></pre>\n<p>Явно указывать используемый форматировщик необязательно. Вместо этого можно использовать перегруженные методы <code>PostAs&lt;&gt;</code>, например <code>PostAsJsonAsync</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">using (var http = new HttpClient())\n&#123;\n  var result = await http.PostAsJsonAsync(\n    \"http://localhost:40139/api/users/create\",\n    new User &#123; Name = \"User1\", Email = \"user1@mail.com\" &#125;);\n\n  result.EnsureSuccessStatusCode();\n&#125;</code></pre>",
            "tags": [
                "ASP.NET"
            ],
            "banner_image": "https://blog.zwezdin.com/2014/webapi-client/75f058ff/webapi_client@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2014/webapi-response-formatting/",
            "url": "https://blog.zwezdin.com/2014/webapi-response-formatting/",
            "title": "Форматирование HTTP-ответа в ASP.NET WebAPI",
            "date_published": "2014-09-06T12:00:00.000Z",
            "content_html": "<p><strong>ASP.NET WebAPI</strong> имеет две модели работы с контентом, который отдается клиенту - управлять непосредственно содержимым ответа или возвращать в качестве результата CLR-объект и предоставить WebAPI возможность самостоятельно решить в каком виде отдавать эти данные. Последний вариант на мой взгляд предпочтительнее, поскольку оставляет инфраструктурные вопросы платформе и позволяет думать только о логике. В таком случае встает вопрос о том, как WebAPI решает в каком виде эти данные уйдут на клиент, а также как на это поведение повлиять.</p>\n<p>Возьмем для примера тривиальный WebAPI контроллер:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class UsersController : ApiController\n&#123;\n  [HttpGet]\n  public IEnumerable&lt;User> Get()\n  &#123;\n    return new[]\n    &#123;\n      new User &#123;Name = \"User1\", Email = \"user1@email.com\"&#125;,\n      new User &#123;Name = \"User2\", Email = \"user2@email.com\"&#125;,\n      new User &#123;Name = \"User3\", Email = \"user3@email.com\"&#125;,\n      new User &#123;Name = \"User4\", Email = \"user4@email.com\"&#125;,\n    &#125;;\n  &#125;\n&#125;</code></pre>\n<p>В качестве результата действие <code>Get()</code> отдает коллекцию объектов. В процессе обработки запроса WebAPI предстоит решить вопрос в каком виде отдавать эти данные - JSON, XML или каком-либо ещё? Для этого в инфраструктуре WebAPI имеется механизм, который называется <strong>Media Formatters</strong>. Задача этих сервисов заключается в форматировании объектов в нужном виде. Процесс обработки запроса выглядит следующим образом:</p>\n<ol>\n<li>При поступлении входящего запроса от клиента анализируется HTTP-заголовок <code>Accept</code>.</li>\n<li>Всем существующим форматировщикам передается это значение и предлагается сформировать ответ для этого запроса.</li>\n<li>Если форматировщик способен работать с данным форматом, он генерирует результат, который передается клиенту.</li>\n<li>Форматировщик может быть ориентирован не только на работу с конкретными типами контента, но и на на работу с конкретными типами объектов.</li>\n</ol>\n<p>Чтобы убедиться в корректности этой схемы, можно запустить приложение и отправить ему пару HTTP-запросов:</p>\n<p>Запрос:</p>\n<pre class=code language-http><code class=\"code__container language-http\">GET http://localhost:40139/api/users/get HTTP/1.1\nUser-Agent: Fiddler\nHost: localhost:40139\nAccept: application/json</code></pre>\n<p>Ответ:</p>\n<pre class=code language-http><code class=\"code__container language-http\">HTTP/1.1 200 OK\nContent-Type: application/json; charset=utf-8\nServer: Microsoft-IIS/8.0\nContent-Length: 173\n\n[&#123;\"Name\":\"User1\",\"Email\":\"user1@email.com\"&#125;,&#123;\"Name\":\"User2\", ...</code></pre>\n<p><strong>Второй вариант:</strong></p>\n<p>Запрос:</p>\n<pre class=code language-http><code class=\"code__container language-http\">GET http://localhost:40139/api/users/get HTTP/1.1\nUser-Agent: Fiddler\nHost: localhost:40139\nAccept: application/xml</code></pre>\n<p>Ответ:</p>\n<pre class=code language-http><code class=\"code__container language-http\">HTTP/1.1 200 OK\nContent-Type: application/xml; charset=utf-8\nServer: Microsoft-IIS/8.0\nContent-Length: 400\n\n&lt;ArrayOfUser ...>&lt;User>&lt;Email>user1@email.com&lt;/Email>&lt;Name>User1...</code></pre>\n<p>Как видно, WebAPI по-разному формирует ответ для разных значений HTTP-заголовка Accept.</p>\n<p>Это поведение можно изменить и добавить свой форматировщик. Например, сделаем форматировщик, отдающий результат в текстовом формате (<code>text/plain</code>).</p>\n<p>Нам потребуется объект, унаследованный от <code>MediaTypeFormatter</code>. Нужно также указать тип ответа и тип объектов, с которыми может работать форматировщик:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class TextFormatter : BufferedMediaTypeFormatter\n&#123;\n  public TextFormatter()\n  &#123;\n    SupportedMediaTypes.Add(new MediaTypeHeaderValue(\"text/plain\"));\n  &#125;\n\n  public override bool CanReadType(Type type)\n  &#123;\n    return false;\n  &#125;\n\n  public override bool CanWriteType(Type type)\n  &#123;\n    return type == typeof(IEnumerable&lt;User>);\n  &#125;</code></pre>\n<p>Для формирования результата следует переопределить метод <code>WriteToStream()</code>, который имеет доступ к исходному объекту (который получен из контроллера), текущему HTTP-запросу, а также потоку для генерации результата:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class TextFormatter : BufferedMediaTypeFormatter\n&#123;\n  // ...\n\n  public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content)\n  &#123;\n    var items = (IEnumerable&lt;User>)value;\n\n    using (var writer = new StreamWriter(writeStream))\n    &#123;\n      foreach (var item in items)\n      &#123;\n        writer.WriteLine(\"Name: &#123;0&#125;, Email: &#123;1&#125;\", item.Name, item.Email);\n      &#125;\n    &#125;\n  &#125;</code></pre>\n<p>Наконец, нужно зарегистрировать этот форматировщик в инфраструктуре WebAPI:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class WebApiConfig\n&#123;\n  public static void Register(HttpConfiguration config)\n  &#123;\n    config.Formatters.Add(new TextFormatter());</code></pre>\n<p>Теперь все запросы, заголовок Accept которых содержит <code>text/plain</code>, а результат работы контроллера возвращает <code>IEnumerable&lt;User&gt;</code>, будут обрабатываться этим объектом. Это легко проверить, выполнив ещё один HTTP-запрос:</p>\n<p>Запрос:</p>\n<pre class=code language-http><code class=\"code__container language-http\">GET http://localhost:40139/api/users/get HTTP/1.1\nUser-Agent: Fiddler\nHost: localhost:40139\nAccept: text/plain</code></pre>\n<p>Ответ:</p>\n<pre class=code language-http><code class=\"code__container language-http\">HTTP/1.1 200 OK\nContent-Type: text/plain\nServer: Microsoft-IIS/8.0\nContent-Length: 148\n\nName: User1, Email: user1@email.com\nName: User2, Email: user2@email.com\nName: User3, Email: user3@email.com\nName: User4, Email: user4@email.com</code></pre>",
            "tags": [
                "ASP.NET"
            ],
            "banner_image": "https://blog.zwezdin.com/2014/webapi-response-formatting/ec954bf4/webapi_response-formatting@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2014/webapi-controller-testing/",
            "url": "https://blog.zwezdin.com/2014/webapi-controller-testing/",
            "title": "Тестирование контроллеров WebAPI",
            "date_published": "2014-08-31T12:00:00.000Z",
            "content_html": "<p>Нужно или нет тестировать контроллеры — я не редко слышу эти споры. Но факт остается фактом — есть случаи когда приложение реализует API для взаимодействия с внешним миром и такой API должен быть задокументирован и протестирован. Поэтому несколько слов о том, <strong>как тестировать WebAPI-контроллеры</strong>.</p>\n<p>Как известно, контроллеры WebAPI могут возвращать результат тремя различными способами:</p>\n<ol>\n<li>Используя объект <code>HttpResponseMessage</code> - в этом случае результат ответа генерируется прямо в контроллере и отдается на сторону клиента “как есть”.</li>\n<li>Используя обертку <code>IHttpActionResult</code> - при этом в процессе обработки запроса он преобразуется в <code>HttpResponseMessage</code> и отправляется клиенту.</li>\n<li>Используя любой другой CLR-объект, содержащий данные - тогда объект сериализуется, упаковывается в сообщение ответа и отправляется в ответ на запрос.</li>\n</ol>\n<p>Каждый из этих пунктов меняет подход к реализации контроллера, а значит и влияет на структуру тестов. Поэтому рассмотрим каждый случай отдельно.</p>\n<h2><code>HttpResponseMessage</code></h2><p>Контроллер в этом случае выглядит так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class JoinController : ApiController\n&#123;\n  public HttpResponseMessage JoinAsUser(int id)\n  &#123;\n    if (id &lt; 0)\n      return Request.CreateResponse(HttpStatusCode.BadRequest);\n\n    // Логика\n\n    return Request.CreateResponse(HttpStatusCode.OK);\n  &#125;</code></pre>\n<p>В результате работы действия контроллера генерируется объект <code>HttpResponseMessage</code>, содержимое которого мы можем проверить в тесте.</p>\n<p>Подготовительные действия в тесте заключаются в создании и инициализации контроллера, а также задании маршрутов:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">// Arrage\nvar controller = new JoinController\n&#123;\n  Request = new HttpRequestMessage\n  &#123;\n    RequestUri = new Uri(\"http://localhost/api/join/joinAsUser\")\n  &#125;,\n  Configuration = new HttpConfiguration(),\n  RequestContext =\n  &#123;\n    RouteData = new HttpRouteData(\n        route: new HttpRoute(),\n        values: new HttpRouteValueDictionary\n        &#123;\n            &#123; \"controller\", \"join\" &#125;,\n            &#123; \"action\", \"joinAsUser\" &#125;\n        &#125;)\n  &#125;\n&#125;;\n\ncontroller.Configuration.Routes.MapHttpRoute(\n  name: \"DefaultApi\",\n  routeTemplate: \"api/&#123;controller&#125;/&#123;action&#125;/&#123;id&#125;\",\n  defaults: new &#123; id = RouteParameter.Optional &#125;);</code></pre>\n<p>После чего можно обратиться к действию контроллера и проверить результат:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">// Act\nvar result = controller.JoinAsUser(5);\n\n// Assert\nAssert.AreEqual(HttpStatusCode.OK, result.StatusCode);</code></pre>\n<p>В данном случае производится проверка на успешный код возврата. Разумеется условия могут быть более сложными, например, включать проверки содержимого ответа.</p>\n<p>Итоговый код теста:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[TestMethod]\npublic void JoinAsUserTest()\n&#123;\n  // Arrage\n  var controller = new JoinController\n  &#123;\n    Request = new HttpRequestMessage\n    &#123;\n      RequestUri = new Uri(\"http://localhost/api/join/joinAsUser\")\n  &#125;,\n    Configuration = new HttpConfiguration(),\n    RequestContext =\n    &#123;\n      RouteData = new HttpRouteData(\n        route: new HttpRoute(),\n        values: new HttpRouteValueDictionary\n        &#123;\n            &#123; \"controller\", \"join\" &#125;,\n            &#123; \"action\", \"joinAsUser\" &#125;\n        &#125;)\n    &#125;\n  &#125;;\n\n  controller.Configuration.Routes.MapHttpRoute(\n    name: \"DefaultApi\",\n    routeTemplate: \"api/&#123;controller&#125;/&#123;action&#125;/&#123;id&#125;\",\n    defaults: new &#123; id = RouteParameter.Optional &#125;);\n\n\n  // Act\n  var result = controller.JoinAsUser(5);\n\n  // Assert\n  Assert.AreEqual(HttpStatusCode.OK, result.StatusCode);\n&#125;</code></pre>\n<h2><code>IHttpActionResult</code></h2><p>Реализация действия контроллера в этом случае может выглядеть так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class JoinController : ApiController\n&#123;\n  public IHttpActionResult Leave(int id)\n  &#123;\n    if (id &lt; 0)\n        return BadRequest();\n\n    // Логика\n\n    return Ok();\n  &#125;</code></pre>\n<p><code>IHttpActionResult</code> - это обертка, которая обладает семантикой, например - запрос обработался успешно, с ошибкой, или возникли проблемы с авторизацией. В базовом классе контроллера WebAPI уже имеется набор заготовленных методов для формирования ответа в таком стиле - <code>Ok()</code>, <code>Ok&lt;&gt;()</code>, <code>Redirect()</code>, <code>BadRequest()</code> и т.п. Мы также можем создать свои типы ответов.</p>\n<p>В этом случае тест будет сводиться к проверке типа ответа и его содержимого:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">// Act\nvar result = controller.Leave(5);\n\n// Assert\nAssert.IsInstanceOfType(result, typeof(OkResult));</code></pre>\n<p>Как и в предыдущем случае, условия проверки могут быть более сложными.</p>\n<p>Итоговый код теста:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[TestMethod]\npublic void LeaveTest()\n&#123;\n  // Arrage\n  var controller = new JoinController\n  &#123;\n    Request = new HttpRequestMessage\n    &#123;\n      RequestUri = new Uri(\"http://localhost/api/join/leave\")\n    &#125;,\n    Configuration = new HttpConfiguration(),\n    RequestContext =\n    &#123;\n      RouteData = new HttpRouteData(\n        route: new HttpRoute(),\n        values: new HttpRouteValueDictionary\n        &#123;\n            &#123; \"controller\", \"join\" &#125;,\n            &#123; \"action\", \"leave\" &#125;\n        &#125;)\n    &#125;\n  &#125;;\n\n  controller.Configuration.Routes.MapHttpRoute(\n    name: \"DefaultApi\",\n    routeTemplate: \"api/&#123;controller&#125;/&#123;action&#125;/&#123;id&#125;\",\n    defaults: new &#123; id = RouteParameter.Optional &#125;);\n\n\n  // Act\n  var result = controller.Leave(5);\n\n  // Assert\n  Assert.IsInstanceOfType(result, typeof(OkResult));\n&#125;</code></pre>\n<h2>CLR-объект в качестве результата</h2><p>Это - частный случай прерыдущих способов. Если нам не требуется управлять HTTP-ответом, а нужно всего лишь вернуть содержимое какого-либо объекта, то можно воспользоваться таким вариантом. Выдача ошибочных статусов кодов тогда будет производится через генерацию исключений.</p>\n<p>Контроллер в этом случае выглядит так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class JoinController : ApiController\n&#123;\n  public string CheckStatus(int id)\n  &#123;\n    if (id &lt; 0)\n      throw new HttpException(400, \"Bad request\");\n\n    return \"Joined\";\n  &#125;</code></pre>\n<p>В данном примере в качестве результата отдается строка, но это может быть любой объект.</p>\n<p>Тестирование в данном случае наиболее прозрачное - вызывается соответствующее действие и проверяется сам объект:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">// Act\nvar result = controller.CheckStatus(5);\n\n// Assert\nAssert.AreEqual(\"Joined\", result);</code></pre>\n<p>Полный код теста:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[TestMethod]\npublic void CheckStatusTest()\n&#123;\n  // Arrage\n  var controller = new JoinController\n  &#123;\n    Request = new HttpRequestMessage\n    &#123;\n      RequestUri = new Uri(\"http://localhost/api/join/сheckStatus\")\n    &#125;,\n    Configuration = new HttpConfiguration(),\n    RequestContext =\n    &#123;\n      RouteData = new HttpRouteData(\n              route: new HttpRoute(),\n              values: new HttpRouteValueDictionary\n      &#123;\n          &#123; \"controller\", \"join\" &#125;,\n          &#123; \"action\", \"сheckStatus\" &#125;\n      &#125;)\n    &#125;\n  &#125;;\n\n  controller.Configuration.Routes.MapHttpRoute(\n    name: \"DefaultApi\",\n    routeTemplate: \"api/&#123;controller&#125;/&#123;action&#125;/&#123;id&#125;\",\n    defaults: new &#123; id = RouteParameter.Optional &#125;);\n\n\n  // Act\n  var result = controller.CheckStatus(5);\n\n  // Assert\n  Assert.AreEqual(\"Joined\", result);\n&#125;</code></pre>",
            "tags": [
                "ASP.NET"
            ],
            "banner_image": "https://blog.zwezdin.com/2014/webapi-controller-testing/4427571e/webapi_controller-testing@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2014/webapi-attribute-routing/",
            "url": "https://blog.zwezdin.com/2014/webapi-attribute-routing/",
            "title": "Роутинг атрибутами в ASP.NET WebAPI",
            "date_published": "2014-08-25T12:00:00.000Z",
            "content_html": "<p><strong>Роутинг в ASP.NET MVC и WebAPI</strong> позволяет сопоставить каждый HTTP-запрос с конкретным контроллером и действием. Традиционно для определения маршрутов используется таблица, описывающая схему маршрутизации. Во второй версии WebAPI появилась альтернативная возможность — задавать маршруты атрибутами, непосредственно при определении контроллера и действия.</p>\n<p>Атрибуты позволяют определить сложные маршруты без лишнего усложнения основной таблицы маршрутов. Для этого каждое действие размечается атрибутом <code>[Route]</code>.</p>\n<h2>Конфигурация</h2><p>Для использования атрибутов маршрутизации следует явно указать это при конфигурировании маршрутов. При создании ASP.NET WebAPI проекта, из шаблона создается файл <code>App_Start/WebApiConfig.cs</code>, содержащий записи о маршрутах для WebAPI. Здесь следует вставить вызов <code>MapHttpAttributeRoutes()</code> — это заставит инфраструктуру просмотреть все контроллеры/действия и учитывать их конфигурацию при роутинге тоже. Обычно код для настройки маршрутов сводится к такому:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static void Register(HttpConfiguration config)\n&#123;\n  config.MapHttpAttributeRoutes();\n\n  config.Routes.MapHttpRoute(\n    name: \"DefaultApi\",\n    routeTemplate: \"api/&#123;controller&#125;/&#123;id&#125;\",\n    defaults: new &#123; id = RouteParameter.Optional &#125;\n  );\n&#125;</code></pre>\n<h2>Определение маршрутов</h2><p>Для задания маршрута нужно воспользоваться атрибутом <code>[Route]</code>, в параметрах которого указать URL, который нужно связать с действием контроллера:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class NodesController : ApiController\n&#123;\n  [Route(\"api/nodes/byowner/&#123;ownerId&#125;\")]\n  public IEnumerable&lt;node> GetByOwner(int ownderId)\n  &#123;\n  &#125;\n&#125;</code></pre>\n<p>Дополнительно может потребоваться указать HTTP-метод, при помощи которого предполагается обрабатывать запрос. Для этого есть универсальный атрибут <code>[AcceptVerbs]</code>, либо его алиасы — <code>[HttpGet]</code>, <code>[HttpPost]</code> и т.п.</p>\n<h2>Префиксы</h2><p>Используя атрибуты легко прийти к ситуации, когда в контроллере большое количество действий, но URL для каждого из них начинается с одного и того же значения. Например:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class NodesController : ApiController\n&#123;\n  [Route(\"api/nodes/list\")]\n  public IEnumerable&lt;node> GetAll()\n  &#123;\n  &#125;\n\n  [Route(\"api/nodes/byowner/&#123;ownerId&#125;\")]\n  public IEnumerable&lt;node> GetByOwner(int ownderId)\n  &#123;\n  &#125;\n\n  [Route(\"api/nodes/byresource/&#123;resourceId&#125;\")]\n  public IEnumerable&lt;node> GetByResource(int resourceId)\n  &#123;\n  &#125;\n&#125;&lt;/node></code></pre>\n<p>Для упрощения схемы роутинга здесь можно использовать префиксы — <code>[RoutePrefix]</code>. Префиксы ассоциируются с контроллером и означают, что для каждого из действий к URL будет добавлен этот префикс. Переписать код, приведенный выше можно так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[RoutePrefix(\"api/nodes\")]\npublic class NodesController : ApiController\n&#123;\n  [Route(\"list\")]\n  public IEnumerable&lt;node> GetAll()\n  &#123;\n  &#125;\n\n  [Route(\"byowner/&#123;ownerId&#125;\")]\n  public IEnumerable&lt;node> GetByOwner(int ownderId)\n  &#123;\n  &#125;\n\n  [Route(\"byresource/&#123;resourceId&#125;\")]\n  public IEnumerable&lt;node> GetByResource(int resourceId)\n  &#123;\n  &#125;\n&#125;</code></pre>\n<p>Кстати, если какое-то из действий не укладывается в схему адресов с префиксом, это тоже решаемо — нужно указать полный URL и в начале добавить <code>~</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[RoutePrefix(\"api/nodes\")]\npublic class NodesController : ApiController\n&#123;\n  [Route(\"list\")]\n  public IEnumerable&lt;Node> GetAll()\n  &#123;\n  &#125;\n\n  [Route(\"~/aliases/bynode/&#123;nodeId&#125;\")]\n  public IEnumerable&lt;Node> GetAliases(int nodeId)\n  &#123;\n  &#125;\n&#125;</code></pre>\n<h2>Ограничения в маршрутах</h2><p>Ограничения - важнейший механизм в маршрутах ASP.NET, позволяющий определить содержимое каждого параметра в URL. При маршрутизации, построенной на атрибутах есть два способа задания ограничений - в строке, при определении шаблона URL и отдельными объектами <code>RouteConstraint</code>.</p>\n<p>Первый способ предполагает наличие ряда стандартных ограничений, например по типу или длине. Выглядит это так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[RoutePrefix(\"api/nodes\")]\npublic class NodesController : ApiController\n&#123;\n  [Route(\"list\")]\n  public IEnumerable&lt;Node> GetAll()\n  &#123;\n  &#125;\n\n  [Route(\"byowner/&#123;ownerId:int&#125;\")]\n  public IEnumerable&lt;Node> GetByOwner(int ownerId)\n  &#123;\n  &#125;\n  [Route(\"byowner/name/&#123;owner:maxlength(32)&#125;\")]\n  public IEnumerable&lt;Node> GetByOwnerName(int owner)\n  &#123;\n  &#125;</code></pre>\n<p>В список поддерживаемых ограничений входит ограничение по типам, длине, минимальному и максимальному значению, диапазонам и regex. Если этого недостаточно, то можно реализовать логику ограничения в отдельном объекте:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class ProductIdConstraint : IHttpRouteConstraint\n&#123;\n  public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary&lt;string, object> values,\n                      HttpRouteDirection routeDirection)\n  &#123;\n    bool result = false;\n\n    // логика\n\n    return result;\n  &#125;\n&#125;</code></pre>\n<p>Чтобы использовать этот класс в атрибутах, нужно его зарегистрировать:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static void Register(HttpConfiguration config)\n&#123;\n  var constraints = new DefaultInlineConstraintResolver();\n  constraints.ConstraintMap.Add(\"productId\", typeof(ProductIdConstraint));\n\n  config.MapHttpAttributeRoutes(constraints);</code></pre>\n<p>Тогда его использование сводится к добавлению алиаса этого ограничения в URL:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[RoutePrefix(\"api/nodes\")]\npublic class NodesController : ApiController\n&#123;\n  [Route(\"byproduct/&#123;id:productId&#125;\")]\n  public IEnumerable&lt;Node> ByProduct(int id)\n  &#123;\n  &#125;</code></pre>\n<h2>Опциональные параметры</h2><p>Некоторые параметры URL могут быть опциональными. Чтобы определить опциональные параметры можно воспользоваться двумя способами.</p>\n<p>Первый способ заключается в определении значения по умолчанию для параметра метода. В этом случае в шаблоне URL нужно просто указать, что параметр не является обязательным:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[RoutePrefix(\"api/nodes\")]\npublic class NodesController : ApiController\n&#123;\n  [Route(\"category/&#123;id:int?&#125;\")]\n  public IEnumerable&lt;Node> ByCategory(int id = -1)\n  &#123;\n  &#125;</code></pre>\n<p>Другой способ — определить значение по умолчанию с самом шаблоне URL:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[RoutePrefix(\"api/nodes\")]\npublic class NodesController : ApiController\n&#123;\n  [Route(\"category/&#123;id:int?=-1&#125;\")]\n  public IEnumerable&lt;Node> ByCategory(int id)\n  &#123;\n  &#125;</code></pre>",
            "tags": [
                "ASP.NET"
            ],
            "banner_image": "https://blog.zwezdin.com/2014/webapi-attribute-routing/5f0dd896/webapi_attribute-routing@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2014/webapi-controller-selector/",
            "url": "https://blog.zwezdin.com/2014/webapi-controller-selector/",
            "title": "Переопределение поведения выбора контроллера/действия ASP.NET WebAPI",
            "date_published": "2014-08-13T12:00:00.000Z",
            "content_html": "<p>В большинстве ситуаций необходимости в изменении логики поиска контроллера/действия нет необходимости - с этим хорошо справляется существующая инфраструктура ASP.NET. Тем не менее, могут возникнуть ситуация, когда контроллеры расположены в другой сборке или выбор конкретного класса контроллера или его метода должны отличаться от правил, которые работают по умолчанию. <strong>ASP.NET WebAPI</strong> имеет возможность изменить это поведение в соответствии с логикой приложения.</p>\n<p>Основные точки расширения для управления этим поведением, на которые стоит обратить внимание - интерфейсы <code>IAssembliesResolver</code>, <code>IHttpControllerSelector</code>, <code>IHttpActionSelector</code>.</p>\n<h2>Поиск контроллера</h2><p>Стандартное поведение <strong>ASP.NET WebAPI</strong> при поиске контроллера - это найти класс, имя которого состоит из имени запрашиваемого контроллера и <code>Controller</code>. Этот класс также должен быть наследником базового класса <code>ApiController</code>.</p>\n<p>Если требуется переопределить это поведение, следует реализовать сервис <code>IHttpControllerSelector</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class CustomControllerSelector : DefaultHttpControllerSelector\n&#123;\n  private readonly HttpConfiguration _configuration;\n\n  public CustomControllerSelector(HttpConfiguration configuration)\n      : base(configuration)\n  &#123;\n    _configuration = configuration;\n  &#125;\n\n  public override HttpControllerDescriptor SelectController(HttpRequestMessage request)\n  &#123;\n    string controllerName;\n    Type controllerType;\n\n    // ...\n\n    return new HttpControllerDescriptor(_configuration, controllerName, controllerType);\n  &#125;\n&#125;</code></pre>\n<p>В методе <code>SelectController()</code> должна быть определена логика поиска контроллера в приложении. Чтобы использовать этот объект нужно заменить соответствующий сервис в инфраструктуре ASP.NET WebAPI:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class WebApiConfig\n&#123;\n  public static void Register(HttpConfiguration config)\n  &#123;\n    config.Services.Replace(typeof(IHttpControllerSelector), new CustomControllerSelector(config));</code></pre>\n<h2>Поиск действия контроллера</h2><p>ASP.NET WebAPI позволяет также переопределить логику поиска действия контроллера. Для этого следует создать отдельный сервис, реализующий <code>IHttpActionSelector</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class CustomActionSelector : ApiControllerActionSelector\n&#123;\n  public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)\n  &#123;\n    var controller = controllerContext.ControllerDescriptor;\n    var controllerType = controller.ControllerType;\n\n    MethodInfo action;\n\n    // ...\n\n    return new ReflectedHttpActionDescriptor(controller, action);\n  &#125;\n&#125;</code></pre>\n<p>В методе <code>SelectAction()</code> производится поиск метода, который будет выбран в качестве действия контроллера. Используя рефлексию, нужно сообщить инфраструктуре какой именно метод следует использовать. Замена сервиса ASP.NET WebAPI для поиска действий:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class WebApiConfig\n&#123;\n  public static void Register(HttpConfiguration config)\n  &#123;\n    config.Services.Replace(typeof(IHttpActionSelector), new CustomActionSelector());</code></pre>",
            "tags": [
                "ASP.NET"
            ],
            "banner_image": "https://blog.zwezdin.com/2014/webapi-controller-selector/1c64fa63/webapi_controller-selector@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2014/webapi-exception-handling/",
            "url": "https://blog.zwezdin.com/2014/webapi-exception-handling/",
            "title": "Обработка исключений в WebAPI",
            "date_published": "2014-08-05T12:00:00.000Z",
            "content_html": "<p>Обработка исключений - одна из важнейших тем, о которой часто забывают и оставляют “на потом”. Типичные поведение веб-приложения при возникновении необработанного исключения - ответить клиенту с кодом <code>HTTP 500</code> и формулировкой “что-то пошло не так”.</p>\n<p>Это логично, когда мы генерируем HTML-страницы. Но реализация REST API часто требует от нас другого. Например, при появлении необработанного исключения из предметной области выдавать какой-то специфичный код возврата, чтобы наши клиенты знали в чем проблема.</p>\n<p>Первое, что приходит в голову — добавить в контроллер логику, которая будет генерировать нужные коды возврата для каждой ситуации.</p>\n<p>В WebAPI есть такой механизм — <strong>Exception Filter</strong> — он позволяет для таких ситуаций создать более прозрачный код.</p>\n<h2>Создание <code>Exception Filter</code></h2><p>Например, имеется WebAPI контроллер, который на определенном наборе данных генерирует <code>NotSupportedException</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class UsersController : ApiController\n&#123;\n  [HttpPost]\n  public void Create(UsersCreateModel model)\n  &#123;\n    if (....)\n      throw new NotSupportedException();\n  &#125;\n&#125;</code></pre>\n<p>Если обратиться к такому методу через HTTP и смоделировать ситуацию, когда выбрасывается данное исключение, то в ответ будет отправлено сообщение похожее на такое:</p>\n<pre class=code language-http><code class=\"code__container language-http\">HTTP/1.1 500 Internal Server Error\nCache-Control: no-cache\nPragma: no-cache\nContent-Type: application/json; charset=utf-8\nExpires: -1\nServer: Microsoft-IIS/8.0\nContent-Length: 2687\n\n&#123;\"Message\":\"An error has occurred.\",\"ExceptionMessage\": ...</code></pre>\n<p>Если приложение запущено в отладочной среде, здесь даже будет содержаться информация о типе исключения с подробным описанием.</p>\n<p>Клиент не всегда ожидает подобного поведения. К примеру, если он отправил некорректный набор данных почему бы не вернуть ему <code>Bad Request</code> с описанием причины? Чтобы переопределить это поведение создадим ExceptionFilter - наследник <code>ExceptionFilterAttribute</code>, в котором переопределен метод <code>OnException</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class NotSupportedExceptionFilterAttribute : ExceptionFilterAttribute\n&#123;\n  public override void OnException(HttpActionExecutedContext context)\n  &#123;\n    if (context.Exception is NotSupportedException)\n    &#123;\n      context.Response = new HttpResponseMessage(HttpStatusCode.BadRequest)\n      &#123;\n        Content = new StringContent(\"Data you provided is not supported.\")\n      &#125;;\n    &#125;\n  &#125;\n&#125;</code></pre>\n<p>Как видно из этого кода, этот фильтр проверяет тип обрабатываемого исключения и при необходимости подменяет ответ. Осталось только подключить этот фильтр к приложению.</p>\n<h2>Подключение к приложению</h2><p>Подключить Exception Filter можно двумя способами:</p>\n<ol>\n<li>Разметить атрибутом нужный контроллер/действия</li>\n<li>Подключить глобально</li>\n</ol>\n<p>Если логика работы данного фильтра актуальна только для конкретного контроллера или действия, то имеет смысл разметить их данным атрибутом, чтобы фильтр начал работу:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class UsersController : ApiController\n&#123;\n  [HttpPost]\n  [NotSupportedExceptionFilter]\n  public void Create(UsersCreateModel model)\n  &#123;\n    throw new NotSupportedException();\n  &#125;\n&#125;</code></pre>\n<p>Возможна такая ситуация, когда логика фильтра должна работать для всего приложения. Например, мы обрабатываем все исключения, связанные с бизнес логикой и формируем корректные ответы. В этом случае можно не размечать контроллеры данным атрибутом, а добавить их при конфигурировании глобально:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class WebApiConfig\n&#123;\n  public static void Register(HttpConfiguration config)\n  &#123;\n    config.Filters.Add(new NotSupportedExceptionFilterAttribute());</code></pre>\n<p>Теперь при выбрасывании необработанных исключений <code>NotSupportedException</code>, клиенту будет отправлен HTTP-ответ следующего вида:</p>\n<pre class=code language-http><code class=\"code__container language-http\">HTTP/1.1 400 Bad Request\nCache-Control: no-cache\nPragma: no-cache\nContent-Length: 32\nContent-Type: text/plain; charset=utf-8\nExpires: -1\nServer: Microsoft-IIS/8.0\n\nData you provided is not supported.</code></pre>",
            "tags": [
                "ASP.NET"
            ],
            "banner_image": "https://blog.zwezdin.com/2014/webapi-exception-handling/52dc5ef0/webapi_exception-handling@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2014/aspnet-mvc-grunt-bower/",
            "url": "https://blog.zwezdin.com/2014/aspnet-mvc-grunt-bower/",
            "title": "Подключаем Grunt и Bower к проекту ASP.NET MVC",
            "date_published": "2014-07-07T12:00:00.000Z",
            "content_html": "<p><strong>ASP.NET</strong> предлагает хорошую инфраструктуру для серверного кода, но веб-приложения - это ещё и фронтенд. Клиентский код нужно процессить, упаковывать, минифицировать, разбивать на пакеты и т.п. В мире .NET популярен так же Nuget, который содержит множество полезных пакетов. Но в JavaScript мире эту роль выполняет Bower. Поэтому давайте попробуем подключить <strong>Grunt</strong> (для автоматизации работы с клиентским кодом) и <strong>Bower</strong> (в качестве пакетного менеджера для JavaScript).</p>\n<h2>Подключение к проекту ASP.NET MVC</h2><p>К сожалению, ASP.NET MVC не имеет встроенной поддержки <strong>Grunt</strong> и <strong>Bower</strong>. Поэтому для их корректной работы предварительно необходимо установить несколько nuget-пакетов:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">Install-Package Npm\nInstall-Package Grunt\nInstall-Package Bower</code></pre>\n<p>Эти пакеты подтянут остальные зависимости, необходимые для работы. В результате установки пакетов, в папке проекта появится папка <code>.bin</code> - не удаляйте её. Эта папка содержит файлы для запуска Grunt и Bower.</p>\n<p>Этого уже достаточно, чтобы запускать grunt и bower в ручном режиме - для этого необходимо запускать соответствующие файлы из папки <code>.bin</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/aspnet-mvc-grunt-bower/d8c1829c/1@2x.png\"  alt=\"Подключаем Grunt и Bower к проекту ASP.NET MVC\"/>\n\n\n\n\n<p>Если требуется делать то же самое при сборке проекта, то необходимо немного модифицировать файл проекта - в самый конец файла <code>.csproj</code> дописываем директивы для запуска npm, grunt и bower:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">  &lt;Target Name=\"NpmBuild\" BeforeTargets=\"Build\">\n    &lt;Exec Command=\".bin\\npm install\" />\n  &lt;/Target>\n  &lt;Target Name=\"BowerInstall\" AfterTargets=\"NpmBuild\">\n    &lt;Exec Command=\".bin\\bower install\" />\n  &lt;/Target>\n  &lt;Target Name=\"GruntBuild\" AfterTargets=\"BowerInstall\">\n    &lt;Exec Command=\".bin\\grunt\" />\n  &lt;/Target>\n&lt;/Project></code></pre>\n<p>Теперь при сборке ASP.NET MVC проекта будут также запускаться инструменты для сборки клиентского кода.</p>\n<h2>Инициализация файлов</h2><p>Для работы Grunt и Bower требуется создать несколько файлов - <code>package.json</code> и <code>bower.json</code>, а так же сам скрипт для сборки - <code>gruntfile.js</code>.</p>\n<p>Необходимо открыть консоль Windows и перейти в папку проекта. После этого последовательно запускаем:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">npm init</code></pre>\n<p>После этого нужно будет ответить на несколько вопросов в интерактивном режиме (имя проекта, версия и т.п.). На этом шаге будет создан файл <code>package.json</code>.</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">bower init</code></pre>\n<p>Все то же самое, но результате создается файл <code>bower.json</code>. При установке пакетов из Bower они будут попадать в папку <code>bower_components</code>. Если вас это не устраивает, то можно добавить в проект файл с именем <code>.bowerrc</code> и переопределить это поведение:</p>\n<pre class=code language-json><code class=\"code__container language-json\">&#123;\n  \"directory\": \"Scripts/.bower_components\"\n&#125;</code></pre>\n<p>И последнее - нужно добавить в папку проекта файл <code>gruntfile.js</code> - это скрипт, который будет выполняться при запуске grunt. Пустой скрипт может выглядеть так:</p>\n<pre class=code language-javascript><code class=\"code__container language-javascript\">module.exports = function (grunt) &#123;\n\n  grunt.initConfig(&#123;\n    pkg: grunt.file.readJSON(\"package.json\")\n  &#125;);\n\n  grunt.registerTask(\"default\", []);\n\n&#125;;</code></pre>\n<p>Более подробно о содержимом этого файла можно почитать на официальном <a href=\"http://gruntjs.com/getting-started\">сайте Grunt</a>.</p>\n<h2>Подключение пакетов</h2><p>Теперь, когда grunt и bower установлены и подключены к проекту, самое время попробовать установить пакеты из общего репозитория.</p>\n<p>Установка пакетов - это прописывание соответствующих директив в <code>package.json</code>/<code>bower.json</code> с последующим запуском этих инструментов. Поэтому существует два способа подключения пакетов - вручную добавить их в указанные файлы, либо воспользоваться инструментами и подключить их из командной строки. Попробуем сделать это.</p>\n<p>Подключим, для начала, пакет <code>grunt-contrib-clean</code>. Это пакет, который занимается очисткой папок перед сборкой. В командной строке выполняем</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">npm install grunt-contrib-clean --save-dev</code></pre>\n<p>Если заглянуть теперь в <code>package.json</code>, то будет видно, что туда добавился новый элемент:</p>\n<pre class=code language-json><code class=\"code__container language-json\">\"devDependencies\":\n&#123;\n  \"grunt\": \"^0.4.5\",\n  \"grunt-contrib-clean\": \"^0.6.0\"\n&#125;</code></pre>\n<p>Кроме того, в папке проекта появилась папка <code>node_modules</code>, которая содержит код этого пакета. На практике эти папки не распространяются с исходным кодом. Т.е. мы можем спокойно удалить папку <code>node_modules</code> и передать код коллеге. Все что нужно сделать в этом случае, на новом месте запустить:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">npm install</code></pre>\n<p>При запуске этой команды Npm создаст папку <code>node_modules</code> и установит туда все зависимости, которые сейчас находятся в файл <code>package.json</code>. Поскольку мы добавили соответствующие директивы в файл проекта <code>.csproj</code>, запуск этой команды вручную не потребуется - все зависимости будут загружены автоматически при сборке проекта.</p>\n<p>Похожа ситуация и с Bower. Подключение пакета:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">bower install -S jquery</code></pre>\n<p>В <code>bower.json</code> добавляется при этом нужна зависимость:</p>\n<pre class=code language-json><code class=\"code__container language-json\">\"dependencies\":\n&#123;\n  \"jquery\": \"~2.1.1\",\n&#125;</code></pre>\n<p>Распространять вместе с проектом папку <code>bower_components</code> тоже не обязательно. Для того, чтобы скачать все подключенные пакеты достаточно запустить следующую команду:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">bower install</code></pre>\n<p>Так же как и для Npm, пакеты Bower будут подтягиваться автоматически при сборке проекта, поэтому запускать эту команду вручную необязательно.</p>\n<p>Как видно, логика подключения пакетов из Npm и Bower очень похожа на Nuget. Напоследок, предлагаю посмотреть как при помощи этих инструментов решаются практические задачи.</p>\n<h2>Минификация кода</h2><p>Предположим у нас есть JavaScript-код, который требуется упаковать в один файл и минифицировать. Для каждой задачи по автоматизации нужно найти подходящий пакет в официальном <a href=\"https://www.npmjs.com/\">репозитории</a>. В нашем случае подойдет пакет <a href=\"https://www.npmjs.com/package/grunt-contrib-uglify\">grunt-contrib-uglify</a>.</p>\n<p>Подключаем пакет к проекту:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">npm install grunt-contrib-uglify --save-dev</code></pre>\n<p>Добавляем в скрипт <code>gruntfile.js</code>:</p>\n<pre class=code language-javascript><code class=\"code__container language-javascript\">module.exports = function (grunt) &#123;\n  grunt.initConfig(&#123;\n    pkg: grunt.file.readJSON(\"package.json\"),\n\n    uglify: &#123;\n      \"WebApp27\": &#123;\n        files: &#123;\n          \"dist/result.js\": [\n            \"Scripts/controller1.js\",\n            \"Scripts/controller2.js\"]\n        &#125;\n      &#125;\n    &#125;\n  &#125;);</code></pre>\n<p>Смысл скрипта заключается в том, что мы берем файлы <code>controller1.js</code> и <code>controller2.js</code>, минифицируем их, а результат складываем в <code>result.js</code>.</p>\n<p>Когда это готово, вызовом метода <code>loadNpmTasks</code> загружаем пакет Uglify при сборке и запускаем его вместе с остальными задачами:</p>\n<pre class=code language-javascript><code class=\"code__container language-javascript\">module.exports = function (grunt) &#123;\n  grunt.initConfig(&#123;\n    pkg: grunt.file.readJSON(\"package.json\"),\n\n    uglify: &#123;\n      \"WebApp27\": &#123;\n        files: &#123;\n          \"dist/result.js\": [\n            \"Scripts/controller1.js\",\n            \"Scripts/controller2.js\"]\n        &#125;\n      &#125;\n    &#125;\n  &#125;);\n\n  grunt.loadNpmTasks(\"grunt-contrib-uglify\");\n\n  grunt.registerTask(\"default\", [\"uglify\"]);\n&#125;;</code></pre>\n<p>Теперь при запуске grunt из командной строки или при сборке ASP.NET проекта, задача по минификации будет также запущена и файл <code>result.js</code> будет содержать минифицированный вариант.</p>\n<p>Подробнее о синтаксисе этого скрипта можно почитать на официальном сайте <a href=\"http://gruntjs.com/getting-started\">Grunt</a>.</p>\n<h2>Сборка LESS</h2><p>Другой задачей, часто решаемой при помощи Grunt является сборка <a href=\"http://lesscss.org/\">LESS-файлов</a>. Аналогично предыдущему примеру установим пакет <code>grunt-contrib-less</code> и добавим его директиву в <code>gruntfile.js</code>:</p>\n<pre class=code language-javascript><code class=\"code__container language-javascript\">module.exports = function(grunt) &#123;\n  grunt.initConfig(&#123;\n    pkg: grunt.file.readJSON(\"package.json\"),\n\n    // ...\n\n    less: &#123;\n      styles: &#123;\n        files: [\n        &#123;\n          \"dist/styles.css\": \"styles/styles.less\"\n        &#125;\n        ]\n      &#125;\n\n    &#125;\n  &#125;);\n\n  // ...\n  grunt.loadNpmTasks(\"grunt-contrib-less\");\n\n  grunt.registerTask(\"default\", [/*...*/, \"less\"]);\n&#125;;</code></pre>\n<p>Добавим файл <code>styles/styles.less</code> в проект:</p>\n<pre class=code language-css><code class=\"code__container language-css\">@color1: #fff;\n\nbody &#123;\n  background-color: @color1;\n&#125;</code></pre>\n<p>И запустим сборку. В итоге в файле <code>dist/styles.css</code> будет содержаться результат компиляции LESS-файла:</p>\n<pre class=code language-css><code class=\"code__container language-css\">body &#123;\n  background-color: #ffffff;\n&#125;</code></pre>",
            "tags": [
                "ASP.NET",
                "Grunt",
                "Bower"
            ],
            "banner_image": "https://blog.zwezdin.com/2014/aspnet-mvc-grunt-bower/3c5e294a/aspnet_mvc-grunt-bower@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2014/uwdc-2014/",
            "url": "https://blog.zwezdin.com/2014/uwdc-2014/",
            "title": "UWDC 2014 в Челябинске (фотоотчет)",
            "date_published": "2014-06-04T12:00:00.000Z",
            "content_html": "<p>30 мая прошла одна из крупнейших в регионе конференций - UWDC 2014. О том, как это было я и хочу рассказать.</p>\n<p>Планировать UWDC мы начали за несколько месяцев. Изначально не всё шло гладко, но в целом конференция получилась. Большая заслуга в этом – <a href=\"http://twitter.com/yasitko\">Андрея Ситко</a>, который руководил всем процессом и ежедневно решал большое количество вопросов.</p>\n<p>И вот наступил этот день – 30 мая. Программу мы запланировали обширную – в отличие от прошлых лет, конференция проходила в 1 день, но доклады шли плотно, в несколько потоков. Поэтому всем организатором в процессе проведения конференции скучать не приходилось.</p>\n<p>Началось как и всегда с регистрации.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/uwdc-2014/ffcc1ef1/1@2x.jpg\"  alt=\"UWDC 2014 в Челябинске (фотоотчет)\"/>\n\n\n\n\n<p>Не без проблем, но удалось в этом году организовать и онлайн-трансляцию.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/uwdc-2014/a9910186/2@2x.jpg\"  alt=\"UWDC 2014 в Челябинске (фотоотчет)\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/uwdc-2014/34f1c71a/3@2x.jpg\"  alt=\"UWDC 2014 в Челябинске (фотоотчет)\"/>\n\n\n\n\n<p>На мой взгляд докладчики очень порадовали уровнем докладов и качеством исполнения. Практически каждый доклад сопровождался серией вопросов и обсуждения продолжались в кулуарах.</p>\n<h2>Доклады</h2>\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2014/uwdc-2014/dda01c12/4@2x.jpg\"  alt=\"Как мы делаем мобильную версию интернет-магазина е96.ru, Коньшин Александр — Фото: Сергей Звездин\"/><figcaption>Как мы делаем мобильную версию интернет-магазина е96.ru, Коньшин Александр <cite>— Фото: Сергей Звездин</cite></figcaption></figure>\n\n\n\n\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2014/uwdc-2014/f33da56a/5@2x.jpg\"  alt=\"Real Time SPA на примере Яндекс.Такси, Павел Горобцов — Фото: Сергей Звездин\"/><figcaption>Real Time SPA на примере Яндекс.Такси, Павел Горобцов <cite>— Фото: Сергей Звездин</cite></figcaption></figure>\n\n\n\n\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2014/uwdc-2014/482d0aa2/6@2x.jpg\"  alt=\"Как мы переписали поисковый движок и выкинули половину серверов, Рамиль Мустафин — Фото: Сергей Звездин\"/><figcaption>Как мы переписали поисковый движок и выкинули половину серверов, Рамиль Мустафин <cite>— Фото: Сергей Звездин</cite></figcaption></figure>\n\n\n\n\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2014/uwdc-2014/e7bc8a60/7@2x.jpg\"  alt=\"Живые обои для Android. Тонкости. Продвижение, Сергей Михайлов — Фото: Сергей Звездин\"/><figcaption>Живые обои для Android. Тонкости. Продвижение, Сергей Михайлов <cite>— Фото: Сергей Звездин</cite></figcaption></figure>\n\n\n\n\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2014/uwdc-2014/c45bf681/8@2x.jpg\"  alt=\"Xamarin. Кроссплатформенная мобильная разработка на C#, Гаевский Андрей — Фото: Сергей Звездин\"/><figcaption>Xamarin. Кроссплатформенная мобильная разработка на C#, Гаевский Андрей <cite>— Фото: Сергей Звездин</cite></figcaption></figure>\n\n\n\n\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2014/uwdc-2014/5c6c8647/9@2x.jpg\"  alt=\"Использование pinba + graphite для мониторинга производительности приложений в реальном времени, наш подход в Lingualeo, Евгений Климов — Фото: Сергей Звездин\"/><figcaption>Использование pinba + graphite для мониторинга производительности приложений в реальном времени, наш подход в Lingualeo, Евгений Климов <cite>— Фото: Сергей Звездин</cite></figcaption></figure>\n\n\n\n\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2014/uwdc-2014/69f8b0e2/10@2x.jpg\"  alt=\"Фото зала во время одного и докладов — Фото: Сергей Звездин\"/><figcaption>Фото зала во время одного и докладов <cite>— Фото: Сергей Звездин</cite></figcaption></figure>\n\n\n\n\n<p>Для меня этот день начался в 6 утра, а вернулся домой я уже за полночь, но это того стоило! :-)</p>\n<p>Надеюсь, у меня получилось передать атмосферу мероприятия. Приходите на UWDC 2015! :-)</p>",
            "tags": [
                "Мероприятия",
                "UWDC"
            ],
            "banner_image": "https://blog.zwezdin.com/2014/uwdc-2014/1c5fa5e0/uwdc_2014@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2014/mvc-build-views/",
            "url": "https://blog.zwezdin.com/2014/mvc-build-views/",
            "title": "Компиляция представлений в ASP.NET MVC",
            "date_published": "2014-05-31T12:00:00.000Z",
            "content_html": "<p>Любой код, написанный для платформы .NET должен быть скомпилирован, и это прекрасно. Компиляция позволяет быть уверенным в том, что код корректный и в нем нет синтаксических ошибок. К сожалению, компиляция обязательная не всегда - представления ASP.NET MVC можно изменять без компиляции представлений. Нет, компиляция, конечно, есть, но она выполняется непосредственно перед запуском этого кода. И если в коде содержится синтаксическая ошибка, то узнаем мы о ней только тогда, когда запустим приложение. Из представления мы можем обращаться к моделям, которые так же могу измениться и приложение перестанет работать.</p>\n<p>Чтобы изменить это поведение, в проекте <strong>ASP.NET MVC</strong> существует специальный параметр — <code>MvcBuildViews</code>. Если установить этот параметр в <code>true</code>, то в процессе сборки приложения, представления тоже будут компилироваться. Это означает, что если у вас есть ошибки в представлениях, вы увидите их уже на этапе компиляции.</p>\n<p>Небольшое замешательство вызывает тот факт, что в свойствах проекта ASP.NET нет опции, чтобы изменить этот параметр. Поэтому, чтобы воспользоваться этой возможностью, придется немного поработать руками. И для этого есть два способа.</p>\n<p><strong>Первый способ</strong> заключается в том, чтобы запустить сборку проекта с явно заданным значением параметра <code>MvcBuildViews</code>:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">MSBuild.exe WebProject.csproj  /p:MvcBuildViews=true</code></pre>\n<p>Внутри msbuild-скрипта это может выглядеть вот так:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;MSBuild Projects=\"WebProject.csproj\" Targets=\"Build\" Properties=\"MvcBuildViews=true\"/></code></pre>\n<p><strong>Второй способ</strong> - задать значение параметра <code>MvcBuildViews</code> прямо в файле проекта. К сожалению, Visual Studio не предоставляет для этого инструментов, поэтому в текстовом редакторе нужно открыть файл проекта <code>.csproj</code> и изменить значение этого параметра:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/mvc-build-views/f21e0a04/1@2x.png\"  alt=\"Компиляция представлений в ASP.NET MVC\"/>\n\n\n\n\n<p>Если параметр отсутствует, то нужно просто создать узел с именем <code>MvcBuildViews</code> и задать значение внутри него.</p>\n<p>После этих манипуляций при сборке проекта ASP.NET MVC представления будут компилироваться вместе с основным проектом и если они содержат ошибки, они будут выявлены ещё на этапе компиляции.</p>",
            "tags": [
                "ASP.NET"
            ],
            "banner_image": "https://blog.zwezdin.com/2014/mvc-build-views/3c1c93b4/mvc_build-views@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2014/razor-for-email-templates/",
            "url": "https://blog.zwezdin.com/2014/razor-for-email-templates/",
            "title": "Использование Razor для генерации содержимого сообщения email",
            "date_published": "2014-05-29T12:00:00.000Z",
            "content_html": "<p>Многие веб-приложения в процессе работы отправляют email-сообщения. И содержимое сообщений могут содержать как простой текст, так и HTML-разметку. Последний вариант предпочтительнее, если хочется видеть красиво отформатированное сообщение. Встает вопрос - а как генерировать эту разметку?</p>\n<p>Существует множество библиотек, которые так или иначе решают эту задачу. Однако, если мы присмотримся, то увидим, что <strong>Razor</strong> - движок представлений в ASP.NET MVC - решает именно эту задачу, но для выдачи в браузер. Так почему же не приспособить Razor для генерации содержимого сообщений электронной почты?</p>\n<ol>\n<li>Для получения представления нужно воспользоваться методом <code>ViewEngines.Engines.FindPartialView</code>.</li>\n<li>Чтобы сгенерировать результат потребуется объект <code>ViewContext</code>, который содержит информацию о текущем контексте (<code>ViewData</code>, модель, параметры) - его просто нужно создать и определить начальные значения.</li>\n<li>Для генерации результата потребуется объект <code>StringWriter</code> - это стандартный прием, используемый движками представлений ASP.NET MVC.</li>\n<li>Чтобы получить результат следует вызвать метод Render и передать туда необходимые параметры.</li>\n<li>После получения результата следует освободить ресурсы, занимаемые представлением. Для этого используется метод <code>ReleaseView</code>.</li>\n</ol>\n<p>Можно сделать метод-расширение, который выполняет эти действия:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class ViewEngineExtensions\n&#123;\n  public static string RenderPartialView(this ControllerContext context,\n      string viewName, object model = null)\n  &#123;\n    StringBuilder result = new StringBuilder();\n\n    using (var writer = new StringWriter(result))\n    &#123;\n      var viewData = new ViewDataDictionary &#123;Model = model&#125;;\n\n      var view = ViewEngines.Engines.FindPartialView(context, viewName);\n      var viewContext = new ViewContext(context, view.View, viewData,\n          new TempDataDictionary(), writer);\n\n      view.View.Render(viewContext, writer);\n      view.ViewEngine.ReleaseView(context, view.View);\n    &#125;\n\n    return result.ToString();\n  &#125;\n&#125;</code></pre>\n<p>Для генерации результата необходимо создать представление. Пусть это будет представление с именем <code>_Message.cshtml</code>, размещенное в папке <code>Views/Shared</code>:</p>\n<pre class=code language-html><code class=\"code__container language-html\">@model int\n&lt;p>Value is &lt;b>@Model&lt;/b>&lt;/p></code></pre>\n<p>Тогда код генерации результата и отправки email-сообщения будет выглядеть следующим образом:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">using (SmtpClient smtp = new SmtpClient())\n&#123;\n  MailMessage message = new MailMessage\n  &#123;\n    Subject = \"Test Message\",\n    IsBodyHtml = true,\n    Body = ControllerContext.RenderPartialView(\"_Message\", 22)\n  &#125;;\n\n  message.To.Add(@\"noreply@email.com\");\n\n  smtp.Send(message);\n&#125;</code></pre>",
            "tags": [
                "ASP.NET",
                "Razor"
            ],
            "banner_image": "https://blog.zwezdin.com/2014/razor-for-email-templates/baa802e3/razor_for-email-templates@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2014/visual-studio-browserlink/",
            "url": "https://blog.zwezdin.com/2014/visual-studio-browserlink/",
            "title": "Browser Link в Visual Studio 2013",
            "date_published": "2014-05-15T12:00:00.000Z",
            "content_html": "<p>В <strong>Visual Studio 2013</strong> появился новый инструмент, помогающий при разработке веб-приложений — <strong>Browser Link</strong>. Идея Browser Link заключается в том, что Visual Studio и браузер имеют постоянный канал связи. Это позволяет централизованно управлять браузерами из Visual Studio. Чтобы лучше понять как работает Browser Link, давайте подключим этот инструмент к веб-сайту.</p>\n<p>Чтобы использовать Browser Link должны быть соблюдены следующие условия:</p>\n<ul>\n<li>Приложение должно быть запущено на <code>localhost</code>.</li>\n<li>Приложение должно работать под управлением .NET версии 4 или более поздней.</li>\n<li>Для приложения должен быть включен режим отладки (свойство <code>debug=true</code> в секции <code>&lt;compilation&gt;</code> в конфигурационном файле).</li>\n</ul>\n<p>Для включения Browser Link в Visual Studio 2013 появилась специальная кнопка на панели управления:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/visual-studio-browserlink/56316699/1@2x.png\"  alt=\"Browser Link в Visual Studio 2013\"/>\n\n\n\n\n<p>В этом же меню присутствует ссылка на <strong>Browser Link Dashboard</strong> — панель, на которой отображаются все активные подключения.</p>\n<p>Теперь, когда мы включили Browser Link в Visual Studio, можно запустить веб-сайт. Если мы заглянем в исходный код страницы, то там можно увидеть, что в нее встроен код, который обеспечивает поддержку работы Browser Link на стороне браузера.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/visual-studio-browserlink/b30ceb09/2@2x.png\"  alt=\"Browser Link в Visual Studio 2013\"/>\n\n\n\n\n<p>Этот код добавляется автоматически и только в том случае, если проект запущен локально. При развертывании на удаленном сервере этого кода не будет. Более того, если для конкретного проекта требуется запретить использование Browser Link, то это можно сделать в файле конфигурации, добавив параметр <code>vs:EnableBrowserLink</code>:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;appSettings>\n  &lt;add key=\"vs:EnableBrowserLink\" value=\"false\"/>\n&lt;/appSettings></code></pre>\n<p>После открытия страницы в различных браузеров можно вернуться в Visual Studio и открыть Browser Link Dashboard - здесь будут видны все активные подключения.</p>\n<p>Если мы теперь откроем нужную страницу в браузерах, а затем изменим её в Visual Studio, то нам не нужно открывать каждый браузер и нажимать <kbd>F5</kbd>. Теперь достаточно выбрать пункт меню <code>Refresh Linked Browsers</code> (или просто <kbd><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Enter</kbd></kbd>) и страница будет обновлена во всех браузерах.</p>\n<p>Если взглянуть на то, как это устроено, то работает это очень просто — при запуске веб-сайта при помощи SignalR устанавливается соединения клиентской части веб-сайта и Visual Studio. По этому каналу и происходит взаимодействие. Минусом такого подхода, кстати, будет то, что если у вас в приложении так же имеется SignalR-подключение, то они будут конфликтовать.</p>\n<p>На первый взгляд может показаться, что это очень незначительная возможность Visual Studio, которая к тому же требует определенной предварительно настройки. На самом же деле Browser Link — это своего рода базовый API, поверх которого можно реализовать собственные сценарии управления клиентским кодом из Visual Studio. Например, некоторые такие сценарии реализованы в популярном плагине <strong>Web Essentials</strong>.</p>",
            "tags": [
                "ASP.NET",
                "Visual Studio"
            ],
            "banner_image": "https://blog.zwezdin.com/2014/visual-studio-browserlink/6fef1f95/visual_studio-browserlink@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2014/visual-studio-installer-project/",
            "url": "https://blog.zwezdin.com/2014/visual-studio-installer-project/",
            "title": "Возможность создания пакетов установщиков вернулась в Visual Studio",
            "date_published": "2014-04-19T12:00:00.000Z",
            "content_html": "<p>Несколько лет назад Microsoft прекратила поддержку создания пакетов установки в Visual Studio. Тогда было предложено воспользоваться технолгией ClickOnce (где это возможно), либо работать с InstallShield напрямую. Однако, отзывы разработчиков делают своё дело и сегодня анонсировано расширение Visual Studio, которое возвращает поддержку этой функциональности в Visual Studio.</p>\n<p>Внешне пакеты установщиков не особо изменились, так же как и не изменился процесс их создания.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/visual-studio-installer-project/02614aca/visual-studio-installer-project@2x.png\"  alt=\"Возможность создания пакетов установщиков вернулась в Visual Studio\"/>\n\n\n\n\n<p>Думаю эта новость может обрадовать многих, кто использовал эту функциональность и потом лишился её.</p>",
            "tags": [
                "Visual Studio"
            ],
            "banner_image": "https://blog.zwezdin.com/2014/visual-studio-installer-project/3b16cc7a/visual_studio-installer-project@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2014/sql-server-data-tools/",
            "url": "https://blog.zwezdin.com/2014/sql-server-data-tools/",
            "title": "Версионирование схемы базы данных при помощи SQL Server Data Tools (SSDT)",
            "date_published": "2014-03-29T12:00:00.000Z",
            "content_html": "<p>Большинство команд в своих проектах давно научились использовать системы контроля версий для хранения истории изменения кода. Но многие до сих пор не могут четко ответить на вопрос, что в этом случае делать с базой данных. Здесь я расскажу а том, как использовать SSDT для решения этого вопроса.</p>\n<p>За время своей работы мне приходилось сталкиваться с различными подходами к версионированию схемы базы данных. Начиная от того, что схема никак не версионировалась, заканчивая фреймворками по миграции данных (DotNetMigrations, EF Migrations). В итоге мы остановились на использовании <strong>SQL Server Data Tools</strong>.</p>\n<p>Давайте разберемся как работают фреймворки миграции. В них всё предельно просто - каждый раз когда меняется схема данных, мы создаем новую миграцию (инкремент схемы данных), в которой указываем что конкретно должно измениться в схеме данных. Например, это может быть создание новой таблицы или изменение существующей. DotNetMigration работают на уровне SQL-скриптов, каждый новый инкремент - новый скрипт. EF Migrations позволяют работать с этим в виде объектов, но идея также. В качестве бонуса при создании миграции можно управлять данными (например, заполнить новую таблицу тестовыми/начальными данными). Для того, чтобы воссоздать схему базы данных, вам нужно последовательно запустить все миграции одну за другой. При этом в системе контроля версий у вас хранится несколько файлов (для каждой миграции) и они неизменны.</p>\n<p>В альтернативу этому подходу, SSDT предлагает хранить в системе контроля версий текущую схему базы данных, а все инкременты доступны в виде истории комитов. По сути, SSDT позволяет создавать database-проекты и публиковать их на нужном сервере. Давайте более подробно рассмотрим процесс использования SSDT.</p>\n<h2>Установка</h2><p>Для работы с SSDT на компьютер следует установить соответствующий пакет, который можно загрузить с <a href=\"http://msdn.microsoft.com/en-us/data/tools.aspx\">официального сайта</a>. После установки этого пакета, в шаблонах проекта появится новый тип проектов - <mark>“SQL Server Database Project”</mark>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/sql-server-data-tools/8696d344/install@2x.png\"  alt=\"Версионирование схемы базы данных при помощи SQL Server Data Tools (SSDT)\"/>\n\n\n\n\n<h2>Создание схемы базы данных</h2><p>Проект готов и мы можем приступить к созданию схемы БД. Для этого добавим в проект новые таблицы.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/sql-server-data-tools/5bea9df5/create-schema@2x.png\"  alt=\"Версионирование схемы базы данных при помощи SQL Server Data Tools (SSDT)\"/>\n\n\n\n\n<p>Для таблиц существует встроенный редактор, который на лету генерирует SQL-скрипт для создания таблицы (который, впрочем, можно редактировать и руками).</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/sql-server-data-tools/87cf2449/table-editor@2x.png\"  alt=\"Версионирование схемы базы данных при помощи SQL Server Data Tools (SSDT)\"/>\n\n\n\n\n<p>Здесь же можно построить индексы, добавить триггеры,  а также связать таблицу с другими таблицами.</p>\n<p>В SSDT вы можете работать практически со всеми необходимыми артефактами базы данных Microsoft SQL Server - таблицы, индексы, хранимые процедуры и функции, пользователи и т.д.</p>\n<h2>Публикация</h2><p>После того, как проект готов, мы можем опубликовать его на реальном сервере. Для этого кликаем правой кнопкой по проекту и выбираем опцию <mark>“Publish”</mark>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/sql-server-data-tools/04a0a954/publish@2x.png\"  alt=\"Версионирование схемы базы данных при помощи SQL Server Data Tools (SSDT)\"/>\n\n\n\n\n<p>При публикации SSDT сравнивает схему целевой базы данных и схему нашего проекта и строит инкремент - скрипт, который адаптирует схему данных целевой базы данных в соответствии со схемой нашего проекта (грубо говоря, набор <code>ALTER TABLE</code> и пр.). Логично, что данные при этом на целевом сервере никуда не исчезают, если это не противоречит самой схеме.</p>\n<p>Если есть риск потерять данные (например, удалилась колонка, или вы добавляете колонку с не-NULL значением, но строки в этой таблице уже есть), то при публикации SSDT поругается и схема БД развернута не будет. В этом случае нужно что-то предпринимать. В идеале, таких ситуаций лучше избегать. Например, если вы добавляете новую не-NULL колонку к существующей таблице, то указать для неё значение по умолчанию. В некоторых случаях достаточно разрешить SSDT удалить какие-то данные ради успеха общего дела (<code>Advanced</code> → <code>Block incremental deployment if data loss may occur</code>).</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/sql-server-data-tools/f0ccb63d/advanced-settings@2x.png\"  alt=\"Версионирование схемы базы данных при помощи SQL Server Data Tools (SSDT)\"/>\n\n\n\n\n<p>После успешной публикации, обновленная схема БД появится на целевом сервере.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/sql-server-data-tools/c684b327/tables@2x.png\"  alt=\"Версионирование схемы базы данных при помощи SQL Server Data Tools (SSDT)\"/>\n\n\n\n\n<p>В процессе публикации SSDT также создает инкремент-скрпит (<code>.sql</code>). Некоторые коллеги также сохраняют и его как артефакт процесса сборки.</p>\n<p>В нашем случае проект SSDT входит в общий состав Solution’а и собирается вместе с остальными проектами на билд-сервере. Также как и запускается публикация на SQL-сервер в процессе развертывания. Важно, что если у вас есть какие-то ошибки или неточности, например, в описании ваших таблиц, то SSDT-проект не соберется и сборка остановится. Это очень помогает.</p>\n<h2>Рефакторинг</h2><p>Схема базы данных - это часть проекта, которая развивается вместе с ним. Появляются новые таблицы, связи, индексы, ключи. И если с добавлением новых сущностей в проект все понятно (они просто появятся в схеме при развертывании), то что делать с изменениями уже существующих сущностей?</p>\n<p>Допустим у нас есть таблица и мы хотим добавить в нее новое поле. В этом случае мы просто делаем это и при развертывании мы получим обновленную таблицу.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/sql-server-data-tools/4f1ebfef/refactoring-editor@2x.png\"  alt=\"Версионирование схемы базы данных при помощи SQL Server Data Tools (SSDT)\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/sql-server-data-tools/ad6eaac2/refactoring-tables@2x.png\"  alt=\"Версионирование схемы базы данных при помощи SQL Server Data Tools (SSDT)\"/>\n\n\n\n\n<p>Сложнее дела обстоят с другими изменениями. Например, если вам нужно переименовать колонку, то вам наверняка захочется, чтобы данные в целевой базе данных сохранились. Для этого нужны рефакторинги в SSDT. Давайте посмотрим на примере. Заполню данными таблицу из предыдущего примера.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/sql-server-data-tools/0944c936/refactoring-tables2@2x.png\"  alt=\"Версионирование схемы базы данных при помощи SQL Server Data Tools (SSDT)\"/>\n\n\n\n\n<p>После чего в проекте SSDT я изменю название одной из колонок.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/sql-server-data-tools/e6cc965f/refactoring-editor2@2x.png\"  alt=\"Версионирование схемы базы данных при помощи SQL Server Data Tools (SSDT)\"/>\n\n\n\n\n<p>Публикую обновленный проект на сервер.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/sql-server-data-tools/a17c26cd/refactoring-tables3@2x.png\"  alt=\"Версионирование схемы базы данных при помощи SQL Server Data Tools (SSDT)\"/>\n\n\n\n\n<p>Схема изменилась, данные сохранились.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/sql-server-data-tools/2dec144b/refactoring-tables4@2x.png\"  alt=\"Версионирование схемы базы данных при помощи SQL Server Data Tools (SSDT)\"/>\n\n\n\n\n<p>Происходит это за счет того, что при каждом таком изменении, SSDT хранит лог рефакторинга. Это - файл <code>.refactorlog</code>, который хранится в проекте в формате XML.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/sql-server-data-tools/a6a3db92/refactoring-script@2x.png\"  alt=\"Версионирование схемы базы данных при помощи SQL Server Data Tools (SSDT)\"/>\n\n\n\n\n<p>В самой базе данных эта информация тоже сохраняется.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/sql-server-data-tools/47a682a4/refactoring-log@2x.png\"  alt=\"Версионирование схемы базы данных при помощи SQL Server Data Tools (SSDT)\"/>\n\n\n\n\n<p>Таким образом, SSDT может гарантировать, что ваши данные не потеряются.</p>\n<p>Сам по себе инструмент рефакторинга - это мощный инструмент. Однако, набор рефакторингов пока крайне ограничен. Например, вы не можете удалить колонку из одной таблицы, поместить её в дургую и при этом сохранить данные. Полный список рефакторингов вы можете увидеть по этой <a href=\"http://msdn.microsoft.com/en-us/library/dd193420(v=vs.100).aspx\">ссылке</a>.</p>\n<h2>Итого</h2><p>В итоге мы получаем инструмент со своими плюсами и минусы, которые в заключение я хочу перечислить.</p>\n<p><strong>Плюсы:</strong></p>\n<ul>\n<li>Понятное управление схемой базы данных.</li>\n<li>Хранение истории изменений в естественном виде (история комитов в системе контроля версий).</li>\n<li>Максимальная интеграция с SQL Server.</li>\n<li>Автоматическое построение инкремента для обновления базы данных.</li>\n<li>Прозрачная интеграция в процесс сборки.</li>\n</ul>\n<p><strong>Минусы:</strong></p>\n<ul>\n<li>Невозможность использования с иными СУБД, кроме SQL Server (для нас это не существенное ограничение).</li>\n<li>Трудности при сложных рефакторингах.</li>\n<li>Требуются дополнительные усилия, когда необходимо наполнить таблицы начальными данными.</li>\n</ul>",
            "tags": [
                "Visual Studio"
            ],
            "banner_image": "https://blog.zwezdin.com/2014/sql-server-data-tools/0224a66a/sql_server-data-tools@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2014/building-nuget-packages-with-nuget-exe/",
            "url": "https://blog.zwezdin.com/2014/building-nuget-packages-with-nuget-exe/",
            "title": "Создание nuget-пакетов при помощи консольной утилиты nuget.exe",
            "date_published": "2014-03-12T12:00:00.000Z",
            "content_html": "<p>Я уже рассматривал вопрос о том, как создать собственный <a href=\"/2013/how-to-setup-nuget-server\">Nuget-сервер</a> и как создать Nuget-пакет с помощью <a href=\"/2014/building-nuget-packages-with-gui-tool\">графической утилиты</a>. На этот раз я расскажу как сделать то же самое, используя только консольную утилиту <code>nuget.exe</code>.</p>\n<p>Необходимость использовать консольную утилиту может появиться тогда, когда нам нужно встроить сборку пакетов в процесс автоматической сборки проекта. В этих случаях использование графической утилиты становится малореальным.</p>\n<h2>nuget.exe</h2><p>Для работы нам потребуется консольная утилита <code>nuget.exe</code>, которую можно загрузить с <a href=\"https://github.com/nuget/home/\">официального сайта</a>. Описание работы с этой утилитой можно найти здесь.</p>\n<h2>Создание пакета</h2><p>Создать пакет в этом случае можно разными способами.</p>\n<p>Если вам требуется создать простой пакет, где будет содержаться одна сборка можно воспользоваться следующей командой:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">nuget spec Core.dll</code></pre>\n<p>Эта команда создаст файл манифеста (<code>nuspec</code>), необходимого для создания пакета.</p>\n<p>В более сложных случаях, когда в пакете содержится множество файлов и скриптов по установке, можно создать этот файл самостоятельно (спецификация <code>nuspec</code>), так же как и структуру папок.</p>\n<p>Другим вариантом будет использование графической утилиты, в которой существует опция <code>Export</code>:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/building-nuget-packages-with-nuget-exe/22621057/1@2x.png\"  alt=\"Создание nuget-пакетов при помощи консольной утилиты nuget.exe\"/>\n\n\n\n\n<p>В этом случае вы получите всю структуру папок и файлов, а также файл <code>nuspec</code> без лишних усилий. После этого вы можете изменять манифест <code>nuspec</code> (поскольку это — обычный файл XML), например, в процессе автоматизированной сборки.</p>\n<h2>Упаковка пакета</h2><p>После того, как все необходимые файлы готовы, а также создан манифест пакета, достаточно запустить утилиту <code>nuget.exe</code> со следующими параметрами:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">nuget pack MyPackage.1.0.0.nuspec</code></pre>\n<p>В результате работы этой утилиты мы получим файл <code>nupkg</code>, который готов к публикации на сервере. По сути, файл <code>nupkg</code> — это ZIP-архив, внутри которого находится всё содержимое пакета в установленном формате.</p>\n<h2>Публикация пакета</h2><p>Для публикации пакета необходимо использовать следующую команду <code>nuget.exe</code>:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">nuget push MyPackage.1.0.0.nupkg 1ab183523cx7usf -s http://nuget.somecompany.com/</code></pre>\n<p>Как видно из формата команды, мы указываем имя пакета, ключ сервера и адрес сервера.</p>\n<p>Если мы всё время работаем с одним и тем же nuget-сервером, то эти параметры можно определить в конфигурационном файле, который расположен по указанному пути — <code>%AppData%\\NuGet\\NuGet.config</code>.</p>\n<p>В этом случае команда по публикации будет немного короче:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">nuget push MyPackage.1.0.0.nupkg</code></pre>\n<p>Если вы публикуете свои пакеты на <code>nuget.org</code>, то вам необходимо зарегистрироваться там и получить ключ. После этого выполнить команду:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">NuGet SetApiKey Your-API-Key</code></pre>\n<p>В этом случае вы также можете использовать сокращенный синтаксис команды. Как можно догадаться, в этом случае будут просто изменены параметры в конфигурационном файле.</p>",
            "tags": [
                "Nuget"
            ],
            "banner_image": "https://blog.zwezdin.com/2014/building-nuget-packages-with-nuget-exe/07ca8a59/building_nuget-packages-with-nuget-exe@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2014/building-nuget-packages-with-gui-tool/",
            "url": "https://blog.zwezdin.com/2014/building-nuget-packages-with-gui-tool/",
            "title": "Создание nuget-пакетов при помощи GUI-утилиты",
            "date_published": "2014-03-02T12:00:00.000Z",
            "content_html": "<p>Недавно я писал о том, как создать собственный Nuget-сервер для локальных нужд. Для того, чтобы картина была полной, опишу процесс создания пакета nuget. Существует два разных способа сделать это — создать все артефакты руками и использовать утилиту <code>nuget.exe</code> для упаковки, либо воспользоваться графической утилитой. Рассмотрим второй вариант.</p>\n<p>При создании nuget-пакетов преимущества графической утилиты перед консольной очевидны — не нужно создавать все артефакты руками, все просто и интуитивно. Поэтому рассмотрим этот способ подробнее.</p>\n<h2>Загрузка и установка утилиты</h2><p>Первое, что нужно сделать — это установить утилиту для создания пакетов. Загрузить её можно <a href=\"https://npe.codeplex.com/downloads/get/clickOnce/NuGetPackageExplorer.application\">здесь</a>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/building-nuget-packages-with-gui-tool/8d2b31a8/1@2x.png\"  alt=\"Создание nuget-пакетов при помощи GUI-утилиты\"/>\n\n\n\n\n<p>Это — ClickOnce-установщик и вам нужно дождаться загрузки и установки приложения на компьютер.</p>\n<h2>Создание пакета</h2><p>Сразу после запуска утилиты, будет предложено несколько опций - создать новый пакет или открыть существующий (локально или из ленты Nuget-сервера). Давайте рассмотрим процесс создания пакета. Выбираем пункт <code>Create a new package</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/building-nuget-packages-with-gui-tool/4b604e19/2@2x.png\"  alt=\"Создание nuget-пакетов при помощи GUI-утилиты\"/>\n\n\n\n\n<p>Заполним информацию о нашем Nuget-пакете. Для этого в меню выбираем <code>Edit</code> → <code>Edit Metadata</code>:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/building-nuget-packages-with-gui-tool/16aac3f0/3@2x.png\"  alt=\"Создание nuget-пакетов при помощи GUI-утилиты\"/>\n\n\n\n\n<p>Здесь же можно установить зависимости нашего пакета на другие пакеты и сборки:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/building-nuget-packages-with-gui-tool/2ac165e7/4@2x.png\"  alt=\"Создание nuget-пакетов при помощи GUI-утилиты\"/>\n\n\n\n\n<h2>Добавляем содержимое в nuget-пакет</h2><p>После того, как информация о пакете заполнена, можно приступить к наполнению пакета.</p>\n<p>В правой части окна видна секция <code>Content</code>, которая отображает содержимое текущего пакета.</p>\n<p>При помощи пункта меню <code>CONTENT</code> добавим все необходимые файлы в пакет:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/building-nuget-packages-with-gui-tool/851b06b8/5@2x.png\"  alt=\"Создание nuget-пакетов при помощи GUI-утилиты\"/>\n\n\n\n\n<p>Наш nuget-пакет готов.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/building-nuget-packages-with-gui-tool/073225a0/6@2x.png\"  alt=\"Создание nuget-пакетов при помощи GUI-утилиты\"/>\n\n\n\n\n<p>Сохраним полученный пакет в файловой системе, получим <code>nupkg</code>-файл, готовый для публикации.</p>\n<h2>Публикация на сервере</h2><p>Полученный пакет можно опубликовать на сервере стандартными средствами <code>nuget.exe</code>, либо при помощи той же утилиты. Для этого используем пункт меню <code>File</code> → <code>Publish</code>:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/building-nuget-packages-with-gui-tool/112f6a30/7@2x.png\"  alt=\"Создание nuget-пакетов при помощи GUI-утилиты\"/>\n\n\n\n\n<p>В открывшемся диалоговом окне укажем адрес нашего сервера и ключ для публикации. После этого шага проект будет опубликован и его можно использовать в ваших проектах.</p>",
            "tags": [
                "Nuget"
            ],
            "banner_image": "https://blog.zwezdin.com/2014/building-nuget-packages-with-gui-tool/82f648ae/building_nuget-packages-with-gui-tool@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2014/aspnet-mvc-throttling/",
            "url": "https://blog.zwezdin.com/2014/aspnet-mvc-throttling/",
            "title": "Регулирование нагрузки на веб-приложение ASP.NET MVC",
            "date_published": "2014-03-01T12:00:00.000Z",
            "content_html": "<p>Бывают ситуации, когда внешние ресурсы обращаются к веб-приложению слишком часто. Это могут быть краулеры или другие роботы. Если это создает проблемы для производительности приложения, то такие запросы можно ограничить. В данной статье я расскажу как это сделать для приложения <strong>ASP.NET MVC</strong>.</p>\n<p>Для того, чтобы ограничить количество запросов, обрабатываемых в единицу времени можно воспользоваться библиотекой <a href=\"https://www.nuget.org/packages/MvcThrottle/\">MvcThrottle</a>.</p>\n<p>Создадим проект и установим пакет из Nuget:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">Install-Package MvcThrottle</code></pre>\n<p>Как только пакет установлен, можно использовать фильтр <code>ThrottlingFilter</code> для регулирования нагрузки. Зарегистрируем этот фильтр глобально:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static void RegisterGlobalFilters(GlobalFilterCollection filters)\n&#123;\n  filters.Add(new ThrottlingFilter\n  &#123;\n    Policy = new ThrottlePolicy(1, null, null, null, null) &#123; IpThrottling = true &#125;\n  &#125;);\n&#125;</code></pre>\n<p>Для того, чтобы включить регулирование нагрузки для контроллера или действия его нужно разметить атрибутом <code>[EnableThrottling]</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class HomeController : Controller\n&#123;\n  [EnableThrottling]\n  public ActionResult Index()\n  &#123;\n    return View();\n  &#125;\n&#125;</code></pre>\n<p>Этого достаточно, чтобы включить регулирование нагрузки для действия контроллера ASP.NET MVC.</p>\n<p>В приведенной выше конфигурации нагрузка ограничивается одним запросом в секунду. Этот параметр можно настраивать более тонко:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static void RegisterGlobalFilters(GlobalFilterCollection filters)\n&#123;\n  filters.Add(new ThrottlingFilter\n  &#123;\n    Policy = new ThrottlePolicy(\n          perSecond: 5,\n          perHour: 200,\n          perDay: 4000) &#123; IpThrottling = true &#125;\n  &#125;);\n&#125;</code></pre>\n<p>Кроме того, эту настройку можно выполнить индивидуально для каждого действия или контроллера:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class HomeController : Controller\n&#123;\n  [EnableThrottling(PerSecond = 10)]\n  public ActionResult Index()\n  &#123;\n    return View();\n  &#125;\n&#125;</code></pre>\n<p>При превышении доступного значения, приложение отдает ошибку <code>HTTP 429</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2014/aspnet-mvc-throttling/cdfadfb4/1@2x.png\"  alt=\"Регулирование нагрузки на веб-приложение ASP.NET MVC\"/>\n\n\n\n\n<p>При желании можно настроить это поведение:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static void RegisterGlobalFilters(GlobalFilterCollection filters)\n&#123;\n  filters.Add(new ThrottlingFilter\n  &#123;\n    Policy = new ThrottlePolicy(\n          perSecond: 5,\n          perHour: 200,\n          perDay: 4000)\n    &#123;\n          IpThrottling = true\n    &#125;,\n    QuotaExceededResponseCode = HttpStatusCode.Forbidden,\n    QuotaExceededMessage = \"Try again later\"\n  &#125;);\n&#125;</code></pre>\n<p>Кроме того, можно настроить:</p>\n<ul>\n<li>собственное представление ошибки, вместо стандартной страницы IIS;</li>\n<li>белый список адресов, к которым регулирование не применяется;</li>\n<li>способ определения конечной точки;</li>\n<li>логгирование.</li>\n</ul>",
            "tags": [
                "ASP.NET"
            ],
            "banner_image": "https://blog.zwezdin.com/2014/aspnet-mvc-throttling/ea1d4efa/aspnet_mvc-throttling@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2014/tfs-2013-automatic-code-reviews/",
            "url": "https://blog.zwezdin.com/2014/tfs-2013-automatic-code-reviews/",
            "title": "Автоматические запросы CodeReview в TFS 2013",
            "date_published": "2014-02-08T12:00:00.000Z",
            "content_html": "<p><strong>TFS2013</strong> имеет очень удобный функционал <strong>Code Review</strong> - каждый член команды может сделать запрос на ревью для одного или нескольких членов команды. Мы задались вопросом можно ли такие Code Review создавать автоматически при комите в систему контроля версий.</p>\n<p>Для того, чтобы реализовать эту функциональность мы воспользовались серверными событиями (<code>TFS Server Side Events</code>). Их суть предельно проста - мы создаем объект-подписчик, метод которого будет вызываться, когда в TFS что-то происходит, и если тип события тот, который нас устраивает, то обрабатываем эту ситуацию.</p>\n<p>Для того, чтобы создать такой объект-подписчик достаточно создать новую сборку, в которой создать объект, реализующий интерфейс <code>ISubscriber</code> из пространства имен <code>Microsoft.TeamFoundation.Framework.Server</code>.</p>\n<p>Предварительно следует добавить ссылки на следующие сборки:</p>\n<ul>\n<li><code>C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\Common7\\IDE\\ReferenceAssemblies\\v2.0\\Microsoft.TeamFoundation.Client.dll</code></li>\n<li><code>C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\Common7\\IDE\\ReferenceAssemblies\\v2.0\\Microsoft.TeamFoundation.Common.dll</code></li>\n<li><code>C:\\Program Files\\Microsoft Team Foundation Server 12.0\\Tools\\Microsoft.TeamFoundation.Framework.Server.dll</code></li>\n<li><code>C:\\Program Files\\Microsoft Team Foundation Server 12.0\\Tools\\Microsoft.TeamFoundation.Server.Core.dll</code></li>\n<li><code>C:\\Program Files\\Microsoft Team Foundation Server 12.0\\Tools\\Microsoft.TeamFoundation.VersionControl.Server.dll</code></li>\n<li><code>C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\Common7\\IDE\\ReferenceAssemblies\\v2.0\\Microsoft.TeamFoundation.WorkItemTracking.Client.dll</code></li>\n<li><code>C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\Common7\\IDE\\ReferenceAssemblies\\v4.5\\Microsoft.VisualStudio.Services.WebApi.dll</code></li>\n</ul>\n<p>Эти сборки содержат необходимые нам объекты, включая интерфейс <code>ISubscriber</code>. Заглушка нашего класса будет выглядеть следующим образом:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class CodeReviewSubscriber : ISubscriber\n&#123;\n  public string Name\n  &#123;\n    get &#123; return \"Automatic Code Reviews\"; &#125;\n  &#125;\n\n  public Type[] SubscribedTypes()\n  &#123;\n    return new Type[] &#123; &#125;;\n  &#125;\n\n  public SubscriberPriority Priority\n  &#123;\n    get &#123; return SubscriberPriority.Normal; &#125;\n  &#125;\n\n  public EventNotificationStatus ProcessEvent(TeamFoundationRequestContext requestContext, NotificationType notificationType, object notificationEventArgs, out int statusCode, out string statusMessage, out Microsoft.TeamFoundation.Common.ExceptionPropertyCollection properties)\n  &#123;\n    // ...\n  &#125;\n&#125;</code></pre>\n<p>Для того, чтобы наш подписчик стал реагировать на комиты, необходимо чтобы метод <code>SubscribedTypes()</code> возвращал соответствующий тип уведомления.</p>\n<p>Итого, пустая заглушка будет выглядеть так —</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class CodeReviewSubscriber : ISubscriber\n&#123;\n  public string Name\n  &#123;\n    get &#123; return \"Automatic Code Reviews\"; &#125;\n  &#125;\n\n  public Type[] SubscribedTypes()\n  &#123;\n    return new Type[] &#123; typeof(CheckinNotification) &#125;;\n  &#125;\n\n  public SubscriberPriority Priority\n  &#123;\n    get &#123; return SubscriberPriority.Normal; &#125;\n  &#125;\n\n  public EventNotificationStatus ProcessEvent(TeamFoundationRequestContext requestContext, NotificationType notificationType, object notificationEventArgs, out int statusCode, out string statusMessage, out Microsoft.TeamFoundation.Common.ExceptionPropertyCollection properties)\n  &#123;\n    statusCode = 0;\n    statusMessage = String.Empty;\n    properties = null;\n\n    return EventNotificationStatus.ActionPermitted;\n  &#125;\n&#125;</code></pre>\n<p>Добавим логику создания запроса на ревью, получим что-то подобное на это —</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class CodeReviewSubscriber : ISubscriber\n&#123;\n  private const string LogFilePath = @\"C:\\log\\tfs.log\";\n  private const string ProjectName = @\"TestProject\";\n  private const string ReviewerName = @\"Sergey\";\n\n  public string Name\n  &#123;\n    get &#123; return \"Automatic Code Reviews\"; &#125;\n  &#125;\n\n  public Type[] SubscribedTypes()\n  &#123;\n    return new[] &#123; typeof(CheckinNotification) &#125;;\n  &#125;\n\n  public SubscriberPriority Priority\n  &#123;\n    get &#123; return SubscriberPriority.Normal; &#125;\n  &#125;\n\n  public EventNotificationStatus ProcessEvent(TeamFoundationRequestContext requestContext, NotificationType notificationType, object notificationEventArgs, out int statusCode, out string statusMessage, out Microsoft.TeamFoundation.Common.ExceptionPropertyCollection properties)\n  &#123;\n    try\n    &#123;\n      if ((notificationType == NotificationType.Notification) &amp;&amp;\n          (notificationEventArgs is CheckinNotification))\n      &#123;\n        CheckinNotification checkinNotification = (CheckinNotification)notificationEventArgs;\n\n        TeamFoundationLocationService tfsService = requestContext.GetService&lt;TeamFoundationLocationService>();\n        Uri tfsServiceUri = tfsService.GetSelfReferenceUri(requestContext, tfsService.GetDefaultAccessMapping(requestContext));\n        TfsTeamProjectCollection tfsServiceCollection = new TfsTeamProjectCollection(tfsServiceUri);\n\n\n        IIdentityManagementService identityManagementService = tfsServiceCollection.GetService&lt;IIdentityManagementService>();\n        TeamFoundationIdentity identity = identityManagementService.ReadIdentity(IdentitySearchFactor.AccountName, checkinNotification.ChangesetOwner.UniqueName, MembershipQuery.None, ReadIdentityOptions.None);\n        TfsTeamProjectCollection projectCollection = new TfsTeamProjectCollection(tfsServiceCollection.Uri, identity.Descriptor);\n\n        WorkItemStore workitemStore = projectCollection.GetService&lt;WorkItemStore>();\n\n        Project project = workitemStore.Projects[ProjectName];\n\n        CreateReviewRequest(project, checkinNotification, workitemStore);\n      &#125;\n    &#125;\n    catch (Exception ex)\n    &#123;\n      WriteLog(ex.ToString());\n    &#125;\n\n    statusCode = 0;\n    statusMessage = String.Empty;\n    properties = null;\n\n    return EventNotificationStatus.ActionPermitted;\n  &#125;\n\n  private void CreateReviewRequest(Project project, CheckinNotification checkinNotification, WorkItemStore workitemStore)\n  &#123;\n    WorkItemType type = project.WorkItemTypes[\"Code Review Response\"];\n    WorkItem workItem = new WorkItem(type)\n    &#123;\n      Title = checkinNotification.Comment\n    &#125;;\n    workItem.Fields[\"System.AssignedTo\"].Value = ReviewerName;\n    workItem.Fields[\"System.State\"].Value = \"Requested\";\n    workItem.Fields[\"System.Reason\"].Value = \"New\";\n\n    workItem.Validate();\n    workItem.Save();\n\n    int workitemId = workItem.Id;\n\n    type = project.WorkItemTypes[\"Code Review Request\"];\n    workItem = new WorkItem(type) &#123;Title = checkinNotification.Comment&#125;;\n    workItem.Fields[\"System.AssignedTo\"].Value = checkinNotification.ChangesetOwner.DisplayName;\n    workItem.Fields[\"Microsoft.VSTS.CodeReview.ContextType\"].Value = \"Changeset\";\n    workItem.Fields[\"Microsoft.VSTS.CodeReview.Context\"].Value = checkinNotification.Changeset;\n    workItem.Fields[\"System.AreaPath\"].Value = project.Name;\n    workItem.Fields[\"System.IterationPath\"].Value = project.Name;\n    workItem.Fields[\"System.State\"].Value = \"Requested\";\n    workItem.Fields[\"System.Reason\"].Value = \"New\";\n    WorkItemLinkTypeEnd linkTypeEnd = workitemStore.WorkItemLinkTypes.LinkTypeEnds[\"Child\"];\n    workItem.Links.Add(new RelatedLink(linkTypeEnd, workitemId));\n\n    workItem.Validate();\n\n    workItem.Save();\n  &#125;\n\n  private void WriteLog(string message)\n  &#123;\n    File.AppendAllText(LogFilePath, message);\n    File.AppendAllText(LogFilePath, Environment.NewLine);\n  &#125;\n&#125;</code></pre>\n<p>Этот код далек от совершенства, но показывает суть происходящего процесса.</p>\n<p>Теперь, когда наш подписчик готов, необходимо вклинить его в процесс работы TFS. Для этого полученную сборку следует поместить в папку <code>C:\\Program Files\\Microsoft Team Foundation Server 12.0\\Application Tier\\Web Services\\bin\\Plugins</code> (на сервере).</p>\n<p>Теперь для каждого комита в TFS вы получите запрос на ревью кода.</p>",
            "tags": [
                "Team Foundation Server",
                "Visual Studio Team System"
            ],
            "banner_image": "https://blog.zwezdin.com/2014/tfs-2013-automatic-code-reviews/d6f310c6/tfs_2013-automatic-code-reviews@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2013/emitmapper/",
            "url": "https://blog.zwezdin.com/2013/emitmapper/",
            "title": "Автоматический маппинг объектов в .NET — Emitmapper",
            "date_published": "2013-12-25T12:00:00.000Z",
            "content_html": "<p>Главным отличием <strong>EmitMapper</strong> от <a href=\"/2013/automapper\">AutoMapper</a> по заявлению разработчиков заключается в использовании механизма Reflection Emit. Этот механизм позволяет строить IL-код на лету использовать его. В результате EmitMapper работает на несколько порядков быстрее.</p>\n<p>Разработчики <strong>EmitMapper</strong> написали простенький benchmark, в котором они сравнивают работу EmitMapper, AutoMapper и маппинг объектов руками. Вот что у них получилось на тестовом прогоне:</p>\n<ul>\n<li>Handwritten Mapper: <strong>80 мс.</strong></li>\n<li>EmitMapper: <strong>487 мс.</strong></li>\n<li>AutoMapper: <strong>28107 мс.</strong></li>\n</ul>\n<p>Цифры говорят сами за себя и показывают причину, почему стоит рассмотреть также и EmitMapper.</p>\n<p>Несмотря на разницу в подходах, AutoMapper и EmitMapper очень похожи в идеологии. Обе библиотеки выполняют маппинг одного объекта в другой.</p>\n<p>Чтобы работать с EmitMapper, установим в проект библиотеку:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">Install-Package EmitMapper</code></pre>\n<p>Основным объектом, с которым мы будем работать - <code>ObjectMapperManager</code>.</p>\n<p>Используем для работы <a href=\"/2013/automapper\">всё те же объекты</a> —</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class UserDto\n&#123;\n  public string FirstName &#123; get; set; &#125;\n\n  public string LastName &#123; get; set; &#125;\n\n  public string Email &#123; get; set; &#125;\n\n&#125;\n\npublic class User\n&#123;\n  public string FullName &#123; get; set; &#125;\n\n  public string Email &#123; get; set; &#125;\n&#125;</code></pre>\n<p>Настроим конфигурацию:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">DefaultMapConfig config = new DefaultMapConfig();\nconfig.ConvertUsing((UserDto source) =>\n  new User\n  &#123;\n      FullName = source.FirstName + \" \" + source.LastName,\n      Email = source.Email\n  &#125;);</code></pre>\n<p>Тогда маппинг можно выполнить так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">var user = ObjectMapperManager\n  .DefaultInstance\n  .GetMapper&lt;UserDto, User>(config)\n  .Map(new UserDto\n  &#123;\n      FirstName = \"Ivan\",\n      LastName = \"Ivanov\",\n      Email = \"ivan@ivanon.ru\"\n  &#125;);</code></pre>\n<p>Как видно, синтаксис предельно похож на синтаксис <a href=\"/2013/automapper\">AutoMapper’а</a>.</p>\n<p>На практике, лучше всего создать слой абстракции, чтобы не зависеть от конкретной реализации маппера.</p>",
            "tags": [
                "C#",
                ".NET"
            ],
            "banner_image": "https://blog.zwezdin.com/2013/emitmapper/475b74ac/emitmapper@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2013/how-to-setup-nuget-server/",
            "url": "https://blog.zwezdin.com/2013/how-to-setup-nuget-server/",
            "title": "Как настроить собственный Nuget-сервер",
            "date_published": "2013-12-23T12:00:00.000Z",
            "content_html": "<p>Многие компании сталкиваются с необходимостью иметь собственный локальный Nuget-сервер для хостинга внтуренних разработок и фреймворков. Одним из наиболее простых вариантов является общая сетевая папка, где могут хранится все пакеты. Однако, это не всегда удобно и более правильным решением будет хостинг веб-приложения, которое и будет выполнять роль Nuget-сервера.</p>\n<p>Чтобы создать Nuget-сервер необходимо выполнить несколько несложных шагов.</p>\n<h2>Создание нового веб-приложения и добавление Nuget-библиотек</h2><p>Для начала создадим новое пустое ASP.NET приложение и установим в него пакет <code>NuGet.Server</code>.</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">Install-Package NuGet.Server</code></pre>\n<p>Заготовка для нашего Nuget-сервера готова.</p>\n<h2>Настройка параметров сервера</h2><p>Перед использованием Nuget, необходимо задать несколько параметров в конфигурационном файле <code>web.config</code>. Все параметры определены в секции <code>appSettings</code>.</p>\n<p>Один из обязательных параметров - это <code>packagesPath</code>, он указывает на папку, в которой будут хранится все пакеты для данного сервера.</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">&lt;appSettings>\n  &lt;add key=\"apiKey\" value=\"C:\\Packages\\\" />\n&lt;/appSettings></code></pre>\n<p>Другие два важных параметра —</p>\n<ul>\n<li>Флаг <code>requireApiKey</code>, который указывает на необходимость предоставления ключа для управления пакетами на сервере. Если этот параметр установлен в <code>true</code>, то сервер в обязательном порядке будет требовать предоставление ключа.</li>\n<li>Ключ <code>apiKey</code> - собственно, сам ключ, который будет использоваться для взаимодействия с сервером. Если флаг <code>requireApiKey</code> будет установлен в <code>true</code>, а значение <code>apiKey</code> будет пустым, то публикация пакетов на сервер будет заблокирована.</li>\n</ul>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;appSettings>\n  &lt;add key=\"requireApiKey\" value=\"true\" />\n  &lt;add key=\"apiKey\" value=\"1ab183523cx7usf\" />\n&lt;/appSettings></code></pre>\n<h2>Запуск сервера</h2><p>Наш Nuget-сервер готов и его можно запускать как обычное ASP.NET приложение.</p>\n<p>При запуске приложения локально, можно обратиться по адресу <code>http://localhost:[port]/nuget</code> и получить текущую ленту пакетов.</p>\n<p>Для публикации своих пакетов на сервер можно воспользоваться следующим синтаксисом утилиты <code>nuget.exe</code>:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">nuget push &#123;package file&#125; -s http://localhost:[port]/ &#123;apikey&#125;</code></pre>\n<h2>Публикация</h2><p>Когда сервер настроен и протестирован, его следует опубликовать на сервер IIS. Процедура публикации не отличается от любого другого веб-приложения. Например, вы можете воспользоваться встроенным средством публикации веб-приложений в Visual Studio.</p>\n<h2>Подключение к Visual Studio</h2><p>Теперь, для того, чтобы использовать только что созданный сервер в Visual Stuido, необходимо в настройках VS указать адрес этого Nuget-сервера. Для этого следует открыть настройки в меню Visual Studio <code>Tools</code> → <code>Options</code> → <code>Package Manager</code> → <code>Package Sources</code> и добавить там адрес только что созданного Nuget-сервера.</p>\n<p>Ваш собственный Nuget-сервер готов к использованию.</p>",
            "tags": [
                "Nuget"
            ],
            "banner_image": "https://blog.zwezdin.com/2013/how-to-setup-nuget-server/42e5fab9/how_to-setup-nuget-server@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2013/automapper/",
            "url": "https://blog.zwezdin.com/2013/automapper/",
            "title": "Автоматический маппинг объектов в .NET — Automapper",
            "date_published": "2013-12-07T12:00:00.000Z",
            "content_html": "<p>При разработке приложений частенько приходится сталкиваться с задачей преобразования одного объекта в другой. Этот процесс можно немного облегчить используя уже готовые библиотеки. Здесь я приведу пример на основе уже достаточно известной библиотеки Automapper.</p>\n<p>Прежде чем приступить к рассмотрению маппига, хочу привести несколько ситуаций когда это может потребоваться:</p>\n<ul>\n<li>преобразование иерархической модели объектов в плоскую;</li>\n<li>получение проекций объектов (из доменного объекта генерируем ViewModel);</li>\n<li>получение доменного объекта из DTO;</li>\n<li>преобразование доменных объектов в сериализуемые объекты для передачи через какую-либо коммуникационную среду.</li>\n</ul>\n<p>Иными словами маппинг нужен там, где объект переходит за границы приложения/уровня/слоя/компонента/…</p>\n<p>Для того, чтобы начать использовать automapper, установим библиотеку из Nuget:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">Install-Package AutoMapper</code></pre>\n<p>Для эксперимента создадим два объекта - <code>UserDto</code> и <code>User</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class UserDto\n&#123;\n  public string FirstName &#123; get; set; &#125;\n\n  public string LastName &#123; get; set; &#125;\n\n  public string Email &#123; get; set; &#125;\n\n&#125;\n\npublic class User\n&#123;\n  public string FullName &#123; get; set; &#125;\n\n  public string Email &#123; get; set; &#125;\n&#125;</code></pre>\n<p>При маппинге из <code>UserDto</code> в <code>User</code>, нам нужно заполнить поле <code>FullName</code> путем объединения свойств <code>FirstName</code> и <code>LastName</code> исходного объекта.</p>\n<p>Используя объект <code>Mapper</code>, создадим конфигурацию маппера:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">Mapper.CreateMap&lt;UserDto, User>()\n     .ForMember(x => x.FullName,\n          x => x.MapFrom(m => m.FirstName + \" \" + m.LastName))\n     .ForMember(x => x.Email,\n          x => x.MapFrom(m => m.Email));</code></pre>\n<p>Запись эта интуитивно понятна и задает правила каким образом нужно маппить один объект в другой.</p>\n<p>Маппинг выполняется также в одну строчку:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">User user = Mapper.Map&lt;User>(new UserDto\n&#123;\n     FirstName = \"Ivan\",\n     LastName = \"Ivanov\",\n     Email = \"ivan@ivanon.ru\"\n&#125;);</code></pre>\n<p>Что интересно, в данном примере правило маппинга для свойства <code>Email</code> избыточно, поскольку Automapper сам сопоставляет поля по имени и типу. Соответственно это правила можно переписать следующим образом:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">Mapper.CreateMap&lt;UserDto, User>()\n     .ForMember(x => x.FullName,\n          x => x.MapFrom(m => m.FirstName + \" \" + m.LastName));</code></pre>\n<p>Результат при этом не изменится.</p>\n<p>Это самый простой способ использования библиотеки <strong>Automapper</strong>, вот более полный список возможностей библиотеки:</p>\n<ul>\n<li>Flattening (преобразование сложной иерархической объектной структуры в простой объект с несколькими полями)</li>\n<li>Projection (получение из исходного объекта его проекции, пример выше)</li>\n<li>Работа со списками</li>\n<li>Маппинг вложенных объектов</li>\n<li>Маппинги с условиями</li>\n</ul>\n<p>Следует отметить, что регистрация правил действует в рамках домена приложения (<code>AppDomain</code>). Если ваше приложение использует несколько доменов, то в каждом из них следует зарегистрировать эти правила.</p>",
            "tags": [
                "C#",
                ".NET"
            ],
            "banner_image": "https://blog.zwezdin.com/2013/automapper/cca0b6d3/automapper@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2013/handle-unknown-actions-at-aspnet-mvc/",
            "url": "https://blog.zwezdin.com/2013/handle-unknown-actions-at-aspnet-mvc/",
            "title": "Обработка несуществующих действий контроллера ASP.NET MVC",
            "date_published": "2013-10-27T12:00:00.000Z",
            "content_html": "<p>При получении HTTP-запроса к несуществующему действию контроллера <strong>ASP.NET MVC</strong>, можно по-разному обрабатывать эту ситуацию. Одним из вариантов является переопределение метода <code>HandleUnknownAction</code>.</p>\n<p>Допустим в нашем приложении есть контроллер:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class HomeController : Controller\n&#123;\n  public ActionResult Index()\n  &#123;\n    return View();\n  &#125;\n&#125;</code></pre>\n<p>При стандартной настройке маршрутов мы можем обратиться к контроллеру по адресу <code>/home/index</code> и страница отобразится пользователю.</p>\n<p>В случае, если мы обратимся по адресу, соответствующему несуществующему контроллеру, мы получим <code>HTTP 404</code>.</p>\n<p>Чтобы исправить эту ситуацию, переопределим метод <code>HandleUnknownAction</code> для данного контроллера:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class HomeController : Controller\n&#123;\n  public ActionResult Index()\n  &#123;\n    return View();\n  &#125;\n\n  protected override void HandleUnknownAction(string actionName)\n  &#123;\n    View(\"Index\").ExecuteResult(ControllerContext);\n  &#125;\n&#125;</code></pre>\n<p>При такой реализации, обращаясь к несуществующему действию, мы получим страницу Index. Каким образом построить дальнейшую логику будет зависеть от конкретного приложения.</p>",
            "tags": [
                "ASP.NET"
            ],
            "banner_image": "https://blog.zwezdin.com/2013/handle-unknown-actions-at-aspnet-mvc/f764e161/handle_unknown-actions-at-aspnet-mvc@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2013/pretty-urls-for-aspnet-mvc-apps/",
            "url": "https://blog.zwezdin.com/2013/pretty-urls-for-aspnet-mvc-apps/",
            "title": "Красивые URL в приложениях ASP.NET MVC",
            "date_published": "2013-10-26T12:00:00.000Z",
            "content_html": "<p>Каждому разработчику на ASP.NET MVC известно, что платформа имеет мощную и удобную систему определения маршрутов в приложении. На основе этих маршрутов выбираются подходящие контроллеры и действия, а также генерируются адреса (URL) внтури приложения. Что меня всегда угнетало, это то, что имена контроллеров/действий используются генерируются в адресе “как есть”. В итоге пользователь видит что-то уродливое вроде <code>http://myapp.ru/Contacts/List</code>. Рассмотрим способ как это можно исправить.</p>\n<p>В данном случае речь идет об адресах, которые мы генерируем в представлениях при использовании стандартных классов-хелперов, например таким образом:</p>\n<pre class=code language-html><code class=\"code__container language-html\">&lt;a href=\"@Url.Action(\"List\",\"Contacts\")\">Contacts&lt;/a></code></pre>\n<p>Как по мне, так URL в нижнем регистре выглядят более привлекательно. Говорят, так же, что это важно для поисковой оптимизации (SEO).</p>\n<p>Существует два способа решения проблемы — простой (доступный в .NET 4.5 и выше) и сложный (доступный до версии 4.5).</p>\n<h2>Решение проблемы в .NET 4.5</h2><p>Решение указанной проблемы в .NET 4.5 лежит на поверхности. Для этого при определении маршрутов достаточно установить свойство <code>LowercaseUrls</code> в <code>true</code> у объекта <code>RouteCollection</code>. В дополнение в .NET 4.5 появилась опция <code>AppendTrailingSlash</code>, при помощи которой мы можем принудительно добавлять слеш в конце.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static void RegisterRoutes(RouteCollection routes)\n&#123;\n  routes.IgnoreRoute(\"&#123;resource&#125;.axd/&#123;*pathInfo&#125;\");\n\n  routes.MapRoute(\n      name: \"Default\",\n      url: \"&#123;controller&#125;/&#123;action&#125;/&#123;id&#125;\",\n      defaults: new &#123; controller = \"Home\", action = \"Index\", id = UrlParameter.Optional &#125;\n  );\n\n  routes.LowercaseUrls = true;\n  routes.AppendTrailingSlash = true;\n&#125;</code></pre>\n<h2>Решение проблемы для проект .NET версии ранее 4.5</h2><p>В этом случае все немного сложнее, поскольку в то время эта функциональность ещё не была реализована. Решением является реализация этого механизма самостоятельно.</p>\n<p>Как известно, в коллекцию <code>RouteCollection</code> добавляются объекты типа Route, которые и содержат информацию о маршруте. Переопределив поведение этого объекта, можно влиять на то, как будут генерироваться URL. Создадим такой класс:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class LowercaseRoute : System.Web.Routing.Route\n&#123;\n  public LowercaseRoute(string url, IRouteHandler routeHandler)\n      : base(url, routeHandler)\n  &#123;\n  &#125;\n\n  public LowercaseRoute(string url, RouteValueDictionary defaults, IRouteHandler routeHandler)\n      : base(url, defaults, routeHandler)\n  &#123;\n  &#125;\n\n  public LowercaseRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints,\n                                IRouteHandler routeHandler)\n      : base(url, defaults, constraints, routeHandler)\n  &#123;\n  &#125;\n\n  public LowercaseRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints,\n                                RouteValueDictionary dataTokens, IRouteHandler routeHandler)\n      : base(url, defaults, constraints, dataTokens, routeHandler)\n  &#123;\n  &#125;\n\n  public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)\n  &#123;\n      VirtualPathData path = base.GetVirtualPath(requestContext, values);\n\n      if (path != null)\n            path.VirtualPath = path.VirtualPath.ToLowerInvariant();\n\n      return path;\n  &#125;\n&#125;</code></pre>\n<p>Теперь можно добавить этот маршрут в коллекцию <code>RouteCollection</code> простым вызовом метода <code>Add</code>. Чтобы это можно было делать как и раньше (путем вызова метода <code>MapRoute</code>) создадим расширение для <code>RouteCollection</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class LowercaseRouteExtensions\n&#123;\n  public static Route MapRouteLowercase(this RouteCollection routes,\n      string name, string url, object defaults)\n  &#123;\n    var route = new LowercaseRoute(url,\n          CreateRouteValueDictionary(defaults),\n          new MvcRouteHandler());\n\n    routes.Add(route);\n\n    return route;\n  &#125;\n\n  private static RouteValueDictionary CreateRouteValueDictionary(object values)\n  &#123;\n    IDictionary&lt;string object=\"\" ,=\"\"> dictionary = values as IDictionary&lt;string object=\"\" ,=\"\">;\n\n    if (dictionary != null)\n    &#123;\n          return new RouteValueDictionary(dictionary);\n    &#125;\n\n    return new RouteValueDictionary(values);\n  &#125;\n&#125;</code></pre>\n<p>Всё, что делает <code>MapRouteLowercase</code> - это создает новый объект-наследник <code>Route</code> и добавляет его в <code>RouteCollection</code>. Этот класс можно расширить и другими методами с различным количеством параметров. Это не трудно сделать, поэтому полный листинг здесь не привожу.</p>\n<p>Настройка маршрутов теперь должна быть выполнена теперь при помощи только что созданного метода <code>MapRouteLowercase</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static void RegisterRoutes(RouteCollection routes)\n&#123;\n  routes.IgnoreRoute(\"&#123;resource&#125;.axd/&#123;*pathInfo&#125;\");\n\n  routes.MapRouteLowercase(\n      name: \"Default\",\n      url: \"&#123;controller&#125;/&#123;action&#125;/&#123;id&#125;\",\n      defaults: new &#123; controller = \"Home\", action = \"Index\", id = UrlParameter.Optional &#125;\n  );\n&#125;</code></pre>\n<p>Теперь наши приложения будут иметь красивые URL и чуть более дружественны к поисковой оптимизации.</p>",
            "tags": [
                "ASP.NET"
            ],
            "banner_image": "https://blog.zwezdin.com/2013/pretty-urls-for-aspnet-mvc-apps/b91a702e/pretty_urls-for-aspnet-mvc-apps@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2013/stack-trace/",
            "url": "https://blog.zwezdin.com/2013/stack-trace/",
            "title": "Обработка стека вызова исключения .NET",
            "date_published": "2013-09-30T12:00:00.000Z",
            "content_html": "<p>При генерации исключения в .NET мы имеем достаточно подробную информацию о том, что произошло. Одна из важнейших частей — стек вызовов. В самом исключении стек вызовов представлен в виде строки. Однако, в .NET существует объект <code>StackTrace</code>, который позволяет работать со стеком вызовов более удобно.</p>\n<p>Чтобы обработать стек вызовов, при перехвате исключения создадим объект <code>StackTrace</code>, в конструкторе которого передадим исключение. В этом случае мы сможем отследить все шаги, которые предшествовали генерации исключения.</p>\n<p>Чтобы посмотреть как это работает создадим класс-заглушку, который будет генерировать для нас исключение.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class SomeWorker\n&#123;\n     public static void Start()\n     &#123;\n          DoSomething1();\n     &#125;\n     public static void DoSomething1()\n     &#123;\n          DoSomething2();\n     &#125;\n     public static void DoSomething2()\n     &#123;\n          DoSomething3();\n     &#125;\n     public static void DoSomething3()\n     &#123;\n          DoSomething4();\n     &#125;\n     public static void DoSomething4()\n     &#123;\n          DoSomething5();\n     &#125;\n     public static void DoSomething5()\n     &#123;\n          throw new ArgumentException();\n     &#125;\n&#125;</code></pre>\n<p>Чтобы обработать стек вызовов, при перехвате исключения создадим объект <code>StackTrace</code>, в конструкторе которого передадим исключение. В этом случае мы сможем отследить все шаги, которые предшествовали генерации исключения.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">try\n&#123;\n     SomeWorker.Start();\n&#125;\ncatch (Exception ex)\n&#123;\n     var stack = new StackTrace(ex);\n     foreach (StackFrame frame in stack.GetFrames())\n     &#123;\n          Console.WriteLine(frame.GetMethod());\n     &#125;\n&#125;</code></pre>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/stack-trace/46ac9ead/1@2x.png\"  alt=\"Обработка стека вызова исключения .NET\"/>\n\n\n\n\n<p>Использование объекта <code>StackTrace</code> бывает удобно, если вы пытаетесь анализировать ошибки более глубоко и системно. Например, таким образом можно собрать информацию по всем классам/методам, которые больше всего подвержены генерации необработанных исключений.</p>",
            "tags": [
                "C#",
                ".NET"
            ],
            "banner_image": "https://blog.zwezdin.com/2013/stack-trace/3c13df22/stack_trace@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2013/visual-studio-debugger/",
            "url": "https://blog.zwezdin.com/2013/visual-studio-debugger/",
            "title": "Изменение поведения отладчика в Visual Studio",
            "date_published": "2013-09-28T12:00:00.000Z",
            "content_html": "<p>Какое неудобство настигает нас при отладке приложения в Visual Studio? Для меня всегда было неудобно осуществлять навигацию по свойствам объекта, когда нужна какая-то информация. В этой статье я приведу набор атрибутов, которые позволяют упростить данный процесс.</p>\n<p>Для того, чтобы упростить этот процесс можно использовать набор предопределенных атрибутов.</p>\n<h2><code>DebuggerDisplay</code></h2><p>Позволяет задать строку для класса или свойства, которая будет отображаться в отладчике.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Person\n&#123;\n     public string FirstName &#123; get; set; &#125;\n\n     public string LastName &#123; get; set; &#125;\n\n     public int Age &#123; get; set; &#125;\n&#125;</code></pre>\n<p>По умолчанию, при просмотре значения отладчик отобразит полное название класса:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/visual-studio-debugger/1b9a79b0/1@2x.png\"  alt=\"Изменение поведения отладчика в Visual Studio\"/>\n\n\n\n\n<p>Добавим атрибут <code>DebuggerDisplay</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[DebuggerDisplay(\"&#123;FirstName&#125; &#123;LastName&#125;, &#123;Age&#125; years old\")]\npublic class Person\n&#123;\n     public string FirstName &#123; get; set; &#125;\n\n     public string LastName &#123; get; set; &#125;\n\n     public int Age &#123; get; set; &#125;\n&#125;</code></pre>\n<p>Тогда отображение в отладчике изменится следующим образом:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/visual-studio-debugger/b0ef4365/2@2x.png\"  alt=\"Изменение поведения отладчика в Visual Studio\"/>\n\n\n\n\n<p>Аналогичным образом можно настроить отображение значений свойств:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[DebuggerDisplay(\"&#123;FirstName&#125; &#123;LastName&#125;, &#123;Age&#125; years old\")]\npublic class Person\n&#123;\n     public string FirstName &#123; get; set; &#125;\n\n     public string LastName &#123; get; set; &#125;\n\n     [DebuggerDisplay(\"&#123;Age&#125; years old\")]\n     public int Age &#123; get; set; &#125;\n&#125;</code></pre>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/visual-studio-debugger/3b1e236c/3@2x.png\"  alt=\"Изменение поведения отладчика в Visual Studio\"/>\n\n\n\n\n<h2><code>DebuggerBrowsable</code></h2><p>Позволяет управлять поведением отладчика, при отображении свойства. В конструкторе должен передаваться параметр <code>DebuggerBrowsableState</code>, который принимает одно из следующих состояний:</p>\n<ul>\n<li><strong>Collapsed</strong> - свойство будет отображаться свернутым, для просмотра его содержимого его нужно развернуть. Это поведение по-умолчанию.</li>\n<li><strong>Never</strong> - свойство не будет отображаться в отладчике.</li>\n<li><strong>RootHidden</strong> - применимо для свойств, содержащих коллекции. Если задана эта опция, то само свойство будет скрыто, а вместо него будут отображены элементы массива.</li>\n</ul>\n<p>На практике использование этого атрибута выглядит так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Person\n&#123;\n     public string FirstName &#123; get; set; &#125;\n\n     public string LastName &#123; get; set; &#125;\n\n     [DebuggerBrowsable(DebuggerBrowsableState.Never)]\n     public int Age &#123; get; set; &#125;\n\n     [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]\n     public string[] Phones &#123; get; set; &#125;\n&#125;</code></pre>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/visual-studio-debugger/69c04fee/4@2x.png\"  alt=\"Изменение поведения отладчика в Visual Studio\"/>\n\n\n\n\n<h2><code>DebuggerTypeProxy</code></h2><p>Этот способ позволяет более радикально изменить способ отображения информации об объекте.</p>\n<p>Для того, чтобы использовать атрибут <code>DebuggerTypeProxy</code>, необходимо создать класс, принимающий в конструкторе отображаемый объект. Свойства этого класса и будут отображаться в отладчике вместо оригинального класса. При этом содержимое исходного класса будет отображаться в ветке <code>Raw View</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[DebuggerTypeProxy(typeof(PersonVisualizer))]\npublic class Person\n&#123;\n     public string FirstName &#123; get; set; &#125;\n\n     public string LastName &#123; get; set; &#125;\n\n     public int Age &#123; get; set; &#125;\n&#125;\n\npublic class PersonVisualizer\n&#123;\n     private readonly Person _person;\n\n     public PersonVisualizer(Person person)\n     &#123;\n          _person = person;\n     &#125;\n\n     public string FullName\n     &#123;\n          get &#123; return _person.FirstName + \" \" + _person.LastName; &#125;\n     &#125;\n&#125;</code></pre>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/visual-studio-debugger/f2518b85/5@2x.png\"  alt=\"Изменение поведения отладчика в Visual Studio\"/>\n\n\n\n\n<h2><code>DebuggerVisualizer</code></h2><p>Если все предыдущие способы позволяли повлиять на отображение значения в окне самого отладчика, то этот способ позволяет определить собственный пользовательский интерфейс для отображения данных. Такой способ может быть полезен при отображении сложных типов.</p>\n<p>Для того, чтобы воспользоваться этим способом, необходимо подключить к проекту сборку <code>Microsoft.VisualStudio.DebuggerVisualizers</code>.</p>\n<p>Следующим шагом будет создание объекта, ответственного за отображение отладочной информации. Для этого нужно создать класс-наследник <code>DialogDebuggerVisualizer</code>. В этом классе следует переопределить метод Show, который принимает контекст для отображения окна, а также объект, позволяющий получить доступ к исходному объекту.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class PersonVisualizer : DialogDebuggerVisualizer\n&#123;\n     protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)\n     &#123;\n\n     &#125;\n&#125;</code></pre>\n<p>Здесь есть одно ограничение, которое накладывается на исходный объект - он должен быть сериализуемый (помечен атрибутом <code>Serializable</code>), для того, чтобы его можно было переслать за рамки приложения. Внутри метода <code>Show</code> пишем код для создания пользовательского интерфейса и отображаем данные:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class PersonVisualizer : DialogDebuggerVisualizer\n&#123;\n     protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)\n     &#123;\n          Person data = (Person)objectProvider.GetObject();\n\n          Form form = new Form();\n\n          form.Controls.Add(new Label\n          &#123;\n               Dock = DockStyle.Top,\n               Text = data.FirstName\n          &#125;);\n\n          form.Controls.Add(new Label\n          &#123;\n               Dock = DockStyle.Top,\n               Text = data.LastName\n          &#125;);\n\n          form.Controls.Add(new Label\n          &#123;\n               Dock = DockStyle.Top,\n               Text = data.Age.ToString()\n          &#125;);\n\n          windowService.ShowDialog(form);\n     &#125;\n&#125;</code></pre>\n<p>Последнее - сообщаем компилятору о том, какой класс отладчик может использовать для отображения информации об объекте:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[DebuggerVisualizer(typeof(PersonVisualizer))]\n[Serializable]\npublic class Person\n&#123;\n     public string FirstName &#123; get; set; &#125;\n\n     public string LastName &#123; get; set; &#125;\n\n     public int Age &#123; get; set; &#125;\n&#125;</code></pre>\n<p>Теперь при запуске отладчика, напротив значения появится кнопка.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/visual-studio-debugger/4403087c/6@2x.png\"  alt=\"Изменение поведения отладчика в Visual Studio\"/>\n\n\n\n\n<p>При нажатии на ней отобразится созданная форма.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/visual-studio-debugger/ca8c4b71/7@2x.png\"  alt=\"Изменение поведения отладчика в Visual Studio\"/>\n\n\n\n\n<h2><code>DebuggerHidden</code> и <code>DebuggerStepThrough</code></h2><p>Позволяет скрыть от отладчика код при выполнении пошаговой отладки.</p>\n<p>Если вы установите точку останова в методе, помеченноматрибутом <code>DebuggerHidden</code>, то точка останова не сработает. Аналогичное поведение будет при использовании команды отладчика <code>Step Into</code>.</p>\n<p>При использовании атрибута <code>DebuggerStepThrough</code>, точки останова будут срабатывать, но команда  отладчика <code>Step Into</code> будет игнорировать метод.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Person\n&#123;\n     public string FirstName &#123; get; set; &#125;\n\n     public string LastName &#123; get; set; &#125;\n\n     public int Age &#123; get; set; &#125;\n\n     [DebuggerHidden]\n     public void Print()\n     &#123;\n     &#125;\n\n     [DebuggerStepThrough]\n     public void Display()\n     &#123;\n     &#125;\n&#125;</code></pre>\n<p>Аналогично можно работать с get- и set-методами свойств.</p>\n<h2><code>Conditional</code></h2><p>Этот атрибут не относится к отладчику, а скорее влияет на компиляцию кода, но также бывает полезным при отладке.</p>\n<p>Этим атрибутом можно разметить метод и указать некоторое условие, аналогично тому, как это делается в директивах препроцессора. Если условие сработало, то указанный метод выполнится; если нет, то метод будет исключен при компиляции кода.</p>\n<p>Например, следующий метод будет запускаться только в случае, если приложение построено в конфигурации <code>Debug</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Person\n&#123;\n     public string FirstName &#123; get; set; &#125;\n\n     public string LastName &#123; get; set; &#125;\n\n     public int Age &#123; get; set; &#125;\n\n     [Conditional(\"DEBUG\")]\n     public void Print()\n     &#123;\n          Console.WriteLine(LastName);\n     &#125;\n&#125;</code></pre>\n<p>Существуют ограничения, которые накладываются на методы, с которыми может использоваться указанный атрибут:</p>\n<ul>\n<li>Метод должен возвращать <code>void</code>.</li>\n<li>Метод не является переопределением метода базового класса.</li>\n<li>Метод не должен являться методом реализуемого интерфейса.</li>\n</ul>\n<p>Эти нехитрые инструменты позволят чувствовать себя более комфортно при отладке кода. Особенно полезно использовать данные приемы, если вы разрабатываете код, который будет повторно использоваться многократно.</p>",
            "tags": [
                "C#",
                ".NET",
                "Visual Studio"
            ],
            "banner_image": "https://blog.zwezdin.com/2013/visual-studio-debugger/d1527e16/visual_studio-debugger@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2013/ajax-errors-handling-aspnet-mvc/",
            "url": "https://blog.zwezdin.com/2013/ajax-errors-handling-aspnet-mvc/",
            "title": "Корректная обработка проблем аутентификации AJAX-запросов для приложений ASP.NET MVC",
            "date_published": "2013-09-18T12:00:00.000Z",
            "content_html": "<p>Для современных веб-приложений стало уже нормой использование AJAX при создании пользовательских интерфейсов. Однако, из-за этого, порой, возникают дополнительные сложности. Часто эти сложности связаны с аутентификацией и процессом обработки таких запросов на клиенте.</p>\n<h2>Проблема</h2><p>Предположим, что наше веб-приложение при помощи jQuery обращается к серверу и получает оттуда данные в виде JSON.</p>\n<p><strong>Сервер:</strong></p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[HttpPost]\npublic ActionResult GetData()\n&#123;\n  return Json(new\n  &#123;\n    Items = new[]\n    &#123;\n      \"Li Chen\",\n      \"Abdullah Khamir\",\n      \"Mark Schrenberg\",\n      \"Katy Sullivan\",\n      \"Erico Gantomaro\",\n    &#125;\n  &#125;);\n&#125;</code></pre>\n<p><strong>Клиент:</strong></p>\n<pre class=code language-javascript><code class=\"code__container language-javascript\">var $list = $(\"#list\");\nvar $status = $(\"#status\");\n$list.empty();\n$status.text(\"Loading...\");\n\n$.post(\"/home/getdata\")\n  .always(function() &#123;\n    $status.empty();\n  &#125;)\n  .success(function(data) &#123;\n    for (var i = 0; i &lt; data.Items.length; i++) &#123;\n      $list.append($(\"&lt;li/>\").text(data.Items[i]));\n    &#125;\n  &#125;);</code></pre>\n<p>Логика предельно простая и понятная. Теперь добавим в наше приложение механизмы аутентификации. С этим опять-таки все довольно просто – пользуемся старым-добрым механизмом <code>FormsAuthentication</code> и атрибутом <code>Authorize</code> в ASP.NET MVC. Код нашего контроллера при этом поменяется так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[HttpPost]\n[Authorize]\npublic ActionResult GetData()\n&#123;\n  return Json(new\n  &#123;\n    Items = new[]\n    &#123;\n      \"Li Chen\",\n      \"Abdullah Khamir\",\n      \"Mark Schrenberg\",\n      \"Katy Sullivan\",\n      \"Erico Gantomaro\",\n    &#125;\n  &#125;);\n&#125;</code></pre>\n<p>Опять же – проблем с этим кодом нет. После аутентификации пользователь видит страницу и может получать данные как и раньше. Однако, проблема в том, что если время аутентификации истечёт (из-за неактивности пользователя) или пользователь выйдет из системы (например, в другой вкладке браузера), то сервер в ответ на обращение к действию контроллеру вернет.. <code>HTTP 302 Found</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/ajax-errors-handling-aspnet-mvc/0132fb1b/1@2x.png\"  alt=\"Корректная обработка проблем аутентификации AJAX-запросов для приложений ASP.NET MVC\"/>\n\n\n\n\n<p>Очевидно, что клиентский код в нашем случае не ожидал такого поворота событий и дальше приложение будет работать некорректно.</p>\n<h2>Причины</h2><p>Прежде чем решать проблему, давайте разберемся с причинами – откуда появилось <a href=\"http://en.wikipedia.org/wiki/HTTP_302\">HTTP 302</a>? Казалось бы - причем здесь <code>302</code>? Было бы <a href=\"http://en.wikipedia.org/wiki/List_of_HTTP_status_codes\">логично</a> допустить, что там должно быть <code>HTTP 401</code>. Дело в том, что когда мы пользуемся <code>FormsAuthentication</code> за сценой действует <code>FormsAuthenticationModule</code> (который добавляется в список HTTP-модулей в глобальном конфигурационном файле). Заглянув под капот этого модуля можно легко понять, что если текущий код для HTTP есть <code>401</code>, то он выполняет редирект, т.е. заменяет его на 302:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/ajax-errors-handling-aspnet-mvc/ad2c0035/2@2x.png\"  alt=\"Корректная обработка проблем аутентификации AJAX-запросов для приложений ASP.NET MVC\"/>\n\n\n\n\n<p>Нетрудно догадаться, что сделано это было с целью перенаправить пользователя на страницу ввода пароля, если запрос не обработан успешно и требуется указать пароль (код возврата - <code>401</code>). В этом случае пользователь увидит приветливую форму ввода пароля, а не страницу IIS с кодом ошибки. Имеет смысл, не правда ли?</p>\n<p>С точки зрения нашего ASP.NET MVC приложения цепочка получается такой:</p>\n<ol>\n<li>Запрос приходит в приложение, где натыкается на фильтр <a href=\"http://msdn.microsoft.com/en-us/library/system.web.mvc.authorizeattribute(v=vs.108).aspx\">AuthorizeAttribute</a>.</li>\n<li>Поскольку пользователь не аутентифицирован, то этот фильтр отдаст <code>HTTP 401</code>, что логично (убедиться в этом достаточно легко, взглянув рефлектором на реализацию этого фильтра).</li>\n<li>Ну а дальше работает наш <code>FormsAuthenticationModule</code>, который подменяет <code>401</code> на редирект.</li>\n</ol>\n<p>В итоге – при обращении к защищенной паролем странице мы видим форму ввода пароля (<strong>что хорошо</strong>), но при обращении к аналогичным ресурсам через AJAX этот ответ не информативен (<strong>что плохо</strong>).</p>\n<h2>Решение</h2><p>Итак, что нужно для решения проблемы —</p>\n<ol>\n<li>Чтобы сервер отдавал <code>401/403</code> для AJAX-запросов, и <code>302</code> для обычных запросов.</li>\n<li>Обрабатывать на клиенте <code>401/403</code>.</li>\n</ol>\n<p>Честно говоря, <code>FormsAuthenticationModule</code> можно заставить не заменять запросы с <code>401</code>. Для этого в HttpResponse есть специальное свойство – SuppressFormsAuthenticationRedirect:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/ajax-errors-handling-aspnet-mvc/be202625/3@2x.png\"  alt=\"Корректная обработка проблем аутентификации AJAX-запросов для приложений ASP.NET MVC\"/>\n\n\n\n\n<p>Вопрос только в том, в каких случаях изменять это свойство и, что не менее важно, кто именно это будет делать?</p>\n<p>Прежде чем ответить на этот вопрос, давайте обратим внимание на то, как клиент должен реагировать, когда получает HTTP-ответ с ошибкой в ответ на AJAX-запрос. Сценария может быть два:</p>\n<ol>\n<li>Пользователь вообще не аутентифицирован в системе (<code>401</code>) и его нужно отправить на страницу с вводом логина пароля.</li>\n<li>Пользователь аутентифицирован, но данное действие ему все равно не доступно (<code>403</code>). Например, мы можем разрешить какое-то действие для определенных ролей, в которые пользователь не входит. Тогда отправлять его на страницу ввода пароля, пожалуй, глупо – в этом случае будет достаточным просто сообщить ему о том, что у него недостаточно полномочий.</li>\n</ol>\n<p>Таким образом нам нужно обрабатывать две ситуации по-разному. Взглянем рефлектором ещё раз на <code>AuthorizeAttribute</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/ajax-errors-handling-aspnet-mvc/4dc81de3/4@2x.png\"  alt=\"Корректная обработка проблем аутентификации AJAX-запросов для приложений ASP.NET MVC\"/>\n\n\n\n\n<p>…т.е. он <em>всегда</em> возвращает <code>401</code>.</p>\n<p><em>Нехорошо.</em> Поэтому придется немного поправить стандартное поведение. Итак, начнем.</p>\n<p><strong>Первое</strong> — определим, является ли текущий запрос AJAX-запросом и если да, то отключаем редирект на страницу ввода пароля:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class ApplicationAuthorizeAttribute : AuthorizeAttribute\n&#123;\n  protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)\n  &#123;\n    var httpContext = filterContext.HttpContext;\n    var request = httpContext.Request;\n    var response = httpContext.Response;\n\n    if (request.IsAjaxRequest())\n      response.SuppressFormsAuthenticationRedirect = true;\n\n    base.HandleUnauthorizedRequest(filterContext);\n  &#125;\n&#125;</code></pre>\n<p><strong>Второе</strong> – добавляем условие: если пользователь аутентифицирован, то отдаем <code>403</code>, если нет, то <code>401</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class ApplicationAuthorizeAttribute : AuthorizeAttribute\n&#123;\n  protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)\n  &#123;\n    var httpContext = filterContext.HttpContext;\n    var request = httpContext.Request;\n    var response = httpContext.Response;\n    var user = httpContext.User;\n\n    if (request.IsAjaxRequest())\n    &#123;\n      if (user.Identity.IsAuthenticated == false)\n        response.StatusCode = (int)HttpStatusCode.Unauthorized;\n      else\n        response.StatusCode = (int)HttpStatusCode.Forbidden;\n\n      response.SuppressFormsAuthenticationRedirect = true;\n      response.End();\n    &#125;\n\n    base.HandleUnauthorizedRequest(filterContext);\n  &#125;\n&#125;</code></pre>\n<p>Новый фильтр готов. Теперь нам следует использовать только что созданный фильтр в нашем приложении, вместо стандартного AuthorizeAttribute. Эстетам это может показаться огромным недостатком, но другого пути я здесь не вижу. Если есть решение, то буду рад увидеть его в комментариях.</p>\n<p>Ну и последнее, что нужно сделать – добавить обработку <code>401/403</code> на клиенте. Чтобы это не делать этого для каждого запроса можно воспользоваться обработчиком ajaxError в jQuery:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">$(document).ajaxError(function (e, xhr) &#123;\n  if (xhr.status == 401)\n    window.location = \"/Account/Login\";\n  else if (xhr.status == 403)\n    alert(\"You have no enough permissions to request this resource.\");\n&#125;);</code></pre>\n<p><strong>В итоге</strong> –</p>\n<ul>\n<li>Если пользователь не аутентифицирован (таймаут истёк, например), то после любого AJAX-запроса он будет отправлен на страницу ввода пароля.</li>\n<li>Если пользователь аутентифицирован, но не имеет полномочий выполнить действие, то он увидит соответствующее сообщение об ошибке.</li>\n<li>Если пользователь аутентифицирован и прав достаточно, то действие будет выполнено.</li>\n</ul>\n<p><strong>Из минусов</strong> – необходимо использовать новый фильтр <code>ApplicationAuthorizeAttribute</code>, вместо стандартного. Соответственно, если в коде стандартный уже использовался, то этот код придется поменять во всех местах.</p>",
            "tags": [
                "ASP.NET"
            ],
            "banner_image": "https://blog.zwezdin.com/2013/ajax-errors-handling-aspnet-mvc/63448de0/ajax_errors-handling-aspnet-mvc@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2013/less-integration-into-aspnet/",
            "url": "https://blog.zwezdin.com/2013/less-integration-into-aspnet/",
            "title": "Интеграция стилей LESS в сайт на основе ASP.NET",
            "date_published": "2013-05-06T12:00:00.000Z",
            "content_html": "<p>Существует такая отличная штука <a href=\"http://lesscss.org/\">LESS</a> — надстройка над CSS, которая позволяет создавать стили для своего сайта более удобно, сохраняя при этом код в более аккуратном и поддерживаемом виде.</p>\n<p>Поскольку изначально процессинг <strong>LESS</strong> написан на JavaScript, то актуальным остается вопрос каким образом интегрировать процессинг LESS-документов в проект <strong>ASP.NET</strong>. Здесь я рассмотрю известные мне способы.</p>\n<h2>JavaScript</h2><p>Самый простой способ интеграции – использовать клиентский процессинг LESS. Этот способ также предлагают разработчики LESS на своем сайте.</p>\n<p>Для подключения LESS на клиенте нужно выполнить два шага:</p>\n<p><strong>Шаг 1:</strong> Добавить ссылки на LESS-файлы</p>\n<pre class=code language-html><code class=\"code__container language-html\">&lt;link rel=\"stylesheet/less\" type=\"text/css\" href=\"styles.less\" /></code></pre>\n<p><strong>Шаг 2:</strong> Добавить ссылку на JavaScript-сценарий less.js с официального сайта:</p>\n<pre class=code language-html><code class=\"code__container language-html\">&lt;script src=\"less.js\" type=\"text/javascript\">&lt;/script></code></pre>\n<p>В этом случае при загрузке страницы будет загружен JavaScript-сценарий для процессинга, файлы LESS, а также дополнительные файлы, если потребуется (ссылки на другие скрипты, изображения и т.д.).</p>\n<p><strong>Плюсы данного подхода:</strong></p>\n<ul>\n<li>Процессинг “из первых рук” – JS-библиотека поставляется разработчиками LESS;</li>\n<li>В процесс сборки не нужно встраивать дополнительных механизмов;</li>\n<li>Если вы работаете в команде, то данный подход не требует установки дополнительных инструментов в среде разработчика, что значительно всё упрощает;</li>\n<li>Можно использовать дополнительные возможности клиентской библиотеки и управлять процессингом LESS на клиенте (изменять переменные, переключать стили и т.д.)</li>\n</ul>\n<p><strong>Минусы данного подхода:</strong></p>\n<ul>\n<li>На клиента тянется много лишнего – LESS-файлы, дополнительные ресурсы и изображения и т.д. Вместо этого клиент должен получать готовый результат в виде минифицированного CSS. С точки зрения клиентской оптимизации этот момент ставит крест на том, чтобы использовать этот метод в живых проектах.</li>\n<li>Процессинг происходит на клиенте, а это тратит ресурсы клиентской машины. Конечно все зависит от объема и сложности LESS-документа и в большинстве случаев клиент этого даже не почуствует, но все же такие вещи лучше делать на сервере.</li>\n<li>Сложно посмотреть результирующий CSS. Можно открыть CSS в инструментах разработчика в браузере и исследовать его там, но лично мне приятнее работать с CSS в среде разработки, в моем случае – в Visual Studio. Как это сделать при данном подходе мне неизвестно.</li>\n</ul>\n<p><strong>Выводы</strong> — данный способ подходит разве что для процесса разработки, когда LESS-код постоянно изменяется. На живый проектах использование данного способа весьма сомнительно. В моем случае минусы перекрывают все плюсы, поэтому я в своей работе не использую данный способ вообще.</p>\n<h2>Web Essentials 2012</h2><p>Существует такое прекрасное расширение для Visual Studio - <a href=\"https://visualstudiogallery.msdn.microsoft.com/07d54d12-7133-4e15-becb-6f451ea3bea6\">Web Essentials 2012</a>. Это расширение добавляет много различных полезных вещей для веб-разработчика. Если вы не знакомы с этим расширением, очень рекомендую его посмотреть.</p>\n<p>В контексте текущей темы, это расширение полезно с точки зрения работы с LESS. Оно может генерировать CSS-стили при сохранении LESS-файлов и при сборке проекта. Для этого после установки расширения нужно открыть окно настроек: <code>Tools</code> → <code>Options</code> → <code>Web Essentials</code> → <code>LESS</code> и задать соответствующие настройки.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/less-integration-into-aspnet/d1771769/1@2x.png\"  alt=\"Интеграция стилей LESS в сайт на основе ASP.NET\"/>\n\n\n\n\n<p>Теперь можно добавить LESS-файл в проект и, если вы задали настройку <code>Show preview window</code>, то генерация CSS будет происходить “на лету”:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/less-integration-into-aspnet/d051e2da/2@2x.png\"  alt=\"Интеграция стилей LESS в сайт на основе ASP.NET\"/>\n\n\n\n\n<p>После генерации CSS-файлы также доступны в проекте:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/less-integration-into-aspnet/cbe0b3b8/3@2x.png\"  alt=\"Интеграция стилей LESS в сайт на основе ASP.NET\"/>\n\n\n\n\n<p>В целом, в таком режиме работать очень удобно – сразу видишь генерируемый код.</p>\n<p>Но пока этот способ у меня, к сожалению, не прижился. Не уверен почему так происходит, но Web Essentials часто отказывается обрабатывать, казалось бы, корректный LESS-документ. Например, если в предыдущем примере переместить переменную в начало документа, то Web Essentials поругается на такой документ:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/less-integration-into-aspnet/ecf08e51/4@2x.png\"  alt=\"Интеграция стилей LESS в сайт на основе ASP.NET\"/>\n\n\n\n\n<p>Расчитывать на то, что будут обрабатываться какие-то уже готовые LESS-документы (например, LESS из <a href=\"https://getbootstrap.com/\">Twitter Bootstrap</a>) и вовсе не приходится, увы.</p>\n<p><strong>Плюсы данного подхода:</strong></p>\n<ul>\n<li>CSS генерируется “на лету”, так, что можно сразу же видеть результат в процессе написания LESS-кода;</li>\n<li>Web Essentials имеет встроенные подсказки при написании LESS-кода. Например, отслеживаются все ваши mixin-ы и предлагаются в качестве подсказки при написании стилей;</li>\n<li>Быстрый – даже при больших документах CSS генерируется действительно быстро, что лично для меня стало сюрпризом;</li>\n<li>Разом получаем полный и минифицированный вариант CSS.</li>\n</ul>\n<p><strong>Минусы данного подхода:</strong></p>\n<ul>\n<li>Самый главный минус описан выше – ошибки при генерации CSS.</li>\n<li>Нет возможности (или это я просто её не нашел) как генерировать CSS не для всех LESS-файлов, а выборочно. Например, генерировать пустой CSS для файла, где только описываются переменные бессмысленно. Более того, некоторые библиотеки написаны так, что сам по себе LESS-файл просто невозможно процессить, а процессится он только как часть другого.</li>\n<li>Если вы работаете в команде, то вам придется всех “заставить” установить Web Essentials, чтобы совместно использовать LESS в проекте. Если учесть, что Web Essentials иногда все-таки подглючивает, а также существенно замедляет работу Visual Studio при редактировании больших CSS, то это тоже может стать проблемой.</li>\n</ul>\n<p><strong>Выводы</strong> – способ очень интересный и перспективный, но пока сырой. В моем случае использовать его не получается из-за постоянных ошибок процессинга.</p>\n<h2>Библиотека dotless</h2><p>Библиотека <a href=\"http://www.dotlesscss.org/\">dotless</a> – это просто находка для .NET-окружения. Она предоставляет множество различных способов генерировать CSS на основе LESS в среде .NET. При этом существует вариант генерации CSS в момент выполнения приложения (есть готовый ASP.NET HTTP-handler), прямо из .NET-кода, а также при помощи консольной утилиты.</p>\n<p>Чтобы не тратить время на генерацию CSS в момент исполнения, я предпочитаю получать CSS в момент сборки проекта. Поэтому в моем случае работает следующее решение. Сразу скажу, что в данном случае при создании проекта придется немного поработать руками – а именно подключить процессинг LESS-файлов через консольную утилиту.</p>\n<p><strong>Шаг 1:</strong> Создаем проект и добавляем в него LESS-файлы.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/less-integration-into-aspnet/73d3d116/5@2x.png\"  alt=\"Интеграция стилей LESS в сайт на основе ASP.NET\"/>\n\n\n\n\n<p><strong>Шаг 2:</strong> Подключаем dotless. Можно загрузить его с <a href=\"http://www.dotlesscss.org/\">официального сайта</a>, либо подключить через <a href=\"https://nuget.org/packages/dotless/\">nuget</a>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/less-integration-into-aspnet/0c8ffaec/6@2x.png\"  alt=\"Интеграция стилей LESS в сайт на основе ASP.NET\"/>\n\n\n\n\n<p><strong>Шаг 3:</strong> Генерируем CSS при помощи dotless в момент сборки проекта.</p>\n<p>Для генерации CSS нам придется немного поработать руками. Для этого открываем файл проекта (<code>.csproj</code>) и в самом конце файла, перед закрытием элемента <code>&lt;Project&gt;</code> добавляем новый Target и запускаем его после (можно перед) сборкой:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Target Name=\"CompileDotlessCss\" AfterTargets=\"AfterBuild\">\n  &lt;ItemGroup>\n    &lt;__LessFiles Include=\"**\\*.less\"/>\n  &lt;/ItemGroup>\n  &lt;Exec Command=\"\"..\\packages\\dotless.1.3.1.0\\tool\\dotless.compiler.exe\" -m -a \"%(__LessFiles.FullPath)\" \"$([System.String]::Copy('%(__LessFiles.FullPath)').Replace('.less','.min.css'))\"\" />\n  &lt;Exec Command=\"\"..\\packages\\dotless.1.3.1.0\\tool\\dotless.compiler.exe\" -a \"%(__LessFiles.FullPath)\" \"$([System.String]::Copy('%(__LessFiles.FullPath)').Replace('.less','.css'))\"\" />\n&lt;/Target></code></pre>\n<p>Этот небольшой скрипт запускает консольную утилиту <code>dotless</code> при сборке проекта и генерирует файлы <code>.css</code> и <code>.min.css</code>. Откроем проект, запустим сборку, видим появившиеся файлы CSS:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/less-integration-into-aspnet/a10637a9/7@2x.png\"  alt=\"Интеграция стилей LESS в сайт на основе ASP.NET\"/>\n\n\n\n\n<p>Очевидно, что в такой иерархии хранить генерируемые CSS очень неудобно – даже при небольшом их количестве в папке будет хаос. Поэтому еще немного поработаем руками.</p>\n<p>Открываем файл проекта, находим сгенерированные файлы CSS и декларируем, что они должны отображаться в качестве дочернего узла в дереве проекта:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/less-integration-into-aspnet/1443e89e/8@2x.png\"  alt=\"Интеграция стилей LESS в сайт на основе ASP.NET\"/>\n\n\n\n\n<p>Загружаем проект, теперь видим, что всё по полочкам:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/less-integration-into-aspnet/b99a4cfa/9@2x.png\"  alt=\"Интеграция стилей LESS в сайт на основе ASP.NET\"/>\n\n\n\n\n<p>Совершенству нет предела – давайте немного улучшим этот способ так, чтобы CSS генерировался не для всех LESS файлов, а выборочно. Сделаем так, чтобы процессинг происходил только для тех LESS-файлов, для которых уже существует CSS-файл. Тогда, чтобы добавить процессинг очередного LESS-файла, просто создадим для него пустой CSS-файл и при сборке будет осуществляться процессинг.</p>\n<p>Для этого идем в файл проекта и изменяем скрипт следующим образом:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Target Name=\"CompileDotlessCss\" AfterTargets=\"AfterBuild\">\n  &lt;ItemGroup>\n    &lt;__LessFiles Include=\"**\\*.less\" />\n    &lt;__LessFilesToProcessMin Include=\"%(__LessFiles.Identity)\" Condition=\"Exists('%(__LessFiles.RootDir)%(__LessFiles.Directory)%(__LessFiles.Filename).min.css')\" />\n    &lt;__LessFilesToProcessDev Include=\"%(__LessFiles.Identity)\" Condition=\"Exists('%(__LessFiles.RootDir)%(__LessFiles.Directory)%(__LessFiles.Filename).css')\" />\n  &lt;/ItemGroup>\n  &lt;Exec Command=\"\"..\\packages\\dotless.1.3.1.0\\tool\\dotless.compiler.exe\" -m -a \"%(__LessFilesToProcessMin.FullPath)\" \"$([System.String]::Copy('%(__LessFilesToProcessMin.FullPath)').Replace('.less','.min.css'))\"\" />\n  &lt;Exec Command=\"\"..\\packages\\dotless.1.3.1.0\\tool\\dotless.compiler.exe\" -a \"%(__LessFilesToProcessDev.FullPath)\" \"$([System.String]::Copy('%(__LessFilesToProcessDev.FullPath)').Replace('.less','.css'))\"\" />\n&lt;/Target></code></pre>\n<p>Теперь генерация CSS выполняется только для тех LESS-файлов, для которых CSS-файлы существуют. По мне – очень неплохой компромис.</p>\n<p><strong>Плюсы данного подхода:</strong></p>\n<ul>\n<li>LESS процессится во время сборки – не тратится время и ресурсы в момент исполнения кода;</li>\n<li>Есть возможность посмотреть результирующий CSS в процессе разработки;</li>\n<li>При сборке получаем полный и минифицированный вариант;</li>\n<li>Можем управлять тем, какие файлы нужно процессить, а какие – нет;</li>\n<li>Несмотря на то, что в процессе сборки запускается консольная утилита, в случае ошибок они корректно отображаются в окне <code>Error List</code> в Visual Studio;</li>\n<li>Если вы работаете в команде, то никаких дополнительных инстурментов на компьютер разработчика устанавливать не нужно – открывайте проект в Visual Studio, собирайте код, получайте результат.</li>\n</ul>\n<p><strong>Минусы данного подхода:</strong></p>\n<ul>\n<li>Нужно руками допиливать файл проекта при его создании, а также при добавлении новых LESS-файлов (если требуется их процессинг в CSS);</li>\n<li>При изменении LESS-файлов требуется пересборка проекта.</li>\n</ul>\n<p><strong>Выводы</strong> — для меня это на данный момент рабочий вариант того, как процессить LESS в ASP.NET проектах. Приходится немного править проект руками, однако, это компенсируется дальнейшими удобствами.</p>\n<p><a href=\"https://github.com/sergeyzwezdin/AspNetLessIntegrationSample\">Пример проекта с таким видом процессинга на github</a></p>\n<h2>ASP.NET Bundling and Minification</h2><p>Не так давно в ASP.NET появилось пространство имен <code>System.Web.Optimization</code>, которое включает в себя инструменты для клиентской оптимизации. Одним из таких механизмов является возможность создания пакетов клиентских файлов (bundle).</p>\n<p>Идея заключается в том, что в момент исполнения приложения ASP.NET создает пакет, который объединяет несколько JS или CSS файлов, при этом минифицируя их и/или выполняя еще какой-либо серверный процессинг. Собственно, идея данного способа заключается в том, что если у вас уже используются Bundle-ы для оптимизации, то вы можете встроить в эту цепочку и процессинг LESS, используя все тот же dotless, например. Как это сделать:</p>\n<p><strong>Шаг 1:</strong> Подключаем dotless к проекту (описывалось ранее).</p>\n<p><strong>Шаг 2:</strong> Создаем класс для LESS-трансформаций:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">using System.Web.Optimization;\n\npublic class LessTransform : IBundleTransform\n&#123;\n    public void Process(BundleContext context, BundleResponse response)\n    &#123;\n        response.Content = dotless.Core.Less.Parse(response.Content);\n        response.ContentType = \"text/css\";\n    &#125;\n&#125;</code></pre>\n<p><strong>Шаг 3:</strong> Добавляем этот класс в общий список трансформаций при определении пакета:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">var stylesBundle = new Bundle(\"~/bundles/styles\").IncludeDirectory(\"~/Content/Styles/\", \"*.less\");\nstylesBundle.Transforms.Add(new LessTransform());\n// ...\nstylesBundle.Add(lessBundle);</code></pre>\n<p>Теперь этот пакет можно использовать как и все остальные пакеты. Про Bundle-ы и оптимизацию можно почтитать еще <a href=\"http://www.asp.net/mvc/tutorials/mvc-4/bundling-and-minification\">тут</a>.</p>\n<p><strong>Плюсы данного подхода:</strong></p>\n<ul>\n<li>Уже практически готовый инструмент для сборки пакетов;</li>\n<li>При сборке получаем полный (по желанию) и минифицированный вариант;</li>\n<li>Можем управлять тем, какие файлы нужно процессить, а какие – нет;</li>\n<li>Если вы работаете в команде, то никаких дополнительных инстурментов на компьютер разработчика устанавливать не нужно – открывайте проект в Visual Studio, собирайте код, получайте результат.</li>\n</ul>\n<p><strong>Минусы данного подхода:</strong></p>\n<ul>\n<li>Нет (по кр.мере я не нашел) простого способа посмотреть результирующий CSS – можно посмотреть dev-тулами в браузере, но это неудобно;</li>\n<li>Процессинг идет в рантайме, что не всегда хорошо</li>\n</ul>\n<p><strong>Выводы</strong> — для тех у кого итак используется Bundles для организации клиентского кода этот способ вполне может подойти. Из-за описаных выше минусов мне этот способ не очень нравится, поэтому я отдаю предпочтение процессингу LESS-файлов на этапе сборке при помощи dotless.</p>",
            "tags": [
                "ASP.NET",
                "LESS"
            ],
            "banner_image": "https://blog.zwezdin.com/2013/less-integration-into-aspnet/fae2566c/less_integration-into-aspnet@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2013/custom-build-action/",
            "url": "https://blog.zwezdin.com/2013/custom-build-action/",
            "title": "Создание собственного типа сборки (Build Action) в Visual Studio 2013",
            "date_published": "2013-04-10T12:00:00.000Z",
            "content_html": "<p>Visual Studio и MSBuild имеют болльше возможности для расширения. Одной из таких возможностей является возможность определения каким именно способом будет обрабатываться конкретный файл в проекте. Здесь я покажу каким образом можно создавать собственные типы сборки и для чего это может быть нужно.</p>\n<p>Я не буду пояснять синтаксис MSBuild, поэтому если вы не знакомы с ним, то перед прочтением предлагаю <a href=\"http://msdn.microsoft.com/en-us/library/wea2sca5(v=vs.90).aspx\">ознакомиться</a>.</p>\n<p>Разрабатывая приложения в Visual Studio, наверное, каждый сталкивался с параметром <code>Build Action</code> в окне свойств файла.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/custom-build-action/d829a39a/1@2x.png\"  alt=\"Создание собственного типа сборки (Build Action) в Visual Studio 2013\"/>\n\n\n\n\n<p>В зависимости от этого параметра, MSBuild корректирует своё поведение относительно того, что именно нужно сделать с конкретным файлом. Например, мы можем пометить файл как ресурс или указать, что изображение будет экраном приветствия.</p>\n<p>Представленные типы могут изменяться в зависимости от типа проекта. Например, типы сборки для консольного приложения могут отличаться от таковых для приложения ASP.NET.</p>\n<p>При разработке своих приложений может также потребоваться добавить новый тип сборки и дополнить его некоторым поведением. Это может быть полезно в случаях, когда нам нужно выполнить какой-либо пре- или пост-процессинг. Например, для web-приложений это может быть процессинг LESS/SASS или сборка JS-файлов при помощи GruntJS.</p>\n<p>Существует множество других способ сделать то же самое. Например, использовать pre- и post-build events, которые можно задать в свойствах проекта. Однако, приведенное решение по моему мнению гораздо удобнее использовать повторно и переносить на новые проекты без боли (например, через Nuget).</p>\n<h2>Создаем новый тип сборки</h2><p>Чтобы создать новый Build Type необходимо открыть файл проекта (<code>.csproj</code> и др.) и добавить секцию <code>AvailableItemName</code>. Предположим, мы будем создавать новый тип сборки для компиляции LESS, тогда это может выглядеть так:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;ItemGroup>\n     &lt;AvailableItemName Include=\"LessBuild\" />\n&lt;/ItemGroup></code></pre>\n<p>Добавление этого кода в файл проекта достаточно для того, чтобы новый тип сборки появился в списке.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/custom-build-action/2cfdae52/2@2x.png\"  alt=\"Создание собственного типа сборки (Build Action) в Visual Studio 2013\"/>\n\n\n\n\n<p>Если мы выберем какой-либо файл в проекте и зададим соответствующий тип сборки, то определение этого файла в проекте изменится следующим образом:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;ItemGroup>\n  &lt;LessBuild Include=\"StyleSheet1.less\" />\n&lt;/ItemGroup></code></pre>\n<p>Если мы попробуем собрать проект с новым типом сборки, то проект успешно соберется, но с ресурсом, для которого этот тип сборки задан, ничего не произойдет. Для этого нам нужно сделать ещё кое-что.</p>\n<h2>Задаем поведение для нового типа сборки</h2><p>При сборке ресурса нам может потребоваться запускать какую-то стороннюю утилиту или задачу MSBuild. Обработка нового типа сборки сводится к созданию и запуску нового <code>Target</code> в проекте MSBuild.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Target Name=\"LessBuildTarget\" AfterTargets=\"AfterBuild\" Condition=\"'@(LessBuild)' != ''\">\n&lt;/Target></code></pre>\n<p>Рассмотрим подробнее как это работает.</p>\n<ol>\n<li><code>Target</code> запускается каждый раз после сборки проекта из-за параметра <code>AfterTargets=&quot;AfterBuild&quot;</code>. Если вам необходимо что-то запустить перед сборкой проекта, то используйте вместо этого <code>BeforeTargets=&quot;BeforeBuild&quot;</code>.</li>\n<li>Если в проекте присутствуют файлы, для которых задан соответствующий тип сборки, то переменная <code>@(LessBuild)</code> будет содержать их имена. Параметр <code>Condition</code> предотвращает запуск скрипта, если в проекте нет ни одного файла с соответствующим типом сборки.</li>\n</ol>\n<p>После того, как входная точка создана, остается обработать соответствующие файлы. Переменная <code>@(LessBuild)</code> в этом случае является строкой, в которой перечислены все файлы проекта с соответствующим типом сборки, перечисленные через точку с запятой. Получим из этой строки список файлов:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Target Name=\"LessBuildTarget\" AfterTargets=\"AfterBuild\" Condition=\"'@(LessBuild)' != ''\">\n     &lt;ItemGroup>\n          &lt;_FilesToProcess Include=\"@(LessBuild)\"/>\n     &lt;/ItemGroup>\n&lt;/Target></code></pre>\n<p>И теперь нам остается только обработать эти файлы, запустив какую-то внешнюю утилиту, задачу MSBuild или как-то ещё.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Target Name=\"LessBuildTarget\" AfterTargets=\"AfterBuild\" Condition=\"'@(LessBuild)' != ''\">\n     &lt;ItemGroup>\n          &lt;_FilesToProcess Include=\"@(LessBuild)\"/>\n     &lt;/ItemGroup>\n\n     &lt;Exec Command=\"lesscompile.exe %(_FilesToProcess.FullPath)\" />\n&lt;/Target></code></pre>\n<p>Разумеется, этот кусок скрипта следует переписать несколько иначе, исходя из текущих потребностей (например, обрабатывая это в несколько шагов). В данном случае я намеренно не хочу усложнять, чтобы идея осталась понятной.</p>\n<p>В итоге, мы имеем достаточно универсальный инструмент, который позволяет интегрировать сборку наших приложений с различными внешними инструментами и утилитами. Надеюсь, это сделает вашу жизнь проще.</p>",
            "tags": [
                ".NET",
                "Visual Studio"
            ],
            "banner_image": "https://blog.zwezdin.com/2013/custom-build-action/fbad2588/custom_build-action@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2013/winrt-responsive/",
            "url": "https://blog.zwezdin.com/2013/winrt-responsive/",
            "title": "Отзывчивый дизайн в Windows RT в закрепленном режиме",
            "date_published": "2013-04-08T12:00:00.000Z",
            "content_html": "<p>В современном мире большое количество различных форм-факторов устройств. Игнорировать этот факт с каждым днем становится все сложнее. Поэтому для адаптации содержимого веб-страниц используют “резиновую” сетку на странице в совокупности с медиа-запросами. Это принятно называть отзывчивым веб-дизайном. Большое количество сайтов с отзывчивым дизайном представлено на сайте <a href=\"http://mediaqueri.es/\">http://mediaqueri.es/</a>.</p>\n<p>К сожалению, не все так гладко в высокотехнологичном мире. Так, например, если мы откроем сайт с медиа-запросами в Windows RT (Internet Explorer Metro mode) и прикрепим окно к краю экрана (snapped mode), то медиа-запросы не сработают, а сайт будет представлен в уменьшенном виде. Логично было бы предположить, что медиа-запросы должны срабатывать и в этом случае, а сайт изменять свой вид. Давайте посмотрим, как можно исправить это недоразумение.</p>\n<h2>Отзывчивый дизайн</h2><p>Основная идея отзывчивых веб-сайтов – использование резиновой сетки (когда содержимое растягивается по ширине окна, а ширина элементов задается в процентах) и использовании медиа-запросов. Медиа-запросы позволяют включать и отключать различные стили, в зависимости от ширины контейнера.</p>\n<p>Для определения медиа-запроса в CSS-документе используется следующий селектор:</p>\n<pre class=code language-css><code class=\"code__container language-css\">@media only screen and (max-width : 600px)\n&#123;\n&#125;</code></pre>\n<p>После применения такого селектора, сайт может выглядеть по-разному в различных форматах:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/winrt-responsive/f07b7658/1@2x.png\"  alt=\"Отзывчивый дизайн в Windows RT в закрепленном режиме\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/winrt-responsive/90a02bbc/2@2x.png\"  alt=\"Отзывчивый дизайн в Windows RT в закрепленном режиме\"/>\n\n\n\n\n<p>Чтобы заставить медиа-запросы работать на мобильных устройствах с большим разрешением, задаются параметры viewport, которые говорят о том, что размер страницы устанавливается на основе ширины устройства:</p>\n<pre class=code language-html><code class=\"code__container language-html\">&lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0\"></code></pre>\n<p>Теперь наша страница готова для того, чтобы адаптироваться к различным размерам экранов.</p>\n<h2>Суть проблемы</h2><p>Как уже упоминалось выше, если мы прикрепим браузер Windows RT к краю экрана, то несмотря на небольшую ширину окна браузера, медиа-запросы работать не будут. В итоге мы увидим сайт, на котором достаточно трудно разглядеть даже заголовки:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/winrt-responsive/7cd613a2/3@2x.png\"  alt=\"Отзывчивый дизайн в Windows RT в закрепленном режиме\"/>\n\n\n\n\n<p>Как видно, браузер просто игнорирует медиа-запросы и отображает документ так, как будто его просматривают на полный экран. Нехорошо.</p>\n<p>Чтобы исправить это поведение, в CSS нужно добавить специальное правило, которое указывает на то, что в таких случаях страницу нужно отображать с меньшей шириной:</p>\n<pre class=code language-css><code class=\"code__container language-css\">@media only screen and (max-width : 320px)\n&#123;\n    @-ms-viewport\n    &#123;\n        width: 320px;\n    &#125;\n&#125;</code></pre>\n<p>Добавим это правило в CSS-документ и обновим страницу. Результат:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/winrt-responsive/557d07e5/4@2x.png\"  alt=\"Отзывчивый дизайн в Windows RT в закрепленном режиме\"/>\n\n\n\n\n<p>Костыль? Да. Но оставлять без внимания пользователей Windows 8 тоже нельзя. Поэтому адаптируйте ваши сайты и под Internet Explorer в snapped-режиме.</p>",
            "tags": [
                "Фронтенд",
                "CSS"
            ],
            "banner_image": "https://blog.zwezdin.com/2013/winrt-responsive/e9ecf663/winrt_responsive@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2013/code-generating-with-roslyn/",
            "url": "https://blog.zwezdin.com/2013/code-generating-with-roslyn/",
            "title": "Генерация кода на языке C# с использованием Roslyn",
            "date_published": "2013-04-02T12:00:00.000Z",
            "content_html": "<p>Roslyn можно использовать не только для <a href=\"/2013/csharp-code-analysis-with-roslyn/\">анализа кода</a>, но и для генерации. В контексте генерации кода обычно рассматриваются два варианта – изменение структуры существующего кода и генерация кода с нуля. Рассмотрим оба варианта.</p>\n<h2>Генерация кода с чистого листа</h2><p>Элементы дерева кода <strong>Roslyn Compiler API</strong> состоят из объектов, которые являются наследниками абстрактного класса <code>SyntaxNode</code>. Для генерации элементов синтаксического дерева используется вспомогательный класс Syntax.</p>\n<p>Рассмотрим процесс генерации кода на примере генерации простого метода. Метод описывается классом <code>MethodDeclarationSyntax</code>. Чтобы создать этот класс воспользуемся методом <code>Syntax.MethodDeclaration()</code>, указав тип возвращаемого значения и имя генерируемого метода:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">MethodDeclarationSyntax method =\n  Syntax.MethodDeclaration(Syntax.ParseTypeName(\"string\"), \"TestMethod\")</code></pre>\n<p>Добавим к нашему методу модификаторы, которые говорят о том, что метод статический и публичный:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">MethodDeclarationSyntax method =\n  Syntax.MethodDeclaration(Syntax.ParseTypeName(\"string\"), \"TestMethod\")\n    .AddModifiers(Syntax.Token(SyntaxKind.PublicKeyword))\n    .AddModifiers(Syntax.Token(SyntaxKind.StaticKeyword))</code></pre>\n<p>Теперь самое время определить тело метода. Для этого существует метод <code>AddBodyStatements()</code>, который принимает переменное число параметров (в зависимости от того, какое количество statement-ов мы хотим добавить). Также можно вызвать этот метод несколько раз, что на мой взгляд удобнее.</p>\n<p>Пусть тело метода содержит вызов метода <code>Console.Write()</code>, параметром которого является строка <code>test</code>. Для этого нам нужно создать цепочку <code>ExpressionStatementSyntax</code> (выражение, которое будет добавлено в тело метода) – <code>InvocationExpressionSyntax</code> (вызов метода) – <code>MemberAccessExpressionSyntax</code> (обращение к методу). Кроме того, для вызова метода нужно передать параметры. Вот как будет выглядеть код по заполнению тела метода:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">.AddBodyStatements(\n  Syntax.ExpressionStatement(\n    Syntax.InvocationExpression(\n      Syntax.MemberAccessExpression(SyntaxKind.MemberAccessExpression, Syntax.IdentifierName(\"Console\"),\n                      Syntax.IdentifierName(\"Write\")),\n      Syntax.ArgumentList(\n        Syntax.SeparatedList(\n          Syntax.Argument(Syntax.LiteralExpression(SyntaxKind.StringLiteralExpression, Syntax.Literal(\"test\")))\n          ))\n      )\n    )\n)</code></pre>\n<p>Добавим также к телу метода вызов оператора return, который будет возвращать строку <code>OK</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">.AddBodyStatements(\n  Syntax.ReturnStatement(\n    Syntax.LiteralExpression(SyntaxKind.StringLiteralExpression, Syntax.Literal(\"OK\"))\n    )</code></pre>\n<p>В итоге код для генерации нашего тестового метода будет выглядеть так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">MethodDeclarationSyntax method =\n  Syntax.MethodDeclaration(Syntax.ParseTypeName(\"string\"), \"TestMethod\")\n    .AddModifiers(Syntax.Token(SyntaxKind.PublicKeyword))\n    .AddModifiers(Syntax.Token(SyntaxKind.StaticKeyword))\n    .AddBodyStatements(\n      Syntax.ExpressionStatement(\n        Syntax.InvocationExpression(\n          Syntax.MemberAccessExpression(SyntaxKind.MemberAccessExpression, Syntax.IdentifierName(\"Console\"),\n                          Syntax.IdentifierName(\"Write\")),\n          Syntax.ArgumentList(\n            Syntax.SeparatedList(\n              Syntax.Argument(Syntax.LiteralExpression(SyntaxKind.StringLiteralExpression, Syntax.Literal(\"test\")))\n              ))\n          )\n        )\n    )\n    .AddBodyStatements(\n      Syntax.ReturnStatement(\n        Syntax.LiteralExpression(SyntaxKind.StringLiteralExpression, Syntax.Literal(\"OK\"))\n        )\n        );</code></pre>\n<p>Выглядит немного громоздко, но это издержки данной предметной области и к этому быстро привыкаешь.</p>\n<p>Далее нам необходимо создать объект <code>CompilationUnitSyntax</code>. В дальнейшем он потребовался бы нам, если бы мы захотели скомпилировать данный код. В нашем случае он будет необходим нам для того, чтобы отформатировать исходный текст (расставить отсутпы и переносы строк):</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">CompilationUnitSyntax tree = Syntax.CompilationUnit()\n    .AddMembers(method);\n\nConsole.WriteLine(tree\n  .Format(FormattingOptions.GetDefaultOptions()).GetFormattedRoot()\n  .ToFullString());</code></pre>\n<p>Метод <code>Format()</code> в данном случае как раз выполняет форматирование кода, а метод <code>ToFullString()</code> отдает нам строковое представление сгенерированного кода.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/code-generating-with-roslyn/f845b9a3/1@2x.png\"  alt=\"Генерация кода на языке C# с использованием Roslyn\"/>\n\n\n\n\n<p>Аналогичным образом генерируются классы, пространства имен и т.д. Таким образом, мы можем получить полноценный сгенерированный код, который в дальнейшем будем компилировать.</p>\n<h2>Изменение структуры существующего кода</h2><p>Другой сценарий – взять уже существующий код и модифицировать в соответствии с некоторыми правилами. За основу возьмем файл из библиотеки Monads.NET. При помощи синтаксического дерева <strong>Roslyn Compiler API</strong> найдем первый метод в исходном файле и будем его модифицировать:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static TSource Do&lt;TSource>(this TSource source, Action&lt;TSource> action)\n  where TSource : class\n&#123;\n  if (source != default(TSource))\n  &#123;\n    action(source);\n  &#125;\n\n  return source;\n&#125;</code></pre>\n<p>В этом методе есть условный оператор <code>if</code>, давайте попробуем изменить его условие:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">SyntaxTree tree = SyntaxTree.ParseText(source);\nMethodDeclarationSyntax method = tree.GetRoot()\n  .DescendantNodes()\n  .OfType&lt;MethodDeclarationSyntax>()\n  .First();\n\nIfStatementSyntax ifStatement = method\n  .DescendantNodes()\n  .OfType&lt;IfStatementSyntax>()\n  .First();\n\nmethod = method.ReplaceNode(ifStatement,\n          ifStatement\n          .WithCondition((Syntax.LiteralExpression(SyntaxKind.TrueLiteralExpression)))\n          );</code></pre>\n<p>Ключевой момент – вызов метода <code>ReplaceNode</code>. Первый аргумент этого метода – узел, который мы хотим заменить, второй – новое состояние этого узла. Вызов метода <code>WithCondition</code> позволяет заменить условие оператора <code>if</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/code-generating-with-roslyn/a45aeff1/2@2x.png\"  alt=\"Генерация кода на языке C# с использованием Roslyn\"/>\n\n\n\n\n<p>Если требуется какая-то более общая замена, вместо изменения условия, то для узла следует использовать метод <code>Update</code>. Давайте найдем элемент <code>BlockSyntax</code> (тело метода), и заменить его на пустую коллекцию:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">SyntaxTree tree = SyntaxTree.ParseText(source);\nMethodDeclarationSyntax method = tree.GetRoot()\n  .DescendantNodes()\n  .OfType&lt;MethodDeclarationSyntax>()\n  .First();\n\nBlockSyntax methodBody = method\n  .DescendantNodes()\n  .OfType&lt;BlockSyntax>()\n  .First();\n\nmethod = method.ReplaceNode(methodBody,\n  methodBody.Update(methodBody.OpenBraceToken, new SyntaxList&lt;StatementSyntax>(), methodBody.CloseBraceToken));</code></pre>\n<p>Как видно, в метод <code>Update</code> вторым параметром передается коллекция элементов. Эта коллеция и содержит новое тело метода. Поскольку в данном случае она пустая, то и тело обновленное тело метода будет пустым.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/code-generating-with-roslyn/bbb0e39f/3@2x.png\"  alt=\"Генерация кода на языке C# с использованием Roslyn\"/>\n\n\n\n\n<p>Наконец, третий вариант изменения структуры кода – использование паттерна Visitor. Для этого нужно реализовать объект-visitor, унаследовав его от базового класса <code>SyntaxRewriter</code>. В созданном классе можно переопределить нужный метод, в зависимости от того, с каким типом элементов требуется работать. В нашем случае мы будем искать элемент типа <code>BlockSyntax</code>, поэтому переопределим метод <code>VisitBlock</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class MyRewriter : SyntaxRewriter\n&#123;\n  public override SyntaxNode VisitBlock(BlockSyntax node)\n  &#123;\n    if (node.DescendantNodes().OfType&lt;BlockSyntax>().Count() >= 1)\n      return node.Update(node.OpenBraceToken,\n                        new SyntaxList&lt;StatementSyntax>(),\n                        node.CloseBraceToken);\n\n    return base.VisitBlock(node);\n  &#125;\n&#125;</code></pre>\n<p>Для того, чтобы запустить этот Visitor используем следующий простой код:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">SyntaxNode updatedTree = new MyRewriter()\n  .Visit(SyntaxTree.ParseText(source)\n  .GetRoot());</code></pre>\n<p>Вызов метода <code>Visit()</code> запустит Visitor и вернет обновленное дерево, которое как и в предыдущем случае будет содержать очищенное тело метода:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/code-generating-with-roslyn/26177a3d/4@2x.png\"  alt=\"Генерация кода на языке C# с использованием Roslyn\"/>\n\n\n\n\n<p>Таким образом, Roslyn Compiler API предоставляет множество различных способов по генерации и модфикации кода. Несмотря на некоторую громозкость кода, API достаточно простой и логичный.</p>",
            "tags": [
                "C#",
                ".NET",
                "Roslyn"
            ],
            "banner_image": "https://blog.zwezdin.com/2013/code-generating-with-roslyn/61dd70e0/code_generating-with-roslyn@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2013/csharp-code-analysis-with-roslyn/",
            "url": "https://blog.zwezdin.com/2013/csharp-code-analysis-with-roslyn/",
            "title": "Анализ кода на языке C# с использованием Roslyn",
            "date_published": "2013-03-22T12:00:00.000Z",
            "content_html": "<p>Одной из самых важных частей Roslyn является возможность анализа кода – <strong>Roslyn Compiler API</strong>. Для экспериментов с Roslyn Compiler API возьмем файл из библиотеки Monads.NET. Я разместил этот файл в ресурсах приложения, чтобы иметь быстрый доступ к нему из любого места в коде.</p>\n<p><strong>Complier API</strong> начинается с класса <code>SyntaxTree</code> – это класс являющийся точкой входа для работы с Roslyn Compiler API. Чтобы выполнить разбор исходного кода, необходимо вызвать статический метод <code>ParseText</code> (если исходный код содержится в строковой переменной) или ParseFile (если исходный код находится в файле, путь до конторого известен) у базового объекта <code>SyntaxTree</code>.</p>\n<p>При анализе кода в Roslyn Compiler API очень удобно получить список всех дочерних элементов, а затем по типу выбрать то, что нас интересует. Каждый тип блока кода имеет собственный класс-наследник базового класса <code>SyntaxNode</code>. Например, элемент, описывающий метод класса представлен классом <code>MethodDeclarationSyntax</code>, элемент, описывающий цикл <code>while</code> – классом <code>WhileStatementSyntax</code> и т.д. Таким образом, чтобы получить список всех методов, которые находятся в исследуемом исходном файле можно использовать следующий код:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">SyntaxTree tree = SyntaxTree.ParseText(source);\nIEnumerable&lt;MethodDeclarationSyntax> methods = tree\n  .GetRoot()\n  .DescendantNodes()\n  .OfType&lt;MethodDeclarationSyntax>();</code></pre>\n<p>При обращении к методу <code>DescendantNodes()</code> можно использовать лямбда-выражение, которое также отфильтрует набор исходных элементов.</p>\n<p>После получения списка методов можно пройтись по нему и отобразить имя каждого метода:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">foreach (MethodDeclarationSyntax method in methods)\n&#123;\n    SyntaxToken methodName = method.Identifier;\n    Console.WriteLine(\"&#123;0&#125;()\", methodName);\n&#125;</code></pre>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/csharp-code-analysis-with-roslyn/dc85e3a8/1@2x.png\"  alt=\"Анализ кода на языке C# с использованием Roslyn\"/>\n\n\n\n\n<p>Поскольку <code>MethodDeclarationSyntax</code> и другие синтаксические единицы наследники одного и того же базового класса, дальнейшие действия по анализу кода сводятся к похожим конструкциям. Давайте подсчитаем количество блоков разного типа в каждом из методов. Для этого сначала получим тело метода – это элемент типа <code>BlockSyntax</code>, внутри которого будем искать элементы нужного нам типа.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">SyntaxTree tree = SyntaxTree.ParseText(source);\nIEnumerable&lt;MethodDeclarationSyntax> methods = tree\n  .GetRoot()\n  .DescendantNodes()\n  .OfType&lt;MethodDeclarationSyntax>();\n\nforeach (MethodDeclarationSyntax method in methods)\n&#123;\n  SyntaxToken methodName = method.Identifier;\n  BlockSyntax methodBody = method.ChildNodes().OfType&lt;BlockSyntax>().Single();\n\n  int ifStatementCount = methodBody.DescendantNodes().OfType&lt;IfStatementSyntax>().Count();\n  int returnStatementCount = methodBody.DescendantNodes().OfType&lt;ReturnStatementSyntax>().Count();\n  int throwStatementCount = methodBody.DescendantNodes().OfType&lt;ThrowStatementSyntax>().Count();\n  int tryStatementCount = methodBody.DescendantNodes().OfType&lt;TryStatementSyntax>().Count();\n\n  Console.WriteLine(\"&#123;0&#125;()\", methodName);\n  Console.WriteLine(\"if: &#123;0&#125;\\treturn: &#123;1&#125;\\tthrow: &#123;2&#125;\\ttry: &#123;3&#125;\",\n    ifStatementCount, returnStatementCount, throwStatementCount, tryStatementCount);\n  Console.WriteLine();\n&#125;</code></pre>\n<p>Здесь для каждого метода подсчитывается число дочерних элементов каждого из типов.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/csharp-code-analysis-with-roslyn/8379dd32/2@2x.png\"  alt=\"Анализ кода на языке C# с использованием Roslyn\"/>\n\n\n\n\n<p>Далее запросы к коду строятся аналогично. Когда это может быть полезно? Вариантов применения множество – подсчет метрик кода, любой анализ кода, автоматизированный рефакторинг кода и т.д.</p>",
            "tags": [
                "C#",
                ".NET",
                "Roslyn"
            ],
            "banner_image": "https://blog.zwezdin.com/2013/csharp-code-analysis-with-roslyn/b7f54a06/csharp_code-analysis-with-roslyn@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2013/roslyn-as-a-scripting-language/",
            "url": "https://blog.zwezdin.com/2013/roslyn-as-a-scripting-language/",
            "title": "Использование Roslyn в качестве скриптового языка в своем приложении",
            "date_published": "2013-03-13T12:00:00.000Z",
            "content_html": "<p>Платформа Roslyn позволяет разработчикам встраивать компиляцию и исполнения кода прямо в приложение. Эта часть платформы <strong>Roslyn</strong> называется <strong>Scripting API</strong>. С её помощью вы можете исполнить код, который вводит пользователь или который, например, хранится в базе данных.</p>\n<p>На первый взгляд может показаться, что эта функциональность не имеет большого применения в практических задачах. На самом деле это может быть удобно в различного рода конструкторах форм, CMS, настраиваемых системах документооборота и там, где мы хотим доверить настройку приложения пользователю чуть больше, чем обычно.</p>\n<p>Для того, чтобы работать с компонентами Roslyn следует добавить ссылку на сборки Roslyn.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/roslyn-as-a-scripting-language/b0c847e4/1@2x.png\"  alt=\"Использование Roslyn в качестве скриптового языка в своем приложении\"/>\n\n\n\n\n<p>Для использования скриптового механизма Roslyn нам понадобится объект <code>ScriptingEngine</code>. Этот объект отвечает за исполнение кода и имеет API для управления зависимостями на внешние модули:</p>\n<ul>\n<li>метод <code>ImportNamespace</code> – позволяет импортировать пространство имен для использования его в коде (аналог <code>using</code>);</li>\n<li>метод <code>AddReference</code> – позволяет задать ссылку на внешюю сборку;</li>\n<li>метод <code>SetReferenceSearchPaths</code> – позволяет указать одну или несколько папок, в которых будет производится поиск сборок для разрешения зависимостей на внешние сборки.</li>\n</ul>\n<p>Кроме задания зависимостей, можно определить какие зависимости уже установлены. Для этого существуют методы <code>GetReferences()</code> и <code>GetImportedNamespaces()</code>.</p>\n<p>После создания <code>ScriptingEngine</code> и указания всех зависимостей нужно создать сессию, в рамках которой мы будем исполнять код. Сессия позволяет объединить последовательность нескольких вызовов и разделять между собой какие-то данные. В некотором приближении сессию можно рассматривать как запуск приложения. Для создания новой сессии используется метод <code>CreateSession</code>.</p>\n<p>После того, как сессия создана, можно запускать собственный код. Для этого у объекта Session (результат выполнения <code>CreateSession</code>) существует метод <code>Execute</code>. Я создал консольное приложение, в котором считываю пользовательский ввод и запускаю его:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">ScriptEngine host = new ScriptEngine();\nhost.ImportNamespace(\"System\");\n\nSession session = host.CreateSession();\n\nConsole.WriteLine(\"Define your command:\");\nstring input;\nwhile ((input = Console.ReadLine()) != String.Empty)\n&#123;\n    try\n    &#123;\n        session.Execute(input);\n    &#125;\n    catch (CompilationErrorException ex)\n    &#123;\n        Console.WriteLine(ex.Message);\n    &#125;\n&#125;</code></pre>\n<p>Результат работы приложения:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/roslyn-as-a-scripting-language/229441ee/2@2x.png\"  alt=\"Использование Roslyn в качестве скриптового языка в своем приложении\"/>\n\n\n\n\n<p>Метод <code>Execute</code> возвращает результат в виде общего типа <code>object</code>. Это полезно тогда, когда исполняемое выражение возвращает какой-то результат, который можно использовать далее:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">try\n&#123;\n    var result = session.Execute(input);\n    Console.WriteLine(result);\n&#125;\ncatch (CompilationErrorException ex)\n&#123;\n    Console.WriteLine(ex.Message);\n&#125;</code></pre>\n<p>В результате:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/roslyn-as-a-scripting-language/a753a61f/3@2x.png\"  alt=\"Использование Roslyn в качестве скриптового языка в своем приложении\"/>\n\n\n\n\n<p>Если предполагается в качестве результата получить какой-то конкретный тип, то использовать <code>object</code> не очень удобно. В этом случае можно использовать перегруженный метод <code>Execute&lt;T&gt;()</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">try\n&#123;\n    int result = session.Execute&lt;int>(input);\n    Console.WriteLine(result);\n&#125;\ncatch (CompilationErrorException ex)\n&#123;\n    Console.WriteLine(ex.Message);\n&#125;</code></pre>\n<p>В целом, Roslyn Scripting API мне представляется очень преспективной разработкой. С его помощью можно настраивать отдельные части приложения с одной стороны гибко, а с другой — без необходимости перекомпиляции всего приложения. Если вдуматься, то в некотором смысле Roslyn Scripting API является конкуретном Workflow Foundation, которая так и не заняла место в сердцах разработчиков.</p>",
            "tags": [
                "C#",
                ".NET",
                "Roslyn"
            ],
            "banner_image": "https://blog.zwezdin.com/2013/roslyn-as-a-scripting-language/86d22c92/roslyn_as-a-scripting-language@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2013/windows-phone-calendar/",
            "url": "https://blog.zwezdin.com/2013/windows-phone-calendar/",
            "title": "Получение содержимого календаря в приложении Windows Phone 8",
            "date_published": "2013-02-07T12:00:00.000Z",
            "content_html": "<p><strong>Windows Phone 8 SDK</strong> позволяет сторонним программам получить доступ к содержимому календаря на устройстве. Это может быть полезным при создании приложений-планировщиков или приложений, чья логика зависит от содержимого календаря.</p>\n<p>Для получения доступа к календарю необходимо установить соответствующее разрешение – <code>ID_CAP_APPOINTMENTS</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/windows-phone-calendar/da6fe845/1@2x.png\"  alt=\"Получение содержимого календаря в приложении Windows Phone 8\"/>\n\n\n\n\n<p>Для получения доступа к содержимому календаря используется объект Appointments. Аналогично тому, как выбираются данные из <a href=\"/2013/windows-phone-contacts\">списка контактов</a>, для получения содержимого календаря нужно сформировать поисковый запрос. Поисковой запрос содержит промежуток времени за который нужно выбрать записи. Кроме того, можно указать максимальное число записей, которые могут быть получены и учетную запись, в которой эти записи хранятся. Для выполнения поискового запроса необходимо выполнить метод SearchAsync. Когда будет получен результат, операционная система сгенерирует событие <code>SearchCompleted</code>. Таким образом, код по получению данных из календаря за последние две недели будет выглядеть так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">protected override void OnNavigatedTo(NavigationEventArgs e)\n&#123;\n  Appointments calendar = new Appointments();\n  calendar.SearchCompleted += calendar_SearchCompleted;\n  calendar.SearchAsync(DateTime.Now.AddDays(-14), DateTime.Now, null);\n\n  base.OnNavigatedTo(e);\n&#125;\n\nvoid calendar_SearchCompleted(object sender, AppointmentsSearchEventArgs e)\n&#123;\n  AppointmentsList.ItemsSource = e.Results;\n&#125;</code></pre>\n<p>Добавим на страницу ListBox и определим для каждой записи календаря шаблон:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;ListBox x:Name=\"AppointmentsList\">\n  &lt;ListBox.ItemTemplate>\n    &lt;DataTemplate>\n      &lt;StackPanel Margin=\"0\">\n        &lt;TextBlock Text=\"&#123;Binding Subject&#125;\" Style=\"&#123;StaticResource PhoneTextNormalStyle&#125;\" FontWeight=\"Bold\" />\n        &lt;TextBlock Text=\"&#123;Binding Details&#125;\" Style=\"&#123;StaticResource PhoneTextNormalStyle&#125;\" />\n      &lt;/StackPanel>\n    &lt;/DataTemplate>\n  &lt;/ListBox.ItemTemplate>\n&lt;/ListBox></code></pre>\n<p>Этот код выберет из календаря все записи за последние две недели и отобразит их на странице.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2013/windows-phone-calendar/06587085/windows_phone-calendar@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2013/windows-phone-contacts/",
            "url": "https://blog.zwezdin.com/2013/windows-phone-contacts/",
            "title": "Получение списка контактов из приложения Windows Phone 8",
            "date_published": "2013-02-04T12:00:00.000Z",
            "content_html": "<p><strong>Windows Phone 8</strong> имеет встроенное приложение для хранения контактов. Контакты используются при совершении исходящих и получении входящих звонков, создании SMS или e-mail сообщения и в любом другом месте, где требуется контактная информация людей из записной книжки. Очевидно, что приложению тоже может потребоваться эта информация, поэтому давайте рассмотрим код, в котором мы получим доступ к списку контактов.</p>\n<p>Первое, что необходимо сделать — разрешить приложению получать доступ к списку контактов. Для этого необходимо установить разрешение <code>ID_CAP_CONTACTS</code> в манифесте приложения:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/windows-phone-contacts/ed42b331/1@2x.png\"  alt=\"Получение списка контактов из приложения Windows Phone 8\"/>\n\n\n\n\n<p>Для получения доступа к списку контактов используется объект <code>Contacts</code> из пространства имен <code>Microsoft.Phone.UserData</code>. Для получения списка контактов следует создать новый объект <code>Contacts</code> и выполнить поисковый запрос путем вызова метода <code>SearchAsync</code>. Для получения всех контактов нужно передать пустую строку. Второй параметр этого метода указывает какие именно данные нужно учитывать при выполнении поиска (например, можно искать по номеру телефона или e-mail). Третий параметр – объект состояния, который будет доступен при получении результата. При завершении операции поиска контактов будет сгенерировано событие <code>SearchCompleted</code>, в параметрах которого можно найти результат. Выведем все контакты из списка контактов в <code>ListBox</code> на форме при открытии страницы:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">protected override void OnNavigatedTo(NavigationEventArgs e)\n&#123;\n  Contacts contacts = new Contacts();\n\n  contacts.SearchCompleted += OnContactsSearchCompleted;\n  contacts.SearchAsync(String.Empty, FilterKind.None, null);\n\n  base.OnNavigatedTo(e);\n&#125;\n\nprivate void OnContactsSearchCompleted(object sender, ContactsSearchEventArgs e)\n&#123;\n  ContactsList.ItemsSource = e.Results;\n&#125;</code></pre>\n<p>Коллекция <code>Results</code> содержит список всех найденых контактов. Каждый объект этой коллекции содержит исчерпывающую информацию о контакте.</p>\n<p>Нередко требуется получить также и фото контакта. Для этого у каждого объекта <code>Contact</code> из коллекции <code>Results</code> существует метод <code>GetPicture</code>, который возвращает поток с изображением. Изменим немного обработчик события получения контактов:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void OnContactsSearchCompleted(object sender, ContactsSearchEventArgs e)\n&#123;\n  ContactsList.ItemsSource = e.Results.Select(x =>\n  &#123;\n    BitmapImage photo = null;\n    try\n    &#123;\n      Stream imageSource = x.GetPicture();\n\n      if (imageSource != null)\n      &#123;\n        photo = new BitmapImage();\n        photo.SetSource(imageSource);\n      &#125;\n    &#125;\n    catch (Exception ex)\n    &#123;\n      Debug.WriteLine(ex);\n    &#125;\n\n    return new\n    &#123;\n      Name = x.DisplayName,\n      Image = photo\n    &#125;;\n&#125;);\n&#125;</code></pre>\n<p>Здесь мы для каждого контакта проверяем наличие изображения и, если оно существует, получаем его. Для отображения в пользовательском интерфейсе подготовим объект <code>BitmapImage</code>. Теперь для отображения создадим <code>DataTemplate</code> для элемента <code>ListBox</code>, который отображает наши контакты:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;ListBox x:Name=\"ContactsList\">\n  &lt;ListBox.ItemTemplate>\n    &lt;DataTemplate>\n      &lt;Grid>\n        &lt;Grid.ColumnDefinitions>\n          &lt;ColumnDefinition Width=\"100\" />\n          &lt;ColumnDefinition Width=\"*\" />\n        &lt;/Grid.ColumnDefinitions>\n\n        &lt;Image Grid.Column=\"0\"\n               Margin=\"10\"\n               Width=\"100\" Height=\"100\"\n               Source=\"&#123;Binding Image&#125;\"/>\n\n        &lt;TextBlock Grid.Column=\"1\"\n                   Margin=\"10\"\n                   Text=\"&#123;Binding Name&#125;\"\n                   VerticalAlignment=\"Center\"\n                   Style=\"&#123;StaticResource PhoneTextNormalStyle&#125;\"/>\n      &lt;/Grid>\n    &lt;/DataTemplate>\n  &lt;/ListBox.ItemTemplate>\n&lt;/ListBox></code></pre>\n<p>Теперь приложение отображает список контактов с фотографиями.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2013/windows-phone-contacts/99595b58/windows_phone-contacts@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2013/windows-phone-camera-access/",
            "url": "https://blog.zwezdin.com/2013/windows-phone-camera-access/",
            "title": "Доступ к камере в Windows Phone 8",
            "date_published": "2013-01-26T12:00:00.000Z",
            "content_html": "<p>В любом устройстве на <strong>Windows Phone</strong> есть <strong>камера</strong>. Еще с первых версий WP7 SDK был возможен программный доступ к камере и осущствлялся при помощи <a href=\"/2010/windows-phone-tasks/\">стандартных диалогов</a>. Это позволяло запустить стандартное приложение для получения фотографий, получить изображение и передать его в приложение. Все это отлично работало в сценариях получения одной статической фотографии и дальнейшей её обработки. Однако, при таком подходе становятся невозможными многие сценарии, например возможность создания приложения дополненной реальности. Поэтому <strong>Windows Phone 8 SDK</strong> содержит механизмы прямого доступа к камере, минуя стандартный диалог операционной системы.</p>\n<p>Чтобы воспользоваться возможностями доступа к камере в манифесте приложения необходимо установить возможность <code>ID_CAP_ISV_CAMERA</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/windows-phone-camera-access/6f0318ad/1@2x.png\"  alt=\"Доступ к камере в Windows Phone 8\"/>\n\n\n\n\n<p>За доступ к камере отвечает объект <code>PhotoCamera</code>. Прежде чем проинициализировать камеру, следует проверить её доступность. Для этого следует вызвать статический метод <code>IsCameraSupported()</code> В <strong>Windows Phone 8</strong> можно работать с задней и основной камерой. При использовании камеры следует указать какую именно из них мы хотим использовать.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">if (PhotoCamera.IsCameraTypeSupported(CameraType.Primary) == true)\n&#123;\n  // ...\n&#125;</code></pre>\n<p>Чтобы отобразить картинку с камеры, необходимо добавить на страницу элемент <code>VideoBrush</code>. Для этого создадим визуальный объект, например <code>Rectangle</code>, и в качестве заливки установим <code>VideoBrush</code>:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Rectangle Height=\"400\" Width=\"400\">\n  &lt;Rectangle.Fill>\n    &lt;VideoBrush x:Name=\"video\">&lt;/VideoBrush>\n  &lt;/Rectangle.Fill>\n&lt;/Rectangle></code></pre>\n<p>Для простого доступа к <code>VideoBrush</code> я задал имя. Понятно, что этого можно не делать и найти <code>VideoBrush</code> через свойство <code>Fill</code> основного элемента.</p>\n<p>Теперь можно проинициализировать камеру. Для этого создадим объект <code>PhotoCamera</code> и укажем куда именно нужно отображать видеопоток:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">if (PhotoCamera.IsCameraTypeSupported(CameraType.Primary) == true)\n&#123;\n  _camera = new PhotoCamera(CameraType.Primary);\n  video.SetSource(_camera);\n&#125;</code></pre>\n<p>Теперь, если мы запустим наше приложение, то на странице появится видео, которое передается с камеры. Операционная система передает перевернутое изображение. Чтобы корректно отображать картинку, повернем изображение на <code>90</code> градусов. Для этого воспользуемся стандартными трансформациями элементов управления в XAML:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Rectangle Height=\"400\" Width=\"400\">\n  &lt;Rectangle.RenderTransform>\n    &lt;RotateTransform Angle=\"90\" CenterX=\"200\" CenterY=\"200\">&lt;/RotateTransform>\n  &lt;/Rectangle.RenderTransform>\n  &lt;Rectangle.Fill>\n    &lt;VideoBrush x:Name=\"video\">&lt;/VideoBrush>\n  &lt;/Rectangle.Fill>\n&lt;/Rectangle></code></pre>\n<p>Запустим приложение и увидим изображение с камеры:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/windows-phone-camera-access/8a074db9/2@2x.jpg\"  alt=\"Доступ к камере в Windows Phone 8\"/>\n\n\n\n\n<p>Для корректной работы приложения следует также позаботиться о том, чтобы удалять объект <code>PhotoCamera</code>, когда он становится ненужным. Поэтому я добавлю на страницу следующий код:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">protected override void OnNavigatedFrom(NavigationEventArgs e)\n&#123;\n  if (_camera != null)\n    _camera.Dispose();\n\n  base.OnNavigatedFrom(e);\n&#125;</code></pre>\n<p>Он уничтожает объект <code>PhotoCamera</code> как только мы уходим со страницы.</p>\n<p>Скорее всего вам захочется не только отображать изображение на странице, но и захватывать его для дальнейшей обработки или сохранения. Для того, чтобы получить изображение с камеры необходимо вызвать метод <code>CaptureImage</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void CaptureClick(object sender, RoutedEventArgs e)\n&#123;\n  _camera.CaptureImage();\n&#125;</code></pre>\n<p>Этот вызов сообщит камере о том, что приложение хочет получить полноразмерную картинку. Когда камера будет готова отдать изображение будет сгенерировано событие <code>CaptureImageAvailable</code>. Поэтому все, что нам необходимо сделать – подписаться на событие и обработать данные с камеры в обработчике:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">protected override void OnNavigatedTo(NavigationEventArgs e)\n&#123;\n  if (PhotoCamera.IsCameraTypeSupported(CameraType.Primary) == true)\n  &#123;\n    _camera = new PhotoCamera(CameraType.Primary);\n    _camera.CaptureImageAvailable += _cam_CaptureImageAvailable;\n    video.SetSource(_camera);\n  &#125;\n\n  base.OnNavigatedTo(e);\n&#125;\n\nvoid _cam_CaptureImageAvailable(object sender, ContentReadyEventArgs e)\n&#123;\n  // e.ImageStream\n&#125;</code></pre>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2013/windows-phone-camera-access/2f48c6b9/windows_phone-camera-access@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2013/windows-phone-lock-screen-part2/",
            "url": "https://blog.zwezdin.com/2013/windows-phone-lock-screen-part2/",
            "title": "Управление экраном блокировки в Windows Phone 8 (часть 2, сообщения на экране блокировки)",
            "date_published": "2013-01-17T12:00:00.000Z",
            "content_html": "<p><a href=\"/2013/windows-phone-lock-screen-part1\">Рассмотрев</a> возможности смены фонового изображения <strong>экрана блокировки Windows Phone 8</strong>, перейдем к рассмотрению возможностей отображения сообщений и статуса на экране блокировки. В качестве примера отображения статуса можно привести отображение предстоящих событий из календаря. Кроме того, на экране блокировки можно увидеть количество пропущенных звонков или непрочтенных сообщений электронной почты – аналогичные счетчики можно отображать из своего приложения.</p>\n<p><em>Технически</em>, статус приложения на главном экране — это та же самая информация, которая отображается на плитке приложения (<code>Live Tile</code>). Поэтому все, что необходимо сделать, указать приложению, что эту информацию нужно отображать также на экране блокировки. Для этого в файле манифеста приложение следует добавить соответствующие элементы <code>Extension</code>:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Extensions>\n  &lt;Extension ExtensionName=\"LockScreen_Notification_IconCount\"\n             ConsumerID=\"&#123;111DFF24-AA15-4A96-8006-2BFF8122084F&#125;\"\n             TaskID=\"_default\" />\n  &lt;Extension ExtensionName=\"LockScreen_Notification_TextField\"\n             ConsumerID=\"&#123;111DFF24-AA15-4A96-8006-2BFF8122084F&#125;\"\n             TaskID=\"_default\" />\n&lt;/Extensions></code></pre>\n<p>Теперь, приложение появится в списке <code>уведомления</code> на системном экране настроек экрана блокировки.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/windows-phone-lock-screen-part2/7f591a83/1@2x.jpg\"  alt=\"Управление экраном блокировки в Windows Phone 8 (часть 2, сообщения на экране блокировки)\"/>\n\n\n\n\n<p>Чтобы задать иконку для счетчика приложения, в манифесте приложения следует задать элемент <code>DeviceLockImageURI</code> для плитки приложения (например, <code>TemplateFlip</code>, которая создается по умолчанию при создании проекта):</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;DeviceLockImageURI IsRelative=\"true\" IsResource=\"false\">Assets\\LockScreenIcon.png&lt;/DeviceLockImageURI></code></pre>\n<p>Чтобы обновить текст на экране блокировки достаточно изменить информацию на плитке:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">ShellTile.ActiveTiles.First().Update(new FlipTileData &#123; Count = 22, WideBackContent = \"Hello!\" &#125;);</code></pre>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2013/windows-phone-lock-screen-part2/1ebeb081/windows_phone-lock-screen-part2@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2013/windows-phone-lock-screen-part1/",
            "url": "https://blog.zwezdin.com/2013/windows-phone-lock-screen-part1/",
            "title": "Управление экраном блокировки в Windows Phone 8 (часть 1, фоновое изображение)",
            "date_published": "2013-01-14T12:00:00.000Z",
            "content_html": "<p>Одно из нововведений в <strong>Windows Phone 8 SDK</strong> — возможность интеграции приложений с экраном блокировки устройства. В этой части будет рассмотрена возможность смены фонового изображения. Эта возможность будет полезна для приложений, работающих с изображениями (например, клиенты социальных сетей, приложения для обработки фото и т.д.).</p>\n<p>Для того, чтобы приложение стало провайдером фонового изображения экрана блокировки, необходимо это явно указать в манифесте приложения. Без этого все дальнейшие шаги будут невозможны. Кроме того, после задания этого разрешения, приложение появится в списке в системном окне настройки экрана блокировки.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/windows-phone-lock-screen-part1/6b38907b/1@2x.jpg\"  alt=\"Управление экраном блокировки в Windows Phone 8 (часть 1, фоновое изображение)\"/>\n\n\n\n\n<p>Чтобы задать это разрешение, необходимо открыть файл манифеста приложения в XML-редакторе и добавить туда <code>Extension</code>:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Tokens>\n  &lt;!-- ... -->\n&lt;/Tokens>\n&lt;Extensions>\n  &lt;Extension ExtensionName=\"LockScreen_Background\" ConsumerID=\"&#123;111DFF24-AA15-4A96-8006-2BFF8122084F&#125;\" TaskID=\"_default\" />\n&lt;/Extensions>\n&lt;ScreenResolutions>\n  &lt;!-- ... -->\n&lt;/ScreenResolutions></code></pre>\n<p>После того, как манифест был изменен, необходимо сделать сделать три действия: 1) задать фоновое изображение, которое будет использоваться по умолчанию; 2) добавить обработку ситуации перехода к приложению из экрана системных настроек; 3) если это необходимо, изменять фоновое изображение экрана блокировки.</p>\n<h2>Стандартное фоновое изображение</h2><p>Когда мы указали, что приложение может быть использовано в качестве провайдера фонового изображения (путем добавления элемента <code>Extension</code> в файл манифеста), оно появится в списке приложений на экране настроек. Пользователь может выбрать это приложение в качестве источника фонового изображение, но само приложение не запустить. В этом случае у приложения нет возможности запустить какой-либо код и инциализировать фоновое изображение. Для таких случаев в приложении можно добавить изображение, которое будет использовано по умолчанию.</p>\n<p>Чтобы добавить стандартное фоновое изображение, необходимо в приложение добавить файл с именем <code>DefaultLockScreen.jpg</code> (имя файла должно быть именно таким) и установить для него <code>Build Action</code> в режим <code>Content</code>. Это позволит включить данный файл в пакет приложения. Теперь это изображение будет использоваться в качестве стандартного фонового изображения, когда в приложение будет использовано в качестве провайдера фонового изображения.</p>\n<h2>Обработка ситуации перехода к приложению из экрана системных настроек</h2><p>Приложение можно запустить из экрана системных настроек. Для этого нужно нажать кнопку <code>открыть приложение</code>, когда приложение выбрано в списке.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/windows-phone-lock-screen-part1/6b38907b/2@2x.jpg\"  alt=\"Управление экраном блокировки в Windows Phone 8 (часть 1, фоновое изображение)\"/>\n\n\n\n\n<p>Очевидно, что такую ситуацию нужно обрабатывать, чтобы сразу переходить к настройке фонового изображения. Чтобы добавить такую обработку, на главной странице следует переопределить метод <code>OnNavigatedTo</code>. Если мы перешли к странице сразу после запуска приложения (<code>e.NavigationMode == NavigationMode.New</code>) и существует параметр в <code>QueryString</code> с именем <code>WallpaperSettings</code> и равный <code>1</code>, то это как раз случай перехода к приложению из настроек. Код для перехода к странице настроек внутри приложения будет выглядеть следующим образом:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">protected override void OnNavigatedTo(NavigationEventArgs e)\n&#123;\n  if (IsNavigatedFromWallpaperSettings(e, NavigationContext.QueryString) == true)\n    NavigationService.Navigate(new Uri(\"/LockScreenSettings.xaml\", UriKind.Relative));\n\n  base.OnNavigatedTo(e);\n&#125;\n\nprivate bool IsNavigatedFromWallpaperSettings(NavigationEventArgs e, IDictionary&lt;string, string> queryString)\n&#123;\n  if (e.NavigationMode == NavigationMode.New)\n  &#123;\n    string wallpaperSettings;\n    return (queryString.TryGetValue(\"WallpaperSettings\", out wallpaperSettings) == true) &amp;&amp; (wallpaperSettings == \"1\");\n  &#125;\n  else\n    return false;\n&#125;</code></pre>\n<p>Теперь при переходе к приложению из экрана настроек будет открываться страница <code>LockScreenSettings.xaml</code>.</p>\n<h2>Изменение фонового изображения экрана блокировки из приложения</h2><p>Прежде чем приложением сможет изменять фон экрана блокировки, следует убедиться используется ли это приложение в качестве источника фонового изображения. Для этого используется объект <code>LockScreenManager</code>. Кроме того, с помощью этого объекта можно запросить у пользователя использовать данное приложение для экрана блокировки. Разместим на главной странице кнопку, которая будет запрашивать данное разрешение. Кнопка будет скрываться, если приложение уже итак используется для экрана блокировки:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">protected override void OnNavigatedTo(NavigationEventArgs e)\n&#123;\n  // ..\n\n  UseAppAsBackground.Visibility = LockScreenManager.IsProvidedByCurrentApplication ? Visibility.Collapsed : Visibility.Visible;\n\n  base.OnNavigatedTo(e);\n&#125;\n\n\nprivate async void OnUseAppAsBackgroundClick(object sender, RoutedEventArgs e)\n&#123;\n  LockScreenRequestResult result = await LockScreenManager.RequestAccessAsync();\n\n  if (result == LockScreenRequestResult.Denied)\n    MessageBox.Show(\"Unable to set this app as lock screen background provider.\");\n  else\n    MessageBox.Show(\"The app is lock screen background provider now.\");\n\n  UseAppAsBackground.Visibility = LockScreenManager.IsProvidedByCurrentApplication ? Visibility.Collapsed : Visibility.Visible;\n&#125;</code></pre>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/windows-phone-lock-screen-part1/e911b081/3@2x.jpg\"  alt=\"Управление экраном блокировки в Windows Phone 8 (часть 1, фоновое изображение)\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/windows-phone-lock-screen-part1/109560ed/4@2x.jpg\"  alt=\"Управление экраном блокировки в Windows Phone 8 (часть 1, фоновое изображение)\"/>\n\n\n\n\n<p>Перейдем к странице настроек фонового изображения. Разместим несколько кнопок, каждая из которых будет устанавливать собственное изображение в качестве фона экрана блокировки. Обработчик нажатия по такой кнопке будет иметь следующий код:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void OnSetBackround1Click(object sender, RoutedEventArgs e)\n&#123;\n  if (LockScreenManager.IsProvidedByCurrentApplication == true)\n    LockScreen.SetImageUri(new Uri(\"ms-appx:///LockScreen1.jpg\"));\n&#125;</code></pre>\n<p>При этом в проект следует добавить изображения, путь до которых указывается в методе <code>SetImageUri</code> (<code>Build Action</code> = <code>Content</code>).</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/windows-phone-lock-screen-part1/9616659b/5@2x.jpg\"  alt=\"Управление экраном блокировки в Windows Phone 8 (часть 1, фоновое изображение)\"/>\n\n\n\n\n<p>Таким образом, приложение готово к тому, чтобы устанавливать фоновое изображение экрана блокировки.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2013/windows-phone-lock-screen-part1/570871a1/windows_phone-lock-screen-part1@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2013/windows-phone-voice-commands/",
            "url": "https://blog.zwezdin.com/2013/windows-phone-voice-commands/",
            "title": "Голосовые команды в Windows Phone 8 (speech-to-text)",
            "date_published": "2013-01-02T12:00:00.000Z",
            "content_html": "<p>Одним из значительных улучшений в <strong>Windows Phone 8</strong> является <strong>голосовое управление</strong> устройством. Пользователь может позвонить кому-либо или открыть нужное приложение, используя голосовые команды.</p>\n<p>По умолчанию эта возможность присутствует для всех приложений. Для того, чтобы запустить приложение нужно удерживать аппаратную кнопку Windows и после появления диалога сказать, например: <code>Открыть календарь</code>. Аналогично можно запускать и другие приложения, называя его имя. В большинстве случаев этого достаточно. Тем не менее, разработчики приложений имеют возможность расширить эту функциональность для своего приложения, определяя новые действия при использовании голосовых команд.</p>\n<p>Для того, чтобы добавить возможности голосового управления необходимо разрешить следующие возможности: <code>ID_CAP_NETWORKING</code>, <code>ID_CAP_MICROPHONE</code>, <code>ID_CAP_SPEECH_RECOGNITION</code> (как и в случае с простым <a href=\"/2012/windows-phone-speech-to-text\">распознованием речи</a>).</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/windows-phone-voice-commands/8e06e16e/1@2x.png\"  alt=\"Голосовые команды в Windows Phone 8 (speech-to-text)\"/>\n\n\n\n\n<p>Теперь нам следует определить список команд, которые будет обрабатывать наше приложение. Команда должна содержать имя приложения и последующую фразу для определения необходимого действия. Для того, чтобы добавить обработку команд, необходимо создать словарь команд, который является XML-файлом с определенной схемой. Для создания такого словаря можно воспользоваться готовым шаблоном <code>Voice Command Definition</code>:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2013/windows-phone-voice-commands/e766f50d/2@2x.png\"  alt=\"Голосовые команды в Windows Phone 8 (speech-to-text)\"/>\n\n\n\n\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;VoiceCommands xmlns=\"http://schemas.microsoft.com/voicecommands/1.0\">\n  &lt;CommandSet xml:lang=\"en-US\">\n    &lt;CommandPrefix>Contoso Rodeo&lt;/CommandPrefix>\n    &lt;Example> play a new game &lt;/Example>\n\n    &lt;Command Name=\"PlayGame\">\n      &lt;Example> play a new game &lt;/Example>\n      &lt;ListenFor> [and] play [a] new game &lt;/ListenFor>\n      &lt;ListenFor> [and] start [a] new game &lt;/ListenFor>\n      &lt;Feedback> Starting a new game... &lt;/Feedback>\n      &lt;Navigate />\n    &lt;/Command>\n\n    &lt;Command Name=\"PlayLevel\">\n      &lt;Example> replay level two &lt;/Example>\n      &lt;ListenFor> replay level &#123;number&#125; &lt;/ListenFor>\n      &lt;Feedback> Going to level &#123;number&#125;... &lt;/Feedback>\n      &lt;Navigate />\n    &lt;/Command>\n\n    &lt;Command Name=\"PlayUnknownLevel\">\n      &lt;Example> replay level two &lt;/Example>\n      &lt;ListenFor> [and] replay level &#123;*&#125; &lt;/ListenFor>\n      &lt;Feedback> Unknown level; going to level selection... &lt;/Feedback>\n      &lt;Navigate Target=\"LevelSelect.xaml\" />\n    &lt;/Command>\n\n    &lt;PhraseList Label=\"number\">\n      &lt;Item> one &lt;/Item>\n      &lt;Item> two &lt;/Item>\n      &lt;Item> three &lt;/Item>\n    &lt;/PhraseList>\n\n  &lt;/CommandSet>\n&lt;/VoiceCommands></code></pre>\n<p>Словарь голосовых команд содержит набор элементов <code>CommandSet</code>, каждый из которых описывает соответствующую команду. Элемент <code>CommandSet</code> внутри себя содержит элементы <code>CommandPrefix</code> (фраза, которая произносится перед командой, обычно это – имя приложения), <code>Example</code> (фраза, которая отображается в подсказке) и <code>Command</code> (содержит описание команды). Элемент <code>Command</code> – самый важный, в нем описывается структура голосовой команды. Внутри этого элемента присутствуют элементы <code>Example</code> (подсказка), <code>ListenFor</code> (фраза команды), <code>Feedback</code> (фраза, которая отображается пользователю, когда команда выполняется) и <code>Navigate</code> (адрес страницы, которая будет отображаться, если нужно запускать приложение не с главной страницы).</p>\n<p>Элемент <code>ListenFor</code> определяет фразу, которую нужно произнести для выполнения команды. Таких фраз может быть несколько.</p>\n<p>Содержимое элемента <code>ListenFor</code> имеет свои особенности. Во-первых, можно определять необязательные слова в фразе. Такие слова заключаются в квадратные скобки. Например, запись <code>[with] show text</code> означает, что слово <code>with</code> может быть произнесено, а может быть и пропущено.</p>\n<p>Другой особенностью элемента <code>ListenFor</code> является то, что в конкретном месте фразы мы можем определить словарь допустимых слов. Для этого в тексте команды вставляется имя словаря, заключенного в фигурные скобки <code>&#123;number&#125;</code>. При этом, после элемента <code>Command</code>, мы должны определить элемент <code>PhraseList</code>, где будут указаны допустимые значения.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Command ...>\n  &lt;!-- ... -->\n&lt;/Command>\n\n&lt;PhraseList Label=\"number\">\n  &lt;Item> one &lt;/Item>\n  &lt;Item> two &lt;/Item>\n  &lt;Item> three &lt;/Item>\n&lt;/PhraseList></code></pre>\n<p>При необходимости мы можем изменить содержимое этого словаря из кода:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">await VoiceCommandService.InstalledCommandSets[\"myCommandSet\"].UpdatePhraseListAsync(\"number\", new[] &#123; \"first\", \"second\" &#125;);</code></pre>\n<p>В данном случае мы выбираем из всех наборов команд (<code>CommandSets</code>) набор с именем <code>myCommandSet</code>. Чтобы задать это имя, необходимо указать атрибут Name в XML-словаре голосовых команд для элемента <code>CommandSet</code>:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;CommandSet xml:lang=\"en-US\" Name=\"myCommandSet\"></code></pre>\n<p>Метод <code>UpdatePhraseListAsync</code> выполняет обновление словаря возможных фраз для выбранного <code>CommandSet</code>, при это имя словаря задается первым параметром этого метода.</p>\n<p>Для того, чтобы голосовые команды заработали для приложения, необходимо зарегистрировать словарь при первом запуске приложения:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private async void InitializeVoiceCommands()\n&#123;\n  if (VoiceCommandService.InstalledCommandSets.Any() == false)\n  &#123;\n    await VoiceCommandService.InstallCommandSetsFromFileAsync(new System.Uri(@\"ms-appx:///VoiceCommandDefinition1.xml\"));\n  &#125;\n&#125;</code></pre>\n<p>Вызов этого метода можно добавить, например, в метод <code>InitializePhoneApplication</code>, который находится в файле <code>App.xaml.cs</code>. Теперь после первого запуска, можно запускать приложение, используя описанные в словаре команды. Для того, чтобы обработать конкретную команду, можно переопределить метод страницы <code>OnNavigatedTo</code>, при вызове которого прочитать содержимое свойства <code>NavigationContext.QueryString</code>, где и будет находиться обработанная информация.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2013/windows-phone-voice-commands/74ad2217/windows_phone-voice-commands@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2012/windows-phone-speech-to-text/",
            "url": "https://blog.zwezdin.com/2012/windows-phone-speech-to-text/",
            "title": "Распознование голосовой речи в Windows Phone 8 (speech-to-text)",
            "date_published": "2012-12-24T12:00:00.000Z",
            "content_html": "<p>Помимо <a href=\"/2012/windows-phone-text-to-speech\">синтеза</a> голосовой речи <strong>Windows Phone 8 SDK</strong> содержит API для распознования голосовой речи и голосовых комманд. В этой статье мы рассмотрим первую часть – каким образом можно распозновать отдельные слова или фразы при помощи данного API.</p>\n<p>Чтобы воспользоваться услугами этого API нам необходимо разрешить следующие возможности для нашего WP8 приложения: <code>ID_CAP_NETWORKING</code>, <code>ID_CAP_MICROPHONE</code>, <code>ID_CAP_SPEECH_RECOGNITION</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2012/windows-phone-speech-to-text/46eb88c1/1@2x.png\"  alt=\"Распознование голосовой речи в Windows Phone 8 (speech-to-text)\"/>\n\n\n\n\n<p>Разрешение <code>ID_CAP_NETWORKING</code> нужно из-за того, что процесс распознования происходит не на устройстве, а в облаке. Поэтому для пользования этой возможностью вам необходимо иметь подключение к сети.</p>\n<p>Список поддерживаемых для распознования языков определен в специальном словаре, который доступен при обращении к свойству <code>InstalledSpeechRecognizers.All</code>. Разместим на странице <code>ListPicker</code>, содержащий все поддерживаемые языки, а также кнопку для запуска распознования текста:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;toolkit:ListPicker x:Name=\"Recognizers\">\n  &lt;toolkit:ListPicker.ItemTemplate>\n    &lt;DataTemplate>\n      &lt;StackPanel Orientation=\"Horizontal\">\n        &lt;TextBlock Text=\"&#123;Binding DisplayName&#125;\"/>\n        &lt;TextBlock Text=\"-\" Margin=\"5,0\"/>\n        &lt;TextBlock Text=\"&#123;Binding Language&#125;\"/>\n      &lt;/StackPanel>\n    &lt;/DataTemplate>\n  &lt;/toolkit:ListPicker.ItemTemplate>\n  &lt;toolkit:ListPicker.FullModeItemTemplate>\n    &lt;DataTemplate>\n      &lt;StackPanel Margin=\"0,0,0,20\">\n        &lt;TextBlock Text=\"&#123;Binding DisplayName&#125;\"/>\n        &lt;TextBlock Text=\"&#123;Binding Language&#125;\"/>\n      &lt;/StackPanel>\n    &lt;/DataTemplate>\n  &lt;/toolkit:ListPicker.FullModeItemTemplate>\n&lt;/toolkit:ListPicker>\n\n&lt;Button Click=\"OnListenClick\">Listen&lt;/Button></code></pre>\n<p>Заполним <code>ListPicker</code> информацией о доступных языках:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">Recognizers.ItemsSource = InstalledSpeechRecognizers.All;</code></pre>\n<p>Простейший код для распознования будет выглядеть вот так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private async void OnListenClick(object sender, RoutedEventArgs e)\n&#123;\n  SpeechRecognizerUI recognizer = new SpeechRecognizerUI();\n\n  recognizer.Recognizer.SetRecognizer((SpeechRecognizerInformation)Recognizers.SelectedItem);\n\n  SpeechRecognitionUIResult result = await recognizer.RecognizeWithUIAsync();\n  if (result.ResultStatus == SpeechRecognitionUIStatus.Succeeded)\n  &#123;\n    MessageBox.Show(result.RecognitionResult.Text);\n  &#125;\n&#125;</code></pre>\n<p>При нажатии на кнопку появится окно операционной системы, указывающее на распознование речи. Это окно имеет ряд настроек, которые можно изменять из кода приложения. Доступны они через свойство <code>Settings</code> объекта <code>SpeechRecognizerUI</code>:</p>\n<ul>\n<li><code>ListenText</code> - заголовок окна распознования речи;</li>\n<li><code>ExampleText</code> - пример того, что пользователь может сказать устройству (например, “да/нет” на односложный вопрос);</li>\n<li><code>ShowConfirmation</code> - устанавливает, необходимо ли отображать подтверждение об успешности распознования (если опция отключена, то приложение должно само взять на себя обязанность об уведомлении пользователя в случае неудачи);</li>\n<li><code>ReadoutEnabled</code> - устанавливает, необходимо ли зачитывать фразу после распознования.</li>\n</ul>\n<p>Пример кода, в котором задаются параметры диалогового окна распознования:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private async void OnListenClick(object sender, RoutedEventArgs e)\n&#123;\n  SpeechRecognizerUI recognizer = new SpeechRecognizerUI();\n  recognizer.Settings.ExampleText = \"Fine\";\n  recognizer.Settings.ListenText = \"How are you?\";\n  recognizer.Settings.ReadoutEnabled = false;\n  recognizer.Settings.ShowConfirmation = false;\n\n  recognizer.Recognizer.SetRecognizer((SpeechRecognizerInformation)Recognizers.SelectedItem);\n\n  SpeechRecognitionUIResult result = await recognizer.RecognizeWithUIAsync();\n  if (result.ResultStatus == SpeechRecognitionUIStatus.Succeeded)\n  &#123;\n    MessageBox.Show(result.RecognitionResult.Text);\n  &#125;\n&#125;</code></pre>\n<p>Если логика приложения не требует отображения системного окна распознования речи можно заменить использование метода <code>RecognizeWithUIAsync</code> на <code>RecognizeAsync</code>, которое доступно через свойство <code>Recognizer</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private async void OnListenWitoutDialogClick(object sender, RoutedEventArgs e)\n&#123;\n  SpeechRecognizerUI recognizer = new SpeechRecognizerUI();\n  recognizer.Settings.ExampleText = \"Fine\";\n  recognizer.Settings.ListenText = \"How are you?\";\n  recognizer.Settings.ReadoutEnabled = false;\n  recognizer.Settings.ShowConfirmation = false;\n\n  recognizer.Recognizer.SetRecognizer((SpeechRecognizerInformation)Recognizers.SelectedItem);\n\n  SpeechRecognitionResult result = await recognizer.Recognizer.RecognizeAsync();\n  MessageBox.Show(result.Text);\n&#125;</code></pre>\n<p>Если логика приложения предполагает в качестве результата одну из нескольких заранее предопределенных фраз, то можно определить свой словарь, который будет использоваться при распозновании. Задать собственный словарь можно через свойство <code>Recognizer.Grammars</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">SpeechRecognizerUI recognizer = new SpeechRecognizerUI();\n// ...\nrecognizer.Recognizer.Grammars.AddGrammarFromList(\"MyGrammar\", new[] &#123; \"fine\", \"bad\" &#125;);\n\nSpeechRecognitionUIResult result = await recognizer.RecognizeWithUIAsync();</code></pre>\n<p>В итоге мы получили приложение, способное распозновать голосовую речь в рамках нашего приложения:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2012/windows-phone-speech-to-text/45b2715a/2@2x.jpg\"  alt=\"Распознование голосовой речи в Windows Phone 8 (speech-to-text)\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2012/windows-phone-speech-to-text/3f237c44/3@2x.jpg\"  alt=\"Распознование голосовой речи в Windows Phone 8 (speech-to-text)\"/>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2012/windows-phone-speech-to-text/3a85250e/windows_phone-speech-to-text@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2012/windows-phone-text-to-speech/",
            "url": "https://blog.zwezdin.com/2012/windows-phone-text-to-speech/",
            "title": "Синтез голосовой речи из текста в Windows Phone 8 (text-to-speech)",
            "date_published": "2012-11-09T12:00:00.000Z",
            "content_html": "<p>С выходом Windows Phone 8 SDK появилась возможность <strong>управлять устройством голосом</strong>. Для того, чтобы это стало возможным необходимо две вещи — распознование голосовых команд (чтобы пользователь мог управлять устройством) и синтез голосовой речи из текста (чтобы устройство могло озвучить результат).</p>\n<p>Чтобы использовать возможности работы с речью необходимо установить <code>ID_CAP_SPEECH_RECOGNITION</code> capability в манифесте проекта:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2012/windows-phone-text-to-speech/b21193dd/1@2x.png\"  alt=\"Синтез голосовой речи из текста в Windows Phone 8 (text-to-speech)\"/>\n\n\n\n\n<p>Для синтеза речи в WP8 используется объект <code>SpeechSynthesizer</code>. Самый простой вариант генерации голосовой речи состоит из двух строк кода:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private async void OnSayButtonClick(object sender, RoutedEventArgs e)\n&#123;\n  SpeechSynthesizer speech = new SpeechSynthesizer();\n  await speech.SpeakTextAsync(Content.Text);\n&#125;</code></pre>\n<p>Голос, который будет использоваться в этом случае будет определяться исходя из региональных настроек устройства. При необходимости можно изменить используемый голос, при этом выбрать национальность и пол говорящего. Список всех голосов можно получить через статическое свойство <code>InstalledVoices.All</code>.</p>\n<p>Разместим <code>ListPicker</code> на странице:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;toolkit:ListPicker x:Name=\"Voices\">\n  &lt;toolkit:ListPicker.ItemTemplate>\n    &lt;DataTemplate>\n      &lt;StackPanel Orientation=\"Horizontal\">\n        &lt;TextBlock Text=\"&#123;Binding DisplayName&#125;\"/>\n        &lt;TextBlock Text=\"-\" Margin=\"5,0\"/>\n        &lt;TextBlock Text=\"&#123;Binding Language&#125;\"/>\n      &lt;/StackPanel>\n    &lt;/DataTemplate>\n  &lt;/toolkit:ListPicker.ItemTemplate>\n  &lt;toolkit:ListPicker.FullModeItemTemplate>\n    &lt;DataTemplate>\n      &lt;StackPanel Margin=\"0,0,0,20\">\n        &lt;TextBlock Text=\"&#123;Binding DisplayName&#125;\"/>\n        &lt;TextBlock Text=\"&#123;Binding Language&#125;\"/>\n        &lt;TextBlock Text=\"&#123;Binding Gender&#125;\"/>\n      &lt;/StackPanel>\n    &lt;/DataTemplate>\n  &lt;/toolkit:ListPicker.FullModeItemTemplate>\n&lt;/toolkit:ListPicker></code></pre>\n<p>И заполним его списком установленных голосов:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">Voices.ItemsSource = InstalledVoices.All;</code></pre>\n<p>Для того, чтобы использовать выбранный голос при синтезе речи  необходимо вызвать метод <code>SetVoice</code> для объекта <code>SpeechSynthesizer</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private async void OnSayButtonClick(object sender, RoutedEventArgs e)\n&#123;\n  string content = Content.Text;\n\n  SpeechSynthesizer speech = new SpeechSynthesizer();\n  speech.SetVoice((VoiceInformation)Voices.SelectedItem);\n  await speech.SpeakTextAsync(content);\n&#125;</code></pre>\n<p>На этом мы получили приложение, которое воспроизводит заданный текст:</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2012/windows-phone-text-to-speech/47fa8811/2@2x.jpg\"  alt=\"Синтез голосовой речи из текста в Windows Phone 8 (text-to-speech)\"/>\n\n\n\n\n<p>Для более точной настройки речи можно воспользоваться специальным языком <a href=\"http://www.w3.org/TR/speech-synthesis/\">SSML</a> (<code>Speech Synthesis Markup Language</code>), который также поддерживается в WP8. Для этого у объекта <code>SpeechSynthesizer</code> поддерживаются два метода – <code>SpeakSsmlAsync</code> (проигрывает <code>SSML</code> из текстовой переменной) и <code>SpeakSsmlFromUriAsync</code> (проигрывает <code>SSML</code> из файла).</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private async void OnSaySsmlButtonClick(object sender, RoutedEventArgs e)\n&#123;\n  SpeechSynthesizer speech = new SpeechSynthesizer();\n  await speech.SpeakSsmlAsync(@\"&lt;speak version=\"\"1.0\"\" xmlns=\"\"http://www.w3.org/2001/10/synthesis\"\" xml:lang=\"\"ru-Ru\"\">\n    &lt;voice gender=\"\"female\"\">\n      Здравствуйте, Александр!&lt;break/>\n      Рада вас видеть!\n    &lt;/voice>\n\n    &lt;voice gender=\"\"male\"\">\n      Здравствуйте, Ирина! И я очень рад.\n    &lt;/voice>\n  &lt;/speak>\");\n&#125;</code></pre>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2012/windows-phone-text-to-speech/15377914/windows_phone-text-to-speech@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2011/windows-phone-urimapper/",
            "url": "https://blog.zwezdin.com/2011/windows-phone-urimapper/",
            "title": "Использование UriMapper в приложениях WP7",
            "date_published": "2011-12-29T12:00:00.000Z",
            "content_html": "<p>Известно, что концепция приложений WP7 включает в себя страницы. <strong>Адреса страниц</strong> в приложении формируются исходя из физического положения страницы в проекте. В этой заметке я расскажу про объект <code>UriMapper</code>, который позволяет задать адрес страницы независимо от размещения страницы в проекте.</p>\n<p>Представим, что в приложении имеется несколько страниц. Для перехода от одной странице к другой следует воспользоваться объектом <code>NavigationService</code> и указать адрес страницы, к которой мы хотим перейти:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">NavigationService.Navigate(new Uri(\"&lt;strong>/SettingsPage.xaml&lt;/strong>\", UriKind.Relative));</code></pre>\n<p>Адрес страницы формируется из ее названия и расположения внутри проекта. Так, например, если мы поместим эту страницу в подпапку <code>Pages</code>, то адрес страницы изменится на <code>/Pages/SettingsPage.xaml</code>; при переносе страницы в другую сборку, адрес также будет изменен.</p>\n<p>Эта проблема оказывается действительно неприятной, когда пересматривается структура папок проекта, а то и структура самого проекта. Самое неприятное в этой ситуации то, что при наличии какой-то более-менее сложной навигации можно просто упустить из виду некоторые вызовы <code>NavigationService</code> и допустить ошибку в приложении. Visual Studio и компилятор такие ошибки отловить не смогут. Как результат, при переходе по адресу несуществующей странице приложение просто завершится.</p>\n<p>Для того, чтобы этого избежать можно использовать объект <code>UriMapper</code>, который позволяет определить адреса страниц независимо от их расположения в проекте. Фактически, мы задаем некоторый алиас для страницы и используем именно его для перехода на страницы. В этом случае при пересмотре структуры проекта нужно будет только сменить настройки для преобразования алиасов в реальные адреса страницы.</p>\n<p>Для того, чтобы использовать <code>UriMapper</code> в своем приложении нужно выполнить следующие действия:</p>\n<p><strong>Шаг 1:</strong> Создать и определить настройки объекта <code>UriMapper</code>. Обычно я это делаю в ресурсах приложения, в файле <code>App.xaml</code>. Естественно, это совсем необязательно.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Application.Resources>\n  &lt;phone:UriMapper x:Name=\"ApplicationUriMapper\">\n    &lt;phone:UriMapping Uri=\"/Home\" MappedUri=\"/Views/MainPage.xaml\"/>\n    &lt;phone:UriMapping Uri=\"/Session/&#123;ID&#125;\" MappedUri=\"/Views/Session.xaml?ID=&#123;ID&#125;\"/>\n    &lt;phone:UriMapping Uri=\"/Sessions\" MappedUri=\"/Views/SessionTracks.xaml\"/>\n    &lt;phone:UriMapping Uri=\"/About\" MappedUri=\"/Views/About.xaml\"/>\n  &lt;/phone:UriMapper>\n&lt;/Application.Resources></code></pre>\n<p><strong>Шаг 2:</strong> Указать приложению, что нужно использовать данный <code>UriMapper</code>. Для этого при инициализации приложения необходимо задать свойство <code>UriMapper</code> для объекта <code>PhoneApplicationFrame</code>. Понятно, что здесь можно сконструировать объект <code>UriMapper</code>, но поскольку он у нас уже определен в ресурсах, то возьмем его прямо оттуда:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void InitializePhoneApplication()\n&#123;\n  if (phoneApplicationInitialized)\n    return;\n\n  RootFrame = new PhoneApplicationFrame();\n  RootFrame.UriMapper = (UriMapper)Resources[\"ApplicationUriMapper\"];</code></pre>\n<p><strong>Шаг 3:</strong> Использовать в качестве адресов указанные алиасы:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">NavigationService.Navigate(new Uri(\"&lt;strong>/About&lt;/strong>\", UriKind.Relative));</code></pre>\n<p>При определении адресов можно пользоваться шаблонами. Например, при определении <code>UriMapper</code> выше используется алиас <code>/Session/&#123;ID&#125;</code>, значение параметра <code>ID</code> потом подставляется в конечный адрес. Аналогично можно использовать несколько параметров.</p>\n<p>Используя такой несложный прием вы можете избежать проблем с путаницей адресов страниц в будущем, если потребуется изменить структуру страницы.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2011/windows-phone-urimapper/5b3d70e9/windows_phone-urimapper@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2011/windows-phone-gesture-service/",
            "url": "https://blog.zwezdin.com/2011/windows-phone-gesture-service/",
            "title": "Как использовать GestureService и добавить обработчики жестов из кода",
            "date_published": "2011-11-24T12:00:00.000Z",
            "content_html": "<p>В <a href=\"http://silverlight.codeplex.com/releases/view/75888\">Windows Phone 7 Toolkit</a> доступен объект для обработки жестов – <code>GestureService</code>. Как известно, этот сервис позволяет обрабатывать стандартные для Windows Phone 7 жесты. Однако, многие примеры использования <code>GestureService</code> сводятся к применению его в XAML. В этой небольшой заметке я покажу как использовать <code>GestureService</code> из кода.</p>\n<p>Для начала работы нам необходимо подключить <strong>Windows Phone 7 Toolkit</strong> к проекту. Для этого нужно добавить ссылку на сборку <code>Microsoft.Phone.Controls.Toolkit.dll</code>.</p>\n<h2>Как использовать <code>GestureService</code> из кода</h2><p>Для использования <code>GestureService</code> из кода, нам нужно получить доступ к объекту <code>GestureListener</code>, который будет ассоциирован с некоторой областью на экране. В рамках этой области (<code>source</code>) будут отслеживаться наши жесты.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">var listener = GestureService.GetGestureListener(source);</code></pre>\n<p>Объект <code>GestureListener</code> готов к работе, теперь можно обрабатывать его жесты.</p>\n<h2>Как добавить обработчик жестов</h2><p>Для обработки конкретного жеста следует подписаться на соответствующее событие объекта <code>GestureListener</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">listener.Tap += OnTapHandler;</code></pre>\n<p>Безусловно можно подписаться и на другие жесты, используя аналогичный подход.</p>\n<h2>Где разместить код обработки жестов</h2><p>Подписываться на события жестов можно в любой момент после создания соответствующих элементов управления. Например, это можно делать в конструкторе страницы (конечно, более правильно это делать во <code>ViewModel</code>, но сейчас мы не будем усложнять пример). Например, следующий код создает новый элемент управления (<code>Rectangle</code>) на странице и добавляет для него обработчик жестов.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public partial class Page1 : PhoneApplicationPage\n&#123;\n    Rectangle _source;\n\n    public MainPage()\n    &#123;\n        InitializeComponent();\n        _source = new Rectangle &#123;Height = 200, Width = 200, Fill = new SolidColorBrush(Colors.Green)&#125;;\n        ContentPanel.Children.Add(_source);\n\n        var listener = GestureService.GetGestureListener(_source);\n        listener.Tap += OnTapHandler;\n    &#125;\n\n    private void OnTapHandler(object sender, GestureEventArgs e)\n    &#123;\n        _source.Fill = new SolidColorBrush(Colors.Yellow);\n    &#125;\n&#125;</code></pre>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2011/windows-phone-gesture-service/8a48602c/windows_phone-gesture-service@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2011/windows-phone-compass/",
            "url": "https://blog.zwezdin.com/2011/windows-phone-compass/",
            "title": "Использование компаса в WP7.5 Mango",
            "date_published": "2011-11-07T12:00:00.000Z",
            "content_html": "<p>Многие устройства на <strong>Windows Phone 7</strong> имеют встроенный <strong>компас</strong>. В последнем обновлении инструментов разработчика для Windows Phone появился API для работы с компасом.</p>\n<p>В качестве базового объекта для работы с компасом используется сенсор <a href=\"http://msdn.microsoft.com/en-us/library/microsoft.devices.sensors.compass(v=vs.92).aspx\">Compass</a>. Как можно увидеть, этот объект унаследован от <code>SensorBase</code> – базового объекта для всех сенсоров в Windows Phone 7. Не все устройства имеют компас, поэтому перед началом работы следует проверить, имеет ли данное устройство компас. Общая последовательность действий для работы с компасом такова:</p>\n<ol>\n<li>Проверка на наличие компаса в устройстве WP7. Если нет, то все остальные действия не выполняются.</li>\n<li>Создание объекта <code>Compass</code>.</li>\n<li>Подписка на событие изменения направления компаса.</li>\n<li>Запуск процесса считывания данных с сенсора.</li>\n<li>При необходимости можно также обрабатывать ситуации, когда требуется снять намагниченность устройства.</li>\n</ol>\n<p>Для проверки доступности компаса в устройстве используется статическое свойство <code>IsSupported</code> объекта <code>Compass</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">if (Compass.IsSupported == true)</code></pre>\n<p>Если компас доступен, то создается объект и запускается прослушивание сенсора. При каждом изменении значения сенсора генерируется событие <code>CurrentValueChanged</code>. В обработчике этого события можно получить доступ к объекту <code>CompassReading</code>, который содержит текущее направление компаса.</p>\n<p>Следующий код выполняет описанные выше действия:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private Compass _compass;\n\npublic MainPage()\n&#123;\n  InitializeComponent();\n\n  if (Compass.IsSupported == true)\n  &#123;\n    _compass = new Compass();\n    _compass.CurrentValueChanged += ComapssValueChanged;\n    _compass.Start();\n\n  &#125;\n  else\n    MessageBox.Show(\"Compass is not supported.\");\n\n&#125;\n\nprivate void ComapssValueChanged(object sender, SensorReadingEventArgs&lt;CompassReading> e)\n&#123;\n  Dispatcher.BeginInvoke(() =>\n  &#123;\n      info.Text = e.SensorReading.TrueHeading.ToString();\n  &#125;);\n&#125;</code></pre>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2011/windows-phone-compass/ec20aa88/windows_phone-compass@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2011/office365-custom-domain/",
            "url": "https://blog.zwezdin.com/2011/office365-custom-domain/",
            "title": "Привязка собственного домена к Office 365",
            "date_published": "2011-11-04T12:00:00.000Z",
            "content_html": "<p>Рассказывая друзьям про Office 365 и его преимущества я очень часто слышу вопрос о том, как привязать к нему собственный домен. С этой проблемой я столкнулся и сам, когда мигрировал на купленный аккаунт Office 365. К сожалению, в то время (а было это буквально через несколько дней после запуска) не было подробной документации о привязке собственного домена. Тем не менее, стандартный домен использовать не хотелось. Поэтому пришлось решать вопрос экспериментальным путем. Не исключено, что на данный момент такой раздел в документации добавили. Тем не менее, в этом посте я кратко опишу действия, которые нужно выполнить (хотя бы для того, чтобы давать эту ссылку друзьям в ответ на соответствующий вопрос).</p>\n<p>Итак, первое что вам необходимо сделать – <strong>зарегистрировать аккаунт</strong> в Office 365. Для этого идем по <a href=\"https://www.office.com/\">ссылке</a> и выбираем Free Trial. В принципе, можно сразу и купить аккаунт, но если вы выбираете режим Trial, то его без проблем можно будет превратить в платный аккаунт позже. После заполнения всех необходимых сведений мы получаем доступ и возможность пользоваться сервисом в домене <code>&quot;.onmicrosoft.com&quot;</code>.</p>\n<h2>Шаг 1: проверка домена</h2><p>Проверка домена необходима для того, чтобы удостовериться, что именно вы являетесь владельцем домена. Для проверки вашего домена вас попросят добавить тестовую запись TXT (или MX – на ваш выбор) в DNS вашего домена.</p>\n<p>Чтобы выполнить проверку домена, входим в панель управления аккаунтом Office 365 и выбираем пункт <mark>“Add and verify your domain”</mark>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2011/office365-custom-domain/bb02b9ec/step-1-1@2x.png\"  alt=\"Привязка собственного домена к Office 365\"/>\n\n\n\n\n<p>После указания имени домена вам предоставят содержимое записи, которую следует внести в настройки домена, а также все шаги по их выполнению в зависимости от регистратора.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2011/office365-custom-domain/b9112887/step-1-2@2x.png\"  alt=\"Привязка собственного домена к Office 365\"/>\n\n\n\n\n<p>Вносим соответствующую запись в настройки домена и ждем некоторое время, чтобы они успели примениться. После того, как записи в DNS обновились, домен появится в списке доменов Office 365.</p>\n<h2>Шаг 2: выбор режима привязки домена</h2><p>Теперь необходимо выбрать режим привязки домена.</p>\n<p>По умолчанию, Office 365 предложит использовать свои Name Servers для вашего домена (в моем случае это были <code>ns1.bdm.microsoftonline.com</code> и <code>ns2.bdm.microsoftonline.com</code>). Если этот вариант вас устраивает, то идем к настройкам домена и изменяем Name Servers на предложенные. После этого у вас должно все заработать и никаких других действий по настройке домена выполнять не нужно.</p>\n<p>Меня такой вариант не устроил, поскольку мне удобно использовать в качестве Name Server другой сервер. Если вам тоже, то читаем дальше.</p>\n<h2>Шаг 3: добавление необходимых записей к настройкам DNS для домена</h2><p>Для того, чтобы использовать собственный Name Server, но при этом пользоваться Office 365 нужно добавить несколько записей в настройки домена. Для этого переходим во вкладку <mark>“Domains”</mark> и выбираем  <mark>“DNS Manager”</mark>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2011/office365-custom-domain/8ffb831b/step-3-1@2x.png\"  alt=\"Привязка собственного домена к Office 365\"/>\n\n\n\n\n<p>DNS Manager содержит настройки для вашего домена. Если вы используете предложенные во втором шаге Name Servers, то именно здесь вы сможете управлять записями для своего домена. Сейчас эта информация пригодится нам для добавления соответствующих записей для своего домена.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2011/office365-custom-domain/4854d081/step-3-2@2x.png\"  alt=\"Привязка собственного домена к Office 365\"/>\n\n\n\n\n<p>Все что необходимо сделать – добавить аналогичные записи для своего домена.</p>\n<p>После выполнения этих действий нужно подождать некоторое время, чтобы настройки успели примениться. После этого можно пользоваться Office 365, но уже со своим доменом.</p>\n<p>Кстати, в Trial-режиме тоже можно привязать собственный домен и проверить как все будет работать.</p>",
            "tags": [
                "Tips&Tricks"
            ],
            "banner_image": "https://blog.zwezdin.com/2011/office365-custom-domain/9c621f84/office365_custom-domain@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2011/windows-phone-state-restore/",
            "url": "https://blog.zwezdin.com/2011/windows-phone-state-restore/",
            "title": "Быстрое восстановление состояния приложения Windows Phone 7.5",
            "date_published": "2011-11-02T12:00:00.000Z",
            "content_html": "<p>Модель исполнения приложений <strong>Windows Phone 7</strong> устроена так, что после деактивации приложения оно может быть выгружено из памяти. Поэтому при повторном обращении к приложению оно должно <strong>восстановить свое состояние</strong>. В некоторых случаях восстановление состояние при повторном обращении к приложению может занимать длительное время. Поэтому в Mango появилась возможность быстрого восстановления состояния.</p>\n<p>Как известно, восстановление состояния происходит в обработчике события <code>Application.Activated</code>. Если приложение было выгружено недавно и ОС не успела выгрузить его состояние из памяти, то восстанавливать состояние необязательно. Для отслеживания таких ситуаций был добавлен специальный флаг <code>IsApplicationInstancePreserved</code>. Если значение этого флага установлено в <code>true</code>, то можно пропустить этап восстановления состояния:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void Application_Activated(object sender, ActivatedEventArgs e)\n&#123;\n  if (e.IsApplicationInstancePreserved == false)\n  &#123;\n    // restore state\n  &#125;\n  else\n  &#123;\n    // ignore state restoring\n  &#125;\n&#125;</code></pre>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2011/windows-phone-state-restore/02667495/windows_phone-state-restore@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2011/windows-phone-gestures/",
            "url": "https://blog.zwezdin.com/2011/windows-phone-gestures/",
            "title": "Как обрабатывать жесты в Windows Phone 7",
            "date_published": "2011-10-30T12:00:00.000Z",
            "content_html": "<p><strong>Жесты</strong> являются одним из ключевых способов взаимодействия пользователя и приложения в <strong>Windows Phone 7</strong>. Это стало возможным благодаря наличию мультитач экранов с 4 касаниями (<em>как минимум</em>) во всех аппаратах на Windows Phone 7. Стандартные элементы управления уже содержат обработчики жестов. Однако, иногда необходимо добавить собственную логику для обработки жестов. В этой заметке будет рассказано о том, как добавить свою логику обработки жестов за несколько шагов.</p>\n<p>Список всех доступных жестов подробно описан в <a href=\"http://msdn.microsoft.com/en-us/library/hh202911(v=VS.92).aspx\">соответствующем разделе MSDN</a>, вот только некоторые, наиболее популярные из них:</p>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2011/windows-phone-gestures/381f8f69/1@2x.png\"  alt=\"Нажатие (Tap)\"/><figcaption>Нажатие (Tap)</figcaption></figure>\n\n\n\n\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2011/windows-phone-gestures/cb050042/2@2x.png\"  alt=\"Двойное нажатие (Double Tap)\"/><figcaption>Двойное нажатие (Double Tap)</figcaption></figure>\n\n\n\n\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2011/windows-phone-gestures/5799b0fc/3@2x.png\"  alt=\"Нажатие и удержание (Touch and Hold)\"/><figcaption>Нажатие и удержание (Touch and Hold)</figcaption></figure>\n\n\n\n\n<p>Для обработки манипуляций каждый элемент управления имеет три события:</p>\n<ul>\n<li><code>ManipulationStarted</code> – генерируется при начале выполнения какого-либо жеста;</li>\n<li><code>ManipulationDelta</code> – генерируется периодически в процессе выполнения жеста (позволяет отследить относительное смещение);</li>\n<li><code>ManipulationCompleted</code> – генерируется в момент завершения жеста (пользователь оторвал пальцы от экрана).</li>\n</ul>\n<p>Можно построить несложную математическую модель для отслеживания жестов на основе этих событий. Однако, более простой способ обрабатывать жесты — использовать объект <code>GestureService</code> из <a href=\"http://silverlight.codeplex.com/\">Windows Phone 7 Toolkit</a>. Этот объект отслеживает стандартные жесты и позволяет подписаться на соответствующие события.</p>\n<p>Для использования <code>GestureService</code> следует добавить ссылку на сборку <code>Microsoft.Phone.Controls.Toolkit.dll</code>, в которой и находится нужный объект. После этого следует добавить соответствующее пространство имен в определение страницы:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">xmlns:toolkit=\"clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit\"</code></pre>\n<p>Теперь можно добавить обработку жестов для элементов управления. Например, создадим элемент <code>Rectangle</code> и добавим обработку жеста <code>Hold</code>:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Rectangle Fill=\"Green\">\n  &lt;toolkit:GestureService.GestureListener>\n    &lt;toolkit:GestureListener Hold=\"OnHoldHandler\" />\n  &lt;/toolkit:GestureService.GestureListener>\n&lt;/Rectangle></code></pre>\n<p>В коде страницы должен быть обработчик с именем <code>OnHoldHandler</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void GestureListener_Hold(object sender, GestureEventArgs e)\n&#123;\n  // ...\n&#125;</code></pre>\n<p>Теперь при обнаружении жеста <code>Hold</code> будет срабатывать заданный обработчик.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2011/windows-phone-gestures/c0f12d1a/windows_phone-gestures@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2011/windows-phone-power-source/",
            "url": "https://blog.zwezdin.com/2011/windows-phone-power-source/",
            "title": "Приложения для Windows Phone 7, зависящие от источника питания",
            "date_published": "2011-10-21T12:00:00.000Z",
            "content_html": "<p>Не всегда мобильное приложение удается сделать <strong>экономичным по отношению к батарее</strong>. Яркий пример таких приложений — автомобильная навигация. В этом случае для предотвращения быстрого разряда батареи следует использовать внешнее питание. Здесь я покажу каким образом отслеживать такие ситуации.</p>\n<p>В случаях, когда сценарии использования нашего приложения предполагают наличие внешнего питания, следовало бы предупредить пользователя об этом. Напрашивается следующая логика: перед запуском ресурсоемкого программного кода мы должны проверить присутствует ли внешний источник питания и, если нет, то предупредить пользователя о том, что батарея может быть быстро истощена. То же самое следует проделывать всякий раз, когда внешнее питание отключается.</p>\n<p>Эту логику просто реализовать, используя класс <a href=\"http://msdn.microsoft.com/en-us/library/hh239075(v=VS.92).aspx\"><code>DeviceStatus</code></a>. Для наших целей он содержит:</p>\n<ul>\n<li>свойство <code>PowerSource</code> – позволяет определить текущий источник питания (батарея или внешний);</li>\n<li>событие <code>PowerSourceChanged</code> – позволяет отловить момент, когда источник питания изменяется.</li>\n</ul>\n<p>Теперь мы можем построить требую для нас логику. Предположим, что у нас есть два метода – <code>StartWorkingHard()</code> и <code>StopWorkingHard()</code>, которые запускают и останавливают запись GPS-трека. Тогда код для отслеживания источника питания может выглядеть так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public MainPage()\n&#123;\n  InitializeComponent();\n\n  DeviceStatus.PowerSourceChanged += PowerSourceChanged;\n  CheckPowerStatus();\n&#125;\n\nprivate void PowerSourceChanged(object sender, EventArgs e)\n&#123;\n  CheckPowerStatus();\n&#125;\n\nprivate void CheckPowerStatus()\n&#123;\n  if (DeviceStatus.PowerSource == PowerSource.Battery)\n  &#123;\n    if (MessageBox.Show(\"Используется питание от батареи. Продолжить запись трека?\", \"Изменен источник питания\", MessageBoxButton.OKCancel) == MessageBoxResult.OK)\n    &#123;\n      StartWorkingHard();\n    &#125;\n    else\n      StopWorkingHard();\n  &#125;\n  else\n    StartWorkingHard();\n&#125;\n\nprivate void StartWorkingHard()\n&#123;\n&#125;\n\nprivate void StopWorkingHard()\n&#123;\n&#125;</code></pre>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2011/windows-phone-power-source/df8335e8/windows_phone-power-source@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2011/windows-phone-memory-consumption/",
            "url": "https://blog.zwezdin.com/2011/windows-phone-memory-consumption/",
            "title": "Определение устройств WP7 с малым объемом оперативной памяти",
            "date_published": "2011-10-11T12:00:00.000Z",
            "content_html": "<p>Любые мобильные приложения ограничены в ресурсах. Поэтому производителю мобильных ОС приходится заботиться о том, чтобы приложения корректно исполнялись во всех конфигурациях. Одним из таких ограничений является ограничение в 90Мб используемой <strong>оперативной памяти</strong> для устройств с ОЗУ 256 Мб (<a href=\"http://msdn.microsoft.com/en-us/library/hh184840(v=VS.92).aspx\">Technical Certification Requirements - 5.2.5 Memory Consumption</a>). Здесь я покажу как определить объем ОЗУ у текущего устройства.</p>\n<p>Как следует из описания этого требования, приложение не должно потреблять больше 90Мб оперативной памяти; это требование не распространяется на устройства, имеющие больше 256Мб ОЗУ.</p>\n<blockquote>\n<p>An application must not exceed 90 MB of RAM usage, except on devices that have more than 256 MB of memory.</p>\n</blockquote>\n<p>Становится понятно, что в случае, если устройство менее малый объем памяти нужно как-то ограничить аппетиты приложения, например, выключить дополнительную анимацию, графику и другие ресурсы, потребляющие ОЗУ, но не влияющие на функциональности. Безусловно, в этом случае пользователь может получить менее красивое приложение, но зато оно хотя бы будет работать.</p>\n<p>Для определения этой информации следует использовать объект <code>DeviceStatus</code> (для WP 7.0 следует использовать объект <a href=\"http://msdn.microsoft.com/en-us/library/microsoft.phone.info.deviceextendedproperties(v=VS.92).aspx\"><code>DeviceExtendedProperties</code></a>). Этот объект содержит свойство <code>DeviceTotalMemory</code>, которое содержит общий объем ОЗУ устройства. Сравнивая это значение с пороговым можно определить с каким именно устройством мы имеем дело:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class MemoryExtensions\n&#123;\n  public static bool IsLowMemoryDevice(this Application app)\n  &#123;\n    return Microsoft.Phone.Info.DeviceStatus.DeviceTotalMemory &lt;= 256*1024;\n  &#125;\n&#125;</code></pre>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2011/windows-phone-memory-consumption/28cea88e/windows_phone-memory-consumption@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2011/windows-phone-network-availability/",
            "url": "https://blog.zwezdin.com/2011/windows-phone-network-availability/",
            "title": "Проверка доступности сети в приложении Windows Phone 7",
            "date_published": "2011-10-01T12:00:00.000Z",
            "content_html": "<p>Большинство мобильных приложений <strong>Windows Phone 7</strong> так или иначе работают с ресурсами из сети. Думаю, многие согласятся со мной, что WP7-устройство, не подключенное к сети не представляет особого смысла. В реальности нам не всегда доступна сеть при использовании устройства. В этом случае следует корректно обрабатывать эту ситуацию в своем приложении. Здесь я покажу простой способ как это можно сделать.</p>\n<p>В действительности для разработчика было бы хорошо иметь не только информацию о наличии сети, но и представлять какой тип сети в данный момент используется. Например, было бы неправильно загружать несколько сотен мегабайт через <strong>3G-сеть</strong>, хотя для <strong>WiFi-сетей</strong> это, как правило, нормально. Такую функциональность предоставляет объект <code>NetworkInterface</code>, который расположен в пространстве имен <code>Microsoft.Phone.Net.NetworkInformation</code>. Следующие примеры кода позволяют получить информацию о наличии сети и ее типе.</p>\n<p>Проверка доступности сети:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">bool isNetworkAvailable = Microsoft.Phone.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable();</code></pre>\n<p>Определение типа сети:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">NetworkInterfaceType type = Microsoft.Phone.Net.NetworkInformation.NetworkInterface.NetworkInterfaceType;</code></pre>\n<p>Тип <code>NetworkInterfaceType</code> – это перечисление, которое может принимать различные значения в зависимости от типа сети:</p>\n<ul>\n<li><code>Ethernet</code></li>\n<li><code>MobileBroadbandCdma</code></li>\n<li><code>MobileBroadbandGsm</code></li>\n<li><code>Wireless80211</code></li>\n<li><code>None</code></li>\n</ul>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2011/windows-phone-network-availability/9120a035/windows_phone-network-availability@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2011/visual-studio-2010-rootsuffix/",
            "url": "https://blog.zwezdin.com/2011/visual-studio-2010-rootsuffix/",
            "title": "Одновременное использование Visual Studio 2010 в различных окружениях",
            "date_published": "2011-08-05T12:00:00.000Z",
            "content_html": "<p>Любите ли вы <a href=\"http://www.hanselman.com/blog/SimplifyYourVisualStudio2010ToolbarAndFreeYourMind.aspx\">настраивать Visual Studio</a> под свои личные предпочтения? Форматирование кода. Темы оформления. Набор расширений. Горячие клавиши. Расположение окон и панелей инструментов. Думаю, что этим занимается каждый разработчик, который заботится о своей продуктивности и комфорте работы. В итоге каждый из нас имеет базовый пакет настроек, специфичный для текущего окружения. В этом окружении можно чувствовать себя как рыба в воде, наизусть знать все горячие клавиши, расположение панелей. А среда разработки помогает вам быть продуктивнее, форматируя код и участвуя в рефакторинге. И казалось бы, уже ничего не может нарушить этой гармонии.</p>\n<p>К сожалению, эту гармонию могут нарушить банальные жизненные обстоятельства. Например, вы решили поучаствовать во внешнем проекте, в котором определены свои правила оформления кода, отличные от ваших. Или вы выступаете с докладом и вас попросили изменить цвет и увеличить размер шрифта из-за особенностей местного проектора. <em>Знакомо, не правда ли?</em></p>\n<p><strong>Что мы обычно делаем в приведенных выше ситуациях?</strong> Конечно мы открываем настройки Visual Studio и изменяем их в соответствии с нашими потребностями. В этом нет ничего плохого, если только вам не требуется это делать периодически. А как быть, если вы, например, с постоянной периодичностью читаете лекции студентам? Или переключаетесь между проектами с различными правилами оформления кода? В этих ситуациях вам потребуется постоянно изменять настройки среды разработки, что в конечном счете может стать сильно неэффективным. И в этом случае напрашивается логичное решение автоматизировать эти действия.</p>\n<p>Решением проблемы могут быть два подхода:</p>\n<ol>\n<li>Использование заранее подготовленных настроек IDE для каждого случая.</li>\n<li>Использование нескольких изолированных экземпляров IDE.</li>\n</ol>\n<p>Рассмотрим каждый из них.</p>\n<h2>Различные настройки среды разработки</h2><p>Этот способ предполагает хранение на жестком диске нескольких конфигурационных файлов. Каждый файл соответствует отдельной ситуации. Получить такой конфигурационный файл очень просто – достаточно просто воспользоваться стандартным механизмом <code>Import and Export Settings Wizard</code>. С его помощью можно как сохранить текущие настройки в конфигурационный файл, так и восстановить состояние среды по существующему файлу.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2011/visual-studio-2010-rootsuffix/a186228b/1@2x.png\"  alt=\"Одновременное использование Visual Studio 2010 в различных окружениях\"/>\n\n\n\n\n<p>Конфигурационные файлы являются обычными файлами XML, содержащие все настройки среды, указанные при экспорте. <a href=\"http://stackoverflow.com/questions/765666/schema-for-vssettings-files\">Теоретически</a> вы можете автоматизировать настройку Visual Studio в соответствии с какими-то собственными алгоритмами. Для этого можно просто изменять содержимое конфигурационного файла. Однако мне очень сложно придумать, в каких ситуациях это действительно может быть востребовано.</p>\n<p>После того, как вы подготовили несколько различных конфигураций, вы можете импортировать их, когда потребуется соответствующее окружение. Но использовать упомянутый диалог <code>Import and Export Settings Wizard</code> всякий раз, когда вам нужно сменить окружение – лишняя трата времени. Вместо этого можно создать несколько ярлыков на рабочем столе для запуска Visual Studio и указать для каждого из них путь до конфигурационного файла:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">devenv.exe /ResetSettings \"D:\\VSConfig\\settings1.vssettings\"</code></pre>\n<p>Нетрудно догадаться, что в приведенном выше примере путь <code>D:\\VSConfig\\settings1.vssetting</code> как раз и указывает на конфигурационный файл. Теперь каждый ярлык Visual Studio на вашем рабочем столе запускает среду разработки с соответствующим окружением.</p>\n<p>К плюсам данного решения можно отнести автоматизацию процесса загрузки новой конфигурации. Но такое решение обладает и рядом недостатков:</p>\n<ul>\n<li>запуск Visual Studio с перезагрузкой настроек занимает более длительное время, чем обычный запуск среды разработки;</li>\n<li>при использовании опции <code>/ResetSettings</code> очищаются внутренние кэши Visual Studio, что может негативно сказаться на скорости работы IDE уже в процессе написания кода;</li>\n<li>файлы конфигурации всегда следует хранить в актуальном состоянии (если вы меняете настройки IDE в процессе работы и хотите сохранить их на будущее, вы должны обновить файл конфигурации на жестком диске);</li>\n<li>неочевидна логика при запуске нескольких экземпляров VIsual Studio с различными настройками.</li>\n</ul>\n<h2>Использование нескольких изолированных экземпляров Visual Studio</h2><p>Альтернативой приведенного выше способа является использование <a href=\"http://msdn.microsoft.com/en-us/library/bb166560.aspx\">изолированных экземпляров Visual Studio</a>. На самом деле изолированные экземпляры были созданы с целью отладки расширений для Visual Studio – вы разрабатываете собственное расширение и запускаете отладку. В этот момент запускается изолированный экземпляр Visual Studio, настройки которого являются независимыми от основной копии Visual Studio.</p>\n<p>Этой особенностью как раз и можно воспользоваться для решения нашей задачи. Для этого нам нужно иметь несколько независимых изолированных экземпляров Visual Studio. Возможность создания изолированных экземпляров появляется после установки <a href=\"http://www.microsoft.com/download/en/details.aspx?id=2680\">Visual Studio 2010 SDK</a>. После установки SDK вам будет доступна утилита <a href=\"http://msdn.microsoft.com/ru-ru/library/dd875761.aspx\">CreateExpInstance.exe</a>, которая как раз и занимается управлением экземплярами Visual Studio.</p>\n<p>Для создания нового экземпляра следует воспользоваться следующей командой:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">CreateExpInstance.exe /VSInstance=10.0 /RootSuffix=Presentation</code></pre>\n<p>На самом деле эта утилита <a href=\"http://msdn.microsoft.com/en-us/library/bb166560.aspx\">просто создает</a> нужные ключи в реестре Windows. Параметр <code>RootSuffix</code> указывает имя создаваемого экземпляра. Это имя потребуется для запуска этого экземпляра. Аналогично использованию конфигурационных файлов, можно создать несколько ярлыков для запуска Visual Studio и указать параметры:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">devenv.exe /RootSuffix Presentation</code></pre>\n<p>Теперь, когда мы создали несколько изолированных экземпляров Visual Studio, мы можем запустить каждый из них и настроить соответствующее окружение, которое будет сохранено именно для этого случая.</p>\n<p>Плюсы подобного подхода очевидны – мы избавляемся от всех минусов предыдущего способа, создавая для каждого окружения свою изолированную среду. К минусам этого решения можно отнести необходимость установки Visual Studio 2010 SDK и применение специальной утилиты для управления экземплярами.</p>\n<h2>Заключение</h2><p>Поднятая в этой заметке проблема может быть актуальной далеко не для всех. Но если для вас эта проблема актуальна, то лучше свести количество необходимых дополнительных действий к минимуму. На мой взгляд решение с использованием изолированных экземпляров Visual Studio более оправдано, поскольку позволяет ускорить запуск IDE по сравнению с первым способом.</p>\n<p>Удачных экспериментов!</p>",
            "tags": [
                "Tips&Tricks",
                "Visual Studio"
            ],
            "banner_image": "https://blog.zwezdin.com/2011/visual-studio-2010-rootsuffix/135afd43/visual_studio-2010-rootsuffix@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2011/windows-phone-reuse-resources/",
            "url": "https://blog.zwezdin.com/2011/windows-phone-reuse-resources/",
            "title": "Как задать повторно используемые ресурсы в Windows Phone 7",
            "date_published": "2011-06-25T12:00:00.000Z",
            "content_html": "<p>Нередко в приложении требуется задать <strong>ресурсы</strong>, которые будут использоваться в различных местах приложения <strong>повторно</strong>. Это, например, могут быть фирменные цвета приложения, изображения и т.д. В этой заметке я покажу как это сделать.</p>\n<p>Для каждого элемента управления Windows Phone 7 (наследника объекта <code>FrameworkElement</code>) существует свойство <code>Resources</code>, которое может содержать ресурсы для этого элемента управления. Ресурсы наследуются для вложенных элементов, т.е. для дочернего элемента управления вы можете использовать ресурсы родительского элемента. При этом каждый ресурс обладает уникальным именем – ключом.</p>\n<p>Для всего приложения также определено свойство <code>Resources</code>, которое может содержать ресурсы. При этом, размещая ресурсы в этом месте, они становятся доступными для каждого элемента. Ресурсы приложения определяются в файле <code>App.xaml</code> следующим образом:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;phone:PhoneApplicationPage.Resources>\n    &lt;SolidColorBrush x:Key=\"TextColor\" Color=\"Green\" />\n&lt;/phone:PhoneApplicationPage.Resources></code></pre>\n<p>После определения глобальных ресурсов их можно использовать при описании элементов управления в приложении:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;TextBlock Text=\"Some text\" Foreground=\"&#123;StaticResource TextColor&#125;\" /></code></pre>\n<p>Кстати, в Windows Phone 7 доступны ресурсы текущей темы, адаптируемые под визуальные настройки самой ОС. На <a href=\"http://msdn.microsoft.com/en-us/library/ff769552%28v=VS.92%29.aspx\">MSDN</a> приведен полный список ключей, которые можно использовать так же, как приведено выше.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2011/windows-phone-reuse-resources/7501ac8c/windows_phone-reuse-resources@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2011/windows-phone-trial-mode/",
            "url": "https://blog.zwezdin.com/2011/windows-phone-trial-mode/",
            "title": "Определение режима лицензирования приложений Windows Phone 7",
            "date_published": "2011-04-30T12:00:00.000Z",
            "content_html": "<p>Магазин <strong>Windows Phone 7 Marketplace</strong> позволяет использовать <strong>испытательный период</strong> (Trial) для платных приложений. В этом режиме платное приложение устанавливается бесплатно, но работает в режиме ограниченной функциональности. В этой заметке я расскажу о том, как определить работает ли приложение в испытательном режиме или оно было куплено.</p>\n<p>На самом деле проблема определения режима несколько шире – разработчик должен иметь способ отладить приложения в обоих режимах – испытательном и полном. По умолчанию все приложения, запускаемые в эмуляторе работают в испытательном режиме. Поэтому создадим небольшой helper-класс, который учитывает эту ситуацию.</p>\n<p>Для определения режима используется объект <code>LicenseInformation</code>, который расположен в пространстве имен <code>Microsoft.Phone.Marketplace</code> и имеет единственный метод <code>IsTrial()</code>. В случае, когда приложения запускается из эмулятора результатом этого метода всегда будет <code>true</code>. Поэтому в случае с эмулятором мы должны игнорировать этот метод и устанавливать значение самостоятельно (для получения возможности отладки для разных случаев).</p>\n<p>Определить тип устройства (реальное устройство или эмулятор) можно из текущего окружения (свойство <code>DeviceType</code> из объекта <code>Environment</code>).</p>\n<p>Таким образом, окончательный вид нашего метода для определения испытательного режима для мобильного приложения:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class ThemeExtensions\n&#123;\n  private const bool IsTrialForEmulator = true;\n\n  public static bool IsTrialMode(this Application app)\n  &#123;\n    if (Microsoft.Devices.Environment.DeviceType == Microsoft.Devices.DeviceType.Device)\n    &#123;\n      LicenseInformation license = new LicenseInformation();\n      return license.IsTrial();\n    &#125;\n    else\n      return IsTrialForEmulator;\n  &#125;\n&#125;</code></pre>\n<p>Использование данного кода в приложении крайне просто:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">bool isTrial = App.Current.IsTrialMode();</code></pre>\n<p>Если впоследствии появится необходимость протестировать приложение в различных режимах (полном и испытательном), то нужно только изменить значение флага <code>IsTrialForEmulator</code> в определении приведенного выше класса.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2011/windows-phone-trial-mode/a8eeac11/windows_phone-trial-mode@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2011/windows-phone-theme/",
            "url": "https://blog.zwezdin.com/2011/windows-phone-theme/",
            "title": "Определение текущей темы в Windows Phone 7",
            "date_published": "2011-04-27T12:00:00.000Z",
            "content_html": "<p>Windows Phone 7 позволяет пользователю <strong>установить одну из тем</strong> – светлую или темную, а также задать цвет акцента. По задумке разработчиков WP7 в зависимости от этого выбора переопределяются и ресурсы приложения, а следовательно и приложение изменяет свой внешний вид. Из-за этого создаются ситуации, когда приложению необходимо определить какая тема на данный момент активна и в зависимости от этого, изменить логику. В этой заметке я покажу как можно определить текущую тему простым способом.</p>\n<p>Определить текущую тему в Windows Phone 7 очень просто. Для этого достаточно обратиться к ресурсам — ОС сама устанавливает значения соответствующих ключей в зависимости от установки текущей темы. В нашем случае интересны два ключа в ресурсах – <code>PhoneDarkThemeVisibility</code> и <code>PhoneLightThemeVisibility</code>. Каждый из этих ключей содержит объекты типа <code>Visibility</code>, которые устанавливаются в значение <code>Visible</code> или <code>Collapsed</code> в зависимости от того, какая тема сейчас установлена. Например, проверить установку темной темы можно следующим образом:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">Visibility themeVisibility = (Visibility)Resources[\"PhoneDarkThemeVisibility\"];\nif (themeVisibility == Visibility.Visible)\n&#123;\n  // Dark theme\n&#125;</code></pre>\n<p>Такой способ не очень удобен для использования в различных частях приложения. Поэтому я создал небольшой класс, содержащий методы расширения объекта <code>Application</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class ThemeExtensions\n&#123;\n  public static bool IsDarkTheme(this Application app)\n  &#123;\n    Visibility themeVisibility = (Visibility)app.Resources[\"PhoneDarkThemeVisibility\"];\n    return themeVisibility == Visibility.Visible;\n  &#125;\n\n  public static bool IsLightTheme(this Application app)\n  &#123;\n    Visibility themeVisibility = (Visibility) app.Resources[\"PhoneLightThemeVisibility\"];\n    return themeVisibility == Visibility.Visible;\n  &#125;\n&#125;</code></pre>\n<p>Теперь для определения текущей темы можно использовать следующий код:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">bool isDark = App.Current.IsDarkTheme();\nbool isLight = App.Current.IsLightTheme();</code></pre>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2011/windows-phone-theme/7f40a4bc/windows_phone-theme@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2011/windows-phone-dynamic-datatemplate/",
            "url": "https://blog.zwezdin.com/2011/windows-phone-dynamic-datatemplate/",
            "title": "Динамическая генерация шаблонов (DataTemplate) для Windows Phone 7 в коде",
            "date_published": "2011-04-22T12:00:00.000Z",
            "content_html": "<p>Обычно шаблоны, описывающие внешний вид не нуждаются в динамическом изменении своего внешнего вида – они статически размещаются в ресурсах приложения и используются. Но для всякого правила бывают исключения, поэтому в данной заметке я покажу каким образом можно генерировать <strong>DataTemplate</strong> “на лету” в коде.</p>\n<p>Представим ситуацию, в которой нам необходимо определить <code>DataTemplate</code> для элементов <code>ListBox</code> динамически в коде (для других элементов управления процедура останется той же). Чтобы это реализовать нужно выполнить следующие шаги:</p>\n<ol>\n<li>Сгенерировать XAML для шаблона;</li>\n<li>На основе полученного XAML получить объект <code>DataTemplate</code>;</li>\n<li>Задать свойство <code>ItemTemplate</code> для элемента <code>ListBox</code>.</li>\n</ol>\n<p>Генерация XAML заключается в получении строки, содержащей валидный XML и соответствующей схеме XAML (<code>http://schemas.microsoft.com/winfx/2006/xaml/presentation</code>). В этом примере мы просто создадим строку с определением шаблона, однако в своем коде вы наверняка здесь добавите какую-то логику (<em>иначе зачем вообще затевать динамическую генерацию шаблона?</em>).</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">string source = @\"&lt;DataTemplate xmlns=\"\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\"\n    xmlns:x=\"\"http://schemas.microsoft.com/winfx/2006/xaml\"\">\n    &lt;Grid>\n        &lt;TextBlock Text=\"\"&#123;Binding&#125;\"\" FontSize=\"\"40\"\" Background=\"\"Red\"\" />\n    &lt;/Grid>\n&lt;/DataTemplate>\";</code></pre>\n<p>Для получения объекта <code>DataTemplate</code> на основе полученной строки следует использовать объект <code>XamlReader</code>. Он разбирает исходный фрагмент XAML и, если он корректен, создает на его основе объект заданного типа (таким образом, кстати, можно создавать любые объекты, а не только <code>DataTemplate</code>). Для разбора XAML следует выполнить метод <code>Load()</code> и результат привести к требуемому типу (в данном случае – <code>DataTemplate</code>):</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">DataTemplate template = (DataTemplate)XamlReader.Load(source);</code></pre>\n<p>Будьте осторожны, если в исходной строке содержаться ошибки (например, недопустимые символы), то вы получите ошибку времени исполнения. По-хорошему, такие ситуации следует явно отслеживать в коде и соответствующе на них реагировать.</p>\n<p>Последнее, что необходимо сделать – присвоить полученный объект <code>DataTemplate</code> свойству <code>ItemTemplate</code> элемента <code>ListBox</code>. Результирующий код будет выглядеть так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">string source = @\"&lt;DataTemplate xmlns=\"\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\"\n    xmlns:x=\"\"http://schemas.microsoft.com/winfx/2006/xaml\"\">\n    &lt;Grid>\n        &lt;TextBlock Text=\"\"&#123;Binding&#125;\"\" FontSize=\"\"40\"\" Background=\"\"Red\"\" />\n    &lt;/Grid>\n&lt;/DataTemplate>\";\n\nDataTemplate template = (DataTemplate)XamlReader.Load(source);\n\nListBox1.ItemTemplate = template;\nListBox1.ItemsSource = ...;</code></pre>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2011/windows-phone-dynamic-datatemplate/f3908ea7/windows_phone-dynamic-datatemplate@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2011/windows-phone-navigate-external/",
            "url": "https://blog.zwezdin.com/2011/windows-phone-navigate-external/",
            "title": "Навигация на страницу Windows Phone 7, расположенную во внешней сборке",
            "date_published": "2011-04-12T12:00:00.000Z",
            "content_html": "<p>Очень часто в приложении <strong>Windows Phone 7</strong> требуется разделить функциональность приложения на несколько сборок. И в этом случае часто возникают вопросы – как использовать страницу (XAML), расположенную не в основной сборке. Мне пришло несколько вопросов подобного характера в почту, отвечаю здесь всем сразу.</p>\n<h2>Навигация на страницы, расположенные в сборке самого приложения</h2><p>За навигацию в Windows Phone 7 отвечает объект <code>NavigationService</code>. Он содержит методы <code>Navigate()</code> и <code>GoBack()</code>, которые позволяют перемещаться по страницам и изменять содержимое стека навигации. Важным моментом при этом является то, что для адресации страницы используется объект <code>Uri</code> (при этом все пути к страницам – относительные).</p>\n<p>Типичный пример кода для перехода на страницу в основной сборке выглядит так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">NavigationService.Navigate(new Uri(\"/About.xaml\", UriKind.Relative));</code></pre>\n<p>В данном случае методу <code>Navigate()</code> передается объект <code>Uri</code>, который и содержит путь до нужной страницы.</p>\n<h2>Навигация на внешние страницы</h2><p>Для навигации на страницы, расположенные во внешней сборки необходимо:</p>\n<ol>\n<li>Сделать ссылку на внешнюю сбоку со страницей из основного приложения (для того, чтобы сборка была включена в результирующий пакет приложения XAP).</li>\n<li>При указании адреса страницы добавить указание на внешнюю сборку.</li>\n</ol>\n<p>С первым пунктом все достаточно просто – нужно добавить ссылку (<code>Add Reference</code>) на сборку, содержащую требуемую страницу. Для указания адреса страницы следует воспользоваться стандартным для приложений Silverlight синтаксисом и указать имя сборки:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">NavigationService.Navigate(new Uri(\"/Project1;component/MyPage.xaml\", UriKind.Relative));</code></pre>\n<p>Путь до страницы внутри сборки может быть вложенным:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">NavigationService.Navigate(new Uri(\"/Project1;component/Views/Part1/Page1.xaml\", UriKind.Relative));</code></pre>\n<p>Аналогичным образом можно делать ссылки из элемента управления <code>HyperlinkButton</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">&lt;HyperlinkButton NavigateUri=\"/Project;component/MyPage.xaml\" Content=\"Navigate to my page\"/></code></pre>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2011/windows-phone-navigate-external/5627ab7a/windows_phone-navigate-external@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2010/wp7-training/",
            "url": "https://blog.zwezdin.com/2010/wp7-training/",
            "title": "Тренинг по Windows Phone 7 в Москве (фотоотчет)",
            "date_published": "2010-09-30T12:00:00.000Z",
            "content_html": "<p><strong>27 сентября 2010, понедельник</strong>. Несмотря на то, что обычно понедельник считается днем тяжелым, более ста разработчиков собрались в офисе Microsoft для того, чтобы обсудить мобильную платформу Windows Phone 7. Это было первое мероприятие по Windows Phone 7 в России с подобным размахом. В нем приняли участие разработчики на различных платформах, представители таких компаний как Samsung и LG, а также просто много хороших людей. Но несомненно всех объединяло одно – огромный интерес к Windows Phone 7.</p>\n<p>Для того, чтобы насладиться атмосферой мероприятия, мне пришлось проснуться немного пораньше, примерно через 8 часов я уже был в офисе в Крылатском, где меня сразу встретили <a href=\"http://twitter.com/mixen\">Миша</a> и <a href=\"http://twitter.com/vladkol\">Володя</a> и вручили телефон на Windows Phone 7.</p>\n<p>Что отличало данное мероприятие от остальных – это пониженная степень официальности. На открытии мероприятия было объявлено, что главная цель – неформальное общение, и определены некоторые правила, главное из которых – не фотографировать то, что будет показываться.</p>\n<p>На самом деле настрой на неформальное общение был определен еще в процессе подготовки мероприятия, когда буквально за час до мероприятия мы вносили некоторые корректировки в выступления и демонстрации. Это, на мой взгляд, придало демонстрациям больше естественности. Например, большинство своих демонстраций я так и не успел проделать до мероприятия, но у меня был в голове сценарий – поэтому почти весь код пришлось писать прямо в процессе доклада. Мне такая практика очень понравилась – ощущается доверие аудитории.</p>\n<h2>Доклады мероприятия</h2><p>Открыл мероприятие Михаил Черномордиков с обзором платформы Windows Phone 7. Доклад был короткий, но имел важное значение – он определил настрой на весь оставшийся день. Миша рассказал, что некоторые моменты Windows Phone 7 до сих пор нельзя показывать широкой аудитории (отсюда и ограничение на фотографирование). Несмотря на это, российский офис Microsoft пошел на то, чтобы использовать в демонстрациях устройство, а не эмулятор.</p>\n<p>После вступительного доклада я рассказывал про разработку мобильных приложений на платформе Silverlight. Несмотря на то, что было запланировано огромное количество демонстраций, показать большую их часть просто не хватило времени. Я постарался подробно рассказать про базовые принципы разработки мобильных приложений, сделал несколько демонстраций по созданию мобильного клиента для магазина Softkey и привязал текущее местоположение к карте Bing Maps, которая управлялась акселерометром. Насколько удался доклад судить, конечно, не мне и я очень рад буду услышать ваши комментарии к нему (если можно – в почту). На самом деле, тот момент, что я не успел показать все демонстрации не такой уж и плохой – будет о чем поговорить на <a href=\"http://msplatforma.ru/\">Платформе</a>.</p>\n<p>Фотографий с первых двух докладов у меня нет, поскольку в это время я готовился к своему докладу и, собственно, делал сам доклад. Далее описание сопровождается фотографиями.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/wp7-training/f39df0e0/1@2x.jpg\"  alt=\"Тренинг по Windows Phone 7 в Москве (фотоотчет)\"/>\n\n\n\n\n<p>Следуя плотному графику сразу же после меня, без перерывов Дмитрий Сошников начал рассказывать про разработку игр для мобильной платформы на основе XNA. Что особенно приятно, Дима поддержал заданный настрой и показывал больше практических вещей, а не слайдов. В докладе он рассказал о том, как разрабатывал так популярную в 90-ые годы игру “Морской бой”.</p>\n<p>В процессе докладов для докладчиков была одна неприятная неожиданность — микрофон нужно было держать в руках. Каждый докладчик по-своему пытался решать эту проблему: я, например, улучшал свои умения в написании программ одной рукой, Володя Колесников для этого дела приспособил карман рубашки. Дима, на мой взгляд, решил эту проблему креативнее всех.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/wp7-training/beaa8d22/2@2x.jpg\"  alt=\"Тренинг по Windows Phone 7 в Москве (фотоотчет)\"/>\n\n\n\n\n<p>По плану после доклада про XNA был обед. Но доклад настолько заинтересовал аудиторию, что никто не спешил уходить на обед, а Диму засыпали огромным количеством вопросов про разработку игр. Представляете себе, что такое удержать больше сотни голодных разработчиков в аудитории перед обедом? Диме это удалось.</p>\n<p>Кстати говоря, участники мероприятия удалились на обед ненадолго. Буквально через несколько минут можно было наблюдать картину, где Мишу атаковала толпа разработчиков – у него было устройство Windows Phone 7!</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/wp7-training/f2ae8b74/3@2x.jpg\"  alt=\"Тренинг по Windows Phone 7 в Москве (фотоотчет)\"/>\n\n\n\n\n<p>После обеда эстафетную палочку докладчиков подхватил Костя Кичинский. Он рассказывал про дизайн приложений Windows Phone 7 и о философии пользовательского интерфейса в новой мобильной платформе.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/wp7-training/f2aacdd6/4@2x.jpg\"  alt=\"Тренинг по Windows Phone 7 в Москве (фотоотчет)\"/>\n\n\n\n\n<p>На мой взгляд, такой доклад делать одновременно и тяжело и легко – есть как явные сторонники стиля Metro (используемого в Windows Phone 7), так и явные противники. К сожалению, так получилось, что я пропустил почти весь доклад. Однако, судя по отзывам других участников доклад удался.</p>\n<p>После этих докладов участников ждали рассказы от Spb Software и ГАЛС-Софт про их опыт разработки для Windows Phone 7.</p>\n<p>Первым взял слово Хитров Денис из Spb Software. Денис – отличный и приветливый докладчик. В начале доклада он трижды поздоровался с аудиторией (а значит, пожелал здоровья). Он рассказал про плюсы и минусы (которых не так много) мобильной платформы, показал прототипы продукты Spb Soft для Windows Phone 7.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/wp7-training/52ecdaae/5@2x.jpg\"  alt=\"Тренинг по Windows Phone 7 в Москве (фотоотчет)\"/>\n\n\n\n\n<p>Кстати говоря, во время демонстраций Дениса из окна просочился яркий солнечный свет: это знак свыше – на новую мобильную платформу наконец-то пролился свет в таком большом количестве! :)</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/wp7-training/3cf919fa/6@2x.jpg\"  alt=\"Тренинг по Windows Phone 7 в Москве (фотоотчет)\"/>\n\n\n\n\n<p>После выступления Дениса, Алексей Петровских, представитель ГАЛС-Софт рассказал про их опыт и успехи. Он раскрыл секреты того, когда нужно использовать Silverlight и XNA, а также показал свои наработки в этой области.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/wp7-training/476c9261/7@2x.jpg\"  alt=\"Тренинг по Windows Phone 7 в Москве (фотоотчет)\"/>\n\n\n\n\n<p>После выступления Алексея был небольшой перерыв и огромное количество общения в кулуарах. К сожалению, это общение пришлось прервать, поскольку дальше всех ожидала практическая часть мероприятия от Володи Колесникова. В ней каждый разработчик на своих ноутбуках мог попробовать по шагам сделать свое приложение для Windows Phone 7, а особо интересующиеся – запустить его на реальном устройстве.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/wp7-training/1762fe4e/8@2x.jpg\"  alt=\"Тренинг по Windows Phone 7 в Москве (фотоотчет)\"/>\n\n\n\n\n<p>В завершении мероприятия Михаил Черномордиков сделал доклад про Windows Marketplace. Начался он со слов “Поговорим про деньги?”. Какой человек бы не заинтересовался бы после этого?</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/wp7-training/d39ae510/9@2x.jpg\"  alt=\"Тренинг по Windows Phone 7 в Москве (фотоотчет)\"/>\n\n\n\n\n<p>На самом деле Миша рассказал много полезной информации о нюансах получения прибыли через Windows Marketplace. Как и ожидалось этот доклад вызвал множество вопросов и бурную дискуссию, которая плавно переросла в фуршет.</p>\n<h2>Про устройство</h2><p>Множество людей спрашивали меня про устройство. Да, действительно пообщаться с устройством мне удалось гораздо больше других. Стоит отметить, что те устройства, которые доступны сейчас – это только прототипы, которые никогда не появятся на рынке. Поэтому оценивать эргономику корпуса, качество сборки или камеры смысла нет. Однако, что можно сказать совершенно точно – он действительно быстрый, а пользовательский интерфейс в нем приятный и отзывчивый. Работа с устройством доставляет огромное удовольствие и никакого дискомфорта.</p>\n<p>Естественно, фотографий устройства я не делал, исходя из ранее оглашенных правил.</p>\n<h2>Про Marketplace</h2><p>Доклад про Windows Marketplace вызвал огромное количество вопросов, поэтому про них подробнее. Краткая выжимка доклада и сессии Q&amp;A:</p>\n<ul>\n<li>Регистрация в Windows Marketplace стоит $99 в год;</li>\n<li>Регистрация включает в себя возможность загружать неограниченное число платных приложений и 5 бесплатных, а также сертификат от компании GeoTrust, который нужен для размещения приложений в магазине;</li>\n<li>Для регистрации и оплаты участия необходимо наличие пластиковой карты, которая обладает возможностью оплаты через Интернет (VISA Electron, например, не подойдет);</li>\n<li>Для получения денег от Microsoft после продажи ваших приложений необходимо иметь счет в банке, получение средств осуществляется банковским переводом, поэтому банк должен уметь работать с международными переводами;</li>\n<li>Разделение прибыли – 70% – разработчику приложений, 30% – Microsoft и партнерам для поддержания инфраструктуры;</li>\n<li>Для получения возможности размещать приложения нужно подтвердить: свой e-mail адрес, свою личность (позвонит GeoTrust и спросит вы – это действительно вы?) и заполнить форму W8 и отправить в офис в Редмонд. Форма W8 нужна для того, чтобы освободить граждан РФ от уплаты налогов США.</li>\n</ul>\n<p>Т.е. выполнив несколько несложных действий по регистрации и заплатив небольшую сумму денег каждый российский разработчик получает возможность продавать свои мобильные приложения по всему миру!</p>\n<h2>Про людей</h2><p>Приятно удивил состав участников конференции. Поскольку мы пригласили на мероприятие разработчиков на совершенно разных платформах, то не исключен был сценарий холиваров. Этого не произошло. Участники, наоборот, были очень заинтересованы в конструктивной беседе и получении информации.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/wp7-training/0daa1329/10@2x.jpg\"  alt=\"Тренинг по Windows Phone 7 в Москве (фотоотчет)\"/>\n\n\n\n\n<p>Очень важно и показательно, что к мероприятию присоединились разработчики на других платформах. Очень много было людей с Mac Book и iPad. Были и разработчики для Android.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/wp7-training/794356bb/11@2x.jpg\"  alt=\"Тренинг по Windows Phone 7 в Москве (фотоотчет)\"/>\n\n\n\n\n<p>С многими мне удалось пообщаться на разные темы лично. В дискуссиях мы пришли к тому, что Windows Phone 7 хоть и молодая, но очень перспективная платформа, обладающая огромнейшим потенциалом на рынке.</p>\n<h2>Итого</h2><p>На мой взгляд в целом мероприятие прошло просто отлично – море общения, информации, обмена мнениями и, конечно же, устройства с Windows Phone 7. Именно так должны происходить фокусные технологические мероприятия.</p>\n<p>Ну и конечно нужно поблагодарить главных зачинщиков Windows Phone 7 Workshop – Мишу Черномордикова и Володю Колесникова. Они действительно приложили много усилий, чтобы всё получилось.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/wp7-training/b0a1dd19/12@2x.jpg\"  alt=\"Тренинг по Windows Phone 7 в Москве (фотоотчет)\"/>",
            "tags": [
                "Windows Phone",
                "Мероприятия"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/wp7-training/de7aa137/wp7_training@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2010/silverlight-gac/",
            "url": "https://blog.zwezdin.com/2010/silverlight-gac/",
            "title": "Как добавить в GAC сборку Silverlight",
            "date_published": "2010-06-27T12:00:00.000Z",
            "content_html": "<p>Недавно я озадачился вопросом добавления сборки <strong>Silverlight в GAC</strong>. Это необходимо для того, чтобы можно было сослаться на сборку из нескольких проектов и не переносить эту сборку каждый раз с проектом.</p>\n<p>Как известно – среда исполнения Silverlight – это не то же самое, что среда исполнения .NET Framework и простое добавление сборки в глобальное хранилище при помощи <code>gacutil</code> не дает никакого эффекта. Но с другой стороны, после установки Silverlight мы можем видеть в списке доступных сборок диалога <code>Add reference</code> в Visual Studio набор стандартных сборок, входящих в состав Sliverlight.</p>\n<p>Как оказалось, для того, чтобы дать возможность ссылаться на сборку из любых проектов через диалог <code>Add reference</code>, нужно добавить специальный ключ в реестр:</p>\n<ol>\n<li>Открываем ключ <code>HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\Microsoft SDKs\\Silverlight\\v4.0\\AssemblyFoldersEx</code>. Если у вас 32-битная ОС, то составляющую <code>Wow6432Node</code> нужно исключить.</li>\n<li>В этом местоположении создаем ключ с каким-либо именем (например, именем проекта).</li>\n<li>Добавляем значение по умолчанию, содержащее путь к сборкам на жестком диске.</li>\n</ol>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/silverlight-gac/77f0870a/1@2x.png\"  alt=\"Как добавить в GAC сборку Silverlight\"/>\n\n\n\n\n<p>Теперь при добавлении ссылки в проект, в диалоге <code>Add reference</code> будут доступны все сборки из указанной папки.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/silverlight-gac/eedff277/2@2x.png\"  alt=\"Как добавить в GAC сборку Silverlight\"/>\n\n\n\n\n<p>Решение проблемы очень не сложное, но в нужный момент, бывает, его найти очень не просто. Успехов вам!</p>",
            "tags": [
                "XAML",
                "Silverlight"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/silverlight-gac/acb40b49/silverlight_gac@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2010/windows-phone-tasks/",
            "url": "https://blog.zwezdin.com/2010/windows-phone-tasks/",
            "title": "Использование стандартных диалогов в Windows Phone 7",
            "date_published": "2010-06-03T12:00:00.000Z",
            "content_html": "<p>Любое мобильное устройство обладает стандартной функциональностью, которая специфична для конкретной платформы. Если говорить, например, о мобильных устройствах на базе <strong>Windows Phone 7</strong>, то такая функциональность – это и запуск камеры, и звонок по телефону, и отправка электронной почты с помощью Outlook Mobile, и ряд других задач. Для того, чтобы приложения могли использовать эту функциональность существует стандартный API, которым могут воспользоваться разработчики. О нем здесь и пойдет речь.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-tasks/f64361d0/1@2x.png\"  alt=\"Использование стандартных диалогов в Windows Phone 7\"/>\n\n\n\n\n<p>Вообще, стандартная функциональность операционной системы доступна приложению путем использования специальных объектов <code>Tasks</code> (задачи), которые входят в стандартный API. Идеология строится на том, чтобы для каждого сценария иметь свою задачу. Каждая задача представлена в виде стандартного объекта, реализующего интерфейс <code>ITask</code> или <code>IChooser</code>.</p>\n<p>Интерфейс <code>ITask</code> очень прост и состоит из единственного метода для запуска задачи.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public interface ITask\n&#123;\n    void Show();\n&#125;</code></pre>\n<p>Как видно, задачи можно просто запускать, более никакой функциональности недоступно. В некоторых ситуациях этого недостаточно. Например, если мы запускаем камеру, то хотелось бы получить результат. Поэтому для таких задач появился интерфейс <code>IChooser</code>, который расширяет функциональность <code>ITask</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public abstract class IChooser : ITask\n&#123;\n    protected IChooser();\n\n    public EventArgs EventArgs &#123; get; set; &#125;\n\n    public abstract void Deserialize(Stream s);\n    public abstract void OnInvokeReturned(byte[] outputBuffer);\n    public abstract void Serialize(Stream s);\n    public abstract void Show();\n&#125;</code></pre>\n<p>Здесь одним из наиболее важных моментов является наличие свойства <code>EventArgs</code>, которое и будет содержать результат выполнения задачи. Особо внимательные читатели уже, наверное, заметили, что на самом деле <code>IChooser</code> – это вовсе никакой не интерфейс, <code>IChooser</code> — это абстрактный класс. Это необходимо для того, чтобы в нем можно было реализовать часть логики.</p>\n<p>Все задачи в Windows Phone 7 (<em>за маленьким исключением</em>) реализуют тот или иной интерфейс. Поэтому с большинством из них мы можем работать, абстрагируясь от конкретной задачи.</p>\n<p>Теперь давайте посмотрим какие типы задач существуют на данный момент в Windows Phone 7 и каким образом их можно использовать. В первую очередь для того, чтобы использовать задачи, следует подключить стандартную сборку <code>Microsoft.Phone.Tasks</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-tasks/30d1be89/2@2x.png\"  alt=\"Использование стандартных диалогов в Windows Phone 7\"/>\n\n\n\n\n<p>После подключения этой сборки становятся доступными объекты, выполняющие различные задачи в Windows Phone 7. Типы задач, доступные на момент написания этого материала (<em>April 2010 CTP</em>):</p>\n<ul>\n<li><code>CameraCaptureTask</code> – позволяет получить изображение с камеры;</li>\n<li><code>EmailAddressChooserTask</code> – позволяет выбрать абонента из телефонной книги и получить его e-mail;</li>\n<li><code>EmailComposeTask</code> – запускает задачу написания e-mail сообщения;</li>\n<li><code>MarketplaceLauncher</code> – запускает приложение для Marketplace;</li>\n<li><code>MediaPlayerLauncher</code> – запускает проигрыватель медиа-файлов;</li>\n<li><code>PhoneCallTask</code> – запускает приложения набора номера телефона;</li>\n<li><code>PhoneNumberChooserTask</code> – позволяет выбрать абонента из телефонной книги и получить его номер телефона;</li>\n<li><code>PhotoChooserTask</code> – позволяет выбрать фото из существующей коллекции изображений или с камеры;</li>\n<li><code>SaveEmailAddressTask</code> – позволяет сохранить адрес электронной почты в телефонной книге;</li>\n<li><code>SavePhoneNumberTask</code> – позволяет сохранить номер телефона в телефонной книге;</li>\n<li><code>SearchTask</code> – запускает системное окно поиска;</li>\n<li><code>SmsComposeTask</code> – запускает задачу написания сообщения SMS;</li>\n<li><code>WebBrowserTask</code> – запускает веб-браузер.</li>\n</ul>\n<p>После запуска задачи и ее выполнения в некоторых случаях требуется обработать результаты выполнения задачи. Для этого можно воспользоваться двумя способами – обратиться к экземпляру класса той задачи, которую запустил пользователь и использовать свойство <code>EventArgs</code>, либо использовать объект <code>ChooserListener</code> и подписаться на событие <code>ChooserCompleted</code> для обработки результата. Оба подхода по большей степени равноценны, и какой из них выбрать – дело конкретного случая. Использование объекта <code>ChooserListener</code> сводится к следующему коду.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public MainPage()\n&#123;\n    InitializeComponent();\n\n    SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape;\n\n    ChooserListener.ChooserCompleted += new EventHandler(ChooserListener_ChooserCompleted);\n&#125;\n\nvoid ChooserListener_ChooserCompleted(object sender, EventArgs e)\n&#123;\n    var data = (TaskEventArgs&lt;PhotoResult>)e;\n\n    var photoStream = data.Result.ChosenPhoto;\n\n    // обработка изображения\n&#125;</code></pre>\n<p>Теперь, когда основные принципы работы с задачами в Windows Phone 7 стали более понятны, давайте рассмотрим небольшой пример, как мы по шагам можем вызывать задачи, а также получим изображение с камеры (в случае эмулятора – виртуальной камеры).</p>\n<h2>Работа с камерой в Windows Phone 7</h2><p>Создадим новый проект Windows Phone 7 и сделаем ссылку на сборку <code>Microsoft.Phone.Tasks</code>.В нашем приложении мы будем использовать <a href=\"/2010/windows-phone-appbar/\">меню</a> для вызова разных типов задач, поэтому сделаем также ссылку на сборку <code>Microsoft.Phone.Shell</code>. Добавим пространство имен для <code>Microsoft.Phone.Shell</code> в разметку формы и создадим объект <code>ApplicationBar</code>.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;phoneNavigation:PhoneApplicationPage\n    x:Class=\"LauncherTest.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:phoneNavigation=\"clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:shell=\"clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone.Shell\"\n    mc:Ignorable=\"d\" d:DesignWidth=\"480\" d:DesignHeight=\"800\"\n    FontFamily=\"&#123;StaticResource PhoneFontFamilyNormal&#125;\"\n    FontSize=\"&#123;StaticResource PhoneFontSizeNormal&#125;\"\n    Foreground=\"&#123;StaticResource PhoneForegroundBrush&#125;\">\n    &lt;phoneNavigation:PhoneApplicationPage.ApplicationBar>\n        &lt;shell:ApplicationBar IsMenuEnabled=\"True\">\n            &lt;shell:ApplicationBar.MenuItems>\n            &lt;/shell:ApplicationBar.MenuItems>\n        &lt;/shell:ApplicationBar>\n    &lt;/phoneNavigation:PhoneApplicationPage.ApplicationBar>\n\n    &lt;Grid x:Name=\"LayoutRoot\" Background=\"&#123;StaticResource PhoneBackgroundBrush&#125;\">\n        &lt;Grid.RowDefinitions>\n            &lt;RowDefinition Height=\"Auto\"/>\n            &lt;RowDefinition Height=\"*\"/>\n        &lt;/Grid.RowDefinitions>\n\n        &lt;!--TitleGrid is the name of the application and page title-->\n        &lt;Grid x:Name=\"TitleGrid\" Grid.Row=\"0\">\n            &lt;TextBlock Text=\"MY APPLICATION\" x:Name=\"textBlockPageTitle\" Style=\"&#123;StaticResource PhoneTextPageTitle1Style&#125;\"/>\n            &lt;TextBlock Text=\"page title\" x:Name=\"textBlockListTitle\" Style=\"&#123;StaticResource PhoneTextPageTitle2Style&#125;\"/>\n        &lt;/Grid>\n\n        &lt;!--ContentGrid is empty. Place new content here-->\n        &lt;Grid x:Name=\"ContentGrid\" Grid.Row=\"1\">\n            &lt;Image Height=\"171\" HorizontalAlignment=\"Left\" Margin=\"179,347,0,0\" Name=\"image1\" Stretch=\"Fill\" VerticalAlignment=\"Top\" Width=\"170\" />\n        &lt;/Grid>\n    &lt;/Grid>\n\n&lt;/phoneNavigation:PhoneApplicationPage></code></pre>\n<p>Несмотря на то, что основной сценарий в данном приложении – использование мобильной камеры, мы также оставим возможно запускать другие типы задач. Поэтому добавим в меню несколько пунктов для каждого из типов задач.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;phoneNavigation:PhoneApplicationPage\n    x:Class=\"LauncherTest.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:phoneNavigation=\"clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:shell=\"clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone.Shell\"\n    mc:Ignorable=\"d\" d:DesignWidth=\"480\" d:DesignHeight=\"800\"\n    FontFamily=\"&#123;StaticResource PhoneFontFamilyNormal&#125;\"\n    FontSize=\"&#123;StaticResource PhoneFontSizeNormal&#125;\"\n    Foreground=\"&#123;StaticResource PhoneForegroundBrush&#125;\">\n    &lt;phoneNavigation:PhoneApplicationPage.ApplicationBar>\n        &lt;shell:ApplicationBar IsMenuEnabled=\"True\">\n            &lt;shell:ApplicationBar.MenuItems>\n                &lt;shell:ApplicationBarMenuItem Text=\"Camera\" Click=\"CameraMenuItem_Click\"/>\n                &lt;shell:ApplicationBarMenuItem Text=\"Email address\" Click=\"EmailAddressMenuItem_Click\"/>\n                &lt;shell:ApplicationBarMenuItem Text=\"Email message\" Click=\"EmailMessageMenuItem_Click\"/>\n                &lt;shell:ApplicationBarMenuItem Text=\"Marketplace\" Click=\"MarketPlaceMenuItem_Click\"/>\n                &lt;shell:ApplicationBarMenuItem Text=\"Media player\" Click=\"MediaPlayerMenuItem_Click\"/>\n                &lt;shell:ApplicationBarMenuItem Text=\"Phone call\" Click=\"PhoneCallMenuItem_Click\"/>\n                &lt;shell:ApplicationBarMenuItem Text=\"Phone number\" Click=\"PhoneNumberMenuItem_Click\"/>\n                &lt;shell:ApplicationBarMenuItem Text=\"Choose photo\" Click=\"ChoosePhotoMenuItem_Click\"/>\n                &lt;shell:ApplicationBarMenuItem Text=\"Save email\" Click=\"SaveEmailMenuItem_Click\"/>\n                &lt;shell:ApplicationBarMenuItem Text=\"Save phone number\" Click=\"SavePhoneNumberMenuItem_Click\"/>\n                &lt;shell:ApplicationBarMenuItem Text=\"Search\" Click=\"SearchMenuItem_Click\"/>\n                &lt;shell:ApplicationBarMenuItem Text=\"Compse SMS\" Click=\"ComposeSMSMenuItem_Click\"/>\n                &lt;shell:ApplicationBarMenuItem Text=\"Web\" Click=\"WebMenuItem_Click\"/>\n            &lt;/shell:ApplicationBar.MenuItems>\n        &lt;/shell:ApplicationBar>\n    &lt;/phoneNavigation:PhoneApplicationPage.ApplicationBar>\n\n    &lt;Grid x:Name=\"LayoutRoot\" Background=\"&#123;StaticResource PhoneBackgroundBrush&#125;\">\n        &lt;Grid.RowDefinitions>\n            &lt;RowDefinition Height=\"Auto\"/>\n            &lt;RowDefinition Height=\"*\"/>\n        &lt;/Grid.RowDefinitions>\n\n        &lt;!--TitleGrid is the name of the application and page title-->\n        &lt;Grid x:Name=\"TitleGrid\" Grid.Row=\"0\">\n            &lt;TextBlock Text=\"MY APPLICATION\" x:Name=\"textBlockPageTitle\" Style=\"&#123;StaticResource PhoneTextPageTitle1Style&#125;\"/>\n            &lt;TextBlock Text=\"page title\" x:Name=\"textBlockListTitle\" Style=\"&#123;StaticResource PhoneTextPageTitle2Style&#125;\"/>\n        &lt;/Grid>\n\n        &lt;!--ContentGrid is empty. Place new content here-->\n        &lt;Grid x:Name=\"ContentGrid\" Grid.Row=\"1\">\n            &lt;Image Height=\"171\" HorizontalAlignment=\"Left\" Margin=\"179,347,0,0\" Name=\"image1\" Stretch=\"Fill\" VerticalAlignment=\"Top\" Width=\"170\" />\n        &lt;/Grid>\n    &lt;/Grid>\n\n&lt;/phoneNavigation:PhoneApplicationPage></code></pre>\n<p>Обработчик для каждого из пунктов меню будет иметь достаточно простой вид – он будет создавать соответствующий тип задачи и запускать его.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Net;\nusing System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Documents;\nusing System.Windows.Input;\nusing System.Windows.Media;\nusing System.Windows.Media.Animation;\nusing System.Windows.Shapes;\nusing Microsoft.Phone.Controls;\nusing Microsoft.Phone.Tasks;\nusing System.Windows.Media.Imaging;\n\nnamespace LauncherTest\n&#123;\n    public partial class MainPage : PhoneApplicationPage\n    &#123;\n        private CameraCaptureTask _task = new CameraCaptureTask();\n        public MainPage()\n        &#123;\n            InitializeComponent();\n\n            SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape;\n        &#125;\n\n        private void CameraMenuItem_Click(object sender, EventArgs e)\n        &#123;\n            (new CameraCaptureTask()).Show();\n        &#125;\n\n        private void EmailAddressMenuItem_Click(object sender, EventArgs e)\n        &#123;\n            (new EmailAddressChooserTask()).Show();\n        &#125;\n\n        private void EmailMessageMenuItem_Click(object sender, EventArgs e)\n        &#123;\n            (new EmailComposeTask()).Show();\n        &#125;\n\n        private void MarketPlaceMenuItem_Click(object sender, EventArgs e)\n        &#123;\n            MarketplaceLauncher.Show(MarketplaceContent.Applications, MarketplaceOperation.Open);\n        &#125;\n\n        private void MediaPlayerMenuItem_Click(object sender, EventArgs e)\n        &#123;\n            (new MediaPlayerLauncher()).Show();\n        &#125;\n\n        private void PhoneCallMenuItem_Click(object sender, EventArgs e)\n        &#123;\n            (new PhoneCallTask()).Show();\n        &#125;\n\n        private void PhoneNumberMenuItem_Click(object sender, EventArgs e)\n        &#123;\n            (new PhoneNumberChooserTask()).Show();\n        &#125;\n\n        private void ChoosePhotoMenuItem_Click(object sender, EventArgs e)\n        &#123;\n            (new PhotoChooserTask()).Show();\n        &#125;\n\n        private void SaveEmailMenuItem_Click(object sender, EventArgs e)\n        &#123;\n            (new SaveEmailAddressTask()).Show();\n        &#125;\n\n        private void SavePhoneNumberMenuItem_Click(object sender, EventArgs e)\n        &#123;\n            (new SavePhoneNumberTask()).Show();\n        &#125;\n\n        private void SearchMenuItem_Click(object sender, EventArgs e)\n        &#123;\n            (new SearchTask()).Show();\n        &#125;\n\n        private void ComposeSMSMenuItem_Click(object sender, EventArgs e)\n        &#123;\n            (new SmsComposeTask()).Show();\n        &#125;\n\n        private void WebMenuItem_Click(object sender, EventArgs e)\n        &#123;\n            (new WebBrowserTask()).Show();\n        &#125;\n    &#125;\n&#125;</code></pre>\n<p>Теперь, запустив приложение и выбрав какую-то из задач, можно убедиться, что они запускаются. Например, запустим задачу выбора изображений.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-tasks/3496ed70/3@2x.png\"  alt=\"Использование стандартных диалогов в Windows Phone 7\"/>\n\n\n\n\n<p>Теперь осталось задать обработчик результата. Воспользуемся для этого объектом <code>ChooserListener</code>, и будем обрабатывать получение изображения от камеры. После выполнения задачи просто отобразим изображение на форме.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public MainPage()\n&#123;\n    InitializeComponent();\n\n    SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape;\n\n    ChooserListener.ChooserCompleted += new EventHandler(ChooserListener_ChooserCompleted);\n&#125;\n\nvoid ChooserListener_ChooserCompleted(object sender, EventArgs e)\n&#123;\n    if (e is TaskEventArgs&lt;PhotoResult>)\n    &#123;\n        var taskEventArgs = (TaskEventArgs&lt;PhotoResult>)e;\n        var photoStream = taskEventArgs.Result.ChosenPhoto;\n\n        var bitmapImage = new BitmapImage();\n        bitmapImage.SetSource(photoStream);\n\n        image1.Source = bitmapImage;\n    &#125;\n&#125;</code></pre>\n<p>Событие ChooserCompleted вызывается после завершения каждой задачи. Однако, в каждом случае тип аргумента будет разным. Например, для задачи <code>CameraCaptureTask</code> аргумент будет иметь тип <code>TaskEventArgs&lt;PhotoResult&gt;</code>. Поскольку камера отсутствует в эмуляторе, то вместо реального изображения там будут сгенерирована случайная картинка.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-tasks/ff14c9ab/4@2x.png\"  alt=\"Использование стандартных диалогов в Windows Phone 7\"/>\n\n\n\n\n<p>Таким образом, мы с вами имеем готовый API для того, чтобы выполнить ряд стандартных для мобильного устройства задач.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/windows-phone-tasks/510c5d98/windows_phone-tasks@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2010/silverlight-simple-behaviors/",
            "url": "https://blog.zwezdin.com/2010/silverlight-simple-behaviors/",
            "title": "Простые поведения в Silverlight",
            "date_published": "2010-06-01T12:00:00.000Z",
            "content_html": "<p>Анимации и различного рода трансформации – это основа интерактивности любого современного пользовательского интерфейса. В WPF и Silverlight для этих целей есть набор объектов для анимацией и трансформаций. Для инициации того или иного действия используются события или триггеры. В конечном счете создание множества триггеров существенно снижает читаемость кода, а также его повторное использование. <a href=\"/2009/visualstatemanager\">VisualStateManager</a> позволяет отчасти решить эту проблему. Однако, есть еще более элегантный способ – использование поведений (Behavior).</p>\n<p>Сразу оговорюсь, что здесь я буду говорить о Silverlight в целом. Аналогичные механизмы доступны для веб-приложений на базе Silverlight, для настольных приложений на базе Silverlight и для мобильных приложений на базе Silverlight. Кроме того, аналогичный подход можно использовать и в WPF-приложениях. Поэтому я не буду конкретизировать платформу, ну а где конкретно применять эти механизмы – решать вам.</p>\n<p>Итак, что же такое поведения? Поведения (behaviors) – это специальные объекты, которые позволяют добавлять функционал в приложения, не написав ни одной строчки кода (изменив только разметку). Каждое поведение реализует небольшой функционал, например анимацию “дрожания” или “размытия”.</p>\n<p>Простые поведения являются объектами, унаследованными от класса <code>Behavior&lt;T&gt;</code>. При реализации своего поведения есть возможность обработать момент подключения и отключения поведения. Каждый элемент Behavior может быть подключен к любому визуальному элементу. Давайте попробуем создать простое поведение и подключить его.</p>\n<h2>Простое поведение</h2><p>Для начала подключим сборку <code>System.Windows.Interactivity</code>. После этого нужно создать класс-наследник <code>Behavior&lt;T&gt;</code>, где параметр <code>T</code> – это тип элемента управления, к которому можно применить данное поведение. В качестве этого параметра можно указать объект <code>UIElement</code> — тогда это поведение можно будет использовать с любыми элементами управления. Класс <code>Behavior&lt;T&gt;</code> содержит два виртуальных метода – <code>OnAttached</code> и <code>OnDetaching</code>. Эти методы вызываются при подключении или отключении поведения. Чтобы обработать эти события можно переопределить эти методы и определить в них свою логику.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class SimpleBehavior1 : Behavior&lt;UIElement>\n&#123;\n  protected override void OnAttached()\n  &#123;\n    base.OnAttached();\n  &#125;\n\n  protected override void OnDetaching()\n  &#123;\n    base.OnDetaching();\n  &#125;\n&#125;</code></pre>\n<p>Для того, чтобы работать с текущим элементом управления (к которому это поведение присоединено) можно использовать свойство <code>AssociatedObject</code>. Логика поведения обычно определяется в событиях, на которые можно подписаться в момент подключения поведения. Давайте создадим эффект размытия для текущего элемента в момент наведения указателя мыши. Для этого будем инициализировать свойство <code>Effect</code> текущего элемента управления объектом <code>BlurEffect</code> в момент наведения и очищать его, когда указатель находится вне этого элемента управления. Получим несложное определение поведения.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class SimpleBehavior1 : Behavior&lt;UIElement>\n&#123;\n  protected override void OnAttached()\n  &#123;\n    base.OnAttached();\n    AssociatedObject.MouseEnter += AssociatedObject_MouseEnter;\n    AssociatedObject.MouseLeave += AssociatedObject_MouseLeave;\n  &#125;\n\n  protected override void OnDetaching()\n  &#123;\n    AssociatedObject.MouseEnter -= AssociatedObject_MouseEnter;\n    AssociatedObject.MouseLeave -= AssociatedObject_MouseLeave;\n    base.OnDetaching();\n  &#125;\n\n  void AssociatedObject_MouseEnter(object sender, MouseEventArgs e)\n  &#123;\n    AssociatedObject.Effect = new BlurEffect() &#123; Radius = 5 &#125;;\n  &#125;\n\n  void AssociatedObject_MouseLeave(object sender, MouseEventArgs e)\n  &#123;\n    AssociatedObject.Effect = null;\n  &#125;\n&#125;</code></pre>\n<p>Для того, чтобы применить это поведение, в разметке следует подключить пространство имен <code>System.Windows.Interactivity</code> и пространство имен с определением поведения.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">xmlns:b=\"clr-namespace:Behavior1\"\nxmlns:interactivity=\"clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity\"</code></pre>\n<p>Добавим теперь кнопку на пустую форму и применим к нему это поведение. Получим следующую разметку.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;UserControl x:Class=\"Behavior1.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:b=\"clr-namespace:Behavior1\"\n    xmlns:interactivity=\"clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity\"\n    mc:Ignorable=\"d\"\n    d:DesignHeight=\"300\" d:DesignWidth=\"400\">\n\n  &lt;Grid x:Name=\"LayoutRoot\" Background=\"White\">\n    &lt;Button Margin=\"50\" Content=\"Счастье\" Width=\"200\" Height=\"150\">\n      &lt;interactivity:Interaction.Behaviors>\n        &lt;b:SimpleBehavior1 />\n      &lt;/interactivity:Interaction.Behaviors>\n    &lt;/Button>\n  &lt;/Grid>\n&lt;/UserControl></code></pre>\n<p>Запустим приложение и увидим, что при наведении указателя мыши на кнопку она становится размытой.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/silverlight-simple-behaviors/ac8fed42/1@2x.png\"  alt=\"Простые поведения в Silverlight\"/>\n\n\n\n\n<h2>Анимация</h2><p>Приведенный выше пример очень простой. Давайте усложним пример и сделаем размытие анимированным. Для этого я создам два объекта <code>Storyboard</code> (для появления и исчезновения размытия) и буду запускать эти анимации при возникновении тех же событий.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class SimpleBehavior2 : Behavior&lt;UIElement>\n&#123;\n  private readonly BlurEffect _effect = new BlurEffect() &#123;Radius = 0&#125;;\n\n  private readonly Storyboard _showStoryboard = new Storyboard();\n  private readonly Storyboard _hideStoryboard = new Storyboard();\n\n  protected override void OnAttached()\n  &#123;\n    base.OnAttached();\n    AssociatedObject.MouseEnter += AssociatedObject_MouseEnter;\n    AssociatedObject.MouseLeave += AssociatedObject_MouseLeave;\n    AssociatedObject.Effect = _effect;\n\n\n    var showAnimation = new DoubleAnimation\n    &#123;\n        To = 100\n    &#125;;\n\n    Storyboard.SetTarget(showAnimation, _effect);\n    Storyboard.SetTargetProperty(showAnimation, new PropertyPath(BlurEffect.RadiusProperty));\n    showAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(900));\n    _showStoryboard.Children.Add(showAnimation);\n\n\n    var hideAnimation = new DoubleAnimation\n    &#123;\n        To = 0,\n    &#125;;\n    Storyboard.SetTarget(hideAnimation, _effect);\n    Storyboard.SetTargetProperty(hideAnimation, new PropertyPath(BlurEffect.RadiusProperty));\n    hideAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(200));\n\n    _hideStoryboard.Children.Add(hideAnimation);\n  &#125;\n\n  protected override void OnDetaching()\n  &#123;\n    AssociatedObject.MouseEnter -= AssociatedObject_MouseEnter;\n    AssociatedObject.MouseLeave -= AssociatedObject_MouseLeave;\n    AssociatedObject.Effect = null;\n    base.OnDetaching();\n  &#125;\n\n\n  void AssociatedObject_MouseEnter(object sender, MouseEventArgs e)\n  &#123;\n    _showStoryboard.Begin();\n  &#125;\n\n  void AssociatedObject_MouseLeave(object sender, MouseEventArgs e)\n  &#123;\n    _hideStoryboard.Begin();\n  &#125;\n&#125;</code></pre>\n<p>Аналогичным образом определим данное поведение в разметке и запустим пример. Теперь размытие появляется плавно. Я не буду делать здесь снимки экрана, поскольку анимацию будет показать очень сложно – запустите пример, чтобы увидеть самим.</p>\n<h2>Трансформации</h2><p>Давайте еще немного усложним пример и попробуем добавить трансформации к объекту. Как известно, для того, чтобы трансформировать объект нужно использовать свойство <code>RenderTransform</code>. Если используется несколько трансформаций, то это свойство необходимо инициализировать объектом <code>TransformGroup</code>, в который поместить все нужные трансформации.</p>\n<p>Поскольку мы незнаем заранее применяются ли уже к объекту трансформации мы должны рассмотреть все варианты и написать несложный код по добавлению своей трансформации к объекту в момент присоединения поведения.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">if (AssociatedObject.RenderTransform == null)\n&#123;\n  AssociatedObject.RenderTransform = _transofrmation;\n&#125;\nelse if (AssociatedObject.RenderTransform is TransformGroup)\n&#123;\n  ((TransformGroup)AssociatedObject.RenderTransform).Children.Add(_transofrmation);\n&#125;\nelse\n&#123;\n  var transformGroup = new TransformGroup();\n  var sourceTransform = AssociatedObject.RenderTransform;\n  transformGroup.Children.Add(sourceTransform);\n  transformGroup.Children.Add(_transofrmation);\n\n  AssociatedObject.RenderTransform = transformGroup;\n&#125;</code></pre>\n<p>Остальное – аналогично тому, как мы это делали в предыдущих примерах – определяем анимации и подписываемся на события. Итого получим несложное поведение, реагирующее также на наведения указателя мыши.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class SimpleBehavior3 : Behavior&lt;UIElement>\n&#123;\n  private readonly SkewTransform _transofrmation = new SkewTransform();\n  private readonly Storyboard _showStoryboard = new Storyboard();\n\n  protected override void OnAttached()\n  &#123;\n    base.OnAttached();\n\n    if (AssociatedObject.RenderTransform == null)\n    &#123;\n      AssociatedObject.RenderTransform = _transofrmation;\n    &#125;\n    else if (AssociatedObject.RenderTransform is TransformGroup)\n    &#123;\n      ((TransformGroup)AssociatedObject.RenderTransform).Children.Add(_transofrmation);\n    &#125;\n    else\n    &#123;\n      var transformGroup = new TransformGroup();\n      var sourceTransform = AssociatedObject.RenderTransform;\n      transformGroup.Children.Add(sourceTransform);\n      transformGroup.Children.Add(_transofrmation);\n\n      AssociatedObject.RenderTransform = transformGroup;\n    &#125;\n\n    AssociatedObject.MouseEnter += AssociatedObject_MouseEnter;\n\n\n\n    var animation = new DoubleAnimationUsingKeyFrames();\n    animation.KeyFrames.Add(new SplineDoubleKeyFrame &#123; KeyTime = KeyTime.FromTimeSpan(TimeSpan.Zero), Value = 0 &#125;);\n    animation.KeyFrames.Add(new SplineDoubleKeyFrame &#123; KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(100)), Value = 20 &#125;);\n    animation.KeyFrames.Add(new SplineDoubleKeyFrame &#123; KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(300)), Value = -20 &#125;);\n    animation.KeyFrames.Add(new SplineDoubleKeyFrame &#123; KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(500)), Value = 10 &#125;);\n    animation.KeyFrames.Add(new SplineDoubleKeyFrame &#123; KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(700)), Value = -10 &#125;);\n    animation.KeyFrames.Add(new SplineDoubleKeyFrame &#123; KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(900)), Value = 0 &#125;);\n\n\n    Storyboard.SetTarget(animation, _transofrmation);\n    Storyboard.SetTargetProperty(animation, new PropertyPath(SkewTransform.AngleXProperty));\n    _showStoryboard.Children.Add(animation);\n  &#125;\n\n  protected override void OnDetaching()\n  &#123;\n    AssociatedObject.MouseEnter -= AssociatedObject_MouseEnter;\n    base.OnDetaching();\n  &#125;\n\n  void AssociatedObject_MouseEnter(object sender, MouseEventArgs e)\n  &#123;\n    _showStoryboard.Begin();\n  &#125;\n&#125;</code></pre>\n<p>Запустим приложение и увидим анимированный объект при наведении указателя мыши.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/silverlight-simple-behaviors/beeccec0/2@2x.png\"  alt=\"Простые поведения в Silverlight\"/>\n\n\n\n\n<p>Что интересно, мы можем применить одновременно несколько поведений к одному объекту. Например, можем применить размытие и трансформацию одновременно. Для этого используем следующую разметку.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Button Margin=\"50\" Content=\"Счастье\" Width=\"200\" Height=\"150\">\n  &lt;interactivity:Interaction.Behaviors>\n    &lt;b:SimpleBehavior2 />\n    &lt;b:SimpleBehavior3 />\n  &lt;/interactivity:Interaction.Behaviors>\n&lt;/Button></code></pre>",
            "tags": [
                "XAML",
                "Silverlight"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/silverlight-simple-behaviors/623cb74d/silverlight_simple-behaviors@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2010/silverlight-trigger-behaviors/",
            "url": "https://blog.zwezdin.com/2010/silverlight-trigger-behaviors/",
            "title": "Поведения в Silverlight, обрабатываемые при срабатывании триггера",
            "date_published": "2010-06-01T12:00:00.000Z",
            "content_html": "<p>Недавно я рассказывал об использовании <strong>поведений в Silverlight</strong>. Мы рассматривали возможности объекта <code>Behavior&lt;T&gt;</code>, позволяющего определить визуальное поведение объекта. Вся логика этого объекта была полностью определена внутри реализации поведения. При этом, если нужно было поменять условие срабатывания того или иного действия, то необходимо было изменить код поведения, что несомненно является большим недостатком такого вида поведений. В этой статье мы рассмотрим другой тип поведений, которые позволяют избежать этого.</p>\n<p>Основной проблемой простых поведений, реализованных на базе <code>Behavior&lt;T&gt;</code> является то, что внутри реализации мы жестко задаем набор событий, с которыми будем работать. Это может быть не всегда удобным. Например, если у нас есть поведение для размытия объекта, то было бы удобно в некоторых ситуациях применять поведение при наведении указателя мыши, а в некоторых – при нажатии на кнопку.</p>\n<p>Для того, чтобы реализовать такое поведение можно воспользоваться другим типом поведений, реагирующих на заданные события. Такие поведения создаются на базе объекта <code>TriggerAction&lt;T&gt;</code> и позволяют реализовать собственное поведение.</p>\n<p>Для создания собственного поведения создадим класс-наследник <code>TriggerAction&lt;T&gt;</code>. В отличие от простых поведений, поведения <code>TriggerAction&lt;T&gt;</code> имеют дополнительный метод <code>Invoke</code>, который срабатывает при наступлении заданного события. Кроме того, в поведениях <code>TriggerAction&lt;T&gt;</code> есть также два перегружаемых метода <code>OnAttached</code> и OnDetaching, позволяющих выполнить предварительные и заключительные действия. Давайте сравним порядок действий, выполняемых для простых поведений и для поведений на основе <code>TriggerAction&lt;T&gt;</code>.</p>\n<p>Простые поведения, реализованные на основе объекта <code>Behavior&lt;T&gt;</code>:</p>\n<ol>\n<li>Создаем объект-наследник от <code>Behavior&lt;T&gt;</code>;</li>\n<li>В методе <code>OnAttached</code> задаем необходимые базовые трансформации, если они необходимы;</li>\n<li>Определяем нужные анимации, если они необходимы;</li>\n<li>В методе <code>OnAttached</code> подписываемся на нужные события, которые будут обрабатываться поведением;</li>\n<li>При наступлении событий запускаем подготовленную ранее анимацию;</li>\n<li>Подключаем поведение к элементу управления.</li>\n</ol>\n<p>Поведения, реализованные на основе объекта <code>TriggerAction&lt;T&gt;</code>:</p>\n<ol>\n<li>Создаем объект-наследник от <code>TriggerAction&lt;T&gt;</code>;</li>\n<li>В методе <code>OnAttached</code> задаем необходимые базовые трансформации, если они необходимы;</li>\n<li>Определяем нужные анимации, если они необходимы;</li>\n<li>Запускаем нужные анимации при вызове метода Invoke;</li>\n<li>Подключаем поведение к элементу управления, указав при этом какое именно событие нужно обрабатывать.</li>\n</ol>\n<p>Как видно, разница заключается в том, что простые поведения сами подписываются на нужные события в момент выполнения метода <code>OnAttached</code>, а поведения на базе <code>TriggerAction&lt;T&gt;</code> позволяют выбрать события в момент подключения поведения к элементу управления. В этом и состоит разница между этими двумя поведениями.</p>\n<h2>Создание поведения на основе <code>TriggerAction&lt;T&gt;</code></h2><p>Для начала необходимо создать объект-наследник <code>TriggerAction&lt;T&gt;</code>. В качестве generic-параметра будем использовать объект <code>UIElement</code> – это позволит применить поведение к любому элементу управления. С другой стороны, если необходимы какие-то специфичные свойства конкретных элементов управления, то можно использовать более конкретный тип, например, <code>Button</code>, <code>TextBox</code> и т.д.</p>\n<p>При создании поведения нужно реализовать единственный метод <code>Invoke</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">using System;\nusing System.Net;\nusing System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Documents;\nusing System.Windows.Ink;\nusing System.Windows.Input;\nusing System.Windows.Media;\nusing System.Windows.Media.Animation;\nusing System.Windows.Media.Effects;\nusing System.Windows.Shapes;\nusing System.Windows.Interactivity;\n\nnamespace SilverlightApplication30\n&#123;\n  public class EventSampleBehavior : TriggerAction&lt;UIElement>\n  &#123;\n    protected override void Invoke(object parameter)\n    &#123;\n\n    &#125;\n  &#125;\n&#125;</code></pre>\n<p>Если необходимо отследить момент подключения/отключения поведения от элемента управления, то можно перегрузить виртуальные методы <code>OnAttaching</code> и <code>OnDeattaching</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">using System;\nusing System.Net;\nusing System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Documents;\nusing System.Windows.Ink;\nusing System.Windows.Input;\nusing System.Windows.Media;\nusing System.Windows.Media.Animation;\nusing System.Windows.Media.Effects;\nusing System.Windows.Shapes;\nusing System.Windows.Interactivity;\n\nnamespace SilverlightApplication30\n&#123;\n  public class EventSampleBehavior : TriggerAction&lt;UIElement>\n  &#123;\n    protected override void OnAttached()\n    &#123;\n      base.OnAttached();\n    &#125;\n\n    protected override void OnDetaching()\n    &#123;\n      base.OnDetaching();\n    &#125;\n\n    protected override void Invoke(object parameter)\n    &#123;\n    &#125;\n  &#125;\n&#125;</code></pre>\n<p>Давайте определим логику, которая при срабатывании поведения будет трансформировать объект в виде небольшой анимации. Для этого добавим объекты трансформации и анимации в состав класса поведения. Также нам необходимо задать логику для определения трансформаций – если трансформации уже есть, то объединить их в группу, а если нет, то просто создать новую. Наконец, нам нужно задать поведение анимации. В данном случае воспользуемся анимацией <code>DoubleAnimationUsingKeyFrames</code>. Все эти действия следует выполнить в момент подключения поведения.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">using System;\nusing System.Net;\nusing System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Documents;\nusing System.Windows.Ink;\nusing System.Windows.Input;\nusing System.Windows.Media;\nusing System.Windows.Media.Animation;\nusing System.Windows.Media.Effects;\nusing System.Windows.Shapes;\nusing System.Windows.Interactivity;\n\nnamespace SilverlightApplication30\n&#123;\n  public class EventSampleBehavior : TriggerAction&lt;UIElement>\n  &#123;\n    private readonly SkewTransform _transofrmation = new SkewTransform();\n    private readonly Storyboard _showStoryboard = new Storyboard();\n\n    protected override void OnAttached()\n    &#123;\n      base.OnAttached();\n\n      if (AssociatedObject.RenderTransform == null)\n      &#123;\n          AssociatedObject.RenderTransform = _transofrmation;\n      &#125;\n      else if (AssociatedObject.RenderTransform is TransformGroup)\n      &#123;\n          ((TransformGroup)AssociatedObject.RenderTransform).Children.Add(_transofrmation);\n      &#125;\n      else\n      &#123;\n          var transformGroup = new TransformGroup();\n          var sourceTransform = AssociatedObject.RenderTransform;\n          transformGroup.Children.Add(sourceTransform);\n          transformGroup.Children.Add(_transofrmation);\n\n          AssociatedObject.RenderTransform = transformGroup;\n      &#125;\n\n\n      var animation = new DoubleAnimationUsingKeyFrames();\n      animation.KeyFrames.Add(new SplineDoubleKeyFrame &#123; KeyTime = KeyTime.FromTimeSpan(TimeSpan.Zero), Value = 0 &#125;);\n      animation.KeyFrames.Add(new SplineDoubleKeyFrame &#123; KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(100)), Value = 20 &#125;);\n      animation.KeyFrames.Add(new SplineDoubleKeyFrame &#123; KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(300)), Value = -20 &#125;);\n      animation.KeyFrames.Add(new SplineDoubleKeyFrame &#123; KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(500)), Value = 10 &#125;);\n      animation.KeyFrames.Add(new SplineDoubleKeyFrame &#123; KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(700)), Value = -10 &#125;);\n      animation.KeyFrames.Add(new SplineDoubleKeyFrame &#123; KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(900)), Value = 0 &#125;);\n\n      Storyboard.SetTarget(animation, _transofrmation);\n      Storyboard.SetTargetProperty(animation, new PropertyPath(SkewTransform.AngleXProperty));\n      _showStoryboard.Children.Add(animation);\n    &#125;\n\n    protected override void OnDetaching()\n    &#123;\n        base.OnDetaching();\n    &#125;\n\n    protected override void Invoke(object parameter)\n    &#123;\n    &#125;\n  &#125;\n&#125;</code></pre>\n<p>Все, что теперь осталось сделать, это запустить анимацию в момент вызова метода <code>Invoke</code>. В итоге получим следующий финальный код поведения.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">using System;\nusing System.Net;\nusing System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Documents;\nusing System.Windows.Ink;\nusing System.Windows.Input;\nusing System.Windows.Media;\nusing System.Windows.Media.Animation;\nusing System.Windows.Media.Effects;\nusing System.Windows.Shapes;\nusing System.Windows.Interactivity;\n\nnamespace SilverlightApplication30\n&#123;\n  public class EventSampleBehavior : TriggerAction&lt;UIElement>\n  &#123;\n    private readonly SkewTransform _transofrmation = new SkewTransform();\n    private readonly Storyboard _showStoryboard = new Storyboard();\n\n    protected override void OnAttached()\n    &#123;\n      base.OnAttached();\n\n      if (AssociatedObject.RenderTransform == null)\n      &#123;\n        AssociatedObject.RenderTransform = _transofrmation;\n      &#125;\n      else if (AssociatedObject.RenderTransform is TransformGroup)\n      &#123;\n        ((TransformGroup)AssociatedObject.RenderTransform).Children.Add(_transofrmation);\n      &#125;\n      else\n      &#123;\n        var transformGroup = new TransformGroup();\n        var sourceTransform = AssociatedObject.RenderTransform;\n        transformGroup.Children.Add(sourceTransform);\n        transformGroup.Children.Add(_transofrmation);\n\n        AssociatedObject.RenderTransform = transformGroup;\n      &#125;\n\n\n      var animation = new DoubleAnimationUsingKeyFrames();\n      animation.KeyFrames.Add(new SplineDoubleKeyFrame &#123; KeyTime = KeyTime.FromTimeSpan(TimeSpan.Zero), Value = 0 &#125;);\n      animation.KeyFrames.Add(new SplineDoubleKeyFrame &#123; KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(100)), Value = 20 &#125;);\n      animation.KeyFrames.Add(new SplineDoubleKeyFrame &#123; KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(300)), Value = -20 &#125;);\n      animation.KeyFrames.Add(new SplineDoubleKeyFrame &#123; KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(500)), Value = 10 &#125;);\n      animation.KeyFrames.Add(new SplineDoubleKeyFrame &#123; KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(700)), Value = -10 &#125;);\n      animation.KeyFrames.Add(new SplineDoubleKeyFrame &#123; KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(900)), Value = 0 &#125;);\n\n      Storyboard.SetTarget(animation, _transofrmation);\n      Storyboard.SetTargetProperty(animation, new PropertyPath(SkewTransform.AngleXProperty));\n      _showStoryboard.Children.Add(animation);\n    &#125;\n\n    protected override void OnDetaching()\n    &#123;\n      base.OnDetaching();\n    &#125;\n\n    protected override void Invoke(object parameter)\n    &#123;\n      _showStoryboard.Begin();\n    &#125;\n  &#125;\n&#125;</code></pre>\n<p>Теперь, после создания поведения его необходимо подключить к элементу управления. Для этого необходимо подключить нужное пространство имен.</p>\n<p>Добавим на форму кнопку и зададим для нее поведение.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;UserControl x:Class=\"SilverlightApplication30.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:i=\"clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity\"\n    xmlns:local=\"clr-namespace:SilverlightApplication30\"\n    mc:Ignorable=\"d\"\n    d:DesignHeight=\"300\" d:DesignWidth=\"400\">\n\n  &lt;Grid x:Name=\"LayoutRoot\" Background=\"White\">\n    &lt;Button Content=\"Button\" Width=\"400\" Height=\"300\" HorizontalAlignment=\"Center\" VerticalAlignment=\"Center\">\n      &lt;i:Interaction.Triggers>\n        &lt;i:EventTrigger EventName=\"MouseMove\">\n          &lt;local:EventSampleBehavior />\n        &lt;/i:EventTrigger>\n      &lt;/i:Interaction.Triggers>\n    &lt;/Button>\n  &lt;/Grid>\n&lt;/UserControl></code></pre>\n<p>Как видно, поведение будет срабатывать в момент запуска события <code>MouseMove</code>. При этом, если нам необходимо использовать поведение в других ситуациях, например, при нажатии на кнопке, то мы можем просто поменять имя события при присоединении его к элементу управления.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;UserControl x:Class=\"SilverlightApplication30.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:i=\"clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity\"\n    xmlns:local=\"clr-namespace:SilverlightApplication30\"\n    mc:Ignorable=\"d\"\n    d:DesignHeight=\"300\" d:DesignWidth=\"400\">\n\n  &lt;Grid x:Name=\"LayoutRoot\" Background=\"White\">\n    &lt;Button Content=\"Button\" Width=\"400\" Height=\"300\" HorizontalAlignment=\"Center\" VerticalAlignment=\"Center\">\n      &lt;i:Interaction.Triggers>\n        &lt;i:EventTrigger EventName=\"Click\">\n          &lt;local:EventSampleBehavior />\n        &lt;/i:EventTrigger>\n      &lt;/i:Interaction.Triggers>\n    &lt;/Button>\n  &lt;/Grid>\n&lt;/UserControl></code></pre>\n<p>Этим поведения на базе <code>TriggerAction</code> и являются более универсальными, чем простые поведения.</p>",
            "tags": [
                "XAML",
                "Silverlight"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/silverlight-trigger-behaviors/5e9a9fae/silverlight_trigger-behaviors@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2010/visual-studio-2010-start-screen/",
            "url": "https://blog.zwezdin.com/2010/visual-studio-2010-start-screen/",
            "title": "Стартовая страница в Visual Studio 2010",
            "date_published": "2010-05-30T12:00:00.000Z",
            "content_html": "<p>Пользовательский интерфейс Visual Studio 2010 построен на основе технологии <strong>Windows Presentation Foundation</strong>. Кроме эмоций (положительных и отрицательных) у этого явления есть еще и рациональное зерно – возможности для расширения различных частей Visual Studio. Поскольку в WPF пользовательский интерфейс описывается на основе языка XAML, то это дает возможность модифицировать его более просто. Одним из наиболее ярких примеров такого расширения функциональности – <strong>стартовые страницы Visual Studio 2010</strong>.</p>\n<p><strong>Стартовые страницы</strong> — это то, что мы видим сразу же после запуска Visual Studio. На них обычно собирается полезная для разработчика информация, такая как ссылки на ресурсы для разработчиков, новости, недавние проекты и др. Если взглянуть на то, как устроены стартовые страницы в Visual Studio 2010, то можно обнаружить, что это – не более, чем файл с разметкой XAML.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/visual-studio-2010-start-screen/4a5ac465/1@2x.png\"  alt=\"Стартовая страница в Visual Studio 2010\"/>\n\n\n\n\n<p>Изменение содержимого стартовой страницы Visual Studio 2010 может быть полезным в самых различных ситуациях. Например, здесь можно подключить ленту корпоративного блога, для того, чтобы оперативно узнавать новости, происходящие внутри команды разработчиков. На самом деле сценарии здесь ограничиваются только фантазией разработчиков. Давайте рассмотрим процесс создания и модификации стартовых страниц.</p>\n<p>Если вы устанавливали Visual Studio 2010 не изменяя стандартного пути, то содержимое стартовых страниц будет расположено в папке <code>C:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\Common7\\IDE\\StartPages\\</code>. В предварительных версиях Visual Studio здесь находились XAML-файлы, которые поддавались модификации. В финальной сборке Visual Studio 2010 здесь находятся файлы, описывающие содержимое страниц, однако, это – не файлы XAML. Вообще говоря, не стоит изменять содержимое этих файлов, можно пойти более цивилизованным путем – использовать стандартные инструменты для модификации стартовых страниц.</p>\n<p>Для создания стартовых страниц уже существует стандартный шаблон в Visual Studio Galery - Custom Start Page Project Template. При установке его в среду Visual Studio появляется новый тип проекта в разделе <code>Extensibility</code> → <code>Custom Start Page</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/visual-studio-2010-start-screen/e1816ba5/2@2x.png\"  alt=\"Стартовая страница в Visual Studio 2010\"/>\n\n\n\n\n<p>Преимуществом этого шаблона является то, что при создании проекта создаются шаблоны для встраивания, элементы управления WPF, а также шаблон VSIX, который позволяет быстро собрать модуль расширения для Visual Studio когда все будет готово.</p>\n<p>Для отладки стартовой страницы можно просто запустить проект (<kbd>F5</kbd>). При этом запустится новый экземпляр Visual Studio 2010, который будет служить средой для просмотра результатов работы. Однако, по умолчанию новая стартовая страница отображаться не будет, для того, чтобы она отобразилась необходимо задать соответствующие параметры в Visual Studio. Для этих целей существует настройка <code>Tools</code> → <code>Options</code> → <code>Environment</code> → <code>Startup</code> → <code>Customize Start Page</code>. Здесь необходимо указать разрабатываемую страницу.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/visual-studio-2010-start-screen/02045ddd/3@2x.png\"  alt=\"Стартовая страница в Visual Studio 2010\"/>\n\n\n\n\n<p>Теперь у нас готов проект для создания собственного представления стартовой страницы Visual Studio 2010.</p>\n<h2>Структура проекта</h2><p>Расширение состоит из двух проектов — главного проекта, в котором содержаться все необходимые элементы для установки страницы и вспомогательного проекта, содержащего элемент управления WPF.</p>\n<p>Главный проект содержит файл описания стартовой страницы <code>StartPage.xaml</code>, на которой размещается все содержимое стартовой страницы. По умолчанию на этой странице содержатся все стандартные вкладки, присутствующие в обычной стартовой странице Visual Studio 2010, и дополнительная вкладка с вашим содержимым. В дополнительной вкладке содержится элемент управления из вспомогательного проекта.</p>\n<p>Также главный проект содержит манифест для создания пакета VSIX – файлы расширения Visual Studio 2010, которые можно установить в несколько нажатий мышью. Таким образом, при сборке приложения в папке с результатом сразу будет находится готовый VSIX-пакет, который можно устанавливать на другие рабочие места.</p>\n<h2>Создание собственного содержимого в стартовой странице</h2><p>Теперь, когда мы разобрались со структурой проекта, давайте перейдем к формированию содержимого стартовой страницы.</p>\n<p>Для управления содержимым стартовой страницы существует два варианта – изменение страницы <code>StartPage.xaml</code> в основном проекте и изменение элемента управления <code>MyControl.xaml</code> во вспомогательном проекте.</p>\n<p>Если мы воспользуемся первым способом, то у нас будет больше возможностей по изменению внешнего вида страницы. Например, мы можем добавить логотип своей компании на стартовую страницу, изменить положение элементов <code>Создать проект</code>, <code>Открыть проект</code> или списка проектов, отрываемых недавно. Сценариев, которые мы можем выполнять, изменяя содержимое этого файла – огромное количество. Общая структура этого файла выглядит следующим образом.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Grid xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n      xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n      xmlns:sp=\"clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.StartPage\"\n      xmlns:vs=\"clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.10.0\"\n      xmlns:vsfx=\"clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.10.0\"\n      xmlns:my=\"clr-namespace:MyStartPageControl;assembly=MyStartPageControl\"\n      mc:Ignorable=\"d\"\n      xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n      xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" d:DesignHeight=\"600\" d:DesignWidth=\"800\">\n\n    &lt;Grid.Resources>\n        &lt;ResourceDictionary>\n            &lt;ResourceDictionary.MergedDictionaries>\n                &lt;ResourceDictionary Source=\"/Microsoft.VisualStudio.Shell.StartPage;component/Styles/startpageresources.xaml\" />\n            &lt;/ResourceDictionary.MergedDictionaries>\n        &lt;/ResourceDictionary>\n    &lt;/Grid.Resources>\n\n    &lt;Grid x:Name=\"LayoutRoot\"\n          Background=\"&#123;DynamicResource &#123;x:Static vsfx:VsBrushes.StartPageBackgroundKey&#125;&#125;\"\n          Style=\"&#123;DynamicResource StartPage.OuterGridStyle&#125;\">\n\n        &lt;Grid.ColumnDefinitions>\n            &lt;!-- Left Hand Column -->\n            &lt;!-- This column is just wide enough to fit the Command Buttons and Start Page Options.\n                 The Recent Project area is constrained to the width of the Commmand Buttons. -->\n            &lt;ColumnDefinition MinWidth=\"&#123;Binding ElementName=commandButtonsGrid, Path=ActualWidth&#125;\"\n                              Width=\"3*\" MaxWidth=\"330\" />\n            &lt;!-- Gutter -->\n            &lt;ColumnDefinition Width=\"6\"/>\n            &lt;!-- Allow main column to collapse to 0 -->\n            &lt;ColumnDefinition Width=\"7*\" MinWidth=\"0\"/>\n        &lt;/Grid.ColumnDefinitions>\n\n        &lt;Grid.RowDefinitions>\n            &lt;!-- Logo-->\n            &lt;RowDefinition Height=\"130\" />\n            &lt;!-- Main Content Area -->\n            &lt;RowDefinition Height=\"*\"/>\n        &lt;/Grid.RowDefinitions>\n\n        &lt;!--Logo Image -->\n        &lt;!--Note: To reference files relative to this XAML file, use syntax like the following: &lt;Image Source=\"&#123;sp:StartPageRelative myImage.png&#125;\" .../> -->\n        &lt;Image Grid.Column=\"0\" Grid.Row=\"0\" Grid.ColumnSpan=\"3\"\n               Source=\"&#123;Binding Background.Logo&#125;\"\n               HorizontalAlignment=\"Left\"\n               VerticalAlignment=\"Top\" Width=\"1600\" />\n\n        &lt;!--Left Column-->\n        &lt;Grid Width=\"Auto\" Grid.Column=\"0\" Grid.Row=\"1\"\n              Margin=\"15,-35,0,15\" VerticalAlignment=\"Stretch\">\n            &lt;Grid.RowDefinitions>\n                &lt;!-- Command Buttons for New Projects -->\n                &lt;RowDefinition Height=\"Auto\"/>\n                &lt;!-- MRU Row -->\n                &lt;RowDefinition Height=\"*\"/>\n                &lt;!--Gutter at bottom of page -->\n                &lt;RowDefinition Height=\"Auto\"/>\n            &lt;/Grid.RowDefinitions>\n\n            &lt;!-- Command Buttons -->\n            &lt;Grid x:Name=\"commandButtonsGrid\" MinWidth=\"270\"\n                  Grid.Row=\"0\" Margin=\"0,15,0,30\" HorizontalAlignment=\"Left\">\n                &lt;Grid.RowDefinitions>\n                    &lt;RowDefinition Height=\"Auto\"/>\n                    &lt;RowDefinition Height=\"3\"/>\n                    &lt;RowDefinition Height=\"Auto\"/>\n                    &lt;RowDefinition Height=\"3\"/>\n                    &lt;RowDefinition Height=\"Auto\"/>\n                &lt;/Grid.RowDefinitions>\n                &lt;!-- ... -->\n\n            &lt;/Grid>\n\n\n            &lt;!-- Recent Projects Section -->\n            &lt;Grid Grid.Row=\"1\" HorizontalAlignment=\"Left\" Width=\"Auto\">\n                &lt;Grid.RowDefinitions>\n                    &lt;!-- Heading -->\n                    &lt;RowDefinition Height=\"Auto\" />\n                    &lt;!-- MRU Container -->\n                    &lt;RowDefinition Height=\"*\" />\n                &lt;/Grid.RowDefinitions>\n\n                &lt;!-- Recent Projects Heading-->\n                &lt;StackPanel Grid.Row=\"0\" Margin=\"0,0,0,10\" Orientation=\"Horizontal\">\n                    &lt;TextBlock Text=\"Recent Projects\" VerticalAlignment=\"Top\"\n                               Style=\"&#123;DynamicResource StartPage.HeadingTextStyle&#125;\"\n                               x:Uid=\"RecentProjects\"/>\n                    &lt;Path VerticalAlignment=\"Center\" Margin=\"6,0,0,-4\" Width=\"Auto\"\n                          Height=\"1\" Stretch=\"Fill\" StrokeThickness=\"1\" StrokeLineJoin=\"Round\"\n                          Stroke=\"&#123;DynamicResource &#123;x:Static vsfx:VsBrushes.StartPageSeparatorKey&#125;&#125;\"\n                          Data=\"F1 M 0.5,0.5L 199.5,0.5\"/>\n                &lt;/StackPanel>\n                &lt;!-- MRU List Container -->\n\n                &lt;!-- ... -->\n\n            &lt;/Grid>\n\n            &lt;!-- Start Page Options -->\n            &lt;StackPanel Margin=\"0,5,0,0\" Grid.Column=\"0\" Grid.Row=\"2\">\n                &lt;CheckBox x:Uid=\"AutoClose_Option\" Content=\"Close page after project load\"\n                          IsChecked=\"&#123;Binding ClosePageOnOpenProject, Mode=TwoWay&#125;\"\n                          Margin=\"0,0,2,0\"/>\n                &lt;CheckBox x:Uid=\"ShowOnStartUp_Option\" Content=\"Show page on startup\"\n                          IsChecked=\"&#123;Binding ShowPageAtStartup, Mode=TwoWay&#125;\"\n                          Margin=\"0,2,0,0\"/>\n            &lt;/StackPanel>\n        &lt;/Grid>\n\n        &lt;!--Center Content-->\n        &lt;Grid Grid.Column=\"2\" Grid.Row=\"1\" Margin=\"0,-35,15,15\">\n            &lt;TabControl Style=\"&#123;DynamicResource StartPage.TabControlStyle&#125;\"\n                        SelectedIndex=\"&#123;Binding SelectedTabItemIndex, Mode=TwoWay&#125;\">\n\n            &lt;!-- ... -->\n\n            &lt;/TabControl>\n        &lt;/Grid>\n    &lt;/Grid>\n&lt;/Grid></code></pre>\n<p>Как видно, при описании страницы используются ряд стандартных пространств имен. Они необходимы для получения информации для отображения, а также для выполнения команд. По этой разметке нетрудно догадаться каким образом определяется содержимое страницы. Видно также, что для основного содержимого используется элемент управления <code>TabControl</code>, который содержит ряд стандартных вкладок.</p>\n<p>Если нам не требуется радикально изменять внешний вид стартовой страницы, то рекомендуется создавать набор необходимых вкладок, которые будут отображать нужную информацию. Как раз здесь и задается дополнительная вкладка для нашего содержимого. Эта вкладка содержит ссылку ну элемент управления, определенный в дополнительной сборке.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;TabControl Style=\"&#123;DynamicResource StartPage.TabControlStyle&#125;\"\n            SelectedIndex=\"&#123;Binding SelectedTabItemIndex, Mode=TwoWay&#125;\">\n\n    &lt;!-- ... -->\n\n    &lt;TabItem Header=\"MyControl\" Style=\"&#123;DynamicResource StartPage.TabItemStyle&#125;\">\n        &lt;my:MyControl/>\n    &lt;/TabItem>\n&lt;/TabControl></code></pre>\n<p>В нашем случае давайте воспользуемся этим подходом и не будем модифицировать основной файл страницы. Вместо этого добавим свою функциональность во внешний элемент управления.</p>\n<p>Давайте создадим специальную вкладку, на которой будем видеть все наши задачи, которые находятся у нас в данный момент в Outlook 2010. Для этого в элемент управления <code>MyControl</code> добавим элемент <code>ListBox</code>, который и будет содержать список задач из Outlook.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;ListBox x:Name=\"Tasks\"\n         Foreground=\"&#123;DynamicResource &#123;x:Static vsfx:VsBrushes.StartPageTextBodyKey&#125;&#125;\"\n         Background=\"Transparent\">\n    &lt;ListBox.ItemTemplate>\n        &lt;DataTemplate>\n            &lt;TextBlock Text=\"&#123;Binding &#125;\" Margin=\"20\" FontSize=\"16\" />\n        &lt;/DataTemplate>\n    &lt;/ListBox.ItemTemplate>\n&lt;/ListBox></code></pre>\n<p>Теперь определим обработчик загрузки элемента управления, в котором будет содержаться логика получения элементов из Outlook. Для получения данных воспользуемся интерфейсами COM, для чего сделаем ссылку на <code>Microsoft.Office.Interop.Outlook</code>. Код для получения информации выглядит очень просто – в нем происходит обращение к Outlook и отображение полученных данных в созданном ранее элементе управления <code>ListBox</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void UserControl_Loaded(object sender, RoutedEventArgs e)\n&#123;\n    var app = new Outlook.Application();\n    var folder = app.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderTasks);\n    Tasks.ItemsSource = folder.Items.OfType&lt;Outlook.TaskItem>().Select(t => t.Subject);\n&#125;</code></pre>\n<p>Теперь запустим проект и увидим, что появилась новая вкладка, в которой содержаться все актуальные задачи.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/visual-studio-2010-start-screen/1cc5a2e6/4@2x.png\"  alt=\"Стартовая страница в Visual Studio 2010\"/>\n\n\n\n\n<p>В данном случае расширение получилось очень простым. Однако, аналогичным образом доработать его до требуемого результата достаточно просто.</p>\n<p>Таким образом, мы без особых усилий изменили вид стартовой страницы Visual Studio 2010 разместив на ней нужное нам содержимое.</p>",
            "tags": [
                "Visual Studio",
                "WPF",
                "XAML"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/visual-studio-2010-start-screen/e64b6257/visual_studio-2010-start-screen@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2010/visual-studio-developer-contest-results/",
            "url": "https://blog.zwezdin.com/2010/visual-studio-developer-contest-results/",
            "title": "Итоги конкурса статей по Visual Studio 2010",
            "date_published": "2010-05-20T12:00:00.000Z",
            "content_html": "<p>В начале апреля мы объявили <a href=\"/2010/visual-studio-developer-contest\">конкурс статей</a> о разработке с Visual Studio 2010. Пришло время подвести итоги и назвать победителей.</p>\n<p>Для начала хотелось бы сказать, что все получилось не совсем так, как хотелось бы. Участников конкурса было не так много. С другой стороны статьи получились действительно интересными и полезными каждая по-своему, за что хотелось бы поблагодарить их авторов. Итак, теперь о результатах.</p>\n<p>После небольшого обсуждения жюри пришло к выводу, что призы должны распределиться следующим образом.</p>\n<p>Главный приз мышь <strong>Microsoft SideWinder Gaming Laser Mouse</strong> получает <strong>Denis Liger</strong>, написавший в общей сложности 5 статей. Кроме того, к основному призу прилагается пивной бокал MSDN, книга “Windows для профессионалов” и сертификат на бесплатную сдачу экзамена MCP.</p>\n<ul>\n<li>Установка Visual Studio Team Foundation Server 2010</li>\n<li>Visual Studio Team Foundation Server 2010</li>\n<li>Система контроля версий в TFS 2010</li>\n<li>Работа с системой отслеживания ошибок в TFS 2010</li>\n<li>Сборки в TFS 2010</li>\n</ul>\n<p>Второй приз <strong>Microsoft LifeCam VX-5000</strong> получает <strong>Roman Kurbangaliev</strong> за интереснейший обзор расширений для Visual Studio. Кроме того, к основному призу прилагается пивной бокал MSDN, книга “Windows для профессионалов” и сертификат на бесплатную сдачу экзамена MCP.</p>\n<p>Поощрительные призы в виде пивного бокала MSDN, книги “Windows для профессионалов” и двух сертификатов на бесплатную сдачу экзамена MCP получают <strong>Курманов Самат</strong> и <strong>Иван Андреев</strong> за их замечательные статьи про <a href=\"http://habrahabr.ru/blogs/XNA/90768/\">разработку игр для Windows Phone 7</a> и создание медиа-плеера в Visual Studio 2010.</p>\n<p>Кроме того, <a href=\"http://xaoccps.habrahabr.ru/\">Владимир Юнев</a> (Microsoft MVP и активный участник сообщества Хабрахабр) предоставил дополнительные призы для авторов этих статей – приглашение на вступление в закрытое сообщество Хабрахабр, за что ему отдельное спасибо.</p>\n<p>Для получения своих призов всем победителям нужно написать мне на электронную почту адрес доставки.</p>\n<p>Несмотря на то, что статей было не так много как хотелось бы, все равно получилось очень интересно, ну а призы нашли своих законных владельцев. Спасибо всем участникам за интересные работы, а также участникам жюри за их посильный вклад в данный конкурс.</p>",
            "tags": [
                "Visual Studio",
                "Конкурсы"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/visual-studio-developer-contest-results/7110840c/visual_studio-developer-contest-results@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2010/windows-phone-ink/",
            "url": "https://blog.zwezdin.com/2010/windows-phone-ink/",
            "title": "Рукописный ввод в Windows Phone 7",
            "date_published": "2010-05-02T12:00:00.000Z",
            "content_html": "<p><strong>Ввод информации</strong> в <strong>Windows Phone 7</strong> осуществляется путем использования встроенной программной или аппаратной (на некоторых устройствах) клавиатуры. Программная клавиатура способна подстраиваться под текущую ситуацию, в которой находится пользователь. Тем не менее, иногда может потребоваться нарисовать что-то на экране – в этом случае необходим рукописный ввод.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-ink/2482cac0/1@2x.png\"  alt=\"Рукописный ввод в Windows Phone 7\"/>\n\n\n\n\n<p>Рукописный ввод хорошо использовать когда нужно что-то нарисовать – небольшую картинку, например, или оставить свою подпись на экране. Такие задачи встречаются не слишком часто, но тем не менее они появляются. Для того, чтобы реализовать в приложении такую функциональность можно воспользоваться объектом <code>InkPresenter</code>. Для этого просто добавим его на форму.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;phoneNavigation:PhoneApplicationPage\n    x:Class=\"InkTest.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:phoneNavigation=\"clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    mc:Ignorable=\"d\" d:DesignWidth=\"480\" d:DesignHeight=\"800\"\n    xmlns:shell=\"clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone.Shell\"\n    FontFamily=\"&#123;StaticResource PhoneFontFamilyNormal&#125;\"\n    FontSize=\"&#123;StaticResource PhoneFontSizeNormal&#125;\"\n    Foreground=\"&#123;StaticResource PhoneForegroundBrush&#125;\">\n\n    &lt;Grid x:Name=\"LayoutRoot\" Background=\"&#123;StaticResource PhoneBackgroundBrush&#125;\">\n        &lt;Grid.RowDefinitions>\n            &lt;RowDefinition Height=\"Auto\"/>\n            &lt;RowDefinition Height=\"*\"/>\n        &lt;/Grid.RowDefinitions>\n\n        &lt;!--TitleGrid is the name of the application and page title-->\n        &lt;Grid x:Name=\"TitleGrid\" Grid.Row=\"0\">\n            &lt;TextBlock Text=\"MY APPLICATION\" x:Name=\"textBlockPageTitle\" Style=\"&#123;StaticResource PhoneTextPageTitle1Style&#125;\"/>\n            &lt;TextBlock Text=\"Ink\" x:Name=\"textBlockListTitle\" Style=\"&#123;StaticResource PhoneTextPageTitle2Style&#125;\"/>\n        &lt;/Grid>\n\n        &lt;!--ContentGrid is empty. Place new content here-->\n        &lt;Grid x:Name=\"ContentGrid\" Grid.Row=\"1\">\n            &lt;InkPresenter Background=\"White\" x:Name=\"ink1\" />\n        &lt;/Grid>\n    &lt;/Grid>\n\n&lt;/phoneNavigation:PhoneApplicationPage></code></pre>\n<p>Однако, простого добавления элемента на форму будет недостаточно. После добавление необходимо обработать несколько событий – <code>MouseMove</code>, <code>MouseLeftButtonDown</code> и <code>MouseLeftButtonUp</code>.</p>\n<p>Дело в том, что объект <code>InkPresenter</code> содержит коллекцию <code>Strokes</code>, которая объекты <code>Stroke</code>. <code>Stroke</code> — это набор точек, нарисованных пользователем. Наша задача заключается в том, чтобы при нажатии и перемещении указателя сохранять все координаты в объект <code>Stroke</code> и при отпускании добавить все эти точки в коллекцию <code>Strokes</code> объекта <code>InkPresenter</code>.</p>\n<p>Для этих целей при нажатии на экран мы создадим новый объект <code>Stroke</code> и при перемещении указателя будем добавлять каждую точку в этот объект. Для этих целей будем использовать события <code>MouseLeftButtonDown</code> и <code>MouseMove</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public MainPage()\n&#123;\n    InitializeComponent();\n\n    SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape;\n    ink1.MouseMove += new MouseEventHandler(ink1_MouseMove);\n    ink1.MouseLeftButtonDown += new MouseButtonEventHandler(ink1_MouseLeftButtonDown);\n&#125;\n\nprivate Stroke _currentStroke;\n\nvoid ink1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)\n&#123;\n    ink1.CaptureMouse();\n    _currentStroke = new Stroke();\n    _currentStroke.DrawingAttributes.Color = Colors.Red;\n\n    var currentPosition = e.GetPosition(ink1);\n    _currentStroke.StylusPoints.Add(new StylusPoint(currentPosition.X, currentPosition.Y));\n\n    ink1.Strokes.Add(_currentStroke);\n&#125;\n\nvoid ink1_MouseMove(object sender, MouseEventArgs e)\n&#123;\n    if (_currentStroke != null)\n    &#123;\n        var currentPosition = e.GetPosition(ink1);\n        _currentStroke.StylusPoints.Add(new StylusPoint(currentPosition.X, currentPosition.Y));\n    &#125;\n&#125;</code></pre>\n<p>После того, как пользователь нарисовал то, что хотел, нужно просто присвоить полю <code>_currentStroke</code> пустое значение (для того, чтобы больше не обрабатывалось событие <code>MouseMove</code>).</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">void ink1_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)\n&#123;\n    _currentStroke = null;\n&#125;</code></pre>\n<p><em>После этого рукописный ввод уже будет работать.</em> Давайте немного усовершенствуем наше приложение и добавим возможность отмены последнего ввода. Как вы, наверное, догадались, для этого необходимо удалить последний объект <code>Stroke</code> из коллекции <code>Strokes</code>.</p>\n<p>Добавим <a href=\"/2010/windows-phone-appbar/\">меню</a>, в котором будет пункт, позволяющий отменять последнее действие. Для этого сделаем ссылку на сборку <code>Microsoft.Phone.Shell</code> и определим пространство имен <code>shell</code> в XAML.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;phoneNavigation:PhoneApplicationPage\n    x:Class=\"InkTest.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:phoneNavigation=\"clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    mc:Ignorable=\"d\" d:DesignWidth=\"480\" d:DesignHeight=\"800\"\n    xmlns:shell=\"clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone.Shell\"\n    FontFamily=\"&#123;StaticResource PhoneFontFamilyNormal&#125;\"\n    FontSize=\"&#123;StaticResource PhoneFontSizeNormal&#125;\"\n    Foreground=\"&#123;StaticResource PhoneForegroundBrush&#125;\">\n\n&lt;!-- содержимое формы -->\n\n&lt;/phoneNavigation:PhoneApplicationPage></code></pre>\n<p>Осталось только определить содержимое меню и создать для него обработчик.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;phoneNavigation:PhoneApplicationPage\n    x:Class=\"InkTest.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:phoneNavigation=\"clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    mc:Ignorable=\"d\" d:DesignWidth=\"480\" d:DesignHeight=\"800\"\n    xmlns:shell=\"clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone.Shell\"\n    FontFamily=\"&#123;StaticResource PhoneFontFamilyNormal&#125;\"\n    FontSize=\"&#123;StaticResource PhoneFontSizeNormal&#125;\"\n    Foreground=\"&#123;StaticResource PhoneForegroundBrush&#125;\">\n    &lt;phoneNavigation:PhoneApplicationPage.ApplicationBar>\n        &lt;shell:ApplicationBar IsMenuEnabled=\"True\">\n            &lt;shell:ApplicationBar.MenuItems>\n                &lt;shell:ApplicationBarMenuItem Text=\"Undo\" Click=\"ApplicationBarMenuItem_Click\"/>\n            &lt;/shell:ApplicationBar.MenuItems>\n        &lt;/shell:ApplicationBar>\n    &lt;/phoneNavigation:PhoneApplicationPage.ApplicationBar>\n\n    &lt;Grid x:Name=\"LayoutRoot\" Background=\"&#123;StaticResource PhoneBackgroundBrush&#125;\">\n        &lt;Grid.RowDefinitions>\n            &lt;RowDefinition Height=\"Auto\"/>\n            &lt;RowDefinition Height=\"*\"/>\n        &lt;/Grid.RowDefinitions>\n\n        &lt;!--TitleGrid is the name of the application and page title-->\n        &lt;Grid x:Name=\"TitleGrid\" Grid.Row=\"0\">\n            &lt;TextBlock Text=\"MY APPLICATION\" x:Name=\"textBlockPageTitle\" Style=\"&#123;StaticResource PhoneTextPageTitle1Style&#125;\"/>\n            &lt;TextBlock Text=\"Ink\" x:Name=\"textBlockListTitle\" Style=\"&#123;StaticResource PhoneTextPageTitle2Style&#125;\"/>\n        &lt;/Grid>\n\n        &lt;!--ContentGrid is empty. Place new content here-->\n        &lt;Grid x:Name=\"ContentGrid\" Grid.Row=\"1\">\n            &lt;InkPresenter Background=\"White\" x:Name=\"ink1\" />\n        &lt;/Grid>\n    &lt;/Grid>\n\n&lt;/phoneNavigation:PhoneApplicationPage></code></pre>\n<p>Обработчик для отмены ввода будет выглядеть очень просто – в нем мы проверим вводил ли пользователь что-либо, и если да, то удалим этот фрагмент (объект <code>Stroke</code>).</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void ApplicationBarMenuItem_Click(object sender, EventArgs e)\n&#123;\n    if (ink1.Strokes != null &amp;&amp; ink1.Strokes.Count > 0)\n    &#123;\n        ink1.Strokes.RemoveAt(ink1.Strokes.Count - 1);\n    &#125;\n&#125;</code></pre>\n<p>Теперь у нас есть возможность отменить последний ввод. <em>Но что, делать, если мы отменили его случайно?</em> Логично, что рядом с <code>Undo</code> должен быть и пункт <code>Redo</code>. Сделать его очень просто – нужно просто вернуть объект <code>Stroke</code> обратно в коллекцию <code>Strokes</code>. Давайте добавим еще один пункт меню и создадим для него обработчик.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;phoneNavigation:PhoneApplicationPage\n    x:Class=\"InkTest.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:phoneNavigation=\"clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    mc:Ignorable=\"d\" d:DesignWidth=\"480\" d:DesignHeight=\"800\"\n    xmlns:shell=\"clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone.Shell\"\n    FontFamily=\"&#123;StaticResource PhoneFontFamilyNormal&#125;\"\n    FontSize=\"&#123;StaticResource PhoneFontSizeNormal&#125;\"\n    Foreground=\"&#123;StaticResource PhoneForegroundBrush&#125;\">\n    &lt;phoneNavigation:PhoneApplicationPage.ApplicationBar>\n        &lt;shell:ApplicationBar IsMenuEnabled=\"True\">\n            &lt;shell:ApplicationBar.MenuItems>\n                &lt;shell:ApplicationBarMenuItem Text=\"Undo\" Click=\"ApplicationBarMenuItem_Click\"/>\n                &lt;shell:ApplicationBarMenuItem Text=\"Redo\" Click=\"ApplicationBarMenuItem_Click_1\"/>\n            &lt;/shell:ApplicationBar.MenuItems>\n        &lt;/shell:ApplicationBar>\n    &lt;/phoneNavigation:PhoneApplicationPage.ApplicationBar>\n\n    &lt;Grid x:Name=\"LayoutRoot\" Background=\"&#123;StaticResource PhoneBackgroundBrush&#125;\">\n        &lt;Grid.RowDefinitions>\n            &lt;RowDefinition Height=\"Auto\"/>\n            &lt;RowDefinition Height=\"*\"/>\n        &lt;/Grid.RowDefinitions>\n\n        &lt;!--TitleGrid is the name of the application and page title-->\n        &lt;Grid x:Name=\"TitleGrid\" Grid.Row=\"0\">\n            &lt;TextBlock Text=\"MY APPLICATION\" x:Name=\"textBlockPageTitle\" Style=\"&#123;StaticResource PhoneTextPageTitle1Style&#125;\"/>\n            &lt;TextBlock Text=\"Ink\" x:Name=\"textBlockListTitle\" Style=\"&#123;StaticResource PhoneTextPageTitle2Style&#125;\"/>\n        &lt;/Grid>\n\n        &lt;!--ContentGrid is empty. Place new content here-->\n        &lt;Grid x:Name=\"ContentGrid\" Grid.Row=\"1\">\n            &lt;InkPresenter Background=\"White\" x:Name=\"ink1\" />\n        &lt;/Grid>\n    &lt;/Grid>\n\n&lt;/phoneNavigation:PhoneApplicationPage></code></pre>\n<p>Модифицируем обработчик отмены ввода таким образом, чтобы он сохранял удаленный объект. Обработчик для восстановления объекта просто будет добавлять к коллекцию этот удаленный объект. Сам удаленный объект будем хранить в локальном поле.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void ApplicationBarMenuItem_Click(object sender, EventArgs e)\n&#123;\n    if (ink1.Strokes != null &amp;&amp; ink1.Strokes.Count > 0)\n    &#123;\n        _canceledStroke = ink1.Strokes.Last();\n        ink1.Strokes.RemoveAt(ink1.Strokes.Count - 1);\n    &#125;\n&#125;\n\nStroke _canceledStroke = null;\n\nprivate void ApplicationBarMenuItem_Click_1(object sender, EventArgs e)\n&#123;\n    if (_canceledStroke != null)\n    &#123;\n        ink1.Strokes.Add(_canceledStroke);\n        _canceledStroke = null;\n    &#125;\n&#125;</code></pre>\n<p>Понятно, что хранить только последнюю отмену – не очень правильно. Гораздо правильнее – хранить список, или лучше стек всех отмененных вводов. Но это я оставлю вам для тренировки пальцев.</p>\n<p>Ну и, наконец, давайте отобразим количество объектов в коллекции <code>Strokes</code> для большей наглядности. Я привяжу свойство Count к тексту на форме.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;TextBlock Text=\"&#123;Binding ElementName=ink1, Path=Strokes.Count&#125;\" x:Name=\"textBlockPageTitle\" Style=\"&#123;StaticResource PhoneTextPageTitle1Style&#125;\"/></code></pre>\n<p>Теперь можно запустить приложение и попробовать нарисовать что-то очень красивое. Например, яблоко :).</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-ink/74dcbf5e/2@2x.png\"  alt=\"Рукописный ввод в Windows Phone 7\"/>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/windows-phone-ink/c4eca1ee/windows_phone-ink@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2010/windows-phone-sqlite/",
            "url": "https://blog.zwezdin.com/2010/windows-phone-sqlite/",
            "title": "Использование SQLite в Windows Phone 7",
            "date_published": "2010-05-01T12:00:00.000Z",
            "content_html": "<p>Я уже <a href=\"/2010/windows-phone-isolated-storage/\">писал</a> о том, как можно использовать встроенные средства для хранения данных в Windows Phone 7. Понятно, что для некоторых задач использование реляционного хранилища было бы намного удобнее и эффективнее. Тем не менее, в Windows Phone 7 CTP, представленном на MIX 2010 нет встроенной поддержки SQL Compact и пока не ясно, появится ли она в финальном релизе. Между тем, через несколько дней после официального выхода Windows Phone 7 SDK CTP было объявлено о портировании переносимой БД <strong>SQLite</strong> на Windows Phone 7. Как просили многие из вас, я расскажу о том, как это можно использовать в своих приложениях.</p>\n<p><strong>SQLite</strong> — это переносимая база данных, подобная SQL Compact и имеющая реализации для огромного числа платформ. Предпосылкой к переносу на Windows Phone 7, судя по всему, послужила уже существующая реализация для настольной версии Silverilght. Признаться честно, просмотрев исходные коды SQLite, я не могу сказать, что они мне нравятся. Однако, эта библиотека действительно работает на Windows Phone 7 и ее можно использовать в своих приложениях.</p>\n<p>Итак, для того, чтобы использовать SQLite в своих приложениях нужно загрузить <a href=\"http://www.neologics.eu/Dan/csharp-sqlite.wp.zip\">исходные коды</a> библиотеки. Загрузив, не пугайтесь – исходные коды библиотеки занимают 10 Мб, это действительно так. Однако, не стоит переживать, при сборке проекта SQLite собирается в сборку размером в <strong>600Кб</strong> (плюс, если необходимо, файл с отладочной информацией 1.4Мб). Размер библиотеки 600Кб – это, конечно, тоже не мало, но по сравнению с 10 Мб выглядит несущественно. Вместе с исходным кодом самой библиотеки поставляется пример приложения, работающего с SQLite. С него мы и начнем рассмотрение работы.</p>\n<p>Как я и говорил, исходные коды SQLite оформлены очень специфично. Это, наверное, дело вкуса, поэтому пока не обращайте внимания на стиль написания кода. Демонстрационное приложение содержит несколько кнопок для выполнения операций – открытия/закрытия БД, создание таблицы, добавления информация и удаления БД. Для каждой из кнопок существует отдельный обработчик. Давайте посмотрим на каждый из них.</p>\n<h2>Открытие/закрытие БД</h2><p>Исходный код:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">Sqlite3.sqlite3 db=null;\n\npublic bool OpenDB()\n&#123;\n    int rc;\n    db = new Sqlite3.sqlite3();\n    rc = Sqlite3.sqlite3_open(fileName, ref db);\n    if (rc != 0)\n    &#123;\n        lbOutput.Text += \"\\nCannot open database: \" + Sqlite3.sqlite3_errmsg(db);\n        db = null;\n        return false;\n    &#125;\n    lbOutput.Text += \"\\nDatabase opened.\";\n    return true;\n&#125;\n\npublic bool CloseDB()\n&#123;\n    int rc;\n    if (db != null)\n    &#123;\n        rc = Sqlite3.sqlite3_close(db);\n        if (rc != 0)\n        &#123;\n            lbOutput.Text += \"\\nCannot close database: \" + Sqlite3.sqlite3_errmsg(db);\n            db = null;\n            return false;\n        &#125;\n    &#125;\n    lbOutput.Text += \"\\nDatabase closed.\";\n    return true;\n&#125;</code></pre>\n<p>Как видно, в форме сохраняется ссылка на объект <code>Sqlite3.sqlite3</code>, представляющий базу данных. Загрузка базы данных осуществляется посредством вызова метода <code>Sqlite3.sqlite3_open</code>. Закрытие текущей базы данных происходит путем вызова метода <code>Sqlite3.sqlite3_close</code>. Каждое из действий записывается в элемент <code>lbOutput</code>, которое находится на форме. Все остальные методы вызывают метод <code>OpenDB</code> в случае, если поле формы <code>db</code> оказывается пустым и используют открытую базу данных для дальнейших действий.</p>\n<h2>Создание/удаление таблицы</h2><p>Исходный код:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void btnTest_Click(object sender, RoutedEventArgs e)\n&#123;\n    int rc;\n    string errMsg = string.Empty;\n    if (db == null)\n        if (!OpenDB())\n            return;\n    rc = Sqlite3.sqlite3_exec(db, btnCreate.Content.ToString().Substring(2), (Sqlite3.dxCallback)this.callback, null, ref errMsg);\n    if (rc != Sqlite3.SQLITE_OK)\n        lbOutput.Text += \"\\nError: \" + Sqlite3.sqlite3_errmsg(db);\n    else\n        lbOutput.Text += \"\\nCommand completed successfully\";\n&#125;\n\nint callback(object pArg, System.Int64 nArg, object azArgs, object azCols)\n&#123;\n    int i;\n    string[] azArg = (string[])azArgs;\n    string[] azCol = (string[])azCols;\n    String sb=\"\";// = new String();\n    for (i = 0; i &lt; nArg; i++)\n        sb+=azCol[i] + \" = \" + azArg[i] + \"\\n\";\n    lbOutput.Text += (\"\\n\" + sb.ToString());\n    return 0;\n&#125;\n\nprivate void btnDrop_Click(object sender, RoutedEventArgs e)\n&#123;\n    int rc;\n    string errMsg = string.Empty;\n    if (db == null)\n        if (!OpenDB())\n            return;\n    rc = Sqlite3.sqlite3_exec(db, btnDrop.Content.ToString().Substring(2), (Sqlite3.dxCallback)this.callback, null, ref errMsg);\n    if (rc != Sqlite3.SQLITE_OK)\n        lbOutput.Text += \"\\nError: \" + Sqlite3.sqlite3_errmsg(db);\n    else\n        lbOutput.Text += \"\\nCommand completed successfully\";\n&#125;</code></pre>\n<p>На самом деле все дальнейшие операции по манипулированию данными – это выполнение SQL-команд. Для этого используется метод” <code>sqlite3_exec</code>, который принимает на вход текущее соединение с БД, SQL-код и метод для обратного вызова. Также на вход передается ссылка на объект-строку, куда будет помещено сообщение об ошибке, в случае если она произойдет. Как видно, в обоих методах перед выполнением проверяется текущее соединение с БД и, если оно закрыто, открывается. В качестве SQL-команды в метод передаются параметры <code>btnCreate.Content.ToString().Substring(2)</code> и <code>btnDrop.Content.ToString().Substring(2)</code> — дело в том, что ребята, создававшие пример поместили SQL-команды прямо внутрь текста кнопки.</p>\n<p>Также очень загадочно выглядит метод обратного вызова. На самом деле в нем происходит обработка результата (в случае, если это выборка) и отображение его в том же элементе <code>lbOutput</code>.</p>\n<h2>Вставка записей</h2><p>Исходный код:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void btnInsert_Click(object sender, RoutedEventArgs e)\n&#123;\n    int rc;\n    string errMsg = string.Empty;\n    if (db == null)\n        if (!OpenDB())\n            return;\n    rc = Sqlite3.sqlite3_exec(db, btnInsert.Content.ToString().Substring(2), (Sqlite3.dxCallback)this.callback, null, ref errMsg);\n    if (rc != Sqlite3.SQLITE_OK)\n        lbOutput.Text += \"\\nError: \" + Sqlite3.sqlite3_errmsg(db);\n    else\n        lbOutput.Text += \"\\nCommand completed successfully\";\n&#125;</code></pre>\n<p>Собственно здесь подход ничем не отличается от предыдущего случая — для исполнения команды используется метод <code>sqlite3_exec</code>, ну а сама SQL-команда по-прежнему хранится внутри кнопки.</p>\n<h2>Выборка записей</h2><p>На самом деле исходный пример не содержал кода для выборки данных. Поэтому я решил восполнить этот пробел и написать в том же духе (нужно же соблюдать общий стиль) еще один метод для выборки данных.</p>\n<p>Исходный код:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void SelectData_Click(object sender, RoutedEventArgs e)\n&#123;\n    if (db == null)\n    &#123;\n        if (!OpenDB())\n        &#123;\n            return;\n        &#125;\n    &#125;\n\n    string errMsg = string.Empty;\n\n    int result = Sqlite3.sqlite3_exec(db, @\"select * from test\", (Sqlite3.dxCallback)this.callback, null, ref errMsg);\n    if (result != Sqlite3.SQLITE_OK)\n        lbOutput.Text += \"\\nError: \" + Sqlite3.sqlite3_errmsg(db);\n    else\n        lbOutput.Text += \"\\nCommand completed successfully\";\n&#125;</code></pre>\n<p>Как видно, здесь в метод <code>sqlite3_exec</code> передается SQL-команда для выборки данных. В этом случае как раз сработает callback-обработчик, который выведет полученные данные на форму.</p>\n<p>Ну а теперь давайте попробуем создать собственное приложение на базе SQLite с более читаемым кодом и посмотрим на все сценарии.</p>\n<h2>Отдельное приложение для работы с данными</h2><p>Прежде всего здесь же создадим еще один проект для приложения Windows Phone 7 и сделаем ссылку на библиотеку SQLite. Добавим на форму несколько кнопок (добавление записей, очистка и получение записей из БД).</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-sqlite/5657c78a/1@2x.png\"  alt=\"Использование SQLite в Windows Phone 7\"/>\n\n\n\n\n<p>Получим небольшую несложную XAML-разметку.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;phoneNavigation:PhoneApplicationPage\n    x:Class=\"DataDrivenApp.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:phoneNavigation=\"clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    mc:Ignorable=\"d\" d:DesignWidth=\"480\" d:DesignHeight=\"800\"\n    FontFamily=\"&#123;StaticResource PhoneFontFamilyNormal&#125;\"\n    FontSize=\"&#123;StaticResource PhoneFontSizeNormal&#125;\"\n    Foreground=\"&#123;StaticResource PhoneForegroundBrush&#125;\">\n\n    &lt;Grid x:Name=\"LayoutRoot\" Background=\"&#123;StaticResource PhoneBackgroundBrush&#125;\">\n        &lt;Grid.RowDefinitions>\n            &lt;RowDefinition Height=\"Auto\"/>\n            &lt;RowDefinition Height=\"*\"/>\n        &lt;/Grid.RowDefinitions>\n\n        &lt;!--TitleGrid is the name of the application and page title-->\n        &lt;Grid x:Name=\"TitleGrid\" Grid.Row=\"0\">\n            &lt;TextBlock Text=\"MY APPLICATION\" x:Name=\"textBlockPageTitle\" Style=\"&#123;StaticResource PhoneTextPageTitle1Style&#125;\"/>\n            &lt;TextBlock Text=\"Data\" x:Name=\"textBlockListTitle\" Style=\"&#123;StaticResource PhoneTextPageTitle2Style&#125;\"/>\n        &lt;/Grid>\n\n        &lt;!--ContentGrid is empty. Place new content here-->\n        &lt;Grid x:Name=\"ContentGrid\" Grid.Row=\"1\">\n            &lt;Grid.RowDefinitions>\n                &lt;RowDefinition Height=\"Auto\"/>\n                &lt;RowDefinition Height=\"*\"/>\n            &lt;/Grid.RowDefinitions>\n\n            &lt;StackPanel Orientation=\"Horizontal\">\n                &lt;Button Content=\"Add\" Click=\"AddButton_Click\"/>\n                &lt;Button Content=\"Clear\" Click=\"ClearButton_Click\"/>\n                &lt;Button Content=\"Refresh\" Click=\"RefreshButton_Click\"/>\n            &lt;/StackPanel>\n\n            &lt;ListBox x:Name=\"Data\" Grid.Row=\"1\">\n            &lt;/ListBox>\n        &lt;/Grid>\n    &lt;/Grid>\n\n&lt;/phoneNavigation:PhoneApplicationPage></code></pre>\n<p>Теперь напишем небольшие обработчики для событий нажатия на каждую из кнопок.</p>\n<p>Создание БД и заполнение данными:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void AddButton_Click(object sender, RoutedEventArgs e)\n&#123;\n    var db = new Sqlite3.sqlite3();\n\n    if (Sqlite3.sqlite3_open(\"test.db\", ref db) == 0)\n    &#123;\n        string errMsg = string.Empty;\n\n        if (Sqlite3.sqlite3_exec(db, @\"create table peoples (id int primary key, name text)\",\n            (Sqlite3.dxCallback)(delegate(object pArg, System.Int64 nArg, object azArgs, object azCols) &#123; return 0; &#125;),\n            null, ref errMsg) == Sqlite3.SQLITE_OK)\n        &#123;\n            for (int i = 1; i &lt;= 20; i++)\n            &#123;\n                Sqlite3.sqlite3_exec(db, String.Format(@\"Insert into peoples (id,name) values (&#123;0&#125;, \"\"&#123;1&#125;\"\")\", i, Guid.NewGuid().ToString().Replace(\"-\", \"\").Substring(1, 5)),\n                    (Sqlite3.dxCallback)(delegate(object pArg, System.Int64 nArg, object azArgs, object azCols) &#123; return 0; &#125;),\n                    null, ref errMsg);\n            &#125;\n\n            MessageBox.Show(\"Created\", \"Data\", MessageBoxButton.OK);\n        &#125;\n        else\n        &#123;\n            MessageBox.Show(\"Table creating error\");\n        &#125;\n    &#125;\n    else\n    &#123;\n        MessageBox.Show(\"Db opening error\");\n    &#125;\n&#125;</code></pre>\n<p>Удаление БД:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void ClearButton_Click(object sender, RoutedEventArgs e)\n&#123;\n    var db = new Sqlite3.sqlite3();\n\n    if (Sqlite3.sqlite3_open(\"test.db\", ref db) == 0)\n    &#123;\n        string errMsg = string.Empty;\n\n        if (Sqlite3.sqlite3_exec(db, @\"drop table peoples\",\n            (Sqlite3.dxCallback)(delegate(object pArg, System.Int64 nArg, object azArgs, object azCols) &#123; return 0; &#125;),\n            null, ref errMsg) == Sqlite3.SQLITE_OK)\n        &#123;\n            MessageBox.Show(\"Dropped\", \"Data\", MessageBoxButton.OK);\n        &#125;\n        else\n        &#123;\n            MessageBox.Show(\"Table dropping error\");\n        &#125;\n    &#125;\n    else\n    &#123;\n        MessageBox.Show(\"Db opening error\");\n    &#125;\n&#125;</code></pre>\n<p>Отображение информации на форме:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void RefreshButton_Click(object sender, RoutedEventArgs e)\n&#123;\n    var db = new Sqlite3.sqlite3();\n\n    if (Sqlite3.sqlite3_open(\"test.db\", ref db) == 0)\n    &#123;\n        string errMsg = string.Empty;\n        Data.Items.Clear();\n\n        if (Sqlite3.sqlite3_exec(db, @\"select * from peoples\",\n            (Sqlite3.dxCallback)(delegate(object pArg, System.Int64 nArg, object azArgs, object azCols)\n        &#123;\n            Data.Items.Add(((string[])azArgs)[1]);\n            return 0;\n        &#125;),\n            null, ref errMsg) == Sqlite3.SQLITE_OK)\n        &#123;\n            MessageBox.Show(\"Refreshed\", \"Data\", MessageBoxButton.OK);\n        &#125;\n        else\n        &#123;\n            MessageBox.Show(\"Table refreshing error\");\n        &#125;\n    &#125;\n    else\n    &#123;\n        MessageBox.Show(\"Db opening error\");\n    &#125;\n&#125;</code></pre>\n<p>В данном случае код стало немного более читаемым.</p>\n<p>Таким образом, работа с реляционными структурами в Windows Phone 7 возможна уже сегодня и вы можете использовать SQLite в ваших приложениях, если вам недостаточно функциональности Isolated Storage.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/windows-phone-sqlite/a5ee4726/windows_phone-sqlite@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2010/windows-phone-isolated-storage/",
            "url": "https://blog.zwezdin.com/2010/windows-phone-isolated-storage/",
            "title": "Хранение данных в Windows Phone 7 средствами Isolated Storage",
            "date_published": "2010-04-30T12:00:00.000Z",
            "content_html": "<p>Вопрос хранения данных затрагивает практически любое приложения и не может оставаться в стороне. При разработке приложений для <strong>Windows Phone 7</strong> в качестве основного сценария на данный момент предлагается использовать механизм <strong>Isolated Storage</strong>, который успешно используется в приложениях Silverlight. Давайте попробуем поработать с Isolated Storage.</p>\n<p><strong>Isolated Storage</strong> предполагает, что для каждого приложения существует изолированная среда для хранения данных.Приложения не могут получать доступ к хранилищу других приложений (в основном из соображений безопасности). Само хранилище при этом разделяется на две части:</p>\n<ul>\n<li>Isolated Settings Storage;</li>\n<li>Isolated File Storage.</li>\n</ul>\n<p>Хранилище <strong>Isolated Settings Storage</strong> предназначено для хранения настроек приложения. Хранение настроек осуществляется в специальном словаре (ключ-значение) и очень похоже на то, как работают аналогичные механизмы в настольной платформе.</p>\n<p>Хранилище <strong>Isolated File Storage</strong> предназначено для хранения различных данных в виде файлов. При этом это хранилище можно структурировать, используя разбиение на папки. Фактически, это – некоторый доступ к файловой системе изолированного хранилища.</p>\n<p>Для организации механизма хранения данных в <strong>Windows Phone 7 SDK</strong> используется следующий набор объектов:</p>\n<ul>\n<li><code>System.IO.IsolatedStorage.IsolatedStorageSettings</code> – предоставляет доступ к словарю (ключ-значение) настроек для текущего хранилища (Isolated Settings Storage);</li>\n<li><code>System.IO.IsolatedStorage.IsolatedStorageFile</code> – позволяет получить доступ к хранилищу данных (Isolated File Storage);</li>\n<li><code>System.IO.IsolatedStorage.IsolatedFileStream</code> – позволяет получить доступ к содержимому файла в хранилище (Isolated File Storage);</li>\n<li><code>System.IO.IsolatedStorage.IsolatedStorageException</code> – исключение, возникающее при ошибках в работе с хранилищем.</li>\n</ul>\n<h2>Хранение настроек</h2><p>Для доступа к настройкам приложения используется класс <code>IsolatedStorageSettings</code>. Этот класс представляет собой словарь, при помощи которого по ключу можно обратиться к конкретной настройке. Для доступа к значению используется индексатор. Например, получить доступ к ключу <code>count</code> можно используя следующий код.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">int count = (int)System.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings[\"count\"];</code></pre>\n<p>Если указанного ключа нет в настройках приложения, то будет сгенерировано соответствующее исключение. Поэтому чтобы избежать подобных неприятностей следует проверять наличие нужного ключа перед обращением к нему.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">int count = 0;\n\nif (System.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings.Contains(\"count\") == true)\n&#123;\n    count = (int)System.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings[\"count\"];\n&#125;</code></pre>\n<p>Аналогичным образом можно работать с любыми ключами <strong>Isolated Settings Storage</strong>.</p>\n<p>Изменение настроек приложения также выполняется достаточно просто. Для этого следует изменить нужный ключ и вызвать метод <code>Save</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">System.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings[\"count\"] = count;\nSystem.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings.Save();</code></pre>\n<p>Так, например, можно написать несложный код по подсчету количества запусков приложения и отображения этой информации пользователю. Для этого добавим на форму элемент <code>TextBlock</code> и будем инкрементировать счетчик при каждом запуске приложения. Эту логику можно поместить, например, в конструктор.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public MainPage()\n&#123;\n    InitializeComponent();\n\n    SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape;\n\n    int count = 0;\n    if (System.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings.Contains(\"count\") == true)\n    &#123;\n        count = (int)System.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings[\"count\"];\n    &#125;\n\n    count++;\n    System.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings[\"count\"] = count;\n    System.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings.Save();\n\n    textBlock1.Text = count.ToString();\n&#125;</code></pre>\n<p>На самом деле, работать с настройками в таком виде не очень удобно – каждый раз приходится проверять наличие ключа в настройках и выполнять приведение типов. По-хорошему, тут еще нужно проверять на соответствие типов перед выполнением приведения типов. Поэтому давайте создадим небольшую надстройку для класса <code>IsolatedStorageSettings</code>.</p>\n<p>Надстройка будет представлять с собой несколько методов-расширений для класса <code>IsolatedStorageSettings</code>. Для получения настроек в типизированном виде создадим метод <code>GetSetting</code>, который будет принимать в качестве параметра имя ключа и значение по умолчанию (если, например, ключ не найден). Также сделаем перегруженную версию метода, чтобы можно было не указывать значение по умолчанию. Внутри этого метода выполним все действия, которые мы видели с вами раньше.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class IsolatedStorageSettingsExtensions\n&#123;\n    public static T GetSetting&lt;T>(this System.IO.IsolatedStorage.IsolatedStorageSettings settings, string keyName)\n    &#123;\n        return GetSetting(settings, keyName, default(T));\n    &#125;\n\n    public static T GetSetting&lt;T>(this System.IO.IsolatedStorage.IsolatedStorageSettings settings, string keyName, T defaultValue)\n    &#123;\n        if (settings != null)\n        &#123;\n            if (settings.Contains(keyName) == true)\n            &#123;\n                try\n                &#123;\n                    return (T)settings[keyName];\n                &#125;\n                catch (InvalidCastException)\n                &#123;\n                &#125;\n            &#125;\n        &#125;\n\n        return defaultValue;\n    &#125;\n&#125;</code></pre>\n<p>Теперь доступ к настройкам немного упростился. Для того, чтобы получить значение, можно просто вызвать метод-расширение с нужными параметрами. Перепишем с учетом этого, код, который использовался выше.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public MainPage()\n&#123;\n    InitializeComponent();\n\n    SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape;\n\n    int count = System.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings.GetSetting&lt;int>(\"count\");\n    count++;\n    System.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings[\"count\"] = count;\n    System.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings.Save();\n\n    textBlock1.Text = count.ToString();\n&#125;</code></pre>\n<p>Мы также можем указать значение по умолчанию, которое будет использоваться, если ключ не найден в настройках. Например, мы можем вести отсчет с числа <code>10</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">int count = System.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings.GetSetting&lt;int>(\"count\", 10);</code></pre>\n<p>Аналогичный метод расширения сделаем и для сохранения настроек. Тогда весь класс, содержащий методы-расширения будет выглядеть следующим образом.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class IsolatedStorageSettingsExtensions\n&#123;\n    public static T GetSetting&lt;T>(this System.IO.IsolatedStorage.IsolatedStorageSettings settings, string keyName)\n    &#123;\n        return GetSetting(settings, keyName, default(T));\n    &#125;\n\n    public static T GetSetting&lt;T>(this System.IO.IsolatedStorage.IsolatedStorageSettings settings, string keyName, T defaultValue)\n    &#123;\n        if (settings != null)\n        &#123;\n            if (settings.Contains(keyName) == true)\n            &#123;\n                try\n                &#123;\n                    return (T)settings[keyName];\n                &#125;\n                catch (InvalidCastException)\n                &#123;\n                &#125;\n            &#125;\n        &#125;\n\n        return defaultValue;\n    &#125;\n\n    public static void SaveSetting&lt;T>(this System.IO.IsolatedStorage.IsolatedStorageSettings settings, string keyName, T value)\n    &#123;\n        if (settings != null)\n        &#123;\n            settings[keyName] = value;\n            settings.Save();\n        &#125;\n    &#125;\n&#125;</code></pre>\n<p>При этом немного упростится код, считывающий и изменяющий настройки приложения.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public MainPage()\n&#123;\n    InitializeComponent();\n\n    SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape;\n\n    int count = System.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings.GetSetting&lt;int>(\"count\", 10);\n    System.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings.SaveSetting(\"count\", ++count);\n    textBlock1.Text = count.ToString();\n&#125;</code></pre>\n<h2>Хранение данных</h2><p>Для доступа к файловому хранилищу используется класс <code>IsolatedStorageFile</code>. Обратившись к его статическому методу <code>GetUserStoreForApplication()</code> можно получить доступ к объекту, предоставляющему всевозможные методы для работы с файлами и папками:</p>\n<ul>\n<li><code>GetDirectoryNames</code> – позволяет получить список всех папок в заданном контексте;</li>\n<li><code>GetFileNames</code> – позволяет получить список всех файлов в заданном контексте;</li>\n<li><code>CreateDirectory</code> – позволяет создать папку;</li>\n<li><code>DeleteDirectory</code> – позволяет удалить папку;</li>\n<li><code>DirectoryExists</code> – позволяет проверить наличие папки;</li>\n<li><code>CreateFile</code> – позволяет создать файл;</li>\n<li><code>OpenFile</code> – позволяет открыть файл для чтения и/или изменения;</li>\n<li><code>DeleteFile</code> – позволяет удалить файл;</li>\n<li><code>FileExists</code> – позволяет проверить наличие файла.</li>\n</ul>\n<p>Как видно, для работы с файлом используется метод <code>OpenFile</code>. Этот файл возвращает поток, с которым можно работать так, как мы это делаем обычно (например, используя <code>StreamReader</code>/<code>StreamWriter</code>). Например, код для записи информации в файл будет выглядеть следующим образом.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">using (var file = IsolatedStorageFile.GetUserStoreForApplication().CreateFile(\"test1.txt\"))\n&#123;\n    using (var fileWriter = new StreamWriter(file))\n    &#123;\n        fileWriter.WriteLine(DateTime.Now.ToString());\n    &#125;\n&#125;</code></pre>\n<p>Аналогичным образом выглядит код для считывания содержимого файла.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">using (var file = IsolatedStorageFile.GetUserStoreForApplication().OpenFile(\"test1.txt\", FileMode.Open))\n&#123;\n    using (var fileReader = new StreamReader(file))\n    &#123;\n        MessageBox.Show(fileReader.ReadToEnd(), \"File\", MessageBoxButton.OK);\n    &#125;\n&#125;</code></pre>\n<p>Давайте добавим в конструктор нашего приложения логику, согласно которой приложение будет проверять наличие файла в хранилище и, если его там нет, записывать в него текущее время; в противном случае – отображать его пользователю.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public MainPage()\n&#123;\n    // ...\n\n    if (IsolatedStorageFile.GetUserStoreForApplication().FileExists(\"test1.txt\") == false)\n    &#123;\n        using (var file = IsolatedStorageFile.GetUserStoreForApplication().CreateFile(\"test1.txt\"))\n        &#123;\n            using (var fileWriter = new StreamWriter(file))\n            &#123;\n                fileWriter.WriteLine(DateTime.Now.ToString());\n            &#125;\n        &#125;\n    &#125;\n    else\n    &#123;\n        using (var file = IsolatedStorageFile.GetUserStoreForApplication().OpenFile(\"test1.txt\", FileMode.Open))\n        &#123;\n            using (var fileReader = new StreamReader(file))\n            &#123;\n                MessageBox.Show(fileReader.ReadToEnd(), \"File\", MessageBoxButton.OK);\n            &#125;\n        &#125;\n    &#125;\n&#125;</code></pre>\n<p>Таким образом, без особых усилий мы создали небольшое приложение, работающее с Isolated Storage - <strong>Isolated Settings Storage</strong> и <strong>Isolated File Storage</strong>. Как видно, на это не требуется много усилий, а работа с хранилищем очень похожа на аналогичные операции в настольном Silverilght. Тем не менее, по сравнению с хранилищем в Silverilght, хранилище в Windows Phone не имеет ограничений, связанных с объемом хранимой информации.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/windows-phone-isolated-storage/f83fb100/windows_phone-isolated-storage@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2010/windows-mobile-autorun/",
            "url": "https://blog.zwezdin.com/2010/windows-mobile-autorun/",
            "title": "Автоматический запуск приложений в Windows Phone Classic",
            "date_published": "2010-04-29T12:00:00.000Z",
            "content_html": "<p>Моя недавняя тема про <a href=\"/2010/windows-mobile-background-services\">фоновые службы в Windows Phone Classic</a> (на базе управляемого кода) дала повод поразмышлять на тему автоматического запуска любых приложений для <strong>Windows Phone Classic</strong>.</p>\n<p>Существует два главных способа заставить приложение запускаться при загрузке операционной системы:</p>\n<ul>\n<li>добавить соответствующий ключ в реестр;</li>\n<li>создать ярлык в папке автозагрузки.</li>\n</ul>\n<p>Кроме того, есть варианты запускать приложения:</p>\n<ul>\n<li>при вставке карты памяти;</li>\n<li>в определенное время по таймеру;</li>\n<li>при наступлении каких-либо событий.</li>\n</ul>\n<h2>Используем реестр Windows</h2><p>По аналогии с настольной версией Windows, запускать приложения автоматически можно просто разместив информацию о них в специальной ветке реестра. Для этого используется секция <code>HKEY_LOCAL_MACHINE\\Init</code>. Для каждого отдельного приложения здесь создается отдельный ключ, имя которого <code>LaunchXX</code>, где <code>XX</code> – номер загрузки для приложения. Этот ключ должен содержать строковое значение – путь до приложения. Также можно создать второй, опциональный ключ с именем <code>DependXX</code>. В нем указывается номер приложения, которого следует ожидать до запуска данного приложения.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-mobile-autorun/56c4dec1/1@2x.png\"  alt=\"Автоматический запуск приложений в Windows Phone Classic\"/>\n\n\n\n\n<p>Понятно, что чтобы зарегистрировать свое приложение следует найти “свободный” номер и сохранить туда путь до своего приложения. Для этих целей можно использовать следующий код.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void Register()\n&#123;\n    using (var key = Registry.LocalMachine.OpenSubKey(\"init\", true))\n    &#123;\n        var values = key.GetValueNames();\n        int keyIndex;\n        for (keyIndex = 100; keyIndex &lt; 199; keyIndex++)\n        &#123;\n            if (values.Contains(\"Launch\" + keyIndex) == false)\n            &#123;\n                break;\n            &#125;\n        &#125;\n\n        key.SetValue(\"Launch\" + keyIndex, @\"path\");\n    &#125;\n&#125;</code></pre>\n<p>Для того, чтобы удалить приложение из автозапуска нужно просто удалить ключ. Соответственно понять, будет ли приложение в автозапуске можно путем поиска ключа с нужным значением. Набросаем для этих целей тоже небольшой фрагмент кода.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static bool IsServiceRegistered\n&#123;\n    get\n    &#123;\n        var serviceFullPath = \"path\".ToLower();\n\n        using (var key = Registry.LocalMachine.OpenSubKey(\"init\", true))\n        &#123;\n            if (key != null)\n            &#123;\n                return key.GetValueNames()\n                    .Where(k => (key.GetValue(k, String.Empty) ?? String.Empty).ToString()== serviceFullPath)\n                    .Count() > 0;\n            &#125;\n        &#125;\n\n        return false;\n    &#125;\n&#125;\n\npublic static void UnRegister()\n&#123;\n\n    var serviceFullPath = \"path\".ToLower();\n\n    using (var key = Registry.LocalMachine.OpenSubKey(\"init\", true))\n    &#123;\n        if (key != null)\n        &#123;\n            var keyVaues = key.GetValueNames().Where(\n                k => (key.GetValue(k, String.Empty) ?? String.Empty).ToString().ToLower() == serviceFullPath)\n                .Select(k => k).\n                ToArray();\n\n            foreach (var keyVaue in keyVaues)\n            &#123;\n                key.DeleteValue(keyVaue, false);\n            &#125;\n        &#125;\n    &#125;\n&#125;</code></pre>\n<p>После запуска приложения в автоматическом режиме следует уведомить операционную систему о том, что запуск произошел успешно. Для этого в <code>coredll.dll</code> содержится функция <code>SignalStarted</code>. Поскольку в данном случае мы имеем дело с приложением <strong>.NET Compact Framework</strong>, то следует использовать <code>P/Invoke</code>. При запуске приложения в автоматическом режиме приложению передается аргумент – так можно отличить автозапуск от обычного старта приложения. Этот аргумент следует передать в виде параметра функции <code>SignalStarted</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">static class Program\n&#123;\n    static void Main(string[] args)\n    &#123;\n        if (args.Length > 0)\n        &#123;\n            SignalStarted(uint.Parse(args[0]));\n        &#125;\n\n        // запуск приложения\n    &#125;\n\n    [DllImport(\"coredll\")]\n    public extern static void SignalStarted(uint dword);\n&#125;</code></pre>\n<p>Теперь при загрузке операционной системы наше приложение будет успешно запускаться.</p>\n<p>Стоит отметить, что операционная система имеет ряд ограничений на запуск приложений подобным образом. Например, приложение должно быть подписано действительным сертификатом. Кроме того, на некоторых устройствах запуск приложений данным способом ограничен. Также, в некоторых случаях может оказаться недостаточно прав доступа на запись в указанную ветку реестра (для разных устройств). Все это, к сожалению, ограничивает область применения данного способа.</p>\n<h2>Ярлык в папке автозагрузки</h2><p>Другим, наиболее простым вариантом является создание ярлыка в папке автозагрузки. Опять таки это аналог того, что есть в настольной версии Windows. В этом случае на приложение не накладывается ограничение, связанное с подписью приложения и оно гарантированно запустится. Единственное ограничение, связанное с подобным подходом заключается в том, что мы не сможем задать зависимости, как это было в предыдущем случае. Однако, это не всегда нужно.</p>\n<p>Все ярлыки для автозапуска должны располагаться в папке <code>\\Windows\\StartUp</code>. Однако, полный путь зависит в т.ч. от локализации устройства, например, для русской версии ОС путь будет выглядеть как <code>\\Windows\\Автозагрузка</code>. Тем не менее, это - не проблема, используя метод <code>GetFolderPath</code> объекта <code>Environment</code> мы можем получить путь к служебным папкам устройства, среди которых есть и папка автозагрузки. Поэтому путь до нашего ярлыка автозапуска можно определить так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">string _shortcutPath = Path.Combine(Environment.GetFolderPath(System.Environment.SpecialFolder.Startup), \"myapp.lnk\");</code></pre>\n<p>Теперь, для того, чтобы автоматически загружать наше приложение, следует создать ярлык по этому пути. Этот файл имеет обычное текстовое содержимое вида <code>XX#path</code>, где <code>XX</code> – количество символов в части <code>path</code>, а <code>path</code> – путь до исполняемого файла. Однако, для создания ярлыка можно воспользоваться более правильным способом, а именно использовать функцию <code>SHCreateShortcut</code> из библиотеки <code>coredll.dll</code>. При вызове этой функции следует указать путь, где необходимо разместить ярлык (в папке автозагрузки) и путь до исполняемого файла.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[DllImport(\"coredll.dll\")]\npublic static extern void SHCreateShortcut(\n  string target,\n  string shortcut);\n\nprivate static string _shortcutPath =\n    Path.Combine(Environment.GetFolderPath(System.Environment.SpecialFolder.Startup), \"myapp.lnk\");\n\nprivate void Register()\n&#123;\n    if (File.Exists(_shortcutPath) == true)\n    &#123;\n        File.Delete(_shortcutPath);\n    &#125;\n\n    SHCreateShortcut(_shortcutPath, \"path\");\n&#125;</code></pre>\n<p>Удаление приложения из автозагрузки или проверка наличия его там сводится к работе с этим файлом (ярлыком).</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private static string _shortcutPath =\n    Path.Combine(Environment.GetFolderPath(System.Environment.SpecialFolder.Startup), \"myapp.lnk\");\n\npublic static bool IsServiceRegistered\n&#123;\n    get\n    &#123;\n        return File.Exists(_shortcutPath);\n    &#125;\n&#125;\n\npublic static void UnRegister()\n&#123;\n    if (File.Exists(_shortcutPath) == true)\n    &#123;\n        File.Delete(_shortcutPath);\n    &#125;\n&#125;</code></pre>\n<h2>Установка карты памяти в устройство</h2><p>При вставке карты памяти операционная система автоматически сканирует ее на наличие приложения <code>Autorun.exe</code> в папке <code>/2577</code>, если приложение найдено, то оно запускается автоматически. На самом деле имя папки зависит от типа процессора и для некоторых моделей может быть иным. Однако, <code>/2577</code> подходит для большинства устройств Windows Phone Classic.</p>\n<p>В приведенном примере приложение должно находится на карте памяти в строго определенном месте. Если приложение находится по другому пути на карте памяти, то можно создать приложение <code>Autorun.exe</code>, которое будет запускать приложение по другому указанному пути.</p>\n<p>Кроме того, если необходимо, чтобы приложение хранилось в основной памяти и не зависело от содержимого карты памяти, то можно воспользоваться другим способом, о котором речь пойдет далее.</p>\n<h2>Запуск в определенное время</h2><p>Операционная система позволяет <a href=\"http://msdn.microsoft.com/en-us/netframework/bb738102.aspx\">задать таймер</a> для запуска приложения. Для этих целей существует функция <code>CeRunApAtTime</code> в библиотеке <code>coredll.dll</code>. Поскольку библиотека работает на основе неуправляемого кода, следует использовать <code>P/Invoke</code> для вызова функции. Перед этим следует определить структуру <code>SystemTime</code>, которая понадобится при вызове функций.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[StructLayout(LayoutKind.Sequential)]\npublic class SystemTime\n&#123;\n    public ushort wYear;\n    public ushort wMonth;\n    public ushort wDayOfWeek;\n    public ushort wDay;\n    public ushort wHour;\n    public ushort wMinute;\n    public ushort wSecond;\n    public ushort wMilliseconds;\n&#125;</code></pre>\n<p>Для вызова функций следует сделать обертку над ними.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class TimeLuanchHelper\n&#123;\n    [DllImport(\"coredll.dll\")]\n    public static extern int CeRunAppAtTime(string application, SystemTime startTime);\n\n    [DllImport(\"coredll.dll\")]\n    public static extern int FileTimeToSystemTime(ref long lpFileTime, SystemTime lpSystemTime);\n    [DllImport(\"coredll.dll\")]\n    public static extern int FileTimeToLocalFileTime(ref long lpFileTime, ref long lpLocalFileTime);\n&#125;</code></pre>\n<p>Как видно, здесь мы воспользовались еще двумя системными функциями, которые позволяют работать со временем. Ну и, наконец, добавим нужные нам методы для отложенного запуска.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class TimeLuanchHelper\n&#123;\n    [DllImport(\"coredll.dll\")]\n    public static extern int CeRunAppAtTime(string application, SystemTime startTime);\n\n    [DllImport(\"coredll.dll\")]\n    public static extern int FileTimeToSystemTime(ref long lpFileTime, SystemTime lpSystemTime);\n    [DllImport(\"coredll.dll\")]\n    public static extern int FileTimeToLocalFileTime(ref long lpFileTime, ref long lpLocalFileTime);\n\n    public static void ScheduleRun(string application, DateTime startTime)\n    &#123;\n        long fileTimeUTC = startTime.ToFileTime();\n        long fileTimeLocal = 0;\n        SystemTime timeToStart = new SystemTime();\n        FileTimeToLocalFileTime(ref fileTimeUTC, ref fileTimeLocal);\n        FileTimeToSystemTime(ref fileTimeLocal, timeToStart);\n        CeRunAppAtTime(application, timeToStart);\n\n    &#125;\n\n    public static void ScheduleRun(string application, TimeSpan timeDisplacement)\n    &#123;\n        var timeToStart = DateTime.Now + timeDisplacement;\n        ScheduleRun(application, timeToStart);\n    &#125;\n&#125;</code></pre>\n<p>Теперь мы можем вызвать метод <code>ScheduleRun</code> и указать абсолютное или относительное время для запуска приложения.</p>\n<h2>Запуск приложения при наступлении внешних событий</h2><p>Операционная система дает возможность запускать приложения при наступлении различных событий в системе. Например, можно запускать приложения в момент восстановления устройства из режима сна, в момент подключения или отключения от сети или после окончания синхронизации устройства через ActiveSync (полный список – ниже).</p>\n<p>Для определения событий используется функция <code>CeRunAppAtEvent</code> библиотеки <code>coredll.dll</code>. В параметрах этой библиотеки следует передать путь до приложения и идентификатор события для срабатывания. Создадим для этой функции небольшую обертку.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class EventLuanchHelper\n&#123;\n    [DllImport(\"coredll.dll\")]\n    public static extern int CeRunAppAtEvent(string application, int EventID);\n&#125;</code></pre>\n<p>Для передачи идентификатора события удобнее пользоваться названиями событий, а не их кодами. Поэтому поместим все события в отдельную структуру.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public enum EventId\n&#123;\n    NOTIFICATION_EVENT_NONE = 0,\n    NOTIFICATION_EVENT_TIME_CHANGE = 1,\n    NOTIFICATION_EVENT_SYNC_END = 2,\n    NOTIFICATION_EVENT_ON_AC_POWER = 3,\n    NOTIFICATION_EVENT_OFF_AC_POWER = 4,\n    NOTIFICATION_EVENT_NET_CONNECT = 5,\n    NOTIFICATION_EVENT_NET_DISCONNECT = 6,\n    NOTIFICATION_EVENT_DEVICE_CHANGE = 7,\n    NOTIFICATION_EVENT_IR_DISCOVERED = 8,\n    NOTIFICATION_EVENT_RS232_DETECTED = 9,\n    NOTIFICATION_EVENT_RESTORE_END = 10,\n    NOTIFICATION_EVENT_WAKEUP = 11,\n    NOTIFICATION_EVENT_TZ_CHANGE = 12,\n    NOTIFICATION_EVENT_MACHINE_NAME_CHANGE = 13\n&#125;;</code></pre>\n<p>Теперь мы можем вызвать метод <code>CeRunAppAtEvent</code> и указать нужное событие.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">EventLuanchHelper.CeRunAppAtEvent(\"path\", (int) EventId.NOTIFICATION_EVENT_ON_AC_POWER);</code></pre>\n<p>Если необходимо отменить поведение запуска приложение на срабатывание события, то нужно вызвать метод с параметром <code>NOTIFICATION_EVENT_NONE</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">EventLuanchHelper.CeRunAppAtEvent(\"path\", (int)EventId.NOTIFICATION_EVENT_NONE);</code></pre>\n<p>Список доступных событий:</p>\n<ul>\n<li><code>NOTIFICATION_EVENT_TIME_CHANGE</code> – изменение времени;</li>\n<li><code>NOTIFICATION_EVENT_SYNC_END</code> – завершение синхронизации через ActiveSync;</li>\n<li><code>NOTIFICATION_EVENT_ON_AC_POWER</code> – подключение внешнего источника питания;</li>\n<li><code>NOTIFICATION_EVENT_OFF_AC_POWER</code> – отключение внешнего источника питания;</li>\n<li><code>NOTIFICATION_EVENT_NET_CONNECT</code> – подключение к сети;</li>\n<li><code>NOTIFICATION_EVENT_NET_DISCONNECT</code> – отключение от сети;</li>\n<li><code>NOTIFICATION_EVENT_DEVICE_CHANGE</code> – подключено устройство (например, вставлена карта памяти);</li>\n<li><code>NOTIFICATION_EVENT_IR_DISCOVERED</code> – соединение по ИК-порту;</li>\n<li><code>NOTIFICATION_EVENT_RS232_DETECTED</code> – соединение по интерфейсу RS-232;</li>\n<li><code>NOTIFICATION_EVENT_WAKEUP</code> – выход из режима сна;</li>\n<li><code>NOTIFICATION_EVENT_TZ_CHANGE</code> – изменение временной зоны;</li>\n<li><code>NOTIFICATION_EVENT_MACHINE_NAME_CHANGE</code> – изменение имени устройства.</li>\n</ul>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/windows-mobile-autorun/21067d33/windows_mobile-autorun@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2010/windows-phone-notification-class/",
            "url": "https://blog.zwezdin.com/2010/windows-phone-notification-class/",
            "title": "Параметр NotificationClass при отправке уведомлений Windows Phone 7",
            "date_published": "2010-04-28T12:00:00.000Z",
            "content_html": "<p>После того, как я рассказал (<a href=\"/2010/windows-phone-push-notifications-raw/\">здесь</a> и <a href=\"/2010/windows-phone-push-notifications-toast/\">здесь</a>) о механизме уведомлений для <strong>Windows Phone 7</strong> некоторые из вас немного огорчились, что я не описал смысл заголовка <code>X-NotificationClass</code>, отправляемого вместе с самим уведомлением. Исправляюсь.</p>\n<p>Итак, как мы помним, для отправки какого-либо уведомления мы используем адрес, полученный от сервиса в облаке и переданный третьему приложению для отправки уведомлений. Например, для отправки Toast-уведомления мы использовали с вами следующий программный код:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">class Program\n&#123;\n    private const string Url = @\"http://sn1.notify.live.net/throttledthirdparty/01.00/AAFTkavjFpAuT4i1fIKvueDiAgoOs1ADAgAAAAQOMDAwAAAAAAAAAAAAAAA\";\n\n    static void Main(string[] args)\n    &#123;\n        Console.Write(\"Header: \");\n        string header = Console.ReadLine();\n        Console.Write(\"Text: \");\n        string text = Console.ReadLine();\n        SendToast(Url, header, text);\n        Console.ReadLine();\n    &#125;\n\n    static void SendToast(string uri, string header, string text)\n    &#123;\n        var client = new WebClient();\n\n        client.Headers.Add(\"Content-Type\", \"text/html\");\n        client.Headers.Add(\"X-NotificationClass\", \"2\");\n\n        var message = \"X-WindowsPhone-Target: toast\" + Environment.NewLine + Environment.NewLine +\n            @\"&lt;?xml version=\"\"1.0\"\" encoding=\"\"utf-8\"\"?>\n                &lt;wp:PushNotification xmlns:wp=\"\"WindowsPhonePushNotification\"\">\n                    &lt;wp:Toast>\n                        &lt;wp:Text1>&#123;0&#125;&lt;/wp:Text1>\n                        &lt;wp:Text2>&#123;1&#125;&lt;/wp:Text2>\n                    &lt;/wp:Toast>\n                &lt;/wp:PushNotification>\";\n\n        var result = client.UploadString(uri, \"POST\", String.Format(message, header, text));\n        Console.WriteLine(result);\n        Console.WriteLine(\"Device status is &#123;0&#125;\", client.ResponseHeaders[\"X-DeviceConnectionStatus\"]);\n        Console.WriteLine(\"Notification status is &#123;0&#125;\", client.ResponseHeaders[\"X-NotificationStatus\"]);\n    &#125;\n&#125;</code></pre>\n<p>Как видно, перед отправкой сообщения мы определяем специальный HTTP-заголовок <code>X-NotificationClass</code>, который в каждом случае имеет разные значения. Какие значения может принимать этот заголовок и для чего он нужен? Давайте разберемся.</p>\n<p>На самом деле этот заголовок выполняет две важные функции – определение типа уведомления и задание приоритета самого сообщения. Как я уже говорил, существует три типа уведомлений:</p>\n<ul>\n<li><strong>Tile Notifications</strong> – уведомления могут изменять изображение на “рабочем столе”;</li>\n<li><strong>Toast Notifications</strong> – уведомления всплывают в виде текстовых подсказок, по нажатию на которые можно перейти к самому приложению (даже если оно свернуто);</li>\n<li><strong>Raw Notifications</strong> – разработчик может сам определить логику обработки уведомления.</li>\n</ul>\n<p>Выбор типа уведомления (<strong>Tile</strong>, <strong>Toast</strong> или <strong>Raw</strong>) зависит от того, какой диапазон значений используется. Уже внутри этого диапазона выбираются приоритеты.</p>\n<p>Для <strong>Tile Notifications</strong> вы можете использовать одно из следующих значений в зависимости от приоритета:</p>\n<ul>\n<li><strong>21:</strong> обычный приоритет;</li>\n<li><strong>11:</strong> приоритетные уведомления (уведомления будут доставляться с фиксированным интервалом времени);</li>\n<li><strong>1:</strong> приоритет “Real Time” (уведомление будет доставлено настолько быстро, насколько это возможно).</li>\n</ul>\n<p>Для <strong>Toast Notifications</strong> вы можете использовать одно из следующих значений в зависимости от приоритета:</p>\n<ul>\n<li><strong>22:</strong> обычный приоритет;</li>\n<li>*<em>12:</em> приоритетные уведомления (уведомления будут доставляться с фиксированным интервалом времени);</li>\n<li><strong>2:</strong> приоритет “Real Time” (уведомление будет доставлено настолько быстро, насколько это возможно).</li>\n</ul>\n<p>Для <strong>Raw Notifications</strong> вы можете использовать одно из следующих значений в зависимости от приоритета:</p>\n<ul>\n<li><strong>23-31:</strong> обычный приоритет;</li>\n<li><strong>13-20:</strong> приоритетные уведомления (уведомления будут доставляться с фиксированным интервалом времени);</li>\n<li><strong>3-10:</strong> приоритет “Real Time” (уведомление будет доставлено настолько быстро, насколько это возможно).</li>\n</ul>\n<p>Таким образом, при отправке уведомлений мы можем изменять их тип и приоритет.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/windows-phone-notification-class/04a37861/windows_phone-notification-class@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2010/windows-phone-classic-compatibility/",
            "url": "https://blog.zwezdin.com/2010/windows-phone-classic-compatibility/",
            "title": "Совместимость приложений Windows Phone Classic и Windows Phone 7",
            "date_published": "2010-04-28T12:00:00.000Z",
            "content_html": "<p>После анонса <strong>Windows Phone 7</strong> и первых технических деталей о новой мобильной платформе от Microsoft, стало известно, что обратной совместимости приложений с <strong>Windows Phone Classic</strong> не будет. По моим наблюдениям, на эту тему началось множество различных спекуляций и провокаций. Здесь я хотел бы подробно обсудить эту тему и рассказать о том, как обстоят дела на самом деле.</p>\n<p>Итак, первое о чем сразу же хотелось бы сказать – обратной совместимости на уровне бинарников действительно нет. Это значит, что вы не можете взять готовое приложение для Windows Phone Classic и запустить его на Windows Phone 7 – этому есть ряд ограничений. Однако, не все так плохо, как может показаться на первый взгляд, и вот почему.</p>\n<p>Дело в том, что все приложения для Windows Phone 7 (исключая системные) работают в рамках управляемого кода. При этом разрабатывать приложения предлагается используя Silverlight или XNA. Вот как выглядит схематическое обобщенное представление платформы Windows Phone 7.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-classic-compatibility/c0201ef0/1@2x.png\"  alt=\"Совместимость приложений Windows Phone Classic и Windows Phone 7\"/>\n\n\n\n\n<p>Весь управляемый код по-прежнему работает в рамках старого и доброго .NET Compact Framework. Да, это не точная копия .NET Compact Framework 2.0 или 3.5, но суть от этого не изменяется. Если мы попробуем глубже взглянуть на среду исполнения, то увидим следующую картинку.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-classic-compatibility/ca539d3f/2@2x.png\"  alt=\"Совместимость приложений Windows Phone Classic и Windows Phone 7\"/>\n\n\n\n\n<p><em>Что следует из этой схемы?</em> А следует несколько интересных выводов. Во-первых, видно, что все управляемые приложения могут использовать стандартную <strong>BCL (Base Class Library)</strong> и множество уже привычных нам объектов и пространств имен, например, таких как <code>System.IO</code>, <code>System.Text</code>, <code>System.Threading</code> и т.д. Дальше отчетливо видно, что и приложения XNA, и приложения Silverlight могут использовать эти объекты для своей работы, однако каждый из них имеет собственные объекты специфичные для каждого из вариантов. Эти объекты в большинстве случаев отвечают за построение пользовательского интерфейса или предназначены для сопутствующих задач. Ну и, наконец, видно, что есть несколько объектов “надстроек” над Silverlight и XNA, которые специфичны для телефона. Например, такие объекты позволяют получить доступ к камере или сенсорам, работать с радио или вызывать системные окна и т.д.</p>\n<p>Если говорить о совместимости приложений, то такой подход позволяет нам безболезненно портировать часть приложения для Windows Phone Classic на новую платформу, если оно написано на управляемом коде. Портировать только часть приложения мы сможем из-за того, что среди представленных объектов нет Windows Forms. Это означает, что функциональность, связанную с пользовательским интерфейсом придется все-таки переделать. Но переделать пользовательский интерфейс – это не значит переделать полностью все приложение!</p>\n<p>Таким образом, если ваше приложение для Windows Phone Classic работает на управляемом коде, то большую часть логики вы скорее всего сможете перенести путем простого копирования кода в новое приложение.</p>\n<p>Как известно, рассуждать проще, чем сделать на практике. Поэтому здесь я прервусь и попробую взять приложение Windows Phone Classic и перенести его на новую платформу. Поехали!</p>\n<h2>Эксперимент по переносу приложения с WP Classic на WP7</h2><p>Итак, для того, чтобы нам перенести наше Windows Phone Classic приложение, нам нужно сначала его создать. Я не буду сейчас уделять внимание какой-либо сложной логике, а создам несложное приложение, вычисляющее тригонометрические функции. Для этого на форме я добавлю несколько элементов управления и задам для них обработчики. Выглядеть эта красота будет примерно так.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-classic-compatibility/f2576b88/3@2x.png\"  alt=\"Совместимость приложений Windows Phone Classic и Windows Phone 7\"/>\n\n\n\n\n<p>Позволю себе напомнить, что при добавлении элементов управления в дизайнер среда разработки автоматически генерирует код и помещает его в файл <code>.designer.cs</code>. Выглядит этот код примерно так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">namespace MyHardApplication\n&#123;\n    partial class Form1\n    &#123;\n        /// &lt;summary>\n        /// Required designer variable.\n        /// &lt;/summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// &lt;summary>\n        /// Clean up any resources being used.\n        /// &lt;/summary>\n        /// &lt;param name=\"disposing\">true if managed resources should be disposed; otherwise, false.&lt;/param>\n        protected override void Dispose(bool disposing)\n        &#123;\n            if (disposing &amp;&amp; (components != null))\n            &#123;\n                components.Dispose();\n            &#125;\n            base.Dispose(disposing);\n        &#125;\n\n        #region Windows Form Designer generated code\n\n        /// &lt;summary>\n        /// Required method for Designer support - do not modify\n        /// the contents of this method with the code editor.\n        /// &lt;/summary>\n        private void InitializeComponent()\n        &#123;\n            this.SinTextBox = new System.Windows.Forms.TextBox();\n            this.SinButton = new System.Windows.Forms.Button();\n            this.CosButton = new System.Windows.Forms.Button();\n            this.CosTextBox = new System.Windows.Forms.TextBox();\n            this.TanButton = new System.Windows.Forms.Button();\n            this.TanTextBox = new System.Windows.Forms.TextBox();\n            this.AtanButton = new System.Windows.Forms.Button();\n            this.AtanTextBox = new System.Windows.Forms.TextBox();\n            this.SuspendLayout();\n\n                    // Определение элементов управления\n\n            //\n            // SinTextBox\n            //\n            this.SinTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)\n                        | System.Windows.Forms.AnchorStyles.Right)));\n            this.SinTextBox.Location = new System.Drawing.Point(12, 16);\n            this.SinTextBox.Name = \"SinTextBox\";\n            this.SinTextBox.Size = new System.Drawing.Size(138, 21);\n            this.SinTextBox.TabIndex = 0;\n\n                    // Аналогичное описание для остальных элементов управления\n\n            //\n            // Form1\n            //\n            this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);\n            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;\n            this.AutoScroll = true;\n            this.ClientSize = new System.Drawing.Size(240, 294);\n            this.Controls.Add(this.AtanButton);\n            this.Controls.Add(this.AtanTextBox);\n            this.Controls.Add(this.TanButton);\n            this.Controls.Add(this.TanTextBox);\n            this.Controls.Add(this.CosButton);\n            this.Controls.Add(this.CosTextBox);\n            this.Controls.Add(this.SinButton);\n            this.Controls.Add(this.SinTextBox);\n            this.Name = \"Form1\";\n            this.Text = \"Form1\";\n            this.ResumeLayout(false);\n\n        &#125;\n\n        #endregion\n\n        private System.Windows.Forms.TextBox SinTextBox;\n        private System.Windows.Forms.Button SinButton;\n        private System.Windows.Forms.Button CosButton;\n        private System.Windows.Forms.TextBox CosTextBox;\n        private System.Windows.Forms.Button TanButton;\n        private System.Windows.Forms.TextBox TanTextBox;\n        private System.Windows.Forms.Button AtanButton;\n        private System.Windows.Forms.TextBox AtanTextBox;\n    &#125;\n&#125;</code></pre>\n<p>Как видно, такое описание достаточно легко поддается прочтению и осмыслению и впоследствии может послужить основной для переноса пользовательского интерфейса. Но об этом позже.</p>\n<p>Как я говорил, для кнопок мы создадим обработчики с минимальной логикой. В реальном приложении кода, конечно, будет больше, но суть от этого не меняется.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public partial class Form1 : Form\n&#123;\n    public Form1()\n    &#123;\n        InitializeComponent();\n    &#125;\n\n    private void SinButton_Click(object sender, EventArgs e)\n    &#123;\n        SinTextBox.Text = Math.Sin(double.Parse(SinTextBox.Text)).ToString();\n    &#125;\n\n    private void CosButton_Click(object sender, EventArgs e)\n    &#123;\n        CosTextBox.Text = Math.Cos(double.Parse(CosTextBox.Text)).ToString();\n    &#125;\n\n    private void TanButton_Click(object sender, EventArgs e)\n    &#123;\n        TanTextBox.Text = Math.Cos(double.Parse(TanTextBox.Text)).ToString();\n    &#125;\n\n    private void AtanButton_Click(object sender, EventArgs e)\n    &#123;\n        AtanTextBox.Text = Math.Cos(double.Parse(AtanTextBox.Text)).ToString();\n    &#125;\n&#125;</code></pre>\n<p>Итак, теперь у нас есть готовое работающее приложение для Windows Phone Classic. Давайте попробуем перенести его на Windows Phone 7.</p>\n<p>Первое, что мы сделаем – попробуем преобразовать пользовательский интерфейс. Для этих целей уже сейчас доступны различные инструменты (например, <strong>WinForm2XAML</strong>). Они позволяют на основе готовой сборки приложения получить разметку XAML.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-classic-compatibility/323f4bb1/4@2x.png\"  alt=\"Совместимость приложений Windows Phone Classic и Windows Phone 7\"/>\n\n\n\n\n<p>На выходе получим разметку, похожую на следующую.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;phoneNavigation:PhoneApplicationPage\n    x:Class=\"MyHardApplication.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:phoneNavigation=\"clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    mc:Ignorable=\"d\" d:DesignWidth=\"480\" d:DesignHeight=\"800\"\n    FontFamily=\"&#123;StaticResource PhoneFontFamilyNormal&#125;\"\n    FontSize=\"&#123;StaticResource PhoneFontSizeNormal&#125;\"\n    Foreground=\"&#123;StaticResource PhoneForegroundBrush&#125;\">\n\n    &lt;Canvas x:Name=\"cvsForm1\" Background=\"&#123;StaticResource PhoneBackgroundBrush&#125;\">\n        &lt;Button x:Name=\"AtanButton\" Width=\"144\" Height=\"20\" Canvas.Top=\"249\" Canvas.Left=\"307\" Content=\"Atan\"\n                Click=\"AtanButton_Click\" />\n        &lt;TextBox x:Name=\"AtanTextBox\" Width=\"289\" Height=\"20\" Canvas.Top=\"249\" Canvas.Left=\"12\" Text=\"\" />\n        &lt;Button x:Name=\"TanButton\" Width=\"144\" Height=\"20\" Canvas.Top=\"171\" Canvas.Left=\"307\" Content=\"Tan\"\n                Click=\"TanButton_Click\" />\n        &lt;TextBox x:Name=\"TanTextBox\" Width=\"289\" Height=\"20\" Canvas.Top=\"171\" Canvas.Left=\"12\" Text=\"\" />\n        &lt;Button x:Name=\"CosButton\" Width=\"144\" Height=\"20\" Canvas.Top=\"93\" Canvas.Left=\"307\" Content=\"Cos\"\n                Click=\"CosButton_Click\" />\n        &lt;TextBox x:Name=\"CosTextBox\" Width=\"289\" Height=\"20\" Canvas.Top=\"93\" Canvas.Left=\"12\" Text=\"\" />\n        &lt;Button x:Name=\"SinButton\" Width=\"144\" Height=\"20\" Canvas.Top=\"16\" Canvas.Left=\"307\" Content=\"Sin\"\n                Click=\"SinButton_Click\" />\n        &lt;TextBox x:Name=\"SinTextBox\" Width=\"289\" Height=\"20\" Canvas.Top=\"16\" Canvas.Left=\"12\" Text=\"\" />\n    &lt;/Canvas>\n\n&lt;/phoneNavigation:PhoneApplicationPage></code></pre>\n<p>К сожалению, используемый мной инструмент для переноса не смог перенести обработчики, а также не совсем корректно обработал отступы. Но в целом результат получился неплохой.</p>\n<p>Ну и осталось только перенести код логики (простым копированием) и обработчиков.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public partial class MainPage : PhoneApplicationPage\n&#123;\n    public MainPage()\n    &#123;\n        InitializeComponent();\n\n        SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape;\n    &#125;\n\n    private void SinButton_Click(object sender, RoutedEventArgs e)\n    &#123;\n        SinTextBox.Text = Math.Sin(double.Parse(SinTextBox.Text)).ToString();\n    &#125;\n\n    private void CosButton_Click(object sender, RoutedEventArgs e)\n    &#123;\n        CosTextBox.Text = Math.Cos(double.Parse(CosTextBox.Text)).ToString();\n    &#125;\n\n    private void TanButton_Click(object sender, RoutedEventArgs e)\n    &#123;\n        TanTextBox.Text = Math.Tan(double.Parse(TanTextBox.Text)).ToString();\n    &#125;\n\n    private void AtanButton_Click(object sender, RoutedEventArgs e)\n    &#123;\n        AtanTextBox.Text = Math.Atan(double.Parse(AtanTextBox.Text)).ToString();\n    &#125;\n&#125;</code></pre>\n<p>Теперь можно запустить приложение и полюбоваться на результат.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-classic-compatibility/5c63af32/5@2x.png\"  alt=\"Совместимость приложений Windows Phone Classic и Windows Phone 7\"/>\n\n\n\n\n<p>Так мы перенесли небольшое приложение с Windows Phone Classic на Windows Phone 7 в несколько шагов.</p>\n<h2>Подводим итог</h2><p>В этом примере я не старался показать, что перенести приложение на Windows Phone 7 можно в несколько кликов. Однако, я думаю теперь стало понятно, что процесс переноса приложений не такой уж и страшный, и некоторых ситуациях не нужно переписывать приложения полностью с чистого листа.</p>\n<p>Тем не менее, справедливости ради стоит отметить, что такой подход не работает в случае когда:</p>\n<ul>\n<li>Исходное приложение работает на неуправляемом (native) коде;</li>\n<li>Исходное приложение использует вызовы <code>P/Invoke</code> (в этом случае этот участок кода придется дорабатывать);</li>\n<li>Исходное приложение использует сторонние (нестандартные) элементы управления – в этом случае конвертеры в XAML вряд ли смогут обработать эти случаи.</li>\n</ul>\n<p>Также стоит сказать, что библиотека .NET CF в WP7 покрывает не все объекты, которые были доступны в WP Classic. Поэтому при переносе логики также могут возникать ньюансы.</p>\n<p>Однако, в целом видно, что платформы очень схожие и перенос своих приложений – не такое уж и тяжелое занятие.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/windows-phone-classic-compatibility/6c4bac88/windows_phone-classic-compatibility@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2010/windows-phone-push-notifications-raw/",
            "url": "https://blog.zwezdin.com/2010/windows-phone-push-notifications-raw/",
            "title": "Push Notifications в Windows Phone 7 (raw)",
            "date_published": "2010-04-28T12:00:00.000Z",
            "content_html": "<p>Я уже <a href=\"/2010/windows-phone-push-notifications-toast\">рассказывал</a> о том, что представляют собой уведомления в Windows Phone 7. Используя этот механизм можно доставлять уведомление из сервиса в облаке непосредственно на мобильное устройство.</p>\n<p>Я не буду рассматривать устройство механизма уведомлений в целом (ознакомиться с ним можно <a href=\"/2010/windows-phone-push-notifications-toast\">здесь</a>), повторю только отдельные типы уведомлений:</p>\n<ul>\n<li><strong>Tile Notifications</strong> — при отправке уведомления указывается URL некоторого изображения, расположенного в сети. Когда устройство получает уведомление оно самостоятельно загружает изображение из сети и обновляет его на рабочем столе устройства. Поскольку в Windows Phone OS CTP, доступной на данный момент размещение своих элементов на рабочем столе недоступно, то мы пока не будем рассматривать этот тип уведомлений и вернемся к нему позже.</li>\n<li><strong>Toast Notifications</strong> — при получении этого типа уведомления на экране устройства всплывает небольшое сообщение, содержащее текст уведомления. При этом приложение в данный момент может быть неактивным. При нажатии на это сообщение пользователь может быстро переключиться к соответствующему уведомлению.</li>\n<li><strong>Raw Notifications</strong> — при получении этого типа уведомления его содержимое передается непосредственно приложению. Приложение в праве обработать содержимое уведомление так, как считает нужным по своему собственному алгоритму. Это наиболее гибкий способ получения уведомлений от внешних сервисов.</li>\n</ul>\n<p>На этот раз давайте поговорим о Raw Notifications.</p>\n<h2>Raw Notifications</h2><p>Raw Notifications – это наиболее гибкий тип уведомлений. Обрабатывать данный тип уведомлений можно по своему усмотрению, используя нужный для приложения алгоритм. Если в случае с Toast Notifications и Tile Notifications мы должны соблюдать определенный формат и тип передаваемых данных (для Toast – строка, для Tile – адрес до изображения). то в случае с Raw Notifications мы можем передавать абсолютно любые данные, которые нам необходимы.</p>\n<p>Основная идея отправки уведомления остается той же самой – мобильное приложение подключается к сервису в облаке, получает уникальный URI и каким-либо образом передает его приложению, которое должно уведомлять наш сервис.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private HttpNotificationChannel notifications;\nprivate const string notificationServiceName = \"Some test\";\n\nprivate void InitializePushChannel()\n&#123;\n    if (notifications != null)\n    &#123;\n        ShowUri(notifications.ChannelUri);\n    &#125;\n    else\n    &#123;\n        try\n        &#123;\n            notifications = new HttpNotificationChannel(Application.Current.ToString(), notificationServiceName);\n            notifications.ChannelUriUpdated += new EventHandler&lt;NotificationChannelUriEventArgs>(notifications_ChannelUriUpdated);\n            notifications.Open();\n            ShowUri(notifications.ChannelUri);\n\n            notifications.BindToShellNotification();\n        &#125;\n        catch (NotificationChannelExistsException)\n        &#123;\n            notifications = HttpNotificationChannel.Find(Application.Current.ToString());\n            notifications.ChannelUriUpdated += new EventHandler&lt;NotificationChannelUriEventArgs>(notifications_ChannelUriUpdated);\n            ShowUri(notifications.ChannelUri);\n        &#125;\n    &#125;\n&#125;</code></pre>\n<p>Как видно, для работы с механизмом уведомлений используется все тот же объект HttpNotificationChannel. Аналогичным образом следует обрабатывать ситуацию, когда сервис уже был зарегистрирован (в этом случае следует использовать метод Find). Также следует обрабатывать событие ChannelUriUpdated на случай, если сервис в облаке решит изменить адрес для отправки уведомлений. Для этого мы создадим специальный обработчик, который в случае изменения адреса будет отображать его пользователю.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void ShowUri(Uri uri)\n&#123;\n    if (uri != null)\n    &#123;\n        Dispatcher.BeginInvoke(() => ChannelUri.Text = uri.ToString());\n        Debug.WriteLine(uri.ToString());\n    &#125;\n&#125;</code></pre>\n<p>Для того, чтобы обработать данные, которые были получены с помощью Raw-уведомлений следует подписаться на событие HttpNotificationReceived. Это событие будет срабатывать всякий раз, когда поступает новое Raw-уведомление.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private HttpNotificationChannel notifications;\nprivate const string notificationServiceName = \"Some test\";\n\nprivate void InitializePushChannel()\n&#123;\n    if (notifications != null)\n    &#123;\n        ShowUri(notifications.ChannelUri);\n    &#125;\n    else\n    &#123;\n        try\n        &#123;\n            notifications = new HttpNotificationChannel(Application.Current.ToString(), notificationServiceName);\n            notifications.ChannelUriUpdated += new EventHandler&lt;NotificationChannelUriEventArgs>(notifications_ChannelUriUpdated);\n            notifications.HttpNotificationReceived += new EventHandler&lt;HttpNotificationEventArgs>(notifications_HttpNotificationReceived);\n            notifications.Open();\n            ShowUri(notifications.ChannelUri);\n\n            notifications.BindToShellNotification();\n        &#125;\n        catch (NotificationChannelExistsException)\n        &#123;\n            notifications = HttpNotificationChannel.Find(Application.Current.ToString());\n            notifications.ChannelUriUpdated += new EventHandler&lt;NotificationChannelUriEventArgs>(notifications_ChannelUriUpdated);\n            notifications.HttpNotificationReceived += new EventHandler&lt;HttpNotificationEventArgs>(notifications_HttpNotificationReceived);\n            ShowUri(notifications.ChannelUri);\n        &#125;\n    &#125;\n&#125;</code></pre>\n<p>Теперь создадим обработчик, который будет срабатывать в момент получения уведомлений. Для этого мы уже подписались на событие HttpNotificationReceived. В параметрах обработчика события передается объект HttpNotificationEventArgs, позволяющий получить доступ к телу HTTP-запроса, который был получен от приложения, создавшего уведомление. В простейшем случае давайте отобразим это сообщение пользователю. Тело сообщения доступно как поток (Stream). Для его чтения мы будем использовать объект StreamReader.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">void notifications_HttpNotificationReceived(object sender, HttpNotificationEventArgs e)\n&#123;\n    using (var r = new StreamReader(e.Notification.Body))\n    &#123;\n        var data = r.ReadToEnd();\n        Debug.WriteLine(data);\n        Dispatcher.BeginInvoke(() => ReceivedData.Text = data);\n    &#125;\n&#125;</code></pre>\n<p>В данном обработчике мы используем элемент управления ReceivedData. Для того, чтобы это стало возможным его необходимо добавить на форму. Сделаем это легким движением руки.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;phoneNavigation:PhoneApplicationPage\n    x:Class=\"ToastNotification.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:phoneNavigation=\"clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    mc:Ignorable=\"d\" d:DesignWidth=\"480\" d:DesignHeight=\"800\"\n    FontFamily=\"&#123;StaticResource PhoneFontFamilyNormal&#125;\"\n    FontSize=\"&#123;StaticResource PhoneFontSizeNormal&#125;\"\n    Foreground=\"&#123;StaticResource PhoneForegroundBrush&#125;\">\n\n    &lt;Grid x:Name=\"LayoutRoot\">\n        &lt;Grid.RowDefinitions>\n            &lt;RowDefinition Height=\"Auto\"/>\n            &lt;RowDefinition Height=\"*\"/>\n        &lt;/Grid.RowDefinitions>\n        &lt;Grid.Background>\n            &lt;ImageBrush ImageSource=\"FormBackground.jpg\" Stretch=\"UniformToFill\"/>\n        &lt;/Grid.Background>\n\n        &lt;!--TitleGrid is the name of the application and page title-->\n        &lt;Grid x:Name=\"TitleGrid\" Grid.Row=\"0\">\n            &lt;TextBlock Text=\"MY APPLICATION\" x:Name=\"textBlockPageTitle\" Style=\"&#123;StaticResource PhoneTextPageTitle1Style&#125;\"/>\n            &lt;TextBlock Text=\"Notifications\" x:Name=\"textBlockListTitle\" Style=\"&#123;StaticResource PhoneTextPageTitle2Style&#125;\"/>\n        &lt;/Grid>\n\n        &lt;!--ContentGrid is empty. Place new content here-->\n        &lt;Grid x:Name=\"ContentGrid\" Grid.Row=\"1\">\n            &lt;Border VerticalAlignment=\"Top\" HorizontalAlignment=\"Center\" Margin=\"20\" Padding=\"20\" CornerRadius=\"5\">\n                &lt;Border.Background>\n                    &lt;SolidColorBrush Color=\"Black\" Opacity=\"0.2\"/>\n                &lt;/Border.Background>\n                &lt;TextBlock Name=\"ReceivedData\"\n                           TextWrapping=\"Wrap\"\n                           FontSize=\"30\"/>\n            &lt;/Border>\n\n            &lt;Border VerticalAlignment=\"Center\" HorizontalAlignment=\"Center\" Margin=\"20\" Padding=\"20\" CornerRadius=\"5\">\n                &lt;Border.Background>\n                    &lt;SolidColorBrush Color=\"Black\" Opacity=\"0.2\"/>\n                &lt;/Border.Background>\n                &lt;TextBlock Name=\"ChannelUri\"\n                           TextWrapping=\"Wrap\"\n                           FontSize=\"30\"/>\n            &lt;/Border>\n\n        &lt;/Grid>\n    &lt;/Grid>\n\n&lt;/phoneNavigation:PhoneApplicationPage></code></pre>\n<p>Мобильное приложение готово, теперь, используя полученный URI для отправки уведомлений отправить HTTP-запрос по этому адресу. Для этого как раньше создадим небольшое консольное приложение, способное выполнить эти несложные действия. Для этих целей мы все так же будем использовать объект WebClient. Однако, в отличие от Toast Notifications, при отправке Raw Notifications не нужно соблюдать какой-либо формат сообщения – можно просто записать в тело сообщения те данные, которые необходимо отправить мобильному устройству. Также стоит изменить заголовок “X-NotificationClass” на значение “3”, что будет означать наивысший приоритет отправки для Raw-уведомлений.</p>\n<p>При отправке уведомления, в ответ мы также получим два заголовка “X-DeviceConnectionStatus” и “X-NotificationStatus”, которые позволяют определить состояние подключения устройства к сети и статус доставки этого сообщения. Таким образом, код нашего простого приложения будет выглядеть следующим образом.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">class Program\n&#123;\n    private const string Url = @\"http://sn1.notify.live.net/throttledthirdparty/01.00/AAFTkavjFpAuT4i1fIKvueDiAgoOs1ADAgAAAAQOMDAwAAAAAAAAAAAAAAA\";\n\n    static void Main(string[] args)\n    &#123;\n        Console.Write(\"Text: \");\n        string text = Console.ReadLine();\n        SendToast(Url, text);\n        Console.ReadLine();\n    &#125;\n\n    static void SendToast(string uri, string text)\n    &#123;\n        var client = new WebClient();\n\n        client.Headers.Add(\"Content-Type\", \"text/html\");\n        client.Headers.Add(\"X-NotificationClass\", \"3\");\n\n        var result = client.UploadString(uri, \"POST\", text);\n        Console.WriteLine(result);\n        Console.WriteLine(\"Device status is &#123;0&#125;\", client.ResponseHeaders[\"X-DeviceConnectionStatus\"]);\n        Console.WriteLine(\"Notification status is &#123;0&#125;\", client.ResponseHeaders[\"X-NotificationStatus\"]);\n    &#125;\n&#125;</code></pre>\n<p>Теперь при запуске приложения и отправке текста, этот текст будет отображен на экране мобильного устройства.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-push-notifications-raw/c063c5c6/1@2x.png\"  alt=\"Push Notifications в Windows Phone 7 (raw)\"/>\n\n\n\n\n<p>Мы создали с вами тривиальное приложение, которое получая строку просто отображает её на экране. Однако, вся мощь Raw Notifications состоит в том, что мы можем определить совершенно любое поведение для обработки уведомлений. Например, давайте изменим приложение так, чтобы при получении названий некоторых цветов оно изменяло цвет букв, отображающих сообщение уведомления.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">void notifications_HttpNotificationReceived(object sender, HttpNotificationEventArgs e)\n&#123;\n    using (var r = new StreamReader(e.Notification.Body))\n    &#123;\n        var data = r.ReadToEnd().Trim('\\0');\n        Debug.WriteLine(data);\n\n        switch (data)\n        &#123;\n            case \"red\":\n                Dispatcher.BeginInvoke(() => ReceivedData.Foreground = new SolidColorBrush(Colors.Red));\n                break;\n            case \"green\":\n                Dispatcher.BeginInvoke(() => ReceivedData.Foreground = new SolidColorBrush(Colors.Green));\n                break;\n            case \"blue\":\n                Dispatcher.BeginInvoke(() => ReceivedData.Foreground = new SolidColorBrush(Colors.Blue));\n                break;\n            case \"white\":\n                Dispatcher.BeginInvoke(() => ReceivedData.Foreground = new SolidColorBrush(Colors.White));\n                break;\n            case \"black\":\n                Dispatcher.BeginInvoke(() => ReceivedData.Foreground = new SolidColorBrush(Colors.Black));\n                break;\n            case \"yellow\":\n                Dispatcher.BeginInvoke(() => ReceivedData.Foreground = new SolidColorBrush(Colors.Yellow));\n                break;\n            case \"orange\":\n                Dispatcher.BeginInvoke(() => ReceivedData.Foreground = new SolidColorBrush(Colors.Orange));\n                break;\n            case \"gray\":\n                Dispatcher.BeginInvoke(() => ReceivedData.Foreground = new SolidColorBrush(Colors.Gray));\n                break;\n            default:\n                Dispatcher.BeginInvoke(() => ReceivedData.Text = data);\n                break;\n        &#125;\n    &#125;\n&#125;</code></pre>\n<p>Теперь, при отправке уведомлений, содержащих имена цветов, цвет текста в приложении будет изменяться.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-push-notifications-raw/b21f736a/2@2x.png\"  alt=\"Push Notifications в Windows Phone 7 (raw)\"/>\n\n\n\n\n<p>Понятно, что это тоже очень простой алгоритм обработки. Однако, он хорошо демонстрирует, что здесь никто не ограничивает нас тем, каким образом построить обработку уведомлений. В общем случае мы можем создавать совершенно любые типы манипуляций и изменять поведение приложения.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/windows-phone-push-notifications-raw/3f02976a/windows_phone-push-notifications-raw@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2010/windows-phone-push-notifications-toast/",
            "url": "https://blog.zwezdin.com/2010/windows-phone-push-notifications-toast/",
            "title": "Push Notifications в Windows Phone 7 (toast)",
            "date_published": "2010-04-27T12:00:00.000Z",
            "content_html": "<p>Рассматривая возможности <strong>Windows Phone 7</strong> нельзя не обратить внимания на механизм <strong>Push Notification</strong>, которого не было в предыдущих версиях Windows Phone. Этот механизм сам по себе очень удобный для разработчиков и пользователей и позволяет асинхронно уведомлять пользователя о происходящих событиях. Например, сервис из облака может сообщить пользователю о том, что произошло какое-то событие, на которое нужно как-то отреагировать. Давайте разберемся как устроен этот механизм.</p>\n<p>В структуре <strong>Push Notifications</strong> существует специальный сервис в облаке (размещенный за счет мощностей Microsoft), который делает возможность работы уведомлений. Когда устройство хочет получать уведомления от обращается к этому сервису и устанавливает с ним постоянное соединение. В ответ на это сервис выдает этому устройству уникальный URI, используя который можно отправить по протоколу HTTP некоторое сообщение. Это сообщение получает тот самый сервис в облаке и пересылает его соответствующему устройству. Логично, что для каждого устройства и приложения этот URI будет уникальный. Таким образом, работу уведомлений в Windows Phone 7 можно описать так:</p>\n<ol>\n<li>Устройство устанавливает соединение с сервером Microsoft и получает уникальный HTTP-адрес;</li>\n<li>Устройство передает этот HTTP-адрес тому, кто будет уведомлять устройство о чем-либо;</li>\n<li>Когда необходимо уведомить устройство о чем-либо, приложение, которому был передан HTTP-адрес отправляет специальное HTTP-сообщение на этот адрес;</li>\n<li>Сервис Microsoft получает это сообщение и по возможности доставляет его на устройство. Если доставить сообщение на устройство не удалось, то отправитель получит соответствующее сообщение.</li>\n</ol>\n<p>Схематически этот алгоритм можно изобразить следующим образом.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-push-notifications-toast/8203f338/1@2x.png\"  alt=\"Push Notifications в Windows Phone 7 (toast)\"/>\n\n\n\n\n<p>Существуют три разных вида уведомлений для Windows Phone 7:</p>\n<ul>\n<li><strong>Tile Notifications</strong> — при отправке уведомления указывается URL некоторого изображения, расположенного в сети. Когда устройство получает уведомление оно самостоятельно загружает изображение из сети и обновляет его на рабочем столе устройства. Поскольку в Windows Phone OS CTP, доступной на данный момент размещение своих элементов на рабочем столе недоступно, то мы пока не будем рассматривать этот тип уведомлений и вернемся к нему позже.</li>\n<li><strong>Toast Notifications</strong> — при получении этого типа уведомления на экране устройства всплывает небольшое сообщение, содержащее текст уведомления. При этом приложение в данный момент может быть неактивным. При нажатии на это сообщение пользователь может быстро переключиться к соответствующему уведомлению.</li>\n<li><strong>Raw Notifications</strong> — при получении этого типа уведомления его содержимое передается непосредственно приложению. Приложение в праве обработать содержимое уведомление так, как считает нужным по своему собственному алгоритму. Это наиболее гибкий способ получения уведомлений от внешних сервисов.</li>\n</ul>\n<h2>Toast Notifications</h2><p>Впоследствии я расскажу о каждом из типов уведомлений. В этот раз дайте поподробнее рассмотрим Toast Notifications. Как я уже говорил, этот тип уведомлений позволяет вывести небольшое сообщение на экран пользователя даже в тех случаях, когда само приложение неактивно. При нажатии на такое уведомление, приложение будет активировано. Давайте создадим приложение, использующее этот тип уведомлений.</p>\n<p>Для начала создадим новое приложение Windows Phone 7 и добавим на форму элемент управления, который будет отображать текущий адрес для отправки уведомлений.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;phoneNavigation:PhoneApplicationPage\n    x:Class=\"ToastNotification.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:phoneNavigation=\"clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    mc:Ignorable=\"d\" d:DesignWidth=\"480\" d:DesignHeight=\"800\"\n    FontFamily=\"&#123;StaticResource PhoneFontFamilyNormal&#125;\"\n    FontSize=\"&#123;StaticResource PhoneFontSizeNormal&#125;\"\n    Foreground=\"&#123;StaticResource PhoneForegroundBrush&#125;\">\n\n    &lt;Grid x:Name=\"LayoutRoot\">\n        &lt;Grid.RowDefinitions>\n            &lt;RowDefinition Height=\"Auto\"/>\n            &lt;RowDefinition Height=\"*\"/>\n        &lt;/Grid.RowDefinitions>\n        &lt;Grid.Background>\n            &lt;ImageBrush ImageSource=\"FormBackground.jpg\" Stretch=\"UniformToFill\"/>\n        &lt;/Grid.Background>\n\n        &lt;!--TitleGrid is the name of the application and page title-->\n        &lt;Grid x:Name=\"TitleGrid\" Grid.Row=\"0\">\n            &lt;TextBlock Text=\"MY APPLICATION\" x:Name=\"textBlockPageTitle\" Style=\"&#123;StaticResource PhoneTextPageTitle1Style&#125;\"/>\n            &lt;TextBlock Text=\"Notifications\" x:Name=\"textBlockListTitle\" Style=\"&#123;StaticResource PhoneTextPageTitle2Style&#125;\"/>\n        &lt;/Grid>\n\n        &lt;!--ContentGrid is empty. Place new content here-->\n        &lt;Grid x:Name=\"ContentGrid\" Grid.Row=\"1\">\n\n            &lt;Border VerticalAlignment=\"Center\" HorizontalAlignment=\"Center\" Margin=\"20\" Padding=\"20\" CornerRadius=\"5\">\n                &lt;Border.Background>\n                    &lt;SolidColorBrush Color=\"Black\" Opacity=\"0.2\"/>\n                &lt;/Border.Background>\n                &lt;TextBlock Name=\"ChannelUri\"\n                           TextWrapping=\"Wrap\"\n                           FontSize=\"30\"/>\n            &lt;/Border>\n\n        &lt;/Grid>\n    &lt;/Grid>\n\n&lt;/phoneNavigation:PhoneApplicationPage></code></pre>\n<p>Теперь при старте приложения следует соединиться с сервисом уведомлений и получить адрес для отправки уведомлений. Для этих целей следует подключить сборку <code>Microsoft.Phone.Notification</code> и использовать объект <code>HttpNotificationChannel</code>. Если мы еще не обращались к сервису уведомлений, то необходимо использовать метод <code>Open()</code> и установить соединение; если ранее мы уже обращались к сервису, то нужно просто заново соединиться с сервисом, используя метод <code>Find()</code>. При установлении соединения мы должны указать уникальное имя канала и/или сервиса. Эти параметры задаются в конструкторе объекта <code>HttpNotificationChannel</code>.</p>\n<p>После установления соединения мы можем обратиться к свойству <code>ChannelUri</code> объекта <code>HttpNotificationChannel</code> и получить текущий адрес для уведомлений. Однако, сервис уведомлений может самостоятельно изменить этот адрес по своему усмотрению. Для того, чтобы быть в курсе таких изменений следует подписаться на событие <code>ChannelUriUpdated</code> объекта <code>HttpNotificationChannel</code> и обрабатывать изменения адреса там.</p>\n<p>После запуска приложения выведем текущий адрес для уведомлений на экран, а также в консоль отладки (чтобы этот адрес можно было скопировать в буфер обмена). Таким образом мы получим небольшой фрагмент кода для приложения Windows Phone 7.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public partial class MainPage : PhoneApplicationPage\n&#123;\n    private HttpNotificationChannel notifications;\n    private const string notificationServiceName = \"Some test\";\n\n    public MainPage()\n    &#123;\n        InitializeComponent();\n\n        SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape;\n\n        InitializePushChannel();\n    &#125;\n\n    private void InitializePushChannel()\n    &#123;\n        if (notifications != null)\n        &#123;\n            ShowUri(notifications.ChannelUri);\n        &#125;\n        else\n        &#123;\n            try\n            &#123;\n                notifications = new HttpNotificationChannel(Application.Current.ToString(), notificationServiceName);\n                notifications.ChannelUriUpdated += new EventHandler&lt;NotificationChannelUriEventArgs>(notifications_ChannelUriUpdated);\n                notifications.Open();\n                ShowUri(notifications.ChannelUri);\n\n                notifications.BindToShellNotification();\n            &#125;\n            catch (NotificationChannelExistsException)\n            &#123;\n                notifications = HttpNotificationChannel.Find(Application.Current.ToString());\n                notifications.ChannelUriUpdated += new EventHandler&lt;NotificationChannelUriEventArgs>(notifications_ChannelUriUpdated);\n                ShowUri(notifications.ChannelUri);\n            &#125;\n        &#125;\n    &#125;\n\n    private void ShowUri(Uri uri)\n    &#123;\n        if (uri != null)\n        &#123;\n            Dispatcher.BeginInvoke(() => ChannelUri.Text = uri.ToString());\n            Debug.WriteLine(uri.ToString());\n        &#125;\n    &#125;\n\n    private void notifications_ChannelUriUpdated(object sender, NotificationChannelUriEventArgs e)\n    &#123;\n        ShowUri(e.ChannelUri);\n    &#125;\n&#125;</code></pre>\n<p>На этом создание приложения для устройства завершено. Теперь нам необходимо отправить специальное HTTP-сообщение по адресу для уведомлений. Для этих целей можно использовать различные сторонние утилиты, например, <a href=\"http://www.fiddler2.com/fiddler2/\">Fiddler</a>. Однако, давайте пройдем путь до конца и создадим приложение, которое будет отправлять соответствующие уведомления по указанному адресу.</p>\n<p>Для того, чтобы отправить Toast-уведомление следует использовать следующий формат сообщения:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">X-WindowsPhone-Target: toast\n\n&lt;?xml version=\"\"1.0\"\" encoding=\"\"utf-8\"\"?>\n&lt;wp:PushNotification xmlns:wp=\"\"WindowsPhonePushNotification\"\">\n    &lt;wp:Toast>\n        &lt;wp:Text1>Header&lt;/wp:Text1>\n        &lt;wp:Text2>Text&lt;/wp:Text2>\n    &lt;/wp:Toast>\n&lt;/wp:PushNotification></code></pre>\n<p>В данном случае <code>Header</code> и <code>Text</code> – это те значения, которые пользователь увидит у себя на экране. Кроме того, HTTP-сообщение должно иметь заголовок <code>X-NotificationClass</code>, которое отражает важность доставляемого сообщения. Более подробно о формате сообщений и классе важности сообщений можно почитать <a href=\"http://msdn.microsoft.com/en-us/library/ff402542(v=VS.92).aspx\">здесь</a> и <a href=\"http://msdn.microsoft.com/en-us/library/ff402545(v=VS.92).aspx\">здесь</a>.</p>\n<p>В ответ на отправляемый HTTP-запрос сервис вернет ответ, в котором содержится два важных HTTP-заголовка — <code>X-DeviceConnectionStatus</code> и <code>X-NotificationStatus</code>. Заголовок <code>X-DeviceConnectionStatus</code> отражает состояние подключения устройства к сервису, а заголовок <code>X-NotificationStatus</code> позволяет понять успешно ли доставлено сообщение.</p>\n<p>Теперь, все, что осталось сделать – это отправить соответствующий HTTP-запрос по указанному адресу и обработать результат. Сделаем небольшое консольное приложение в которое поместим следующий код.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">class Program\n&#123;\n    private const string Url = @\"http://sn1.notify.live.net/throttledthirdparty/01.00/AAFTkavjFpAuT4i1fIKvueDiAgoOs1ADAgAAAAQOMDAwAAAAAAAAAAAAAAA\";\n\n    static void Main(string[] args)\n    &#123;\n        Console.Write(\"Header: \");\n        string header = Console.ReadLine();\n        Console.Write(\"Text: \");\n        string text = Console.ReadLine();\n        SendToast(Url, header, text);\n        Console.ReadLine();\n    &#125;\n\n    static void SendToast(string uri, string header, string text)\n    &#123;\n        var client = new WebClient();\n\n        client.Headers.Add(\"Content-Type\", \"text/html\");\n        client.Headers.Add(\"X-NotificationClass\", \"2\");\n\n        var message = \"X-WindowsPhone-Target: toast\" + Environment.NewLine + Environment.NewLine +\n            @\"&lt;?xml version=\"\"1.0\"\" encoding=\"\"utf-8\"\"?>\n                &lt;wp:PushNotification xmlns:wp=\"\"WindowsPhonePushNotification\"\">\n                    &lt;wp:Toast>\n                        &lt;wp:Text1>&#123;0&#125;&lt;/wp:Text1>\n                        &lt;wp:Text2>&#123;1&#125;&lt;/wp:Text2>\n                    &lt;/wp:Toast>\n                &lt;/wp:PushNotification>\";\n\n\n\n        var result = client.UploadString(uri, \"POST\", String.Format(message, header, text));\n        Console.WriteLine(result);\n        Console.WriteLine(\"Device status is &#123;0&#125;\", client.ResponseHeaders[\"X-DeviceConnectionStatus\"]);\n        Console.WriteLine(\"Notification status is &#123;0&#125;\", client.ResponseHeaders[\"X-NotificationStatus\"]);\n    &#125;\n&#125;</code></pre>\n<p>Теперь при запуске мобильного приложения из консоли отладки получим нужный URI для отправки уведомлений и вставим его в полученное консольное приложение. При запуске консольного приложения укажем значения <code>Header</code> и <code>Text</code>. Если все прошло успешно, в ответ на этот запрос увидим сообщение о доставке уведомления.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-push-notifications-toast/d0e57a0f/2@2x.png\"  alt=\"Push Notifications в Windows Phone 7 (toast)\"/>\n\n\n\n\n<p>Если теперь взглянуть на экран эмулятора Windows Phone 7, то можно увидеть, что там появилось соответствующее уведомление.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-push-notifications-toast/da9dec7e/3@2x.png\"  alt=\"Push Notifications в Windows Phone 7 (toast)\"/>\n\n\n\n\n<p>Обратите внимание, что вместе с сообщением отображается некий значок. Это – значок приложения, который можно легко изменить.</p>\n<p>Как видно, создать свое приложение, использующее уведомления очень просто. Ну а в следующий раз я расскажу о том, как использовать Raw Notifications.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/windows-phone-push-notifications-toast/c093c029/windows_phone-push-notifications-toast@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2010/windows-phone-appbar/",
            "url": "https://blog.zwezdin.com/2010/windows-phone-appbar/",
            "title": "Меню и Application Bar для приложений Windows Phone 7",
            "date_published": "2010-04-26T12:00:00.000Z",
            "content_html": "<p>Меню и панель инструментов являются неотъемлемыми частями для практически любого современного приложения. Эти элементы могут видоизменяться (например, превращаясь в ленту), но основной смысл всегда остается один – дать пользователю возможность перемещаться от одной функции приложения к другой.</p>\n<p>Не обошлось без подобных элементов и в Windows Phone 7 – здесь появились два специфичных элемента, объединенных общим названием <strong>Application Bar</strong>. Элемент Application Bar позволяет разместить одновременно до 7 кнопок управления, а также добавить меню, которое вызывается пользователем по требованию. Application Bar всегда размещается в нижней части окна (если используется вертикальное расположение) или располагается слева или справа (если используется горизонтальное расположение). Приложение, использующее Application Bar выглядит следующим образом.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-appbar/fa590fb4/1@2x.png\"  alt=\"Меню и Application Bar для приложений Windows Phone 7\"/>\n\n\n\n\n<p>Используя Application Bar приложения могут получить стандартизованное представление элементов навигации в приложении. При этом за разработчика уже будут решены стандартные вопросы, связанные с адаптацией внешнего вида под различные расположения и размеры экранов и т.д.</p>\n<p>Для того, чтобы создать приложение с Application Bar следует выполнить несколько несложных шагов.</p>\n<p>Во-первых, нужно добавить ссылку на сборку <code>Microsoft.Phone.Shell</code> (судя по всему в финальном выпуске название может быть переименовано). Эта сборка содержит все необходимые объекты для работы с Application Bar.</p>\n<p>Во-вторых следует определить пространство имен в разметке страницы, которое соотносится с этими объектами. Назовем это пространство имено <code>shell</code>.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;phoneNavigation:PhoneApplicationPage\n    x:Class=\"AppBar.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:phoneNavigation=\"clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:shell=\"clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone.Shell\"\n    mc:Ignorable=\"d\" d:DesignWidth=\"480\" d:DesignHeight=\"800\"\n    FontFamily=\"&#123;StaticResource PhoneFontFamilyNormal&#125;\"\n    FontSize=\"&#123;StaticResource PhoneFontSizeNormal&#125;\"\n    Foreground=\"&#123;StaticResource PhoneForegroundBrush&#125;\">\n\n&lt;!-- Содержимое окна -->\n\n&lt;/phoneNavigation:PhoneApplicationPage></code></pre>\n<p>Каждый объект <code>PhoneApplicationPage</code> содержит свойство <code>ApplicationBar</code>. Нужно проинициализировать это свойство для того, чтобы иметь возможность работать с Application Bar. Сделать это можно двумя способами – в разметке и программно. Если определить это свойство в разметке, то код страницы будет выглядеть следующим образом.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;phoneNavigation:PhoneApplicationPage\n    x:Class=\"AppBar.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:phoneNavigation=\"clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:shell=\"clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone.Shell\"\n    mc:Ignorable=\"d\" d:DesignWidth=\"480\" d:DesignHeight=\"800\"\n    FontFamily=\"&#123;StaticResource PhoneFontFamilyNormal&#125;\"\n    FontSize=\"&#123;StaticResource PhoneFontSizeNormal&#125;\"\n    Foreground=\"&#123;StaticResource PhoneForegroundBrush&#125;\">\n    &lt;phoneNavigation:PhoneApplicationPage.ApplicationBar>\n        &lt;shell:ApplicationBar Visible=\"True\" IsMenuEnabled=\"True\" Opacity=\"1.0\">\n        &lt;/shell:ApplicationBar>\n    &lt;/phoneNavigation:PhoneApplicationPage.ApplicationBar>\n\n&lt;!-- Содержимое окна -->\n\n&lt;/phoneNavigation:PhoneApplicationPage></code></pre>\n<p>Как видно, используя различные свойства объекта <code>AppliationBar</code> можно управлять поведением нашей панели. Например, можно включить или отключить меню, задать режимы видимости и т.д. Для того, чтобы задать набор кнопок, которые будут располагаться на <code>ApplicationBar</code> следует добавить их в коллекцию <code>Buttons</code>. Каждая кнопка представляется объектом <code>ApplicationBarIconButton</code>, который также находится в сборке <code>Microsoft.Phone.Shell</code>.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;phoneNavigation:PhoneApplicationPage\n    x:Class=\"AppBar.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:phoneNavigation=\"clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:shell=\"clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone.Shell\"\n    mc:Ignorable=\"d\" d:DesignWidth=\"480\" d:DesignHeight=\"800\"\n    FontFamily=\"&#123;StaticResource PhoneFontFamilyNormal&#125;\"\n    FontSize=\"&#123;StaticResource PhoneFontSizeNormal&#125;\"\n    Foreground=\"&#123;StaticResource PhoneForegroundBrush&#125;\">\n    &lt;phoneNavigation:PhoneApplicationPage.ApplicationBar>\n        &lt;shell:ApplicationBar Visible=\"True\" IsMenuEnabled=\"True\" Opacity=\"1.0\">\n            &lt;shell:ApplicationBar.Buttons>\n                &lt;shell:ApplicationBarIconButton IconUri=\"/Images/icon1.png\"/>\n                &lt;shell:ApplicationBarIconButton IconUri=\"/Images/icon2.png\"/>\n                &lt;shell:ApplicationBarIconButton IconUri=\"/Images/icon3.png\"/>\n            &lt;/shell:ApplicationBar.Buttons>\n        &lt;/shell:ApplicationBar>\n    &lt;/phoneNavigation:PhoneApplicationPage.ApplicationBar>\n\n&lt;!-- Содержимое окна -->\n\n&lt;/phoneNavigation:PhoneApplicationPage></code></pre>\n<p>Как видно, при добавлении кнопки указывается путь до изображения с иконкой. Для того, чтобы иконка отображалась в приложении следует создать соответствующую папку в проекте, добавить туда нужные файлы и выставить для этих файлов следующие параметры:</p>\n<ul>\n<li>Build Action: <code>Content</code></li>\n<li>Copy to Output Directory: <code>Always</code></li>\n</ul>\n<p>Теперь при запуске приложения на форме появится Application Bar с несколькими кнопками.</p>\n<p>Аналогичным образом определяется меню приложения. Однако, в этом случае следует использовать объекты <code>ApplicationBarMenuItem</code> и добавлять их в коллекцию <code>MenuItems</code>.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;phoneNavigation:PhoneApplicationPage\n    x:Class=\"AppBar.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:phoneNavigation=\"clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:shell=\"clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone.Shell\"\n    mc:Ignorable=\"d\" d:DesignWidth=\"480\" d:DesignHeight=\"800\"\n    FontFamily=\"&#123;StaticResource PhoneFontFamilyNormal&#125;\"\n    FontSize=\"&#123;StaticResource PhoneFontSizeNormal&#125;\"\n    Foreground=\"&#123;StaticResource PhoneForegroundBrush&#125;\">\n    &lt;phoneNavigation:PhoneApplicationPage.ApplicationBar>\n        &lt;shell:ApplicationBar Visible=\"True\" IsMenuEnabled=\"True\" Opacity=\"1.0\">\n            &lt;shell:ApplicationBar.Buttons>\n                &lt;shell:ApplicationBarIconButton IconUri=\"/Images/icon1.png\"/>\n                &lt;shell:ApplicationBarIconButton IconUri=\"/Images/icon2.png\"/>\n                &lt;shell:ApplicationBarIconButton IconUri=\"/Images/icon3.png\"/>\n            &lt;/shell:ApplicationBar.Buttons>\n            &lt;shell:ApplicationBar.MenuItems>\n                &lt;shell:ApplicationBarMenuItem Text=\"About\"/>\n                &lt;shell:ApplicationBarMenuItem Text=\"Settings\"/>\n                &lt;shell:ApplicationBarMenuItem Text=\"Close\"/>\n            &lt;/shell:ApplicationBar.MenuItems>\n        &lt;/shell:ApplicationBar>\n    &lt;/phoneNavigation:PhoneApplicationPage.ApplicationBar>\n\n&lt;!-- Содержимое окна -->\n\n&lt;/phoneNavigation:PhoneApplicationPage></code></pre>\n<p>Теперь при запуске приложения панель будет отображаться, меню будет доступно, но при нажатии на каждый пункт ничего не будет происходить. Нужно добавить обработчик нажатия на кнопки и пункты меню. Делается это очень просто, стандартным для XAML способом.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;phoneNavigation:PhoneApplicationPage\n    x:Class=\"AppBar.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:phoneNavigation=\"clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:shell=\"clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone.Shell\"\n    mc:Ignorable=\"d\" d:DesignWidth=\"480\" d:DesignHeight=\"800\"\n    FontFamily=\"&#123;StaticResource PhoneFontFamilyNormal&#125;\"\n    FontSize=\"&#123;StaticResource PhoneFontSizeNormal&#125;\"\n    Foreground=\"&#123;StaticResource PhoneForegroundBrush&#125;\">\n    &lt;phoneNavigation:PhoneApplicationPage.ApplicationBar>\n        &lt;shell:ApplicationBar Visible=\"True\" IsMenuEnabled=\"True\" Opacity=\"1.0\">\n            &lt;shell:ApplicationBar.Buttons>\n                &lt;shell:ApplicationBarIconButton IconUri=\"/Images/icon1.png\" Click=\"ApplicationBarIconButton_Click\"/>\n                &lt;shell:ApplicationBarIconButton IconUri=\"/Images/icon2.png\" Click=\"ApplicationBarIconButton_Click\"/>\n                &lt;shell:ApplicationBarIconButton IconUri=\"/Images/icon3.png\" Click=\"ApplicationBarIconButton_Click\"/>\n            &lt;/shell:ApplicationBar.Buttons>\n            &lt;shell:ApplicationBar.MenuItems>\n                &lt;shell:ApplicationBarMenuItem Text=\"About\" Click=\"ApplicationBarMenuItem_Click\"/>\n                &lt;shell:ApplicationBarMenuItem Text=\"Settings\" Click=\"ApplicationBarMenuItem_Click\"/>\n                &lt;shell:ApplicationBarMenuItem Text=\"Close\" Click=\"ApplicationBarMenuItem_Click\"/>\n            &lt;/shell:ApplicationBar.MenuItems>\n        &lt;/shell:ApplicationBar>\n    &lt;/phoneNavigation:PhoneApplicationPage.ApplicationBar>\n\n&lt;!-- Содержимое окна -->\n\n&lt;/phoneNavigation:PhoneApplicationPage></code></pre>\n<p>Ну и осталось добавить обработчики в коде. В данном случае я буду просто выводить сообщение на экран.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void ApplicationBarIconButton_Click(object sender, EventArgs e)\n&#123;\n    MessageBox.Show(\"Button clicked.\");\n&#125;\n\nprivate void ApplicationBarMenuItem_Click(object sender, EventArgs e)\n&#123;\n    MessageBox.Show(\"Menu clicked\");\n&#125;</code></pre>\n<p>В данном случае я задал один и тот же обработчик для всех кнопок и для всех пунктов меню. Понятно, что в вашем приложении можно создать и определить несколько разных обработчиков, в каждом из которых определить собственное поведение.</p>\n<p>Также функциональность Application Bar позволяет немного переопределить внешний вид. Можно использовать свойство <code>Opacity</code> для изменения прозрачности панели. Если значение <code>Opacity</code> равно <code>1</code>, то панель непрозрачна, при этом нижняя часть окна соприкасается с панелью Application Bar. Если значение свойства <code>Opacity</code> меньше <code>1</code>, то окно растягивается по всей ширине, а панель располагается поверх окна. При этом, если установить значение свойства Opacity равным <code>0.01</code>, то панель станет прозрачной и будет располагаться над окном. Определим <code>50%</code> полупрозрачность панели и посмотрим как это будет выглядеть для пользователя. Для того, чтобы разница была заметна, разместим на фоне некоторое изображение.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;phoneNavigation:PhoneApplicationPage\n    x:Class=\"AppBar.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:phoneNavigation=\"clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:shell=\"clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone.Shell\"\n    mc:Ignorable=\"d\" d:DesignWidth=\"480\" d:DesignHeight=\"800\"\n    FontFamily=\"&#123;StaticResource PhoneFontFamilyNormal&#125;\"\n    FontSize=\"&#123;StaticResource PhoneFontSizeNormal&#125;\"\n    Foreground=\"&#123;StaticResource PhoneForegroundBrush&#125;\">\n    &lt;phoneNavigation:PhoneApplicationPage.ApplicationBar>\n        &lt;shell:ApplicationBar Visible=\"True\" IsMenuEnabled=\"True\" Opacity=\"0.5\">\n            &lt;shell:ApplicationBar.Buttons>\n                &lt;shell:ApplicationBarIconButton IconUri=\"/Images/icon1.png\" Click=\"ApplicationBarIconButton_Click\"/>\n                &lt;shell:ApplicationBarIconButton IconUri=\"/Images/icon2.png\" Click=\"ApplicationBarIconButton_Click\"/>\n                &lt;shell:ApplicationBarIconButton IconUri=\"/Images/icon3.png\" Click=\"ApplicationBarIconButton_Click\"/>\n            &lt;/shell:ApplicationBar.Buttons>\n            &lt;shell:ApplicationBar.MenuItems>\n                &lt;shell:ApplicationBarMenuItem Text=\"About\" Click=\"ApplicationBarMenuItem_Click\"/>\n                &lt;shell:ApplicationBarMenuItem Text=\"Settings\" Click=\"ApplicationBarMenuItem_Click\"/>\n                &lt;shell:ApplicationBarMenuItem Text=\"Close\" Click=\"ApplicationBarMenuItem_Click\"/>\n            &lt;/shell:ApplicationBar.MenuItems>\n        &lt;/shell:ApplicationBar>\n    &lt;/phoneNavigation:PhoneApplicationPage.ApplicationBar>\n\n    &lt;Grid x:Name=\"LayoutRoot\">\n        &lt;Grid.RowDefinitions>\n            &lt;RowDefinition Height=\"Auto\"/>\n            &lt;RowDefinition Height=\"*\"/>\n        &lt;/Grid.RowDefinitions>\n        &lt;Grid.Background>\n            &lt;ImageBrush ImageSource=\"Images/landscape.jpg\" />\n        &lt;/Grid.Background>\n\n        &lt;!--TitleGrid is the name of the application and page title-->\n        &lt;Grid x:Name=\"TitleGrid\" Grid.Row=\"0\">\n            &lt;TextBlock Text=\"MY APPLICATION\" x:Name=\"textBlockPageTitle\" Style=\"&#123;StaticResource PhoneTextPageTitle1Style&#125;\"/>\n            &lt;TextBlock Text=\"page title\" x:Name=\"textBlockListTitle\" Style=\"&#123;StaticResource PhoneTextPageTitle2Style&#125;\"/>\n        &lt;/Grid>\n\n        &lt;!--ContentGrid is empty. Place new content here-->\n        &lt;Grid x:Name=\"ContentGrid\" Grid.Row=\"1\">\n        &lt;/Grid>\n    &lt;/Grid>\n\n&lt;/phoneNavigation:PhoneApplicationPage></code></pre>\n<p>Теперь при запуске приложения можно увидеть следующий результат.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-appbar/4726854d/2@2x.png\"  alt=\"Меню и Application Bar для приложений Windows Phone 7\"/>\n\n\n\n\n<p>Все аналогичные манипуляции с Application Bar можно выполнять и программно. Для этого следует воспользоваться теми же самыми объектами и свойством формы <code>ApplicationBar</code>.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/windows-phone-appbar/2c0b1916/windows_phone-appbar@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2010/windows-phone-input/",
            "url": "https://blog.zwezdin.com/2010/windows-phone-input/",
            "title": "Ввод информации в приложениях Windows Phone 7",
            "date_published": "2010-04-25T12:00:00.000Z",
            "content_html": "<p>Активные пользователи мобильных устройств очень хорошо знают, что одной из главных проблем при использовании устройства является ввод информации. Для ввода информации используется реальная или <strong>виртуальная клавиатуры</strong>. Более того, зачастую используется именно привычная QWERTY-раскладка клавиатуры. Проблема заключается в размерах.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-input/e05ed2a0/1@2x.png\"  alt=\"Ввод информации в приложениях Windows Phone 7\"/>\n\n\n\n\n<p>Физические размеры устройств достаточно малы и разместить на экране таких аппаратов несколько десятков кнопок достаточно сложно, если оставлять кнопки большого размера. Поэтому зачастую кнопки остаются небольшими и ввод информации не такой удобный, как на настольных компьютерах или ноутбуках. С аппаратной выдвижной клавиатурой работать более удобно, но упомянутая выше проблема сохраняется – размеры кнопок невелики. Поэтому ввод информации при использовании мобильных устройств – одна из наиболее сложных проблем в удобстве пользования этих устройств.</p>\n<p>Однако, недостаток в виде небольших размеров одновременно является и преимуществом данного вида устройств. Именно благодаря небольшим размерам мы имеем возможность использовать эти устройства в тех случаях, когда использование, например, ноутбука невозможно. Например, бывает очень сложно достать ноутбук и просмотреть содержимое своего электронного ящика, когда гуляешь по супермаркету, однако, это вполне реально сделать при использовании мобильного устройства. Именно поэтому эта проблема будет жить еще долго в мобильных устройствах и разработчики вряд ли что-то с этим смогут поделать. На самом деле, кое-что разработчики все-таки могут сделать. А именно, сделать работу с виртуальной клавиатурой более удобной.</p>\n<p>Каким образом мы, как разработчики мобильных приложений можем упростить жизнь пользователям? Некоторое время назад я <a href=\"/2009/windows7-sensor-and-location-platform-part1/\">писал</a> о контекстно-зависимых приложениях. На самом деле тот же самый контекстно-зависимый подход мы можем использовать при вводе информации пользователем в мобильное устройство. Необходимо только понять, на какие условия необходимо реагировать.</p>\n<p>Если рассмотреть наиболее типичные для пользователя сценарии, то можно понять, что в большинстве случаев они повторяются. Так, например, клавиатура обычно используется для ввода номера телефона, имени контакта, ввода текстовой информации (например, сообщения электронной почты или SMS), ввода адреса электронной почты, адреса веб-ресурса и т.д. Сейчас не так важно понять все сценарии, сколько важно понять, что они есть и они действительно зачастую повторяются. При этом в каждом конкретном случае от клавиатуры необходима разная функциональность. Например, при вводе адреса электронной почты нам наверняка потребуются знаки <code>@</code> и точка, а при вводе телефонного номера скорее всего нужны будут только цифры. Все эти варианты специфичны для каждого случая.</p>\n<p>Мы плавно подошли к тому, что приложение способно помочь пользователю с вводом информации, адаптируя вид виртуальной клавиатуры в соответствии с его потребностями. Основная проблема в том, как определить что пользователь собирается вводить в данный момент. Если попробовать решить эту задачу в общем, то операционная система может только в ряде случае с трудом догадаться какую именно информацию собирается вводить пользователь. Поэтому, если бы проблема решалась именно таким образом, то вряд ли бы можно было рассчитывать на что-то эффективное.</p>\n<p>Давайте посмотрим на это с другой стороны. Каждый разработчик при разработке своего приложения с большой вероятностью может знать для чего предназначено то или иное поле. Другими словами, когда разработчик размещает на форме очередное поле ввода, он, скорее всего, точно представляет для чего оно нужно и какая информация там будет. Поэтому наиболее правильным способом является дать разработчику приложения возможность определить поведение виртуальной клавиатуры для каждого элемента управления. Такой подход,  одной стороны не сильно затруднит разработчика, а другой – сильно упростит жизнь обычному пользователю. Именно такой подход используется в Windows Phone 7 для ввода информации. Для этих целей для элементов управления WIndows Phone 7 существует свойство <code>InputScope</code>. Давайте посмотрим как оно работает.</p>\n<p>Для начала создадим пустое Windows Phone 7 приложение на основе Silverlight и добавим на форму поле ввода (<code>TextBox</code>).</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;phoneNavigation:PhoneApplicationPage\n    x:Class=\"InputScope.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:phoneNavigation=\"clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    mc:Ignorable=\"d\" d:DesignWidth=\"480\" d:DesignHeight=\"800\"\n    FontFamily=\"&#123;StaticResource PhoneFontFamilyNormal&#125;\"\n    FontSize=\"&#123;StaticResource PhoneFontSizeNormal&#125;\"\n    Foreground=\"&#123;StaticResource PhoneForegroundBrush&#125;\">\n\n    &lt;Grid x:Name=\"LayoutRoot\" Background=\"&#123;StaticResource PhoneBackgroundBrush&#125;\">\n        &lt;Grid.RowDefinitions>\n            &lt;RowDefinition Height=\"Auto\"/>\n            &lt;RowDefinition Height=\"*\"/>\n        &lt;/Grid.RowDefinitions>\n\n        &lt;!--TitleGrid is the name of the application and page title-->\n        &lt;Grid x:Name=\"TitleGrid\" Grid.Row=\"0\">\n            &lt;TextBlock Text=\"MY APPLICATION\" x:Name=\"textBlockPageTitle\"\n                       Style=\"&#123;StaticResource PhoneTextPageTitle1Style&#125;\"/>\n            &lt;TextBlock Text=\"page title\" x:Name=\"textBlockListTitle\"\n                       Style=\"&#123;StaticResource PhoneTextPageTitle2Style&#125;\"/>\n        &lt;/Grid>\n\n        &lt;!--ContentGrid is empty. Place new content here-->\n        &lt;Grid x:Name=\"ContentGrid\" Grid.Row=\"1\">\n            &lt;TextBox Height=\"31\" HorizontalAlignment=\"Left\"\n                     Margin=\"6,29,0,0\" Name=\"textBox1\" Text=\"TextBox\"\n                     VerticalAlignment=\"Top\" Width=\"446\" />\n        &lt;/Grid>\n    &lt;/Grid>\n\n&lt;/phoneNavigation:PhoneApplicationPage></code></pre>\n<p>Как видно, это совершенно обычное приложение. При запуске этого приложения и переключения фокуса на поле ввода мы увидим стандартную виртуальную клавиатуру Windows Phone 7.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-input/0eb95de4/2@2x.png\"  alt=\"Ввод информации в приложениях Windows Phone 7\"/>\n\n\n\n\n<p>Давайте теперь поработаем с полем ввода и определим, что это поле предназначено для ввода телефонного номера. Для этого определим свойство <code>InpuScope</code> для поля ввода, указав ему значение <code>TelephoneNumber</code>. Свойство <code>InputScope</code> принимает значения из перечисления <code>InputScopeNameValue</code>. Значение <code>TelephoneNumber</code> как раз является одним из значений перечисления <code>InputScopeNameValue</code>.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;phoneNavigation:PhoneApplicationPage\n    x:Class=\"InputScope.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:phoneNavigation=\"clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    mc:Ignorable=\"d\" d:DesignWidth=\"480\" d:DesignHeight=\"800\"\n    FontFamily=\"&#123;StaticResource PhoneFontFamilyNormal&#125;\"\n    FontSize=\"&#123;StaticResource PhoneFontSizeNormal&#125;\"\n    Foreground=\"&#123;StaticResource PhoneForegroundBrush&#125;\">\n\n    &lt;Grid x:Name=\"LayoutRoot\" Background=\"&#123;StaticResource PhoneBackgroundBrush&#125;\">\n        &lt;Grid.RowDefinitions>\n            &lt;RowDefinition Height=\"Auto\"/>\n            &lt;RowDefinition Height=\"*\"/>\n        &lt;/Grid.RowDefinitions>\n\n        &lt;!--TitleGrid is the name of the application and page title-->\n        &lt;Grid x:Name=\"TitleGrid\" Grid.Row=\"0\">\n            &lt;TextBlock Text=\"MY APPLICATION\" x:Name=\"textBlockPageTitle\"\n                       Style=\"&#123;StaticResource PhoneTextPageTitle1Style&#125;\"/>\n            &lt;TextBlock Text=\"page title\" x:Name=\"textBlockListTitle\"\n                       Style=\"&#123;StaticResource PhoneTextPageTitle2Style&#125;\"/>\n        &lt;/Grid>\n\n        &lt;!--ContentGrid is empty. Place new content here-->\n        &lt;Grid x:Name=\"ContentGrid\" Grid.Row=\"1\">\n            &lt;TextBox Height=\"31\" HorizontalAlignment=\"Left\"\n                     Margin=\"6,29,0,0\" Name=\"textBox1\" Text=\"TextBox\"\n                     VerticalAlignment=\"Top\" Width=\"446\" >\n                &lt;TextBox.InputScope>\n                    &lt;InputScope Names=\"TelephoneNumber\" />\n                &lt;/TextBox.InputScope>\n            &lt;/TextBox>\n        &lt;/Grid>\n    &lt;/Grid>\n\n&lt;/phoneNavigation:PhoneApplicationPage></code></pre>\n<p>При запуске приложения и установке фокуса на поле ввода мы также увидим виртуальную клавиатуру, но в этот раз она примет несколько иной вид.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-input/fd709fcc/3@2x.png\"  alt=\"Ввод информации в приложениях Windows Phone 7\"/>\n\n\n\n\n<p>Как видно, добавлением одной строчки кода в разметке мы добились того, что виртуальная клавиатура существенно изменила свой внешний вид и функциональность. На самом деле перечисление <code>InputScopeNameValue</code> имеет несколько десятков всевозможных значений, которые изменяют функциональность виртуальной клавиатуры.</p>\n<p>Для того, чтобы увидеть каждый случай можно, конечно, изменять это значение и перезапускать приложение; однако, мы воспользуемся другим способом. Для начала с помощью Reflection получим всевозможные значения этого перечисления. После этого для каждого значения перечисления создадим новое поле ввода и определим для него соответствующее значение свойства <code>InputScope</code>. Разметка формы при этом изменится следующим образом.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;phoneNavigation:PhoneApplicationPage\n    x:Class=\"InputScope.MainPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:phoneNavigation=\"clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    mc:Ignorable=\"d\" d:DesignWidth=\"480\" d:DesignHeight=\"800\"\n    FontFamily=\"&#123;StaticResource PhoneFontFamilyNormal&#125;\"\n    FontSize=\"&#123;StaticResource PhoneFontSizeNormal&#125;\"\n    Foreground=\"&#123;StaticResource PhoneForegroundBrush&#125;\">\n\n    &lt;Grid x:Name=\"LayoutRoot\" Background=\"&#123;StaticResource PhoneBackgroundBrush&#125;\">\n        &lt;Grid.RowDefinitions>\n            &lt;RowDefinition Height=\"Auto\"/>\n            &lt;RowDefinition Height=\"*\"/>\n        &lt;/Grid.RowDefinitions>\n\n        &lt;!--TitleGrid is the name of the application and page title-->\n        &lt;Grid x:Name=\"TitleGrid\" Grid.Row=\"0\">\n            &lt;TextBlock Text=\"MY APPLICATION\" x:Name=\"textBlockPageTitle\"\n                       Style=\"&#123;StaticResource PhoneTextPageTitle1Style&#125;\"/>\n            &lt;TextBlock Text=\"page title\" x:Name=\"textBlockListTitle\"\n                       Style=\"&#123;StaticResource PhoneTextPageTitle2Style&#125;\"/>\n        &lt;/Grid>\n\n        &lt;!--ContentGrid is empty. Place new content here-->\n        &lt;Grid x:Name=\"ContentGrid\" Grid.Row=\"1\">\n            &lt;ScrollViewer BorderThickness=\"0\" Height=\"280\" VerticalAlignment=\"Top\">\n                &lt;StackPanel x:Name=\"Elements\">\n                    &lt;StackPanel.Resources>\n                        &lt;Style TargetType=\"TextBox\">\n                            &lt;Setter Property=\"Margin\" Value=\"10\"/>\n                        &lt;/Style>\n                    &lt;/StackPanel.Resources>\n                &lt;/StackPanel>\n            &lt;/ScrollViewer>\n        &lt;/Grid>\n    &lt;/Grid>\n\n&lt;/phoneNavigation:PhoneApplicationPage></code></pre>\n<p>При этом в момент создания и загрузки формы создадим следующий код. Здесь мы получаем все значения перечисления и для каждого значения создадим новое поле ввода и помещаем его на форму. При этом для каждого поля ввода задается текущее значение <code>InputScope</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">foreach (var inputName in typeof(InputScopeNameValue).\n  GetFields(BindingFlags.Public | BindingFlags.Static).Select(t => t.Name))\n&#123;\n    InputScopeNameValue val = (InputScopeNameValue)(Enum.Parse(typeof(InputScopeNameValue), inputName, true));\n\n    if (val > 0)\n    &#123;\n        Elements.Children.Add(new TextBox()\n        &#123;\n            Text = inputName,\n            InputScope = new System.Windows.Input.InputScope\n            &#123;\n                Names =\n            &#123;\n                new InputScopeName()\n                    &#123;\n                        NameValue = val\n                    &#125;\n            &#125;\n            &#125;\n        &#125;);\n    &#125;\n&#125;</code></pre>\n<p>При запуске приложения на форме будет созданы элементы управления. При установлении фокуса на каждое поле можно увидеть различные варианты виртуальной клавиатуры.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-input/b6a420ea/4@2x.png\"  alt=\"Ввод информации в приложениях Windows Phone 7\"/>\n\n\n\n\n<p>Наиболее интересные варианты виртуальной клавиатуры:</p>\n<p><strong>Url:</strong> адаптировано для ввода адреса интернет ресурса, характерно, что в нижней части есть кнопка для ввода домена (при нажатии и удержании можно увидеть другие домены) и кнопка перехода к ресурсу (справа снизу).</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-input/2362140b/5@2x.png\"  alt=\"Ввод информации в приложениях Windows Phone 7\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-input/dd0a96b7/6@2x.png\"  alt=\"Ввод информации в приложениях Windows Phone 7\"/>\n\n\n\n\n<p><strong>EmailSmtpAddress:</strong> адаптировано для ввода адреса электронной почты, характерно, что в нижней части вынесен символ <code>@</code> и точка, а также есть отдельная кнопка для ввода домена.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-input/a15c8cda/7@2x.png\"  alt=\"Ввод информации в приложениях Windows Phone 7\"/>\n\n\n\n\n<p><strong>PersonalFullName:</strong> адаптировано для ввода имени.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-input/0ba91c2a/8@2x.png\"  alt=\"Ввод информации в приложениях Windows Phone 7\"/>\n\n\n\n\n<p><strong>PostalCode:</strong> адаптировано для ввода кода города, характерно, что по умолчанию используется раскладка для цифр и знаков, но можно переключить в режим ввода букв. Аналогичная раскладка используется и для других способов ввода, использующих цифры.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-input/4d5f0359/9@2x.png\"  alt=\"Ввод информации в приложениях Windows Phone 7\"/>\n\n\n\n\n<p><strong>TelephoneNumber:</strong> адаптировано для ввода номера телефона.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-input/21fc12e6/10@2x.png\"  alt=\"Ввод информации в приложениях Windows Phone 7\"/>\n\n\n\n\n<p><strong>Text:</strong> адаптировано для ввода текста, характерно, что есть отдельная кнопка для ввода смайлов (которые зачастую используется в теле электронного письма или SMS).</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-input/178623b4/11@2x.png\"  alt=\"Ввод информации в приложениях Windows Phone 7\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-input/216c4e64/12@2x.png\"  alt=\"Ввод информации в приложениях Windows Phone 7\"/>\n\n\n\n\n<p><strong>Chat:</strong> адаптировано для использования в чатах, аналогично предыдущему способу, но здесь используются графические смайлы.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-input/8fbfb018/13@2x.png\"  alt=\"Ввод информации в приложениях Windows Phone 7\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-input/1f8642bb/14@2x.png\"  alt=\"Ввод информации в приложениях Windows Phone 7\"/>\n\n\n\n\n<p>Кроме этих режимов, перечисление <code>InputScopeNameValue</code> содержит еще огромное количество значений. На данный момент для некоторых значений вид виртуальной клавиатуры совпадает. Как это будет выглядеть в финальной версии пока не понятно, но большое количество различных значений вызывает надежду на то, что различных режимов виртуальной клавиатуры будет еще больше.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/windows-phone-input/d23983bc/windows_phone-input@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2010/powershell-remote-session/",
            "url": "https://blog.zwezdin.com/2010/powershell-remote-session/",
            "title": "Работа с удаленными сессиями Powershell",
            "date_published": "2010-04-19T12:00:00.000Z",
            "content_html": "<p>Не так давно пришлось потратить немного времени на настройку удаленного доступа через Powershell. Мне такой подход кажется очень хорошей альтернативной Remote Desktop Services в ряде случаев (перезапустить сервис на удаленном хостинге VDS, сделать резервные копии, посмотреть состояние системы и т.д.).</p>\n<p>Возможность создания удаленных сеансов Powershell появилась в версии 2. Для этого используется командлет <code>Enter-PSSession</code>/<code>Invoke-Command</code>. Однако, перед их использованием следует подготовить среду.</p>\n<p>Что делаем <strong>на сервере</strong>:</p>\n<p><strong>Шаг 1:</strong> Открываем консоль Powershell и разрешаем удаленные сессии командлетом <code>Enable-PSRemoting</code> с ключом <code>Force</code>.</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">Enable-PSRemoting -Force</code></pre>\n<p><strong>Шаг 2:</strong> Убеждаемся в том, что служба WinRM запущена.</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">Start-Service WinRM</code></pre>\n<p><strong>Шаг 3:</strong> Настраиваем правила в брэндмауэре, чтобы входящие подключения были возможны.</p>\n<p>На компьютере, который будет использоваться как <strong>клиентский</strong> тоже необходимо выполнить несколько действий:</p>\n<p><strong>Шаг 1:</strong> Разрешить подключение к удаленным узлам. Для доступа к любым узлам можно воспользоваться следующей конструкцией:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">Set-Item wsman:\\localhost\\client\\trustedhosts * -Force</code></pre>\n<p><strong>Шаг 2:</strong> Убедиться, что брэндмауэр не блокирует исходящие соединения.</p>\n<p>Теперь для подключения к удаленному узлу через Powershell можно осуществлять так:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">Enter-PSSession 192.168.1.160 -Credential VMNAME\\User</code></pre>\n<p>Значения <code>192.168.1.160</code> и <code>VMNAME\\User</code> необходимо заменить адресом удаленного узла и именем пользователя Windows на сервере.</p>\n<p>Теперь удаленный доступ через Powershell работает. Однако, есть еще один ньюанс. Возможно, кто-либо из вас пользуется <a href=\"http://technet.microsoft.com/en-us/library/ee692764.aspx\">профилями в Powershell</a>. Профили – это специальные скрипты, которые запускаются при запуске самой консоли. Здесь, например, можно определить все необходимые алиасы и выполнить предварительные действия.</p>\n<p>Проблема заключается в том, что профили не запускаются при использовании удаленных сеансов. Решить это можно путем использования разных конфигураций подключения. Для этого первоначально необходимо зарегистрировать конфигурацию на удаленном сервере. Сделать это можно путем выполнения коммандлета <code>Register-PSSessionConfiguration</code>. При этом каждой конфигурации присваивается имя. Для каждой конфигурации можно задать путь до скрипта, который будет выполняться при старте сессии.</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">Register-PSSessionConfiguration -name Config1 -startupScript c:\\scripts\\Startup.ps1</code></pre>\n<p>После этого, при подключении к удаленному узлу, при использовании коммандлета <code>Enter-PSSession</code> следует указать имя конфигурации.</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">Enter-PSSession 192.168.1.160 -ConfigurationName Config1 -Credential VMNAME\\User</code></pre>\n<p>Теперь можно не тратить ресурсы сервера на создание сессии подключения через Remote Desktop Services и удаленно управлять сервером, используя Powershell.</p>",
            "tags": [
                "Powershell"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/powershell-remote-session/d15d7dfc/powershell_remote-session@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2010/windows-mobile-context-menu/",
            "url": "https://blog.zwezdin.com/2010/windows-mobile-context-menu/",
            "title": "Всплывающее меню для Windows Phone Classic",
            "date_published": "2010-04-17T12:00:00.000Z",
            "content_html": "<p>Все мы видели контекстные меню в <strong>Windows Mobile</strong>. Несмотря на то, что они более-менее функциональные, работать с ними зачастую не очень удобно. В то же время общие тренды говорят о том, что всплывающее меню должно занимать весь экран и позволять работать пальцами. Такие меню уже есть в других мобильных платформах (например, Android). В <strong>Windows Phone 7</strong> также реализуются крупные меню для приложений. Давайте попробуем сделать удобное с точки зрения использование контекстное меню для Windows Phone Classic. Для того, чтобы было понятно что именно мы будем делать в этой статье, я приведу несколько снимков экранов того, что должно получиться в результате.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-mobile-context-menu/d92ea2ec/1@2x.png\"  alt=\"Всплывающее меню для Windows Phone Classic\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-mobile-context-menu/18c45a5e/2@2x.png\"  alt=\"Всплывающее меню для Windows Phone Classic\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-mobile-context-menu/96f5785b/3@2x.png\"  alt=\"Всплывающее меню для Windows Phone Classic\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-mobile-context-menu/63592def/4@2x.png\"  alt=\"Всплывающее меню для Windows Phone Classic\"/>\n\n\n\n\n<p>Т.о. при отображении меню общий фон затемняется и поверх него появляется всплывающее меню с ориентированными под палец кнопками. Давайте попробуем сделать такую реализацию.</p>\n<p>Для достижения поставленной цели мы будем придерживаться следующего порядка действий:</p>\n<ol>\n<li>При попытке отобразить всплывающее меню предварительно делаем снимок экрана;</li>\n<li>Слегка затемняем получившийся снимок;</li>\n<li>Создаем новую форму, фон которой будет содержать затемненный список;</li>\n<li>Рисуем на этой форме прямоугольник нужных размеров, а также разделители элементов (горизонтальные линии);</li>\n<li>Добавляем на форму нужно количество кнопок;</li>\n<li>Отображаем форму в полноэкранном режиме;</li>\n<li>При нажатии на какую-либо из кнопок закрываем форму.</li>\n</ol>\n<p>Как видим, последовательность действий простая. Понятно, что затемнение фона в данном случае – это только иллюзия (например, если будет изменяться время, то в «затемненном» варианте оно не изменится), но это работает достаточно успешно.</p>\n<p>Итак, приступим. Для реализации описанной логики создадим новый класс, унаследованный от базового класса <code>Form</code>. Этот класс будет реализовывать всю логику нового всплывающего меню. Реализуем в этом объекте также интерфейс <code>IDisposable</code> (поскольку дочерние элементы <code>Bitmap</code>, которые нам пригодятся требует освобождения памяти).</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class PopupForm : Form, IDisposable\n&#123;\n  // ..\n&#125;</code></pre>\n<p>Для того, чтобы передать в форму информацию о кнопках создадим объект, описывающий эту кнопку. Пусть этот объект содержит название и заголовок кнопки, значок (если необходимо) и состояние (включен/выключен). Тогда описание такой кнопки будет очень простым.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class ButtonDescription\n&#123;\n  public ButtonDescription()\n  &#123;\n    Checked = false;\n    CanBeChecked = false;\n    Icon = null;\n  &#125;\n\n  public string Name &#123; get; set; &#125;\n  public string Title &#123; get; set; &#125;\n  public bool CanBeChecked &#123; get; set; &#125;\n  public bool Checked &#123; get; set; &#125;\n  public Bitmap Icon &#123; get; set; &#125;\n&#125;</code></pre>\n<p>Теперь при создании нашего всплывающего меню в конструкторе передадим коллекцию таких кнопок и сохраним эту коллекцию в приватном поле. Также установим в конструкторе стиль окна.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public PopupForm(ButtonDescription[] buttons)\n&#123;\n  WindowState = FormWindowState.Maximized;\n  FormBorderStyle = FormBorderStyle.None;\n\n  _buttons = buttons;\n&#125;</code></pre>\n<p>Следующим шагом необходимо получить снимок экрана и затемнить его. Для получения снимка воспользуемся функцией <code>BitBlt</code> из системной библиотеки <code>coredll.dll</code>. Для затемнения изображения воспользуемся функцией <code>AlphaBlend</code> из той же библиотеки. Я не буду подробно рассматривать последовательность действий для этого шага. Всю последовательность действий иллюстрирует метод, код которого приведен ниже.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[DllImport(\"coredll.dll\")]\nextern public static Int32 AlphaBlend(IntPtr hdcDest, Int32 xDest,\n                Int32 yDest, Int32 cxDest, Int32 cyDest,\n                IntPtr hdcSrc, Int32 xSrc, Int32 ySrc,\n                Int32 cxSrc, Int32 cySrc,\n                BlendFunction blendFunction);\n\n[DllImport(\"coredll.dll\")]\npublic static extern bool BitBlt(IntPtr hObject, int nXDest,\n                int nYDest, int nWidth,\n                int nHeight, IntPtr hObjSource, int nXSrc,\n                int nYSrc, TernaryRasterOperations dwRop);\n\n\n[DllImport(\"coredll.dll\")]\npublic static extern IntPtr GetDC(IntPtr hwnd);\n\n[DllImport(\"coredll.dll\")]\npublic static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);\n\npublic static Bitmap GenerateSnapshot(byte alpha)\n&#123;\n  Rectangle rect = Screen.PrimaryScreen.Bounds;\n  var result = new Bitmap(rect.Width, rect.Height);\n\n  var deviceContext = PlatformAPIs.GetDC(IntPtr.Zero);\n\n  try\n  &#123;\n    using (Graphics resultGx = Graphics.FromImage(result))\n    &#123;\n      var screenHdc = deviceContext;\n      var resultHdc = resultGx.GetHdc();\n\n      PlatformAPIs.BitBlt(screenHdc, 0, 0,\n        rect.Width, rect.Height, deviceContext, 0, 0,\n        TernaryRasterOperations.SRCCOPY);\n\n      if (alpha &lt;= 255)\n      &#123;\n        PlatformAPIs.AlphaBlend(resultHdc, 0, 0,\n            result.Width, result.Height, screenHdc,\n            rect.Left, rect.Top, rect.Width, rect.Height,\n                    new BlendFunction\n                    &#123;\n                        BlendOp = 0,\n                        BlendFlags = 0,\n                        SourceConstantAlpha = alpha,\n                        AlphaFormat = 0\n                    &#125;);\n      &#125;\n\n      resultGx.ReleaseHdc(resultHdc);\n    &#125;\n  &#125;\n  finally\n  &#123;\n    PlatformAPIs.ReleaseDC(IntPtr.Zero, deviceContext);\n  &#125;\n\n  return result;\n&#125;</code></pre>\n<p>Теперь у нас есть метод, который делает снимок экрана и затемняет его. При отображении формы получим это изображение и сохраним его в закрытом поле. Для этого переопределим методы Show и <code>ShowDialog</code> формы, а также создадим метод <code>ShowMenu</code>, который будет возвращать имя выбранной кнопки.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private Image _formSnapshot;\n\npublic new void Show()\n&#123;\n  ShowMenu();\n&#125;\n\npublic new void ShowDialog()\n&#123;\n  ShowMenu();\n&#125;\n\npublic string ShowMenu()\n&#123;\n  UpdateBackground(PlatformAPIs.GenerateSnapshot(true));\n\n  // ...\n&#125;\n\nprivate void UpdateBackground(Image formSnapshot)\n&#123;\n  if (_formSnapshot != null)\n  &#123;\n    _formSnapshot.Dispose();\n  &#125;\n\n  _formSnapshot = formSnapshot;\n\n  // ...\n&#125;</code></pre>\n<p>Мы помним, что для отображения фона формы нам нужно затемненное изображение, поверх которого нарисован прямоугольник (для меню). Поэтому в момент заданий фона формы нарисуем этот прямоугольник и сохраним изображение фона.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void UpdateBackground(Image formSnapshot)\n&#123;\n  // ..\n\n  RefreshFormBackground();\n&#125;\n\nprivate void RefreshFormBackground()\n&#123;\n  // удаляем старое изображение, если есть\n  if (_backgroundImage != null)\n  &#123;\n    _backgroundImage.Dispose();\n    _backgroundImage = null;\n  &#125;\n\n\n  // подсчитываем количество кнопок и размер экрана\n  var itemsCount = _buttons.Length;\n  var currentSize = Screen.PrimaryScreen.Bounds;\n\n  // создаем чистое изображения для фона\n  var backgroundImage = new Bitmap(currentSize.Width, currentSize.Height);\n\n  // генерируем фон\n  using (var backgroundImageGraphics = Graphics.FromImage(backgroundImage))\n  &#123;\n    // наносим скриншот экрана, если есть\n    if (_formSnapshot != null)\n    &#123;\n      backgroundImageGraphics.DrawImage(_formSnapshot, 0, 0);\n    &#125;\n\n    // наносим фон меню\n    using (var menuBackgroundImage = GenerateMenuBackground(itemsCount,\n                                      ItemWidth, ItemHeight, itemsCount))\n    &#123;\n      backgroundImageGraphics.DrawImage(menuBackgroundImage,\n                              currentSize.Width / 2 - menuBackgroundImage.Width / 2,\n                              currentSize.Height / 2 - menuBackgroundImage.Height / 2);\n  &#125;\n  &#125;\n\n  // сохраняем результат\n  _backgroundImage = backgroundImage;\n&#125;\n\nprivate static Bitmap GenerateMenuBackground(int itemsCount, int itemWidth,\n                                             int itemHeight, int borderRadius)\n&#123;\n  // чистое изображение\n  var result = new Bitmap(itemWidth, itemHeight * itemsCount);\n\n  using (var gx = Graphics.FromImage(result))\n  &#123;\n    // наносим прямоугольник со скругленными краям\n    gx.DrawRoundedRectangle(Color.White, Color.White,\n      new Rectangle(0, 0, itemWidth, itemHeight * itemsCount),\n      new Size(borderRadius, borderRadius));\n\n    // наносим разделители\n    var dividerPen = new Pen(Color.Silver);\n\n    for (int i = 1; i &lt; itemsCount; i++)\n    &#123;\n      gx.DrawLine(dividerPen, borderRadius, itemHeight * i,\n                  itemWidth - borderRadius, itemHeight * i);\n    &#125;\n  &#125;\n\n  return result;\n&#125;</code></pre>\n<p>Как видно, мы используем метод <code>DrawRoundedRectangle</code> для генерации прямоугольника со скругленными краями. На самом деле это – метод расширения для класса <code>Graphics</code>, разработанный Алексом Яхниным в его библиотеке <a href=\"http://code.msdn.microsoft.com/uiframework\">UI Framework</a>. Теперь при отрисовке фона формы будем использовать заранее подготовленное изображение.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">protected override void OnPaintBackground(PaintEventArgs e)\n&#123;\n  if (_backgroundImage != null)\n  &#123;\n    e.Graphics.DrawImage(_backgroundImage, 0, 0);\n  &#125;\n  else\n  &#123;\n    base.OnPaintBackground(e);\n  &#125;\n&#125;</code></pre>\n<p>Теперь у нас есть форма, которая делает снимок экрана, затемняет его и выводит на экран форму с прямоугольником. Осталось только добавить кнопки на эту форму. Все кнопки у нас будут графическими. Поэтому стандартные кнопки нам не подходят (по вашему вкусу здесь вы можете добавить свои элементы управления). Для простоты создадим свои кнопки, которые имеют несколько изображений для разных состояний. Для этого создадим класс, унаследованный от элемента управления <code>Control</code>, который принимает в конструкторе текст и другие параметры и генерирует три изображения (обычное состояние, нажатое состояние, отмеченное состояние). Эти изображения будут использоваться в момент отрисовки элемента управления. Дальше обработам события <code>OnMouseUp</code> и <code>OnMouseDown</code> и будем переключать состояние, управляющее выбором одного из трех изображений. Я не буду приводить полный исходный код этого элемента управления (чтобы не загромождать пространство). При желании вы можете посмотреть его в приложенных исходных кодах.</p>\n<p>Теперь когда у нас есть элемент управления для кнопки, рассчитаем размеры экрана и разместим на форме все эти элементы управления. При этом, если на форме были какие-то еще элементы управления, то удалим их.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void RefreshButtonControls()\n&#123;\n  // очистка старых элементов управления\n  var oldControls = Controls.OfType&lt;IDisposable>().ToArray();\n  Controls.Clear();\n  foreach (IDisposable control in oldControls)\n  &#123;\n    control.Dispose();\n  &#125;\n\n  // создание элементов управления и размещение на форме\n  var itemsCount = _buttons.Length;\n  int dx = Screen.PrimaryScreen.Bounds.Width / 2\n                    - ItemWidth / 2;\n  int dy = Screen.PrimaryScreen.Bounds.Height / 2\n                    - (ItemHeight * itemsCount) / 2;\n\n  for (int i = 0; i &lt; itemsCount; i++)\n  &#123;\n    var buttonInfo = _buttons[i];\n    var btn = new PopupButton(_buttons[i].Name, buttonInfo.Title,\n                            MenuFont, ItemWidth - 10, ItemHeight - 10,\n                            Color.Black, Color.White, PressedColor, CheckedColor,\n                            buttonInfo.CanBeChecked, buttonInfo.Checked,\n                            buttonInfo.Icon)\n                            &#123; Left = dx + 5, Top = i * ItemHeight + 5 + dy &#125;;\n    btn.Click += delegate(object sender, EventArgs e)\n    &#123;\n      foreach (PopupButton control in\n                Controls.OfType&lt;PopupButton>())\n      &#123;\n        if (control != sender)\n        &#123;\n          control.IsChecked = false;\n\n        &#125;\n      &#125;\n\n      // сохраняем результат\n      Result = ((PopupButton)sender).Name;\n\n      // закрываем форму\n      Close();\n    &#125;;\n    Controls.Add(btn);\n  &#125;\n&#125;</code></pre>\n<p>Теперь у нас есть форма, которая в качестве фона имеет затемненный снимок экрана и отрисованный на ней прямоугольник, и содержит набор кнопок в качестве элементов управления контекстного меню. При нажатии на любой кнопке форма скрывается. Результат работы такого меню будет выглядеть следующим образом.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-mobile-context-menu/96f5785b/5@2x.png\"  alt=\"Всплывающее меню для Windows Phone Classic\"/>\n\n\n\n\n<p>Такими относительно несложными шагами мы добились создания эффектного контекстного меню, которое может успешно заменить стандартное контекстное меню. Понятно, что приведенный пример – это только небольшая демонстрация. При желании его можно усовершенствовать и добавить возможности листания, более сложные элементы управления, вложенности и т.д.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/windows-mobile-context-menu/6e5feb1f/windows_mobile-context-menu@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2010/windows-mobile-background-services/",
            "url": "https://blog.zwezdin.com/2010/windows-mobile-background-services/",
            "title": "Фоновые службы в Windows Phone Classic",
            "date_published": "2010-04-14T12:00:00.000Z",
            "content_html": "<p>Несмотря на то, что на рынке начинают появляться новые тренды в мобильных операционных системах (в т.ч. Windows Phone 7), предыдущие версии Windows Phone (WM 5.x, WM 6.x) остаются и еще долго будут оставаться актуальными операционными системами для многих пользователей и разработчиков. Поэтому на этот раз я бы хотел рассказать о том, как можно создавать свои собственные фоновые службы в Windows Phone Classic на основе .NET Compact Framework.</p>\n<p><strong>Фоновая служба</strong> — это некоторое приложение, которое запускается при запуске операционной системы и остается работать в фоне на протяжении всей работы устройства. При этом фоновая служба не имеет какого-либо пользовательского интерфейса и не отображается в диспетчере задач.</p>\n<p>К сожалению, .NET Compact Framework не содержит готовых механизмов по созданию подобных служб. При этом создавать фоновые службы можно используя только неуправляемый код. Ограничение состоит в том, что для управляемой среды просто не реализована эта функциональность. Однако, хорошая новость заключается в том, что энтузиасты <a href=\"http://bansky.net/\">Pavel Bánský</a> и <a href=\"http://www.codeplex.com/site/users/view/PeterNowak\">Peter Nowak</a> создали собственную обертку для неуправляемых интерфейсов и поместили ее в специальную библиотеку с открытым исходным кодом — <a href=\"http://managedserviceswm.codeplex.com/\">Managed Services for Windows Mobile</a>. Эта библиотека позволяет создавать фоновые службы для Windows Phone Classic на основе управляемого кода в рамках .NET Compact Framework.</p>\n<h2>Создание простой службы</h2><p>Давайте попробуем создать небольшое приложение, которое будет следить за поступлением новых SMS-сообщений и записывать в специальный журнал все моменты поступления нового сообщение.</p>\n<p>Первое, что необходимо сделать – это создать новый проект и добавить ссылку на сборку <a href=\"http://managedserviceswm.codeplex.com/\">ManagedService.dll</a>. Поскольку приложение будет являться фоновой службой, то для него не нужен пользовательский интерфейс. Поэтому при создании проекта укажем, что необходимо создать консольное приложение.</p>\n<p>После создания приложения следует создать новый класс и определить для него в качестве наследника класс <code>ServiceApplicaiton</code> (доступный в сборке <code>ManagedService.dll</code>). Этот класс, по сути, и будет являться фоновой службой. Необходимо чтобы в приложении был единственный экземпляр этого класса. Мы не будем использовать изощренные архитектурные конструкции и для простоты примера просто используем <code>Singleton</code>. Для этого определим закрытый конструктор и статическое поле для хранения созданного экземпляра этого класса.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class BackgroundService : ServiceApplication\n&#123;\n  private static readonly BackgroundService _service =\n    new BackgroundService();\n\n  public static BackgroundService Service\n  &#123; get &#123; return _service; &#125; &#125;\n\n  private BackgroundService()\n  &#123;\n\n  &#125;\n&#125;</code></pre>\n<p>Прежде чем наш сервис начнет работать следует выполнить еще несколько важных действий в конструкторе:</p>\n<ul>\n<li>определить идентификатор сервиса (для того, чтобы операционная система могла отличать этот сервис от других сервисов);</li>\n<li>определить имя сервиса;</li>\n<li>зарегистрировать сервис (фактически, сохранить информацию о сервисе в реестре Windows).</li>\n</ul>\n<p>Эти действия выполняются с помощью конструкций базового класса.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private BackgroundService()\n&#123;\n  ServiceGuid = new Guid(\"EA6E9740-4698-11DF-88EE-BF5256D89593\");\n  ServiceName = \"SmsLogger\";\n  RegisterService();\n&#125;</code></pre>\n<p>Последнее, что осталось сделать – это запускать данный сервис при запуске исполняемого файла. Для этого в методе <code>Main</code> следует получить экземпляр класса для этого сервиса и вызвать метод <code>Start</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">namespace SmsLogger\n&#123;\n  class Program\n  &#123;\n    static void Main(string[] args)\n    &#123;\n      BackgroundService.Service.Start();\n    &#125;\n  &#125;\n&#125;</code></pre>\n<p>Этого достаточно для того, чтобы наш сервис запустился и начал работать в фоновом режиме.</p>\n<h2>Внешние события</h2><p>После успешного создания сервиса нужно определить для него какую-то логику (<em>зачем нужен сервис, который ничего не делает?</em>). Для этого в конструкторе можно инициировать новый поток, который будет выполнять какие-либо действия, либо подписаться на уведомления от системных событий, происходящих в системе. Чтобы слегка упростить пример воспользуемся вторым подходом и подпишемся на системное событие, уведомляющее о том, что изменилось количество непрочтенных SMS. Для этого добавим ссылки на две стандартные сборки – <code>Microsoft.WindowsMobile</code> и <code>Microsoft.WindowsMobile.Status</code>, после чего будем использовать объект <code>SystemState</code> для получения системных уведомлений. Для обработки уведомления сохраним объект <code>SystemState</code> в закрытом поле, подпишемся на событие изменения и создадим обработчик события.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private SystemState state =\n  new SystemState(SystemProperty.MessagingSmsUnread);\n\nprivate BackgroundService()\n&#123;\n  // ...\n\n  state.Changed += MessagingSmsUnreadStateChanged;\n&#125;\n\nvoid MessagingSmsUnreadStateChanged(object sender,\n  ChangeEventArgs args)\n&#123;\n  WriteLog(String.Format(\"Total unread sms is &#123;0&#125;\",\n                          args.NewValue));\n&#125;</code></pre>\n<p>Теперь, при изменении количества непрочтенных SMS будет срабатывать обработчик события. Как видно, в нем мы вызываем метод для записи информации в некоторый лог. Для того, чтобы проект запустился определим этот метод. Для простоты, пусть он записывает все события в файл, находящийся в корневой папке.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private static void WriteLog(string text)\n&#123;\n  using (var fs = new FileStream(@\"\\smslog.txt\", FileMode.Append))\n  &#123;\n    using (var sw = new StreamWriter(fs))\n    &#123;\n      sw.WriteLine(\"&#123;0&#125;: &#123;1&#125;\", DateTime.Now.ToString(), text);\n    &#125;\n  &#125;\n&#125;</code></pre>\n<p>Теперь, при запуске нашей службы и поступлении новых SMS на телефон, будет срабатывать обработчик и сохранять данные в системный лог.</p>\n<h2>Автоматический запуск</h2><p>После создания сервиса и определения логики следует указать операционной системе, что сервис необходимо запускать при старте приложения. По аналогии с настольной версией Windows, в Windows Phone автоматический запуск приложений осуществляется путем указания пути к приложению в соответствующей ветке реестра.</p>\n<p>Для того, чтобы сервисом можно было управлять и отслеживать его состояние, в рамках проекта <code>Managed Services for Windows Mobile</code> было создано специальное приложение, которое управляет запуском сервисов. Идея приложения простая — при запуске оно само себя помещает в автозапуск. После этого оно сканирует ветку реестра <code>HKEY_LOCAL_MACHINE\\Software\\ManagedServices</code> и запускает все необходимые службы.</p>\n<p>В это секции создаются секции (имя которой соответствует идентификатору службы), в которых указывается имя сервиса, путь для загрузки и другая информация.К счастью, базовый класс <code>ServiceApplication</code> уже содержит методы <code>RegisterService</code> и <code>UnRegisterService</code>, которые выполняют всю работу по регистрации службы в реестре. Поэтому работать с реестром вручную не придется.</p>\n<p>Таким образом, для запуска служб необходимо установить это приложение (ссылка ниже). Вы также сами можете прописать службы для автозагрузки минуя эту утилиту, для этого вам вручную придется создать секцию в разделе <code>HKLM\\init</code> и контролировать её.</p>\n<p>Мы выполним регистрацию службы немного позднее, когда разберемся с возможностью взаимодействия службы с другими приложениями.</p>\n<h2>Управление состоянием службы</h2><p>Аналогично службе Windows, службы для Windows Phone имеют несколько состояний:</p>\n<ul>\n<li>запускается (<code>Starting</code>);</li>\n<li>исполняется (<code>Running</code>);</li>\n<li>приостановка (<code>Suspending</code>);</li>\n<li>приостановлено (<code>Suspended</code>);</li>\n<li>возобновление (<code>Resuming</code>);</li>\n<li>завершение (<code>Exiting</code>);</li>\n<li>завершено (<code>Exited</code>).</li>\n</ul>\n<p>Узнать текущее состояние службы можно из свойства <code>ServiceState</code> базового класса службы <code>ServiceApplication</code>. Кроме того, в базовом классе присутствует два события, позволяющих отловить моменты изменения текущего состояния службы – <code>OnServiceStateChanged</code> и <code>OnServiceExiting</code>. Как следует из названия, первое событие генерируется всякий раз, когда изменяется текущее состояние сервиса, а второе – в момент завершения службы. Для примера давайте сохранять текущее состояние службы в лог при его изменении. Для этого в конструкторе подпишемся на соответствующее событие и создадим обработчик.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private BackgroundService()\n&#123;\n  // ..\n\n  OnServiceStateChanged += ServiceStateChanged;\n&#125;\n\nvoid ServiceStateChanged(ServiceStateEventArgs serviceStateEventArgs)\n&#123;\n  WriteLog(String.Format(\"Current stat of servce is &#123;0&#125;\",\n    serviceStateEventArgs.ServiceState));\n&#125;</code></pre>\n<p>Для управления состоянием службы в базовом классе <code>ServiceApplication</code> существует набор методов:</p>\n<ul>\n<li><code>Start</code></li>\n<li><code>Suspend</code></li>\n<li><code>Resume</code></li>\n<li><code>Quit</code></li>\n</ul>\n<p>Эти методы позволяют управлять работой службы. Кроме того, в базовом классе они определены как виртуальные. Это означает, что их можно переопределить непосредственно в классе службы и задать какое-то собственное поведение. Однако, после (или до) выполнения ваших действий, управление все-таки следует передать методу, определенному в базовом классе, иначе служба не запустится/не остановится/и т.д.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public override void Start()\n&#123;\n  // do something\n\n  base.Start();\n\n  // do something\n&#125;</code></pre>\n<p>На самом деле вы можете изменять состояние свойства <code>ServiceState</code> самостоятельно в процессе работы службы. Однако, это уже делает базовый класс <code>ServiceApplication</code>, поэтому об этом можно не заботиться.</p>\n<h2>Пользовательский интерфейс</h2><p>Практически каждая фоновая служба может (и должна) иметь приложение с пользовательским интерфейсом, которое будет управлять работой службы. Например, если фоновая служба ведет запись GPS-трека, то это приложение может управлять записью (начать, остановить), а также запускать или останавливать работу фоновой службы.</p>\n<p>С точки зрения фоновой службы, приложение, управляющее её работой является отдельным приложением, оно запускается отдельным процессом и, по сути, не имеет каких-то общих точек соприкосновения со службой. Взаимодействие этого приложения и фоновой службы происходит посредством обмена сообщениями Windows.</p>\n<p>Получить список служб, установленных на устройстве можно путем считывания всех значений из ключа реестра <code>HKEY_LOCAL_MACHINE\\Software\\ManagedServices</code>. Для взаимодействия с фоновой службой потребуется знать только её идентификатор. Если приложение знает идентификатор службы, с которой будет работать (что бывает зачастую), то ему не потребуется считывать соответствующие ключи реестра.</p>\n<p>Согласно <a href=\"http://msdn.microsoft.com/en-US/library/microsoft.windowsce.forms.messagewindow(VS.80).aspx\">документации</a>, для определения сообщения мы можем использовать идентификаторы, значения которых больше, чем <code>0x400</code>. В базовом классе уже определено несколько стандартных сообщений.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public const int UM_QUIT_SERVICE = 6028;\npublic const int UM_RESUME_SERVICE = 6027;\npublic const int UM_START_SERVICE = 6025;\npublic const int UM_SUSPEND_SERVICE = 6026;\npublic const int WM_USER = 1024;</code></pre>\n<p>Название этих сообщений говорит само за себя. Эти сообщения обрабатываются базовым классом и создавать для них собственные обработчики не нужно.</p>\n<p>Для того, чтобы добавить обработчик для собственного события в конструкторе сервиса следует вызвать метод <code>RegisterMessage</code> и передать ему код сообщения. После этого следует подписаться на событие <code>OnRegisteredMessage</code> и определить логику обработки для этих сообщений.</p>\n<p>Давайте определим сообщение, которое будет очищать лог файл. Для этого определим код соответствующего сообщения и зададим для него обработчик.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private const int UM_CLEAR = WM_USER + 5010;\n\nprivate BackgroundService()\n&#123;\n  // ..\n\n  RegisterMessage(UM_CLEAR);\n\n  OnRegisteredMessage += OnRegisteredMessageProcessing;\n&#125;\n\nvoid OnRegisteredMessageProcessing(ref Message message)\n&#123;\n  switch (message.Msg)\n  &#123;\n    case UM_CLEAR:\n      File.Delete(@\"\\smslog.txt\");\n      break;\n  &#125;\n&#125;</code></pre>\n<p>Теперь при поступлении сообщения с кодом <code>6034</code> (<code>WM_USER + 5010</code>) это сообщение будет обработано и файл будет удален.</p>\n<p>Осталось разработать приложение с пользовательским интерфейсом, которое будет управлять фоновой службой. Для этого создадим новое приложение и разместим на нем кнопку <code>Clear Log</code>. Для отправки сообщений Windows создадим небольшой класс, позволяющий выполнить это действие. Я не буду приводить код этого класса, его вы можете увидеть в исходных кодах, приложенных к описанию. Обработчик кнопки для очистки лога будет выглядеть следующим образом.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private const int WM_USER = 1024;\nprivate const int UM_CLEAR = WM_USER + 5010;\n\nprivate void ClearLogButton_Click(object sender, EventArgs e)\n&#123;\n  MessageHelper.SendMessage(\"ea6e9740469811df88eebf5256d89593\",\n    UM_CLEAR);\n&#125;</code></pre>\n<p>Теперь при нажатии на кнопку (при запущенной службе) сообщение будет отправлено службе и файл с логами будет удален.</p>\n<h2>Итого</h2><p>Таким образом, мы рассмотрели процесс создания фоновых сервисов для Windows Phone Classic в рамках .NET Compact Framework. Несмотря на то, что поддержка таких служб не поддерживается изначально в .NET Compact Framework, существуют обертки, которые позволяют с относительно небольшими усилиями такие службы создавать.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/windows-mobile-background-services/e25851f9/windows_mobile-background-services@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2010/visual-studio-developer-contest/",
            "url": "https://blog.zwezdin.com/2010/visual-studio-developer-contest/",
            "title": "Конкурс для разработчиков, использующих в своей работе Visual Studio",
            "date_published": "2010-04-08T12:00:00.000Z",
            "content_html": "<p>За последние несколько месяцев у меня накопилось много всяких интересных вещей (в т.ч. полученных в рамках IT-bonus), которые не жалко подарить хорошим людям. Однако, дарить это случайным образом не интересно и не по-спортивному. Так родилась идея провести небольшой конкурс среди разработчиков и IT-профессионалов. Поскольку совсем скоро нас ожидает запуск Visual Studio 2010, то было решено посвятить конкурс именно этой теме.</p>\n<p>Итак, идея конкурса состоит в следующем. Каждый кто желает участвовать в конкурсе может написать небольшую заметку у себя в блоге на тему разработки в Visual Studio. В этой заметке обязательно должна присутствовать ссылка на это сообщение (для того, чтобы другие разработчики также узнали о существовании конкурса). После этого в комментариях к этому сообщению следует оставить ссылку на свою заметку (комментарии публиковаться не будут до окончания конкурса). В момент подведения итогов я опубликую ссылки на все статьи. Для того, чтобы избежать дублирования статей (чтобы участники не “перенимали” идеи друг у друга), при появлении одинаковых статей, в конкурсе будет участвовать только та, чья ссылка была добавлена сюда раньше.</p>\n<h2>О чем писать</h2><p>Мне бы не хотелось получить статьи с перечислением функций Visual Studio или описанием работы окна Solution Explorer. Было бы интересно, если бы вы написали каким образом вам удается выжать из вашей среды разработки все соки; о том какие приемы, подходы и практики вы используете при работе с Visual Studio, которые делают вашу разработку более эффективной. Это может быть описание набора плагинов, которые позволяют быть эффективным разработчику, практикующему TDD или другие практики. Или же это может быть описание подходов к организации каких-либо сниппетов, которые ускоряют разработку приложений. Также это может быть просто набор практик, которые можно использовать при работе в Visual Studio. Т.е. здесь вы не ограничены какими-то рамками и можете просто рассказать о том, как вам удается быть эффективным вместе с Visual Studio. Таким образом, предполагается, что участвовать в конкурсе будут небольшие статьи в стиле Tips&amp;Tricks.</p>\n<h2>Когда</h2><p>Конкурс начинается <strong>9 апреля 2010 г.</strong> и заканчивается <strong>9 мая 2010 г.</strong> Результаты конкурса будут объявлены до <strong>15 мая 2010 г.</strong> в этом блоге.</p>\n<p>На протяжении этого времени вы можете писать свои статьи и публиковать ссылки здесь. Количество статей одно автора <u>не ограничивается</u> (т.е. вы можете написать несколько статей).</p>\n<h2>Призы</h2><p>Конечно любой конкурс – это прежде всего спортивное соревнование со своими коллегами, спортивный интерес. Однако, этот интерес будет еще большим при наличии материальных призов. Конечно в качестве приза <a href=\"http://www.mercedes-amg.com/\">Mercedes Benz SL65 AMG</a> не обещаю, но кое-какие призы все-таки будут.</p>\n<p>Призы не будут разделяться по местам, мы просто разделим их между победителями неформально (если вам хочется получить какой-то конкретный приз из перечисленных – напишите об этом в комментариях вместе со ссылкой на свою статью). Итак, призы.</p>\n<p><strong>Первый приз</strong> – это настоящая гемейрская мышь <strong>Microsoft SideWinder Gaming Laser Mouse</strong> (1 шт.). К особенностям этой мыши относятся:</p>\n<ul>\n<li>10 кнопок (включая 5 программируемых и 2 металлических, вертикально расположенных);</li>\n<li>ЖК-дисплей;</li>\n<li>высокая точность позиционирования;</li>\n<li>металлическое колесо прокрутки;</li>\n<li>комплект грузиков для регулировки массы;</li>\n<li>комплект сменных ножек;</li>\n<li>футляр для аксессуаров.</li>\n</ul>\n<p><strong>Следующий приз</strong> – веб-камера <strong>Microsoft LifeCam VX-5000</strong> (1 шт.). К особенностям камеры относятся:</p>\n<ul>\n<li>разрешение 1.3 Mpix;</li>\n<li>возможность работы при плохом освещении;</li>\n<li>гибкая подставка;</li>\n<li>в поставке – LifeCam Dashboard (приложение, позволяющее позабавиться с вашим видео в режиме реального времени);<br>отличная совместимость с Live Messanger, Live Movie Maker, Live Photo Gallery.</li>\n</ul>\n<p>Камера LifeCam VX-5000 – отличное решение для общения в интернет.</p>\n<p><strong>Еще один приз</strong> – <strong>пивной бокал с символикой Visual Studio 2010 и MSDN</strong> (4 шт.).</p>\n<p>Наверняка у большинства уже есть такие, но когда в гости приходят друзья-разработчики бокалов, как говорится, лишних не бывает.</p>\n<p>Главная особенность бокала состоит в том, что его нельзя нигде купить ни за какие деньги, а можно только получить бесплатно, в т.ч. участвуя в этом конкурсе.</p>\n<p><strong>Другой приз</strong> (один из наиболее ценных) – книга <strong>Windows для профессионалов Джеффри Рихтера</strong> (2 шт.).</p>\n<p>Я не буду говорить об особенностях книги. По своему это – очень нужный настольный справочник, который должен быть у каждого разработчика под Windows.</p>\n<p>Как известно, книга – лучший подарок. Разве не так?</p>\n<p>Ну и напоследок самое ценное – <strong>ваучеры на бесплатную сдачу любого сертификационного экзамена Microsoft</strong> (4 шт.).</p>\n<p>При наличии этого ваучера вы можете подтвердить свою высокую квалификацию разработчика или ИТ-профессионала в любом городе, где есть центры тестирования и получить почетное звание Microsoft Certified Professional.</p>\n<p>Таким образом, у нас есть <strong>12 призов</strong>, и запуск Visual Studio 2010 будет происходить 12 апреля, поэтому на этом количестве мы пока и остановимся.</p>\n<p>Также вместе с каждым призом (кроме ваучеров на сдачу MCP-экзаменов) также будет выслан диск с записями докладов с ресурса <a href=\"http://www.techdays.ru/\">TechDays.ru</a>.</p>\n<p>Кроме того, количество призов может быть увеличено, если количество достойных работ будет больше количества призов.</p>\n<h2>Оценка работ</h2><p>Чтобы упростить процесс завершения конкурса и избежать моего субъективного мнения, оценкой работ будут заниматься несколько человек, обладающих статусом <a href=\"https://mvp.microsoft.com/\">Microsoft MVP</a>:</p>\n<ul>\n<li><a href=\"http://twitter.com/Vitus74\">Дильмухаметов Виталий</a> – Client Application Development MVP;</li>\n<li><a href=\"http://twitter.com/wonderu/\">Игорь Подсекин</a> – DirectShow MVP;</li>\n<li><a href=\"http://twitter.com/iamruss\">Руслан Грабар</a> – Sharepoint Server MVP;</li>\n<li><a href=\"http://twitter.com/sergeyzwezdin/\">Сергей Звездин</a> – Visual C# MVP.</li>\n</ul>\n<p>Оценка работ будет происходить с 9 по 15 мая 2010 г. Результаты будут объявлены не позднее 15 мая 2010 г.</p>\n<h2>Что еще?</h2><p>Чтобы сделать конкурс еще более интересным, мне бы хотелось привлечь как можно больше участников. Поэтому я был бы вам очень благодарен, если бы вы порекламировали этот конкурс на своих ресурсах. Вы можете просто написать пару слов о конкурсе со ссылкой на эту статью или отправить ссылку в твиттер-ленту (например, используя кнопку внизу этой статьи). Вы также можете использовать любые другие способы, чтобы сообщить об этом конкурсе своим друзьям, коллегам, всем заинтересованным лицам. Ведь чем нас будет больше, тем интереснее!</p>\n<p>Также, если вы окажете действительно реальную помощь в популяризации конкурса, то для вас тоже найдутся какие-нибудь призы.</p>\n<h2>Что мы все в итоге получим от этого?</h2><p>Во-первых, мы получим <strong>огромное количество удовольствия</strong> от духа соревнования, причем сделаем это очень неформально, отчего удовольствия будет только больше (конкурс не завязан на какую-либо конкретную компанию или акцию, это - моя личная инициатива).</p>\n<p>Во-вторых, после завершения конкурса мы получим (я надеюсь) <strong>огромное число бесценных статей</strong> на тему того, как сделать разработку в Visual Studio еще более эффективной (а ведь это именно то, чем мы с вами занимаемся каждый день!) .</p>\n<p>Ну и в-третьих, каждый автор, поделившийся своим уникальным опытом получит свой <strong>приз</strong> (мелочь, а приятно).</p>\n<p><em>Я надеюсь, что этот конкурс все-таки получится и призы найдут своих обладателей. Ну а если конкурс будет иметь успех среди разработчиков, то можно будет устраивать его периодически, изменяя тематику и набор призов.</em></p>\n<p>Поехали!</p>",
            "tags": [
                "Visual Studio",
                "Конкурсы"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/visual-studio-developer-contest/430ddcd2/visual_studio-developer-contest@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2010/windows-phone-background-tasking/",
            "url": "https://blog.zwezdin.com/2010/windows-phone-background-tasking/",
            "title": "Фоновое исполнение задач в Windows Phone 7",
            "date_published": "2010-04-08T12:00:00.000Z",
            "content_html": "<p>Мой рассказ о <a href=\"/2010/windows-phone-multitasking\">мультизадачности</a> в Windows Phone 7 вызвал неоднозначную реакцию среди будущих пользователей и разработчиков для этой мобильной платформы. В итоге за две недели я получил больше сотни комментариев из различных источников (в блоге, на сторонних ресурсах, в почте, в IM). В основном авторы этих комментариев были недовольны отсутствием многозадачности в Windows Phone 7. Давайте разберемся как же все-таки обстоят дела на самом деле.</p>\n<p>Главное, что хочется сказать – это <strong>многозадачность в Windows Phone 7 есть</strong> (<em>дочитайте до конца и убедитесь, что это действительно так</em>). Процессы в операционной системе исполняются параллельно и могут обрабатывать задачи пользователя.</p>\n<p>Путаницу вызывает то, что Windows Phone 7 приостанавливает приложения <strong>Silverlight</strong> и <strong>XNA</strong> при переключении на другое приложение или при возникновении какого-либо события (например, звонок по телефону). Более того, приложение может быть и вовсе удалено из памяти, если операционной системе будет недостаточно ресурсов (состояние приложение при этом не теряется и будет восстановлено операционной системой при следующем запуске приложения).</p>\n<p>Однако, как я уже <a href=\"/2010/windows-phone-multitasking\">писал</a>, команда разработчиков в ближайшем будущем начнет работы над созданием службы для выполнения фоновых задач. Это означает, что <strong>вы сможете написать собственную фоновую службу и запустить ее на Windows Phone 7</strong>. Эта служба будет работать как фоновый процесс и не будет прерываться операционной системой. При этом, судя по всему, приложение Silverlight или XNA сможет взаимодействовать с вашей фоновой службой и управлять ею.</p>\n<p>Таким образом, для приложений, которым важна именно работа в фоне предлагается модель разработки, при которой приложение на Silverlight/XNA будет некоторой графической оболочкой, а фоновая служба – некоторой логикой, которая работает постоянно. В таком случае приостановка/выгрузка именно приложения Silverlight/XNA очень логично, поскольку это всего лишь пользовательский интерфейс для работающей в фоне службы (которая, напомню, не приостанавливается).</p>\n<p>Кроме того, нужно не забывать о том, что в Windows Phone 7 существуют уведомление (Push Notifications), которые позволяют по внешнему событию восстановить работу и приложения Silverlight/XNA, т.е. по сути запустить нашу графическую оболочку.</p>\n<p>Еще одним подтверждением наличия многозадачности является то, что эмулятор, поставляемый в составе <strong>Windows Phone Developers Tools CTP</strong> на данный момент не реализует полностью функциональность приостановки приложений в момент переключения. Это означает, что если мы напишем небольшое приложение, которое по таймеру увеличивает счетчик и отображает его на форме, запустим приложение и переключимся на другое приложение, то таймер все равно будет работать.</p>\n<p>Естественно эта недоработка версии CTP будет исправлена в последующих выпусках. Однако сейчас это дает право убедиться в том, что на уровне операционной системы многозадачность поддерживается в полном объеме.</p>\n<p>Примеры задач, требующих фонового исполнения, которые смогли привести мои собеседники:</p>\n<ul>\n<li>клиенты IM (Jabber, ICQ, MSN, ..);</li>\n<li>запись трека GPS (другие сценарии работы с GPS);</li>\n<li>антивирусное ПО;</li>\n<li>проигрывание музыки;</li>\n<li>агенты для корпоративного использования;</li>\n<li>загрузка почты;</li>\n<li>будильник;</li>\n<li>автоматическое обновление социальных служб (например, Twitter) при изменении условий (например, местоположения).</li>\n</ul>\n<p>Как видно, предложенный подход к организации многозадачности позволяет покрыть каждый из этих сценариев. Это означает, что Windows Phone 7 есть полноценная многозадачность, а не “немного многозадачности”.</p>\n<p>На этом, я надеюсь, мифы об отсутствии многозадачности в Windows Phone 7 будут развенчаны, а будущие пользователи Windows Phone 7 останутся довольными.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/windows-phone-background-tasking/aeed5f0e/windows_phone-background-tasking@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2010/windows-phone-multitasking/",
            "url": "https://blog.zwezdin.com/2010/windows-phone-multitasking/",
            "title": "Многозадачность в Windows Phone 7",
            "date_published": "2010-03-23T13:00:00.000Z",
            "content_html": "<p>Одним из наиболее спорным вопросом, обсуждаемых при рассмотрении <strong>Windows Phone 7</strong> является <strong>многозадачность</strong>, а точнее ее отсутствие. Как выяснилось, этот вопрос вызывает озабоченность у большого числа пользователей и разработчиков. Давайте подробнее рассмотрим этот вопрос. Для этого обратимся к реализации многозадачности в <em>Windows Phone Classic</em> (старое название – Windows Mobile 5.x, 6.x).</p>\n<p>В Windows Phone Classic многозадачность была реализована в полной мере. Это означает, что в один и тот же момент времени без приостановки могут выполнятся несколько приложений. Это, по сути, некоторый аналог настольной платформе, где этот процесс выглядит аналогичным образом. Однако, давайте подумаем – а действительно ли использование подобного подхода для мобильной платформы также обоснованно, как и для настольной?</p>\n<p>Давайте попробуем представить типичный сценарий использования мобильных устройств. Наверняка он будет включать в себя использование нескольких приложений поочередно в течении длительного количества времени. Например, в данный момент я использую несколько таких приложений: просмотр фотографий, чтение электронной почты, чтение ленты Twitter, просмотр текущего трафика, Windows Media Player, редактирование и просмотр документов Microsoft Office, просмотр документов PDF, калькулятор и др. При длительном использовании устройства можно обнаружить, что в памяти загружены все или часть этих приложений, при этом в данный момент они совершенно не нужны, поскольку нет необходимости в их использовании. Тем не менее, в данный момент они действительно загружены в память и исполняются, загружают центральный процессор, занимают память и расходуют батарею. Конечно, некоторая часть пользователей внимательно следит за тем, чтобы в данный момент были загружены только необходимые приложения, но так делают далеко не все. Именно по этой причине использование подобного подхода не является обоснованным для мобильной платформы.</p>\n<p>В некоторых мобильных платформах существует другая крайность — возможность запускать в каждый момент времени только одно приложение. Такой подход также не является правильным, поскольку иногда все же необходимо запускать одновременно несколько приложений. Классический пример – использование проигрывателя музыки в момент использования других приложений, например, чтении почты.</p>\n<p>Разработчики Windows Phone 7 решили не прибегать к крайностям и выбрали золотую середину. В Windows Phone 7 в каждый момент времени может выполнятся только одно запущенное приложение. При этом существует набор системных приложений, которые могут выполняться “в фоне”. При переходе на главное окно или переключении на другие окна, текущее приложение переключается в приостановленное состояние. Что интересно, операционная система в произвольный момент времени может вовсе удалить этот процесс из памяти. Поэтому в момент приостановки следует сохранить состояние приложения, а в момент возврата к приложению – восстановить это состояние. Служебные механизмы Windows Phone 7 самостоятельно выполняют действия по сохранению и восстановлению состояния, поэтому разработчику приложения не нужно об этом заботиться. Пользователь при переключении между приложениями также не заметит никакой разницы, если приложение было удалено из памяти – он вернется к тому состоянию, в котором находилось приложение в последний раз.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/windows-phone-multitasking/4315b8c8/1@2x.png\"  alt=\"Многозадачность в Windows Phone 7\"/>\n\n\n\n\n<p>Существует также еще один тип событий, которые могут прерывать выполнение приложения. Однако, в отличии от предыдущего сценария, в данном случае прерывание происходит на короткий промежуток времени. Такие прерывания могут случаться, например, при поступлении входящего вызова или при срабатывании будильника. В этом случае приложение продолжает свою работу в фоновом режиме. Однако, если приложение требует от пользователя постоянного взаимодействия (например, игровое приложение), разработчик этого приложения может реагировать на подобные события и принудительно приостанавливать работу приложения на время обработки внешнего события.</p>\n<p>Описанный выше процесс с точки зрения мобильной платформы является более правильным, поскольку позволяет не тратить ресурсы на выполнение ненужных фоновых приложений. Более того, в этом случае состояние приложения сохраняется и восстанавливается в автоматическом режиме, что несомненно очень удобно с точки зрения разработчика приложений.</p>\n<p>Однако, ряд задач не могут быть реализованы при использовании описанного процесса. Дело в том, что есть некоторые задачи, которые требуют фонового исполнения. Например, это может быть приложение для записи GPS-трека пользователя или будильник, который должен сработать в определенный момент. К сожалению, в той версии Windows Phone 7, которая была представлена на MIX 2010 эта функциональность не реализована. Однако, команда разработчиков в данный момент занимается этим сценарием и в ближайшее время мы с вами сможем попробовать реализовать и такие сценарии.</p>\n<p>Таким образом, видно, что подход для организации работы множества приложений в Windows Phone 7 стал более эффективным, чем он был реализован ранее, в Windows Phone Classic.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/windows-phone-multitasking/709d1f46/windows_phone-multitasking@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2010/what-is-windows-phone-7/",
            "url": "https://blog.zwezdin.com/2010/what-is-windows-phone-7/",
            "title": "Windows Phone 7 — что таит в себе новая мобильная платформа",
            "date_published": "2010-03-21T13:00:00.000Z",
            "content_html": "<p>Прошедшее в феврале 2010 г. мероприятие Mobile World Congress 2010 наделало много шума относительно новой мобильной платформы Windows Phone 7. Однако, многие детали были раскрыты только мероприятии MIX 2010, которое прошло в марте 2010 г. Платформа <strong>Windows Phone 7</strong> действительно хороша, в ней сделаны коренные изменения, подарившие вторую жизнь мобильной платформе Microsoft. Однако, как видно по многочисленным отзывам, многими эти изменения были восприняты неправильно и они сделали неверные выводы. На самом деле большинство решений, принятых при разработке <strong>Windows Phone 7</strong> очень логичные и правильные, и именно они помогут платформе выйти на новый, более качественный уровень. Поэтому на протяжении нескольких заметок я расскажу о различных аспектах новой мобильной платформы и разработке для нее. Рассказ будет строится с позиции разработчиков приложений для мобильных платформ.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/what-is-windows-phone-7/980af78e/1@2x.png\"  alt=\"Windows Phone 7 — что таит в себе новая мобильная платформа\"/>\n\n\n\n\n<p>Итак, первое что бросается в глаза при рассмотрении <strong>Windows Phone 7</strong> – это очень смелые и радикальные шаги команды разработчиков по изменению направления развития самой платформы. Если раньше Windows Mobile позиционировалась как мобильная платформа для бизнес-приложений, являющаяся, по сути, облегченным вариантом настольной операционной системы, то сейчас Windows Phone 7 позиционируется как платформа, ориентированная на конечного пользователя. <em>Плохо это или хорошо?</em> Давайте попробуем разобраться. Для этого давайте посмотрим для каких сценариев разрабатываются мобильные приложения:</p>\n<ul>\n<li>приложения для бизнеса;</li>\n<li>приложения для обычных пользователей.</li>\n</ul>\n<p>Нетрудно догадаться, что потребности в приложениях для обычных пользователей намного выше. Действительно, огромное число пользователей смартфонов и коммуникаторов используют их для фотографирования, чтения почты, общения в различных социальных сетях и программах обмена мгновенными сообщениями; при этом гораздо меньшее число пользователей используют свои устройства для работы с корпоративными документами или работе в какой-либо корпоративной системе. Это говорит о том, что подавляющее число пользователей подобных устройств в большей степени нуждается в несложных приложениях для повседневного использования и в меньшей – в бизнес-приложениях. В то же время успех платформы определяется тем, насколько хорошо она решает проблемы своих пользователей. Поэтому не удивительно почему был сделан именно такой шаг. Однако, это вовсе не означает, что корпоративный пользователь забыт, просто ему уделяется несколько меньше внимания.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/what-is-windows-phone-7/01d609a8/2@2x.png\"  alt=\"Windows Phone 7 — что таит в себе новая мобильная платформа\"/>\n\n\n\n\n<p>При таком подходе успех или неуспех платформы сильно зависит от того, насколько активно независимые разработчики будут разрабатывать приложения для новой платформы. Поэтому задача разработчиков Windows Phone 7 заключалась в том, чтобы сделать этот процесс одновременно простым и гибким. Это означает, что разработчик приложений должен создавать новые приложения, затрачивая при этом минимальные усилия по обеспечению инфраструктуры (механизмы построения пользовательского интерфейса, обновления, работа с данными, защита приложения и т.д.). Поэтому разработка приложений осуществляется на базе двух основных платформ – <strong>XNA</strong> и <strong>Silverlight</strong>. Платформа XNA подходит для создания игровых приложений, в то время как Silverlight используется для всех остальных случаев. Однако, это не означает, что вы не можете создавать игр на Silverlight или, наоборот, приложения для чтения RSS-ленты на XNA. Как видно, в обоих случаях используется управляемый код, что позволяет сделать приложения более управляемыми и контролируемыми со стороны операционной системы. В целом платформу разработки можно представить следующим образом.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/what-is-windows-phone-7/d2f9b7ce/3@2x.png\"  alt=\"Windows Phone 7 — что таит в себе новая мобильная платформа\"/>\n\n\n\n\n<p><strong>Windows Phone 7</strong> поддерживает надмножество <strong>Silverlight 3</strong>. Это означает, что мы можем легко портировать существующие Silverlight-приложения на мобильную платформу. При этом приложение может одинаково корректно работать как на PC, так и на мобильном устройстве. С платформой XNA ситуация похожая – разработанный однажды код может одновременно работать на PC, XBOX и мобильном устройстве. Это дает пользователю использовать аналогичные возможности на всех указанных устройствах и тем самым повышать эффективность его работы. В Microsoft это называют концепцией трех экранов.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/what-is-windows-phone-7/26e1ade3/4@2x.png\"  alt=\"Windows Phone 7 — что таит в себе новая мобильная платформа\"/>\n\n\n\n\n<p>Для разработки приложений существует отдельная редакция Visual Studio Express for Windows Phone, которая поставляется бесплатно. Также возможно разрабатывать для Windows Phone 7, используя приложение Expression Blend. Эти и другие инструменты можно найти на портале <a href=\"http://developer.windowsphone.com/\">developer.windowsphone.com</a>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/what-is-windows-phone-7/7716b252/5@2x.png\"  alt=\"Windows Phone 7 — что таит в себе новая мобильная платформа\"/>\n\n\n\n\n<p>Поскольку платформа Windows Phone 7 ориентирована в большей степени на потребителя, то важную роль в ней занимает пользовательский интерфейс. В этом вопросе было принято решение использовать успешный опыт пользовательского интерфейса Zune HD и остановиться на подобном графическом решении. Сами разработчики называют это решение как «<strong>язык дизайна Metro</strong>» — это не просто цветовое оформление пользовательского интерфеса, это – целая философия того, как нужно строить пользовательский интерфейс. В основу Metro заложены несколько главных идей:</p>\n<ul>\n<li>пользовательский интерфейс не содержит излишеств (вроде различных градиентов) – он прост и понятен, но в то же время сохраняет свою привлекательность;</li>\n<li>используются только крупные шрифты;</li>\n<li>определение акцентов в приложение делается с помощью цветовой схемы;</li>\n<li>особое внимание уделяется шрифтам.</li>\n</ul>\n<p>Успешным ли получился пользовательский интерфейс оценят потребители, однако он уже имел успех среди пользователей Zune HD, поэтому можно предположить, что и здесь он найдет своего потребителя.</p>\n<p>Последнее о чем хочется сказать, рассматривая Windows Phone 7 — это попытка команды разработчиков стандартизации аппаратного обеспечения. Опыт с Windows Mobile (когда аппаратное обеспечения отличается от модели к модели) показал, что это очень осложняет жизнь разработчику. Вводя четкие стандарты, Microsoft дает возможность разработчикам быть точно уверенным в том, есть ли в наличии то или иное устройство и какими характеристиками оно обладает. Таким образом, Windows Phone 7 содержит минимальные требования к аппаратной платформе, которые выглядят следующим образом:</p>\n<ul>\n<li>процессор ARM v.7;</li>\n<li>память – 256 Mb;</li>\n<li>multi-touch экран (обработка минимум 4-х прикосновений);</li>\n<li>сенсоры: A-GPS, акселерометр, компас, датчик света;</li>\n<li>камера (минимум 5 Mpx);</li>\n<li>GPU (DirectX 9).</li>\n</ul>\n<p><em>Плохо это или хорошо, что есть жесткие ограничения, предъявляемые к устройству?</em> Я думаю, что это очень хорошо. Производители мобильных устройств скорее всего найдут в себе силы для создания подобной платформы, а разработчикам приложения это позволит не ограничиваться старыми устройствами.</p>\n<p>В итоге, видно, что платформа Windows Phone 7 – это второе дыхание мобильной платформы Microsoft, которая имеет все шансы на успех. Наступит ли этот успех покажет время. Далее мы продолжим рассматривать особенности Windows Phone 7 и будем говорить о различных аспектах разработки для новой мобильной платформы.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2010/what-is-windows-phone-7/47633c5d/6@2x.png\"  alt=\"Windows Phone 7 — что таит в себе новая мобильная платформа\"/>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2010/what-is-windows-phone-7/9f7493d5/what_is-windows-phone-7@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2009/windows-mobile-location-aware/",
            "url": "https://blog.zwezdin.com/2009/windows-mobile-location-aware/",
            "title": "Как определить \"Где я?\" в приложениях на Windows Mobile",
            "date_published": "2009-12-08T13:00:00.000Z",
            "content_html": "<p>На сегодняшний день многие приложения - мобильные и настольные - приобретают новые возможности и реализуют различные интересные и нестандартные сценарии. К таким необычным на сегодняшний день приложениям можно отнести приложения, адаптирующиеся к текущим условиям, в которых находится пользователь. Например, это может быть приложение, которое изменяет яркость и размер шрифта при плохом освещении или приложение, которое изменяет свой пользовательский интерфейс в зависимости от того, где сейчас находится пользователь (дома, в офисе и т.д.). Такие приложения имеют общее название - контекстно-зависимые приложения. Одним из частных случаев контекстно-зависимых приложений является приложения, зависящие от географического местоположения пользователя. Например, если приложение отображает прогноз погоды, то логично было бы отображать его именно для той местности где находится пользователь. Точно также было бы логично вести отсчет времени именно в том часовом поясе, где находится пользователь. Примеров подобных сценариев можно привести много, что еще раз доказывает ценность подобных приложений в реальной жизни.</p>\n<p><em>Каким образом реализуются приложения, зависящие от местоположения сегодня?</em> Как правило, на ум сразу приходит использование GPS-устройств, которые способны предоставлять координаты текущего местоположения. Однако, GPS устройства есть далеко не везде и не всегда. Более того, применение GPS-устройств ограничивается тем, что их нельзя использовать внутри зданий и закрытых помещениях. Засчастую на этом этапе заканчиваются идеи относительно получения текущего местоположения. Однако, если задуматься, то существуют и другие источники получения этой информации. Поскольку в нашем случае речь идет о <strong>Windows Mobile</strong>, а устройства на базе этой операционной системы зачастую обладают <em>GSM-модулем</em>, то информацию о местоположении можно брать с базовых станций состовых операторов. Кроме того, не стоит забывать про сервисы определения местоположения по <em>IP-адресу</em> – на подобных устройствах мобильный интернет настроен в большинстве случаев.</p>\n<p>Каждый из приведенных выше способов имеет собственные преимущества и недостатки. Поэтому каждый способ может работать только в определенных условиях и при определенных ограничениях. Наиболее правильный способ – использовать все приведенные ранее способы и объеденить преимущества этих способов в одном приложении.</p>\n<p>На протяжении данной статьи мы рассмотрим процесс создания приложения для <strong>Windows Mobile</strong>, при котором мы будем использовать несколько альтернативных источников получения информации и построим приложение, которое может получать информацию о местоположении в различных условиях. Прежде чем переходить к реализации самого приложения давайте рассмотрим каждый из способов получения информации о местоположении.</p>\n<h2>GPS</h2><p><strong>GPS</strong> — это наиболее популярный способ получения информации о местоположении. Этот способ работает в устройствах, оборудованных приемником GPS. Для этого GPS-приемник использует одновременно несколько спутников (минимум 4) и на основании этого получает координаты текущего местоположения. Кроме непосредственно координат, GPS-приемник предоставляет также информацию о текущей скорости, направлении (север-юг-запад-восток), высоте и т.д. В нашем случае понадобятся только координаты, поэтому остальные параметры мы использовать не будем.</p>\n<p>К преимуществам этого способа можно отнести точность получаемой информации и возможность ее постоянного обновления (в случае перемещения). Более того, в общем случае, для получения координат не требуется подключение к Интернет или сети сотового оператора. Однако, у GPS есть также и недостатки. <em>Во-первых</em>, GPS-приемники, как правило, не работают в закрытых помещениях (это связано с особенностями самой технологии). <em>Во-вторых</em>, зачастую GPS-приемнику требуется некоторое время (от нескольких секунд до нескольких минут) для того, чтобы установить связь со спутниками. Наконец, в какие-то моменты времени GPS-приемник может просто не установить связь со спутниками из-за погодных условий, вашего расположения относительно спутников или других причин. Все это создает неудобства при работе с GPS, а в некоторых ситуациях может сделать невозможным использование данного способа.</p>\n<p>Для работы с GPS-приемником в Windows Mobile могут использоваться различные подходы. Обычно GPS-приемник поставляет свои данные через COM-порт. Поэтому можно подключиться к нему и считывать всю необходимую информацию. Однако, при таком подходе могут быть проблемы с подключением нескольких приложений к GPS-приемнику. Кроме того, в этом случае нам придется вручную разбирать форматы данных, которые поступают от приемника (мелочь, а не приятно). Поэтому в данном случае рекомендуется использовать некоторую «прослойку» в Windows Mobile, которая позволяет централизованно получить информацию с GPS-устройства – GPS API. По сути, GPS API работает напрямую с COM-портом, обрабатывает данные и предоставляет интерфейсы для других приложений. Используя GPS API, приложения могут получать всю информацию, которая доступна от GPS-приемника.</p>\n<p>Для работы с GPS API в Windows Mobile существует библиотека <code>gpsapi.dll</code>, которая поставляется вместе с операционной системой. Эта библиотека является неуправляемой, поэтому для работы с ней необходимо использовать <code>PInvoke</code>. К счастью, в Microsoft уже сделали управляемую обертку для этой библиотеки, которая поставляется вместе с <strong>Windows Mobile 6 SDK</strong>. В нашем случае мы возьмем эту обертку для использования в нашем приложении.</p>\n<p>Использование управляемой обертки является достаточно простым. Для этого необходимо создать объект <code>NativeWrapperGps</code>, подписаться на событие <code>LocationChanged</code> и вызвать метод <code>Open</code>. При изменении текущего местоположения будет сгенерировано событие <code>LocationChanged</code>. После окончания работы с GPS-устройством необходимо вызвать метод <code>Close</code>. В общем случае работа через <strong>GPS API</strong> может выглядеть следующим образом.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">var gps = new NativeWrapperGps();\ngps.LocationChanged +=\n  delegate(object sender, NativeWrapperGps.LocationChangedEventArgs args)\n  &#123;\n    // ...\n  &#125;;\n\ngps.Open();\n// ...\ngps.Close();</code></pre>\n<p>Таким образом, мы можем работать с GPS-приемником в Windows Mobile и получать информацию о текущем местоположении.</p>\n<h2>Сотовые сети</h2><p>Другим источником информации о текущем местоположении может служить информация, которая доступна от оператора сотовой связи. Поскольку сети беспроводной связи строятся по принципу «сот», то в каждый момент мы работаем с какой-либо конкретной базовой станцией. Каждая базовая станция имеет некий уникальный идентификатор, который определяет каждую базовую станцию. Этот идентификатор состоит из нескольких значений – <code>Cell ID</code> (или <code>Tower ID</code>), <code>Location Area Code</code> (<code>LAC</code>), <code>Mobile Country Code</code> (<code>MCC</code>) и <code>Mobile Network Code</code> (<code>MNC</code>). Все эти значения можно получить от базовой станции и они будут уникальными для каждой базовой станции.</p>\n<p>Тем не менее, сам по себе данный идентификатор не представляет ценной информации о местоположении. Однако, существуют централизованные базы данных, сопоставляющие эти идентификаторы с географической информацией. Понятно, что точность определения местоположения пользователя зависит от того, какую площадь обслуживает базовая станция. Однако, такая точность может удовлетворять пользователя достаточно часто.</p>\n<p>Таким образом, предложенный способ не содержит недостатков способа определения местоположения с помощью GPS – не требуется длительная инициализация, процесс определения не зависит от погодных условий и положения спутников, а определение местоположения происходит достаточно быстро. Однако, у приведенного способа есть и недостатки. Основным недостатком является низкая точность определения местоположения: если GPS определяет координаты с точностью до нескольких метров, то приведенный способ может «ошибиться» на несколько километров. Кроме того, централизованные базы данных могут не содержать информацию о базовой станции с которой вы сейчас работаете – в этом случае определить местоположение невозможно. Наконец, для определения местоположения требуется наличие покрытия территории сетями мобильных операторов, а также требуется доступ к Интернет (GPRS, EDGE, 3G и т.д.). Доступ к глобальной сети необдоходим для того, чтобы воспользоваться услугами централизованных баз данных для определения координат. Таким образом, видно, что данный способ имеет достаточно серьезные ограничение, но в некоторых случаях может применяться достаточно успешно.</p>\n<p>Для того, чтобы получать информацию от <strong>GSM-модуля</strong> телефона можно воспользоваться библиотекой <code>ril.dll</code> (RIL, Radio Interface Layer). Эта библиотека поставляется вместе с операционной системой и разработана на неуправляемом коде. По этой причине использовать её функции можно через <code>PInvoke</code>. В рамках RIL доступно множество интересных функций, которыми можно воспользоваться. В нашем случае необходимо получить информацию о базовой станции, это можно сделать используя функцию <code>GetCellTowerInfo</code>. Перед использованием этой функции необходимо вызвать функцию инициализации, а после использования — деинициализации. При инициализации указывается метод, который будет вызван после получения информации (процесс получения информации о базовой станции асинхронный). Таким образом, для работы с <code>RIL</code> потребуется небольшая обертка над неуправляемой библиотекой.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class RilInterop\n&#123;\n  public delegate void RilResultCallback(uint dwCode, IntPtr hrCmdID, IntPtr lpData, uint cbData, uint dwParam);\n\n  public delegate void RilNotifyCallback(uint dwCode, IntPtr lpData, uint cbData, uint dwParam);\n\n  [DllImport(\"ril.dll\", EntryPoint = \"RIL_Initialize\")]\n  public static extern IntPtr Initialize(uint dwIndex, RilResultCallback pfnResult, RilNotifyCallback pfnNotify, uint dwNotificationClasses, uint dwParam, out IntPtr lphRil);\n\n  [DllImport(\"ril.dll\", EntryPoint = \"RIL_GetCellTowerInfo\")]\n  public static extern IntPtr GetCellTowerInfo(IntPtr hRil);\n\n  [DllImport(\"ril.dll\", EntryPoint = \"RIL_Deinitialize\")]\n  public static extern IntPtr Deinitialize(IntPtr hRil);\n&#125;</code></pre>\n<p>После этого необходимо проинициализировать RIL, указать метод обратного вызова, вызвать метод <code>GetCellTowerInfo</code> и вызвать метод деинициализации. Данные будут получены в асинхронном режиме, поэтому если необходимо их получать синхронно нужно воспользоваться объектами синхронизации, например, <code>AutoResetEvent</code>. Таким образом, получение информации о базовой станции будет выглядеть следующим образом.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class RilWrapper\n&#123;\n  private static RilCellTowerInfo _towerDetails;\n\n  private static readonly AutoResetEvent WaitHandle = new AutoResetEvent(false);\n\n  public static CellTower GetCellTowerInfo()\n  &#123;\n    IntPtr rilHandle;\n\n    if (RilInterop.Initialize(1, CellDataCallback, null, 0, 0, out rilHandle) != IntPtr.Zero)\n    &#123;\n      return null;\n    &#125;\n\n    RilInterop.GetCellTowerInfo(rilHandle);\n\n    WaitHandle.WaitOne();\n\n    RilInterop.Deinitialize(rilHandle);\n\n    return new CellTower\n    &#123;\n      TowerId = Convert.ToInt32(_towerDetails.DwCellID),\n      LocationAreaCode = Convert.ToInt32(_towerDetails.DwLocationAreaCode),\n      MobileCountryCode = Convert.ToInt32(_towerDetails.DwMobileCountryCode),\n      MobileNetworkCode = Convert.ToInt32(_towerDetails.DwMobileNetworkCode),\n    &#125;;\n  &#125;\n\n  public static void CellDataCallback(uint dwCode, IntPtr hrCmdID, IntPtr lpData, uint cbData, uint dwParam)\n  &#123;\n    _towerDetails = new RilCellTowerInfo();\n    Marshal.PtrToStructure(lpData, _towerDetails);\n    WaitHandle.Set();\n  &#125;\n&#125;</code></pre>\n<p>После вызова метода <code>GetCellTowerInfo()</code> будет возвращен объект, содержащий значения, которые характеризуют текущую базовую станцию. Как было сказано выше, для получения географических координат на основе этой информации необходимо использовать какую-либо общедоступную базу данных. Например, такую информацию содержит открытый сервис <code>OpenCellId</code> (<a href=\"http://opencellid.org/\">opencellid.org</a>). Используя этот ресурс можно получить координаты местоположения по информации о базовой станции. Используя заданный формат URL, можно обратиться к сервису и получить ответ в виде XML. Для распознования информации от этого сервиса создадим небольшой объект, выполняющий эти функции.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class OpenCellIdProvider : ICellToGpsProvider\n&#123;\n  public GpsLocation GetCoordinates(int cellId, int locationAreaCode, int mobileCountryCode, int mobileNetworkCode)\n  &#123;\n    GpsLocation result = null;\n\n    var doc = XDocument.Load(String.Format(\"http://www.opencellid.org/cell/get?key=&#123;4&#125;&amp;mnc=&#123;3&#125;&amp;mcc=&#123;2&#125;&amp;lac=&#123;1&#125;&amp;cellid=&#123;0&#125;\", cellId, locationAreaCode, mobileCountryCode, mobileNetworkCode, Key));\n\n    if ((doc.Root != null) &amp;&amp; (doc.Root.Attribute(XName.Get(\"stat\")).Value == \"ok\"))\n    &#123;\n      var cellInfoElem = doc.Root.Element(XName.Get(\"cell\"));\n\n      if (cellInfoElem != null)\n      &#123;\n        result = new GpsLocation\n        &#123;\n          Latitude = Convert.ToDouble(cellInfoElem.Attribute(XName.Get(\"lat\")).Value.Replace('.', ',')),\n          Longitude = Convert.ToDouble(cellInfoElem.Attribute(XName.Get(\"lon\")).Value.Replace('.', ','))\n        &#125;;\n      &#125;\n    &#125;\n\n    return result;\n  &#125;\n&#125;</code></pre>\n<p>Теперь, после получения информации о базовой станции мы можем обратиться к этому сервису и получить координаты текущего местоположения.</p>\n<h2>GeoIp</h2><p>Последний способ, который мы рассмотрим в качестве варианта получения текущего местоположение – это определение текущих координат на основе IP-адреса. Поскольку большинство устройств имеют GRPS-подключение к Интернет, то этот способ может достаточно успешно работать. Получение местоположения по IP-адресу – это операция, которая не всегда возвращает достоверную информацию. За счет использования «серых» IP-адресов информация может сильно отличаться от действительности (например, когда российский IP-адрес определяется ирландским). Тем не менее, при использовании мобильного интернета вероятность получения правильной информации выше. Поэтому мы рассмотрим этот метод в качестве одной из альтернатив.</p>\n<p>Сильной стороной этого метода является то, что он не зависит от наличия приемника GPS, погодных условий и возможности соединения спутника. Кроме того, этот способ не зависит от наличия информации в базе данных по базовым станциям. Однако, недостатки у этого метода присутствуют и они являются достаточно существенными. Во-первых, точность определения местоположения зачастую оставляет желать лучшего. Как правило – это рамки города (даже не района). Однако, если этот уровень детализации устраивает, то почему бы и нет? Другой недостаток – это необходимость наличия подключенного интернета; если подключенного интернета нет, то этот способ не работает. Более того, большинство полноценных иструментов определения местоположения по IP-адресу либо являются платными, либо накладывают ограничения на число обращений.</p>\n<p>Тем не менее, давайте возьмем на заметку этот способ на случай, когда все перечисленные ограничения не являются существенными. GeoIp-сервисов существует большое множество. Большинство из них платные, другие могут просто требовать регистрации. В нашем случае мы остановимся на сервисе <code>GeoIpTool</code> (<a href=\"http://geoiptool.com/\">geoiptool.com</a>, в общем же случае можно использовать любой из представленных в сети Интернет. Этот сервис возвращает HTML при обращении к его главной странице, внутри которой есть координаты, соответствющие IP-адресу клиента. В данном случае мы просто обработаем HTML-код и получим из него координаты.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public GpsLocation GetLocation()\n&#123;\n  double? lat = null;\n  double? lon = null;\n\n  var html = GetHtml(\"http://www.geoiptool.com/en/\");\n\n  var lines = Regex.Matches(html.Replace(\"\\r\", \" \").Replace(\"\\n\", \" \"), @\"&lt;tr>(.)+?&lt;/tr>\", RegexOptions.IgnoreCase);\n  foreach (Match line in lines)\n  &#123;\n    try\n    &#123;\n      if (line.Value.Contains(\"Longitude:\"))\n      &#123;\n        lon = Convert.ToDouble(Regex.Match(line.Value, @\"&lt;td(.)*?>(?&lt;val>[0-9\\.]+)&lt;/td>\").Groups[\"val\"].Value.Replace('.', ','));\n      &#125;\n      if (line.Value.Contains(\"Latitude:\"))\n      &#123;\n        lat = Convert.ToDouble(Regex.Match(line.Value, @\"&lt;td(.)*?>(?&lt;val>[0-9\\.]+)&lt;/td>\").Groups[\"val\"].Value.Replace('.', ','));\n      &#125;\n    &#125;\n    catch (FormatException)\n    &#123;\n    &#125;\n  &#125;\n\n  return (lat != null) &amp;&amp; (lon != null) ? new GpsLocation &#123; Latitude = (double)lat, Longitude = (double)lon &#125; : null;\n&#125;</code></pre>\n<p>Таким образом, мы можем получить свое местоположение по IP-адресу.</p>\n<h2>Строим финальное приложение</h2><p>Теперь, когда мы умеем определять текущее местоположение как минимум тремя способами, давайте построим приложение, которое будет использовать одновременно все эти способы. Поскольку каждый из способов имет свои преимущества и недостатки расположим эти способы в порядке приоритетов. Понятно, что самым точным способом является определение координат на основе данных GPS. Поэтому приоритетно будем использовать его. В случаях, когда GPS-приемник отсутствует в устройстве или он не может соединиться со спутниками, мы будем использовать данные от базовых станций мобильной сети. В этом случае точность определения будет снижена, но свое примерное местоположение мы узнать сможем. Наконец, если по каким-то причинам мы не смогли узнать местоположение и этим способом (например, данные о текущей базовой станции не найдены в базе данных), то можно узнать местоположение с помощью мобильного интернета и IP-адреса. В этом случае точность снизится еще больше, но информацию мы получим.</p>\n<p>Для работы в таком режиме хотелось бы иметь общий механизм, которому мы будем задавать вопрос о текущем местоположении, а он в свою очередь уже доступным способом определяет местоположение. Для этого каждый из способов будет реализовывать интерфейс провайдера <code>ILocationProvider</code>, определенный следующим образом.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public interface ILocationProvider\n&#123;\n  GpsLocation GetLocation();\n  string ProviderName &#123; get; &#125;\n&#125;</code></pre>\n<p>Этот интерфейс содержит метод для получения текущего местоположения, а также возвращает имя провайдера. После этого необходимо реализовать этот интерфейс в объектах, реализующих логику для каждого из приведенных способов. Несложно догадаться как это можно сделать на основе кода, приведенного для каждого из способов. Наконец, необходим объект, выполняющий функции координирования всех провайдеров, своего рода менеджер. Этот объект должен содержать коллекцию доступных провайдеров и при обращении к нему должен поочередно опрашивать каждого провайдера. Если более приоритетный провайдер сгенерировал исключение или просто вернул null, то нужно обратиться к следующему провайдеру и т.д. Таким образом, получается следующее описание класса для подобного объекта.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class LocationManager : ILocationProvider\n&#123;\n  private readonly List&lt;ILocationProvider> _providers = new List&lt;ILocationProvider>();\n\n  private string _lastProviderName = null;\n\n  public void ClearProviders()\n  &#123;\n    _providers.Clear();\n  &#125;\n\n  public void RegisterProvider(ILocationProvider provider)\n  &#123;\n    if (_providers.Contains(provider)==false)\n    &#123;\n      _providers.Add(provider);\n    &#125;\n  &#125;\n\n  public void RemoveProvider(ILocationProvider provider)\n  &#123;\n    if (_providers.Contains(provider) == true)\n    &#123;\n      _providers.Remove(provider);\n    &#125;\n  &#125;\n\n  #region ILocationProvider Members\n\n  public GpsLocation GetLocation()\n  &#123;\n    GpsLocation result = null;\n    _lastProviderName = null;\n\n    foreach (var provider in _providers)\n    &#123;\n      try\n      &#123;\n        result = provider.GetLocation();\n\n        if (result != null)\n        &#123;\n          _lastProviderName = provider.ProviderName;\n          break;\n        &#125;\n      &#125;\n      catch (Exception ex)\n      &#123;\n        continue;\n      &#125;\n    &#125;\n\n    return result;\n  &#125;\n\n  public string ProviderName\n  &#123;\n    get &#123; return _lastProviderName; &#125;\n  &#125;\n\n  #endregion\n&#125;</code></pre>\n<p>Таким образом, для использования этого механизма необходимо зарегистрировать все необходимые провайдеры, а затем обратиться к объекту-менеджеру с вопросом «где я?». Например, этот код может выглядть так.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">LocationManager manager = new LocationManager();\n\nmanager.RegisterProvider(new GpsLocationProvider());\nmanager.RegisterProvider(new CellLocationProvider(new OpenCellIdProvider()));\nmanager.RegisterProvider(new GeoIpLocationProvider());\n\nvar location = manager.GetLocation();</code></pre>\n<p>В случае, когда нужно исключить из процесса какой-либо из провайдеров, мы просто его не регистрируем. Напоследок, можно отобразить полученные координаты на карте и вывести их пользователю.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows-mobile-location-aware/5b77eb0b/1@2x.png\"  alt=\"Как определить &#34;Где я?&#34; в приложениях на Windows Mobile\"/>\n\n\n\n\n<p>Таким образом, мы получили приложение, которое использует несколько альтернативных источников информации для получения своего местоположения: если в данный момент можно использовать GPS, то используется именно этот способ, если же GPS недоступен, то используются альтернативные способы, пусть и менее точные.</p>\n<h2>Вместо заключения</h2><p>Приложения, реагирующие на изменение контекста становятся все более популярными в условиях развития современных приложений. Мобильные приложения не являются исключением. Однако, многие разработчики до сих пор считают приемник GPS единственным средством получения информации о текущем местоположении. Здесь мы рассмотрели способ, с помощью которого можно получать подобную информацию в случае невозможности использования GPS и даже без наличия приемника GPS. Зачастую альтернативные способы являются менее точными при определении местоположения, но точность нужна не везде и не всегда – большое количество сценариев отлично реализуются и на основе информации о том, в каком я сейчас городе или даже стране. Поэтому не нужно ограничивать себя в подобных возможностях, а нужно просто включить фантазию и начать использовать контекстно-зависимые сценарии в своих приложениях.</p>",
            "tags": [
                "Windows Phone"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/windows-mobile-location-aware/64f65f71/windows_mobile-location-aware@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2009/adonet-data-services-enums/",
            "url": "https://blog.zwezdin.com/2009/adonet-data-services-enums/",
            "title": "Использование перечисляемых типов в ADO.NET Data Services",
            "date_published": "2009-11-29T13:00:00.000Z",
            "content_html": "<p>Этой осень довелось пообщаться на тему <strong>ADO.NET Data Services</strong> в рамках <a href=\"http://izhevsk.ineta.ru/\">Ижевского сообщества разработчиков</a>. Кроме прочих обсуждаемых вопросов, речь зашла об использовании перечисляемых типов в составе сущности, которая используется в рамках сервиса ADO.NET Data Services. Поскольку в течении этого времени вопросом интересовалось еще несколько человек, решил вынести эту информацию сюда.</p>\n<p>Суть проблемы заключается в следующем. Предположим мы решили создать сервис на базе <strong>ADO.NET Data Services</strong> и использовать в его источнике сущность, содержащую поле перечисляемого типа. Обратившись к такому сервису можно увидеть незамысловатую ошибку.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/adonet-data-services-enums/fc60f0a4/1@2x.png\"  alt=\"Использование перечисляемых типов в ADO.NET Data Services\"/>\n\n\n\n\n<p>Можно посмотреть детали этой ошибки. Для этого необходимо соответствующим образом настроить сервис.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[ServiceBehavior(IncludeExceptionDetailInFaults = true)]\npublic class WebDataService1 : DataService&lt;MyDataContext>\n&#123;</code></pre>\n<p>Обратившись к сервису снова, мы увидим ошибку, которая говорит о том, что перечисляемые типы нельзя использовать в сущностях для подобных сервисах. К сожалению, эта досадная недоработка в ADO.NET Data Services может серьезно подпортить жизнь разработчикам и, как я уже сказал, нет стандартного решения этой проблемы. Давайте попробуем решить эту проблему хотя бы частично.</p>\n<p>Для этого создадим сущность и перечисляемый тип, который будем использовать в сервисе:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public enum StateEnum\n&#123;\n    Normal = 1,\n    Error = 2\n&#125;\n\n[DataServiceKey(\"ID\")]\npublic class SampleEntity\n&#123;\n    public int ID &#123; get; set; &#125;\n\n    public string Name &#123; get; set; &#125;\n\n    public StateEnum State\n    &#123;\n        get;\n        set;\n    &#125;\n&#125;</code></pre>\n<p>Теперь создадим собственный контекст данных и стандартный сервис ADO.NET Data Services. Пусть этот контекст содержит статические данные. В вашем случае это может быть контекст с собственной логикой.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class MyDataContext\n&#123;\n    private static readonly IEnumerable&lt;SampleEntity> Entities = new List&lt;SampleEntity>()\n     &#123;\n        new SampleEntity() &#123;ID = 1, Name = \"Name1\", State = StateEnum.Normal&#125;,\n        new SampleEntity() &#123;ID = 2, Name = \"Name2\", State = StateEnum.Error&#125;,\n        new SampleEntity() &#123;ID = 3, Name = \"Name3\", State = StateEnum.Normal&#125;,\n        new SampleEntity() &#123;ID = 4, Name = \"Name4\", State = StateEnum.Normal&#125;,\n        new SampleEntity() &#123;ID = 5, Name = \"Name5\", State = StateEnum.Normal&#125;,\n    &#125;;\n\n    public IQueryable&lt;SampleEntity> Data\n    &#123;\n        get\n        &#123;\n            return Entities.AsQueryable&lt;SampleEntity>();\n        &#125;\n    &#125;\n&#125;</code></pre>\n<p>Понятно, что с таким сервисом мы получим ошибку, которую видел в самом начале. Чтобы избежать ошибки мы будем использовать атрибут <code>IgnoreProperties</code>. С его помощью можно указать ADO.NET Data Services о том, что нужно игнорировать указанные свойства. Наша сущность примет следующий вид.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[DataServiceKey(\"ID\")]\n[IgnoreProperties(\"State\")]\npublic class SampleEntity\n&#123;\n    public int ID &#123; get; set; &#125;\n\n    public string Name &#123; get; set; &#125;\n\n    public StateEnum State\n    &#123;\n        get &#123; return (StateEnum)StateValue; &#125;\n        set &#123; StateValue = (int)value; &#125;\n    &#125;\n&#125;</code></pre>\n<p>Отлично, ошибка перестала появляться, но при этом значение данного свойства также перестало передаваться на клиентскую сторону.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>\n&lt;feed xml:base=\"http://localhost:4321/WebDataService1.svc/\" xmlns:d=\"http://schemas.microsoft.com/ado/2007/08/dataservices\" xmlns:m=\"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\" xmlns=\"http://www.w3.org/2005/Atom\">\n    &lt;title type=\"text\">Data&lt;/title>\n    &lt;id>http://localhost:4321/WebDataService1.svc/Data&lt;/id>\n    &lt;updated>2009-11-29T06:06:57Z&lt;/updated>\n    &lt;link rel=\"self\" title=\"Data\" href=\"Data\" />\n    &lt;entry>\n        &lt;id>http://localhost:4321/WebDataService1.svc/Data(1)&lt;/id>\n        &lt;title type=\"text\" />\n        &lt;updated>2009-11-29T06:06:57Z&lt;/updated>\n        &lt;author>\n        &lt;name />\n        &lt;/author>\n        &lt;link rel=\"edit\" title=\"SampleEntity\" href=\"Data(1)\" />\n        &lt;category term=\"WebApplication69.SampleEntity\" scheme=\"http://schemas.microsoft.com/ado/2007/08/dataservices/scheme\" />\n        &lt;content type=\"application/xml\">\n            &lt;m:properties>\n                &lt;d:ID m:type=\"Edm.Int32\">1&lt;/d:ID>\n                &lt;d:Name>Name1&lt;/d:Name>\n            &lt;/m:properties>\n        &lt;/content>\n    &lt;/entry>\n    &lt;entry>\n        &lt;id>http://localhost:4321/WebDataService1.svc/Data(2)&lt;/id>\n        &lt;title type=\"text\" />\n        &lt;updated>2009-11-29T06:06:57Z&lt;/updated>\n        &lt;author>\n        &lt;name />\n        &lt;/author>\n        &lt;link rel=\"edit\" title=\"SampleEntity\" href=\"Data(2)\" />\n        &lt;category term=\"WebApplication69.SampleEntity\" scheme=\"http://schemas.microsoft.com/ado/2007/08/dataservices/scheme\" />\n        &lt;content type=\"application/xml\">\n            &lt;m:properties>\n                &lt;d:ID m:type=\"Edm.Int32\">2&lt;/d:ID>\n                &lt;d:Name>Name2&lt;/d:Name>\n            &lt;/m:properties>\n        &lt;/content>\n    &lt;/entry>\n    &lt;entry>\n        &lt;id>http://localhost:4321/WebDataService1.svc/Data(3)&lt;/id>\n        &lt;title type=\"text\" />\n        &lt;updated>2009-11-29T06:06:57Z&lt;/updated>\n        &lt;author>\n        &lt;name />\n        &lt;/author>\n        &lt;link rel=\"edit\" title=\"SampleEntity\" href=\"Data(3)\" />\n        &lt;category term=\"WebApplication69.SampleEntity\" scheme=\"http://schemas.microsoft.com/ado/2007/08/dataservices/scheme\" />\n        &lt;content type=\"application/xml\">\n            &lt;m:properties>\n                &lt;d:ID m:type=\"Edm.Int32\">3&lt;/d:ID>\n                &lt;d:Name>Name3&lt;/d:Name>\n            &lt;/m:properties>\n        &lt;/content>\n    &lt;/entry>\n    &lt;entry>\n        &lt;id>http://localhost:4321/WebDataService1.svc/Data(4)&lt;/id>\n        &lt;title type=\"text\" />\n        &lt;updated>2009-11-29T06:06:57Z&lt;/updated>\n        &lt;author>\n        &lt;name />\n        &lt;/author>\n        &lt;link rel=\"edit\" title=\"SampleEntity\" href=\"Data(4)\" />\n        &lt;category term=\"WebApplication69.SampleEntity\" scheme=\"http://schemas.microsoft.com/ado/2007/08/dataservices/scheme\" />\n        &lt;content type=\"application/xml\">\n            &lt;m:properties>\n                &lt;d:ID m:type=\"Edm.Int32\">4&lt;/d:ID>\n                &lt;d:Name>Name4&lt;/d:Name>\n            &lt;/m:properties>\n        &lt;/content>\n    &lt;/entry>\n    &lt;entry>\n        &lt;id>http://localhost:4321/WebDataService1.svc/Data(5)&lt;/id>\n        &lt;title type=\"text\" />\n        &lt;updated>2009-11-29T06:06:57Z&lt;/updated>\n        &lt;author>\n        &lt;name />\n        &lt;/author>\n        &lt;link rel=\"edit\" title=\"SampleEntity\" href=\"Data(5)\" />\n        &lt;category term=\"WebApplication69.SampleEntity\" scheme=\"http://schemas.microsoft.com/ado/2007/08/dataservices/scheme\" />\n        &lt;content type=\"application/xml\">\n            &lt;m:properties>\n                &lt;d:ID m:type=\"Edm.Int32\">5&lt;/d:ID>\n                &lt;d:Name>Name5&lt;/d:Name>\n            &lt;/m:properties>\n        &lt;/content>\n    &lt;/entry>\n    &lt;/feed></code></pre>\n<p>Для того, чтобы все-таки передавать это значение на клиент достаточно вспомнить, что значение перечисляемого типа — это, по сути, целое число, а это простой тип, который можно передавать через сервис. Таким образом, делаем небольшое дополнение к этой сущности и получаем следующее её определение.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[DataServiceKey(\"ID\")]\n[IgnoreProperties(\"State\")]\npublic class SampleEntity\n&#123;\n    public int ID &#123; get; set; &#125;\n\n    public string Name &#123; get; set; &#125;\n\n    public int StateValue &#123; get; set; &#125;\n\n    public StateEnum State\n    &#123;\n        get &#123; return (StateEnum)StateValue; &#125;\n        set &#123; StateValue = (int)value; &#125;\n    &#125;\n&#125;</code></pre>\n<p>После этого, сервис, работающий с указанной сущностью будет игнорировать свойство <code>State</code>, но при этом на клиент будет передаваться <code>StateValue</code>. В этом легко убедиться, если обратиться к сервису через браузер.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>\n&lt;feed xml:base=\"http://localhost:4321/WebDataService1.svc/\" xmlns:d=\"http://schemas.microsoft.com/ado/2007/08/dataservices\" xmlns:m=\"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\" xmlns=\"http://www.w3.org/2005/Atom\">\n    &lt;title type=\"text\">Data&lt;/title>\n    &lt;id>http://localhost:4321/WebDataService1.svc/Data&lt;/id>\n    &lt;updated>2009-11-29T06:13:47Z&lt;/updated>\n    &lt;link rel=\"self\" title=\"Data\" href=\"Data\" />\n    &lt;entry>\n        &lt;id>http://localhost:4321/WebDataService1.svc/Data(1)&lt;/id>\n        &lt;title type=\"text\" />\n        &lt;updated>2009-11-29T06:13:47Z&lt;/updated>\n        &lt;author>\n        &lt;name />\n        &lt;/author>\n        &lt;link rel=\"edit\" title=\"SampleEntity\" href=\"Data(1)\" />\n        &lt;category term=\"WebApplication69.SampleEntity\" scheme=\"http://schemas.microsoft.com/ado/2007/08/dataservices/scheme\" />\n        &lt;content type=\"application/xml\">\n            &lt;m:properties>\n                &lt;d:ID m:type=\"Edm.Int32\">1&lt;/d:ID>\n                &lt;d:Name>Name1&lt;/d:Name>\n                &lt;d:StateValue m:type=\"Edm.Int32\">1&lt;/d:StateValue>\n            &lt;/m:properties>\n        &lt;/content>\n    &lt;/entry>\n    &lt;entry>\n        &lt;id>http://localhost:4321/WebDataService1.svc/Data(2)&lt;/id>\n        &lt;title type=\"text\" />\n        &lt;updated>2009-11-29T06:13:47Z&lt;/updated>\n        &lt;author>\n        &lt;name />\n        &lt;/author>\n        &lt;link rel=\"edit\" title=\"SampleEntity\" href=\"Data(2)\" />\n        &lt;category term=\"WebApplication69.SampleEntity\" scheme=\"http://schemas.microsoft.com/ado/2007/08/dataservices/scheme\" />\n        &lt;content type=\"application/xml\">\n            &lt;m:properties>\n                &lt;d:ID m:type=\"Edm.Int32\">2&lt;/d:ID>\n                &lt;d:Name>Name2&lt;/d:Name>\n                &lt;d:StateValue m:type=\"Edm.Int32\">2&lt;/d:StateValue>\n            &lt;/m:properties>\n        &lt;/content>\n    &lt;/entry>\n    &lt;entry>\n        &lt;id>http://localhost:4321/WebDataService1.svc/Data(3)&lt;/id>\n        &lt;title type=\"text\" />\n        &lt;updated>2009-11-29T06:13:47Z&lt;/updated>\n        &lt;author>\n        &lt;name />\n        &lt;/author>\n        &lt;link rel=\"edit\" title=\"SampleEntity\" href=\"Data(3)\" />\n        &lt;category term=\"WebApplication69.SampleEntity\" scheme=\"http://schemas.microsoft.com/ado/2007/08/dataservices/scheme\" />\n        &lt;content type=\"application/xml\">\n            &lt;m:properties>\n                &lt;d:ID m:type=\"Edm.Int32\">3&lt;/d:ID>\n                &lt;d:Name>Name3&lt;/d:Name>\n                &lt;d:StateValue m:type=\"Edm.Int32\">1&lt;/d:StateValue>\n            &lt;/m:properties>\n        &lt;/content>\n    &lt;/entry>\n    &lt;entry>\n        &lt;id>http://localhost:4321/WebDataService1.svc/Data(4)&lt;/id>\n        &lt;title type=\"text\" />\n        &lt;updated>2009-11-29T06:13:47Z&lt;/updated>\n        &lt;author>\n        &lt;name />\n        &lt;/author>\n        &lt;link rel=\"edit\" title=\"SampleEntity\" href=\"Data(4)\" />\n        &lt;category term=\"WebApplication69.SampleEntity\" scheme=\"http://schemas.microsoft.com/ado/2007/08/dataservices/scheme\" />\n        &lt;content type=\"application/xml\">\n            &lt;m:properties>\n                &lt;d:ID m:type=\"Edm.Int32\">4&lt;/d:ID>\n                &lt;d:Name>Name4&lt;/d:Name>\n                &lt;d:StateValue m:type=\"Edm.Int32\">1&lt;/d:StateValue>\n            &lt;/m:properties>\n        &lt;/content>\n    &lt;/entry>\n    &lt;entry>\n        &lt;id>http://localhost:4321/WebDataService1.svc/Data(5)&lt;/id>\n        &lt;title type=\"text\" />\n        &lt;updated>2009-11-29T06:13:47Z&lt;/updated>\n        &lt;author>\n        &lt;name />\n        &lt;/author>\n        &lt;link rel=\"edit\" title=\"SampleEntity\" href=\"Data(5)\" />\n        &lt;category term=\"WebApplication69.SampleEntity\" scheme=\"http://schemas.microsoft.com/ado/2007/08/dataservices/scheme\" />\n        &lt;content type=\"application/xml\">\n            &lt;m:properties>\n                &lt;d:ID m:type=\"Edm.Int32\">5&lt;/d:ID>\n                &lt;d:Name>Name5&lt;/d:Name>\n                &lt;d:StateValue m:type=\"Edm.Int32\">1&lt;/d:StateValue>\n            &lt;/m:properties>\n        &lt;/content>\n    &lt;/entry>\n&lt;/feed></code></pre>\n<p>Последнее, что осталось сделать — это сообщить клиентской библиотеке о том, как воспринимать подобные значения. Для этого на клиентской стороне также объявим нужный перечисляемый тип, в частичном классе добавим нужное свойство и укажем атрибут <code>IgnoreProperties</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public enum StateEnum\n&#123;\n    Normal = 1,\n    Error = 2\n&#125;\n\n[IgnoreProperties(\"State\")]\npublic partial class SampleEntity\n&#123;\n    public StateEnum State\n    &#123;\n        get &#123; return (StateEnum)StateValue; &#125;\n        set &#123; StateValue = (int)value; &#125;\n    &#125;\n&#125;</code></pre>\n<p>Теперь на клиентской стороне можно обращаться к нужному свойству и получать значение перечисляемого типа. При этом сервис будет работать корректно.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">class Program\n&#123;\n    static void Main(string[] args)\n    &#123;\n        var context = new MyDataContext(new Uri(@\"http://localhost:4321/WebDataService1.svc/\"));\n        foreach (var entity in context.Data)\n        &#123;\n            Console.WriteLine(entity.State);\n        &#125;\n        Console.Read();\n    &#125;\n&#125;</code></pre>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/adonet-data-services-enums/fb0edd99/2@2x.png\"  alt=\"Использование перечисляемых типов в ADO.NET Data Services\"/>\n\n\n\n\n<p>Конечно, представленное решение не претендует на элегантное решение. Однако, если сильно хочется использовать перечисляемые типы, то это может быть одним из решений, которое можно использовать в приложениях.</p>",
            "tags": [
                "Windows Communication Foundation"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/adonet-data-services-enums/021f9f98/adonet_data-services-enums@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/wcf-progress-bar/",
            "url": "https://blog.zwezdin.com/2009/wcf-progress-bar/",
            "title": "Отображение прогресса загрузки данных в сервисах WCF",
            "date_published": "2009-11-04T13:00:00.000Z",
            "content_html": "<p>Платформу <strong>Windows Communication Foundation</strong> можно использовать не только для простого вызова операций, но и для передачи большого объема данных (например, файлов в несколько мегабайт). Иногда такие сценарии применимы и на медленных каналах. В последнем случае зачастую очень не хватает индикатора выполнения операции (<code>progress bar</code>). Когда-то давно я задавался такой задачей, но довести ее до реализации не хватило времени. Лучше поздно, чем никогда. Поэтому сегодня я предлагаю рассмотреть способ реализации прогресса выполнения на базе WCF.</p>\n<p>Для простоты мы будем использовать привязку <code>BasicHttpBinding</code>. Поскольку протокол HTTP не имеет принципиальный противоречий для отображение прогресса выполнения, то этот способ нам подойдет. Ключевым моментом при реализации данного сценария является установка параметра <code>TransferMode</code> и привязки в значение <code>Streamed</code>. Таким образом, данные будут передаваться в виде потока.</p>\n<p>После этого определим контракт взаимодействия клиента и сервера. На клиентскую сторону требуется передать имя файла, его размер и содержимое. Поэтому определим контракт следующим образом.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[ServiceContract]\npublic interface IFileTransferService\n&#123;\n    [OperationContract]\n    RemoteFileInfo DownloadFile(DownloadRequest request);\n&#125;\n\n[MessageContract]\npublic class DownloadRequest\n&#123;\n    [MessageBodyMember]\n    public string FileName;\n&#125;\n\n[MessageContract]\npublic class RemoteFileInfo : IDisposable\n&#123;\n    [MessageHeader(MustUnderstand = true)]\n    public string FileName;\n\n    [MessageHeader(MustUnderstand = true)]\n    public long Length;\n\n    [MessageBodyMember(Order = 1)]\n    public Stream FileByteStream;\n\n    public void Dispose()\n    &#123;\n        if (FileByteStream != null)\n        &#123;\n            FileByteStream.Close();\n            FileByteStream = null;\n        &#125;\n    &#125;\n&#125;</code></pre>\n<p>Как видно, операция <code>DownloadFile</code> возвращает объект <code>RemoteFileInfo</code>, который содержит необходимую информацию. Реализация данного сервиса, я думаю, вопросов не вызывает — для этого нужно создать объект <code>RemoteFileInfo</code> и заполнить его всеми необходимыми данными.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class FileTransferService : IFileTransferService\n&#123;\n    public RemoteFileInfo DownloadFile(DownloadRequest request)\n    &#123;\n        var filePath = request.FileName;\n        var fileInfo = new FileInfo(filePath);\n\n        if (fileInfo.Exists==false)\n        &#123;\n            throw new FileNotFoundException(\"File not found\", request.FileName);\n        &#125;\n\n        var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);\n\n        var result = new RemoteFileInfo\n        &#123;\n            FileName = request.FileName,\n            Length = fileInfo.Length,\n            FileByteStream = stream\n        &#125;;\n\n        return result;\n    &#125;\n&#125;</code></pre>\n<p>На этом с серверной стороны мы сделали все необходимое, осталось только сформировать конфигурационный файл и определить хост-среду для сервиса. Для простоты в качестве хоста будем использовать консольное приложение. Тогда код для размещения сервиса будет выглядеть следующим образом.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">class Program\n&#123;\n    static void Main()\n    &#123;\n        var myServiceHost = new ServiceHost(typeof(FileService.FileTransferService));\n        myServiceHost.Open();\n\n        Console.ReadKey();\n\n        myServiceHost.Close();\n    &#125;\n&#125;</code></pre>\n<p>При этом конфигурационный файл будет иметь следующий вид.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;?xml version=\"1.0\" encoding=\"utf-8\" ?>\n&lt;configuration>\n    &lt;system.serviceModel>\n        &lt;bindings>\n            &lt;basicHttpBinding>\n                &lt;binding name=\"FileTransferServicesBinding\"\n                         transferMode=\"Streamed\"\n                         messageEncoding=\"Mtom\"\n                         maxReceivedMessageSize=\"20134217728\" />\n            &lt;/basicHttpBinding>\n        &lt;/bindings>\n        &lt;services>\n            &lt;service behaviorConfiguration=\"MyServiceTypeBehaviors\"\n                     name=\"FileService.FileTransferService\">\n                &lt;endpoint address=\"getfile\"\n                  binding=\"basicHttpBinding\"\n                  bindingConfiguration=\"FileTransferServicesBinding\"\n                  contract=\"FileService.IFileTransferService\" />\n                &lt;host>\n                    &lt;baseAddresses>\n                        &lt;add baseAddress=\"http://localhost/\" />\n                    &lt;/baseAddresses>\n                &lt;/host>\n            &lt;/service>\n        &lt;/services>\n        &lt;behaviors>\n            &lt;serviceBehaviors>\n                &lt;behavior name=\"MyServiceTypeBehaviors\">\n                    &lt;serviceMetadata httpGetEnabled=\"true\" />\n                    &lt;serviceDebug includeExceptionDetailInFaults=\"true\" />\n                &lt;/behavior>\n            &lt;/serviceBehaviors>\n        &lt;/behaviors>\n    &lt;/system.serviceModel>\n&lt;/configuration></code></pre>\n<p>Теперь самое время заняться клиентской частью. Во-первых, определим конфигурацию для данного сервиса.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;?xml version=\"1.0\" encoding=\"utf-8\" ?>\n&lt;configuration>\n    &lt;system.serviceModel>\n        &lt;bindings>\n            &lt;basicHttpBinding>\n                &lt;binding name=\"fileBinding\" maxReceivedMessageSize=\"20134217728\"\n                         messageEncoding=\"Mtom\" transferMode=\"Streamed\" />\n            &lt;/basicHttpBinding>\n        &lt;/bindings>\n        &lt;client>\n            &lt;endpoint address=\"http://localhost/getfile\"\n                      binding=\"basicHttpBinding\"\n                      bindingConfiguration=\"fileBinding\"\n                      contract=\"Client.FileTransferClient.IFileTransferService\" />\n        &lt;/client>\n    &lt;/system.serviceModel>\n&lt;/configuration></code></pre>\n<p>После генерации клиентского прокси он выглядит следующим образом.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[System.Diagnostics.DebuggerStepThroughAttribute()]\n[System.CodeDom.Compiler.GeneratedCodeAttribute(\"System.ServiceModel\", \"3.0.0.0\")]\npublic partial class FileTransferServiceClient :\n    System.ServiceModel.ClientBase&lt;Client.FileTransferClient.IFileTransferService>,\n    Client.FileTransferClient.IFileTransferService\n&#123;\n   public FileTransferServiceClient()\n   &#123;\n   &#125;\n\n   public FileTransferServiceClient(string endpointConfigurationName) :\n           base(endpointConfigurationName)\n   &#123;\n   &#125;\n\n   public FileTransferServiceClient(string endpointConfigurationName, string remoteAddress) :\n           base(endpointConfigurationName, remoteAddress)\n   &#123;\n   &#125;\n\n   public FileTransferServiceClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :\n           base(endpointConfigurationName, remoteAddress)\n   &#123;\n   &#125;\n\n   public FileTransferServiceClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :\n           base(binding, remoteAddress)\n   &#123;\n   &#125;\n\n   Client.FileTransferClient.RemoteFileInfo Client.FileTransferClient.IFileTransferService.DownloadFile(Client.FileTransferClient.DownloadRequest request)\n   &#123;\n       return base.Channel.DownloadFile(request);\n   &#125;\n&#125;</code></pre>\n<p>Как мы помним, операция сервиса <code>DownloadFile</code> возвращает информацию о файле и его поток. В этом виде не очень удобно работать с операцией, поэтому создадим еще один перегруженный вариант.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public long DownloadFile(ref string fileName, out System.IO.Stream fileByteStream)\n&#123;\n    Client.FileTransferClient.DownloadRequest inValue = new Client.FileTransferClient.DownloadRequest();\n    inValue.FileName = FileName;\n    Client.FileTransferClient.RemoteFileInfo retVal = ((Client.FileTransferClient.IFileTransferService)(this)).DownloadFile(inValue);\n    FileName = retVal.FileName;\n    FileByteStream = retVal.FileByteStream;\n    return retVal.Length;\n&#125;</code></pre>\n<p>Таким образом, мы получили метод, который немедленно возвращает длину файла и его содержимое в виде потока (<code>Stream</code>). Теперь мы должны получить этот поток и считывать из него информацию порциями. При этом после каждого считывания необходимо обновлять информацию о прогрессе передачи данных. Полученные данные можно записывать, например, в локальный файл.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">var client = new FileTransferClient.FileTransferServiceClient();\n\nStream inputStream;\nlong length = client.DownloadFile(ref fileName, out inputStream);\n\nusing (var writeStream = new FileStream(fileName, FileMode.CreateNew, FileAccess.Write))\n&#123;\n    const int bufferSize = 2048;\n    var buffer = new byte[bufferSize];\n\n    do\n    &#123;\n        int bytesRead = inputStream.Read(buffer, 0, bufferSize);\n        if (bytesRead == 0)\n        &#123;\n            break;\n        &#125;\n\n        writeStream.Write(buffer, 0, bytesRead);\n\n        progressBar1.Value = (int)(writeStream.Position * 100 / length);\n    &#125;\n    while (true);\n\n    writeStream.Close();\n&#125;\n\ninputStream.Dispose();\nclient.Close();</code></pre>\n<p>Отлично, теперь мы получили приложение, которое будет отображать прогресс загрузки данных на клиент. Аналогичным образом можно сделать загрузку данных на сервер.</p>",
            "tags": [
                "Windows Communication Foundation"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/wcf-progress-bar/49ea074c/wcf_progress-bar@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/wcf-throttling/",
            "url": "https://blog.zwezdin.com/2009/wcf-throttling/",
            "title": "Балансировка нагрузки в WCF (throttling)",
            "date_published": "2009-11-01T13:00:00.000Z",
            "content_html": "<p>Платформа <strong>Windows Communication Foundation</strong> используется практически в каждом проекте, требующим использования сетевых сервисов. Фактически, если в проекте стает необходимость исопльзовать сервисы, мы несомненно используем WCF непосредственно или опосредовано (например, используя ADO.NET Data Services). Обычно сервисы работают что называется “из коробки” и многие не заботятся о дополнительном конфигурировании сервиса. Однако, при построении сервисов, которые должны работать под нагрузкой приходится задумываться о более сложных параметрах.</p>\n<p>Здесь я бы хотел в компактном виде привести основные сведения, которые нужно знать при настройке сервиса, работающего под нагрузкой.</p>\n<h2><code>ConcurrencyMode</code></h2><p>Параметр, который определяет режим работы сервиса – сколько реальных потоков будут обрабатывать сообщения и выполнять действия. Этот параметр может принимать значения:</p>\n<ul>\n<li><code>Single</code> – сообщения обрабатываются последовательно в единственном потоке (<em>значение по умолчанию</em>).</li>\n<li><code>Reentrant</code> – аналогично Single с некоторыми ньюансами (см. <a href=\"http://msdn.microsoft.com/en-us/library/aa395214.aspx\">MSDN</a>).</li>\n<li><code>Multiple</code> – сообщения обрабатываются параллельно в нескольких потоках.</li>\n</ul>\n<p>Также с этим параметром тесно связан параметр <a href=\"http://msdn.microsoft.com/en-us/library/system.servicemodel.description.servicethrottlingbehavior.maxconcurrentcalls.aspx\">MaxConcurrentCalls</a>, который ограничивает максимальное количество потоков обработки конкурирующих вызовов. Обычно параметр <code>ConcurrencyMode</code> задается при определении сервиса.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]\npublic class Service1 : IService1\n&#123;\n    public string GetData(int value)\n    &#123;\n        return string.Format(\"You entered: &#123;0&#125;\", value);\n    &#125;\n\n    public CompositeType GetDataUsingDataContract(CompositeType composite)\n    &#123;\n        if (composite.BoolValue)\n        &#123;\n            composite.StringValue += \"Suffix\";\n        &#125;\n        return composite;\n    &#125;\n&#125;</code></pre>\n<h2><code>InstanceContextMode</code></h2><p>Очень важный параметр, который определяет каким способом создается объект на стороне сервиса для обслуживания запроса. Параметр может принимать следующие значения:</p>\n<ul>\n<li><code>PerCall</code> – создавать новый экземпляр сервиса на каждый вызов (<em>значение по умолчанию</em>).</li>\n<li><code>PerSession</code> – создавать новый экземпляр сервиса на каждую сессию.</li>\n<li><code>Single</code> – создавать единственный экземпляр сервиса, который будет обслуживать все запросы.</li>\n</ul>\n<p>Понятно, что более эффективно использовать режим <code>Single</code>. При этом будет создан единственный объект сервиса и не нужно заботиться об использовании памяти и других ресурсов. Однако, в случаях, когда сервис содержит состояние, использовать режим <code>Single</code> невозможно. В этом случае можно ограничить количество одновременно существующих экземпляров сервиса параметром <a href=\"http://msdn.microsoft.com/en-us/library/system.servicemodel.description.servicethrottlingbehavior.maxconcurrentinstances.aspx\">MaxConcurrentInstances</a>. Также можно ограничить количество одновременно работающих сессий, это делается при помощи параметра <a href=\"http://msdn.microsoft.com/en-us/library/system.servicemodel.description.servicethrottlingbehavior.maxconcurrentsessions.aspx\">MaxConcurrentSessions</a>.</p>\n<p>Обычно параметр <code>InstanceContextMode</code> задается при определении сервиса.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]\npublic class Service1 : IService1\n&#123;\n    public string GetData(int value)\n    &#123;\n        return string.Format(\"You entered: &#123;0&#125;\", value);\n    &#125;\n\n    public CompositeType GetDataUsingDataContract(CompositeType composite)\n    &#123;\n        if (composite.BoolValue)\n        &#123;\n            composite.StringValue += \"Suffix\";\n        &#125;\n        return composite;\n    &#125;\n&#125;</code></pre>\n<p>Кроме того, на параметры устойчивости приложения на внешние атаки могут влиять параметры, задающие квоты на различные параметры. Например, к таким квотам относятся настройки <code>sendTimeout</code>, <code>receiveTimeout</code>, <code>maxReceivedMessageSize</code> и т.д. Более подробно о них можно прочитать в <a href=\"http://msdn.microsoft.com/en-us/library/ms731078.aspx\">MSDN</a>.</p>",
            "tags": [
                "Windows Communication Foundation"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/wcf-throttling/8930b311/wcf_throttling@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/adonet-data-services-paging/",
            "url": "https://blog.zwezdin.com/2009/adonet-data-services-paging/",
            "title": "ADO.NET Data Services 1.5: server driven paging",
            "date_published": "2009-09-18T12:00:00.000Z",
            "content_html": "<p>Мы продолжаем рассматривать новые возможности <strong>ADO.NET Data Services</strong> 1.5 и сегодня мы посмотрим возможность принудительной разбивки данных на страницы.</p>\n<p>В первой версии Astoria возможность разбивки данных на страницы уже присутствовала. Для этих целей можно было построить запрос с помощью конструкций <code>Take</code>/<code>Skip</code>. Изменяя значения этих параметров можно было получить постраничный доступ к данным с клиента. Тем не менее, при обращении к коллекции без параметров сервис возвращал полностью всю коллекцию. Описанное поведение кажется вполне логичным, но оно не всегда является желательным. К примеру, если в коллекции содержится большое количество объектов, то обращение к такой коллекции непременно повлечет повышенную нагрузку на сервер, а клиент получит задержку при обращении к сервису. Поэтому в версии 1.5 было принято решение о добавлении возможности принудительной разбивки на страницы.</p>\n<p>Для включения описанной возможности необходимо определить дополнительное поведение при конфигурировании сервиса. В частности нам необходим метод <code>SetEntitySetPageSize()</code>, который позволяет определить размер страницы для каждой коллекции. Этот метод принимает два параметра — имя коллекции и размер страницы. Если необходимо определить размер для всех коллекций, то в качестве имени коллекции необходимо использовать <code>*</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class WebDataService : DataService&lt;NorthwindEntities>\n&#123;\n    public static void InitializeService(DataServiceConfiguration config)\n    &#123;\n        config.SetEntitySetAccessRule(\"*\", EntitySetRights.All);\n\n        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;\n        config.SetEntitySetPageSize(\"Categories\", 5);\n    &#125;\n&#125;</code></pre>\n<p>В случае описанного выше сервиса, при обращении к коллекции <code>Categories</code> данные будут разбиты на страницы по пять объектов. При этом в конце результирующего набора данных будет выдана подсказка, позволяющая обратиться к следующей страницы набора данных.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/adonet-data-services-paging/1f73235f/1@2x.png\"  alt=\"ADO.NET Data Services 1.5: server driven paging\"/>\n\n\n\n\n<p>Как видно, для обращения к следующей странице необходимо использовать ключевое слово <code>skiptoken</code>. В этом случае формат URI будет выглядеть следующим образом:</p>\n<pre class=code language-clike><code class=\"code__container language-clike\">http://.../WebDataService.svc/Categories?$skiptoken=5</code></pre>\n<p>Данная функциональность также поддерживается и в рамках клиентского прокси. При обычном обращении к коллекции клиенту будет выдано только указанное число объектов. Для обращения к остальным страницам необходимо использовать метод <code>Skip()</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">var client = new NorthwindEntities(new Uri(@\"...\"));\nvar q = from c in client.Categories.Skip(2)\n        select c;\n\nforeach (var item in q)\n&#123;\n    // ...\n&#125;</code></pre>\n<p>При таком подходе мы также можем использовать всю функциональность сервисов данных – фильтрация, сортировка, проекции и т.д. При этом количество записей на странице будет всегда ограничиваться указанным значением.</p>\n<p>Однако, несмотря на привлекательность данной возможности, не стоит прибегать к частым ее использованием. В случае, если нет особой надобности в подобной разбивке на странице, то лучше этого не делать, поскольку разбивка на страницы несомненно ведет к увеличению числа запросов к сервису для получения одного и того же набора данных.</p>",
            "tags": [
                "Windows Communication Foundation"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/adonet-data-services-paging/ca3d742f/adonet_data-services-paging@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/adonet-data-services-projections/",
            "url": "https://blog.zwezdin.com/2009/adonet-data-services-projections/",
            "title": "ADO.NET Data Services 1.5: проекции",
            "date_published": "2009-09-15T12:00:00.000Z",
            "content_html": "<p>Недавно я начал тему, посвященную <strong>ADO.NET Data Services 1.5</strong>. Ранее я рассказал о общих тенденциях в этом проекте. Настало время поделиться некоторыми деталями. Здесь мы рассмотрим возможность создания проекций с помощью нового функционала, который появится в версии 1.5.</p>\n<p>Необходимость создания проекций присутствовала еще в первой версии Astoria. Однако, как мы знаем, время и ресурсы ограничены, поэтому такая возможность появилась только в версии 1.5. Возможность создания проекций необходима в тех ситуациях, когда сущности, содержащиеся в коллекциях на сервисе содержат большое количество полей, а клиенту нужны только некоторые поля. В этом случае клиент вынужден загружать все поля этих объектов и тем самым генерировать лишний трафик и увеличивая время выполнения запросов.</p>\n<p>В версии 1.0 предлагалось решить проблему путем разделения большой сущности на несколько маленьких и работать с ними отдельно. В этом случае, если необходимо все-таки получить доступ ко всем полям объекта создавалось представление (<code>View</code>), которое существовало в рамках сервиса в виде отдельной коллекции или сервисной операции. Безусловно, такой подход создает массу проблем. Например, в такой ситуации требуется приложить дополнительные усилия в случае редактирования таких коллекций. Кроме того, количество коллекций данных на сервисе может существенно увеличиться. В этом случае напрашивается логичное решение - “попросить” у сервиса вернуть не все поля объектов, а только те, которые необходимо прямо в URI.</p>\n<p>В этом и заключается сущность работы механизма проекций в <strong>ADO.NET Data Services 1.5</strong>. Всякий раз, когда необходимо вернуть специфичный набор полей, мы задаем его явно в URI. При этом остальные поля игнорируются и не передаются клиенту.</p>\n<p>Для выполнения подобного запроса появилось новое ключевое слово <code>select</code>. Например, если необходимо выбрать два поля <code>ProductName</code> и <code>UnitPrice</code>, то необходимо выполнить следующий запрос:</p>\n<pre class=code language-clike><code class=\"code__container language-clike\">http://../WebDataService.svc/Products()?$select=ProductName,UnitPrice</code></pre>\n<p>Аналогично, если необходимо выбрать все поля, проигнорировав этот оператор или указав в качестве значения <code>*</code>:</p>\n<pre class=code language-clike><code class=\"code__container language-clike\">http://../WebDataService.svc/Products()?$select=*</code></pre>\n<p>В некоторых ситуациях возможность получения проекций может быть нежелательна. В этом случае её можно отключить, используя дополнительные параметры конфигурирования сервиса. Для этого в классе <code>DataServiceConfiguration</code> появилось свойство <code>DataServiceBehavior</code>, которое позволяет настраивать поведение сервиса. В данном случае нас интересует свойство <code>AcceptProjectionRequests</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class WebDataService : DataService&lt;NorthwindEntities>\n&#123;\n    public static void InitializeService(DataServiceConfiguration config)\n    &#123;\n        config.SetEntitySetAccessRule(\"*\", EntitySetRights.All);\n\n        config.DataServiceBehavior.AcceptProjectionRequests = true;\n        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;\n    &#125;\n&#125;</code></pre>\n<p>При использовании клиентского прокси возможность создания проекций также можно использовать удобным образом. Для этого нужно построить запрос, где в секции <code>Select</code> указать необходимые поля.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">var client = new NorthwindEntities(new Uri(@\"http://.../WebDataService.svc/\"));\nvar q = from c in client.Categories\n        select new &#123; c.CategoryName, c.Description &#125;;</code></pre>\n<p>В этом случае также будет генерироваться запрос с оператором <code>select</code> в URI.</p>\n<p>Кроме того, Astoria 1.5 решает еще одну проблему. Если клиенту необходимо обратиться к сервису для получения количества объектов в коллекции, то в ранних версиях было необходимо обратиться к сервису, получить сущности и посчитать их. Это могло существенно увеличивать трафик и время загрузки. В Astoria 1.5 появился дополнительный оператор <code>count</code>, который позволяет вычислить количество сущностей в рамках данного запроса на стороне сервиса. Для этого необходимо выполнить следующий запрос:</p>\n<pre class=code language-clike><code class=\"code__container language-clike\">http://.../WebDataService.svc/Products()/$count</code></pre>\n<p>Более того, если необходимо выполнить какой-то запрос (например, фильтрацию) и получить количество записей, то это также возможно. Для этого мы обращаемся к подобному URI, а затем указываем параметры запроса:</p>\n<pre class=code language-clike><code class=\"code__container language-clike\">http://.../WebDataService.svc/Products()/$count?$filter=CategoryId gt 5</code></pre>\n<p>С точки зрения клиентского прокси, подобные URI будут генерироваться при вызове метода-расширения <code>Count()</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">var client = new NorthwindEntities(new Uri(@\"http://localhost:1076/WebDataService.svc/\"));\nvar с = (from c in client.Categories\n         where c.CategoryID > 5\n         select c).Count();</code></pre>\n<p>Возможность подсчета количества сущностей в запросе также может быть отключена при настройке сервиса. Это можно сделать, используя свойство <code>AcceptCountRequests</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class WebDataService : DataService&lt;NorthwindEntities>\n&#123;\n    public static void InitializeService(DataServiceConfiguration config)\n    &#123;\n        config.SetEntitySetAccessRule(\"*\", EntitySetRights.All);\n\n         config.DataServiceBehavior.AcceptCountRequests = true;\n        config.DataServiceBehavior.AcceptProjectionRequests = true;\n        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;\n    &#125;\n&#125;</code></pre>\n<p>Подобной функциональности очень нехватало в первой версии <strong>ADO.NET Data Services</strong> и сейчас она была добавлена. И теперь мы можем использовать сервисы еще более эффективно.</p>",
            "tags": [
                "Windows Communication Foundation"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/adonet-data-services-projections/96d32528/adonet_data-services-projections@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part3/",
            "url": "https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part3/",
            "title": "Windows 7 Sensor and Location platform - реализация поддержки собственного сенсора (Часть 3/3)",
            "date_published": "2009-07-02T12:00:00.000Z",
            "content_html": "<p>Я рассказал о том, как можно использовать <strong>Sensor and Location platform</strong> в своих приложениях. Мы увидели, что использование этого набора компонент может быть очень удобным для приложения и не требует много усилий. Также мы получаем возможность работы с множеством устройств в унифицированном виде. Единственной проблемой использования этой платформы остается наличие драйверов для Windows 7 и наличие оберток для Sensor API. Разработка драйвера для устройства – задача производителя, в первую очередь. А вот реализацию поддержки в Sensor API вполне можно сделать собственными силами. Здесь мы и займемся этим.</p>\n<p>Как я уже говорил точкой входа является класс <code>SensorManager</code>. С помощью этого класса можно получить доступ к нужным сенсорам и поработать с ними. Этот класс имеет такие методы как получение списка всех сенсоров, получение сенсора по ID или по типу, запрос на использование сенсора, а также событие изменения количества сенсоров в системе.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part3/056f822a/1@2x.png\"  alt=\"Windows 7 Sensor and Location platform - реализация поддержки собственного сенсора (Часть 3/3)\"/>\n\n\n\n\n<p>Каждый сенсор имеет два основных типа идентификаторов – <code>SensorId</code> и <code>TypeId</code>. <code>TypeId</code> идентифицирует отдельный класс устройств. Например, по нему можно получить все датчики света в системе, или какие-то другие типы устройств. <code>SensorId</code> присваивается уникально каждому устройству. Например, если в системе три однотипных датчика движений, то каждый будет иметь уникальный идентификатор. Есть еще также <code>CategoryId</code>, который объединяет сенсоры в категории.</p>\n<p>Каждый идентификатор представляет собой <code>GUID</code>. Они задаются производителями при разработке устройства и драйверов. Таким образом, можно получить конкретный сенсор только зная его <code>ID</code>. Каждый сенсор представлен классом Sensor. Он имеет общую информацию о сенсоре и методы, которые позволяют получить данные из обобщенных коллекций в нетипизированном виде. Понятно, что такое представление данных не очень удобно для наших приложений. Поэтому для каждого сенсора принято реализовывать класс-обертку в рамках <code>Sensor API</code>. Реализуется она путем наследования от общего класса <code>Sensor</code>. В демонстрационных примерах уже есть две такие реализации – для акселерометра и для датчика света. Однако, в устройстве, которое мы рассматривали ранее присутствуют также сенсорные кнопки, которые также можно использовать. Поэтому давайте реализуем такой класс для этого датчика.</p>\n<p>Мы определим новый класс, который будет наследником класса <code>Sensor</code>. Для того, чтобы он распознавался в Sensor API его нужно пометить атрибутом <code>SensorDescription</code>, в котором указать <code>TypeId</code> для этого типа сенсоров. В базовом классе Sensor существуют две важные вещи для нас – свойство <code>DataReport</code> и событие <code>DataReportChanged</code>. Это свойство содержит данные от сенсора, а событие срабатывает при их изменении. Задача нашего класса – воспользоваться этими данными и доставить их пользователю нашего класса в удобном виде. Для этого создадим еще один небольшой класс, который будет заниматься разбором информации из DataReport.</p>\n<p>Экспериментальным путем мы выясним, что при нажатии кнопки 1 генерируется код 1, при нажатии 2 – генерируется код 2, при нажатии 3 – генерируется код 4, а при нажатии 4 – генерируется код 8. Видно, что здесь используются просто двоичные разряды. Также генерируется код 0 в случае отпускания всех кнопок. Таким образом, мы можем написать следующий код.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[SensorDescription(\"545C8BA5-B143-4545-868F-CA7FD986B4F6\")]\npublic class SwitchArraySensor : Sensor\n&#123;\n    public class SwitchArraySensorData\n    &#123;\n        private static Guid KeyStateProperyId = new Guid(@\"38564a7c-f2f2-49bb-9b2b-ba60f66a58df\");\n\n        public SwitchArraySensorData(SensorReport report)\n        &#123;\n            uint state = (uint) report.Values[KeyStateProperyId][0];\n            Button1Pressed = (state &amp; 0x01) != 0;\n            Button2Pressed = (state &amp; 0x02) != 0;\n            Button3Pressed = (state &amp; 0x04) != 0;\n            Button4Pressed = (state &amp; 0x08) != 0;\n        &#125;\n\n        public bool Button1Pressed &#123; get; private set; &#125;\n        public bool Button2Pressed &#123; get; private set; &#125;\n        public bool Button3Pressed &#123; get; private set; &#125;\n        public bool Button4Pressed &#123; get; private set; &#125;\n    &#125;\n\n    public SwitchArraySensorData Current\n    &#123;\n        get &#123; return new SwitchArraySensorData(DataReport); &#125;\n    &#125;\n\n    public event EventHandler StateChanged;\n\n    public SwitchArraySensor()\n    &#123;\n        DataReportChanged += SwitchArraySensor_DataReportChanged;\n    &#125;\n\n    void SwitchArraySensor_DataReportChanged(Sensor sender, EventArgs e)\n    &#123;\n        if (StateChanged != null)\n        &#123;\n            StateChanged.Invoke(sender, e);\n        &#125;\n    &#125;\n&#125;</code></pre>\n<p>Фактически, этот класс является оберткой в Sensor API для нужного нам сенсора. Для его использования я должен подписаться на событие <code>StateChanged</code> и получать информацию через свойство <code>Current</code>.</p>\n<p>Для получения списка доступных сенсоров заданного типа можно использовать метод <code>GetSensorsByTypeId</code> класса <code>SensorManager</code>. При этом <code>TypeId</code> этих сенсоров будет определяться исходя из заданного атрибута <code>SensorDescription</code>. Теперь, используя эти сенсоры мы можем подписаться на нужные событие и получать данные в удобном для приложения виде. Например, можем отображать на форме состояние нажатия кнопок.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void Window_Loaded(object sender, RoutedEventArgs e)\n&#123;\n    var sensors = SensorManager.GetSensorsByTypeId&lt;SwitchArraySensor>();\n    foreach (SwitchArraySensor sensor in sensors)\n    &#123;\n        switch (sensor.FriendlyName)\n        &#123;\n            case \"Left Switch Array Sensor\":\n                sensor.StateChanged += delegate(object leftSensor, EventArgs arg)\n                &#123;\n                    var buttons = ((SwitchArraySensor)leftSensor).Current;\n                    SwitchState(LeftButton1, buttons.Button1Pressed);\n                    SwitchState(LeftButton2, buttons.Button2Pressed);\n                    SwitchState(LeftButton3, buttons.Button3Pressed);\n                    SwitchState(LeftButton4, buttons.Button4Pressed);\n                &#125;;\n                break;\n            case \"Right Switch Array Sensor\":\n                sensor.StateChanged += delegate(object rightSensor, EventArgs arg)\n                &#123;\n                    var buttons = ((SwitchArraySensor)rightSensor).Current;\n                    SwitchState(RightButton1, buttons.Button1Pressed);\n                    SwitchState(RightButton2, buttons.Button2Pressed);\n                    SwitchState(RightButton3, buttons.Button3Pressed);\n                    SwitchState(RightButton4, buttons.Button4Pressed);\n                &#125;;\n                break;\n        &#125;\n    &#125;\n&#125;</code></pre>\n<p>В итоге получим приложение, которое выглядит следующим образом.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part3/ecdbb3ae/2@2x.png\"  alt=\"Windows 7 Sensor and Location platform - реализация поддержки собственного сенсора (Часть 3/3)\"/>\n\n\n\n\n<p>Конечно, пример с реализацией подобного сенсора достаточно синтетический. Однако, он явно демонстрирует процесс подключения датчика к Sensor API.</p>",
            "tags": [
                "Internet of Things",
                "Windows"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part3/bf8319bb/windows7_sensor-and-location-platform-part3@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part2/",
            "url": "https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part2/",
            "title": "Windows 7 Sensor and Location platform - программирование сенсоров (Часть 2/3)",
            "date_published": "2009-07-01T12:00:00.000Z",
            "content_html": "<p>Недавно я писал о том, что такое Sensor and Location platform в Windows 7 и зачем она нужна. Сейчас мы поговорим о том, каким образом эту платформу можно использовать в своих приложениях.</p>\n<p>Для того, чтобы проводить эксперименты не с виртуальными датчиками, а с чем-то более-менее приближенным к реальности, мы будем использовать устройство от <strong>Freescale semiconductor</strong>, построенное на базе микроконтроллера <code>JMBADGE2008-B</code>. Это устройство представляет собой небольшую плату, на которой также есть несколько датчиков – акселерометр, датчик света и кнопки.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part2/7b98289f/1@2x.png\"  alt=\"Windows 7 Sensor and Location platform - программирование сенсоров (Часть 2/3)\"/>\n\n\n\n\n<p>Это <a href=\"http://www.freescale.com/webapp/sps/site/prod_summary.jsp?code=JMBADGE2008-B\">устройство</a> разработано специально для демонстрации возможностей <strong>Sensor and Location platform</strong> в Windows 7. Фактически, каждый может купить его. Таким образом, это устройство хорошо подходит для того, чтобы продемонстировать эту возможность Windows 7.</p>\n<p>Прежде чем рассматривать конкретные приложения, давайте посмотрим на то, как устроена платформа Sensor and Location. До появления Windows 7 и Sensor&amp;Location platform подключение различных датчиков сводилось к реализации драйвера и программного обеспечения к нему.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part2/a7598ecf/2@2x.png\"  alt=\"Windows 7 Sensor and Location platform - программирование сенсоров (Часть 2/3)\"/>\n\n\n\n\n<p>При такой организации задачи взаимодействия с внешними датчиками можно, но тяжело. Для этого каждое приложение должно взаимодействовать с тем API, которое предложит разработчик датчика и программного обеспечения, которое обслуживает этот датчик. Проблема встает особенно остро в случае, если приложение должно использовать множество однотипных датчиков от разных производителей. Как эту проблему предлагает решить Sensor&amp;Location platform?</p>\n<p>На уровне операционной системы присутствуют механизмы работы с датчиками. Существует стандартный унифицированный программный интерфейс для работы с датчиками – Sensor API. При этом все взаимодействия с датчиком происходят именно через Sensor API. Важно, что взаимодействия со всеми датчиками происходит в едином стиле. Теперь вам не нужно интегрироваться с native API через <code>p/invoke</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part2/8c2cd41b/3@2x.png\"  alt=\"Windows 7 Sensor and Location platform - программирование сенсоров (Часть 2/3)\"/>\n\n\n\n\n<p>Для того, чтобы работать с Sensor and Location API необходимо загрузить соответствующую библиотеку <a href=\"http://code.msdn.microsoft.com/Project/Download/FileDownload.aspx?ProjectName=SensorsAndLocation&DownloadId=5039\">.NET Interop Sample Library</a>. В ней существуют .NET-обертки для работы с Sensor API. В нем существует несколько классов, при помощи которых можно работать с датчиками.</p>\n<p>Класс <code>SensorManager</code> является точкой входа. Через него можно получить информацию о сенсорах, а также поработать с ними. Например, при помощи метода <code>GetSensorBySensorId&lt;&gt;</code> можно получить доступ к интересующему нас датчику. Каждый датчик должен иметь класс-обертку, который наследуется от базового класса <code>Sensor</code>. В <code>.NET Interop Sample Library</code> уже существуют три таких реализации – <code>AmbientLightSensor</code>, <code>Accelerometer3D</code>, <code>UnknownSensor</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part2/25d62557/4@2x.png\"  alt=\"Windows 7 Sensor and Location platform - программирование сенсоров (Часть 2/3)\"/>\n\n\n\n\n<p>Основная идея при работе с датчиками заключается в следующем. При изменении состояния датчика (подключен/отключен/активен/итд) генерируется событие <code>StateChanged</code>. Это событие необходимо для начала или прекращения работы с датчиками. После того, как связь с датчиком налажена, при получении новых данных генерируется событие <code>DataReportChanged</code>. Насколько часто будет генерироваться это событие зависит от реализации датчика и драйвера к нему.  При обработке этого события можно считать состояние датчиков и каким-то образом изменить работу приложения. Для этих целей используется метод <code>GetProperty</code>. В параметрах этого метода передается идентификатор свойства, которое нужно считать от датчика. Как правило, детали вызовов этого метода скрываются в классах, которые реализуются для специфичного датчика.</p>\n<p>Кроме того, каждый датчик имеет собственный идентификатор (<code>GUID</code>), по которому можно идентифицировать устройство. При реализации класса-обертки для датчика этот <code>ID</code> указывается при помощи атрибута. Таким образом, доступ к датчику можно получить как явно указав идентификатор этого датчика, так и сославшись на этот класс-обертку.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">/// &lt;summary>\n/// Represents a generic ambient light sensor\n/// &lt;/summary>\n[SensorDescription( \"97F115C8-599A-4153-8894-D2D12899918A\" )]\npublic class AmbientLightSensor : Sensor\n&#123;\n\n// .....\n// .....\n// .....\n\nvar sensors = SensorManager.GetSensorsByTypeId&lt;AmbientLightSensor>();</code></pre>\n<p>Давайте попробуем реализовать несколько примеров работы с датчиками, которые доступны в устройстве от Freescale. Мы будем работать с двуми типами датчиков – акселерометром (позволяет измерять угол наклона устройства) и датчиком света (измеряет уровень освещенности в помещении).</p>\n<p>Первое приложение, которое мы реализуем будет отображать уровень освещенности в виде горящей лампочки на форме. Для начала подпишемся на событие изменения состояния в Sensor API. Это необходимо для того, чтобы приложение начало работать, если датчик подключен на ходу. В обработчике этого события мы получим список всех датчиков нужного типа и подпишемся у них на событие <code>DataReportChanged</code>. В обработчике этого события мы будем считывать значение с датчика освещенности и записывать его в TextBox на форме. Т.к. событие генерируется в дополнительном потоке, также потребуется сделать вызов метода <code>Dispatcher.Invoke</code>, чтобы обработка шла в основном потоке и мы могли взаимодействовать с элементами на форме. Таким образом получим следующий код.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void Window_Loaded(object sender, RoutedEventArgs e)\n&#123;\n    SensorManager.SensorsChanged += SensorManagerSensorsChanged;\n&#125;\n\nvoid SensorManagerSensorsChanged(SensorsChangedEventArgs change)\n&#123;\n    Dispatcher.Invoke((System.Threading.ThreadStart)(UpdateSensorsList));\n&#125;\n\nprivate void UpdateSensorsList()\n&#123;\n    var sensors = SensorManager.GetSensorsByTypeId&lt;AmbientLightSensor>();\n    foreach (var sensor in sensors)\n        sensor.DataReportChanged += delegate(Sensor sender, EventArgs e)\n        &#123;\n            Dispatcher.Invoke((System.Threading.ThreadStart)(delegate\n            &#123;\n                if (ActiveSensorsListBox.SelectedItem == sender)\n                &#123;\n                    CurrentValue.Text =\n                        ((AmbientLightSensor)sender).CurrentLuminousIntensity.Intensity.ToString();\n                &#125;\n            &#125;));\n        &#125;;\n&#125;</code></pre>\n<p>Теперь в TextBox на форме выводится текущее значение освещенности. Теперь нетрудно реализовать какую-то визуализацию для этого. При помощи привязок в WPF будем отображать степень освещенности в виде лампочек. В итоге получим следующее приложение.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part2/64b11bb3/5@2x.jpg\"  alt=\"Windows 7 Sensor and Location platform - программирование сенсоров (Часть 2/3)\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part2/3e75c79c/6@2x.jpg\"  alt=\"Windows 7 Sensor and Location platform - программирование сенсоров (Часть 2/3)\"/>\n\n\n\n\n<p>Другой датчик более интересен – он позволяет определять степень наклона устройства по разным осям. Для демонстрации степени наклона мы возьмем трехмерную модель самолета для WPF приложения и будем вращать ее в пространстве в зависимости от показателей датчика. Принцип этого приложения аналогичен предыдущему – мы находим нужные датчики, подписываемся на события и при их обработке записываем координаты в поля ввода на форме. После этого привязываем координаты модели к значениям этих полей ввода.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void UpdateSensorsList()\n&#123;\n    foreach (var sensor in SensorManager.GetSensorsByTypeId&lt;Accelerometer3D>())\n    &#123;\n        sensor.DataReportChanged += delegate(Sensor sender, EventArgs e)\n        &#123;\n            Dispatcher.Invoke((System.Threading.ThreadStart)(delegate\n            &#123;\n                    if (UseXCoordinate.IsChecked == true)\n                        CurrentXValue.Text = ((Accelerometer3D)sender).CurrentAcceleration[Accelerometer3D.AccelerationAxis.X].ToString();\n                    if (UseYCoordinate.IsChecked == true)\n                        CurrentYValue.Text = ((Accelerometer3D)sender).CurrentAcceleration[Accelerometer3D.AccelerationAxis.Y].ToString();\n                    if (UseZCoordinate.IsChecked == true)\n                        CurrentZValue.Text = ((Accelerometer3D)sender).CurrentAcceleration[Accelerometer3D.AccelerationAxis.Z].ToString();\n            &#125;));\n        &#125;;\n    &#125;\n&#125;</code></pre>\n<p>Как видно из этого примера, код для работы с датчиками изменился не очень сильно. Фактически, изменился только код получения данных от датчиков, а остальное осталось неизменным.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part2/b99b0221/7@2x.jpg\"  alt=\"Windows 7 Sensor and Location platform - программирование сенсоров (Часть 2/3)\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part2/ff6b80d6/8@2x.jpg\"  alt=\"Windows 7 Sensor and Location platform - программирование сенсоров (Часть 2/3)\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part2/df5f552f/9@2x.jpg\"  alt=\"Windows 7 Sensor and Location platform - программирование сенсоров (Часть 2/3)\"/>\n\n\n\n\n<p>Что интересно, эти датчики могут использовать несколько приложений одновременно. Также в одном приложении можно использовать несколько датчиков. Давайте совместим приложение с вращением трехмерной модели с датчиком света. В этом случае кроме поворота модели будем показывать солнце. Если освещенность в помещении снижается, то и солнце будет скрываться. Чем больше освещения в помещении, тем солнце будет светить интенсивнее. Соответственно в этом приложении используется код из двух предыдущих примерах. Поэтому я не буду приводить код, а сразу покажу результат.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part2/c0d98a04/10@2x.jpg\"  alt=\"Windows 7 Sensor and Location platform - программирование сенсоров (Часть 2/3)\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part2/6787dbc2/11@2x.jpg\"  alt=\"Windows 7 Sensor and Location platform - программирование сенсоров (Часть 2/3)\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part2/5802a04b/12@2x.jpg\"  alt=\"Windows 7 Sensor and Location platform - программирование сенсоров (Часть 2/3)\"/>\n\n\n\n\n<p>На этих примерах хорошо видно, что работа с датчиками в Windows 7 является очень простой. Однако, для этого необходимо иметь драйвер для Windows 7 и класс-обертку для Sensor&amp;Location platform. Как правило, драйвера поставляются самим производителем аппаратной платформы, а вот класс-обертку можно реализовать самостоятельно. В следующий раз мы поговорим как раз на эту тему.</p>",
            "tags": [
                "Internet of Things",
                "Windows"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part2/6daac7a3/windows7_sensor-and-location-platform-part2@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part1/",
            "url": "https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part1/",
            "title": "Windows 7 Sensor and Location platform - что это такое? (Часть 1/3)",
            "date_published": "2009-06-15T12:00:00.000Z",
            "content_html": "<p>Одна из составных частей Windows 7 – <a href=\"http://www.microsoft.com/whdc/sensors/\">Sensor and Location platform</a>. <strong>Sensor and Location</strong> – это часть Windows 7, которая позволяет организовать работу с различными датчиками и дополнительными устройствами для измерения чего-либо.</p>\n<p>Зачем это нужно? Датчики нужны для того, чтобы упросить некоторые тривиальные действия и избавить нас от лишних забот в работе. Особенно актуально это для владельцев ноутбуков, чья жизнь является очень динамичной. Представьте себе, что в компьютер встроен датчик освещения, который доступен для всех приложений и дает возможность этим приложениям корректировать свою картинку в зависимости от освещения. Другим примером может являться датчик GPS-координат. В этом случае приложения могут подстраиваться под ту местность, где вы в данный момент находитесь. Например, приложения могут отображать информацию о погоде именно для того города, где вы находитесь. На самом деле примеров можно привести большое количество, все зависит от фантазии и конкретных случаев. Приложения, которые изменяют свое поведение в зависимости от внешних условий выделяются в отдельный класс приложений и называются контекстно-зависимыми приложениями.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part1/4abdd553/1@2x.jpg\"  alt=\"Windows 7 Sensor and Location platform - что это такое? (Часть 1/3)\"/>\n\n\n\n\n<p>Может возникнуть вопрос – <em>“а что, собственно, изменилось?”</em>, <em>“почему этого нельзя было сделать раньше?”</em>. Ответ прост – ранее эти сценарии также можно было реализовать. Но сделать это было не так просто. Фактически, работа с внешними датчиками сводилась к обмену информацией через COM-порт и каждый датчик имел свой специфический API. По этой причине было очень тяжело организовать какой-то универсальный программный интерфейс, с которым можно было работать одновременно из нескольких приложений и этот процесс был бы прозрачен.</p>\n<p>Именно эту проблему решает библиотека <strong>Sensor and Location</strong>. С ее помощью можно обращаться к различными датчикам и получать от них информацию в едином для всех стилей. Важно, что эта проблема решается на уровне операционной системы. Такой шаг может дать новый толчок для развития контекстно-зависимых приложений. Далее приведена диаграмма, отображающая структуру объектов для работы с датчиками. В следующих частях мы рассмотрим это подробнее.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part1/5e943628/2@2x.png\"  alt=\"Windows 7 Sensor and Location platform - что это такое? (Часть 1/3)\"/>\n\n\n\n\n<p>Для подключения датчика к Sensor and Location platform в Windows 7 необходимо реализовать для него драйвер и несложные классы-обертки на .NET для работы с этим датчиком.</p>\n<p>Безусловно, в ближайшее время конечные пользователи вряд ли смогут в полной мере ощутить мощь всей этой платформы. Для этого потребуется некоторое время, чтобы разработчики аппаратного обеспечения могли разработать и встроить свои датчики в аппаратные платформы. Однако, нам, разработчикам, можно начинать готовиться к этому уже сегодня. Поэтому далее я планирую рассказать о том, каким образом работать с Sensor and Location platform в контексте наших бизнес-приложений.</p>",
            "tags": [
                "Internet of Things",
                "Windows"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/windows7-sensor-and-location-platform-part1/9e1674fd/windows7_sensor-and-location-platform-part1@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/windows7-taskbar-wpf/",
            "url": "https://blog.zwezdin.com/2009/windows7-taskbar-wpf/",
            "title": "Работа с панелью задач Windows 7 в приложениях WPF",
            "date_published": "2009-06-13T12:00:00.000Z",
            "content_html": "<p>Не так давно я опубликовал <a href=\"/tag/win7-taskbar\">серию статей</a>, посвященных возможностям Windows 7 для разработчиков при работе с панелью задач. За это время я получил огромное количество откликов, за что огромное вам спасибо. Что интересно, вопросы были совершенно различного характера. Однако, большая часть вопросов была на тему использования подобной функциональности в приложениях на базе WPF. Действительно, в большинстве примеров я упустил из вида WPF приложения. Это упущение я постараюсь компенсировать здесь.</p>\n<p>В реальности, использование этих возможностей в приложениях WPF не сильно отличается от их реализации в WinForms. Как известно, для использование возможностей панели задач Windows 7 мы пользовались статическим классом WindowsFormsExtensions, в котором содержаться методы-расширения для класса Form (представление формы в Windows Forms). К сожалению, в .NET Interop Sample Library нет подобного класса для WPF приложений. Однако совсем не сложно создать его самостоятельно.</p>\n<p>Если взглянуть на класс <code>WindowsFormsExtensions</code> внимательно, то становится понятно, что все вызовы перенаправляются классу <code>Windows7Taskbar</code>, который для идентификации формы используется <code>Handle</code> окна, с которым производится работа. В случае с Windows Forms у формы есть свойство <code>Handle</code>, которое возвращает нужное значение. Соответственно первая задача – это получить handle окна Windows Presentation Foundation. К сожалению, у окон WPF нет свойства <code>Handle</code>. Однако, можно воспользоваться услугами класса <code>WindowInteropHelper</code> и получить эту информацию. Для этого я сделал еще один метод, который возвращает Handle WPF-окна.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private static IntPtr GetWindowHandle(Window window)\n&#123;\n  return (new WindowInteropHelper(window)).Handle;\n&#125;</code></pre>\n<p>Далее во всех методах-расширениях необходимо изменить тип параметра (тип, к которому применяется extension method) с From на Window. После этого большинство возможностей уже доступны для WPF-приложений.</p>\n<p>Другим ньюансом при работе с панелью задач Windows 7 является то, что в некоторых ситуациях требуется перехватывать сообщения Windows, которые приходят к текущему окну. В Windows Forms для этих целей можно было переопределить метод <code>WndProc</code> и в нем отлавливать все нужные сообщения.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">protected override void WndProc(ref Message m)\n&#123;\n  if (m.Msg == Windows7Taskbar.TaskbarButtonCreatedMessage)\n  &#123;\n    // ...\n  &#125;\n\n  base.WndProc(ref m);\n&#125;</code></pre>\n<p>К сожалению, для окон WPF такого метода нет. Однако, в WPF приложениях можно воспользоваться объектом <code>HwndSource</code> и добавить перехват сообщений Windows через него.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void Window_Loaded(object sender, RoutedEventArgs e)\n&#123;\n  HwndSource.FromHwnd(new WindowInteropHelper(this).Handle).AddHook(WndProc);\n&#125;\n\nprivate IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)\n&#123;\n  if (msg == WM_DWMSENDICONICLIVEPREVIEWBITMAP)\n  &#123;\n    // ...\n  &#125;\n  return IntPtr.Zero;\n&#125;</code></pre>\n<p>В остальном использование возможностей панели задач Windows 7 в приложениях WPF реализуется аналогично Windows Forms.</p>",
            "tags": [
                "WPF"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/windows7-taskbar-wpf/c81274ce/windows7_taskbar-wpf@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/windows7-federated-search/",
            "url": "https://blog.zwezdin.com/2009/windows7-federated-search/",
            "title": "Интеграция ваших источников данных с федеративным поиском Windows 7",
            "date_published": "2009-06-11T12:00:00.000Z",
            "content_html": "<p>Как часто вам приходится искать различную информацию? Скорее всего это происходит каждый день. Нужно отметить, что задача поиска информации далеко не тривиальная. Ситуация осложняется тем, что информация может находится в совершенно различных источниках – в файлах, в сообщениях электронной почты, в документах и т.д. Не секрет, что большая часть информации находится в сети – локальной и глобальной.</p>\n<p>Для упрощения поиска информации в Windows Vista был разработан инструмент Windows Search, который позволяет легко и удобно искать информацию на основе заранее подготовленных индексов. В Windows 7 тема поиска нашла свое продолжение и в новой операционной системе появился инструмент Windows 7 Federated Search.</p>\n<p><strong>Windows 7 Federated Search</strong> – это инструмент, который позволяет искать информацию в сети на основе Windows Explorer. В качестве источника данных в этом случае может выступать все что угодно – корпоративный сайт, интернет-магазин, интернет-аукционы и т.д.</p>\n<p>Отличие федеративного поиска от Windows Search заключается в том, что механизмы федеративного поиска не индексируют источники данных, а просто обращаются к ним с просьбой выполнить поисковый запрос. Федеративный поиск ориентирован именно на распределенные удаленные источники информации. Когда речь идет об удаленных источниках, их индексация может быть неэффективной и привести к излишнему расходу трафика. Именно поэтому выбран подход, при котором задача обработки поискового запроса ложится на плечи удаленного источника. Таким образом, появляется возможность подключить все удаленные источники и выполнять поиск по ним не выходя из привычного Windows Explorer.</p>\n<p>Несмотря на все удобство такого поиска, реализация провайдера поиска является очень простой задачей. Федеративный поиск в Windows 7 базируется на стандарте <a href=\"http://www.opensearch.org/Specifications/OpenSearch/1.1\">OpenSearch 1.1</a> и работает следующим образом. Для выполнения поиска Windows 7 обращается к внешему веб-сервису, построенному на базе REST-подхода. Это означает, что искомая строка, а также другие данные, необходимые для поиска, передаются в URI при обращении к этому веб-сервису. Веб-сервис на основе этих данных должен выполнить поиск в своем источнике данных и вернуть результат в формате RSS или AtomPub. После этого Windows 7 представит результаты поиска из полученных данных в виде файлов и отобразит их пользователю.</p>\n<p>Для добавления своего провайдера поиска в Windows 7 необходимо создать файл описания этого провайдера. Формат этого файла базируется на XML и содержит информацию о данном сервисе поиска, в т.ч. формат URI для обращения к сервису.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-federated-search/8fa2b946/1@2x.png\"  alt=\"Интеграция ваших источников данных с федеративным поиском Windows 7\"/>\n\n\n\n\n<p>Таким образом, для реализации собственного провайдера федеративного поиска в Windows 7 необходимо выполнить два несложных действия – создать REST-сервис для поиска информации и сделать для него файл описания.</p>\n<p>Давайте рассмотрим процесс создания провайдера поиска на следующем примере. Имеется список книг с описанием, автором и другой информацией. В данном случае этот список содержится в файле XML (для демонстрационного примера). В качестве источника данных можно использовать все что угодно. Сделаем провайдер поиска по этому списку.</p>\n<p>Файл описания провайдера поиска представляет собой XML-файл и имеет расширение <code>.osdx</code>. Этот файл имеет следующую структуру.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;?xml version=\"1.0\" encoding=\"utf-8\" ?>\n&lt;OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\">\n  &lt;ShortName>Federated Search sample provider&lt;/ShortName>\n  &lt;Url type=\"application/rss+xml\" template=\"http://somehost.com/search/?q=&#123;searchTerms&#125;&amp;amp;start=&#123;startIndex&#125;&amp;amp;count=&#123;count&#125;\" />\n&lt;/OpenSearchDescription></code></pre>\n<p>В секции <code>Url</code> этого файла задается шаблон адреса, который будет использоваться при обращении к веб-сервису. Видно, что этот адрес может принимать совершенно различный вид. В шаблоне адреса используется несколько секций, в которые будут подставляться значения. Главная секция – это секция <code>searchTerms</code>. В эту секцию будет подставляться строка для поиска. Федеративный поиск Windows 7 получает данные постранично, поэтому существуют секции <code>count</code> и <code>startIndex</code>, которые задают размер и номер страницы. Это необходимо для, того, чтобы Windows смогла получить первые результаты поиска, отобразить их пользователю, а затем заняться остальными элементами.</p>\n<p>В демонстрационном примере будет создан веб-сервис, который будет размещен на локальной машине. По этой причине файл описания провайдера поиска будет иметь следующий вид.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;?xml version=\"1.0\" encoding=\"utf-8\" ?>\n&lt;OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\">\n  &lt;ShortName>Federated Search sample provider&lt;/ShortName>\n  &lt;Url type=\"application/rss+xml\" template=\"http://localhost:8731/FederatedSearchProviderSample/search/&#123;searchTerms&#125;/?start=&#123;startIndex&#125;&amp;amp;count=&#123;count&#125;\" />\n&lt;/OpenSearchDescription></code></pre>\n<p>Последнее, что необходимо сделать – это создать сам сервис, который будет выполнять поиск. В этом случае нет привязки к конкретной технологии и единственное требование – сервис должен возвращать результат в формате RSS/Atom. Понятно, что сам сервис может быть построен на совершенно различных платформах и технологиях. Это может быть PHP, Ruby, Native CGI и т.д. Если рассматривать набор технологий для построения REST-сервисов от Microsoft, то лучший в данном случае выбор – использование возможностей WCF для построения REST-сервисов. Построению подобных сервисов уже уделено немало внимания, поэтому я не буду подробно останавливаться на этом, а опишу лишь ключевые шаги.</p>\n<p>Первое, что необходимо сделать — это определить контракт. В контракте будут присутствовать две операции — выполнения поиска и получения детальной информации.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[ServiceContract]\n[ServiceKnownType(typeof(Atom10FeedFormatter))]\n[ServiceKnownType(typeof(Rss20FeedFormatter))]\npublic interface ISearchProvider\n&#123;\n  [OperationContract]\n  [WebGet(UriTemplate = \"search/&#123;searchTerms&#125;/*\")]\n  SyndicationFeedFormatter Search(string searchTerms);\n\n  [OperationContract]\n  [WebGet(UriTemplate = \"details/&#123;id&#125;\")]\n  Stream Description(string id);\n&#125;</code></pre>\n<p>Самое главное, на что нужно обратить внимание в этот момент – определение шаблона URI. Как видно в данном случае шаблон <code>search/&#123;searchTerms&#125;/*</code> полностью соответствует тому, что был определен в файл описания.</p>\n<p>Остается только реализовать этот сервис. При реализации необходимо учитывать указанные параметры при обращении к сервису (<code>searchTerms</code>, <code>start</code>, <code>count</code>) и разбивать результат поиска на страницы, если это необходимо. Для этого идеально подходят методы LINQ - <code>Take</code>/<code>Skip</code>. Таким образом, реализация сервиса будет выглядеть следующим образом.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class SearchProvider : ISearchProvider\n&#123;\n  public SyndicationFeedFormatter Search(string searchTerms)\n  &#123;\n    int count;\n    int startIndex;\n\n    int.TryParse(WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters.Get(\"count\"), out count);\n    int.TryParse(WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters.Get(\"start\"), out startIndex);\n\n    var result = SearchBooks(searchTerms);\n\n    if (count > 0)\n    &#123;\n      if (startIndex >= 0)\n      &#123;\n        result = result.Skip(count * (startIndex - 1)).Take(count);\n      &#125;\n      else\n      &#123;\n        result = result.Take(count);\n      &#125;\n    &#125;\n\n    return new Rss20FeedFormatter(\n      new SyndicationFeed(\"Federated search sample\", String.Empty, null,\n        from item in result\n        select new SyndicationItem(item.Element(XName.Get(\"name\")).Value,\n          item.Element(XName.Get(\"description\")).Value,\n          new Uri(WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri.ToString() + @\"/details/\" + item.Element(XName.Get(\"id\")).Value))\n          &#123;\n            PublishDate = DateTimeOffset.Parse(item.Element(XName.Get(\"date\")).Value),\n          &#125;));\n  &#125;\n\n  //...\n&#125;</code></pre>\n<p>Также в сервисе присутствуют методы для отображения детальной информации и выполнения поиска. При желании можно загрузить пример и посмотреть их реализацию там.</p>\n<p>После того, как сервис готов и запущен, необходимо открыть файл описания сервиса (<code>.osdx</code>) в Windows и согласиться с предложением добавить провайдер поиска. После этого этот провайдер появится в общем списке провайдеров поиска.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-federated-search/226d4dae/2@2x.png\"  alt=\"Интеграция ваших источников данных с федеративным поиском Windows 7\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-federated-search/6709afee/3@2x.png\"  alt=\"Интеграция ваших источников данных с федеративным поиском Windows 7\"/>\n\n\n\n\n<p>Теперь, когда веб-сервис работает и провайдер поиска успешно добавлен, можно искать на сетевом ресурсе прямо из Windows Explorer. Кроме того, можно воспользоваться окном предварительного просмотра. По умолчанию в него загружается содержимое, которое расположено по адресу, на который ссылается данный пункт (в демонстрационном приложении – это <code>/details/&#123;id&#125;</code>). По этому адресу может быть расположено HTML-содержимое, которое будет отображаться пользователю.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-federated-search/62e9a150/4@2x.png\"  alt=\"Интеграция ваших источников данных с федеративным поиском Windows 7\"/>\n\n\n\n\n<p>Как видно, реализация провайдера поиска для Windows 7 Federated Search является очень простой, однако, может сделать использование ваших данных намного удобней и проще. Одним из наиболее удачных примеров внедрения федеративного поиска по внешним источникам является провайдер поиска для корпоративных сайтов на базе Sharepoint. Почему бы и нам не реализовать подобную функциональность для своих приложений?</p>",
            "tags": [
                "Tips&Tricks"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/windows7-federated-search/d36af119/windows7_federated-search@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2009/windows7-taskbar-animations/",
            "url": "https://blog.zwezdin.com/2009/windows7-taskbar-animations/",
            "title": "Анимация переключения задач в Windows 7",
            "date_published": "2009-06-07T12:00:00.000Z",
            "content_html": "<p>Среди круга пользователей Windows встречаются такие люди, которым не нужны плавные анимации при переключении окон, эффекты скрытия, полупрозрачности. Для таких пользователей важна скорость работы и удобство. Одним словом, то, что им нужно – отзывчивый интерфейс. Именно с таким пользователем совсем недавно мы общались по электронной почте. Его проблема заключалась в том, что при наведении на значок приложения в панели задач ему необходимо подождать какое-то время перед тем, как он увидит окно предварительного просмотра для приложения. По этой причине я решил написать пару советов для такой категории пользователей.</p>\n<h2>Ускорение анимации появления окна предварительного просмотра</h2><p><strong>Ситуация:</strong> при наведении на значок приложения окно предварительного просмотра появляется с задержкой.</p>\n<p>Как ускорить анимацию:</p>\n<ol>\n<li>Запускаем редактор реестра (<code>Пуск</code> → <code>Выполнить</code> → <code>regedit</code>)</li>\n<li>Находим ключ <code>HKEY_CURRENT_USER</code> → <code>Control Panel</code> → <code>Mouse</code></li>\n<li>В этом ключе находим значение <code>MouseHoverTime</code></li>\n<li>Исправляем это значение с <code>400</code> на <code>50</code> <em>(время задержки анимации в миллисекундах)</em></li>\n<li>Перезагружаемся</li>\n</ol>\n<h2>Отключение анимации при сворачивании/разворачивании окон</h2><p><strong>Ситуация:</strong> при сворачивании/разворачивании окон присутствует анимация, которая может мешать в работе. Если это справедливо для вашего случая, то ее можно отключить.</p>\n<p>Отключение анимации:</p>\n<ol>\n<li>Открываем панель управления (<code>Control Panel</code>)</li>\n<li>Открываем раздел <code>System and Security</code>, затем подраздел <code>System</code></li>\n<li>В левой части окна выбираем пункт <code>Advnced system settings</code></li>\n<li>В появившемся окне во владке <code>Advanced</code>, в группе <code>Performance</code> нажимаем кнопку <code>Settings</code></li>\n<li>В появившемся окне выключаем опцию <code>Animate windows when minimizing and maximizing</code></li>\n</ol>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-animations/cd20acd4/1@2x.png\"  alt=\"Анимация переключения задач в Windows 7\"/>",
            "tags": [
                "Tips&Tricks"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/windows7-taskbar-animations/923838bb/windows7_taskbar-animations@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/windows7-taskbar-part10/",
            "url": "https://blog.zwezdin.com/2009/windows7-taskbar-part10/",
            "title": "Программируем Windows 7: Taskbar. Часть 10 – JumpLists",
            "date_published": "2009-06-01T12:00:00.000Z",
            "content_html": "<p>Одной из важнейших функций панели задач в Windows 7 являются Jump Lists. Jump lists отображаются, если выделить значок приложения в панели задач и нажать правую кнопку мыши. Если приложение использует функциональность JumpLists, то кроме стандартных действий (<code>pin</code>, <code>close</code>) появятся еще ряд дополнительных действий, которые облегчают нашу повседневную работу.</p>\n<p>Этой функциональностью панели задач активно пользуются различные приложения. Например, Windows Media Player отображает опции переключения плейлистов. Internet Explorer и Windows Explorer содержат в jump lists с сылками на последние места, куда вы заходили. Windows Live Messanger отображает опции переключения состояния.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part10/311b1c34/1@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 10 – JumpLists\"/>\n\n\n\n\n<p>В jump list могут присутствовать несколько различных типов элементов: задачи, ссылки на последние открытые документы и ссылки на постоянные документы. Кроме того, эти позиции могут быть закреплены. Такие элементы не исчезнут из jump list со временем. Это удобно, например, если мы часто работаем с одним и тем же документом. Схематично jump list можно представить следующим образом.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part10/7a035b01/2@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 10 – JumpLists\"/>\n\n\n\n\n<p>По сути, каждый пункт в jump list представляет собой ссылку на программу или файл. Так, например, мы можем запустить калькулятор, или какой-либо документ заданного формата. К сожалению, у нас нет возможности напрямую перехватить событие нажатия на пункт в jump list. Каждый раз, когда мы выбираем очередной пункт будет запускаться новый экземпляр приложения. Это связано в первую очередь с тем, что jump list можно вызвать даже тогда, когда приложение не запущено. В этом случае оно должно быть закреплено (pinned) на панели задач. Например, на следующем рисунке видно, что Internet Explorer в данный момент не запущен, но jump list мы открыть можем.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part10/f19bd79c/3@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 10 – JumpLists\"/>\n\n\n\n\n<p>Итак, давайте посмотрим каким образом подобная функциональность может быть реализована в наших приложениях. Для работы с jump list нам понадобится объект типа JumpListManager, который входит в состав <a href=\"http://code.msdn.microsoft.com/Windows7Taskbar/Release/ProjectReleases.aspx?ReleaseId=2246\">.NET Interop Sample Library</a>. Создавать его необходимо в момент создания кнопки нашего приложения на панели задач. Для этих целей можно переопределить метод WndProc следующим образом.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">protected override void WndProc(ref Message m)\n&#123;\n    if (m.Msg == Windows7Taskbar.TaskbarButtonCreatedMessage)\n    &#123;\n        _jumpListManager = WindowsFormsExtensions.CreateJumpListManager(this);\n        _jumpListManager.UserRemovedItems += (o, e) =>\n        &#123;\n            e.CancelCurrentOperation = false;\n        &#125;;\n\n        // add items\n\n        _jumpListManager.Refresh();\n    &#125;\n\n    base.WndProc(ref m);\n    &#125;</code></pre>\n<p>Обратите внимание на вызов метода Refresh после создания объекта <code>JumpListManager</code>. Вызов этого метода необходим для обновления позиций в jump list. Также обязательным действием является подписка на событие <code>UserRemovedItems</code>. Оно исполняется когда производится попытка удаления устаревших пунктов из jump list. Давайте теперь попробуем добавить новые позиции в jump list. Для этих целей существуют классы-обертки и нужные методы у объекта <code>JumpListManager</code>.</p>\n<p>Самый простой тип позиций в jump list – задачи. Задачей может быть запуск внешнего приложения или нашего приложения с какими-то параметрами. В WLM в виде задач реализовано переключение состояния пользователя. Для создания задачи в jump list используется метод <code>AddUserTask</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">_jumpListManager.AddUserTask(new ShellLink\n&#123;\n    Path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), \"calc.exe\"),\n    Title = \"Calculator\",\n    Category = \"Applications\",\n    IconLocation = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), \"calc.exe\"),\n    IconIndex = 0\n&#125;);</code></pre>\n<p>Как видим, здесь создается новый объект типа <code>ShellLink</code>, в который передается путь до приложения, заголовок и значок. Я добавил в свое приложение две задачи и получил следующий результат.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part10/3d59a110/4@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 10 – JumpLists\"/>\n\n\n\n\n<p>Другой вариант заполнения jump list – ссылки на документы, которые были загружены ранее. Для этих целей я создал несколько текстовых файлов с расширением <code>.myapp</code> и ассоциировал этот тип файлов со своим приложением. При запуске приложения я проверяю передано ли имя файла в качестве параметра при запуске приложения. Если имя задано, то я считываю этот файл и добавляю его в список ранее загруженных файлов. Для этих целей существует метод <code>AddToRecent</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">if (File.Exists(Environment.GetCommandLineArgs().Skip(1).FirstOrDefault() ?? String.Empty) == true)\n&#123;\n    _jumpListManager.AddToRecent(Environment.GetCommandLineArgs().Skip(1).FirstOrDefault() ?? String.Empty);\n&#125;</code></pre>\n<p>Теперь из Windows Explorer я открою эти файлы. При этом будет запускаться мое демонстрационное приложение (если мы правильно ассоциировали данный тип файлов с приложением). При этом при вызове jump list я увижу, что в категории <code>Recent</code> появились ссылки на ранее открытые файлы.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part10/c1d96ae1/5@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 10 – JumpLists\"/>\n\n\n\n\n<p>Я могу закрепить те позиции, с которыми я работаю чаще всего. В этом случае я смогу быстро открывать нужные мне документы через jump list.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part10/77defe65/6@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 10 – JumpLists\"/>\n\n\n\n\n<p>Другой способ размещения позиций в jump list – создание ссылок на постоянные документы/программы. В этом случае мы также можем сгруппировать эти позиции по категориям. Для этих целей существует метод <code>AddCustomDestination</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">_jumpListManager.AddCustomDestination(new ShellLink\n&#123;\n    Path = @\"about.txt\",\n    Title = \"About\",\n    Category = \"My application\",\n    IconLocation = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), \"shell32.dll\"),\n    IconIndex = 11\n&#125;);</code></pre>\n<p>Я добавил несколько таких ссылок в jump list и получил следующий результат.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part10/bb129ef5/7@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 10 – JumpLists\"/>\n\n\n\n\n<p>На этом рисунке видно, что появились две дополнительные группы со ссылками внутри.</p>\n<p>Замечательной особенностью jump list является то, что его содержимое также доступно в меню <code>Пуск</code>. Так, например, если мы активно пользуемся этой функциональностью, то это можно использоваться и из стартового меню.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part10/4dffd2e1/8@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 10 – JumpLists\"/>\n\n\n\n\n<p>Таким образом мы можем использовать дополнительную функциональность, которую нам предоставляет панель задач Windows 7 для обеспечения максимально комфортной работы пользователя.</p>",
            "tags": [
                ".NET",
                "Windows",
                "Панель задач Windows 7"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/windows7-taskbar-part10/75578584/windows7_taskbar-part10@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2009/windows7-taskbar-part9/",
            "url": "https://blog.zwezdin.com/2009/windows7-taskbar-part9/",
            "title": "Программируем Windows 7: Taskbar. Часть 9 – PeekBitmap",
            "date_published": "2009-05-29T12:00:00.000Z",
            "content_html": "<p>Ранее я писал о возможности панели задач Windows 7 изменять preview для окна. Мы говорили о том, что в preview можно отображать как часть окна, так и собственное изображение. У Windows 7 есть такая приятная особенность, что если мы наведем курсор мыши на preview окна, то все остальные окна скроются, а выделенное окно будет отображаться. Выглядит это следующим образом.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part9/2c993106/1@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 9 – PeekBitmap\"/>\n\n\n\n\n<p>Панель задач Windows 7 в этом случае также позволяет нам задать собственное поведение. В этом случае мы имеем возможность задать содержимое нашей формы нашим собственным изображением. Например, там мы можем написать какой-то полезный текст. В этом случае сценарий работы может выглядеть следующим образом. У пользователя в панели задач находится приложение, которое что-то выполняет. Он может отслеживать его состояние на основе <code>ProgressBar</code> и <code>OverlayIcon</code>, о которых я писал ранее. Если этой информации ему недостаточно, он может навести курсор мыши на иконку приложение. В этом случае ему отобразится preview, в котором содержится дополнительная информация (о чем я тоже писал). Пользователь может навести на preview нужного окна и в этом случае все окна скроются и на экране останется только выделенное окно. В этом окне мы можем вывести еще больше информации, которая необходима пользователю.</p>\n<p>Вообще, по умолчанию при таком сценарии при скрытии остальных окон выведется само содержимое окна. Переопределение содержания окна в этот момент может быть полезно, если на форме информация разрознена и сразу непонятно что происходит в приложении.</p>\n<p>Для того, чтобы реализовать подобный сценарий в нашем приложении мы воспользуемся <a href=\"http://code.msdn.microsoft.com/Windows7Taskbar/Release/ProjectReleases.aspx?ReleaseId=2246\">.NET Interop Sample Library</a>. Для этих целей предназначен метод <code>SetPeekBitmap</code> класса-обертки. Генерировать изображение необходимо в момент времени, когда пользователь наводит на preview окна. Для этих целей переопределим метод WndProc и отловим событие <code>WM_DWMSENDICONICLIVEPREVIEWBITMAP</code>. Как раз в этот момент времени необходимо генерировать изображение.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">protected override void WndProc(ref Message m)\n&#123;\n    if (m.Msg == WM_DWMSENDICONICLIVEPREVIEWBITMAP)\n    &#123;\n        WindowsFormsExtensions.SetPeekBitmap(this, GeneratePeekBitmap(this, Images._111), true);\n    &#125;\n\n    base.WndProc(ref m);\n&#125;</code></pre>\n<p>В данном случае мы вызываем метод, который будет генерировать нужный нам Bitmap. Генерация этого изображения также не представляет какой-либо сложности. В общем случае мы можем скопировать в этот <code>Bitmap</code> снимок нашего окна и поверх этого нарисовать нужную нам информацию. Давайте в демонстрационном приложении я заполню эту область неким фоном и нарисую значок состояния поверх этого.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private static Bitmap GeneratePeekBitmap(Form form, Image stateImage)\n&#123;\n    var preview = new Bitmap(form.ClientSize.Width, form.ClientSize.Height);\n\n    var g = Graphics.FromImage(preview);\n\n    g.DrawImage(Images.background.GetThumbnailImage(form.ClientSize.Width, form.ClientSize.Height, null, IntPtr.Zero), 0, 0);\n\n    if (stateImage != null)\n    &#123;\n        Size thumbSize = new Size(100, 100);\n        g.DrawImage(stateImage.GetThumbnailImage(thumbSize.Width, thumbSize.Height, null, IntPtr.Zero), form.ClientSize.Width / 2 - thumbSize.Width / 2, form.ClientSize.Height / 2 - thumbSize.Height / 2);\n    &#125;\n\n    return preview;\n&#125;</code></pre>\n<p>Обратите внимание, что для корректного отображения размеры этого изображения должны совпадать с размером формы. После этого у нас получится приложение, которое выглядит следующим образом.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part9/3619c7d5/2@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 9 – PeekBitmap\"/>\n\n\n\n\n<p>На самом деле, после это изображение можно устанавливать откуда угодно. Главное, чтобы это изображение также генерировалось в момент обработки указанного выше события. Например, в демонстрационном приложении я также устанавливаю это изображение в таймере. Таким образом я могу навести курсор мыши на preview и наблюдать за тем как изменяется сама форма при срабатывании таймера.</p>\n<p>Кроме того, у метода SetPeekBitmap существует третий параметр логического типа (<code>boolean</code>). Изменяя этот параметр можно указать нужно ли убирать рамку приложения, когда осуществляется подобный просмотр. Например, если я установлю этот параметр в <code>false</code>, то увижу следующий результат.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part9/0a6caf9f/3@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 9 – PeekBitmap\"/>",
            "tags": [
                ".NET",
                "Windows",
                "Панель задач Windows 7"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/windows7-taskbar-part9/a99c0cdc/windows7_taskbar-part9@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2009/windows7-taskbar-part8/",
            "url": "https://blog.zwezdin.com/2009/windows7-taskbar-part8/",
            "title": "Программируем Windows 7: Taskbar. Часть 8 – IconicThumbnail",
            "date_published": "2009-05-27T12:00:00.000Z",
            "content_html": "<p>Недавно я рассказал о том, каким образом можно влиять на preview окна в панели задач Windows 7. Тогда мы отображали только ту часть окна, которая наиболее важна для пользователя. Тем не менее в определенных ситуациях гораздо полезнее может быть отображение не части окна, а совершенно другая картинка, отображающая состояние приложения.</p>\n<p>Например, этой возможностью пользуется Windows Live Messanger. Если мы наведем курсор мыши на значок WLM, то во всплывающем окне он отобразит аватар текущего пользователя.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part8/b7098e80/1@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 8 – IconicThumbnail\"/>\n\n\n\n\n<p>Давайте разберемся с этой возможностью панели задач Windows 7. Для реализации подобного поведения в <a href=\"http://code.msdn.microsoft.com/Windows7Taskbar/Release/ProjectReleases.aspx?ReleaseId=2246\">.NET Interop Sample Library</a> существует метод <code>SetIconicThumbnail</code>. Именно с его помощью мы можем легко и просто создать собственный preview для нашего окна. Однако, прежде чем мы начнем его использовать мы должны разрешить это поведение с помощью метода <code>EnableCustomWindowPreview</code>. В противном случае при выполнении метода <code>SetIconicThumbnail</code> мы получим соответствующее исключение. Сделать это можно прямо в конструкторе формы.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public Form1()\n&#123;\n    InitializeComponent();\n    WindowsFormsExtensions.EnableCustomWindowPreview(this);\n&#125;</code></pre>\n<p>Стоит сказать, что размеры изображения, которое мы хотим отобразить в <code>preview</code> ограничены. Это логично, т.к. представьте что бы было, если preview могли бы быть размером <code>1024x768</code>. Эти размеры ограничены значениями <code>200x120</code>. Изображение может быть меньше этого размера, в этом случае само окно preview также будет уменьшено. Если изображение больше этих размеров, то будет сгенерировано исключение.</p>\n<p>В параметрах метода <code>SetIconicThumbnail</code> передается ссылка на текущую форму и изображение (<code>Bitmap</code>), которое нужно отобразить. Наша задача – сгенерировать этот <code>Bitmap</code>. И в этом случае у нас полностью развязаны руки – мы можем сгенерировать все что нам угодно. Это может быть снимок окна, с каким-то дополнительным текстом поверх. Это может быть какая-то собственная картинка, содержащая информацию. Это может быть текст с некой статистикой. Все зависит от вашей фантазии.</p>\n<p>В демонстрационном приложении я буду генерировать изображение, отображающее состояние приложение текстом и графически, а также небольшой снимок окна. Для этого я создам Bitmap нужных мне размеров и залью его фоном. После этого нарисую там изображение состояния и снимок окна. Затем я напишу на этом изображении текст состояния и результат передам в метод <code>SetIconicThumbnail</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private static void SetState(Form form, string stateText, Image stateImage)\n&#123;\n    // blank image\n    var preview = new Bitmap(200, 120);\n    var g = Graphics.FromImage(preview);\n\n    // fill background\n    g.DrawImage(Images.background, 0, 0);\n\n    // file image of state\n    if (stateImage != null)\n    &#123;\n        g.DrawImage(stateImage.GetThumbnailImage(100, 100, null, IntPtr.Zero), 100, 10);\n    &#125;\n\n    // fill image of form\n    g.DrawImage(GetFormImage(form, 50, 60), 10, 10);\n\n    // draw text\n    g.DrawString(stateText, new Font(\"Verdana\", 18), Brushes.White, 10, 70);\n\n    // setting thumbnail\n    WindowsFormsExtensions.SetIconicThumbnail(form, preview);\n&#125;</code></pre>\n<p>Как видим, код получился достаточно лаконичный. Как результат мы можем увидеть окно со следующим <code>preview</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part8/a5154434/2@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 8 – IconicThumbnail\"/>\n\n\n\n\n<p>Что интересно, наше приложение может само в фоне изменять это состояние. При этом, если мышь находится на значке приложение и preview в данный момент отображается, то смена изображения во всплывающем окне происходит плавно, без каких-либо побочных эффектов. Так, например, я могу создать таймер, в обработчике которого через определенные промежутки времени изменять состояние.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private readonly string[] _stateTexts = new[] &#123; \"Deleting..\", \"Wireless\", \"Security\", \"Stop\", \"Help\", \"O'kay\", \"Playing\", \"Login\", \"Warning\", \"Showcase\", \"Search\", \"Processing..\", \"Locked\", \"Error\", \"Refreshing..\" &#125;;\nprivate readonly Bitmap[] _stateImages = new[] &#123; Images._104, Images._110, Images._111, Images._112, Images._113, Images._114, Images._120, Images._125, Images._129, Images._134, Images._17, Images._25, Images._41, Images._50, Images._52 &#125;;\nprivate int _currentIndex = 0;\n\nprivate void timer1_Tick(object sender, EventArgs e)\n&#123;\n    if (_currentIndex + 1 >= _stateImages.Length)\n    &#123;\n        _currentIndex = 0;\n    &#125;\n    else\n    &#123;\n        _currentIndex++;\n    &#125;\n    SetState(this, _stateTexts[_currentIndex], _stateImages[_currentIndex]);\n&#125;</code></pre>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part8/101bc11c/3@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 8 – IconicThumbnail\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part8/eda328aa/4@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 8 – IconicThumbnail\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part8/4501cc8d/5@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 8 – IconicThumbnail\"/>\n\n\n\n\n<p>В этом случае preview окна нашего приложения будет изменяться динамически, а пользователь может отслеживать это состояние легко и удобно.</p>",
            "tags": [
                ".NET",
                "Windows",
                "Панель задач Windows 7"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/windows7-taskbar-part8/f529cc8c/windows7_taskbar-part8@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2009/windows7-taskbar-part7/",
            "url": "https://blog.zwezdin.com/2009/windows7-taskbar-part7/",
            "title": "Программируем Windows 7: Taskbar. Часть 7 – ThumbnailClip",
            "date_published": "2009-05-26T12:00:00.000Z",
            "content_html": "<p>Как мы уже видели, Windows 7 обладает удобной функциональностью по отображению <code>preview</code> окон приложений. При наведении мыши на значок окна в панели задач можно посмотреть уменьшенное представление окна за считанные секунды. Это очень удобно когда у пользователя открыто большое количество окон.</p>\n<p>По умолчанию в этих <code>preview</code> отображается все содержимое окна. Однако, для некоторых приложений было бы гораздо удобнее показывать в <code>preview</code> не все содержимое окна, а только его часть. Такая функциональность также предусмотрена для панели задач Windows 7 и мы можем использовать ее для своих приложений. Давайте разберемся с этой возможностью панели задач Windows 7.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part7/47ac9be3/1@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 7 – ThumbnailClip\"/>\n\n\n\n\n<p>Как обычно для этого будем использовать <a href=\"http://code.msdn.microsoft.com/Windows7Taskbar/Release/ProjectReleases.aspx?ReleaseId=2246\">.NET Interop Sample Library</a>. В рамках оберток для функций из системных библиотек Windows здесь присутствует метод <code>SetThumbnailClip</code>, который поможет нам в реализации данной функциональности. При вызове данного метода в параметрах необходимо передать текущий экземпляр формы и координаты, ограничивающие область окна.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void Clip5_Click(object sender, EventArgs e)\n&#123;\n    WindowsFormsExtensions.SetThumbnailClip(this, new Rectangle(10, 10, 145, 145));\n&#125;</code></pre>\n<p>На этом примере хорошо видно, что использовать этот метод очень легко.</p>\n<p>Давайте создадим небольшое приложение, где посмотрим на возможности этой функциональности. Для этого я создам пустое приложение, в которое добавлю несколько несколько элементов управления. После запуска приложения preview окна будет выглядеть следующим образом.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part7/1db27651/2@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 7 – ThumbnailClip\"/>\n\n\n\n\n<p>Видно, что изначально в preview отображается все окно. Давайте ограничим область отображения при помощи метода <code>SetThumbnailClip</code>. Отобразим, например, только поля ввода, которые находятся на форме.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part7/95356155/3@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 7 – ThumbnailClip\"/>\n\n\n\n\n<p>Что интересно, если у нас на форме имеется динмический контент (например, видео), то он будет отображаться в динамике. В демонстрационном приложении я разместил несколько анимированных изображений и отобразил их в рамках <code>preview</code>. При этом нет необходимости для этого делать что-то еще.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part7/e360da65/4@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 7 – ThumbnailClip\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part7/f9f962a8/5@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 7 – ThumbnailClip\"/>\n\n\n\n\n<p>Что важно, в процессе работы приложения мы можем изменять содержимое <code>preview</code>. Например, в какой-то момент времени нам может быть необходимо вывести содержимое какого-то важного поля ввода, а в другой момент - отобразить изображение с формы. Такая динамичность может дать пользователю возможность получать актуальную для него информацию.</p>\n<p>Наконец если нам необходимо отобразить все содержимое формы можно использовать тот же самый метод, но передать ему размеры всей формы. В этом случае в <code>preview</code> снова будет отображаться все окно.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void NoClip_Click(object sender, EventArgs e)\n&#123;\n    WindowsFormsExtensions.SetThumbnailClip(this, new Rectangle(new Point(0, 0), Size));\n&#125;</code></pre>",
            "tags": [
                ".NET",
                "Windows",
                "Панель задач Windows 7"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/windows7-taskbar-part7/32bd6877/windows7_taskbar-part7@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2009/windows7-taskbar-part6/",
            "url": "https://blog.zwezdin.com/2009/windows7-taskbar-part6/",
            "title": "Программируем Windows 7: Taskbar. Часть 6 – AppId",
            "date_published": "2009-05-24T12:00:00.000Z",
            "content_html": "<p>Недавно я писал о том, как можно организовать отображение вкладок для дочерних окон нашего приложения. В этом случае нам требовалось отобразить дочерние окна нашего приложения. Для этих целей мы пользовались классом <code>CustomWindowManager</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part6/a17962bb/1@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 6 – AppId\"/>\n\n\n\n\n<p>Такого же эффекта можно добиться, если мы запустим несколько экземпляров одного и того же нашего приложения. В этом случае Windows 7 определит что это одно и то же приложение и сгруппирует окна в один значок.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part6/97a3b183/2@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 6 – AppId\"/>\n\n\n\n\n<p>Давайте попробуем разобраться как это работает. В основе всего лежит такое понятие как <code>Application ID</code>. Для каждого окна мы можем задать свой <code>ID</code>. Эту особенность также можно использовать тогда, когда необходимо избежать такого поведения. В этом случае нужно задать различные <code>AppId</code> для каждого такого окна. Сделать это можно используя <a href=\"http://code.msdn.microsoft.com/Windows7Taskbar/Release/ProjectReleases.aspx?ReleaseId=2246\">.NET Interop Sample Library</a>. Для этого необходимо вызвать метод <code>SetAppId</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void SetAppIdButton_Click(object sender, EventArgs e)\n&#123;\n    WindowsFormsExtensions.SetAppId(this, \"SomeAppId\");\n&#125;</code></pre>\n<p>Таким образом, мы можем задать различные <code>AppId</code> для каждого окна и они не будут группироваться.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part6/86edd4c5/3@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 6 – AppId\"/>\n\n\n\n\n<p>Однако, аналогичным образом также возможно сгруппировать несколько окон от разных приложений. Для этого необходимо задать одинаковый <code>AppId</code> для каждого из окон. И в этом случае панель задач Windows 7 сгруппирует эти приложения в один значок. Например, в данном случае я задал одинаковый <code>AppId</code> для разных окон из разных приложений и эти окна оказались сгруппированными в панели задач Windows 7.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part6/86edd4c5/3@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 6 – AppId\"/>",
            "tags": [
                ".NET",
                "Windows",
                "Панель задач Windows 7"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/windows7-taskbar-part6/a11372ae/windows7_taskbar-part6@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2009/windows7-taskbar-part5/",
            "url": "https://blog.zwezdin.com/2009/windows7-taskbar-part5/",
            "title": "Программируем Windows 7: Taskbar. Часть 5 – CustomWindowsManager",
            "date_published": "2009-05-21T12:00:00.000Z",
            "content_html": "<p>Мы продолжаем изучать возможности панели задач в Windows 7. Наверняка все вы обратили внимание на то, что если одно и то же приложение запустить несколько раз, Windows 7 автоматически сгруппирует их в одну кнопку на панели задач. Кроме того при наведении на значок этого приложения панель задач покажет набор preview для этих окон.</p>\n<p>Однако, мы видим, что такое же поведение реализовано для открытых вкладок в Internet Explorer 8. В этом случае фактически запущен один экземпляр IE8 (технические детали мы опустим) и много вкладок. В этом случае Internet Explorer отображает их в виде нескольких preview. В данном случае это очень полезно, т.к. прямо из панели задач можно сразу переключиться на нужную вкладку.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part5/47914dd8/1@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 5 – CustomWindowsManager\"/>\n\n\n\n\n<p>Как можно догадаться, такую функциональность несложно реализовать и для своего приложения. Это актуально, если в вашем окне содержится набор других окон (как в случае с IE8). Для этих целей в <a href=\"http://code.msdn.microsoft.com/Windows7Taskbar/Release/ProjectReleases.aspx?ReleaseId=2246\">.NET Interop Sample Library</a> присутствует класс CustomWindowsManager. Давайте подробнее рассмотрим создание такого приложения.</p>\n<p>Итак, для начала нам необходимо создать приложение. Пусть это приложение будет работать в режиме MDI. Наша цель – добиться того, чтобы все дочерние окна этого приложения также отображались в панели задач. После создания основного и дочернего окна давайте займемся последним.</p>\n<p>Нам необходим момент отображения окна, когда это окно уже создано и готово к работе. Очень хорошо для этих целей подходит событие <code>OnShown</code>. В обработчике мы должны создать экземпляр объекта <code>CustomWindowsManager</code> и передать ему <code>Handle</code> дочернего окна, а также родительского окна, с точки зрения модели MDI.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">CustomWindowsManager _windowsManager;\n&amp;nbsp;\nprotected override void OnShown(EventArgs args)\n&#123;\n    _windowsManager =\n        CustomWindowsManager.CreateWindowsManager(Handle, MdiParent.Handle);\n\n    base.OnShown(args);\n&#125;</code></pre>\n<p>Мы сохраним ссылку на объект <code>CustomWindowsManager</code>, она понадобится нам позже. Однако, этих действий недостаточно для корректной работы. Во-первых, мы должны подписаться на событие <code>ThumbnailRequested</code>, в котором сгенерировать Bitmap, содержащий отображение нашего окна для всплывающих окон в панели задач. Во-вторых, мы должны подписаться на событие PeekRequested, в котором сгенерировать Bitmap, содержащий отображения нашего окна для выделения его в момент наведения курсора мыши на preview окна в панели задач.</p>\n<p>Событие <code>ThumbnailRequested</code> содержит параметр типа <code>BitmapRequestedEventArgs</code>. Этот объект будет управлять тем, что будет отображаться на панели задач. Самое простое, что мы можем сделать, это указать параметр UseWindowScreenshot равным true. В этом случае будет сделан скриншот окна без вашего участия. Если же хочется отобразить какую-то собственную картинку в preview окна, то можно воспользоваться параметром <code>Bitmap</code>, куда положить свой объект <code>Bitmap</code>, который должен быть отображен. Однако, нужно понимать, что этот объект должен иметь строго заданные размеры. Эти размеры мы также можем получить из <code>BitmapRequestedEventArgs</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">_windowsManager.ThumbnailRequested += (o, e) =>\n&#123;\n    Bitmap bmp = new Bitmap(e.Width, e.Height);\n    this.DrawToBitmap(bmp, new Rectangle(0, 0, e.Width, e.Height));\n    e.Bitmap = bmp;\n&#125;;</code></pre>\n<p>Понятно, что здесь мы можем манипулировать этим объектом так как нам удобно. Например, можем наложить на <code>preview</code> нашу картинку.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">_windowsManager.ThumbnailRequested += (o, e) =>\n&#123;\n    Bitmap bmp = new Bitmap(e.Width, e.Height);\n    this.DrawToBitmap(bmp, new Rectangle(0, 0, e.Width, e.Height));\n    Graphics.FromImage(bmp).DrawImage(Images.coffeecup, 35, 5);\n    e.Bitmap = bmp;\n&#125;;</code></pre>\n<p>Событие <code>PeekRequested</code> предназначено для того, чтобы выделить окно при наведении мыши на его <code>preview</code> в панели задач. Здесь также присутствует параметр типа <code>BitmapRequestedEventArgs</code>. Аналогичным образом можно использовать свойства <code>UseWindowScreenshot</code> и <code>Bitmap</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">_windowsManager.PeekRequested += (o, e) =>\n&#123;\n    var result = new Bitmap(e.Width, e.Height);\n    DrawToBitmap(result, new Rectangle(0, 0, e.Width, e.Height));\n    e.Bitmap = result;\n&#125;;</code></pre>\n<p>Обратите внимание, что в этих примерах я использую метод <code>DrawToBitmap</code> формы. Однако, я могу выводить <code>preview</code> любого другого элемента управления. Например, я могу отображать <code>TextBox</code>.</p>\n<p>Ну и, наконец, необходимо вызвать метод <code>WindowClosed</code> когда окно закрывается. Для этого хорошо подходит событие <code>OnClosed</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">protected override void OnClosed(EventArgs e)\n&#123;\n    if (_windowsManager != null)\n    &#123;\n        _windowsManager.WindowClosed();\n    &#125;\n\n    base.OnClosed(e);\n&#125;</code></pre>\n<p>После этого мы получили следующее приложение.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part5/3d1b6629/2@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 5 – CustomWindowsManager\"/>\n\n\n\n\n<p>Если мы посмотрим на панель задач, то увидим следующее.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part5/26de6474/3@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 5 – CustomWindowsManager\"/>\n\n\n\n\n<p>Однако, если мы начнем изменять внешний вид формы, то увидим, что preview не изменилось. Это вызвано тем, что Windows 7 не запрашивала событие получения <code>preview</code>. Это особенно критично, если на форме находится контент, который постоянно изменяется, например видео. В этом случае есть способ обновить preview принудительно. Для этого необходимо вызвать метод <code>InvalidatePreviews</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void InvalidateButton_Click(object sender, EventArgs e)\n&#123;\n    _windowsManager.InvalidatePreviews();\n&#125;</code></pre>\n<p>Этот метод в каждом конкретном случае надо вызывать в нужные моменты времени. Например, при обновлении текста в <code>TextBox</code>. Для видео хороший вариант – вызывать его по таймеру.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part5/c83df49d/4@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 5 – CustomWindowsManager\"/>",
            "tags": [
                ".NET",
                "Windows",
                "Панель задач Windows 7"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/windows7-taskbar-part5/7f7a52a1/windows7_taskbar-part5@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2009/windows7-taskbar-part4/",
            "url": "https://blog.zwezdin.com/2009/windows7-taskbar-part4/",
            "title": "Программируем Windows 7: Taskbar. Часть 4 – Custom OverlayIcon",
            "date_published": "2009-05-19T12:00:00.000Z",
            "content_html": "<p>Ранее я писал о том, как можно добавить значок состояния для вашего приложения в панели задач Windows 7. Мы рассматривали статические изображения для этих целей. Напомню, что для этого мы использовали метод <code>SetTaskbarOverlayIcon</code>, в параметрах которого передается ссылка на <code>Icon</code>.</p>\n<p>Однако, один из читателей блога предложил другой сценарий, в котором на месте дополнительной иконки отображается какая-то информация. К примеру, это может быть текущая скорость закачки, если ваше ПО что-то загружает по сети. Либо это может быть количество новых писем в почтовом ящике, если это почтовый клиент. Примеров можно много привести.</p>\n<p>Напомню, что для того, чтобы задать статическое изображение необходимо выполнить следующий код.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">WindowsFormsExtensions.SetTaskbarOverlayIcon(this, Icons.Error, \"Error\");</code></pre>\n<p>В качестве второго параметра здесь передается объект <code>Icon</code>. Однако кто нам мешает генерировать этот объект динамически? Давайте воспользуемся нехитрым кодом и сделаем это. Я создам метод, который будет генерировать такое изображение и покажу эту иконку.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private static Icon BuildIcon(int param)\n&#123;\n    Bitmap image = Icons.BLANK2334242;\n    Graphics.FromImage(image).DrawString(param.ToString(@\"D2\"), new Font(\"Arial\", 54), Brushes.White, 10, 25);\n    return Icon.FromHandle(image.GetHicon());\n&#125;\n\nprivate void ShowStatus(object sender, EventArgs e)\n&#123;\n    WindowsFormsExtensions.SetTaskbarOverlayIcon(this, BuildIcon(50, \"Status\");\n&#125;</code></pre>\n<p>Таким образом, при помощи метода <code>BuildIcon</code> будет сгенерирована новая иконка, которая будет отображена на панели задач.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part4/461f7e57/1@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 4 – Custom OverlayIcon\"/>\n\n\n\n\n<p>На данном скриншоте хорошо видно, что к стандартному значку мы добавили текст и отобразили это на панели задач.</p>\n<p>В демонстрационном приложении я создал таймер, который имитирует работу менеджера загрузки, выдавая постоянно разную “скорость” закачки.</p>",
            "tags": [
                ".NET",
                "Windows",
                "Панель задач Windows 7"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/windows7-taskbar-part4/4ab0568b/windows7_taskbar-part4@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/windows7-taskbar-part3/",
            "url": "https://blog.zwezdin.com/2009/windows7-taskbar-part3/",
            "title": "Программируем Windows 7: Taskbar. Часть 3 – OverlayIcon",
            "date_published": "2009-05-18T12:00:00.000Z",
            "content_html": "<p>На этот раз мы поговорим о другой особенности панели задачи Windows 7 – OverlayIcon. Эта возможность исключительно полезна для приложений, которые обладают каким-то состоянием с точки зрения пользователя.</p>\n<p>К таким приложениям можно отнести программы для обмена мгновенными сообщениями (IM). Например, Windows Live Messanger активно использует эту возможность. Например, если мы откроем WLM и будем изменять состояние, то можно увидеть как оно отображается на панели задач.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part3/a745d492/1@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 3 – OverlayIcon\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part3/e4bbbb59/2@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 3 – OverlayIcon\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part3/3310b94c/3@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 3 – OverlayIcon\"/>\n\n\n\n\n<p>Такая функциональность может быть полезна для множества приложений. К счастью реализовать ее не так сложно. Как и в предыдущих примерах мы воспользуемся библиотекой <a href=\"http://code.msdn.microsoft.com/Windows7Taskbar/Release/ProjectReleases.aspx?ReleaseId=2246\">.NET Interop Sample Library</a>.</p>\n<p>Чтобы добавить иконку состояния к основному значку приложения, необходимо добавить файл ресурсов в проект и разместить там нужные иконки. Также, мы можем получить объекты <code>Icon</code> из другого места, если это необходимо.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part3/bac0ba4a/4@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 3 – OverlayIcon\"/>\n\n\n\n\n<p>Теперь все, что необходимо сделать – это воспользоваться методами расширения, позволяющими задавать иконки для нашего приложения. Для этих целей определен метод <code>SetTaskbarOverlayIcon</code>. Мы должны передать ему в параметрах нашу форму, иконку и описание. Т.о., установка новой иконки из кода будет выглядеть следующим образом.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">WindowsFormsExtensions.SetTaskbarOverlayIcon(this, Icons.Error, \"Error\");</code></pre>\n<p>Также мы можем удалить этот значок. Для этого необходимо передать значение <code>null</code> вместо самой иконки.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">WindowsFormsExtensions.SetTaskbarOverlayIcon(this, null, String.Empty);</code></pre>\n<p>Давайте создадим для примера небольшое приложение, в котором можем переключать состояние. Выглядеть оно будет следующим образом.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part3/4c84e2e6/5@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 3 – OverlayIcon\"/>\n\n\n\n\n<p>При нажатии на каждую из кнопок переключается состояние, которое отображается в панели задач.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part3/ce36ad99/6@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 3 – OverlayIcon\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part3/6934bb8b/7@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 3 – OverlayIcon\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part3/96141433/8@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 3 – OverlayIcon\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part3/0e1ff239/9@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 3 – OverlayIcon\"/>\n\n\n\n\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part3/c3c9554b/10@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 3 – OverlayIcon\"/>",
            "tags": [
                ".NET",
                "Windows",
                "Панель задач Windows 7"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/windows7-taskbar-part3/d370086f/windows7_taskbar-part3@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/windows7-taskbar-part2/",
            "url": "https://blog.zwezdin.com/2009/windows7-taskbar-part2/",
            "title": "Программируем Windows 7: Taskbar. Часть 2 - ThumbButtons",
            "date_published": "2009-05-08T12:00:00.000Z",
            "content_html": "<p>Недавно я рассказывал о том, как в Windows 7 можно отображать прогресс выполнения операции прямо в панели задач Windows. На этот раз мы продолжим разговаривать про возможности Windows 7 для программиста и рассмотрим возможность добавления собственных кнопок управления в preview окна.</p>\n<p>Подобную функциональность вы уже могли заметить при использовании Windows 7. Например, подобные кнопки существуют для Windows Media Player. Они позволяют переключать треки, а также останавливать воспроизведение. Всего таких кнопок можно создать не более семи.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part2/c84245cb/1@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 2 - ThumbButtons\"/>\n\n\n\n\n<p>Несомненно, такая функциональность может быть полезна не только для Media Player, но и для наших приложений. Давайте посмотрим как можно реализовать это в нашем приложении.</p>\n<p>Как я уже говорил, для всех системных функций Windows 7 существует обертка на .NET, которая называется <a href=\"http://code.msdn.microsoft.com/Windows7Taskbar/Release/ProjectReleases.aspx?ReleaseId=2246\">.NET Interop Sample Library</a>. Мы пользовались услугами этой библиотеки когда управляли состоянием progress bar. Сейчас мы также воспользуемся этой библиотекой.</p>\n<p>Создание наших кнопок должно происходить в момент обработки события WM_TaskbarButtonCreated. Поэтому в форме необходимо переопределить метод WndProc и обрабатывать моменты появления этого события.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">protected override void WndProc(ref Message m)\n&#123;\n    if (m.Msg == Windows7Taskbar.TaskbarButtonCreatedMessage)\n    &#123;\n        // initialize buttons\n    &#125;\n\n    base.WndProc(ref m);\n&#125;</code></pre>\n<p>Для инициализации кнопок необходим объект <code>ThumbButtonManager</code>. Этот объект управляет поведением и отображением этих кнопок. Этот объект можно создать, используя метод расширения <code>CreateThumbButtonManager</code>. После этого необходимо воспользоваться методом <code>CreateThumbButton</code> и создать объект кнопки. После того, как все кнопки будут созданы необходимо добавить их на панель задач при помощи метода <code>AddThumbButtons</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">protected override void WndProc(ref Message m)\n&#123;\n    if (m.Msg == Windows7Taskbar.TaskbarButtonCreatedMessage)\n    &#123;\n        InitializeThumbButtons();\n    &#125;\n\n    base.WndProc(ref m);\n&#125;\n\nprotected void InitializeThumbButtons()\n&#123;\n    ThumbButtonManager thumbButtonManager =\n        WindowsFormsExtensions.CreateThumbButtonManager(this);\n\n    var decreaseThumbButton = thumbButtonManager.CreateThumbButton(1,\n        Icons.Navigation_First_2, \"To reduce the progress\");\n\n    decreaseThumbButton.Clicked += delegate\n    &#123;\n      // ..\n    &#125;;\n\n    thumbButtonManager.AddThumbButtons(decreaseThumbButton);\n&#125;</code></pre>\n<p>Теперь, при запуске приложения можно увидеть что появилась одна кнопка управления. Однако, если мы попробуем нажать на нее, то увидим, что обработчик события не срабатывает. Для того, чтобы обработчик начал работать необходимо в методе <code>WndProc</code> явно передать возможность объекту <code>ThumbButtonManager</code> обрабатывать события.</p>\n<p>В итоге получим следующий несложный код.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private ThumbButtonManager _thumbButtonManager;\n\nprotected override void WndProc(ref Message m)\n&#123;\n    if (m.Msg == Windows7Taskbar.TaskbarButtonCreatedMessage)\n    &#123;\n        InitializeThumbButtons();\n    &#125;\n\n    if (_thumbButtonManager != null)\n        _thumbButtonManager.DispatchMessage(ref m);\n\n    base.WndProc(ref m);\n&#125;\n\nprotected void InitializeThumbButtons()\n&#123;\n    if (_thumbButtonManager == null)\n    &#123;\n        _thumbButtonManager = WindowsFormsExtensions.CreateThumbButtonManager(this);\n    &#125;\n\n    var decreaseThumbButton = _thumbButtonManager.CreateThumbButton(1,\n      Icons.Navigation_First_2, \"To reduce the progress\");\n\n    decreaseThumbButton.Clicked += delegate\n    &#123;\n        Progress.Text = (float.Parse(Progress.Text) - 10).ToString();\n        WindowsFormsExtensions.SetTaskbarProgress(this, float.Parse(Progress.Text));\n    &#125;;\n\n    // other buttons\n\n    _thumbButtonManager.AddThumbButtons(decreaseThumbButton, normalStateThumbButton,\n      indeterminateStateThumbButton, pauseStateThumbButton,\n      errorStateThumbButton, increaseThumbButton);\n&#125;</code></pre>\n<p>Это приложение содержит кнопки для управления прогрессом (как и в прошлом случае) и содержит 6 кнопок.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part2/7c7daffa/2@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 2 - ThumbButtons\"/>",
            "tags": [
                ".NET",
                "Windows",
                "Панель задач Windows 7"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/windows7-taskbar-part2/f4b31a4d/windows7_taskbar-part2@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2009/jlinq/",
            "url": "https://blog.zwezdin.com/2009/jlinq/",
            "title": "jLinq – LINQ for JavaScript",
            "date_published": "2009-05-05T12:00:00.000Z",
            "content_html": "<p>На сегодняшний день огромное число разработчиков используют JavaScript в своих проектах. Однако, само по себе программирование на JavaScript – мероприятие не очень приятное. Благодаря этому за последнее время мы могли наблюдать как появляются одни за другими новые JavaScript Framework`и (jQuery, ExtJS, Prototype, Moo и т.д) и библиотеки.</p>\n<p>Сегодня я хочу рассказать об еще одной библиотеке – jLinq.</p>\n<p>Уникальность <strong>jLinq</strong> заключается в том, что с ее помощью мы можем делать LINQ-запросы в JavaScript коде. Выглядит запрос на jLinq следующим образом.</p>\n<pre class=code language-javascript><code class=\"code__container language-javascript\">var results = jLinq.from(data.users)\n    .startsWith(\"first\", \"a\")\n    .orEndsWith(\"y\")\n    .orderBy(\"admin\",\"age\")\n    .select();</code></pre>\n<p>Таким образом, мы можем получить преимущества LINQ при программировании на JavaScript.</p>",
            "tags": [
                "LINQ",
                "JavaScript"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/jlinq/355a12a7/jlinq@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/windows7-taskbar-part1/",
            "url": "https://blog.zwezdin.com/2009/windows7-taskbar-part1/",
            "title": "Программируем Windows 7: Taskbar. Часть 1 - Progress Bar",
            "date_published": "2009-04-30T12:00:00.000Z",
            "content_html": "<p>Новая операционная система Windows 7 содержит большое количество нововведений и улучшений. Эти улучшения касаются безопасности, производительности, надежности и т.д. Серьезное внимание также уделено и пользовательскому интерфейсу. Для разработчиков ПО на платформе Windows новая ОС также представляет интерес, т.к. в ней содержатся элементы, на которые можно воздействовать программно. В течении нескольких постов мы поговорим об основных нововведениях и программной модели для них.</p>\n<p>Первое что бросается в глаза при работе с Windows 7 – это, конечно, обновленный task bar. В новом taskbar’е действительно много концептуальных изменений. Одно из таких изменений – возможность отображения прогресса выполнения задачи (progress bar).</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part1/0f3a2ce1/1@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 1 - Progress Bar\"/>\n\n\n\n\n<p>На этом рисунке хорошо видно, что прямо на панели задач отображается информация о процессе копирования. Такая функциональность реализована в Windows 7 для копирования файлов на диске, загрузке данных из сети (IE8). Однако, важно, что эту функциональность мы можем использовать и для своих приложений. Сценариев может быть огромное количество – отображение процесса преобразования, копирования, формирования данных, построение отчетов, генерация изображений, и т.д.</p>\n<p>С точки зрения разработчика клиентских приложений для Windows, процесс использования данной функциональности представляется достаточно просто. Взаимодействие с ОС происходит на unmanaged уровне, поэтому для .NET приложений необходима реализация управляемых оберток. Всю эту работу уже проделали разработчики из Microsoft и поместили это в библиотеку <a href=\"http://code.msdn.microsoft.com/Windows7Taskbar/Release/ProjectReleases.aspx?ReleaseId=2246\">.NET Interop Sample Library</a>.</p>\n<p>Библиотека .NET Interop Sample Library состоит из множества компонентов и демонстрационных приложений. Мы не будем подробно останавливаться на каждом из них. Для нас важно, что в ее состав входит <code>Vista Bridge Sample Library</code>, <code>Windows7.DesktopIntegration</code> и <code>Windows7.DesktopIntegration.Registration</code>.</p>\n<p>Проект <code>Windows7.DesktopIntegration</code> содержит те классы, которые необходимы нам для работы с taskbar’ом. В составе этого проекта есть класс <code>WindowsFormsExtensions</code>, который содержит набор методов расширения (extension methods) для класса Form (Windows Forms). В нашем случае нас интересуют сдледующие методы:</p>\n<ul>\n<li><code>SetTaskbarProgress(float percent)</code></li>\n<li><code>SetTaskbarProgressState(ThumbnailProgressState state)</code></li>\n</ul>\n<p>Вызов первого метода позволяет указать процент выполнения текущей задачи. Т.к. это методы расширения для класса Form, то этот класс передается в метод в качестве параметра. Это необходимо для того, чтобы определить handle окна, для которого выполняются действия.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">// процесс выполнения = 35%\n\n// вызов метода расширения\nWindowsFormsExtensions.SetTaskbarProgress(this, 35);\n\n// или так (this = Form)\nthis.SetTaskbarProgress(35);</code></pre>\n<p>У нас также имеется возможность указать состояние прогресс-бара. Доступные состояния:</p>\n<ul>\n<li><code>NoProgress</code> – прогресс не отображается</li>\n<li><code>Indeterminate</code> – прогресс бар постоянно мерцает</li>\n<li><code>Normal</code> – обычное отображение прогресса</li>\n<li><code>Error</code> – отображение ошибки</li>\n<li><code>Paused</code> – отображение паузы</li>\n</ul>\n<p>Задание состояние прогресс-бара осуществляется при помощи второго метода.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">WindowsFormsExtensions.SetTaskbarProgressState(this, Windows7Taskbar.ThumbnailProgressState.Normal);\n\n// или так\n\nthis.SetTaskbarProgressState(Windows7Taskbar.ThumbnailProgressState.Normal);</code></pre>\n<p>К сожалению, методы расширения существуют только для WinForms приложений. Однако, несложно построить подобный класс и для WPF приложений (см. демо-приложение).</p>\n<p>В итоге я собрал небольшое приложение, которое демонстрирует возможности прогресс-бара в панели задач Windows 7. Выглядит оно следующим образом.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part1/5dad68a3/2@2x.png\"  alt=\"Программируем Windows 7: Taskbar. Часть 1 - Progress Bar\"/>\n\n\n\n\n<p>С помощью управляющих кнопок мы можем указать текущее значение прогресс-бара, а также можем выбрать его состояние.</p>\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part1/b669911b/3@2x.png\"  alt=\"Режим &#34;Normal&#34;\"/><figcaption>Режим \"Normal\"</figcaption></figure>\n\n\n\n\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part1/a669cefa/4@2x.png\"  alt=\"Режим &#34;Indeterminate&#34;\"/><figcaption>Режим \"Indeterminate\"</figcaption></figure>\n\n\n\n\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part1/fbab175a/5@2x.png\"  alt=\"Режим &#34;Error&#34;\"/><figcaption>Режим \"Error\"</figcaption></figure>\n\n\n\n\n\n\n\n    <figure><img src=\"https://blog.zwezdin.com/2009/windows7-taskbar-part1/0e166312/6@2x.png\"  alt=\"Режим &#34;Paused&#34;\"/><figcaption>Режим \"Paused\"</figcaption></figure>",
            "tags": [
                ".NET",
                "Windows",
                "Панель задач Windows 7"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/windows7-taskbar-part1/b79b8a6b/windows7_taskbar-part1@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/visualstatemanager/",
            "url": "https://blog.zwezdin.com/2009/visualstatemanager/",
            "title": "VisualStateManager или как удобно определить внешний вид приложений на WPF и Silverlight",
            "date_published": "2009-04-29T12:00:00.000Z",
            "content_html": "<p>Все мы знаем, что технологии WPF и XAML позволяют достаточно существенно изменять внешний вид приложения. Однако, как выясняется, далеко не все знают как это можно делать удобно.</p>\n<p>Для определения внешнего вида элементов управления мы обычно используем <code>ControlTemplate</code>, а динамику задаем используя триггеры. Несомненно, триггеры в XAML – мощнейший инструмент благодаря которому у нас появляется столько дополнительных возможностей. Тем не менее при построении сложных интерфейсов логика, построенная на триггерах бывает нетривиальной и непрозрачной, а порой и вовсе задает определенные ограничения.</p>\n<p>Альтернативным способом построения задания внешнего вида элементов управления является использование объекта <code>VisualStateManager</code>. Я не буду вдаваться в глубокие технические детали реализации <code>VisualStateManager</code>, упомяну лишь самое необходимое.</p>\n<p><code>VisualStateManager</code> – это <code>DependencyObject</code>, со всеми вытекающими последствиями. Мы можем задать поведение этого объекта для каждого элемента управления, которым хотим управлять. Идея VSM основана на реализации состояния элемента управления. Мы можем определить набор состояний (<code>VisualState</code>), в которых может находится элемент управлений и задать для каждого состояния свой вид. Состояния упаковываются в группы (<code>VisualStateGroup</code>). Таким образом, можно определить логические группы состояний и определить в них переходы.</p>\n<p>Каждое состояние (<code>VisualState</code>) по сути представляет собой <code>Storyboard</code>. Реализация состояния заключается в том, что при переходе в это состояние срабатывает заданный Storyboard, т.е. запускается анимация.</p>\n<p>Можно графически представить представление описанной структуры.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/visualstatemanager/e19b9908/1@2x.png\"  alt=\"VisualStateManager или как удобно определить внешний вид приложений на WPF и Silverlight\"/>\n\n\n\n\n<p>Давайте создадим небольшой пример на основе состояний. Создадим новый <code>UserControl</code> в WPF приложении с самой простой структурой.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;UserControl x:Class=\"WpfApplication15.UserControl1\"\n  xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n  xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">\n  &lt;Grid>\n    &lt;Border Name=\"State1Panel\" Background=\"Red\" Opacity=\"1\"/>\n\n    &lt;TextBlock Text=\"Text of control\" Foreground=\"White\" HorizontalAlignment=\"Center\" VerticalAlignment=\"Center\"/>\n  &lt;/Grid>\n&lt;/UserControl></code></pre>\n<p>Как видно, это обычный элемент управления с текстом и красным фоном. Давайте определим два состояния для этого элемента управления – <code>State1</code> и <code>State2</code>. Каждое из состояний визуально будет отличаться фоном. Для этого опишем два объекта <code>VisualState</code> и зададим анимации перехода. В нашем случае просто положим друг на друга два элемента <code>Border</code> с разным фоном. Анимация будет заключаться в скрытии и отображении нужного элемента в зависимости от состояния.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;UserControl x:Class=\"WpfApplication15.UserControl1\"\n  xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n  xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">\n  &lt;Grid>\n    &lt;VisualStateManager.VisualStateGroups>\n      &lt;VisualStateGroup x:Name=\"Common\">\n        &lt;VisualState x:Name=\"State1\">\n          &lt;Storyboard>\n              &lt;DoubleAnimation To=\"1\" Duration=\"0:00:00.4\" Storyboard.TargetName=\"State1Panel\" Storyboard.TargetProperty=\"(UIElement.Opacity)\" />\n              &lt;DoubleAnimation To=\"0\" Duration=\"0:00:00.7\" Storyboard.TargetName=\"State2Panel\" Storyboard.TargetProperty=\"(UIElement.Opacity)\" />\n          &lt;/Storyboard>\n        &lt;/VisualState>\n        &lt;VisualState x:Name=\"State2\">\n          &lt;Storyboard>\n              &lt;DoubleAnimation To=\"0\" Duration=\"0:00:00.7\" Storyboard.TargetName=\"State1Panel\" Storyboard.TargetProperty=\"(UIElement.Opacity)\" />\n              &lt;DoubleAnimation To=\"1\" Duration=\"0:00:00.4\" Storyboard.TargetName=\"State2Panel\" Storyboard.TargetProperty=\"(UIElement.Opacity)\" />\n          &lt;/Storyboard>\n        &lt;/VisualState>\n      &lt;/VisualStateGroup>\n    &lt;/VisualStateManager.VisualStateGroups>\n    &lt;Border Name=\"State2Panel\" Background=\"Green\" Opacity=\"0\"/>\n    &lt;Border Name=\"State1Panel\" Background=\"Red\" Opacity=\"1\"/>\n\n    &lt;TextBlock Text=\"Text of control\" Foreground=\"White\" HorizontalAlignment=\"Center\" VerticalAlignment=\"Center\"/>\n  &lt;/Grid>\n&lt;/UserControl></code></pre>\n<p>Теперь, используя <code>VisualStateManager</code>, можно переключать состояния элемента управления и таким образом влиять на внешний вид. Для этого разместим элемент управления на форме.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Window x:Class=\"WpfApplication15.Window1\"\n  xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n  xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n  xmlns:WpfApplication15=\"clr-namespace:WpfApplication15\"\n  Title=\"Window1\" Height=\"300\" Width=\"300\">\n  &lt;Grid>\n    &lt;Grid.RowDefinitions>\n      &lt;RowDefinition/>\n      &lt;RowDefinition Height=\"40\"/>\n    &lt;/Grid.RowDefinitions>\n    &lt;WpfApplication15:UserControl1 x:Name=\"Control1\" Height=\"118\" VerticalAlignment=\"Top\" Margin=\"50,12,101,0\" />\n\n    &lt;StackPanel Orientation=\"Horizontal\" Grid.Row=\"1\">\n      &lt;Button Name=\"State1Button\" Width=\"75\" Click=\"State1Button_Click\">State1&lt;/Button>\n\n      &lt;Button Name=\"State2Button\" Width=\"75\" Click=\"State2Button_Click\">State2&lt;/Button>\n    &lt;/StackPanel>\n  &lt;/Grid>\n&lt;/Window></code></pre>\n<p>И создадим обработчики нажатия кнопок.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void State1Button_Click(object sender, RoutedEventArgs e)\n&#123;\n  VisualStateManager.GoToState(Control1, \"State1\", true);\n&#125;\n\nprivate void State2Button_Click(object sender, RoutedEventArgs e)\n&#123;\n  VisualStateManager.GoToState(Control1, \"State2\", true);\n&#125;</code></pre>\n<p>В итоге имеем следующее приложение.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/visualstatemanager/09b35053/2@2x.png\"  alt=\"VisualStateManager или как удобно определить внешний вид приложений на WPF и Silverlight\"/>\n\n\n\n\n<p>Таким образом мы можем создать несколько состояний нашего элемента управления и на основе него строить пользовательский интерфейс.</p>\n<p>Такой подход становится особенно полезным, когда мы видим, что для стандартных элементов управления существует набор предопределенных состояний. Например, для кнопки – <code>Normal</code>, <code>MouseOver</code>, <code>Pressed</code>, <code>Disabled</code> и т.д. Такие состояния есть и у других элементов. Благодаря такой возможности существенно упрощается создание шаблонов для элементов управления. Например, кнопку мы можем описать следующим образом.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Style TargetType=\"&#123;x:Type Button&#125;\">\n  &lt;Setter Property=\"Margin\" Value=\"5\"/>\n  &lt;Setter Property=\"Width\" Value=\"100\"/>\n  &lt;Setter Property=\"Template\">\n    &lt;Setter.Value>\n      &lt;ControlTemplate TargetType=\"&#123;x:Type Button&#125;\">\n        &lt;Grid>\n          &lt;VisualStateManager.VisualStateGroups>\n            &lt;VisualStateGroup x:Name=\"Common\">\n              &lt;VisualState x:Name=\"Normal\"/>\n              &lt;VisualState x:Name=\"MouseOver\">\n                &lt;Storyboard>\n                    &lt;DoubleAnimation To=\"0\" Duration=\"0:00:00.5\" Storyboard.TargetName=\"NormalBorder\" Storyboard.TargetProperty=\"(UIElement.Opacity)\" />\n                    &lt;DoubleAnimation To=\"1\" Duration=\"0:00:00.3\" Storyboard.TargetName=\"OverBorder\" Storyboard.TargetProperty=\"(UIElement.Opacity)\" />\n                    &lt;DoubleAnimation From=\"0\" To=\"10\" AutoReverse=\"True\" Duration=\"0:00:00.3\" Storyboard.TargetName=\"OverBorderRotateTransform\" Storyboard.TargetProperty=\"Angle\" />\n                &lt;/Storyboard>\n              &lt;/VisualState>\n              &lt;VisualState x:Name=\"Pressed\">\n                &lt;Storyboard>\n                    &lt;DoubleAnimation To=\"0\" Duration=\"0:00:00.5\" Storyboard.TargetName=\"NormalBorder\" Storyboard.TargetProperty=\"(UIElement.Opacity)\" />\n                    &lt;DoubleAnimation To=\"1\" Duration=\"0:00:00.3\" Storyboard.TargetName=\"OverBorder\" Storyboard.TargetProperty=\"(UIElement.Opacity)\" />\n                    &lt;DoubleAnimation From=\"0\" To=\"360\" AutoReverse=\"True\" Duration=\"0:00:00.4\" Storyboard.TargetName=\"OverBorderRotateTransform\" Storyboard.TargetProperty=\"Angle\" />\n                &lt;/Storyboard>\n              &lt;/VisualState>\n            &lt;/VisualStateGroup>\n          &lt;/VisualStateManager.VisualStateGroups>\n\n          &lt;Border Name=\"NormalBorder\" CornerRadius=\"5\" Background=\"Gray\" Opacity=\"1\"/>\n          &lt;Border Name=\"OverBorder\" CornerRadius=\"5\" Background=\"Green\" Opacity=\"0\">\n            &lt;Border.RenderTransform>\n                &lt;RotateTransform x:Name=\"OverBorderRotateTransform\" CenterX=\"50\" CenterY=\"20\" />\n            &lt;/Border.RenderTransform>\n          &lt;/Border>\n\n          &lt;ContentPresenter Content=\"&#123;TemplateBinding Content&#125;\" Margin=\"5\" HorizontalAlignment=\"Center\" VerticalAlignment=\"Center\"/>\n        &lt;/Grid>\n      &lt;/ControlTemplate>\n    &lt;/Setter.Value>\n  &lt;/Setter>\n&lt;/Style></code></pre>\n<p>Теперь наши кнопки будут выглядеть следующим образом.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/visualstatemanager/7da5a97b/3@2x.png\"  alt=\"VisualStateManager или как удобно определить внешний вид приложений на WPF и Silverlight\"/>\n\n\n\n\n<p>Аналогичным образом можно настраивать внешний вид в приложениях на Silverlight.</p>",
            "tags": [
                "WPF",
                "XAML",
                "Silverlight"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/visualstatemanager/ed5c0f70/visualstatemanager@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2009/silverlight-3d/",
            "url": "https://blog.zwezdin.com/2009/silverlight-3d/",
            "title": "3D-возможности в Silverlight 3",
            "date_published": "2009-04-13T12:00:00.000Z",
            "content_html": "<p>Как известно, в WPF присутствуют замечательные возможности по работе с 3D. Silverlight в этом отношении существенно уступает WPF, т.к. является кросс-платформенной технологией. Так было до второй версии Silverlight. Однако, в третьей версии поддержка 3D появилась. Эту тему мы как раз и рассмотрим.</p>\n<p>На самом деле, если взглянуть чуть детальнее на возможности 3D в Siliverlight 3, то можно быстро понять что это не та поддержка, которая присутствует в WPF. Это, собственно, и логично. По своей сути, возможности 3D в Silverlight сводятся к получению проекций изображения под разными углами по трём осям. Конечно, это не то, чего хотелось бы в идеале, тем не менее эта функциональность позволяет представить наше приложение в трехмерном виде.</p>\n<p>За получение 3D-проекций в Silverlight 3 отвечает свойство <code>Projection</code>, которое доступно для всех объектов. Используя это свойство можно задать параметры отображения элемента. Давайте создадим обычную панель, в которую положим стандартные элементы управления.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;StackPanel Name=\"InputPanel\" Grid.Row=\"1\">\n  &lt;TextBlock Text=\"Text:\"/>\n  &lt;TextBox />\n  &lt;ComboBox />\n  &lt;Button Content=\"Go!\"/>\n&lt;/StackPanel></code></pre>\n<p>Теперь получим 3D-проекцию нашей панели. Повернем панель по оси X.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;StackPanel Name=\"InputPanel\" Grid.Row=\"1\">\n  &lt;StackPanel.Projection>\n    &lt;PlaneProjection RotationX=\"20\" />\n  &lt;/StackPanel.Projection>\n  &lt;TextBlock Text=\"Text:\"/>\n  &lt;TextBox />\n  &lt;ComboBox />\n  &lt;Button Content=\"Go!\"/>\n&lt;/StackPanel></code></pre>\n<p>Что интересно, значения параметров 3D-проекций можно изменять при помощи привязок и в рамках различных действий анимации (что логично). Это делает проекции Silverlight вполне пригодным для использования механизмом.</p>\n<p>Давайте протестируем это. Создадим три элемента управления <code>Slider</code>. Привяжем каждый такой элемент управления к своей оси.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;StackPanel>\n  &lt;Slider Value=\"&#123;Binding ElementName=InputPanel, Path=Projection.RotationX, Mode=TwoWay&#125;\" Minimum=\"0\" Maximum=\"720\" />\n  &lt;Slider Value=\"&#123;Binding ElementName=InputPanel, Path=Projection.RotationY, Mode=TwoWay&#125;\" Minimum=\"0\" Maximum=\"720\" />\n  &lt;Slider Value=\"&#123;Binding ElementName=InputPanel, Path=Projection.RotationZ, Mode=TwoWay&#125;\" Minimum=\"0\" Maximum=\"720\" />\n&lt;/StackPanel>\n\n&lt;StackPanel Name=\"InputPanel\" Grid.Row=\"1\">\n  &lt;StackPanel.Projection>\n    &lt;PlaneProjection RotationX=\"20\" />\n  &lt;/StackPanel.Projection>\n  &lt;TextBlock Text=\"Text:\"/>\n  &lt;TextBox />\n  &lt;ComboBox />\n  &lt;Button Content=\"Go!\"/>\n&lt;/StackPanel></code></pre>\n<p>Теперь мы можем вращать изображение в 3D-пространстве в режиме реального времени! :))) Вот как выглядит приложение.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/silverlight-3d/2d1cc647/1@2x.png\"  alt=\"3D-возможности в Silverlight 3\"/>",
            "tags": [
                "XAML",
                "Silverlight"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/silverlight-3d/655d5693/silverlight_3d@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2009/silverlight-hyperlink/",
            "url": "https://blog.zwezdin.com/2009/silverlight-hyperlink/",
            "title": "Использование гиперссылок внутри текста в Silverlight 3",
            "date_published": "2009-04-10T12:00:00.000Z",
            "content_html": "<p>Вставить гиперссылку внутри какого-то текста – совершенно обычная задача для web-приложений. Очень часто мы делаем это при создании HTML документов. К сожалению, элементы управления в Silverlight не позволяют вставлять гиперссылки внутри текста. Давайте рассмотрим пример того, как можно обойти это ограничение.</p>\n<p>Если быть до конца честным, то в Silverlight 3 есть элемент управления <code>HyperlinkButton</code>, который все-таки позволяет вставлять гиперссылки в приложения Silverlight. Проблема этого контрола заключается в том, что по умолчанию невозможно вставить его внутри текста. Одним из решением данной проблемы может быть построение нового элемента управления следующим образом.</p>\n<p>Во-первых, разобьем наше предложение на слова. Разделителем может быть, например, пробел и знаки препинания. После этого необходимо будет понять какие из слов являются гиперссылками. Для этих целей можно использовать, например, регулярные выражения.</p>\n<p>Во-вторых, для каждого слова создадим собственный элемент управления. Если слово не является гиперссылкой, то создадим для него элемент управления <code>TextBlock</code>. Если слово является гиперссылкой, то создадим для него элемент управления <code>HyperlinkButton</code>.</p>\n<p>В третьих, разместим все наши элементы управления в контейнере <code>WrapPanel</code>.</p>\n<p>Собственный элемент управления можно реализовать несколькими способами. В качестве базы можно использовать <code>UserControl</code> или какие-то другие типы контролов. Однако, удобнее в качестве базового объекта выбрать <code>WrapPanel</code>, т.к. никакой дополнительной логики в нашем случае не требуется.</p>\n<p>Давайте рассмотрим процесс построения такого элемента управления по шагам.</p>\n<p><strong>Шаг 1:</strong> Создадим элемент управления и унаследуем его от <code>WrapPanel</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class LinkTextBlock : WrapPanel\n&#123;\n  //...\n&#125;</code></pre>\n<p><strong>Шаг 2:</strong> Создадим свойство, которое содержит текст.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class LinkTextBlock : WrapPanel\n&#123;\n  public static DependencyProperty TextProperty;\n\n  static LinkTextBlock()\n  &#123;\n    TextProperty = DependencyProperty.Register(\"Text\", typeof(string), typeof(LinkTextBlock), new PropertyMetadata(String.Empty, TextChangedCallback));\n    // ...\n  &#125;\n\n  private static void TextChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)\n  &#123;\n    var control = (LinkTextBlock)d;\n    control.BuildControls();\n  &#125;\n\n  public string Text\n  &#123;\n    get &#123; return (string)GetValue(TextProperty); &#125;\n    set &#123; SetValue(TextProperty, value); &#125;\n  &#125;\n\n  // ...\n&#125;</code></pre>\n<p><strong>Шаг 3:</strong> Определим метод для разбивки предложения на слова. Будем запускать этот метод всякий раз когда обновляется текст (метод <code>TextChangedCallback</code>).</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private void BuildControls()\n&#123;\n  Children.Clear();\n\n  foreach (UIElement control in from word in Text.Split(' ')\n                                select BuildWordControl(word))\n  &#123;\n    Children.Add(control);\n  &#125;\n&#125;</code></pre>\n<p><strong>Шаг 4:</strong> Определяем метод для построения элементов управления для каждого слова.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">private UIElement BuildWordControl(string text)\n&#123;\n  UIElement result;\n  string url, displayText;\n\n  if ((CheckLinkMethod != null) &amp;&amp; (CheckLinkMethod(text, out url, out displayText) == true))\n  &#123;\n    Uri navigateUri = new Uri(..);\n\n    result = new HyperlinkButton() &#123; Content = displayText, Tag = url, NavigateUri = navigateUri &#125;;\n    ((HyperlinkButton)result).Click += delegate(object sender, RoutedEventArgs e)\n                                            &#123;\n                                                // do something\n                                            &#125;;\n  &#125;\n  else\n  &#123;\n    result = new TextBlock() &#123; Text = text &#125;;\n  &#125;\n\n  return result;\n&#125;</code></pre>\n<p>На этом элемент управления готов к употреблению :)</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/silverlight-hyperlink/2c92ad77/1@2x.png\"  alt=\"Использование гиперссылок внутри текста в Silverlight 3\"/>\n\n\n\n\n<p>В финальном коде я вставил делегаты, которые позволяют задавать логику определения гиперссылок, стили для слов, подсказки и другие вспомогательные элементы. Скачать исходный код проекта можно по ссылке ниже.</p>",
            "tags": [
                "XAML",
                "Silverlight"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/silverlight-hyperlink/b43402e4/silverlight_hyperlink@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2009/tooltip-silverlight-3-part2/",
            "url": "https://blog.zwezdin.com/2009/tooltip-silverlight-3-part2/",
            "title": "Tooltip в Silverlight 3 – часть 2",
            "date_published": "2009-04-07T12:00:00.000Z",
            "content_html": "<p>Совсем недавно мы поговорили о том, каким образом можно переопределить внешний вид всплывающей подсказки в Silverlight приложениях. После этого подсказка стала выглядеть более интересно. Однако, эффект появления так и остается унылым :) Давайте попробуем изменить способ появления этой подсказки путем добавления анимации.</p>\n<p>Однако, хочу сразу оговориться о том, что задание анимации требует использования триггеров, а их, к сожалению, нельзя задать из стилей. По этой причине на этот раз откажемся от использования стилей.</p>\n<p>Итак, самое простое что мы можем анимировать – это свойство <code>Opacity</code>. Давайте сделаем это.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Button Content=\"Click me!\" Width=\"100\" Height=\"100\">\n  &lt;ToolTipService.ToolTip>\n    &lt;ToolTip Name=\"RootLayout\" Width=\"100\" Height=\"100\" >\n      &lt;ToolTip.Triggers>\n        &lt;EventTrigger>\n          &lt;BeginStoryboard>\n            &lt;Storyboard>\n                &lt;DoubleAnimation Storyboard.TargetName=\"RootLayout\"\n                   Storyboard.TargetProperty=\"Opacity\"\n                   From=\"0\"\n                   To=\"1\"\n                   Duration=\"0:0:00.2\"/>\n            &lt;/Storyboard>\n          &lt;/BeginStoryboard>\n        &lt;/EventTrigger>\n      &lt;/ToolTip.Triggers>\n      &lt;TextBlock Text=\"123\"/>\n    &lt;/ToolTip>\n  &lt;/ToolTipService.ToolTip>\n&lt;/Button></code></pre>\n<p>Из приведенного кода видно, что в процессе анимации изменяется прозрачность главного элемента. Однако подобная анимация мало чем отличается от стандартного поведения. Давайте добавим больше динамичности. Реализуем появление подсказки в виде маятника.</p>\n<p>Для этих целей нам потребуется добавить две трансформации – <code>ScaleTransofrm</code> и <code>RenderTransform</code>. После этого можно изменять параметры этих трансформаций и моделировать поведение маятника.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;DoubleAnimation Storyboard.TargetName=\"ScaleMe\" Storyboard.TargetProperty=\"ScaleX\" From=\"0\" To=\"1\" Duration=\"0:0:00.1\"/>\n&lt;DoubleAnimation Storyboard.TargetName=\"ScaleMe\" Storyboard.TargetProperty=\"ScaleY\" From=\"0\" To=\"1\" Duration=\"0:0:00.1\"/>\n&lt;DoubleAnimationUsingKeyFrames Storyboard.TargetName=\"RotateMe\" Storyboard.TargetProperty=\"Angle\">\n  &lt;DoubleAnimationUsingKeyFrames.KeyFrames>\n    &lt;DoubleKeyFrameCollection>\n      &lt;LinearDoubleKeyFrame KeyTime=\"0:00:00.1\" Value=\"-15\" />\n      &lt;LinearDoubleKeyFrame KeyTime=\"0:00:00.2\" Value=\"15\" />\n      &lt;LinearDoubleKeyFrame KeyTime=\"0:00:00.3\" Value=\"-11\" />\n      &lt;LinearDoubleKeyFrame KeyTime=\"0:00:00.4\" Value=\"11\" />\n      &lt;LinearDoubleKeyFrame KeyTime=\"0:00:00.5\" Value=\"-3\" />\n      &lt;LinearDoubleKeyFrame KeyTime=\"0:00:00.6\" Value=\"3\" />\n      &lt;LinearDoubleKeyFrame KeyTime=\"0:00:00.7\" Value=\"0\" />\n    &lt;/DoubleKeyFrameCollection>\n  &lt;/DoubleAnimationUsingKeyFrames.KeyFrames>\n&lt;/DoubleAnimationUsingKeyFrames></code></pre>",
            "tags": [
                "XAML",
                "Silverlight"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/tooltip-silverlight-3-part2/1d175589/tooltip_silverlight-3-part2@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/tooltip-silverlight-3-part1/",
            "url": "https://blog.zwezdin.com/2009/tooltip-silverlight-3-part1/",
            "title": "Tooltip в Silverlight 3 – часть 1",
            "date_published": "2009-04-06T12:00:00.000Z",
            "content_html": "<p>Достаточно часто в наших приложениях нам требуется создавать всплывающие подсказки. В Silverlight для этого существует класс <code>ToolTip</code>.</p>\n<p>С помощью этого класса мы можем достаточно легко создавать такие подсказки:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Button Content=\"Click me!\" Width=\"100\" Height=\"100\">\n  &lt;ToolTipService.ToolTip>\n    &lt;ToolTip Width=\"100\" Height=\"100\">\n      &lt;TextBlock Text=\"123\"/>\n    &lt;/ToolTip>\n  &lt;/ToolTipService.ToolTip>\n&lt;/Button></code></pre>\n<p>Как видно, код достаточно прост и лаконичен. Однако, подсказки, которые мы видим при этом могут показаться скучными.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/tooltip-silverlight-3-part1/c747ef5a/1@2x.png\"  alt=\"Tooltip в Silverlight 3 – часть 1\"/>\n\n\n\n\n<p>Давайте посмотрим на то, каким образом можно определить поведение этих подсказок в более интересном виде. Также как и в WPF мы можем переопределить общий шаблон для объекта <code>ToolTip</code> и существенно изменить его внешний вид.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Button Content=\"Click me!\" Width=\"100\" Height=\"100\">\n  &lt;ToolTipService.ToolTip>\n    &lt;ToolTip Width=\"100\" Height=\"100\">\n      &lt;ToolTip.Template>\n        &lt;ControlTemplate TargetType=\"ToolTip\">\n          &lt;Border CornerRadius=\"5\" Background=\"Red\">\n            &lt;ContentPresenter Margin=\"5\" Content=\"&#123;TemplateBinding Content&#125;\"/>\n          &lt;/Border>\n        &lt;/ControlTemplate>\n      &lt;/ToolTip.Template>\n\n      &lt;TextBlock Text=\"123\"/>\n    &lt;/ToolTip>\n  &lt;/ToolTipService.ToolTip>\n&lt;/Button></code></pre>\n<p>Теперь наша подсказка стала более привлекательной.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/tooltip-silverlight-3-part1/fe5c9e58/2@2x.png\"  alt=\"Tooltip в Silverlight 3 – часть 1\"/>\n\n\n\n\n<p>Понятно, что определять <code>ControlTemplate</code> каждый раз для каждой подсказки дело неблагодарное, поэтому определим подсказки стиль.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Button Content=\"Click me!\" Width=\"100\" Height=\"100\">\n  &lt;ToolTipService.ToolTip>\n    &lt;ToolTip Width=\"100\" Height=\"100\">\n      &lt;ToolTip.Style>\n        &lt;Style TargetType=\"ToolTip\">\n          &lt;Setter Property=\"Template\">\n            &lt;Setter.Value>\n              &lt;ControlTemplate TargetType=\"ToolTip\">\n                &lt;Border CornerRadius=\"5\" Background=\"Red\">\n                  &lt;ContentPresenter Margin=\"5\" Content=\"&#123;TemplateBinding Content&#125;\"/>\n                &lt;/Border>\n              &lt;/ControlTemplate>\n            &lt;/Setter.Value>\n          &lt;/Setter>\n        &lt;/Style>\n      &lt;/ToolTip.Style>\n\n      &lt;TextBlock Text=\"123\"/>\n    &lt;/ToolTip>\n  &lt;/ToolTipService.ToolTip>\n&lt;/Button></code></pre>\n<p>После этого можно вынести этот стиль в ресурсы и использовать его многократно.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;UserControl.Resources>\n  &lt;Style x:Key=\"TooltipStyle\" TargetType=\"ToolTip\">\n    &lt;Setter Property=\"Template\">\n      &lt;Setter.Value>\n        &lt;ControlTemplate TargetType=\"ToolTip\">\n          &lt;Border CornerRadius=\"5\" Background=\"Red\">\n            &lt;ContentPresenter Margin=\"5\" Content=\"&#123;TemplateBinding Content&#125;\"/>\n          &lt;/Border>\n        &lt;/ControlTemplate>\n      &lt;/Setter.Value>\n    &lt;/Setter>\n  &lt;/Style>\n&lt;/UserControl.Resources>\n\n&lt;!-- .... --->\n\n&lt;Button Content=\"Click me!\" Width=\"100\" Height=\"100\">\n  &lt;ToolTipService.ToolTip>\n    &lt;ToolTip Width=\"100\" Height=\"100\" >\n      &lt;TextBlock Text=\"123\"/>\n    &lt;/ToolTip>\n  &lt;/ToolTipService.ToolTip>\n&lt;/Button></code></pre>\n<p>Теперь наша подсказка стала более приятной визуально.</p>\n<p>Однако, справедливости ради, нужно сказать, что вынесение в стили не всегда возможно, но об этом мы поговорим в следующий раз.</p>",
            "tags": [
                "XAML",
                "Silverlight"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/tooltip-silverlight-3-part1/a3c690a2/tooltip_silverlight-3-part1@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/adonet-data-services-sqlcompact/",
            "url": "https://blog.zwezdin.com/2009/adonet-data-services-sqlcompact/",
            "title": "Использование ADO.NET Data Services и SQL Compact",
            "date_published": "2009-03-30T12:00:00.000Z",
            "content_html": "<p>На этот раз мы с вами посмотрим каким образом можно работать с данными из <strong>SQL Compact</strong> поверх <strong>ADO.NET Data Services</strong>.</p>\n<p><strong>Важное замечание.</strong> На самом деле, <strong>SQL Compact</strong> не совсем правильно использовать совместно с <strong>ADO.NET Data Services</strong>. Дело в том, что SQL Compact изначально не ориентирован на режим многопользовательской работы и предназначен для других целей – для использования в качестве локального хранилища. <em>Использование SQL Compact в качестве хранилища данных в данном случае может отрицательно сказаться на производительности вашего веб-сервиса.</em></p>\n<p>Тем не менее, не исключено, что в вашем случае необходим будет именно <strong>SQL Compact</strong>. Именно для этого случая давайте посмотрим как же все-таки можно построить веб-сервис на основе SQL Compact в качестве хранилища данных. Для того, чтобы это сделать нужно выполнить несколько простых шагов:</p>\n<ol>\n<li>Создаем, собственно, сам файл данных SQL Compact. Для этого можно воспользоваться либо средствами Visual Studio, либо сделать это, используя SQL Management Studio;</li>\n<li>Определиться уровнем доступа данных – какую технологию будем использовать. Как вариант, можно использовать ADO.NET Entity Framework или LINQ to SQL. Если используем последнюю, то генерируем модель данных используя утилиту <code>SQLMetal</code>, добавляем явное указание первичных ключей и, если это необходимо, добавляем реализацию <code>IUpdatable</code>;</li>\n<li>Создаем веб-сервис;</li>\n<li>Видим ошибку о том, что невозможно инстанциировать класс модели данных. Дело в том, что LINQ to SQL модель для файлов данных SQL Compact не имеет конструктора по умолчанию (без параметров). Поэтому переопределим метод <code>CreateDataSource()</code> у веб-сервиса, в котором явно создадим новую модель данных, в конструкторе которой передадим строку подключения;</li>\n<li>Запустим веб-сервис и увидим ошибку о том, что использовать SQL Compact в данном случае очень не хорошо. Согласимся с этим утверждением, еще раз подумаем действительно ли нам это так необходимо, осознаем все последствия и, если мы до сих пор решительно настроены использовать SQL Compact, настроим среду для возможности использования последнего. Для этого в какой-то начальный момент времени (например, в момент <code>Application_Start</code>) необходимо соответствующим образом сконфигурировать среду исполнения.</li>\n</ol>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">AppDomain.CurrentDomain.SetData(\"SQLServerCompactEditionUnderWebHosting\", true);</code></pre>\n<p>Удобнее всего это сделать в файле <code>Global.asax</code>, в котором как раз можем создать обработчик данного события.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class Global : System.Web.HttpApplication\n&#123;\n    protected void Application_Start(object sender, EventArgs e)\n    &#123;\n        AppDomain.CurrentDomain.SetData(\"SQLServerCompactEditionUnderWebHosting\", true);\n    &#125;\n\n    //...</code></pre>\n<p>Теперь наш сервис может работать поверх данных на базе SQL Compact и даже изменять эти данные.</p>",
            "tags": [
                "Windows Communication Foundation"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/adonet-data-services-sqlcompact/6e7b4db7/adonet_data-services-sqlcompact@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/adonet-data-services-update/",
            "url": "https://blog.zwezdin.com/2009/adonet-data-services-update/",
            "title": "Обновление данных в рамках сервиса ADO.NET Data Services на основе LINQ to SQL",
            "date_published": "2009-03-27T13:00:00.000Z",
            "content_html": "<p>Мы с вами продолжаем исследовать возможности использования <strong>LINQ to SQL</strong> в качестве исходной модели данных для <strong>ADO.NET Data Services</strong>. Сегодня мы поговорим о возможности изменения данных.</p>\n<p>Платформа <strong>ADO.NET Data Services</strong> предоставляет нам возможность не только выбирать данные через веб-сервис, но и обновлять их используя методы <code>POST</code>/<code>PUT</code>/<code>MERGE</code>/<code>DELETE</code>. В рамках самой модели данных необходимо реализовать интерфейс <code>IQueryable</code> (для каждой коллекции) для выборки данных, и <code>IUpdatable</code> (для всего класса-контекста) для обновления.</p>\n<p><em>Вернемся к LINQ to SQL.</em> Если мы попробуем создать модель данных LINQ to SQL, предоставить к ней доступ через <strong>ADO.NET Data Services</strong> и попробовать обновить данные, то мы с досадой обнаружим, что веб-сервис генерирует ошибку. При неглубоком изучении причины ошибки можно легко догадаться, что проблема заключается в том, что модель LINQ to SQL к сожалению не реализует <code>IUpdateable</code>. Здесь можно долго ругаться на разработчиков, но причины такого исхода очевидны: интерфейс <code>IUpdatable</code> – это составная часть ADO.NET Data Services. LINQ to SQL вышел намного раньше, чем ADO.NET Data Services. Отсюда можно сделать вывод, что реализовать еще не готовый интерфейс команда LINQ to SQL не могла.</p>\n<p>Тем не менее, ограничение действительно серьезное и может существенно повлиять на конечное решение. Можно, конечно, отказаться от использования LINQ to SQL, но ведь это не наш метод :) Как справится с этой проблемой? Очень просто – реализовать <code>IUpdatable</code> вручную :)</p>\n<p>Однако, взглянув на интерфес <code>IUpdatable</code> можно прийти в уныние, ведь там необходимо реализовать 12 методов с непрозрачной, на первый взгляд, логикой.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">partial class NewsDataContext : IUpdatable\n&#123;\n    public void AddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded)\n    &#123;\n        throw new System.NotImplementedException();\n    &#125;\n\n    public void ClearChanges()\n    &#123;\n        throw new System.NotImplementedException();\n    &#125;\n\n    public object CreateResource(string containerName, string fullTypeName)\n    &#123;\n        throw new System.NotImplementedException();\n    &#125;\n\n    public void DeleteResource(object targetResource)\n    &#123;\n        throw new System.NotImplementedException();\n    &#125;\n\n    public object GetResource(System.Linq.IQueryable query, string fullTypeName)\n    &#123;\n        throw new System.NotImplementedException();\n    &#125;\n\n    public object GetValue(object targetResource, string propertyName)\n    &#123;\n        throw new System.NotImplementedException();\n    &#125;\n\n    public void RemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved)\n    &#123;\n        throw new System.NotImplementedException();\n    &#125;\n\n    public object ResetResource(object resource)\n    &#123;\n        throw new System.NotImplementedException();\n    &#125;\n\n    public object ResolveResource(object resource)\n    &#123;\n        throw new System.NotImplementedException();\n    &#125;\n\n    public void SaveChanges()\n    &#123;\n        throw new System.NotImplementedException();\n    &#125;\n\n    public void SetReference(object targetResource, string propertyName, object propertyValue)\n    &#123;\n        throw new System.NotImplementedException();\n    &#125;\n\n    public void SetValue(object targetResource, string propertyName, object propertyValue)\n    &#123;\n        throw new System.NotImplementedException();\n    &#125;\n&#125;</code></pre>\n<p>Я не буду вдаваться в смысл и детали реализации каждого метода. При первом же рассмотрении эти методы кажутся вполне логичными и понятными. Для того же, чтобы вы могли уже сейчас использовать возможность обновления данных через веб-сервис на базе модели данных LINQ to SQL предлагаю вам один из вариантов реализации данного интерфейса. <a href=\"http://code.msdn.microsoft.com/IUpdateableLinqToSql\">Скачать его себе</a> (и использовать в своих проектах) вы можете по ссылке ниже.</p>\n<p>Как вы видите интерфейс реализуется в partial-классе. Это существенно, т.к. если вы его реализуете внутри автогенерируемого файла <code>*.designer.cs</code>, то вы подвержены потерять этот код при любом изменении данных.</p>",
            "tags": [
                "Windows Communication Foundation"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/adonet-data-services-update/ed85beec/adonet_data-services-update@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/adonet-data-services-instance/",
            "url": "https://blog.zwezdin.com/2009/adonet-data-services-instance/",
            "title": "Контроль за инстанциацией модели данных в ADO.NET Data Services",
            "date_published": "2009-03-25T13:00:00.000Z",
            "content_html": "<p>При работе с сервисами <strong>ADO.NET Data Services</strong> мы ссылаемся на модель данных используя generic-параметр. При этом экземпляр класса, реализующего модель данных создает уже инфраструктура <strong>ADO.NET Data Services</strong>, используя при этом конструктор без параметров.</p>\n<p>Зачастую такого поведения вполне достаточно – создаем модель данных, в конфигурационном файле прописываем строку соединения и все работает. Тем не менее, можно встретить сценарии, когда необходимо вмешаться в этот процесс.</p>\n<p>Причины могут быть самыми разнообразными:</p>\n<ul>\n<li>Необходимо хранить строку соединения за пределами конфигурационного файла;</li>\n<li>Необходимо создавать не сам класс <code>DataContext</code>, а его наследника (в котором каким-то образом изменено стандартное поведение);</li>\n<li>Класс <code>DataContext</code> не имеет конструктора без параметров (да-да, и такое тоже бывает:));</li>\n<li>и т.д.</li>\n</ul>\n<p>Во всех этих и других сценариях платформа <strong>ADO.NET Data Services</strong> предлагает нам достаточно красивый способ решения проблемы. Все, что необходимо сделать – это переопределить метод <code>CreateDataSource()</code> базового класса.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class WebDataService1 : DataService&lt;DataClasses1DataContext>\n&#123;\n    public static void InitializeService(IDataServiceConfiguration config)\n    &#123;\n        config.SetEntitySetAccessRule(\"*\", EntitySetRights.AllRead);\n    &#125;\n\n    protected override DataClasses1DataContext CreateDataSource()\n    &#123;\n        return base.CreateDataSource();\n    &#125;\n&#125;</code></pre>\n<p>Как видно, этот метод как раз возвращает источник данных того типа, который указан в generic-параметре при определении базового класса. Логично, что этот метод вызывается всякий раз когда необходимо получить доступ к модели данных. Теперь мы можем задать в этом методе ту логику, которая нам необходима. Например, переопределить строку соединения:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class WebDataService1 : DataService&lt;DataClasses1DataContext>\n&#123;\n    public static void InitializeService(IDataServiceConfiguration config)\n    &#123;\n        config.SetEntitySetAccessRule(\"*\", EntitySetRights.AllRead);\n    &#125;\n\n    protected override DataClasses1DataContext CreateDataSource()\n    &#123;\n        return new DataClasses1DataContext(\"some connection string\");\n    &#125;\n&#125;</code></pre>",
            "tags": [
                "Windows Communication Foundation"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/adonet-data-services-instance/62f95ae8/adonet_data-services-instance@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/adonet-data-services-linqtosql/",
            "url": "https://blog.zwezdin.com/2009/adonet-data-services-linqtosql/",
            "title": "ADO.NET Data Services и LINQ to SQL",
            "date_published": "2009-03-23T13:00:00.000Z",
            "content_html": "<p>Как правило работа сервисов <strong>ADO.NET Data Services</strong> представляется при использовании модели данных на основе <strong>ADO.NET Entity Framework</strong>. Однако, работа с реальными проектами (и заказчиками:)) порой диктует нам другие условия. Зачастую нет никакой необходимости использовать Entity Framework. И в этом случае мы задаемся вполне резонной задачей – использовать в качестве модели данных <strong>LINQ to SQL</strong>.</p>\n<p>В ранних версиях <strong>ADO.NET Data Services</strong> все было просто — создаем модель данных, создаем сервис и все работает. С выходом релиза <strong>ADO.NET Data Services</strong> ситуация немного изменилась — при построении подобного сервиса на основе LINQ to SQL и обращении к этому сервису мы видим сообщение об ошибке.</p>\n<p>Корни этой проблемы кроются в стратегии задания полей, содержащих первичные ключи. В ранних версиях при отсутствии явного указания этих полей инфраструктура ADO.NET Data Services сама пыталась определить их. Однако, в релизе эту стратегию поменяли и сейчас необходимо делать это явно.</p>\n<p>Подобные поля задаются простым и естественным образом – путем навешивания атрибута <code>DataServiceKey</code> на сущность, в конструкторе которого как раз и указываются поля, являющиеся ключевыми. Информация о ключевых полях очень важна для сервиса, т.к. как вы помните при обращении к каждой конкретной сущности мы как раз указываем значение этого поля.</p>\n<p><em>В чем же, собственно, проблема в LINQ to SQL?</em> Проблема простая – LINQ to SQL при генерации сущности не помечает сущности этими атрибутами. Конечно же, это может вызвать негодование у разработчика, тем не менее этот досадный недостаток очень легко поправить. Вспомним, что все сущности в модели данных LINQ to SQL – это <code>partial</code>-классы. Сгенерируем partial-класс для каждой сущности модели данных и пометим атрибутом <code>DataServiceKey</code> эту сущность.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[DataServiceKey(\"NewsId\")]\n partial class New\n&#123;\n\n&#125;</code></pre>\n<p>И вот, теперь наш сервис на базе LINQ to SQL работает.</p>\n<p>На самом деле, существует некое соглашение, по которому <strong>ADO.NET Data Services</strong> пытается самостоятельно определить ключевые поля в сущности. Это соглашение заключается в следующем — если сущность не помечена атрибутом <code>DataServiceKey</code>, то выполняются следующие действия:</p>\n<ol>\n<li>Среди свойств сущности ищется поле <code>ID</code>. Если оно существует, то оно и будет являться ключевым;</li>\n<li>Если свойство <code>ID</code> не найдено, то ищется поле <code>EntityTypeID</code>, где <code>EntityType</code> – имя вашей сущности. Например, если ваша сущность называется <code>Customer</code>, то будет произведена попытка обнаружить свойство <code>CustomerID</code>. Если такой свойство найдено, то оно и будет ключевым.</li>\n<li>Если эти свойства не найдены, то будет сгенерирована ошибка.</li>\n</ol>",
            "tags": [
                "Windows Communication Foundation"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/adonet-data-services-linqtosql/734ace0b/adonet_data-services-linqtosql@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/adonet-data-services-php-toolkit/",
            "url": "https://blog.zwezdin.com/2009/adonet-data-services-php-toolkit/",
            "title": "PHP Toolkit для ADO.NET Data Services",
            "date_published": "2009-03-23T13:00:00.000Z",
            "content_html": "<p>Microsoft Interoperability team объявила о выходе нового проекта, который является мостом между <strong>PHP</strong> и <strong>.NET</strong> и еще больше стирает грани технологий. Этот проект позволяет разработчикам на PHP еще более проще обращаться и использовать всю мощь сервисов .NET на основе <strong>ADO.NET Data Services</strong>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/adonet-data-services-php-toolkit/604676ba/1@2x.png\"  alt=\"PHP Toolkit для ADO.NET Data Services\"/>\n\n\n\n\n<p>С точки зрения разработки схема выглядит достаточно классическим образом. В процессе разработки необходимо использовать утилиту PHPDataSv</p>\n<p>Давайте посмотрим каким образом будет выглядеть код на платформе PHP для выполнения запросов к ADO.NET Data Services. Прежде всего необходимо подключить файл с прокси-классами, которые сгененрированы с помощью утилиты PHPDataSv</p>\n<pre class=code language-php><code class=\"code__container language-php\">&lt;?PHP\n    require_once 'NorthwindClient.php';\n    define(\"SERVICE_URI\", \"http://localhost:8080/Northwind.svc\");\n?></code></pre>\n<p>После этого можно создавать объекты класса, реализующие логику клиента и осуществлять запросы к удаленному сервису. К сожалению, здесь мы не сможем воспользоваться синтаксисом LINQ для построения запросов к сервису. Поэтому запрос в виде URI придется написать вручную.</p>\n<pre class=code language-php><code class=\"code__container language-php\">&lt;?PHP\n    $client = new NorthwindEntities(SERVICE_URI);\n    $query = $client->ExecuteQuery(\"Customers?\\$filter=Country eq 'UK'\");\n?></code></pre>\n<p>Теперь, после успешного конструирования запроса можно обратиться за данными и вывести результат пользователю:</p>\n<pre class=code language-php><code class=\"code__container language-php\">&lt;?PHP\n    foreach($query as $customer)\n    &#123;\n        echo \"&lt;p>\";\n        echo $customer->CustomerID;\n        echo \"&lt;br/>\";\n        echo $customer->CompanyName;\n        echo \"&lt;/p>\";\n    &#125;\n?></code></pre>\n<p>Здесь хорошо видно, что мы обращаемся к строго типизированным объектам, а результат получаем в виде объектных коллекций. Такой способ очень похож на то, как мы работаем с сервисами ADO.NET Data Services в рамках .NET-клиента. Эти классы-сущности были также сгенерированы утилитой PHPDataSv</p>\n<p>Pablo Castro (program manager, ADO.NET Data Services) и Claudio Caldato (program manager, interoperability , techincal strategy team) сделали <a href=\"http://channel9.msdn.com/posts/jccim/Consuming-data-over-the-web-between-PHP-and-NET-with-REST-and-ADONET-Data-Services/\">небольшой обзор</a> этой библиотеки на Channel 9 (en), в котором они рассказывают чуть больше деталей.</p>\n<p>Ну и наконец очень интересно то, что проект является проектом с открытым исходным кодом и доступен на CodePlex. Кроме того, проект разрабатывается компанией Persistent Systems, но оплачивается Microsoft.</p>",
            "tags": [
                "Windows Communication Foundation"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/adonet-data-services-php-toolkit/03397f5b/adonet_data-services-php-toolkit@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/adonet-data-services-debug/",
            "url": "https://blog.zwezdin.com/2009/adonet-data-services-debug/",
            "title": "Отладка сервисов ADO.NET Data Services",
            "date_published": "2009-03-19T13:00:00.000Z",
            "content_html": "<p>Интерес к платформе <strong>ADO.NET Data Services</strong> растет среди разработчиков веб-сервисов. Это также подтверждается и тем, что я все чаще получаю письма с вопросами о той или иной проблеме. Как правило эти вопросы сводятся к тому, что после выполнения определенной последовательности действий сервис начинает выдавать невнятную ошибку и предлагает ознакомится с деталями в логах.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/adonet-data-services-debug/13e7b7d8/1@2x.png\"  alt=\"Отладка сервисов ADO.NET Data Services\"/>\n\n\n\n\n<p><em>Знакомо ли вам данное сообщение?</em> Если вы уже испытывали <strong>ADO.NET Data Services</strong> и пытались что-то сделать, то думаю что да.</p>\n<p>Причина всех недоумений заключается в том, что в действительности по умолчанию <strong>ADO.NET Data Services</strong> не пишет никаких сообщений в логи, а если вам это необходимо, то вы должны позаботится об этом сами. Что же делать если хочется понять в чем истинная ошибка?</p>\n<p>Достаточно обратиться к статическому методу инициализации сервиса <code>InitializeService</code>, параметром которого является объект-наследник <code>IDataServiceConfiguration</code>. Последний имеет в своем составе свойство <code>UseVerboseErrors</code>. Для получения внятного описания ошибки необходимо установить это свойство в <code>true</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static void InitializeService(IDataServiceConfiguration config)\n&#123;\n    config.SetEntitySetAccessRule(\"*\", EntitySetRights.AllRead);\n    config.UseVerboseErrors = true;\n&#125;</code></pre>\n<p>Теперь снова обратимся к сервису и увидим описание ошибки.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2009/adonet-data-services-debug/8fb1431e/2@2x.png\"  alt=\"Отладка сервисов ADO.NET Data Services\"/>\n\n\n\n\n<p>Здесь мы уже можем увидеть какие-то детали и даже Stack Trace. Часто этого бывает достаточно. Однако, в определенных ситуациях до сих пор не удается понять в чем дело. После несложных операций в рефлекторе можно понять, что вся инфраструктура построена поверх <strong>Windows Communication Foundation</strong>, а конкретно <strong>WCF REST API</strong>. Как известно, в целях безопасности WCF по умолчанию скрывает детали исключения. Тем не менее, мы можем переопределить это поведение, явно указав WCF что необходимо делать в этом случае. В нашем случае сделать это проще всего используя атрибут <code>SerivceBehavior</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[ServiceBehavior(IncludeExceptionDetailInFaults = true)]\npublic class WebDataService1 : DataService&lt;MvcMiniCmsEntities>\n&#123;\n    //...</code></pre>\n<p>Теперь WCF будет включать детали исключения в ответ и можно понять что происходит в каждом конкретном случае.</p>",
            "tags": [
                "Windows Communication Foundation"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/adonet-data-services-debug/13f55e63/adonet_data-services-debug@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2009/wpf-hardware/",
            "url": "https://blog.zwezdin.com/2009/wpf-hardware/",
            "title": "Изменение внешнего вида пользовательского интерфейса в зависимости от аппаратных возможностей в WPF",
            "date_published": "2009-02-24T13:00:00.000Z",
            "content_html": "<p>Всякий раз когда в своих приложениях на базе WPF мне хочется применить тот или иной эффект или анимацию (которая была бы к месту) меня останавливает одно – как быть с пользователями, у которых аппаратные ресурсы поскромнее.</p>\n<p>На первый взгляд кажется, что решение не лежит на поверхности – нужно каким-то образом определять возможности аппаратного обеспечения и корректировать пользовательский интерфейс. Однако, на самом деле все очень просто.</p>\n<p>Дело в том, что есть такой класс <code>RenderCapatibility</code>, который имеет статическое свойство <code>Tier</code>. Это свойство может содержать три разных значения, в зависимости от того, какими аппаратными возможностями вы располагаете:</p>\n<ul>\n<li><code>0</code> – аппаратное ускорение для графики отсутствует на данном устройстве;</li>\n<li><code>1</code> – частичная поддержка графического аппаратного ускорения, поддержка DirectX версии 7.0 – 9.0;</li>\n<li><code>2</code> – полная поддержка аппаратного ускорения, поддержка DirectX версии 9.0 или выше.</li>\n</ul>\n<p>Таким образом, основываясь на этом значении вы можете корректировать визуальную составляющую в вашем приложении. Однако, есть одно <em>“но”</em> :) Дело в том, что на самом деле это свойство выдает значения не <code>0</code>, <code>1</code> и <code>2</code>, а <code>0</code>, <code>0x00010000</code> и <code>0x00020000</code>. Как можно заметить, это те же цифры <code>0</code>, <code>1</code> и <code>2</code>, но сдвинутые логически на 16 разрядов. Для получения нужных нам значений необходимо произвести обратную операцию:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">int renderingTier = (RenderCapability.Tier >> 16);</code></pre>\n<p>Теперь давайте посмотрим на то, каким образом мы можем изменять пользовательский интерфейс в зависимости от этого значения декларативно (т.е. внутри XAML).</p>\n<p>Для этого нам необходимо сделать очень простую обертку над данным значениям, с помощью которой мы получим <code>DependencyProperty</code>, возвращающее нужное значение.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class RenderCapabilityWrapper\n&#123;\n  public static readonly DependencyProperty TierProperty =\n    DependencyProperty.RegisterAttached(\n    \"Tier\",\n    typeof(int),\n    typeof(RenderCapabilityWrapper),\n    new PropertyMetadata(RenderCapability.Tier >> 16));\n\n  public static int GetTier(DependencyObject depObj)\n  &#123;\n    return (int)TierProperty.DefaultMetadata.DefaultValue;\n  &#125;\n\n  public static int Tier\n  &#123;\n    get\n    &#123;\n      return (int)TierProperty.DefaultMetadata.DefaultValue;\n    &#125;\n  &#125;\n&#125;</code></pre>\n<p>Такой класс-обертка позволит нам прямо из описания пользовательского интерфейса получить значение свойства <code>Tier</code> и произвести изменения в пользовательском интерфейсе. Для этого лучше всего подходят триггеры.</p>\n<p>Давайте попробуем теперь описать стиль для кнопки.</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Style TargetType=\"&#123;x:Type Button&#125;\">\n  &lt;Style.Triggers>\n    &lt;Trigger Property=\"hardware:RenderCapabilityWrapper.Tier\" Value=\"2\">\n    &lt;!-- описание стиля для Tier = 2 -->\n    &lt;/Trigger>\n    &lt;Trigger Property=\"hardware:RenderCapabilityWrapper.Tier\" Value=\"1\">\n    &lt;!-- описание стиля для Tier = 1 -->\n    &lt;/Trigger>\n    &lt;Trigger Property=\"hardware:RenderCapabilityWrapper.Tier\" Value=\"0\">\n    &lt;!-- описание стиля для Tier = 0 -->\n    &lt;/Trigger>\n  &lt;/Style.Triggers>\n&lt;/Style></code></pre>\n<p>Вот так легко и просто мы можем создавать адаптивный пользовательский интерфейс для наших приложений, который изменяется в зависимости от аппаратных возможностей.</p>",
            "tags": [
                "WPF"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/wpf-hardware/411912d3/wpf_hardware@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/linq-recurse/",
            "url": "https://blog.zwezdin.com/2009/linq-recurse/",
            "title": "Рекурсивная выборка с помощью LINQ to Objects",
            "date_published": "2009-02-06T13:00:00.000Z",
            "content_html": "<p>Приходилось ли вам когда-нибудь работать с рекурсивными алгоритмами? Я думаю приходилось. Для этого мы создаем рекурсивный метод, в котором описываем нужную логику и сам рекурсивный вызов. Однако, достаточно часто все что необходимо сделать – это обработать “плоский” список. Давайте рассмотрим как это сделать при помощи <strong>LINQ to Objects</strong>.</p>\n<p>Итак, представим, что у нас есть определенная сущность, которая содержит список дочерних узлов аналогичного типа:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">class Data\n&#123;\n    public string Name &#123; get; set; &#125;\n    public IEnumerable&lt;Data> Childs &#123; get; set; &#125;\n&#125;</code></pre>\n<p>Мы не будем рассматривать процесс инстанциации дерева, просто предположим у нас получилась вот такая структура:</p>\n<ul>\n<li>Node1<ul>\n<li>Node2<ul>\n<li>Node3</li>\n</ul>\n</li>\n<li>Node4<ul>\n<li>Node5</li>\n<li>Node6</li>\n</ul>\n</li>\n<li>Node7<ul>\n<li>Node8</li>\n</ul>\n</li>\n<li>Node9<ul>\n<li>Node10<ul>\n<li>Node11</li>\n<li>Node12<ul>\n<li>Node13</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>Node14</li>\n</ul>\n</li>\n</ul>\n<p>Предположим, что для обработки всех этих данных нам нужен всего лишь “плоский” список этих узлов:</p>\n<ul>\n<li>Node1</li>\n<li>Node2</li>\n<li>Node3</li>\n<li>Node4</li>\n<li>Node5</li>\n<li>Node6</li>\n<li>Node7</li>\n<li>Node8</li>\n<li>Node9</li>\n<li>Node10</li>\n<li>Node11</li>\n<li>Node12</li>\n<li>Node13</li>\n<li>Node14</li>\n</ul>\n<p>Что мы можем сделать? Можно написать стандартный рекурсивный обработчик:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public IEnumerable&lt;Data> GetAllNodes(IEnumerable&lt;Data> data)\n&#123;\n    var result = new List&lt;Data>();\n\n    if (data != null)\n    &#123;\n        foreach (var item in data)\n        &#123;\n            result.Add(item);\n            result.AddRange(GetAllNodes(item.Childs);\n        &#125;\n    &#125;\n\n    return result;\n&#125;</code></pre>\n<p>Тем не менее, было бы неплохо иметь возможность с помощью <strong>LINQ to Objects</strong> обработать коллекцию рекурсивно и получить “плоский” список. Если не считать <code>SelectMany</code> (который здесь совсем не подходит), из стандартных возможностей LINQ нет ничего подходящего. Однако, благодаря C# 3.0 мы всегда можем расширить функциональность с помощью extension-методов. Для нашего случая напишем несложный метод расширения:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">internal static class RecursiveExtension\n&#123;\n    public static IEnumerable&lt;T> SelectRecursive&lt;T>(this IEnumerable&lt;T> source, Func&lt;T, IEnumerable&lt;T>> recursiveFunc)\n    &#123;\n        if (source != null)\n        &#123;\n            foreach (var mainItem in source)\n            &#123;\n                yield return mainItem;\n\n                IEnumerable&lt;T> recursiveSequence = (recursiveFunc(mainItem) ?? new T[] &#123; &#125;).SelectRecursive(recursiveFunc);\n\n                if (recursiveSequence != null)\n                &#123;\n                    foreach (var recursiveItem in recursiveSequence)\n                    &#123;\n                        yield return recursiveItem;\n                    &#125;\n                &#125;\n            &#125;\n        &#125;\n    &#125;\n&#125;</code></pre>\n<p>Все готово. Теперь, используя этот метод расширения мы можем построить запрос для получения “плоского” списка:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">IEnumerable&lt;Data> data = ...;\n\n// \"Плоский список\"\nvar flatList = data.SelectRecursive(c => c.Childs);\n\nforeach(var item in flatList)\n&#123;\n    Console.WriteLine(item.Name);\n&#125;\n/*  Result:\n    --\n    Node1\n    Node2\n    Node3\n    Node4\n    Node5\n    Node6\n    Node7\n    Node8\n    Node9\n    Node11\n    Node12\n    Node13\n    Node14\n*/\n\n// Запрос\nvar query = from d in data.SelectRecursive(c => c.Childs)\n            where d.Name.Contains(\"5\")\n            select d.Name;\n\nforeach(var item in query)\n&#123;\n    Console.WriteLine(item);\n&#125;\n\n/*  Result:\n    --\n    Node5\n*/</code></pre>",
            "tags": [
                "C#",
                "LINQ"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/linq-recurse/f778dd11/linq_recurse@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/wcf-httpcontext/",
            "url": "https://blog.zwezdin.com/2009/wcf-httpcontext/",
            "title": "HttpContext.Current и Windows Communication Foundation",
            "date_published": "2009-02-03T13:00:00.000Z",
            "content_html": "<p>Случается, порой, так, что из сервисов <strong>WCF</strong> необходимо обращаться к <code>HttpContext.Current</code>. Когда? Например тогда, когда вы пользуетесь существующими HTTP-модулями (например, <em>безопасность</em>), которые сконфигурированы для всего web-приложения, в т.ч. наших сервисов. В этом случае при попытке обратиться к <code>HttpContext.Current</code> мы получаем <code>null</code>. Что с этим делать?</p>\n<p>Вообще, если речь идет конкретно об аутентификационных данных, то правильнее здесь пользоваться <code>ServiceSecurityContext.Current</code> – в этом случае используется система безопасности WCF и клиенты, которые используют эти механизмы аутентификации/авторизации также будут корректно обрабатываться.</p>\n<p>В случаях, когда есть необходимость работать именно с <code>HttpContext</code>, этого также можно достичь. Для этого необходимо включить режим совместимости хостинг-среды WCF. Для этого необходимо сделать две вещи:</p>\n<p><strong>Первое:</strong> Разметить сервис атрибутом <code>AspNetCompatibilityRequirements</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]\npublic class MetadataService : IMetadata</code></pre>\n<p><strong>Второе:</strong> В настройках указать, что необходимо использовать <strong>режим совместимости ASP.NET</strong>:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;system.serviceModel>\n    &lt;!-- .... -->\n    &lt;serviceHostingEnvironment aspNetCompatibilityEnabled=\"true\" />\n&lt;/system.serviceModel></code></pre>\n<p>Конечно же такой сервис должен работать на основе HTTP в рамках IIS. Но, собственно, в противном случае <code>HttpContext</code> и смысла то не имеет.</p>\n<p>Но все-таки предпочтительнее использовать встроенные механизмы безопасности.</p>",
            "tags": [
                "Windows Communication Foundation",
                "ASP.NET"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/wcf-httpcontext/69bd4f0f/wcf_httpcontext@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2009/already-implemented-things/",
            "url": "https://blog.zwezdin.com/2009/already-implemented-things/",
            "title": "Наиболее популярные велосипеды",
            "date_published": "2009-01-09T13:00:00.000Z",
            "content_html": "<p>Когда-то все мы начинали изучение платформы .NET и языка C#/VB. В эти моменты жизни достаточно часто получается изобретать велосипеды. Тем не менее, бывает полезно посмотреть на них и сейчас. Приведу наиболее популярные.</p>\n<h3>Работа с путями</h3><ul>\n<li><code>Path.GetFileNameWithoutExtension()</code> – позволяет получить имя файла без расширения</li>\n<li><code>Path.GetRandomFileName()</code> – генерирует случайное имя файла или папки</li>\n<li><code>Path.GetTempFileName()</code> – создает файл во временной папке со случаным именем</li>\n<li><code>Path.Combine(basePath, fileName)</code> – позволяет объединить две части пути (вместо использования <code>basePath + @&quot;\\&quot; + fileName</code>)</li>\n<li><code>Directory.CreateDirectory()</code> – создает папку. Кроме того, многие программисты забывают, что этот метод выполняет проверку на наличие этой папки и оборачивают этот метод в <code>if(Directory.Exists())</code></li>\n<li><code>Environment.GetFolderPath()</code> – позволяет получить путь до специальной папки (например, <code>Мои документы</code>)</li>\n</ul>\n<h3>Специальные символы</h3><ul>\n<li><code>Environment.NewLine</code> – символ новой строки (вместо <code>\\r\\n</code>)</li>\n<li><code>Path.DirectorySeparatorChar</code> – символ разделения имен папок/файлов при указании пути (вместо <code>/</code>)</li>\n</ul>\n<h3>Работа с файлами</h3><ul>\n<li><code>File.ReadAllBytes()</code>, <code>File.ReadAllLines()</code>, <code>File.ReadAllText()</code> – бывает что для чтения файлов мы используем потоки (<code>FileStream</code>), при этом заботясь об их корректном открытии/закрытии, хотя вся операция делается в 1 строчку</li>\n<li><code>File.WriteAllBytes()</code>, <code>File.WriteAllLines()</code>, <code>File.WriteAllText()</code> – аналогично для записи в файл</li>\n</ul>\n<h3>Работа со строками</h3><ul>\n<li><code>String.IsNullOrEmpty()</code> – позволяет проверить строку на пустоту (делает две проверки: <code>== null</code> и <code>== &quot;&quot;</code>)</li>\n<li><code>String.Split()</code> – позволяет разбить строку на массив строк по заданному символу (например, по запятой)</li>\n<li><code>String.Join()</code> – обратная операция, позволяет объединить массив строк в одну строку</li>\n<li><code>&quot;...&quot;.StartsWith()</code>, <code>&quot;...&quot;.EndsWith()</code> – позволяет определить начинается ли или кончается ли заданная строка в соответствии с шаблоном</li>\n</ul>\n<h3>Работа с сетью</h3><ul>\n<li>класс <code>WebClient</code> – позволяет получить данные из сети используя простые методы <code>DownloadString()</code>, <code>DownloadFile()</code>, <code>DownloadData()</code></li>\n<li><code>Dns.GetHostName()</code> – получить имя текущего хоста</li>\n<li><code>Dns.Resolve()</code> – получить информацию об узле по имени хоста</li>\n<li><code>Dns.GetHostByAddress()</code> – получить информацию об узле по IP-адресу</li>\n</ul>\n<h3>Данные</h3><ul>\n<li><code>Enumerable.Range()</code> – сгенерировать массив чисел заданного диапазона</li>\n<li><code>IEnumrable&lt;T&gt;.Any(predicate)</code> – определяет удовлетворяет ли заданному условию хотя бы один элемент из списка</li>\n<li><code>IEnumrable&lt;T&gt;.All(predicate)</code> – определяет удовлетворяет ли заданному условию все элементы списка</li>\n<li>[**UPDATED:** Спасибо <a href=\"http://dev.net.ua/blogs/mikechaliy/\">Mike Chaliy</a>] <code>IEnumerable&lt;T&gt;.Select()</code> – позволяет также выполнять какие-то действия над каждым элементом списка:</li>\n</ul>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">var data = Enumerable.Range(0, 100);\ndata.Select(delegate(int d)\n&#123;\n    DoSomething(d);\n\n    return data;\n&#125;).ToList();</code></pre>\n<ul>\n<li>класс <code>BitConverter</code> – позволяет представлять данные в виде последовательности байт и обратно</li>\n<li>классы <code>System.Data.Common.DbConnectionStringBuilder</code>, <code>System.Data.SqlClient.SqlConnectionStringBuilder</code> – позволяют генерировать строки соединения</li>\n</ul>\n<h3>Reflection</h3><ul>\n<li><code>MethodBase.GetCurrentMethod().Name</code> – позволяет получить имя текущего исполняемого метода (удобно для записи в Log)</li>\n<li><code>Enum.Parse()</code> – позволяет из строки получить значение перечисления</li>\n<li><code>System.CodeDom</code> – позволяет генерировать и исполнять код</li>\n</ul>\n<h3>Прочее</h3><ul>\n<li><code>System.Diagnostics.Stopwatch</code> – позволяет замерять время работы участков кода (очень распространенный велосипед:))</li>\n<li><code>CultureInfo.GetCultures()</code> – позволяет получить список всех культр:</li>\n</ul>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">foreach (CultureInfo culture in CultureInfo.GetCultures(CultureTypes.AllCultures &amp;~ CultureTypes.NeutralCultures))\n&#123;\n    RegionInfo region = new RegionInfo(culture.LCID);\n    Console.WriteLine(@\"&#123;0&#125; - &#123;1&#125;\", region.EnglishName, region.DisplayName);\n&#125;</code></pre>",
            "tags": [
                "Fun",
                "C#",
                ".NET"
            ],
            "banner_image": "https://blog.zwezdin.com/2009/already-implemented-things/2058df58/already_implemented-things@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2008/linq-dynamic-queries/",
            "url": "https://blog.zwezdin.com/2008/linq-dynamic-queries/",
            "title": "Dynamic Queries with LINQ",
            "date_published": "2008-12-22T13:00:00.000Z",
            "content_html": "<p><em>“LINQ классная штука, но т.к. там все строго типизировано я не могу строить динамические запросы, а это большой минус”</em> – такие утверждения слышу все чаще от различных разработчиков. Правильный ответ – динамические запросы генерировать можно.</p>\n<p>Вообще такие вопросы вижу как в онлайн (e-mail), раз пять отвечал на этот вопрос в зоне “Спроси Эксперта” на Платформе 2009, но последней каплей стало то, что мой блог нашли по запросу <mark>“dynamic queries with LINQ”</mark>. =))))))</p>\n<h2>Expression Tree</h2><p>Итак, в основе всего лежит интерфейс <code>IQueryable</code> и <strong>Expression Trees</strong>. Что это такое? Давайте подумаем, что на самом деле представляет собой выражение LINQ – фактически оно трансформируется в цепочку вызовов Extension-методов, который в свою очередь – в вызовы статических методов. Происходит это примерно в следующей последовательности:</p>\n<p><strong>Шаг 1:</strong> Выражение LINQ:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">var q = from d in data\n        where d.Length > 5\n        select d;</code></pre>\n<p><strong>Шаг 2:</strong> Цепочка методов расширения:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">var q = data.Where(d => d.Length > 5).Select(d => d);</code></pre>\n<p><strong>Шаг 3:</strong> Вызовы статических методов:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">var q = Enumerable.Select(Enumerable.Where(data, d => d.Length > 5), d => d);</code></pre>\n<p>Все хорошо когда мы работаем с in-memory коллекциями, но что делать LINQ когда необходимо работать, например, с СУБД? Фактически, нужно трансформировать запрос LINQ в запрос T-SQL (в нашем случае). Вместо T-SQL может быть все что угодно, но давайте пока остановимся именно на этом варианте. Итак, как же LINQ будет осуществлять эту трансформацию? Ответ прост – для этого необходимо иметь представление нашего запроса в виде некоторой объектной модели – <strong>Expression Tree</strong>. Эта модель описывает все моменты запроса – условия, порядок сортировки, результат выборки и т.д. Основываясь на данных этой модели уже можно строить запрос на каноническом языке, в нашем случае T-SQL.</p>\n<p>Причем же здесь <code>IQueryable</code>? Все просто — помните <code>IEnumerable</code>? Этот интерфейс позволяет нам пробежаться по коллекции в однонаправленном режиме. Именно на основе <code>IEnumerable</code> строятся все коллекции. IQueryable – это наследник <code>IEnumerable</code>, который включает также модель <strong>Expression Tree</strong>.</p>\n<h2>Генерируем Expression Tree</h2><p>Возвращаясь к теме, давайте поговорим о том, каким образом мы можем формировать динамические запросы. Для этого, собственно, нам необходимо как раз сформировать Expression Tree и получить из этого лямбда выражение (-ия).</p>\n<p>Для формирования <strong>Expression Tree</strong> существует класс <code>Expression</code>, который содержит различные статические методы для формирования конструкций. Здесь проще показать на примере.</p>\n<p>Предположим, что у нас имеется коллекция строк и нам необходимо выполнить по ней фильтрацию, причем условий будет несколько и объединяться они должны по “ИЛИ”. При этом строки для поиска будет вводить пользователь и их может быть переменное количество. Если решать ту же самую задачу, но с объединением условий по “И”, то все гораздо проще: в этом случае нам необходимо несколько раз подряд вызвать extension-метод <code>.Where&lt;&gt;()</code>.</p>\n<p>Логично, что для решения этой задачи нам нужно сформировать лямбда-выражение примерно такого вида:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">c => c.Contains(\"a\") || c.Contains(\"b\") || c.Contains(\"c\") || итд</code></pre>\n<p>Давайте попробуем это сделать по шагам:</p>\n<p><strong>Шаг 1:</strong> Формируем параметр:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">var param = Expression.Parameter(typeof(string), \"c\");</code></pre>\n<p><strong>Шаг 2:</strong> Формируем вызов метода:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">var condition = Expression.Call(param, typeof (string).GetMethod(\"Contains\"), Expression.Constant(\"a\"));</code></pre>\n<p><strong>Шаг 3:</strong> Получаем лямбда-выражение:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">var l = Expression.Lambda&lt;Func&lt;string, bool>>(condition, param);</code></pre>\n<p>Теперь, если мы выведем полученное лямбда-выражение на экран, то увидим следующую вещь:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">c => c.Contains(\"a\")</code></pre>\n<p>Отлично, теперь никто нам не мешает создать несколько таких условий, используя оператор “ИЛИ” (<code>Expression.Or</code>). Финальный код может выглядеть следующим образом:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">var param = Expression.Parameter(typeof(string), \"c\");\n\nExpression condition = null;\nstring input;\nwhile (String.IsNullOrEmpty(input = Console.ReadLine()) == false)\n&#123;\n  if (condition == null)\n  &#123;\n    condition = Expression.Call(param,\n                                typeof(string).GetMethod(\"Contains\"),\n                                Expression.Constant(input));\n  &#125;\n  else\n  &#123;\n    condition = Expression.Or(condition,\n                    Expression.Call(param,\n                                    typeof (string).GetMethod(\"Contains\"),\n                                    Expression.Constant(input)));\n  &#125;\n&#125;\n\nvar l = Expression.Lambda&lt;Func&lt;string, bool>>(condition, param);</code></pre>\n<p>Теперь, если мы выведем на экран получившееся выражение, то увидим, что все условия объединены через “ИЛИ”.</p>\n<h2>Compile</h2><p>Если мы посмотрим на переменную <code>l</code>, то увидим, что там содержится не <code>Func&lt;string, bool&gt;</code>, а <code>Expression&lt;Func&lt;string, bool&gt;&gt;</code>. Давайте исправим это, для этого необходимо вызвать метод <code>Compile()</code>. После этого можно использовать лямбда-выражение в ваших запросах:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">string[] data = new[] &#123; \"qwerty\", \"asdfgh\", \"zxcvb\", \"wertyu\" &#125;;\n\nvar param = Expression.Parameter(typeof(string), \"c\");\n\nExpression condition = null;\nstring input;\nwhile (String.IsNullOrEmpty(input = Console.ReadLine()) == false)\n&#123;\n    if (condition == null)\n    &#123;\n        condition = Expression.Call(param,\n                                    typeof(string).GetMethod(\"Contains\"),\n                                    Expression.Constant(input));\n    &#125;\n    else\n    &#123;\n        condition = Expression.Or(condition,\n                                  Expression.Call(param,\n                                                  typeof (string).GetMethod(\"Contains\"),\n                                                  Expression.Constant(input)));\n    &#125;\n&#125;\n\nvar l = Expression.Lambda&lt;Func&lt;string, bool>>(condition, param);\n\nConsole.WriteLine(l);\n\nvar q = data.Where(l.Compile());\n\nforeach (var s in q)\n&#123;\n    Console.WriteLine(s);\n&#125;</code></pre>",
            "tags": [
                "C#",
                "LINQ"
            ],
            "banner_image": "https://blog.zwezdin.com/2008/linq-dynamic-queries/4c967658/linq_dynamic-queries@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2008/adonet-data-services-representation/",
            "url": "https://blog.zwezdin.com/2008/adonet-data-services-representation/",
            "title": "Представление данных в ADO.NET Data Services",
            "date_published": "2008-12-17T13:00:00.000Z",
            "content_html": "<p>Сервисы <strong>ADO.NET Data Services</strong> могут представлять наши данные в различных форматах. В последнее время все чаще получаю вопросы о том как получить данные от сервиса в формате отличном от <code>AtomPub</code>.</p>\n<p><strong>ADO.NET Data Services</strong> выдает информацию в том формате, в котором попросит его об этом пользователь, основываясь на HTTP-заголовке <code>Accept</code>. Если значение этого заголовка в HTTP-запросе не задано, то по умолчанию используется <code>AtomPub</code>. Итак, какие значения заголовка Accept можно использовать.</p>\n<ul>\n<li><strong>ATOM:</strong> application/atom+xml</li>\n<li><strong>JSON:</strong> application/json</li>\n</ul>\n<p>Для проверки этой функциональности очень удобно использовать утилиту <a href=\"http://www.fiddlertool.com/fiddler/\">Fiddler</a>, которая позволяет исполнять HTTP-запросы с заданными параметрами, в т.ч. HTTP-заголовками. Утилита эта бесплатная, так что можно ей спокойно пользоваться :)</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2008/adonet-data-services-representation/402062b1/1@2x.png\"  alt=\"Представление данных в ADO.NET Data Services\"/>",
            "tags": [
                "Windows Communication Foundation"
            ],
            "banner_image": "https://blog.zwezdin.com/2008/adonet-data-services-representation/7c316b65/adonet_data-services-representation@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2008/tsql-for-sqlcompact/",
            "url": "https://blog.zwezdin.com/2008/tsql-for-sqlcompact/",
            "title": "Генерация T-SQL скрипта для создания базы данных SQL Compact",
            "date_published": "2008-12-15T13:00:00.000Z",
            "content_html": "<p>Во “взрослой” версии SQL Server в Management Studio существует опция генерации T-SQL скрипта для создания базы данных. Это бывает удобно когда мы, например, хотим создать чистый дубликат существующей базы данных или для каких-то других целей. К сожалению, для SQL Compact такой опции не существует.</p>\n<p>Тем не менее, уже нашлись умельцы, которые создали утилиту, которая на основании данных системных представлений генерирует соответствующий T-SQL скрипт.</p>\n<p>Эта утилита называется <a href=\"http://www.codeplex.com/ExportSqlCE\">ExportSqlCE</a> и размещена на CodePlex. Представляет она собой консольное приложение, которое на выходе генерирует нужный скрипт. Запустить ее можно так:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">ExportSQLCE.exe \"Data Source=C:\\DB\\Northwind.sdf;\" Northwind.sql</code></pre>\n<p>После этого, в выходном файле мы увидим скрипт, который можем исполнить где-то еще.</p>",
            "tags": [
                "LINQ"
            ],
            "banner_image": "https://blog.zwezdin.com/2008/tsql-for-sqlcompact/d8ea585d/tsql_for-sqlcompact@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2008/bizspark/",
            "url": "https://blog.zwezdin.com/2008/bizspark/",
            "title": "Microsoft идет на встречу инновационным компаниям-разработчикам",
            "date_published": "2008-12-12T13:00:00.000Z",
            "content_html": "<p>Совсем недавно компания Microsoft объявила о ряде программ, поддерживающих различные сегменты рынка. Так, например, наверняка вы видели информацию о программе DreamSpark, которая призвана помочь студентам. Для молодого бизнеса появилась другая программа, встречайте — BizSpark.</p>\n<p><strong>BizSpark</strong> — это инновационная программа, предоставляющая начинающим компаниям-разработчикам ПО в течение 3 лет набор следующих преимуществ:</p>\n<p>Доступ к технологиям, в том числе:</p>\n<ul>\n<li>Средства для дизайна, разработки и тестирования. Предоставляются лицензии на  Expression Studio (1 лицензия), Visual Studio Team System c MSDN Premium и Team Foundation Server (стандартный выпуск) на всю команду разработки</li>\n<li>Лицензии на промышленное использование для размещения в сети Интернет решений типа SaaS и интернет-сервисов, созданных на базе программного обеспечения.  Предоставляются лицензии на  Windows Server, SQL Server, SharePoint Portal Server, Systems Center, BizTalk Server без ограничения количества.</li>\n</ul>\n<p>Поддержка:</p>\n<ul>\n<li>Профессиональная поддержка от Microsoft: 2 инцидента технической поддержки на компанию</li>\n<li>Доступ к MSDN Premium</li>\n<li>Поддержка Партнеров по сообществу BizSpark : консультации, инвестиции, хостинг, и пр.</li>\n</ul>\n<p>Продвижение</p>\n<ul>\n<li>Возможность заявить о себе, создав свой профиль в интерактивном каталоге BizsparkDB на сайте Microsoft Startup Zone</li>\n<li>Возможность быть отобранной в BizSparkDB как «Компания недели» BizSpark на сайте Microsoft Startup Zone</li>\n</ul>\n<p>Условия предоставления BizSpark:</p>\n<ul>\n<li>Компания должна разрабатывать программный продукт или размещаемый интернет-сервис, основанный на программном обеспечении, который будет являться ключевой компонентой собственного бизнеса компании. Если компания развивает одновременно несколько видов бизнеса, то лицензии, полученные в рамках BizSpark могут использоваться в рамках разработки тиражируемых продуктов и сервисов, но не могут – для оказания услуг конечным заказчикам , таких как хостинг, дизайн веб-сайтов, системная интеграция, аутсорсинговая разработка ПО.</li>\n<li>Компания должна быть частной, находиться в бизнесе не более 3 лет и иметь годовой доход не более 500 тыс. долл. США (цифра дохода приведена для России). Предприниматели, которые только находятся в процессе регистрации юр. лица, также могут участвовать в программе.</li>\n</ul>\n<p>Регистрация в программе бесплатна. <del>По окончании членства в программе, начинающая компания должна оплатить Microsoft взнос за участие в программе в размере 100 долл. США.</del></p>",
            "tags": [
                "Всякое разное"
            ],
            "banner_image": "https://blog.zwezdin.com/2008/bizspark/7444c3e6/bizspark@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2008/adonet-data-services-security/",
            "url": "https://blog.zwezdin.com/2008/adonet-data-services-security/",
            "title": "Безопасность в ADO.NET Data Services",
            "date_published": "2008-12-10T13:00:00.000Z",
            "content_html": "<p>Тема безопасности в распределенных приложениях является очень актуальной, особенно когда сервис работает в глобальной сети. Давайте рассмотрим вопросы безопасности в рамках технологии <strong>ADO.NET Data Services</strong>.</p>\n<p>В рамках <strong>ADO.NET Data Services</strong> механизмы безопасности можно рассматривать на нескольких уровнях.</p>\n<p><em>Во-первых</em>, т.к. сервисы ADO.NET Data Services работают в рамках hosting-среды ASP.NET, они очень тесно интегрируются с существующей инфраструктурой ASP.NET. Это означает, что мы можем использовать существующие механизмы аутентификации ASP.NET, Membership API, Card Space и т.д. Что интересно, мы можем использовать все существующие наработки в этой области, например собственные механизмы аутентификации, реализованные в рамках HTTP-модулей.</p>\n<p><em>Во-вторых</em>, существует разграничение видимости на уровне объектных коллекций. Существуют варианты полного доступа (чтения/записи), чтения и выполнения запросов, только чтения одиночных сущностей, записи одиночных сущностей и др. Для того, чтобы настроить видимость на уровне контейнеров необходимо в методе <code>InitializeService</code> воспользоваться объектом <code>config</code>. У этого объекта есть два метода – <code>SetEntitySetAccessRule</code> и <code>SetServiceOperationAccessRule</code>. Эти методы идентичны, за тем исключением, что первый задает видимость для объектных коллекций, а второй – сервисных операций.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class NorthwindService : DataService&lt;NorthwindEntities>\n&#123;\n    public static void InitializeService(IDataServiceConfiguration config)\n    &#123;\n        config.SetEntitySetAccessRule(\"*\", EntitySetRights.All);\n        config.SetServiceOperationAccessRule(\"*\", ServiceOperationRights.All);\n    &#125;\n\n    // ..\n&#125;</code></pre>\n<p>Первым параметром передается имя контейнера или операции в виде строки. Если необходимо задать настройки для всех контейнеров/операций, то используется символ <code>*</code>. Тем не менее, в работающих проектах таким синтаксисом я бы пользоваться не стал. Вторым параметром этим методам передается значение enum-а, который указаывает, значение видимости. Например, если мы укажем значение None, то при работе с сервисом заданную коллекцию мы просто не увидим, как будто ее нету :)</p>\n<p>Ну и последний, самый мощный механизм – это перехватички (<code>interceptors</code>). Перехватчики – это специальные методы, которые исполняются на каждую отдаваемую клиенту сущность.</p>\n<p>Например, у меня есть коллекция, которая возвращает набор заказчиков. Я могу создать следующий перехватчик:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[QueryInterceptor(\"Customers\")]\npublic Func&lt;Customers, bool> OnQueryCustomers()\n&#123;\n   return c => c.ContactName.Contains(\" \") == false;\n&#125;</code></pre>\n<p>Как видно, этот метод возвращает лямбда-выражение, которое исполняется при отдаче клиенту каждой сущности. Это лямбда-выражение возвращет булевское значение, если оно равно <code>true</code>, то данная сущность отдаете клиенту, если <code>false</code>, то клиент ничего об этой сущности не узнает :) В данном примере мы выдаем только те сущности <code>Customer</code>, которые не содержат пробелов. Можно переделать этот пример следующим образом:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[QueryInterceptor(\"Customers\")]\npublic Func&lt;Customers, bool> OnQueryCustomers()\n&#123;\n   return c => c.ContactName == HttpContext.Current.User.Identity.Name;\n&#125;</code></pre>\n<p>В этом случае мы используем <code>HttpContext</code> и получаем имя текущего пользователя. Затем если имя текущего пользователя и поле <code>ContactName</code> совпадают, то эту сущность мы клиенту отдаем. Таким образом, каждый пользователь будет работать со своим набором данных, несмотря на то, что данные для всех пользователей хранятся в одной таблице.</p>\n<p>Перехватичик существуют не только для операций выборки, но и для операций модификации. В этом случае перехватчик представляет собой метод, который генерирует исключение, в случае, если операция недопустима.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[ChangeInterceptor(\"Customers\")]\npublic void OnChangeCategories(Customers c, UpdateOperations mode)\n&#123;\n   if ((mode == UpdateOperations.Add) || (mode == UpdateOperations.Change))\n   &#123;\n      if (c.ContactTitle.Contains(\" \") == true)\n      &#123;\n       throw new DataServiceException(400, \"Contact title consist of a single word.\")\n      &#125;\n   &#125;\n&#125;</code></pre>\n<p>Видно, что в перехватчик принимает два параметра – изменяемую сущность и тип операции. В случае ошибки генерируется исключение <code>DataServiceException</code>.</p>\n<p>Точно также можно использовать инфраструктуру безопасности при модификации данных.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[ChangeInterceptor(\"Customers\")]\npublic void OnChangeCategories(Customers c, UpdateOperations mode)\n&#123;\n   if (mode == UpdateOperations.Delete)\n   &#123;\n      if (c.ContactName == HttpContext.Current.User.Identity.Name)\n      &#123;\n         throw new DataServiceException(400, \"You can't delete it.\")\n      &#125;\n   &#125;\n&#125;</code></pre>\n<p>Важная особенность перехватчиков, что эти методы помечаются атрибутами <code>QueryInterceptor</code> или <code>ChangeInterceptor</code>, в параметрах которых мы указываем для какой сущности предназначены эти перехватчики.</p>",
            "tags": [
                "Windows Communication Foundation"
            ],
            "banner_image": "https://blog.zwezdin.com/2008/adonet-data-services-security/6e2fb653/adonet_data-services-security@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2008/adonet-data-services-sources/",
            "url": "https://blog.zwezdin.com/2008/adonet-data-services-sources/",
            "title": "Источники данных в ADO.NET Data Services",
            "date_published": "2008-12-08T13:00:00.000Z",
            "content_html": "<p><strong>ADO.NET Data Services</strong> позвоялет строить REST-сервисы поверх какой-то готовой модели данных. Очень часто мы видим примеры сервисов <strong>ADO.NET Data Services</strong> на основе Entity Data Model или модели LINQ to SQL. Однако, это не единственные сценарии. Давайте посмотрим как это работает в действительности.</p>\n<p>Как мы помним, при создании сервиса, создается класс-наследник от базового generic-класса <code>DataContext&lt;&gt;</code>. В generic-параметре мы как раз указываем класс, реализующий модель данных.</p>\n<p>На самом деле, при обращении к сервису создается новый экзмепляр этого класса и производится поиск полей и свойств, которые имеют тип <code>IQueryable</code>. Если ADO.NET Data Services находит в этом классе свойства таких типов, то они предоставляеются через сервис для работы с ними. Теперь независимо от того, что на самом деле из себя представляет модель данных мы можем строить запросы к этому сервису, используя стандартный синтаксис URI.</p>\n<p>Что это нам дает? Благодаря этому мы можем строить свои модели данных, которые используют информацию из реляционного источника, файловой системы, другого веб-сервиса, каких-то текстовых, XML файлов, и т.д. Например, мы можем построить модель данных следующим образом:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class MyDataModel\n&#123;\n    public IQueryable&lt;string> Data1\n    &#123;\n        get\n        &#123;\n            string[] data = GetData();\n            return data.AsQueryable();\n        &#125;\n    &#125;\n&#125;</code></pre>\n<p>В этом примере мы возвращаем коллекцию строк, однако мы можем построить более сложную модель данных, которая будет работать с какими-то сущностями. Для этого нам нужно создать класс, содеращий нужные нам поля и пометить этот класс атрибутом <code>DataServiceKey</code>, в параметрах которого указать ключевое поле:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[DataServiceKey(\"ID\")]\npublic class Person\n&#123;\n    public int ID &#123; get; set; &#125;\n    public string Name &#123; get; set; &#125;\n    public string Address &#123; get; set; &#125;\n&#125;</code></pre>\n<p>После этого можно предоставить коллекцию этих объектов через сервис.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class MyDataModel\n&#123;\n    public IQueryable&lt;Person> Persons\n    &#123;\n        return ...;\n    &#125;\n&#125;</code></pre>\n<p>Атрибут <code>DataServiceKey</code> является обязательным, иначе <strong>ADO.NET Data Services</strong> не сможет понять что является ключевым полем.</p>\n<p>Таким образом, для того, чтобы создать собственную модель данных мы должны создать совершенно обычный класс, который содержит свойства, возвращающие интерфейс <code>IQueryable</code>. Однако, таким образом мы реализовали read-only модель данных. Для того, чтобы эту модель данных можно было обновлять, необходимо реализовать интерфейс <code>IUpdatable</code> уже у самого класса модели данных. Но об этом в следующий раз :)</p>",
            "tags": [
                "Windows Communication Foundation"
            ],
            "banner_image": "https://blog.zwezdin.com/2008/adonet-data-services-sources/60ee2b5e/adonet_data-services-sources@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2008/adonet-data-services-extensibility/",
            "url": "https://blog.zwezdin.com/2008/adonet-data-services-extensibility/",
            "title": "Способы расширения функциональности ADO.NET Data Services с помощью сервисных операций",
            "date_published": "2008-12-06T13:00:00.000Z",
            "content_html": "<p>Как мы знаем <strong>ADO.NET Data Services</strong> предоставляет нам удобный синтаксис URI, с помощью которого можно адресовать любые части модели данных. Кроме того, конструируя URI мы можем выполнять какие-то серверные операции вроде сортировки, фильтрации или разбиения на страницы. Но как же быть со сложными запросами, которые, например, включают в себя несколько join-ов?</p>\n<p>При решении этой задачи можно конечно сделать несколько обращений к сервису, получить нужные данные, а затем построить свой сложный запрос поверх in-memory объектов, но есть способ лучше :).</p>\n<p>В рамках сервисов <strong>ADO.NET Data Services</strong> мы можем создать специальные методы, которые называются сервисные операции.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[WebGet]\npublic IQueryable&lt;Customers> GetCustomersWithOrders()\n&#123;\n  return ...;\n&#125;</code></pre>\n<p>Как мы видим у этого метода есть две особенности: во-первых, мы помечаем его атрибутом <code>WebGet</code> или <code>WebInvoke</code>; а во-вторых, для того, чтобы это была полноценная коллекция данных мы должны вернуть интерфейс <code>IQueryable</code>.</p>\n<p>Теперь, когда в рамках сервиса имеется такой метод мы можем совершенно стандартно обратиться к нему следующим образом:</p>\n<pre class=code language-clike><code class=\"code__container language-clike\">http://../Service.svc/GetCustomersWithOrders/</code></pre>\n<p>Точно также мы можем создать операцию с параметрами.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[WebGet]\npublic IQueryable&lt;Customers> GetCustomersWithOrders(int count)\n&#123;\n  return ...;\n&#125;</code></pre>\n<p>В этом случае можно обратиться к этой операции так:</p>\n<pre class=code language-clike><code class=\"code__container language-clike\">http://../Service.svc/GetCustomersWithOrders/?$count=25</code></pre>\n<p>Понятно, что в рамках этой операции мы можем строить сколько угодно сложный запрос, включая join-ы и т.д. При этом, используя свойство <code>CurrentDataSource</code> мы можем обратиться к источнику данных, на основе которого построен сам сервис. Таким образом, метод может выглядеть следующим образом:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[WebGet]\npublic IQueryable&lt;Customers> GetCustomersWithOrders(int count)\n&#123;\n  var q = from c in this.CurrentDataSource.Customers\n          where c.Orders.Count > count\n          select c;\n\n  return q.AsQueryable();\n&#125;</code></pre>\n<p>В данном случае мы создаем LINQ запрос к источнику данных, используя возможно какие-то параметры, join-ы, группировки, фильтровки и др. и возвращаем результат в виде <code>IQueryable</code>.</p>\n<p>В этих случаях мы можем осуществлять GET-запросы к указанным операциям. Если мы хотим использовать методы, например, <code>PUT</code>, <code>POST</code> или любые другие, то мы должны использовать атрибут <code>WebInvoke</code>, вместо <code>WebGet</code>.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[SingleResult]\n[WebInvoke(Method=\"POST\")]\npublic void DoSomething()\n&#123;\n   // ...\n&#125;</code></pre>\n<p>При этом мы можем указать абсолютно разнообразные методы при указании параметров атрибута.</p>\n<p>Ну и на самом деле мы можем вовзращять не обязательно коллекцию объектов, можно вернуть например строку. При этом мы должны пометить операцию атрибутом <code>SingleResult</code>. Если необходимо, чтобы сервис корретно выдавал HTTP-заголовок <code>ContentType</code>, то нужно пометить класс сервиса атрибутом <code>MimeType</code> и в нем указать к какой метод какой тип информации должен возвращать. В итоге можем получить следующую операцию:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[MimeType(\"Help\", \"text/html\")]\npublic class NorthwindService : DataService&lt;NorthwindEntities>\n&#123;\n   [SingleResult]\n   [WebGet]\n   public string Help()\n   &#123;\n      return @\"&lt;html>...&lt;/html>\";\n   &#125;\n\n  // ...\n&#125;</code></pre>\n<p>Кстати, не обязательно возвращать строку, можно вернуть например бинарный поток, в этом случае в качестве типа возвращаемого значения будет <code>Stream</code>.</p>\n<p>Однако, с операциями, которые возвращают не <code>IQueryable</code> существует одна досадная неприятность. Дело в том, что если мы вернем например строку, и обратимся к этому методу как обычно, то результат будет обернут в XML. Для того, чтобы получить тот же самый HTML, который мы хотим получить, необходимо обратиться к операции, используя ключевое слово <code>$value</code>.</p>\n<pre class=code language-clike><code class=\"code__container language-clike\">http://../Service.svc/Help/$value</code></pre>\n<p>Теперь, на выходе мы получим HTML-код и заголовок <code>ContentType</code> будет содержать правильное значение. Точно также дела обстоят с бинарными данными, например картинками.</p>",
            "tags": [
                "Windows Communication Foundation"
            ],
            "banner_image": "https://blog.zwezdin.com/2008/adonet-data-services-extensibility/704b8c0c/adonet_data-services-extensibility@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2008/adonet-data-services-expand/",
            "url": "https://blog.zwezdin.com/2008/adonet-data-services-expand/",
            "title": "Навигация по связанным сущностям в рамках ADO.NET Data Services",
            "date_published": "2008-12-04T13:00:00.000Z",
            "content_html": "<p>В последнее время, когда данные все чаще представляют в виде объектных моделей время от времени появляются разговоры о навигации по связанным сущностям. Т.е. с точки зрения модели данных есть две коллекции, сущности которых ссылаются друг на друга, и технологии, которые позволяют работать с моделями данных так или иначе стараются решить проблемы навигации/перехода от одной сущности к другой.</p>\n<p>Сейчас я бы хотел поговорить о том, каким образом это реализуется в технологии <a href=\"/2008/adonet-data-services-introduction\">ADO.NET Data Services</a>. ADO.NET Data Services – это платформа, которая позволяет предоставить доступ к данным в Web по протоколу HTTP в REST-стиле.</p>\n<p>Соответственно, когда на клиентской стороне мы получаем доступ к какой-либо модели данных, полученной от <strong>ADO.NET Data Services</strong>, задача навигации между сущностями кажется совершенно нормальной. ADO.NET Data Services предлагает два варианта решения этой задачи:</p>\n<ul>\n<li>Использовать ключевое слово <code>$expand</code> при выполнении запроса.</li>\n<li>Использовать метод клиентского класса <code>LoadProperty()</code>.</li>\n</ul>\n<p>Давайте посмотрим в чем разница.</p>\n<p>При использовании ключевого слова <code>$expand</code> мы явно при выполнении запроса указываем какие связанные контейнеры объектов хотим получить. Например, у нас есть таблица <code>Customers</code> и связанная с ней таблица <code>Orders</code>. В этом случае мы можем осуществить следующий запрос:</p>\n<pre class=code language-clike><code class=\"code__container language-clike\">http://.../Service.svc/Customers/?$expand=Orders</code></pre>\n<p>В этом случае в рамках одного запроса к серверу (round-trip) будет получена коллекция Customers (например, в виде AtomPub) и для каждой сущности Customer будут представлены объекты <code>Order</code>. Таким образом, на клиентской стороне после выполнения такого запроса и навигации от объекта <code>Customer</code> к объектам <code>Order</code> запросов больше выполняться не будет – все нужные данные уже загружены. Для того, чтобы клиентский прокси-класс включил в генерируемый URI ключевое слово <code>Expand</code> необходимо воспользоваться одноименным extension-методом. Вот как может выглядеть код клиентского приложения:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">var context = new NorthwindService.NorthwindEntities(new Uri(@\"http://../Service.svc\"));\nvar query = context.Customers.Expand(\"Orders\");\nforeach (var customer in query)\n&#123;\n    Console.WriteLine(customer.CompanyName);\n    foreach (var order in customer.Orders)\n    &#123;\n        Console.Write(order.OrderID);\n    &#125;\n    Console.WriteLine();\n&#125;</code></pre>\n<p>Однако, такой подход хорош только тогда, когда мы точно знаем что данные из связанной коллекции нам будут нужны. Если при перемещенни по коллекции Customers данные о заказах (<code>Orders</code>) нам нужны только в небольшом количестве случаев, то возможно нету большого смысла загружать все данные из связанной коллекции <code>Orders</code>. В этом случае мы можем прибегнуть ко второму способу – использованию метода <code>LoadProperty()</code>.</p>\n<p>При вызове метода <code>LoadProperty()</code> будет осуществляться отдельный round-trip к сервису для получения только тех объектов <code>Orders</code>, которые связаны с текущим объектом <code>Customer</code>. Таким образом, мы можем не использовать <code>Expand</code> для нашего запроса, а тогда, когда нам будут необходимы данные из связанной коллекции мы можем обратиться к методу <code>LoadProperty()</code> и после исполнения запроса получить необходимые данные. Вот как может выглядеть код клиентского приложения в этом случае:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">var context = new NorthwindService.NorthwindEntities(new Uri(@\"http://../Service.svc\"));\nvar query = context.Customers; //.Expand(\"Orders\")\nforeach (var customer in query)\n&#123;\n    Console.WriteLine(customer.CompanyName);\n\n    // определенные условия\n    if (..)\n    &#123;\n        context.LoadProperty(customer, \"Orders\");\n\n        foreach (var order in customer.Orders)\n        &#123;\n            Console.Write(order.OrderID);\n            Console.Write(\" \");\n        &#125;\n    &#125;\n    Console.WriteLine();\n&#125;</code></pre>\n<p>Понятно, что оба варианта по своему хороши. Если мы точно знаем что каждый раз будем работать со связанной коллекцией, то нет смысла генерировать большое количество запросов к сервису — в этом случае проще получить все за один запрос; в случае если данные из связанных коллекций нужны далеко не всегда, то в этом случае можно подгружать их только тогда, когда это необходимо и не генерировать огромный ответ, который включает по сути две коллекции, хотя это вовсе не обзятельно.</p>",
            "tags": [
                "Windows Communication Foundation"
            ],
            "banner_image": "https://blog.zwezdin.com/2008/adonet-data-services-expand/58c1655b/adonet_data-services-expand@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2008/linqpad/",
            "url": "https://blog.zwezdin.com/2008/linqpad/",
            "title": "LINQPad",
            "date_published": "2008-12-01T13:00:00.000Z",
            "content_html": "<p>Набрел сегодня в пространствах Интернета на достаточно интересный инструмент — <strong>LINQPad</strong>.</p>\n<p>Сама по себе она достаточно простая. Но в то же время можно <em>“по быстрому”</em> опробовать возможности LINQ и строить какие-то запросы. Тем не менее, не смотря на её простоту она умеет работать с LINQ to SQL, LINQ to Objects и LINQ to XML, хотя я успел попробовать только первое.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2008/linqpad/0d2a5df9/1@2x.png\"  alt=\"LINQPad\"/>\n\n\n\n\n<p>Распространяется LINQPad бесплатно, а размер всего 2Mb.</p>",
            "tags": [
                "Tools",
                "LINQ"
            ],
            "banner_image": "https://blog.zwezdin.com/2008/linqpad/9b6e7fc9/linqpad@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2008/snippet-designer/",
            "url": "https://blog.zwezdin.com/2008/snippet-designer/",
            "title": "Snippet Designer",
            "date_published": "2008-11-22T13:00:00.000Z",
            "content_html": "<p>Достаточно часто встречаются ситуации, когда какой-то кусок кода хочется оформить в сниппет и пользоваться им в будущем, но тратить драгоценное время на написание XML-описания очень не хочется. Так бывает например при разработке приложений или при подготовке к презентации с демо, в которой много кода.</p>\n<p>В силу указанных причин ранее я не писал сниппеты, а нужные куски кода просто перетаскивал в Toolbox у Visual Studio. Такой подход работает в презентациях/демонстрациях, но разрабатывать реальное приложения исопльзуя такой подход очень тяжело.</p>\n<p>Оказывается существует замечательный плагин для Visual Studio 2008 – <strong>Snippet Designer</strong>. Этот плагин позволяет буквально в несколько нажатий мышки создать нужный сниппет и разместить его в нужном месте.</p>\n<p>Давайте посмотрим как работает Snippet Designer. После установки Snippet Designer сделаем следующие шаги.</p>\n<p><strong>Шаг 1:</strong> Для начала создадим пустой проект и в нем напишем некий код. После этого выделим нужный участок и в контекстном меню выберем пункт <code>Export as snippet</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2008/snippet-designer/251a4a63/1@2x.png\"  alt=\"Snippet Designer\"/>\n\n\n\n\n<p><strong>Шаг 2:</strong> После этого откроется окно редактирования сниппета. Выберем нужные участки в коде и с помощью контекстного меню сделаем их заменяемыми.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2008/snippet-designer/73cd6d01/2@2x.png\"  alt=\"Snippet Designer\"/>\n\n\n\n\n<p>После этого в нижней части окна появятся все заменяемые части. Там для каждой части мы можем задать текст подсказки, значение по умолчанию, тип и т.д.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2008/snippet-designer/c9ae40b1/3@2x.png\"  alt=\"Snippet Designer\"/>\n\n\n\n\n<p><strong>Шаг 3:</strong> После этого у сниппета можно задать свойства. Для этого используется окно Properties. Там мы можем задать различные параметры. Самый главный – это параметр <code>Shortcut</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2008/snippet-designer/9e351019/4@2x.png\"  alt=\"Snippet Designer\"/>\n\n\n\n\n<p><strong>Шаг 4:</strong> После этого сохраняем наш сниппет и он становится доступным для использования.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2008/snippet-designer/1cd5c181/5@2x.png\"  alt=\"Snippet Designer\"/>\n\n\n\n\n<p>Вот такой вот интересный плагин. Что приятно, проект размещен на <del>CodePlex</del>Github вместе с исходным кодом и распространяется бесплатно.</p>",
            "tags": [
                "Tools",
                "Visual Studio"
            ],
            "banner_image": "https://blog.zwezdin.com/2008/snippet-designer/648fed3b/snippet_designer@cover.png"
        },
        {
            "id": "https://blog.zwezdin.com/2008/dynamic-features-in-dotnet4/",
            "url": "https://blog.zwezdin.com/2008/dynamic-features-in-dotnet4/",
            "title": "Dynamic features в .NET 4.0",
            "date_published": "2008-11-21T13:00:00.000Z",
            "content_html": "<p>Одной из наиболее важной особенностью .NET 4.0 является поддержка динмаического типа. Давайте посмотрим что это такое.</p>\n<p>Поддержка динамического типа стала возможной благодаря появлению ключевого слова <code>dynamic</code>. При объявлении динамического объекта можно обращаться к любому члену класса, при этом заранее не зная что это за класс (не зная типа). Например так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">static void DynamicMethod(object someObject)\n&#123;\n    dynamic dynamicObject = someObject;\n    dynamicObject.MyMethod();\n&#125;</code></pre>\n<p>В этом примере мы видим, что в метод приходит объект неизвестного нам типа и теперь, используя ключевое слово <code>dynamic</code>, мы можем вызывать любые методы и обращаться к любым свойствам объекта.</p>\n<p>На самом деле в недрах .NET 4.0 для такой конструкции используется Reflection и поиск нужного члена класса с немного хитрым алгоритмом (кэшированием), но не будем о нем сейчас. Как вы понимаете, если у вызываемого объекта нету такого члена, то получим исключение в runtime. Поэтому нужно быть с <code>dynamic</code> осторожнее.</p>\n<p><em>Что можно сделать еще?</em> Можно передавать динамические объекты как параметр метода. Таким образом становится возможным передать в метод например объект анонимного типа.</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">static void Main(string[] args)\n&#123;\n    DynamicMethod2(new &#123; Id = 5, Name = \"1234\" &#125;);\n&#125;\n\nstatic void DynamicMethod2(dynamic someObject)\n&#123;\n    Console.WriteLine(someObject.Name);\n&#125;</code></pre>\n<p>Соответственно, этот код работает аналогично. Такие возможности .NET4.0 в первую очередь ориентированы на динамические языки и позволят работать с ними очень хорошо. Например, можем взять JSON-описание объекта и превратить его в нормальный объект .NET и с ним уже работать.</p>\n<p>Ну и точно также мы можем вернуть динамический объект, например анонимный:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">static dynamic DynamicMethod3()\n&#123;\n    return new &#123; Id = 555 &#125;;\n&#125;</code></pre>",
            "tags": [
                "C#",
                ".NET"
            ],
            "banner_image": "https://blog.zwezdin.com/2008/dynamic-features-in-dotnet4/be0ab159/dynamic_features-in-dotnet4@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2008/wcf-rest/",
            "url": "https://blog.zwezdin.com/2008/wcf-rest/",
            "title": "WCF + REST",
            "date_published": "2008-10-01T12:00:00.000Z",
            "content_html": "<p>Я уже <a href=\"/2008/soap-vs-rest\">рассказывал</a> о том, что для построения веб-сервисов, кроме наиболее популярного подхода SOAP, существует другой подход – REST. Очень удачной для реализации REST-сервисов является технология <a href=\"/2008/adonet-data-services-introduction\">ADO.NET Data Services</a>. Однако, для построения REST-сервисов мы также можем воспользоваться и Windows Communication Foundation. Давайте посмотрим каким образом.</p>\n<p>Прежде чем рассматривать инструменты для построения REST-сервисов WCF, давайте вспомним отличительные черты таких сервисов.</p>\n<p>Любой веб-сервис на базе REST:</p>\n<ol>\n<li>Позволяет через Web (а именно поверх HTTP/HTTPS) предоставить доступ к данным;</li>\n<li>Каждый блок некоторой информации, предоставляемый через REST-сервис однозначно адресуется с помощью URI;</li>\n<li>Результатом работы такого сервиса будет некий XML документ (RSS, Atom, ..), т.е. feed и вспомогательные данные (бинарные, html, все что угодно).</li>\n</ol>\n<p>Если к 1) и 3) пункту вопросов не возникает, то видно что 2) пункт явно не укладывается в идеологию WCF в том виде, в котором она существовала ранее. Т.е. если ранее мы могли как-то адресовать веб-сервисы, то на каждый URI нужно было описать публичный контракт и создать сервис. Однако, для REST-сервисов это критичный момент. Поэтому для реализации такой поддержки в составе WCF .NET Framework 3.5 появился целый HTTP API, который призван решить эти и другие проблемы.</p>\n<p>В рамках HTTP API существуют такие важные классы как <code>UriTemplate</code>, <code>UriTemplateMatch</code> и <code>UriTemplateTable</code>. Именно этот набор классов позволяет осуществить маппинг конкретных URI на нужные операции. Несмотря на то, что эти объекты напрямую мы не используем, я думаю будет очень полезно для общего понимания посмотреть как они работают. Для этого давайте посмотрим следующий пример:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">Uri address = new Uri(\"http://localhost:8000\");\nUriTemplate template = new UriTemplate(\"&#123;category&#125;/&#123;product&#125;\");\nUri boundUri = template.BindByPosition(address, \"notebooks\", \"lenovo-t61\");\nConsole.WriteLine(address.ToString()); // http://localhost:8000/notebooks/lenovo-t61</code></pre>\n<p>Как видно, здесь мы задаем некий базовый URI и шаблон URI. Далее, используя полученный шаблон можно задать значение секций, которые определены в шаблоне. Как видим, в результате мы получаем URI вида <code>http://localhost:8000/notebooks/lenovo-r61</code>. Понятно, что если мы просто изменим шаблон URI, например, на <code>shop/&#123;category&#125;/&#123;product&#125;/info</code>, то в результате получим <code>http://localhost:8000/shop/notebooks/lenovo-r61/info”</code> И так далее. Дальше, используя класс <code>UriTemplateMatch</code> можно выполнить обратную операцию – по полученному URI и шаблону получить значение конкретных секций (в данном примере - <code>category</code>, <code>product</code>).</p>\n<p>Как я уже говорил, эти объекты мы не используем напрямую, тем не менее они используются при маппинге URI на конкретную операцию.</p>\n<p>Теперь, когда мы знаем как осуществляется маппинг, давайте рассмотрим каким образом можно построить REST-сервис на базе WCF. Для этого очень удобно пользоваться стандартным шаблоном при созданни проекта в VS2008 <code>Syndication Service Library</code>. Конечно, это не обязательное условие.</p>\n<p>Построение публичного контракта. Для создания любого WCF-сервиса в первую очередь необходимо создать для него публичный контракт. REST-сервисы на базе WCF не исключение. Стандартный контракт в WCF для REST-сервиса выглядит следующим образом:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[ServiceContract]\n[ServiceKnownType(typeof(Atom10FeedFormatter))]\n[ServiceKnownType(typeof(Rss20FeedFormatter))]\npublic interface IFeed1\n&#123;\n    [OperationContract]\n    [WebGet(UriTemplate = \"*\")]\n    SyndicationFeedFormatter CreateFeed();\n\n    // TODO: Add your service operations here\n&#125;</code></pre>\n<p>В таком контракте, кроме стандартных атрибутов <code>ServiceContract</code> и <code>OperationContract</code> мы видим одно нововведение – атрибут <code>WebGet</code>. Именно с помощью этого атрибута мы можем указать какой URI соответствует какой операции. В приведенном примере все URI отображаются на единственную операцию — <code>CreateFeed()</code>. Давайте этот пример несколько модифицируем:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[ServiceContract]\n[ServiceKnownType(typeof(Atom10FeedFormatter))]\n[ServiceKnownType(typeof(Rss20FeedFormatter))]\npublic interface IFeed1\n&#123;\n    [OperationContract]\n    [WebGet(UriTemplate = \"authors\")]\n    SyndicationFeedFormatter AuthorsFeed();\n\n    [WebGet(UriTemplate = \"authors/&#123;author&#125;\")]\n    SyndicationFeedFormatter AuthorsFeed(string author);\n&#125;</code></pre>\n<p>Теперь, в таком сервисе мы можем использовать два вида URI: <code>../feed1/authors</code> и <code>../feed1/authors/&lt;имя автора&gt;</code>. Причем при вызове того или иного URI, WCF автоматически определит какую операцию нужно вызвать, а во втором случае еще и передаст нужные параметры в метод (которые извлекаются из самого URI, помним объект <code>UriTemplateMatch</code> из HTTP API). Подобным образом мы можем создать нужное количество операций и определить для них те URI, которые нам необходимы.</p>\n<p>Теперь давайте обратим внимание на тип возвращаемого операцией значения. В данном случае мы возвращаем некую ленту (feed). В поставке .NET 3.5 существуют два стандартных форматировщика – для RSS 2.0 и AtomPub 1.0. Однако, никто не мешает нам написать свои собственные форматировщики :).</p>\n<p>Я уже говорил о том, что в рамках такого сервиса мы можем возвращать не только XML, но и вообще говоря любые данные, например, картинку. Для этого мы можем изменить контракт следующим образом:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[ServiceContract]\n[ServiceKnownType(typeof(Atom10FeedFormatter))]\n[ServiceKnownType(typeof(Rss20FeedFormatter))]\npublic interface IFeed1\n&#123;\n    [OperationContract]\n    [WebGet(UriTemplate = \"authors\")]\n    SyndicationFeedFormatter AuthorsFeed();\n\n    [WebGet(UriTemplate = \"authors/&#123;author&#125;\")]\n    SyndicationFeedFormatter AuthorsFeed(string author);\n\n    [WebGet(UriTemplate = \"authors/photo/&#123;author&#125;\")]\n    Stream AuthorPhoto(string author);\n&#125;</code></pre>\n<p>В данном случае мы видим, что добавилась еще одна операция со своим URI, которая возвращает Stream. Внутри этого потока может быть что угодно, в т.ч. картинка.</p>\n<p>Контракт готов, давайте перейдем к реализации самого сервиса. Собственно, сам сервис – это класс, реализующий наш публичный контракт. Здесь самое время поговорить об объектах <code>SyndicationFeed</code> и <code>SyndicationItem</code> – это объекты, являющются абстракциями ленты (feed) и ее содержимого. Наличие таких объектов позволяет нам не думать о конкретном формате ленты (RSS, Atom или еще что), а просто создать содержимое нашей ленты. Таким образом, метод <code>AuthorsFeed()</code> может выглядеть следующим образом:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public SyndicationFeedFormatter AuthorsFeed()\n&#123;\n    SyndicationFeed feed = new SyndicationFeed(\"Feed Title\", \"A WCF Syndication Feed\", null);\n\n    List&lt;SyndicationItem> items = new List&lt;SyndicationItem>();\n    items.Add(new SyndicationItem(\"Author1\", \"Author1. Age: 23.\", null));\n    items.Add(new SyndicationItem(\"Author2\", \"Author2. Age: 26.\", null));\n\n    feed.Items = items;\n\n    return new Rss20FeedFormatter(feed);\n&#125;</code></pre>\n<p>Как видно, здесь мы создаем ленту, наполняем ее содержимым и возвращаем конкретный форматировщик – в данном случае форматировщик RSS 2.0. Если мы хотим работать с AtomPub, можно несколько изменить реализацию метода:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public SyndicationFeedFormatter AuthorsFeed()\n&#123;\n    SyndicationFeed feed = new SyndicationFeed(\"Feed Title\", \"A WCF Syndication Feed\", null);\n\n    // наполнение ленты содержимым\n\n    return new Atom10FeedFormatter(feed);\n&#125;</code></pre>\n<p>Поскольку при реализации REST-сервиса мы имеем дело с HTTP, то мы можем оперировать с различными HTTP-сущностями, например HTTP-заголовками или GET-параметрами. Сделать это можно, используя объект <code>WebOperationContext</code>, который также входит в состав HTTP API. Таким образом, мы можем на основе значения GET-параметра выбирать нужный форматировщик:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public SyndicationFeedFormatter AuthorsFeed()\n&#123;\n    SyndicationFeed feed = new SyndicationFeed(\"Feed Title\", \"A WCF Syndication Feed\", null);\n\n    // ...\n\n    string query = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters[\"format\"];\n\n    if (query == \"atom\")\n    &#123;\n        return new Atom10FeedFormatter(feed);\n    &#125;\n    else\n    &#123;\n        return new Rss20FeedFormatter(feed);\n    &#125;\n&#125;</code></pre>\n<p>Точно также можно работать, например с HTTP-заголовками.</p>\n<p>Однако, в рамках нашего REST-сервиса мы можем возвращать не только ленту, но и, как я уже упоминал, любые другие данные. Например изображение:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public Stream AuthorPhoto(string author)\n&#123;\n    MemoryStream result = new MemoryStream();\n\n    // заполнение потока\n\n    WebOperationContext.Current.OutgoingResponse.ContentType = \"image/jpeg\";\n\n    return result;\n&#125;</code></pre>\n<p>Обратите внимание, в данном случае кроме того, что мы возвратили поток нужно установить значение HTTP-заголовка Content-Type. Это можно осуществить используя объект <code>WebOperationContext</code>. Подобным образом можно вернуть, например, некий html-код или что-то еще.</p>\n<p>Мы посмотрели на то, каким образом мы можем предоставить доступ к данным в рамках нашего REST-сервиса. Однако, REST-подход предполагает не только получение данных, но и их модификацию. Для этого используются HTTP-методы POST, PUT и DELETE. Для того, чтобы реализовать это в рамках сервиса, необходимо пометить нашу операцию в рамках публичного контракта атрибутом <code>WebInvoke</code>, вместо <code>WebGet</code>. При этом потребуется указать какой метод используется (свойство <code>Method</code>) и шаблон URI, на который отображается наша операция. В остальном реализация метода зависит от семантики операции. Например, мы можем что-то изменить в нашем источнике.</p>\n<p>Не смотря на большое количество текста, получившегося при написании поста, реализация REST-сервиса на базе WCF вовсе не сложное занятие. Заканчивая, хочется подвести небольшой итог. Если у нас есть некая объектая модель данных (будь то концептуальная модель Entity Framework или модель LINQ to SQL, либо что-то еще), то несомненно реализовать REST-сервис будет проще на базе ADO.NET Data Services. WCF Syndication имеет смысл использовать в тех сценариях, где данные не очень удобно представлять в виде объектных структур. Например, если мы хотим предоставить доступ к каким-то файлам в рамках файловой системы, удобнее будет построить REST-сервис на базе WCF. Т.о. используя WCF мы можем реализовывать более глубокие сценарии, нежели просто предоставление доступа к модели данных через Web.</p>",
            "tags": [
                "Windows Communication Foundation"
            ],
            "banner_image": "https://blog.zwezdin.com/2008/wcf-rest/acc42669/wcf_rest@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2008/wpf-bindings/",
            "url": "https://blog.zwezdin.com/2008/wpf-bindings/",
            "title": "Bindings в WPF",
            "date_published": "2008-09-11T12:00:00.000Z",
            "content_html": "<p>Разработчики, которые уже успели поработать с технологией WPF очень хорошо знакомы с таким механизмом как <strong>Bindings</strong>. Обычно этот механизм ассоциируют с привзякой одного свойства элемента управления к другому или с привязкой к каким-то коллекциям данных. Однако, этими сценариями Bindings не ограничиваются. Давайте посмотрим в каких еще сценариях мы можем применять привязки.</p>\n<h2>Привязка к свойству внешнего объекта</h2><p>Предположим, в рамках нашего приложения существует некий слой, который является источником данных для нашего пользовательского интерфейса. Этот слой может быть реализован различным образом - начиная от простого статического класса, заканчивая неким сервисом с публичным контрактом и сложной логикой инстанциации. В любом из этих сценариев мы можем декларативно осуществить привязку к этому объекту.</p>\n<p>Возьмем для примера класс <code>Settings</code>, который генерирует Visual Studio при задании каких-либо конфигурационных параметров. Получить экземпляр этого класса с заполненными данными можно получить через публичное статическое свойство <code>Default</code>:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase\n&#123;\n  public static Settings Default\n  &#123;\n    get\n    &#123;\n        return defaultInstance;\n    &#125;\n  &#125;</code></pre>\n<p>Далее в этом классе определяются публичные поля, соответствующие настройкам нашего приложения. В демонстрационном примере я создам одну настройку <code>Param1</code>. Таким образом, в составе данного класса появится нужное свойство:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public string Param1\n&#123;\n  get\n  &#123;\n    return ((string)(this[\"Param1\"]));\n  &#125;\n  set\n  &#123;\n    this[\"Param1\"] = value;\n  &#125;\n&#125;</code></pre>\n<p>После этого, мы можем, например, императивно обратится к этому свойству и присвоить его значение какому-то элементу управления:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">Text1.Text = Settings.Default.Param1;</code></pre>\n<p>Однако, эту же функциональность можно задать декларативно используя XAML:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;TextBox Name=\"Text1\" Text=\"&#123;Binding Source=&#123;x:Static config:Settings.Default&#125;, Path=Param1&#125;\"/></code></pre>\n<p>Как мы видим, здесь в качестве источника указывается статическое свойство класса, а в параметре <code>Path</code> указывается имя свойства уже инстанциированного класса. Кроме отображений настроек класса <code>Settings</code> очень удобно пользоваться Bindings для изменения этих настроек, для этого используется режим двунаправленного Binding:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;TextBox Name=\"Text1\" Text=\"&#123;Binding Source=&#123;x:Static config:Settings.Default&#125;, Path=Param1, Mode=TwoWay&#125;\"/></code></pre>\n<p>Как вы понимаете, подобным образом мы можем обращаться не только к <code>Settings</code>, но и к любым другим классам, например <code>DateTime</code>:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;TextBox Name=\"Text2\" Text=\"&#123;Binding Source=&#123;x:Static System:DateTime.Now&#125;, Path=DayOfYear, Mode=OneWay&#125;\"/></code></pre>\n<p>Здесь как мы видим используется режим однонаправленного связывания (<code>Mode=OneWay</code>), т.к. свойство <code>DayOfYear</code> класса <code>DateTime</code> предназанчено только для чтения.</p>\n<p>Подобным образом можно привязывать не только единичные значения свойств, но и работать с коллекциями. Например, определим класс, который возвращает некую коллекцию:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class DataSource1\n&#123;\n  public static DataSource1 Default\n  &#123;\n    get &#123; return new DataSource1(); &#125;\n  &#125;\n\n  public IEnumerable List1\n  &#123;\n    get &#123; return GetType().GetMethods(); &#125;\n  &#125;\n&#125;</code></pre>\n<p>Тогда привязка к элементу <code>ListBox</code> будет выглядеть следующим образом:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;ListBox Name=\"List1\" ItemsSource=\"&#123;Binding Source=&#123;x:Static WpfApplication38:DataSource1.Default&#125;, Path=List1, Mode=OneWay&#125;\" /></code></pre>\n<p>Другой сценарий — класс, поставляющий нам данные не имеет статического свойства, поставляющего нам инстанс класса. В этом случае мы можем объявить этот класс в ресурсах, при этом при созданни экземпляра окна будет создан один инстанс такого класса, который будет обслуживать все последующие запросы:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Window.Resources>\n  &lt;WpfApplication38:DataSource1 x:Key=\"Source1\"/>\n&lt;/Window.Resources>\n...\n&lt;ListBox Name=\"List2\" ItemsSource=\"&#123;Binding Source=&#123;StaticResource Source1&#125;, Path=List1, Mode=OneWay&#125;\" /></code></pre>\n<p>Кроме того, класс, к данным которого мы хотим привязать значение элемента управления вообще может быть статическим:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class DataSource2\n&#123;\n  public static string Data1 &#123; get; set; &#125;\n  public static string Data2 &#123; get; set; &#125;\n&#125;</code></pre>\n<p>В таком случае мы также можем определить привязку декларативно:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;TextBox Name=\"Text3\" Text=\"&#123;Binding Source=&#123;x:Static WpfApplication38:DataSource2.Data1&#125;, Mode=OneWay&#125;\"/>\n&lt;TextBox Name=\"Text4\" Text=\"&#123;Binding Source=&#123;x:Static WpfApplication38:DataSource2.Data2&#125;, Mode=OneWay&#125;\"/></code></pre>\n<h2>Привязка к методу внешнего объекта</h2><p>Хорошо, со свойствами разобрались. Однако, очень часто бывает так, что данные нам передаются не через свойства, а посредством вызова методов. В этом случае все сложнее, не не на много :).</p>\n<p>Для того, чтобы определить связь с данными, возвращаемыми методом существует объект <code>ObjectDataProvider</code>. Он позволяет указать класс, в котором содержится метод, инстанциировать его если нужно и вызвать метод. Например у нас существует класс, в котором определен метод, возвращающий данные:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class DataSource1\n&#123;\n  //...\n\n  public string GetData1()\n  &#123;\n    return Guid.NewGuid().ToString();\n  &#125;\n&#125;</code></pre>\n<p>Для того, чтобы получить возможность определения Binding на основе такого класса в XAML-коде необходимо определить <code>ObjectDataProvider</code> и указать в нем имя класса и имя вызываемого метода:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Window.Resources>\n  &lt;ObjectDataProvider x:Key=\"Source2\"\n                      ObjectType=\"&#123;x:Type WpfApplication38:DataSource1&#125;\"\n                      MethodName=\"GetData1\">\n\n  &lt;/ObjectDataProvider>\n&lt;/Window.Resources></code></pre>\n<p>После этого, можно ссылаться на <code>Source2</code> в качестве источника:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;TextBox Name=\"Text5\" Text=\"&#123;Binding Source=&#123;StaticResource Source2&#125;, Mode=OneWay&#125;\"/></code></pre>\n<p>Точно также в качестве результата работы метода можно вернуть не просто какой-то простой тип, но и объект или коллекцию:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class DataSource1\n&#123;\n  // ...\n\n  public object GetData2()\n  &#123;\n    return new\n    &#123;\n      Value1 = Guid.NewGuid().ToString(),\n      Value2 = DateTime.Now.ToString()\n    &#125;;\n  &#125;\n\n  public IEnumerable GetData3()\n  &#123;\n    return GetType().GetMethods();\n  &#125;\n&#125;</code></pre>\n<p>И создать привязку к ним:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Window.Resources>\n    &lt;ObjectDataProvider x:Key=\"Source3\"\n                        ObjectType=\"&#123;x:Type WpfApplication38:DataSource1&#125;\"\n                        MethodName=\"GetData2\"/>\n\n    &lt;ObjectDataProvider x:Key=\"Source4\"\n                        ObjectType=\"&#123;x:Type WpfApplication38:DataSource1&#125;\"\n                        MethodName=\"GetData3\"/>\n&lt;/Window.Resources>\n\n...\n\n&lt;TextBox Name=\"Text6\" Text=\"&#123;Binding Source=&#123;StaticResource Source3&#125;, Path=Value1, Mode=OneWay&#125;\"/>\n&lt;ListBox Name=\"List3\" ItemsSource=\"&#123;Binding Source=&#123;StaticResource Source4&#125;, Mode=OneWay&#125;\" /></code></pre>\n<p>Ну и наконец мы можем передавать значения параметров методов, если метод содержит параметры. Для этого мы можем использовать коллекцию <code>MethodParameters</code> объекта <code>ObjectDataProvider</code>:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;Window.Resources>\n  ...\n  &lt;ObjectDataProvider x:Key=\"Source5\"\n                      ObjectType=\"&#123;x:Type WpfApplication38:DataSource1&#125;\"\n                      MethodName=\"GetData4\">\n    &lt;ObjectDataProvider.MethodParameters>\n      &lt;System:String>qwerty&lt;/System:String>\n    &lt;/ObjectDataProvider.MethodParameters>\n  &lt;/ObjectDataProvider>\n&lt;/Window.Resources></code></pre>\n<p>Понятно, что аналогичным образом мы можем добавить несколько параметров в вызов метода.</p>\n<p><strong>UPDATE</strong> (спасибо <a href=\"http://twitter.com/vitus74\">Виталию Дильмухаметову</a>):<br>Если в качестве параметра нужно передать тип (<code>typeof(...)</code>), это можно сделать так:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;ObjectDataProvider x:Key=\"GetMembers\"\n                    ObjectType=\"&#123;x:Type WpfApplication34:EnumService&#125;\"\n                    MethodName=\"GetResult\">\n  &lt;ObjectDataProvider.MethodParameters>\n      &lt;x:Type TypeName=\"WpfApplication34:MyEnum\"/>\n  &lt;/ObjectDataProvider.MethodParameters>\n&lt;/ObjectDataProvider></code></pre>\n<p>При этом вызываемый метод может выглядеть следующим образом:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class EnumService\n&#123;\n  public static IEnumerable&lt;string> GetResult(Type type)\n  &#123;\n    return from f in type.GetFields(BindingFlags.Public | BindingFlags.Static) select f.Name;\n  &#125;\n&#125;</code></pre>\n<p>Тогда в качестве значения параметра будет передано <code>typeof(WpfApplication34.MyEnum)</code>.</p>\n<p>Таким образом, мы видим, что имеем возможность получить доступ к данным, которые представлены через вызов методов класса.</p>\n<p>Такие вот преимущества мы получаем используя подход декларативного связывания данных, который появился в WPF. В противовес этому, при использовании WinForms нам предлагается написать много императивного кода, особенно когда нужно реализовать сценарий двунаправленного связывания. Понятно как это скажется на читаемости кода и поддержке/развитии приложения. :)</p>",
            "tags": [
                "WPF",
                "XAML"
            ],
            "banner_image": "https://blog.zwezdin.com/2008/wpf-bindings/c54b49c0/wpf_bindings@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2008/adonet-data-services-introduction/",
            "url": "https://blog.zwezdin.com/2008/adonet-data-services-introduction/",
            "title": "Знакомство с ADO.NET Data Services",
            "date_published": "2008-08-18T12:00:00.000Z",
            "content_html": "<p>Не так давно я <a href=\"/2008/soap-vs-rest\">рассказывал</a> о двух <del>взаимозаменяющих</del> взаимодополняющих подходах к построению сервисов — SOAP и REST. На этот раз мы поговорим о технологии, которая позволит нам с легкостью строить REST-сервисы — <strong>ADO.NET Data Services</strong>.</p>\n<p>Итак, вспомним еще раз о том, что сервис в стиле REST позволяет нам работать с данными. Это означает, что алгоритм построения сервис-ориентированной системы на базе REST будет примено следующим:</p>\n<ol>\n<li>Получить доступ к источнику данных (в виде объектов и объектных коллекций);</li>\n<li>Опубликовать наши коллекции в веб (буквально легкими движениями руки);</li>\n<li>Настроить права доступа и логику предоставления доступа к данным;</li>\n<li>Получить прокси-класс для работы на клиенте.</li>\n</ol>\n<p>Теперь можно перейти к делу.</p>\n<p>В своем примере я использую <strong>VS2008SP1 + SQL Server 2008</strong>. В качестве источника данных возьмем <strong>Entity Data Model</strong> (ADO.NET Entity Framework).</p>\n<p>Для того, чтобы иметь возможность работать с ADO.NET Data Services необходимо установить <a href=\"http://www.microsoft.com/downloads/details.aspx?FamilyID=a9c6bc06-b894-4b11-8300-35bd2f8fc908\">ASP.NET 3.5 Extensions</a>, либо VS2008SP1 + .NET3.5SP1.</p>\n<p>Первым делом создадим веб-приложение ASP.NET. Добавим в наше приложение Entity Data Model. В качестве источника пусть будет база данных <code>Northwind</code>.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2008/adonet-data-services-introduction/f7b10ae0/1@2x.png\"  alt=\"Знакомство с ADO.NET Data Services\"/>\n\n\n\n\n<p>При создании модели скажем мастеру использовать все таблицы БД, получится симпотичная модель базы <code>Northwind</code>.</p>\n<p>После того как источник данных готов к работе добавим в наш проект сервис. Для этого еще раз обратимся к пункту меню <code>Add new item</code> и из предложенных шаблонов выберем пункт <code>ADO.NET Data Service</code>. В итоге среда разработки создаст нам заготовку класса-сервиса. Этот класс - наследник от <code>WebDataService&lt;..&gt;</code>, который в качестве generic-параметра использует тип источника данных:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class WebDataService1 :\n  WebDataService&lt; /* TODO: put your data source class name here */ >\n  &#123;\n    ...</code></pre>\n<p>Для того, чтобы наш сервис заработал, укажем в качестве generic-параметра контекст нашего источника данных (<code>DataClasses1Context</code>):</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public class WebDataService1 :\n  WebDataService&lt;DataClasses1Context>\n  &#123;</code></pre>\n<p>Следующим шагом необходимо определить видимость контейнеров для клиентов. Дело в том, что ADO.NET Data Services позволяет на уровне контейнера разграничить права доступа - одни контейнеры мы, например, можем только читать (read only), по другим - выполнять запросы и читать, а третьи - изменять.</p>\n<p>В данный момент разрешим все для всех контейнеров. Для этого изменим статический метод <code>InitializeService(..)</code> и добавим к нему следующий код:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static void InitializeService(IWebDataServiceConfiguration config)\n&#123;\n  config.SetResourceContainerAccessRule(\"*\", ResourceContainerRights.All);\n&#125;</code></pre>\n<p>Кроме того, что мы можем ограничить доступ к данным на уровне контейнеров, можно управлять правами на уровне операций, а также создавать свои перехватчики (<code>interceptors</code>). Но об этом в следующий раз.</p>\n<p>Собственно на этом создание сервиса завершено. Теперь мы можем обратиться к нему из браузера и посмотреть что из этого получилось. В результате мы увидим наши коллекции в формате <code>AtomPub</code>.</p>\n<p>Но нет толку от сервиса, когда нету клиента. Давайте реализуем клиента. Задача реализации клиента сводится к построению прокси-класса к нашему сервису. Для этого служит утилита <code>WebDataGen</code>. Для того, чтобы она сгенерировала нам прокси-класс запустим наш сервис и запустим webdatagen:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">\"C:\\Program Files (x86)\\Microsoft ASP.NET 3.5 Extensions\\Webdatagen.exe\" /uri:http://localhost:51126/NorthwindService.svc /language:CSharp /outobjectlayer:./NorthwindService.cs /mode:ClientClassGeneration</code></pre>\n<p>В результате работы <code>WebDataGen</code> получим файл <code>NorthwindService.cs</code>, который и содержит прокси-класс. Далее мы можем использовать этот класс где угодно — от консольных до WPF-приложений. При этом нам останется только указать адрес, по которому расположен сервис:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">NorthwindDataClasses context =\n  new NorthwindDataClasses(@\"http://localhost:51126/NorthwindService.svc\");\n\nvar q = from c in context.Customers\n        select c;</code></pre>\n<p>Теперь наша база данных Northwind доступна через REST-сервис.</p>",
            "tags": [
                "Windows Communication Foundation"
            ],
            "banner_image": "https://blog.zwezdin.com/2008/adonet-data-services-introduction/d29eb381/adonet_data-services-introduction@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2008/soap-vs-rest/",
            "url": "https://blog.zwezdin.com/2008/soap-vs-rest/",
            "title": "REST vs. SOAP/WS-*",
            "date_published": "2008-08-14T12:00:00.000Z",
            "content_html": "<p>На сегодняшний день одним из важнейших направлений в области разработки ПО является направление (веб)-сервисов. Сервисы позволяют строить большие распределенные системы. При этом подходов к построению сервисов сегодня как минимум два - SOAP и REST.</p>\n<p>Оба подхода решают схожие задачи - задачи построения распределенных систем. Но делают это по-разному.</p>\n<h2>SOAP</h2><p>В случае с SOAP/WS-* фактически мы имеем дело с выполнением операций на удаленном хосте. Обмениваясь SOAP-сообщениями, клиент фактически “просит” сервера выполнить ту или иную операцию, передавая ему при этом параметры (если это необходимо). В ответ на это, сервер отправляет клиенту SOAP-сообщение, в котором содержится - либо отметка об успешном выполнении операции и результат, либо сообщение об ошибке.</p>\n<p>Кроме того, передавать SOAP-сообщения можно потенциально через любой транспорт (WCF, например, умеет это делать поверх http, tcp, named pipes, msmq, …).</p>\n<p>В итоге, SOAP-ориентированный подход фактически подталкивает нас к ориентации на операции.</p>\n<p>Стоит отметить, что сегодня подавляющее большинство программных продуктов, ориентированных на сервисы используют SOAP в качестве основы.</p>\n<h2>REST</h2><p>Подход в стиле REST отличается от SOAP тем, что такой сервис ориентирует нас на работу с данными. Предоставив REST-сервис, мы фактически публикуем данные (коллекции) в Веб.</p>\n<p>REST обладает следующими отличительными чертами:</p>\n<ol>\n<li>REST-сервисы строятся исключительно поверх HTTP/HTTPS. Это объясняется тем, что REST хорошо использует все тонкие стороны протокола HTTP - HTTP-методы используются для манипулирования информацией (<code>GET</code>, <code>POST</code>, <code>PUT</code>, <code>DELETE</code>), а данные передаются в теле HTTP-сообщений.</li>\n<li>REST использует свои правила для формирования URI. Каждый ресурс (коллекция, сущность, поле сущности) внутри сервиса может быть адресован при помощи URI.</li>\n<li>Данные могут быть представлены в различном виде (<code>AtomPub</code>, <code>JSON</code>, ..)</li>\n</ol>\n<p>Кроме того, отмечу что REST-сервисы не выходят за рамки HTTP, а это означает, что мы можем использовать все наработанные алогритмы в области веб-приложений - кэширование, сжатие трафика, аутентификация и т.д.</p>\n<p>И итоге, REST-ориентированный подход подталкивает нас к ориентации на данные.</p>\n<p>Разница между REST и SOAP достаточно очевидная и очень важно понимать значение того и другого подхода и использовать каждый из них по назначению. :) Как показывает моя практика общения с различными разработчиками, такое понимание есть далеко не у всех.</p>\n<p>Давайте посмотрим пару примеров, чтобы понять какой подход выбрать в конкретной ситуации на практике.</p>\n<h2>Несколько примеров</h2><p><strong>Пример 1.</strong> Мы хотим разработать серивс-ориентированное приложение для работы с банковским счетом. В сервисе будут 2 бизнес-операции: получить текущий остаток на счету и списать денежные средства. При этом для выполнения операции списания мы должны обратится к локальной СУБД под управлением Microsoft SQL Server, продублировать эту же операцию на мэйнфрейме и отправить уведомление на дублирующий сервер по протоколу TCP. Этот пример ярко демонстрирует ориентацию на операции: для того чтобы списать сумму со счета необходимо выполнить целую цепочку действий. Понятно, что для такого сервиса мы выберем сервисы на базе SOAP.</p>\n<p><strong>Пример 2.</strong> У нас есть существующая B2C (business to customers) система - веб-приложение, которым пользуются наши любимые клиенты. И тут мы понимаем, что клиентам было бы гораздо удобнее размещать свои каталоги в нашей системе используя настольное приложение, а не веб. Принимается решение создать веб-сервис, через который настольное приложение будет работать с источником данных. Если посмотреть на данный веб-сервис, то мы понимаем, что все что он делает - работает с данными: добавить позицию, редактировать позицию, удалить позицию и получить список всех позиций. Реализация SOAP-сервиса в данном случае избыточна (нам не требуется выполнять бизнес-операции). Для решения задачи идеально подходит решение на базе REST - на сервере мы просто получаем от источника данные, публикуем их в веб и отдаем клиенту URI. Никаких нудных реализаций <code>AddItem(..)</code>, <code>UpdateItem(..)</code> и т.д.</p>\n<h2>Релизация</h2><p>Ну и напоследок о технологиях. Решения на базе SOAP/WS-* позволяют реализовывать множество технологий - ASP.NET Web Services, WSE и конечно WCF.</p>\n<p>Решения на базе REST можно удобно построить на базе ADO.NET Data Services (т.н. Project Astoria). Кроме того, для кого-то наверное окажется новостью, но WCF также умеет строить REST-style сервисы, но об этом позже.</p>",
            "tags": [
                "Windows Communication Foundation"
            ],
            "banner_image": "https://blog.zwezdin.com/2008/soap-vs-rest/ce8e742e/soap_vs-rest@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2008/linq2sql-sqlcompact/",
            "url": "https://blog.zwezdin.com/2008/linq2sql-sqlcompact/",
            "title": "LINQ to SQL и SQL Server Compact Edition",
            "date_published": "2008-07-14T12:00:00.000Z",
            "content_html": "<p>В последнее время на различных мероприятиях для разработчиков мы часто слышим много новых и модных красивых слов. К таковым, например, относятся LINQ и SQL Server Compact Edition.</p>\n<p>Как вы уже знаете, <strong>LINQ</strong> - это типизированный язык запросов к любым источникам данных. Используя LINQ мы строим строго типизированный запрос, который проверяется на этапе компиляции, получаем строго типизированные результаты и т.д.; при этом эти запросы мы строим единообразно будь источником данных у нас SQL Server, либо коллекция объектов.</p>\n<p><strong>SQL Server Compact Edition (SSCE)</strong> — тоже многообещающая технология. Фактически это in-proc мини-engine баз данных, который поддерживает T-SQL, не требует установки и может работать как на desktop-, так и на mobile-приложениях.</p>\n<p>Обе технологии, несомненно интересные и многообещающие. Так, например, все очень хорошо понимают сколько времени можно сэкономить на построении уровня данных, используя LINQ. С другой стороны для небольших приложений использование SSCE позволяет сэкономить много времени при развертывании.</p>\n<p>Конечно, напрашивается решение использовать эти две технологии совместно, но не тут то было :) При попытке добавить в проект <code>&quot;LINQ to SQL Classes&quot;</code> (.dbml), и замапить их на источник данных SSCE получаем ненавязчивое сообщение.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2008/linq2sql-sqlcompact/835f2942/error@2x.png\"  alt=\"LINQ to SQL и SQL Server Compact Edition\"/>\n\n\n\n\n<p>Нда.. не очень, конечно, приятно. Но выход есть - использование утилиты <code>SqlMetal</code>. Эта утилита представляет собой консольное приложение, которое по существующей схеме данных позволяет сгенерировать dbml-файл. Собственно, утилита работать может как с обычным SQL Server, так и с SSCE, что нам и нужно. В случае с SSCE использовать ее очень просто:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">SqlMetal /dbml:northwind.dbml northwind.sdf</code></pre>\n<p>Т.е. мы просто указываем пути к файлу данных SSCE (.sdf) и путь к файлу с результатами. На выходе получаем .dbml-файл, который теперь можем запросто добавить в проект и работать с SSCE уже через LINQ to SQL. В конструкторе context-класса нас попросят указать connection string следующего вида: <code>&quot;Data Source=.\\\\Northwind.sdf&quot;</code></p>\n<p>Однако, на этом наши проблемы не закончатся. При первом запуске может возникнуть ошибка:</p>\n<pre class=code language-clike><code class=\"code__container language-clike\">Cannot open \"Northwind.sdf\". Provider 'System.Data.SqlServerCe.3.5' not installed.1</code></pre>\n<p>Все дело в том, что в корневом конфигурационном файле не прописан провайдер для SSCE, поэтому в config-файле своего приложения сделаем это:</p>\n<pre class=code language-xml><code class=\"code__container language-xml\">&lt;configuration>\n  &lt;system.data>\n    &lt;DbProviderFactories>\n      &lt;remove invariant=\"System.Data.SqlServerCe.3.5\" />\n      &lt;add name=\"Microsoft SQL Server Compact Data Provider\"\n           invariant=\"System.Data.SqlServerCe.3.5\"\n           description=\".NET Framework Data Provider for Microsoft SQL Server Compact\"\n           type=\"System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe, Version=3.5.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91\" />\n    &lt;/DbProviderFactories>\n  &lt;/system.data>\n&lt;/configuration></code></pre>\n<p>Собственно, эти данные прописываются в <code>machine.config</code> автоматически, если мы устанавливаем SSCE Windows Installer-ом (а не через x-copy).</p>\n<p>Если у вас Windows 32-bit, то на этом шаге у вас все должно заработать. Однако мне повезло меньше - у меня Windows 64-bit, из-за чего проблемы продолжились. Насколько мне удалось найти информацию на эту тему я сделал вывод - на данный момент SSCE работает только в 32-разрядном режиме. Поэтому проблему можно решить определив, что проект компилируется для x86 (в Configuration Manager).</p>\n<p>Другой, более изощренный способ - использовать утилиту <code>&quot;corflags.exe&quot;</code>, которая позволяет включить 32-битный режим уже для готовой сборки:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">corflags ...\\bin\\Debug\\consoleapplication1.exe /32BIT+</code></pre>\n<p>Вывод. Использовать LINQ2SQL и SSCE можно, но не без проблем :)</p>\n<p><strong>Updated:</strong> поддержка x64 будет в SSCE 3.5 SP1.</p>",
            "tags": [
                "LINQ"
            ],
            "banner_image": "https://blog.zwezdin.com/2008/linq2sql-sqlcompact/33f7646b/linq2sql_sqlcompact@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2008/zoomit/",
            "url": "https://blog.zwezdin.com/2008/zoomit/",
            "title": "ZoomIt для ваших презентаций",
            "date_published": "2008-03-31T12:00:00.000Z",
            "content_html": "<p>На последних Днях Разработчиков у некоторых докладчиков заметил интересную утилиту. Она позволяет во время доклада увеличивать участки экрана (zoom). Это может быть удобно, например, когда вы показываете участки кода во время демонстраций и т.д. Кроме того, она умеет рисовать на экране и включать таймер обратного отсчета.</p>\n<p>Итак, встречайте - <strong>ZoomIt</strong> by Марк Руссинович (sysinternals). Каково было мое удивление, когда я узнал, что кроме того, что она бесплатная, она еще и весит всего 44 Кб в архиве.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2008/zoomit/0dc27ab5/1@2x.png\"  alt=\"ZoomIt для ваших презентаций\"/>\n\n\n\n\n<p>Скачивайте и делайте ваши презентации ярче!</p>",
            "tags": [
                "Tools"
            ],
            "banner_image": "https://blog.zwezdin.com/2008/zoomit/f5cde781/zoomit@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2008/extensions-methods/",
            "url": "https://blog.zwezdin.com/2008/extensions-methods/",
            "title": "Использование Extensions Methods для создания экземпляра класса",
            "date_published": "2008-03-12T13:00:00.000Z",
            "content_html": "<p>С момента появления C#3.0 нашлось достаточно много интересных применений extensions method’ам. Различных вариаций на эту тему достаточно большое множество, и в этом посте мне бы хотелось дополнить эту копилку еще одним таким примером.</p>\n<p>Дело в том, что в силу специфики своего проекта мне достаточно часто приходится инстанциировать объекты основываясь на строковом имени класса (знаем строку, в которой задано имя класса =&gt; получаем экземпляр этого класса). Конечно, каждый из вас знает что делается это, например, при помощи объекта <code>Activator</code>. Например так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">object o = Activator.CreateInstance(Type.GetType(@\"WindowsFormsApplication1.Class1\", false));</code></pre>\n<p>Однако, если поиграться с extensions method’ами, то можно сделать следующее.</p>\n<p>Определим extension method:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public static class InstanceExtension\n&#123;\n  public static object CreateInstance(this string type)\n  &#123;\n    return Activator.CreateInstance(Type.GetType(type, false));\n  &#125;\n&#125;</code></pre>\n<p>Теперь воспользуемся им:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">object o = \"WindowsFormsApplication1.Class1\".CreateInstance();</code></pre>\n<p>Помоему выглядит достаточно интересно. Если пример немного усложнить и добавить какую-либо логику в реализацию extension method (например, инжектирование через DI в новоиспеченный инстанс какого-либо объекта, если он реализует заданный контракт, итд), то толк из этого может вполне получится.</p>",
            "tags": [
                "C#",
                ".NET",
                "LINQ"
            ],
            "banner_image": "https://blog.zwezdin.com/2008/extensions-methods/4c26a765/extensions_methods@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2007/wcf-cache/",
            "url": "https://blog.zwezdin.com/2007/wcf-cache/",
            "title": "Кэширование в Windows Communication Foundation (WCF)",
            "date_published": "2007-07-18T12:00:00.000Z",
            "content_html": "<p>Те, кто уже начал промышленное использование <strong>Windows Communcation Foundation</strong> в своих разработках уже наверняка столкнулись с множеством проблем различного характера.</p>\n<p>Одной из таких проблем является кэширование результатов выполнения операций в рамках сервиса.</p>\n\n\n<p>Как оказалось решение данной проблемы не такая уж и сложная задача. Чтобы осуществить это необходимо создать и подменить стандартный Invoker.<br>Для реализации своего <code>Invoker</code>-а необходимо создать новый класс и реализовать в нем интерфейс <code>IOperationInvoker</code>. При этом необходимо сохранить ссылку на <em>“оригинальный”</em> <code>Invoker</code>. В новоиспеченном Invoker-е нас интересует следующий метод:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public object Invoke(object instance, object[] inputs, out object[] outputs)</code></pre>\n<p>Переопределив его, мы можем контролировать процесс выполнения операции на сервисе. Самое простое что можно сделать - это вызвать метод <code>Invoke</code> “оригинального” <code>Invoker</code>-а. Но кто нам мешает сделать еще несколько манипуляций перед этим? Именно тут мы и реализуем логику кэширования. Для самого простого случая это выглядить примерно так:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public object Invoke(object instance, object[] inputs,\n  out object[] outputs)\n&#123;\n  string result = String.Empty;\n  int invokeInputParam = (int)inputs[0];\n\n  if (_cacheDictionary.ContainsKey(invokeInputParam) == true)\n  &#123;\n    Console.WriteLine(@\"Cached for param value: \" + invokeInputParam.ToString());\n    result = _cacheDictionary[invokeInputParam];\n  &#125;\n  else\n  &#123;\n    result = (string)_baseInvoker.Invoke(instance, inputs, out outputs);\n    _cacheDictionary.Add(invokeInputParam, result);\n  &#125;\n\n  outputs = new object[] &#123; &#125;;\n  return result;\n&#125;</code></pre>\n<p>После реализации <code>Invoker</code>-а необходимо каким-то образом “прикрутить” его к операции. Вариантов тут много. Один из них — создать класс-атрибут, в котором реализован интерфейс <code>IOperationBehavior</code>. Здесь, в методе <code>ApplyDispatchBehavior</code> как раз необходимо выполнить подмену <code>Invoker</code>-а:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">public void ApplyDispatchBehavior(OperationDescription operationDescription,\n  System.ServiceModel.Dispatcher.DispatchOperation dispatchOperation)\n&#123;\n  dispatchOperation.Invoker = new CacheInvoker(dispatchOperation.Invoker);\n&#125;</code></pre>\n<p>Осталось только навесить данный атрибут на операцию при реализации сервиса и все готово:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">[Cache]\npublic string LongOperation(int a)\n&#123;\n  System.Threading.Thread.Sleep(2000);\n  return String.Format(\"Result &#123;0&#125; / DateTime: &#123;1&#125;\", a, DateTime.Now);\n&#125;</code></pre>\n<p>Аналогичным образом можно организовать не только кэширование, но и различные другие операции. Например, формировать список выполненных операций и отправлять их на e-mail.</p>",
            "tags": [
                "Windows Communication Foundation"
            ],
            "banner_image": "https://blog.zwezdin.com/2007/wcf-cache/55f78d0a/wcf_cache@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2007/avoid-security-dll-names/",
            "url": "https://blog.zwezdin.com/2007/avoid-security-dll-names/",
            "title": "Не называйте свои сборки \"security.dll\"",
            "date_published": "2007-04-20T12:00:00.000Z",
            "content_html": "<p>Недавно, работая над текущим проектом, потратил кучу времени, разбираясь с загадочным исключением - “<em>Не удается найти точку входа EnumerateSecurityPackagesW в DLL (security.dll)</em>“.</p>\n<p><em>Предыстория.</em> Приложение на базе WCF. На сервисной части запускается набор сервисов. Затем на клиентской части вызываются методы удаленного объекта. Все работало замечательно. До тех пор, пока я не начал писать Security часть всего этого.</p>\n<p>В какой-то момент времени все просто перестало работать — на клиенте выбрасывалось указанное исключение. Все мои попытки что-либо сделать ни к чему не приводили. После того как я просидел с этой проблемой некоторое время выяснилось следующее.</p>\n<p>После того, как я из главного проекта добавил Reference на сборку Security, у меня, естественно, в папке с исполняемым файлом появился файлик <code>&quot;security.dll&quot;</code>. WCF работал поверх HTTP, используя защищенное соединение (т.е. https). При работе через http инфраструктура WCF, видимо, использует объект <code>HttpWebRequest</code>. Как позже выяснилось, при работе через защищенные соединения, <code>HttpWebRequest</code> импортирует библиотеку <code>&quot;C:\\Windows\\System32\\security.dll&quot;</code>, которая является частью ОС. Причем, при импорте данной библиотеки он смотрит сначала в папку, в которой находится исполняемый файл моей программы, а только потом - в <code>System32</code>. Соответственно, когда я сделал Reference на свою сборку <code>&quot;Security&quot;</code>, она скопировалась в папку с программой и <code>HttpWebRequest</code> пытался загрузить ее. Теперь понятно откуда исключение :)</p>\n<p>Для меня такое решение (поиск библиотеки в папке с программой) достаточно сомнителен в данной ситуации. А что если бы я при разработке использовал <code>NetTcpBinding</code> или <code>HTTP</code>, но не защищенный? А потом у Заказчика развернул бы на базе HTTPS? Поэтому, рекомендую не использовать в качестве имени сборки название <code>&quot;security.dll&quot;</code>.</p>\n<p>Ну и напоследок, продемонстрируем как это “работает”. :) Создаем приложение и добавляем следующий код:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(\"https://mcp.microsoft.com/mcp/\");\nWebResponse response = webRequest.GetResponse();\nStream responseStream = response.GetResponseStream();\nStreamReader r = new StreamReader(responseStream);\nMessageBox.Show(r.ReadToEnd());\nresponseStream.Close();</code></pre>\n<p>Запускаем, видим HTML-код с сайта Microsoft. Заходим в папку с программой и создаем пустой файл с названием <code>&quot;security.dll&quot;</code>.</p>\n<p>Запускаем указанный выше код еще раз. “Наслаждаемся”. :)</p>\n<p>Ну и повторюсь еще раз, что если работать не через https, то HttpWebRequest не импортирует указанную библиотеку и все работает нормально.</p>",
            "tags": [
                "Tips&Tricks"
            ],
            "banner_image": "https://blog.zwezdin.com/2007/avoid-security-dll-names/e01fbe41/avoid_security-dll-names@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2007/icq-bots/",
            "url": "https://blog.zwezdin.com/2007/icq-bots/",
            "title": "ICQ-боты",
            "date_published": "2007-03-06T13:00:00.000Z",
            "content_html": "<p>До сегодняшнего дня думал что <strong>ICQ-боты</strong> — это зло. Как оказалось - не всегда :-)</p>\n\n\n<p>Итак, сначала поясню что это такое для тех кто не в курсе. Как говорит Wikipedia, <strong>ICQ-бот</strong> - <em>компьютерная программа, работающая по протоколу ICQ и выполняющая различные функции</em>. Принцип работы: бот принимает входящие сообщения, анализирует их и отсылает результат выполнения и/или выполняет нужную команду.</p>\n<p>Сначала меня поразил бот от Gismeteo - пишешь ему в ICQ свой город, а он в ответ - прогноз погоды. Удобно! Особенно если трафик ICQ не учитывается провайдером:)))</p>\n<p>Но на этом я не остановился и решил посмотреть что Google знает о ICQ-ботах. Как выяснилось, этих самых ботов существует досточно большое количество, причем далеко не все являются бесполезными:) Вот некоторые из них:</p>\n<ul>\n<li>Уже упомянутый бот от Gismeteo - <code>474974</code> - отсылаем название города - получаем в ответ прогноз погоды</li>\n<li><code>319550804</code>, <code>335297520</code> — анекдоты</li>\n<li><code>8666668</code> — переводчик</li>\n<li><code>660668</code> — представляет собой средство получения различной информации из различных источников, например: новости, прогнозы погоды, программа телепередач, гороскопы, курсы валют и т.п.</li>\n<li><code>744444</code> — представляет собой ICQ-шлюз, т.е. мы можем говорить с кем-угодно от имени этого номера. (<code>!help</code> - справка по командам)</li>\n<li><code>4444444</code> - просто бот-болталка. Ради интереса, стоит хотя бы пять минут поговорить с ним. Для осознания, так сказать, проблем, стоящих перед искусственным интеллектом.</li>\n<li><code>236056743</code> - бот для внесения новых записей в Живой Журнал.</li>\n</ul>\n<p>Подумалось - как бы было неплохо сделать бот для добавления записей в блоги (dasblog, например), форумы gotdotnet и т.д.</p>\n<p>Вобщем, интересная идея :-)</p>",
            "tags": [
                "Tools"
            ],
            "banner_image": "https://blog.zwezdin.com/2007/icq-bots/196ea94e/icq_bots@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2007/iis-apache/",
            "url": "https://blog.zwezdin.com/2007/iis-apache/",
            "title": "Как подружить Apache и IIS",
            "date_published": "2007-02-13T13:00:00.000Z",
            "content_html": "<p>Довелось как-то решать задачу: в интрасети на одном и том же сервере необходимо установить два веб-сервера - <strong>IIS</strong> и <strong>Apache</strong>; причем чтобы оба отзывались по 80 порту, только по разным DNS-именам.</p>\n\n\n<p>Естественно было решено назначить сетевому интерфейсу 2 IP-адреса, и “<em>раскидать</em>“ оба сервера на разные адреса и соответствующим образом настроить DNS. Однако, не тут то было! :-) IIS напрочь отказывался стартовать в таких условиях. После некоторого изучения проблемы выяснилось следующее.</p>\n<p>Итак, у нас имеется сервер на Windows 2003 Server. Для одного и того же сетевого интерфейса прописано два разных IP.</p>\n<p>Проблема в том, что сервер IIS по умолчанию делает привязку ко всем IP-адресам. Но нам нужно привязаться только к 1 IP. Поможет нам в этом утилита <code>httpcfg.exe</code>. Найти ее можно в архиве <code>Support.cab</code>, который находится в папке <code>Support\\Tools</code> на оригинальном диске Windows 2003.</p>\n<p>Итак, чтобы задать IP-адрес, к которому будет привязываться IIS делаем так:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">httpcfg set iplisten -i xxx.xxx.xxx.xxx</code></pre>\n<p>После этого можно посмотреть результат запроса следующим образом:</p>\n<pre class=code language-powershell><code class=\"code__container language-powershell\">httpcfg query iplisten</code></pre>\n<p>Теперь осталось привязать Apache ко второму IP и перезапустить IIS.</p>\n<p>Готово. Проверялось на IIS6.</p>",
            "tags": [
                "ASP.NET",
                "IIS"
            ],
            "banner_image": "https://blog.zwezdin.com/2007/iis-apache/125d92d7/iis_apache@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2007/windows-vista-rospatent/",
            "url": "https://blog.zwezdin.com/2007/windows-vista-rospatent/",
            "title": "Россиянин зарегистрировал Windows Vista в Роспатенте",
            "date_published": "2007-01-25T13:00:00.000Z",
            "content_html": "<p>По утверждению Антона Серго, президента компании «Интернет и право», Федеральная служба по интеллектуальной собственности, патентам и товарным знакам (Роспатент) зарегистрировала на него программу для ЭВМ Windows Vista.</p>\n\n\n\n    <img src=\"https://blog.zwezdin.com/2007/windows-vista-rospatent/b647a7ed/vista@2x.jpg\"  alt=\"Россиянин зарегистрировал Windows Vista в Роспатенте\"/>\n\n\n\n\n<p>«<em>Этой регистрацией я хочу показать особенности законодательства, а не нажиться на перепродаже прав на Windows Vista кому-либо, мне не хочется злить или раздражать Microsoft</em>», — заявил Антон Серго. На вопрос, как такая регистрация могла быть возможной, он ответил, что российское законодательство не препятствует созданию и регистрации объектов с одинаковыми названиями.</p>",
            "tags": [
                "Fun"
            ],
            "banner_image": "https://blog.zwezdin.com/2007/windows-vista-rospatent/9ff8134f/windows_vista-rospatent@cover.jpg"
        },
        {
            "id": "https://blog.zwezdin.com/2007/dead-messagebox/",
            "url": "https://blog.zwezdin.com/2007/dead-messagebox/",
            "title": "Убийственный MessageBox",
            "date_published": "2007-01-13T13:00:00.000Z",
            "content_html": "<p>Случайно обнаружил на просторах интернета обсуждение, в котором объясняется как в несколько строчек убить Windows.</p>\n<p>Итак, для начала код.</p>\n<p>Вариант на C#:</p>\n<pre class=code language-csharp><code class=\"code__container language-csharp\">class MboxTest\n&#123;\n  [DllImport(\"user32.dll\")]\n  unsafe public static extern int MessageBoxA(uint hwnd, byte* lpText, byte* lpCaption, uint uType);\n\n  static unsafe void Main()\n  &#123;\n    byte[] helloBug = new byte[] &#123;0x5C, 0x3F, 0x3F, 0x5C, 0x21, 0x21, 0x21, 0x00&#125;;\n    uint MB_SERVICE_NOTIFICATION = 0x00200000u;\n    fixed(byte* pHelloBug = &amp;helloBug[0])\n    &#123;\n      for(int i=0; i&lt;10; i++)\n        MessageBoxA(0u, pHelloBug, pHelloBug, MB_SERVICE_NOTIFICATION);\n    &#125;\n  &#125;\n&#125;</code></pre>\n<p>Вариант на С++:</p>\n<pre class=code language-cpp><code class=\"code__container language-cpp\">char bug [] = \"\\\\??\\\\C:\\\\\";\nfor(int i = 0; i &lt; 10; i ++)\n&#123;\n  ::MessageBoxA(0, bug, bug, MB_SERVICE_NOTIFICATION);\n&#125;</code></pre>\n<p>Результатом работы данного кода является несколько MessageBox-ов и…. синий экран!</p>\n<p>Самое интересное, что данный баг работает на многих версиях Windows, в т.ч. на Vista.</p>",
            "tags": [
                "Fun"
            ],
            "banner_image": "https://blog.zwezdin.com/2007/dead-messagebox/a69919f0/dead_messagebox@cover.jpg"
        }
    ]
}