понедельник, 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 клиента:


воскресенье, 23 ноября 2014 г.

Отладка 64-битных веб-приложений в Visual Studio

Представьте себе ситуацию, то вы разрабатываете веб-приложение, которое либо просто само по себе 64-битное (x64), либо еще и использует какие-либо x64 библиотеки. При попытке запуска отладки такого приложения вы получите сообщение об ошибке из серии BadImageFormatException.


А происходит такое по тому, что IIS Express, используемый в Visual Studio для отладки, работает как 32-битный процесс, в чем легко убедится, взглянув в Task Manager:

И если при использовании версии Vsual Studio до 2012 единственным способом было использование для отладки локального "большого" IIS, соответствующим образом настроив свойства проекта:


суббота, 22 ноября 2014 г.

Ошибки 0x800F081F и 0x800F0906 при установке .Net Framework 3.5 на Windows 8/8.1

По умолчанию .Net Framework 3.5 в Windows 8 и 8.1 не устанавливается, так что, во-первых, он не будет доступен в Visual Studio, а во-вторых, при попытке запустить что-то, что использует .Net 3.5 (например Windows 7 SDK) вы увидите примерно такое предложение:


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

В английском варианте Windows ошибка звучит так:
0x800F0906  The source files could not be downloaded.
Windows couldn't connect to the Internet to download necessary files. Make sure that you're connected to the Internet and click "Retry" to try again.


суббота, 15 ноября 2014 г.

Помещение текущей даты в переменную в .bat-файле.

Недавно столкнулся с задачей, требующей ежедневного архивирования содержимого папки в архив вида "название файлаDD_MM_YYYY". Чтобы это сделать в одном батнике надо выполнить несколько действий:


1. Понять как выводится дата в вашей локали. Для этого запускаем CMD и пишем DATE/T. Русская локаль -самая простая, в ней текущая дата выводится в виде 15.11.2014. В Английской, например, все несколько сложнее, там перед датой идет сокращенное название дня недели, например Sat 15.11.2014. На на данном этапе интересует та часть строки, которая содержит дату. Чтобы получить ее, на нужна сделать так:

for /f %%i in ('DATE/T') do set date=%%i 

или, если перед датой что-то находится, например Sat

for /f "tokens=2" %%i in ('DATE/T') do set date=%%i

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


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

Microsoft открыла исходные коды .Net Core

Сегодня Microsoft открыла исходный код основных компонент ядра .Net. Все исходника выложены на GitHub под лицензией MIT, так что, теперь можете изучать исходники .Net без дополнительных телодвижений типа Relector, и использовать части кода в своих продуктах.

Пока доступен код только нескольких компонент:
  • ASP.NET 5
  • EntityFramework
  • .NET Core 5
  • .NET Compiler Platform ("Roslyn")
Но, Microsoft обещает скоро выложить еще.

Лежит все тут: https://github.com/Microsoft/dotnet


четверг, 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:


среда, 5 ноября 2014 г.

ASP.NET 4.5 и Unobtrusive Validation, Progressive Degradation и что это все нам дает.

В ASP.NET 4.5 среди прочих нововведений, во всю начал применяться так называемый подход Unobtrusive JavaScript, то есть, "ненавязчивый JavaScript" и использование jQuery. Эта технология уже применяется в ASP.NET MVC, по-моему, с третьей версии, и вот, теперь дело дошло и до WebForms.


Что же это за зверь "Unobtrusive JavaScript"? Это относительно новый подход к разработке клиентской части web-страниц, предполагающий несколько вещей:

  1. Отделение поведения (JavaScript) от структуры и представления (HTML/CSS). То есть, такой своеобразный вариант клиентского MVC.
  2. Попытки избежать традиционных проблем JavaScript (т.е., максимально возвожная браузеро- и платформонезависимость и масштабируемость)
  3. Использование подхода Graceful degradation, то есть приложение должно оставаться работоспособным при использовании даже старых браузеров возможно вообще не поддерживающих JavaScript.
В ASP.NET все эти принципы проявляются в виде Unobtrusive Validation, о которой я постараюсь вам рассказать применительно к общей концепции Unobtrusive JavaScript.

В качестве примера давайте создадим простой web-приложение на ASP.NET 4.5 и добавим в него одну единственную страничку с текстовым полем, кнопкой и валидатором того, что текстовое поле заполнено. 

Как вы, наверное, помните, в предыдущих версиях добавление валидатора влекло за собой добавление в код страницы целой простыни JavaScript кода. А что сейчас? Давайте попробуем запустить наш пример.


вторник, 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);
        }


четверг, 24 июля 2014 г.

Функции ранжирования в MS SQL

Как вы наверняка знаете, в реляционных базах данных строки в таблице не имеют порядка. Можно, конечно, использовать дополнительные механизмы, например IDENTITY-столбец, но это все равно не поможет вам получить, например, порядок строки в результатах запроса. Для возможности как-то соотносить порядок одних строк с другими и придуманы функции ранжирования. Прежде чем я перейду к рассказу о них, отмечу один важный момент: все ранжирующие функции являются недетерминированными, то есть результат их выполнения каждый раз может быть разным (хотя, на одном и том же наборе данных, один и тот же запрос будет возвращать один и тот же результат).

Итак, собственно, функции ранжирования. Представим, что у нас есть вот такая табличка:


И мы хотим получить порядок строк отсортированных по типу оборудования. Для этого можно использовать функция ROW_NUMBER().

