Одна из мелких фич, упомянутых в What’s New in the .NET Framework 4.5 Beta в разделе Networking – Support for Email Address Internationalization. Ссылка из What’s New оптимистично ведет в MSDN по System.Net.Mail, где про EAI, естественно, упоминаний уже нет.

Суть фичи на самом деле – новое свойство SmtpClient.DeliveryFormat, позволяющее включить поддержку UTF-8 в заголовках, в соответствии с RFC 5336 RFC 6531.

SmtpClient client = new SmtpClient();
client.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory;

client.PickupDirectoryLocation = @"d:tempsmtptest";
Directory.CreateDirectory(client.PickupDirectoryLocation);
client.Send("Василий <test@example.com>", "Павел <test2@example.com>", "тема", "тело");

client.DeliveryFormat = SmtpDeliveryFormat.International;
client.Send("Василий <test@example.com>", "Павел <test2@example.com>", "тема", "тело");

Результат вызова со старым форматом доставки:

X-Sender: =?utf-8?Q?=D0=92=D0=B0=D1=81=D0=B8=D0=BB=D0=B8=D0=B9?=
 <test@example.com>
X-Receiver: =?utf-8?Q?=D0=9F=D0=B0=D0=B2=D0=B5=D0=BB?= <test2@example.com>
MIME-Version: 1.0
From: =?utf-8?Q?=D0=92=D0=B0=D1=81=D0=B8=D0=BB=D0=B8=D0=B9?=
 <test@example.com>
To: =?utf-8?Q?=D0=9F=D0=B0=D0=B2=D0=B5=D0=BB?= <test2@example.com>
Date: 16 Apr 2012 21:32:53 +0300
Subject: =?utf-8?B?0YLQtdC80LA=?=
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: base64

0YLQtdC70L4=

С новым форматом доставки:

X-Sender: "Василий" <test@example.com>
X-Receiver: "Павел" <test2@example.com>
MIME-Version: 1.0
From: "Василий" <test@example.com>
To: "Павел" <test2@example.com>
Date: 16 Apr 2012 21:32:53 +0300
Subject: тема
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: base64

0YLQtdC70L4=

P.S. Пост для себя, на всякий случай, чтобы не вспоминать в следующий раз “что такое DeliveryFormat и EAI?” :)

30. May 2011 · Write a comment · Categories: C#, Misc

