Показаны сообщения с ярлыком C#. Показать все сообщения
Показаны сообщения с ярлыком C#. Показать все сообщения

среда, 30 сентября 2015 г.

Про using или знаете ли вы что...

Наверняка, вы знаете что в C# есть такая конструкция, как using. И, скорее всего, в курсе, что использовать его можно и нужно только с объектами, реализующими интерфейс IDisposable (и, соответственно, скорее всего, что-то делающими с неуправляемыми ресурсами). На всякий случай, напомню, что конструкция вида:

 
using (someType obj = new someType())
{
         //тут что-то происходит
}
разворачивается компилятором вот в такую:

 
someType obj = new someType();
try
{
        //тут что-то происходит
}
finally
{
        //если объект значимого типа (value-type)
        ((IDisposable)obj).Dispose();

        //или, если объект ссылочного типа (reference-type)
        if (obj!=null)
           ((IDisposable)obj).Dispose();
}
а где же блок catch, спросите вы? Почему его нет? И почему не предусмотрен вариант using, где его можно задать?


среда, 25 марта 2015 г.

Как в .Net получить действительно случайные числа

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

        
        static string GetRandomNum(int minValue, int maxValue)
        {
            Random rnd = new Random();
            return rnd.Next(minValue, maxValue).ToString();
        }

        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine(GetRandomNum(1,11));
        }


В данном случае вы получите совершенно одинаковые все "случайные" цифры.

Происходит так потому, что при инициализации объекта Random с использованием конструктора по умолчанию в качестве числа, использующегося для вычисления случайных чисел используется Environment.TickCount (количество миллисекунд, прошедших со времени старта системы), который обновляется раз в 15.6 миллисекунд.


четверг, 12 марта 2015 г.

Используем сессию в обработчиках (HTTP handlers) ASP.NET

Представьте себе гипотетическую ситуацию, когда вам нужно отдавать пользователю файлы, с, допустим, какими-то индивидуальными для каждого пользователя данными, причем именно отдавать уже готовые файлы, а не генерировать на лету. Самым простым и очевидным вариантом было бы создать обработчик, который будет вызываться для запросов вида "*_report.pdf", но для этого нам нужно понимать, можно ли отдавать пользователю файл, который он запрашивает или он принадлежит другому пользователю.
Передавать идентификатор в явном виде не только не правильно идеологически, но и просто не красиво, да и сохраненный файл, полученный по ссылке вида 10102_u323467_report.pdf?sessId=05446DA8F736B29A будет по умолчанию сохранен с таким же "кривым" именем.

Значительно проще положить этот идентификатор в сессию и получать его в обработчике уже оттуда. Для того, чтобы объект Session был доступен, нам нужно, чтобы обработчик наследовал не только от интерфейса IHttpHandler, но од одного из интерфейсов IRequiresSessionState или IReadOnlySessionState в зависимости от того нужен ли нам доступ только на чтение (IReadOnlySessionState) или на чтение и запись (IRequiresSessionState). На самом деле, записать в объект сессии вы сможете что угодно в любом случае, единственное отличие, что при использовании IReadOnlySessionState в конце обработки запроса данные сессии не сохраняются.


воскресенье, 11 января 2015 г.

Делаем локализацию сайта на ASP.NET MVC

Представьте, что вы решили делать сайт с использованием ASP.NET MVC. И, помимо всего прочего, вам на этом сайте вам нужна возможность отображения его на нескольких языках. Задача, хоть и кажущаяся с первого взгляда не самой тривиальной, на самом деле, достаточно проста. И я сейчас расскажу что для этого нужно сделать.


Во-первых, необходимо как-то передавать идентификатор языка. Можно, конечно, придумать какое-нибудь извращение, типа хранения текущего языка в сессии, но лучше всего просто передавать его в URL. Как минимум, в это случае, локализованными ссылками на ваш сайт можно будет без проблем делиться, хоть в соцсеятх, хоть пересылать их по e-mail. 
Для этого в вашем проекте открываем файл RouteConfig.cs, находящийся в папке App_Start и добавляем туда путь для локализованного  контента.
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "Localization",
                url: "{lang}/{controller}/{action}/{id}",
                defaults: new { lang = "ru", controller = "Home", action = "Index", id = UrlParameter.Optional },
                //ограничение необходимо, чтобы отличить параметр языка от параметра контроллера. 
                //В данном случае, в качестве параметра lang подходят все двухсимвольные комбинации из букв латинского алфавита, например "en" или "fr"
                constraints: new { lang = @"[a-z]{2}" }
            );

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }           
            );
        }