SELECT ROW_NUMBER() OVER (ORDER BY PositionType DESC) as RowNumber
      ,[maker]
      ,[positionName]
      ,[positionType]
      ,[price]
  FROM [test].[dbo].[rowNumbering]


В результате выполнения этого запроса мы получим вот такой результат:

Как вы заметили, для указания на базе какого столбца будет сделана нумерация в запросе используется конструкция ORDER BY. На больших наборах данных из-за этого может пострадать производительность, но, если порядок следования рядов в результате выдачи вам не важен, то сортировки можно избежать, используя примерно такую конструкцию:

SELECT ROW_NUMBER() OVER (ORDER BY (SELECT TOP 1 1 FROM [test].[dbo].[rowNumbering])) as RowNumber
      ,[maker]
      ,[positionName]
      ,[positionType]
      ,[price]
  FROM [test].[dbo].[rowNumbering]




среда, 23 июля 2014 г.

"Задачки на сообразительность" на интервью

Наткнулся на интересную запись в блоге Эрика Липперта, не могу не поделиться, это волшебно :) Да, перевод взят из официальной русской версии блога Эрика.

----

Никто из моих знакомых в компании Microsoft больше не задает эти ужасные задачки «на нестандартную логику». Хотя, может быть, кто-то еще их задает, я не знаю. Но ходят слухи, что многие другие компании все еще следуют тому, как компания Microsoft проводила собеседования в 90-х. Продолжая эту старую традицию, я хочу представить продолжение истории Кейта Митчела о нестандартном мышлении. Итак, мы еще раз постараемся ответить на вопрос: «насколько успешно лауреат Нобелевской премии по физике, Ричард Филипс Фейнман прошел бы техническое собеседование в сотфверную компанию?»

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

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

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

Интервьюер: Хм… Да, пожалуй, этот способ сработает. Но что, если у вас нет больших зеркал, или вы не можете установить их правильным образом?

РФФ: Тогда я достану дорогую цифровую видеокамеру, установлю ее на игрушку, и прикреплю к ней веревку достаточной длины. Я помещу видеокамеру в освещенную комнату, включу ее, и протяну другой конец веревки в комнату с выключателями. Затем, я немного поиграю с выключателями, делая пометки, в какой последовательности и в какое время я их переключал. Затем я вытяну камеру назад, в комнату с выключателями и просмотрю сделанные камерой записи. Сопоставив мои записи о порядке переключения выключателей с записью видеокамеры, я смогу определить, какой выключатель, какой лампой управляет.


понедельник, 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);




среда, 18 июня 2014 г.

Как в ASP.NET установить favicon

Если вы хотите на сайте сделанном на asp.net установить favicon, то все что нужно для этого сделать это добавить вот такие две строчки в <head>:

    <link href="/favicon.ico" rel="shortcut icon" runat="server" type="image/x-icon"/>
    <link href="/favicon.ico" rel="icon" runat="server" type="image/ico" />

А так как оба этих линка - серверные контролы, то на разных страницах, если добавите свойства id, вы можете из кода устанавливать разные иконки ;)


понедельник, 19 мая 2014 г.

Интересный семинар по SharePoint

Если вам интересная тематика SharePoint, то предлагаю потратить немного времени в начале июня и сходить на интересный семинар - SharePoint for IT Professionals – tips and tricks. Вот цитата из описания семинара:

Компании Xylos и Microsoft приглашают Вас на эксклюзивный семинар: SharePoint for IT Professionals – tips and tricks. На семинаре будут рассмотрены такие актуальные для профессионалов ИТ темы, как подготовка фермы SharePoint к хостингу приложений, а также новая архитектура, которую предлагает Microsoft для построения фермы SharePoint

Заинтересовавшимся регистрироваться тут: http://xylos.timepad.ru/event/123584/



Коллекция иконок Microsoft

Отпуск кончился, опять возвращаюсь к работе. И делюсь с вами полезностью - коллекцией иконок, используемых в Visual Studio 2013/2012, Windows, Office и других продуктах.
Полезно для утилитарных и инструментальных приложений под Windows. Теперь не надо пытаться перерисовывать или извлекать из ресурсов. Да и вообще, можно много куда приспособить.


Скачивать тут:  http://www.microsoft.com/en-us/download/details.aspx?id=35825&WT.mc_id=rss_alldownloads_all


суббота, 17 мая 2014 г.

В аэропорту Парижа Шарль де Голль

Удивительное рядом. В аэропорту Шарль де Голль, в не самом крутом терминале 2C, у выходов на посадку поставили Playstation 3, так что, в ожидании рейса можно скоротать время за игрой. ;)


четверг, 15 мая 2014 г.

Старая добрая музыка в Диснейленде.

Оказывается, в парижском Диснейленде есть магазины не только с диснеевской атрибутикой. Например, в Disney Village есть ресторан-магазин King Ludwig's Castle, и там продаются неплохие музыкальные сборнички за все года, начиная с 50х.


среда, 14 мая 2014 г.

Android vs. Iphone