Никогда не стоит преуменьшать опасность гугления кода. Вас может увлечь за толпой леммингов. Например, попытавшись найти пример отправки POST-запроса из C# вы получите кучу примеров использования HttpWebRequest. В 95% случаев ручная работа с WebRequest не оправдана ничем, кроме незнания стандартной библиотеки. WebRequest обычно тянет за собой:

  • 10-20 строк кода на каждый запрос.
  • Ручное выставление Cookie Container для каждого запроса.
  • Неочевидную последовательность using-ов (для тех, кто знает о IDisposable).
  • Ссылку на System.Web, и отказ от клиентcкого профиля.
  • В то же время стандарный класс WebClient умеет практически все, ради чего вы пишете код с WebRequest/Response/Stream и прочим шаманством. Он даже умеет энкодить отправляемые значения без HttpUtility.

    Отправка GET-запросов:

    С WebRequest (без using, мне повезет):

    System.Net.WebRequest reqGET = System.Net.WebRequest.Create("http://site.ru/");
    System.Net.WebResponse resp = reqGET.GetResponse();
    System.IO.Stream stream = resp.GetResponseStream();
    System.IO.StreamReader sr = new System.IO.StreamReader(stream);
    string s = sr.ReadToEnd();
    Console.WriteLine(s);

    С WebClient:

    using (var client = new System.Net.WebClient())
    {
        string s = client.DownloadString("http://site.ru/");
        Console.WriteLine(s);
    }

    Отправка POST-запросов:

    С WebRequest (опять без using):

    System.Net.WebRequest reqPOST = System.Net.WebRequest.Create("http://site.ru/send.php");
    reqPOST.Method = "POST";
    reqPOST.ContentType = "application/x-www-form-urlencoded";
    byte[] sentData = Encoding.Unicode.GetBytes("message=" + System.Web.HttpUtility.UrlEncode("some data"));
    reqPOST.ContentLength = sentData.Length;
    System.IO.Stream sendStream = reqPOST.GetRequestStream();
    sendStream.Write(sentData, 0, sentData.Length);
    sendStream.Close();
    System.Net.WebResponse result = reqPOST.GetResponse();

    С WebClient:

    using (var client = new System.Net.WebClient())
    {
        var values = new System.Collections.Specialized.NameValueCollection();
        values.Add("message", "some data");
        client.UploadValues("http://site.ru/send.php", values);
    }

    Сохранение Cookies между запросами:

    WebClient по умолчанию не сохраняет cookies между запросами. Это легко лечится наследованием от него, и переопределением одного метода:

    public class CookieAwareWebClient : WebClient
    {
        private CookieContainer m_container = new CookieContainer();

        protected override WebRequest GetWebRequest(Uri address)
        {
            WebRequest request = base.GetWebRequest(address);
            if (request is HttpWebRequest)
            {
                (request as HttpWebRequest).CookieContainer = m_container;
            }
            return request;
        }
    }

    (подсмотрено у Yuriy Solodkyy)

    Вместо заключения:

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

    Стандартный отчет Scrum Burndown никак не учитывает выходные при построении тренда. В Q and A для шаблона есть решение по учету выходных от Tore Østergaard, но оно требует предварительной сборки.

    Готовый к использованию fix:

    1. Скачать исправленный Report Definition.

    2. Открыть Report Site:

    image

    3. Залить обновленный Report Definition:

    image

    image

    4. Просмотреть отчет. На месте выходных тренд должен принять горизонтальное положение:

    image

    Первая часть, где мы остановились на рабочих выпадающих списках с нерабочей валидацией.

    Для того, чтобы стандартная клиентская валидация заработала, нужно сделать следующее:

    1. Заполнить метаданные. Мы сделали это в прошлой части.
    2. Заполнить поля ViewData.TemplateInfo. В нашем случае – из TemplateInfo родительского действия.
    3. Отрендерить выпадающий список на основе метаданных и данных шаблона.

    Изменения затронут только представление выпадающего списка.

    ViewsListDropDown.cshtml

    @model SelectList
    @{
       
    this
    .ViewContext.ViewData.TemplateInfo.FormattedModelValue
            =
    this
    .ViewContext.ParentActionViewContext.ViewData.TemplateInfo.FormattedModelValue;
           
       
    this
    .ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix
            =
    this
    .ViewContext.ParentActionViewContext.ViewData.TemplateInfo.HtmlFieldPrefix;
    }
    @
    Html.DropDownList(
       
    ""
    ,
       
    this
    .Model,
       
    this.ViewData.ModelMetadata.NullDisplayText
        )

    Важно: ни в коем случае нельзя подменять ссылку ViewData.TemplateInfo на объект из родительского контекста. ASP.NET MVC рендерит атрибуты валидации только для первого элемента управления с заданным именем. В нашем примере это приведет к отсутствию валидаторов у всех списков, кроме первого.

    После этого валидаторы заработают, и даже слишком хорошо:

    image

    В стандартном валидаторе целочисленного формата есть баг – он учитывает только атрибут value у элементов списка, и не учитывает innerText. Т.е. выбранный на картинке выше <option>1992</option> он числом не считает.

    Для обхода этого бага можно создать SelectList с явным указанием имен свойств для Text/Value:

    ControllersListController.cs

    public ActionResult Years()
    {
       
    int? selectedYear = this.ControllerContext.ParentActionViewContext.ViewData.Model as int
    ?;

       
    var model = new SelectList(Enumerable.Range(1990, 20).Select(y => new
    { Value = y }),
           
    "Value", "Value"
    , selectedYear);

       
    this
    .ViewData.Model = model;
       
    this.ViewData.ModelMetadata = this
    .ControllerContext.ParentActionViewContext.ViewData.ModelMetadata;

       
    return View("DropDown");
    }

    Некрасиво, но работает. Надеюсь, баг исправят в релизе :)

    Исходный код к этой статье: MvcDropDowns2.zip. Требует ASP.NET MVC 3.0 RC2 для запуска.

    Наткнулся на забавный глюк – студия стала намертво подвисать при попытке открытия web.config rewrite-файла. После перезапуска файл открывался вполне нормально, но через некоторое время зависон повторяется. Убивание suo/user не помогло.

    Поковырял ее отладчиком, и выяснил что висит она на SqlConnection.Open. Непонятно зачем, но при открытии реврайта студия пытается подсоединиться ко всем серверам, зарегистрированным в Server Explorer/Data Connection. Почистил оттуда мертвые записи – подвисания прошли.