Теперь, если в URL содержится идентификатор языка, то он будет приходить нам в коллекции RequestContext.RouteData, и получить его проще всего в методе в методе Initialize контроллера. Давайте для простоты сделаем локализованную страничку авторизации, которая будет отображаться сразу же при входе на сайт, то есть по пути /.


понедельник, 15 декабря 2014 г.

О передаче параметров в C#

Давайте сразу начнем с кода. У нас есть вот такой код, давайте разберемся почему он выводит то, что выводит:

 
    class Program
    {
        static void Main(string[] args)
        {
            int a = 1;
            ProcessValue(a);
            Console.WriteLine(a.ToString());
            //здесь будет выведено 1

            TestClass test = new TestClass();
            test.val = 2;
            ProcessRef(test);
            Console.WriteLine(test.val.ToString());
            //здесь будет выведено 10

            TestClass test = new TestClass();
            test.val = 3;
            Process(test);
            Console.WriteLine(test.val.ToString());
            //а здесь будет выведено 3
        }

        static void ProcessValue(int par)
        {
            par = 3;
        }

        static void ProcessRef(TestClass par)
        {
            par.val = 10;
        }

        static void Process(TestClass par)
        {
            par = new TestClass();
            par.val = 100;
        }
    }
 

    public class TestClass
    {
        public int val;
    }


Вы, наверняка, знаете, что в .Net есть значимые (value) и ссылочные (reference) типы. Первые - размещаются полностью в стеке, а вторые размещаются в управляемой куче, а в стеке размещается только указатель на них.


воскресенье, 7 декабря 2014 г.

Обработка исключений при работе с WCF-сервисами

Наверняка вы сталкивались с ситуацией, когда в случае ошибки в WCF-сервисе нужно передать клиенту какие-либо данные либо, например, подробное описание что случилось. Очень часто встречаются реализации WCF- сервисов, использующие возвращаемые клиенту объекты с дополнительными, используемыми только в случае ошибки, полями, даже если реализация метода не требует возврата какого-либо результата клиенту. Так делают потому, что если просто выбросить исключение, то клиент не получит практически никакой информации о том, что случилось. Представьте, что у нас есть WCF сервис вот с таким простым контрактом и реализацией.


  
    //контракт
    [ServiceContract]
    public interface IDataService
    {        
        [OperationContract]        
        void TestError();
    }

    //реализация
    public void TestError()
    {
       throw new Exception("Error!");
    }


В если реализовать сервис именно так, то на клиент придет исключение, которое не дает никаких сведений о случившемся:

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


суббота, 6 декабря 2014 г.

Несколько мыслей о получении IP адреса клиента в ASP.NET

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

Итак, самый широко известный в мире ASP.NET способ получения IP-адреса клиента - это использование HttpContext.Current.Request.UserHostAddress, который, по сути является оберткой над HTTP заголовком REMOTE_ADDR, в которую записывается адрес хоста от которого серверу пришел запрос. А этот заголовок используется практически во всех языках программирования, для целей определения IP клиента. И везде не утихают споры правильно ли использовать именно его. Почему? Потому, что REMOTE_ADDR содержит адрес компьютера, установившего соединение с сервером. Во времена становления интернет и появления протокола HTTP этик компьютером почти наверняка являлся клиент, так что все было хорошо. Сейчас же, в большинстве случаев, в REMOTE_ADDR будет адрес прокси сервера внутренней сети (корпоративной, или домашней - смысла это не меняет) через который прошел запрос клиента.
И еще один пример, с которым, особенно часто сталкиваются Linux программисты, в результате криво настроенного nginx. ;)
Представьте себе ситуацию, что у вас есть криво настроенный балансировщик нагрузки. В таком случае в REMOTE_ADDR будет 10.10.0.2, что, естественно совершенно не клиентский адрес.



четверг, 27 ноября 2014 г.

Как получить IP-адрес клиента в WCF-сервисе.