Сегодня, гуляя по Диснейленду, наткнулся на стенд с чехлами (то есть, бамперами) для телефонов. Так как сам с недавних пор полностью перешел на Android, я начал смотреть, что интересного можно прикупить...
Как выяснилось, практически ничего. Есть два с половина варианта (три коробочки слева в верхнем ряду), и те подходят исключительно к Galaxy S3. Все остальное для четвертого и пятого айфона.
И ведь производителей понять можно. Вариантов андроидных телефонов - вагон и маленькая тележка. На всех не угодишь. Или почти гарантированно не продаш и половины произведенной продукции.
Так что, в плане кастомизации внешнего вида, андроидофонам, увы, еще далеко до яблочной продукции...


Русский язык во Франции

Во франции, как мне показалось, уже все знают русский. Аптекари, официанты, продавцы... очень многие, узнав что я из России пытались "блеснуть знаниями" - кто "доброе утро" скажет, кто "спасибо", кто даже цену по-русски озвучит. Приятно, однако. ;)
И да, в Диснейленде теперь есть русскоязычные планы парка.


вторник, 13 мая 2014 г.

Панорамы Парижа

На этой неделе я в отпуске, так что вместо постов про программирование буду писать про Париж и Диснейленд. Вот, для начала, несколькоэпанорамок Парижа


воскресенье, 4 мая 2014 г.

Про курение

А тем временем, сегодня уже полгода как я не курю. Я не читал Алена Карра, не обклеивался пластырями и не живал живачки. Просто однажды в прошлом году я проснулся утром и не закурил. Продержался до вечера. И мне стало интересно сколько я продержусь. С тех пор и не курю. Честно скажу, сильно здоровее я себя чувствовать не стал, хотя аэробные нагрузки (включая таскание пакетов из супермаркета) даются легче, и да, с похмельем, с тех пор как бросил курить, я тоже не сталкивался. Теперь меня не напрягает летать в самолетах, ездить в европейских поездах, пить в европейских барах и жить в номерах для не курящих. А самое интересное, что обоняние и вкусовые ощущения стали значительно острее. Я, например, раньше и подумать не мог, что у простой воды может быть столько разных вкусов...
Теперь о минусах. Первый минус, видимо, обусловлен усилением обоняния и вкуса. Табачный дым я теперь воспринимаю значительно острее чем, нвпример, моя никогда не курившая супруга. То есть она может относительно спокойно сидеть в ресторане в зале для курящих, а я нет. И в туалет, где только что кто-то покурил я вообще захожу только задержав дыхание.
А самое обидное, что курящие - это какая-то особая тусковка. В офисе у них какая-то своя движуха, они все друг-друга знают, они в курсе всего. А если большинство в отделе курит, то они и рабочие вопросы решают в курилке, а некурящие узнают о принятых решениях постфактум. Мне повезло,  сейчас у меня на работе почти никто не курит, так то с этим никаких проблем нет. Но вот на предыдущем месте я частенько ловил-себя на мысли, не зря ли я бросил курить....
И да, вес я совсем не набрал, хотя ел первые месяц-два практически круглосуточно. Может спортзал помог, а может еще что, но факт есть факт.

Вот так вот.


вторник, 29 апреля 2014 г.

Entity Framework и транзакции

При использовании баз данных в проектах чуть более сложных, нежели простенький сайт на пару страниц, рано или поздно встает проблема использования транзакций. Если же вы решили использовать Entity Framework, то, до выхода EF6 стандартного механизма управления транзакциями в Entity Framework не существовало.  Теперь же, у свойства контекста Database появился метод BeginTransaction(). Используется от вот так.

using (var context = new YourDBContext())
{
   using (var tran = context.Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted))
   {
     try 
     {
           /..../
           tran.Commit();
     }     
     catch
     {
           tran.Rollback();
     }        
   }
}

Есть вариант BeginTransaction() и без явного указания уровня изоляции, при использовании которого используется уровень изоляции по умолчанию для источника данных. Соответственно, все что находится в блоке using включается в транзакцию. Причем, включаться то оно, включается, но вот откатывать или коммитить транзакцию - это ваша обязанность. Автоматически при выходе из using ничего не происходит.

Ну а если вы используете предыдущие версии Entity Framework, то можно использовать делать так: подключаете сборку System.Transactions и используете класс TransactionScope.

using (var scope = new TransactionScope(TransactionScopeOptions.Requires, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
   {
      try
      {
          /..../
          scope.Complete();
      }
      catch
      {

      }
   }


В этом случае, при выходе из using транзакция автоматически откатывается. И, кстати, TransactionScope поддерживает распределенные транзакции, для чего, правда, нужно настраивать на всех машинах где исполняется код или находятся базы данных Distributed Transaction Coordinator.


Поиск по Lookup полям с помощью CAML

Lookup поля в SharePoint представляются своего рода структуру, содержащую два свойства LookupId и LookupValue, которые, когда вы делаете .ToString() отображаются как {LookupId;#LookupValue}, то есть, если у вас в списке контактов есть поле ссылающееся на список отделов, то оно может выглядеть так: {1;#IT}. И то что при отображении этого поля его истинная структура скрывается, особенно, если вы недавно начали использовать CAML, вполне может запутать, в результате чего вы напишете что-то типа:

                        
               SPQuery query = new SPQuery
               {
                   Query = @"
                                
                                   
                                   12345
                                
                             "
               };
               SPListItemCollection items = itemsList.GetItems(query);

И, кстати, если вы хотите искать по значению (то есть, по текстовой составляющей), то это сработает. А если вы ищите по ID, то этот поиск не даст никаких результатов. Для поиска Lookup-полях по ID нужно немного изменить запрос. Вот так:

 
                SPQuery query = new SPQuery
                {
                    Query = @"
                                
                                   
                                   12345
                                
                             "
                };
                SPListItemCollection items = itemsList.GetItems(query); 


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

Как победить невозможность соединения с локальным SharePoint 2013 из Visual Studio

Как вам наверное известно, в SharePoint 2013 убрали возможность установки на клиентские операционные системы (Windows 7), так что теперь остается два варианта для рабочего места разработчика - установка Windows Server 2008 или работа в терминальном режиме. Но это так, предыстория. 


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

The local SharePoint server is not available. Check that the server is running and connected to the SharePoint farm.

или такую:

Cannot connect to the SharePoint site: http://yoursite. Make sure that the Site URL is valid, that the SharePoint site is running on the local computer, and that the current user as the necessary permissions to access the site.

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




воскресенье, 27 апреля 2014 г.

Про не очевидные ошибки в Team Foundation Server

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

Первая ошибка появляется всегда, если не сервере, где установлен TFS не установлена Visual Studio. Текст ошибки примерно такой:

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Microsoft.Common.targets (983): The reference assemblies for framework ".NETFramework,Version=v4.5" were not found. To resolve this, install the SDK or Targeting Pack for this framework version or retarget your application to a version of the framework for which you have the SDK or Targeting Pack installed. Note that assemblies will be resolved from the Global Assembly Cache (GAC) and will be used in place of reference assemblies. Therefore your assembly may not be correctly targeted for the framework you intend.

И не смотря на то, что вначале кажется, что почему-то не установился .Net Framework, дело совсем не этом. Просто скопируйте с машины на которой установлена Visual Studio папки с библиотеками, относящимися к определенной версии .Net. Например, для .Net 4.5 это:

C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5

Со второй ошибкой вы можете столкнуться, если ваш проект ссылается на какие-то нестандартные библиотеки. Текст ошибки примерно такой:

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Microsoft.Common.targets (1360): Could not resolve this reference. Could not locate the assembly "Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, processorArchitecture=MSIL". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors.

Лечится ошибка просто - нужно в конфигурации MSBuild для того билда на котором происходит эта ошибка добавить ссылку на директорию с дополнительными библиотеками. Делается это добавлением вот такого ключа:

/p:ReferencePath="C:\MyDLLs\"

 И сложить библиотеки в эту папку.


четверг, 10 апреля 2014 г.

Как бороться с ошибкой "шаблон формы опубликован на сервере, но для его открытия нужен Infopath Filler"

Если при редактировании InfoPath формы, привязанной к рабочему процессу на SharePoint, во время сохранения уже отредактированной формы на сервере, она у вас не будет сохраняться с этой ошибкой (англоязычный вариант: "An unknown error has occurred. The form template has been published to the server but it can only be opened in infopath filler"), то не спешите паниковать. Хотя решение и не очевидное - но оно есть.
В интернете, если поискать по английской версии ошибки, находится куча всяких советов, большинство которых, увы не работает (по крайней мере, у меня не заработало). Помогло только добавление компонента "Desktop Experience" (в русскоязычном варианте - "Возможности рабочего стола"). Добавляется она так:

в англоязычной версии:
Server Manager > Features > Add Features  > Desktop Experience.

в русскоязычной версии:
Диспетчер сервера > Компоненты > Добавить компоненты > Возможности рабочего стола 


вторник, 8 апреля 2014 г.

Про ошибки в русскоязычной версии MS SQL

Сегодня столкнулся с одной ошибкой на русскоязычной версии MS SQL, вначале долго смеялся, потом долго тупил. Выглядела эта ошибка вот так:

Потом попробовал перевести текст ошибки обратно на английский и сразу стало все понятно.

Transaction (Process ID 57) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

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

PS. Да, на всякий случай, если кто не знал: жертва дедлока (то есть, процессом, принудительно убиваемым). Вначале проверяется приоритет транзакций (если не установлен специально, с помощью SET DEADLOCK_PRIORITY , то у всех транзакций приоритет одинаковый - NORMAL или 0) и убивается транзакция с меньшим приоритетом. Если приоритет одинаковый, то жертвой выбирается транзакция, которую быстрее откатить (то есть, сделать ROLLBACK). Если же и затраты на ROLLBACK у транзакций, попавших в дедлок одинаковы, то жертва выбирается случайным образом.


Как установить стартовой страницу на другом узле.

Допустим, что у вас русская версия сайта и английская находятся на разных узлах, то есть в корне живет русскоязычный сайт, а на сайте /en/ его англоязычная копия. И вдруг вы захотели сделать стартовой страницей страницу англоязычных новостей, то есть /en/Pages/news.aspx. Стандартными средствами (SharePoint Designer, PowerShell) вы это сделать не сможете, из-за того, что страница не является частью сайта для которого вы хотите сделать ее главной. Как быть?
Тут нам на помощь приходит знание того, что сайт SharePoint - это, по сути, обычный ASP.NET сайт. И соответственно, мы можем делать с шарепоинтным конфигом то же самое что и с любым кофигом сайта на ASP.NET. Так что, чтобы сделать совершенно любую страницу стартовой вам достаточно добавить в секцию <system.web> вот такой элемент:

<urlMappings enabled="true">
  <clear />
  <add url="~/" mappedUrl="~/en/Pages/news.aspx" />
</urlMappings>


воскресенье, 6 апреля 2014 г.

Вызов методов web-сервисов/серверных методов с помощью jQuery

В старые добрые времена, если вы хотели обратиться из клиентского кода к веб-сервису, или, что серверному методу [WebMethod], то приходилось писать много кода использующего javascript-объект XMLHttpRequest. Сейчас же, если вы используете в своем ASP.NET проекте jQuery, сделать серверный запрос из клиентского кода очень просто. Допустим, у вас есть вот такой метод:

        [WebMethod]
        public static string GetSomeNewString(string _id, string _name)
        {
             string result;
             /..../
             return result;
        }

Все что вам нужно, чтобы вызвать его на клиенте это написать что-то типа:
        $.ajax({
            type: "POST",
            url: "~/Default.aspx/GetSomeNewString",
            data: "{'_id': '" + Id + "','_taskid': '' }",
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            context: this,
            success: function (response) {
                //сюда мы попадаем в случае успешного выполнения серверной функции
                //результат выполнения будет в response.d
            },
            failure: function (response) {
                 //сюда мы попадаем в случае ошибки
                 alert(response.d);
            }
        });


Кстати, этот же способ можно использовать и для веб-частей SharePoint. Единственное отличие в том, что веб-часть не может содержать серверные методы (то есть, конечно, может, но смысла в этом мало - обращаться к вы можете только либо к веб-сервисам, либо к страницам с веб-методами).  Так что, все что вам нужно - это добавить в проект решения для SharePoint страницу приложения (Application Page) которая и будет содержать веб-методы:


вторник, 1 апреля 2014 г.

Где хранить пользовательские данные часть 2: про SPPersistedObject

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

Но, вернемся к нашей теме. Что вообще такое этот SPPersistedObject? Вот краткая выдержка из MSDN:

Класс SPPersistedObject предоставляет базовый класс для всех объектов администрирования. 
Все поля с атрибутом Persisted к XML-сериализации и записывает XML blob в базу данных конфигурации. 

Так что, все что нам нужно - это создать свой класс наследующий у SPPersistedObject и содержащий нужные нам Persisted поля. Вот так, например:
       public class MySettingsClass: SPPersistedObject 
       {
            public MySettingsClass() {}

            public MySettingsClass(string name, SPPersistedObject parent, Guid id)  :  base(name, parent, id) { }
   
            [Persisted]
            public string MySomeProperty;

            [Persisted]
            public string MyOtherProperty;
      }

Итак, свой класс мы создали. Надо сказать, это был самый сложный этап. Дальше все делается парой строк кода. Давайте сохраним наши установки. Например, в контексте объекта webApplication:
     MySettingsClass settings = new MySettingsClass("my settings", SPContext.Current.Site.WebApplication, Guid.NewGuid());
     settings.MySomeProperty = "test";
     settings.MyOtherProperty = "test2";
     settings.Update();

Ну и теперь, получив из webApplication объект нашего класса мы можем восстановить наши установки.
     MySettingsClass settings = SPContext.Current.Site.WebApplication.GetChild< mysettingsclass >("my settings");
     string property1 = settings.MySomeProperty; // "test";
     string property2 = settings.MyOtherProperty  // "test2";

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


понедельник, 31 марта 2014 г.

Где хранить пользовательские данные или установки в SharePoint?

В SharePoint есть несколько вариантов хранения пользовательских данных, разной степени "правильности" и удобства.

Самый просто вариант - это, конечно, куки, которые в SharePoint, к и в любом веб-сайте никто не отменял. На всякий случай напомню как работать с куками в ASP.NET:

      //Записываем данные
      HttpCookie myCookie = new HttpCookie("MyKey");
      myCookie.Value = someData;
      myCookie.Expires = DateTime.Now.AddDays(1000);
      Response.Cookies.Add(myCookie);


      //Читаем данные
      if (Request.Cookies["MyKey"] != null)
      {
                    string someData = Request.Cookies["MyKey"].Value.ToString();
      }

      //Удаляем куку устанавливая куку с просроченным временем истечения. Данные нам в этом случае неважны, поэтому просто ""
      HttpCookie myCookie = new HttpCookie("MyKey");
      myCookie.Value = "";
      myCookie.Expires = DateTime.Now.AddDays(-1000);
      Response.Cookies.Add(myCookie);


Второй вариант - использования свойства Properties объекта SPWeb. Если решите использовать этот способ имейте ввиду, что все, что вы добавляете в Properties автоматически дублируется в свойстве AllProperties, но с ключом состоящим только из маленьких букв.
   // Добавляем свойство
   web.Properties["MyKey"] = value;
   web.Update();
   web.Properties.Update();
 
   // Читаем свойство

   var value = web.Properties["MyKey"];

   // Удаляем свойство
   web.AllProperties.Remove("mykey");
   web.Properties["MyKey"] = null;
   web.Update();
   web.Properties.Update();




Получаем список всех таблиц/тригеров/вьюшек в БД MS-SQL

В любой базе данных MS-SQL есть несколько очень полезных системных вьюшек. Самая интересная из них называется sys.objects и содержит данные практически обо всем в вашей базе. Например, вот так можно получить список всех хранимых процедур:

  SELECT *
  FROM [test].[sys].[sysobjects]
  where type='P'

А вот так список всех пользовательских таблиц:
  SELECT *
  FROM [test].[sys].[sysobjects]
  where type='U'

Тип информации о которой вы хотите узнать задается параметром type и может быть одним из следующих вариантов:


четверг, 27 марта 2014 г.

Процесс разработки ПО. Наглядно.




Как проверить существует ли объект (список, поле и т.п.) на сайте SharePoint

В Sharepoint почему-то все не однозначно устроено на предмет получения информации о наличии того или иного элемента.

Самый очевидный и простой способ - это типы контента. Достаточно сделать вот так:
  var myType = web.AvailableContentTypes["Special Document"];
  if (myType!=null) //тип контента существует

На этом простое заканчивается. О существовании списка уже так не узнаешь. Если списка нет, то подобная конструкция вызовет исключение. И, обертывание доступа к списку в try-catch, в общем-то, единственный способ узнать о том, что списка на сайте нет и не уронить приложение. Так что, рекомендую написать примерно такой метод расширения и использовать при необходимости его.
    public static bool ListExists(this SPWeb web, string listName)
    {
       try
       {
          var list = web.Lists[listName];
          return true;
       }
       catch
       {
           return false;
       }            
    }
Ну а самое интересное с полями. Представьте, что вы создали вот такое поле:


среда, 26 марта 2014 г.

Static в .Net - почему иногда это плохо и как с этим быть?

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

Начнем с классов. Если вы объявляете класс статическим, то на него автоматически накладываются несколько ограничений:
1) Статический класс может содержать только статические методы.
2) Статический класс автоматически помечается как sealed, и следовательно от него нельзя наследовать
3) Нельзя создавать экземпляры статических классов.