Если вам нужно узнать с какого адреса приходит обращение к вашему WCF-сервису, то сделать это вы можете несколькими способами в зависимости от версии .Net Framework. Давайте по порядку.
В версии 3.0 (то есть, самая первая версия .Net, в которой стала доступна WCF) по заверениям Microsoft не существует гарантированного способа это сделать.  В принципе, если сервис хостится на IIS, то можно попробовать пару извращений, типа получения IP-адреса клиента из логов сервера (хотя встает вопрос как определять какая из записей лога соответствует текущему запросу). В общем, предлагаю придерживаться позиции Microsoft по этому вопросу ;)
В версии .Net 3.5 в WCF появляется класс  System.ServiceModel.OperationContext, так что подключаем сборку  System.ServiceModel и используем вот такой код, который и вернет на IP клиента:


четверг, 6 ноября 2014 г.

Что такое EntityValidationErrors и как с ним бороться.

При использовании Entity Framework вы можете столкнуться с ошибкой, гласящей "Validation failed for one or more entities. See 'EntityValidationErrors' property for more details." Причина появления этой ошибки в большинстве случаев - банальна, либо вы забыли какие ограничения существуют на полях в вашей базе данных, либо, если БД в вашей компании занимается отдельный человек, то он вам забыл о чем-то сообщить. ;)


EntityValidationErrors - это коллекция объектов DbEntityValidationResult, каждый из которых содержит информацию об ошибках одной EF-сущности в виде объектов DbValidationError.
На самом деле, ничего страшного в этой ошибке нет, и можно легко узнать в чем конкретно у нас ошибка в коде. Все, что для этого необходимо - это обернуться вызов метода SaveChanges() вот в такой блок try-catch:


вторник, 4 ноября 2014 г.

Замена пустого места на что-то осмысленное в GridView при отображении NULL-значений.

Может быть, заголовок не передает всей идеи того, о чем я хочу рассказать, так что попробую объяснить ситуацию более подробно. Наверняка, многие из вас сталкивались с чем-то подобным.
Итак, представьте, что у нас в базе данных есть табличка примерно такого вида. Для простоты дальнейшего повествования предлагаю считать, что все поля, кроме id - nullable, так что null может быть где угодно, а не только в поле job.


И мы хотим вывести данные из нее в GridView. По умолчанию у нас получится вот что:

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

Сделать это можно двумя способами. Первый способ, для нашей задачи не очень правильный, путем создания обработчика события RowDataBound:


суббота, 11 октября 2014 г.

COM Interop на примере работы с Excel

В предыдущем посте я писал о создании csv-файла и экспорта в него данных  из ASP.NET и при этом, перечисляя разные варианты, не упомянул один совсем уже бредовый для web-приложения, но частенько очень подходящий для приложений десктопных. Это работа с установленным в системе COM-объектом c помощью технологии под названием COM Interop.

COM Interop используется в .Net для предоставления возможности взаимодействия управляемого .Net кода с COM-объектами. Тля того чтобы использовать какой-либо COM-объект из управляемого кода, необходимо создать сборку, содержащую информацию о типах содержащихся в COM-библиотеке, в совместимом с CRL формате.

В процессе работы приложения CLR создает для каждого COM-объекта внутренний объект, называемый Runtime Callable Wrapper (Вызываемая оболочка времени выполнения) или RCW, которая используется для создания  COM-объекта и маршалинга данных между управляемой и неуправляемой средой. Также, RCW используется для мониторинга количества активных ссылок на COM-объект и его уничтожение, когда количество активных ссылок станет равным нулю. Выглядит это примерно так:

Обычно, если вы ходите создать CLR библиотеку для какого-либо компонента самостоятельно, то вам нужно использовать утилиту Tlbimp.exe, но для использования компонентов Office этого делать не нужно. Все необходимые библиотеки уже устанавливаются вместе с продуктом. Нам достаточно только добавить нужную сборку в проект.


пятница, 10 октября 2014 г.

Создаем .csv файлы в ASP.NET

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

Выгрузить CSV-файл с нужными данными можно несколькими способами:
красивым и правильным, с использованием ASP.NET хэндлера (о хендлерах я как-нибудь напишу отдельно),
изврашенным - написав консольное приложение, которое будет запускаться по определенному графику и создавать где-нибудь доступный для web-сервера файл,
и простым и не очень правильным - просто изменив MIME-тип в ASPX-странице.
Какой бы способ вы не выбрали, все сведется примерно к такому коду:

 
            StringBuilder sb = new StringBuilder();

            sb.AppendLine("1,2,3,4");
            sb.AppendLine("5,6,7,8");
            sb.AppendLine("9,10,11,12");
            Response.ClearContent();
            Response.Clear();
            Response.ContentEncoding = Encoding.GetEncoding("Windows-1251");
            Response.ContentType = "application/vnd.ms-excel";
            Response.AddHeader("Content-Disposition", "attachment; filename=csvfile.csv;");
            Response.Write(sb.ToString());
            Response.Flush();
            Response.End();

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