Исходя из всего этого напрашивается вывод что единственные кандидаты в статические классы - это классы которые не должны хранить состояние. То есть классы, методы которого получают что-то на вход и на базе этих данных выдают результат. Как самый показательный пример - класс Math.
И самый правильный вариант использования начиная с .Net 3.0 - создание классов содержащих методы расширения (extension methods). Их разрешено объявлять только в статических классах.

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


Как развернуть решение на удаленном сервере SharePoint с помощью PowerShell

Я часто сталкиваюсь с ситуацией, когда, вроде бы, вполне вменяемые люди ставят Visual Studio на продакшн-сервер где установлен Sharepoint. И совершенно не для отладки (я говорю именно про вменяемых людей, которые обычно знают о существовании Remote Debug), а исключительно для развертывания решений. Часть из них просто не хочет изучать какой-то еще PowerShell, часть думает, что для того чтобы развернуть решение на удаленной машине нужно устанавливать Sharepoint Designer и делать это с его помощью.... в общем отмазки разные, а результат один и тот же.
На самом же деле абсолютно ничего сложно в развертывании решении с помощью PowerShell нет - достаточно запомнить (или записать) очень простой порядок действий:

1. Скопируйте на сервер файл решения (тот который с расширением wsp).
2. Зайдите на сервер с помощью, например, Remote Desctop и откройте Sharepoint 2010 Management Shell
3. Теперь загрузите файл решения вот этой командой:

Add-SPSolution -LiteralPath [путь к wsp-файлу]
например: PS C:\Users\eugene> Add-SPSolution -LiteralPath C:\Solution\MyWebPart.wsp

4. Теперь установите загруженное решение:

Install-SPSolution -Identity MyWebPart.wsp -GACDeployment -AllWebApplications

В принципе, команда Install-SPSolution должна автоматически активировать фичу, содержащуюся в решении, но если это не произошло то нужен последний шаг:

Enable-SPFeature -Identity "MyWebPart_Feature1" -URL [адрес приложения для которого активировать фичу]

Вот и все, ничего сложного в этом нет. А вот последовательность команд для удаления решения:

Disable-SPFeature -identity "MyWebPart_Feature1" -URL [адрес приложения для которого деактивировать фичу]

Uninstall-SPSolution -Identity MyWebPart.wsp -AllWebApplications

Remove-SPSolution -Identity MyWebPart.wsp


вторник, 25 марта 2014 г.

Шифруем строки соединения с БД в web.confg

Одно из правил "хорошего тона" при созданий приложений с претензией на безопасность и защищенность от взлома - нигде не писать логины и пароли. Особенно пароли к локальным БД в которых, как правило, живут очень-важные-секретные-данные (а вот скажите честно, вы всегда руководствуетесь принципом минимально возможных прав доступа, или честно под sa соединяетесь? ;)


Так вот, в ASP.NET начиная с версии 2.0 существует очень простая возможность прозрачного шифрования строк соединения.  Прежде всего вам необходимо предоставить пользователю, под которым работает IIS, доступ к хранилищу ключей RSA:

aspnet_regiis -pa "NetFrameworkConfigurationKey" "NT Authority\Network Service"

А после этого, для того, чтобы зашифровать содержимое секции достаточно сделать очень простую вещь:
    public void EncryptConnString()
    {  
       Configuration config = WebConfigurationManager.OpenWebConfiguration(Request.ApplicationPath);
       ConfigurationSection section = config.GetSection("connectionStrings");
       if (!section.SectionInformation.IsProtected)
       {
           section.SectionInformation.ProtectSection("RsaProtectedConfigurationProvider");
           config.Save();
       }
    }

И что самое главное, зашифровав секцию таким образом вы все равно сможете ей пользоваться совершенно прозрачно, как будто она и не зашифрована. Единственный минус, работа с зашифрованным секциями несколько медленнее.
   string connString =  WebConfigurationManager.ConnectionStrings["MyConnString"].ConnectionString;