вторник, 7 октября 2014 г.

Как очистить таблицу с помощью Entity Framework.

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

Вариант первый. "Правильный" с точки зрения EF, так как при этом отслеживается состояние объектов. По этой же причине - более медленный.

 
           context.YourTable.RemoveRange(context.YourTable);
           context.SaveChanges();

И второй способ. Менее "правильный", но зато быстрый. Если вам нужно очистить таблицу с какими-то временными, не имеющими отношений с другими объектами, то этот способ - самое оно.

           context.Database.ExecuteSqlCommand("TRUNCATE TABLE [YourTable]");


четверг, 2 октября 2014 г.

Сортировка случайных строк по одной из их частей.

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

Подводный камень номер один - производительность. Если использовать для выделения первой части строки string.Split(), то производительность падает пропорционально длине строк, раз, и размеру текста - два.  Эту проблему можно решить, используя регулярные выражения.
Например, для строк нашего типа можно сделать вот так:

 
    string line = "1 тест";
    string firstNumber = Regex.Match(line, "^(.*?)\s.*$").Groups[1].Value;

Подробно о результатах сравнения производительности .Split() и Regex я напишу в отдельном посте, а сейчас продолжим. Итак, мы получили первый элемент строки, по которому нам нужно сортировать, теперь нужно придумать как сортировать строки.


понедельник, 29 сентября 2014 г.

Как программно сделать скриншот.

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

 
     Bitmap screen;

     //в начале, нам надо получить размеры экрана.
     Rectangle screenDimensions = System.Windows.Forms.Screen.PrimaryScreen.Bounds;
     Size s = new Size(screenDimensions.Width, screenDimensions.Height);

     //и создать соответствующее пустое изображение.
     screen = new Bitmap(s.Width, s.Height);
     Graphics memoryGraphics = Graphics.FromImage(screen);

     //вуаля! делаем скриншот.
     memoryGraphics.CopyFromScreen(0, 0, 0, 0, s);

     //и сохраняем в файл.
     screen.Save("any_filename_you_like.jpg",System.Drawing.Imaging.ImageFormat.Jpeg);


PS. Да, не забудьте подключить пространство имен System.Drawing.


понедельник, 22 сентября 2014 г.

Печатаем текст вертикально снизу вверх.

Если у вас возникнет необходимость напечатать на странице вертикальный текст идущий снизу вверх, то вы будете не приятно удивлены. Такой возможности в .Net нет. Напечатать вертикальный текст сверху-вниз элементарно, это делается буквально в пару строк:

  
            StringFormat formatver = new StringFormat(StringFormatFlags.DirectionVertical);
            e.Graphics.DrawString("Проверка", this.Font, Brushes.Black, 0, 150, formatver);


В результате получаем вот такой текст (1). Напечатать же текст (2) не прибегая к разным ухищрениям невозможно.

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


понедельник, 11 августа 2014 г.

Как сделать сортировку по умолчанию в GridView

Если вам нужно чтобы данные в привязанном к данным (databound) GridView были по умолчанию как-то отсортированы, то, для того чтобы найти быстрое и правильное решение достаточно вспомнить о существовании у GridView такого свойства как SortExpression, которое содержит название столбца по которому в данный момент произведена сортировка. Соответственно, если данные не отсортированы, то SortExpression будет содержать пустое значение.
Таким образом, проверяя в Page_Load это свойство может понять является ли текущее состояние грида состоянием по умолчанию или сортировка уже применена. Если же сортировка отсутствует, то мы можем применить необходимую нам по умолчанию. Например, вот так:

    
        protected void Page_Load(object sender, EventArgs e)
        {
            if (String.IsNullOrEmpty(GridView1.SortExpression)) 
               GridView1.Sort("recordDate", SortDirection.Descending);
        }


понедельник, 21 июля 2014 г.

Использование типа DateTime в CAML запросах.

Предположим, что вы хотите создать CAML запрос в котором проверяете значение поле типа DateTime. Естественно, первое, что приходит на ум написать что-то типа этого:

Query = @"<Where>
          <Eq>
             <FieldRef name='SomeDateField'>
                <Value type='DateTime'>" + DateTime.Now.ToString() + @"</value>
             </FieldRef>
          </Eq>
          </Where>"

Что, естественно, работать не будет по причине того, что даты хранятся в Sharepoint в формате ISO8601, то есть вот в таком виде: 2014-01-01T10:11:12Z
Получить дату в таком формате можно как руками используя, например, формат в методе .ToString(), а можно поступить проще, используя вот такой метод из SPUtilities:

Query = @"<Where>
          <Eq>
             <FieldRef name='SomeDateField'>
                <Value type='DateTime'>" + SPUtility.CreateISO8601DateTimeFromSystemDateTime(DateTime.Now) + @"</value>
             </FieldRef>
          </Eq>
          </Where>"


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


Запуск рабочего процесса из кода

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

во-первых, подключить пространство имен Microsoft.SharePoint.Workflow,
а во-вторых использовать метод SPWorkflowManager.StartWorkflow. Существует несколько перегрузок этого метода, удобных в разных специфических ситуациях (например, если вам нужно только создать объект рабочего процесса, а запустить его на выполнение позднее), самый же простой и функциональный вариант - вот этот:

public SPWorkflow StartWorkflow(
 SPListItem item,
 SPWorkflowAssociation association,
 string eventData,
 bool isAutoStart
)

Вместо объяснения что значат все эти параметры и как из использовать  (см. MSDN ;)) я предлагаю вам взглянуть на вот такую небольшую функцию, которую я написал для запуска рабочих процессов. В комментариях небольшие пояснения, а так, в общем-то, функция вполне self-explaining.


        public SPWorkflow RunWorkflow(string workflowName, string listName, int itemId, string activationData = "<root/>")
        {
            using (SPSite site = new SPSite(siteUrl))
            {
                using (SPWeb web = site.OpenWeb())
                {
                    //получаем элемент списка для которого нужно выполнить рабочий процесс
                    SPList itemsList = web.Lists.TryGetList(listName);
                    SPListItem item = itemsList.GetItemById(itemId);
                    //создаем объект ассоциации шаблона рабочего процесса
                    SPWorkflowAssociation workflowAssociation = web.WorkflowAssociations.GetAssociationByName(workflowName, System.Globalization.CultureInfo.InvariantCulture);
                    if (workflowAssociation != null)
                    {
                        //ну и, собственно, запускаем рабочий процесс. StartWorkflow возвращает ссылку на экземпляр рабочего процесса
                        //через которую можно с ним в дальнейшем взаимодействовать, например, проверять состояние.
                        return site.WorkflowManager.StartWorkflow(item, workflowAssociation, activationData, true);
                    }
                    else return null;
                }
            }
        }


Нюансы Mail.SmtpClient

Недавно столкнулся с одной интересной ошибкой, которая может возникнуть при использовании класса System.Mail.SntpMail.

Имеем вот такой код:

MailMessage mail = new MailMessage();

//........

SmtpClient client = new SmtpClient("mail.yourhost.com", 25);
client.DeliveryMethod = SmtpDeliveryMethod.Network;
client.Credentials = new System.Net.NetworkCredential("username", "password");
client.UseDefaultCredentials = false;

client.Send(mail);


В результате выполнения этого кода вы, скорее всего получите ошибку:
Почтовый ящик недоступен. Отклик сервера: 5.7.1 Unable to relay

а все потому, что при установке client.UseDefaultCredentials = false, client.Credentials становится равен null. Что самое интересное, о такой особенности этого свойства на MSDN нет ни слова.

Ну да ладно, дальше - интереснее, если закомментировать строку с UseDefaultCredentials, то, если ваш сервер настроен на проверку подлинности клиента перед отправкой почты (стандартное поведение для Microsoft Exchange), то вы получите другую ошибку, которая, кстати, не чуть не более очевидна:
Почтовый ящик недоступен. Отклик сервера: 5.7.1 Client does not have permissions to send as this sender

И итоге, чтобы все работало как нужно нужно просто поменять местами строки с UseDefaultCredentials и с client.Credentials. И все. Вот правильная версия кода:
MailMessage mail = new MailMessage();

//........

SmtpClient client = new SmtpClient("mail.yourhost.com", 25);
client.DeliveryMethod = SmtpDeliveryMethod.Network;
client.UseDefaultCredentials = false;
client.Credentials = new System.Net.NetworkCredential("username", "password");

client.Send(mail);