Статические конструкторы в .Net

Многие знают, что в .Net есть такая вещь, как статические конструкторы. И то, что для статических конструкторов CLR гарантирует две вещи:

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

В общем-то из-за второго свойства статические конструкторы обычно используют для создания объектов по шаблону Singleton. Примерно вот так:
    
    public class SampleSingleton
    {
        private static SampleSingleton instance;

        public static SampleSingleton Instance
        {
            get
            {
                return instance;
            }
        }

        private SampleSingleton() { }

        static SampleSingleton()
        {
            instance = new SampleSingleton();
        }

        public void SomeMethod()
        {
            Console.WriteLine("блаблабла");
        }
    }

А дальше начинается то, о чем не многие задумываются. Как вы думаете реализуется эта "одноразовость" вызова статического конструктора? Реализуется она банально захватом внутренней блокировки из чего следует потенциальная возможность устроить себе дедлок, который будет не очень просто отлаживать.  Вот, посмотрите на этот код:


воскресенье, 23 марта 2014 г.

Как удалить элементы из списка.

Представьте себе, что у нас есть список, содержащий какие-то элементы, а нам нужно в какой-то момент удалить часть из них. Какие варианты есть? Самый очевидный для многих вариант - с использованием foreach работать не будет:

            foreach (var item in list)
            {
                if (item % 2 == 0)
                { list.Remove(item); }
            }


Не будет он работать вот почему. foreach компилятором разворачивается в примерно такую конструкцию:
            
            List< t >.Enumerator enumerator = list.GetEnumerator();
            try
            {
               while (enumerator.MoveNext())
               {
                 ....
               }
            }
            finally
            {
               enumerator.Dispose();
            }

А если мы используем что-то типа Resharper и посмотрим на метод MoveNext(), то увидим, что он сравнивает версию коллекции, и если она изменилась, то выбрасывает исключение.
            
            [__DynamicallyInvokable]
            public bool MoveNext()
            {
                List< t > list = this.list;
                if ((this.version == list._version) && (this.index < list._size))
                {
                    this.current = list._items[this.index];
                    this.index++;
                    return true;
                }
                return this.MoveNextRare();
            }

            private bool MoveNextRare()
            {
                if (this.version != this.list._version)
                {
                    ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
                }
                this.index = this.list._size + 1;
                this.current = default(T);
                return false;
            }


Давайте попробуем дрогой вариант - цикл for.


Кое-что про числа в .Net

Посмотрите на этот код. Как думаете какой будет результат?

            Double a = 1;
            Double b = 3;
            Double c = a/b;
            Double d = c * 3;

            Single a3 = 1;
            Single b3 = 3;
            Single c3 = a3 / b3;
            Single d3 = c3 * 3;

            decimal a2 = 1;
            decimal b2 = 3;
            decimal c2 = a2 / b2;
            decimal d2 = c2 * 3;

            Console.WriteLine("Double:");
            Console.WriteLine(c.ToString());
            Console.WriteLine(d.ToString());

            Console.WriteLine();
            Console.WriteLine("Single:");
            Console.WriteLine(c3);
            Console.WriteLine(d3);

            Console.WriteLine();
            Console.WriteLine("Decimal:");
            Console.WriteLine(c2);
            Console.WriteLine(d2);

Не смотря на то, что, казалось бы, все три результата должны быть идентичны, только при использовании типа decimal результат такой же как и при обычном делении/умножении на бумажке. И double и single в результате дадут единицу. Почем так получается, спросите вы? Все из-за того, что single и double содержат два дополнительных разряда в конце, благодаря которым можно получить исходное значение при выполнении арифметических действий в обратном порядке. Увидеть их можно вот так:
            Console.WriteLine("{0:R}", c);

На этом, кстати, интересности относительно single и double не заканчиваются как думаете что будет в результате выполнения вот этого кода?
            Double number = 123;
            Double zero = 0;
            Double result = number / zero;



четверг, 20 марта 2014 г.

Языконезависимая фильтрация полей типа choise в коде

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


И, by design, должно фильтровать список задач по соответствующим статусам. Проблема в том, что при англоязычной установке статус называется "Not Started", а на русскоязычной "Не начата". И более того, все эти статусы можно банально поменять или добавить новые, которые тоже должны работать как ожидается.



Использование модальных диалогов в SharePoint

Возможность редактирования и добавления элементов нужна всегда, а возиться и делать свои собственные диалоги не всегда есть возможность и необходимость. Тем более, если есть возможность использовать стандартные. Давайте, посмотрим, куда, например, ведет ссылка "Add new item" в стандартном списке "Tasks":


Ведет она вот сюда:
Add new item


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

Итак, первый способ использование модальных диалоговых окошек - использование этой функции. Правда, для ее использования вам нужно знать несколько параметром: Guid списка (обязательно), ID элемента (обязательно для редактирования), ID типа контента (нужно только, если ваш список использует несколько типов контента, и вам нужен отличный от того, что по умолчанию). Ну и самое главное - нужно указать тип диалога. Их всего три: PageType=4 (просмотр), PageType=6 (редактирование) и PageType=8 (создание нового элемента). То есть, получается примерно так:

Просмотр элемента

Редактирование элемента

Добавление элемента

А теперь второй способ:


среда, 19 марта 2014 г.

Делаем плагины в программах на .Net

В .Net есть два варианта того, как сделать свои плагины: использование Microsoft Enterprise Library и использование отражений (reflection). Первый способ не совсем тривиальный и дня него нужно дополнительно скачивать MEL. Второй же способ доступен "из коробки" и, в большинстве ситуаций, его возможностей вполне достаточно.
Вообще, механизм отражений в .net - вещь достаточно мощная и интересная, так что, в будущем расскажу про него еще что-нибудь интересное. А пока переходим к плагинам.

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

    
    public interface IPluginDef
    {
        string SayHello();
    }

Итак, что наследуя ваш интерфейс, насоздавали классов, наприсылали вам DLL'ек c ними и надо с этип добром теперь что-то делать. Для начала объявим где-нибудь в вашей программе глобальную переменную, в которой будут жить загруженные классы плагинов:
    
  List< IPluginDef > all_plugins = new List< IPluginDef >(); 



Управление пользователями SharePoint из кода

Все, что необходимо для управления пользователями в объектной модели SharePoint содержится в пространстве имен Microsoft.Office.Server.UserProfiles самый интересный для нас класс которого - UserProfileManager. Все, что нужно для его инициализации это передать в конструктор текущий контекст:

     UserProfileManager userPM = new UserProfileManager(SPServiceContext.Current);

Все готово к работе. Теперь, например, можно создать нового пользователя:
    string accountName = "DOMAIN\\newuser";
    if (!userPM.UserExists(accountName)) 
    {
      userPM.CreateUserProfile(accountName);

      UserProfile user = userPM.GetUserProfile(accountName);
      //Установим отображаемое имя пользователя. По сути, PreferredName - это AD'шный DisplayName, но об этом ниже.
      user["PreferredName"] = "Новый пользователь";
      user.Commit();
    }

А потом сказать, что он и Вася Пупки - коллеги, а Петя Васечкин - его руководитель:
    UserProfile user = userPM.GetUserProfile("DOMAIN\\newuser");
    UserProfile userColleague = userProfileManager.GetUserProfile("DOMAIN\\vpupkin");
    if (!user.Colleagues.IsColleague(userProfileColleague.ID))
    { 
       //если использовать Create, то коллега получит мейл, что его добавили. 
       //Если это не нужно, используйте CreateWithoutEmailNotification, сигнатура у него такая же.                  
       user.Colleagues.Create(userColleague,                        
       ColleagueGroupType.General,      
       //если нужно, тут указываем группу конфиденциальности, в которую поместить коллегу.      
       string.Empty,   
       //должен ли коллега быть частью рабочей группы                    
       true,                               
       Privacy.Public);  
       
       if (userPM.UserExists("DOMAIN\pvasechkin")) 
       {
          user["Manager"].Clear();               
          user["Manager"].Add("DOMAIN\pvasechkin");                    
          user.Commit();
       }
    }

Ну и в конце, создадим Васе персональный сайт:
    UserProfile user = userPM.GetUserProfile(sAccount);
    user.CreatePersonalSite();

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


вторник, 18 марта 2014 г.

Про инженеров

Жизненно...


Как найти все списки, созданные на базе определенного шаблона

Если вам нужно найти все списки созданные на базе, например, шаблона "Задачи" (tasks), то, прежде всего, нужно знать ID типа шаблона (можно подглядеть вот тут: http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.splisttemplatetype.aspx )
Зная ID шаблона можно сделать вот так:
    foreach (SPList list in SPContext.Current.Web.Lists) 
    { 
        //107 - шаблон tasks 
        if (!list.Hidden & (int)list.BaseTemplate == 107) 
        { 
           //Мы нашли список на базе шаблона 
        } 
    } 
Если же вы хотите найти списки, созданные на базе какого-то кастомного шаблона, то так просто это сделать не получится. В таком случае list.BaseTemplate все равно будет содержать идентификатор базового шаблона, на базе которого создан ваш шаблон. Поэтому, чтобы как-то определить на основе вашего шаблона создан список, или на основе базового, то нужно ввести в шаблон что-то уникальное, например, добавить свой тип контента. Тогда, если базовый тип списка совпадает с базовым для вашего шаблона, то все что нужно остается - это проверить, содержит ли список ваш тип контента:
    foreach (SPList list in SPContext.Current.Web.Lists) 
    { 
        if (!list.Hidden & (int)list.BaseTemplate == 107) 
        { 
            //если мы хотим найти лист, созданный на базе кастомного шаблона 
            foreach (SPContentType cT in list.ContentTypes) 
            { 
                if (cT.Name == "MyNewContentType") 
                //мы нашли необходимый нам список 
                break; 
            } 
        } 
    } 


понедельник, 17 марта 2014 г.

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

Иногда бывает необходимо отлавливать события внутри приложения вне зависимости от того, какой контрол активен. Например, вы хотите реализовать аналог "IDDQD" в своем приложении ;))) Или просто хотите обрабатывать события по каким-то своим законам. Сегодня я расскажу вас как это сделать.

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

    
    public delegate void MouseWheelEvent(int rotation);
    public delegate void KeyDownEvent(int keyCode);

    public class GlobalEventHandler : IMessageFilter
    {
        public event MouseWheelEvent TheMouseWheel;
        public event KeyDownEvent TheKeyDown;

        public bool PreFilterMessage(ref Message m)
        {

        }

    }
следующий этап - реализовать метод PreFilterMessage: