Люди всегда делились на две категории: одни всегда "впереди планеты всей", вторые их догоняют. В мире программирования всё происходит аналогично: одни используют новое ПО начиная с ранних CTP (Community Technology Preview) версий, вторые переходят на них, в лучшем случае, уже после релиза. Так сложилось и в ноём текущем проекте: .net framework 4.0 вышел достаточно давно, но перейти на него получилось только сейчас, и то не полностью.

Несмотря на то, что, как правило, переход на новую версию .net framework не вызывает особых проблем (код с 3.5 успешно работает в среде 4.0), некоторые нюансы всё же есть. Нише привожу описание шагов и проблем, с которыми столкнулся.

1. Открываем наш solution (project) в Visual Studio 2010. Тут не должно быть никаких проблем: IDE сама предложит сконвертировать ваши проекты в новый формат. Если в solution нет никаких специфических проектов (например, проекты, которые создаются какими-то плагинами и их поддержки нет из коробки), то вскоре вы увидете отчёт об успешной конвертации.

2. В свойствах каждого проекта меняем Target Framework на .NET Framework 4 и пробуем всё это скомпилировать. Честно говоря, я не слышал ещё, что бы у кого-то были проблемы с этим шагом.

3. Двигаемся дальше и пробуем запусти Web Application. Вот здесь нас и поджидают первые сюрпризы. Я рассматриваю вариант, когда сайт живёт на полноценном IIS7, а не на том веб-сервере, который встроен в Visual Studio. И если при конвертации проекта не был выбран пункт “Автоматически перевести все проекты на .NET 4.0” (такое вполне вероятно когда сначала был произведен переход на Visual Studio 2010, а смена версии .NET Framework производилась посже), то вместо привычного вида своего сайта видна серверная ошибка:

error

Ошибка появляется из-за того, что немного поменялся формат файла web.config и он стал несовместимым с предыдущей версией. Решение простое - поменять web.config. И тут, как всегда, есть два способа: ручной и автоматический. Я для себя выбрал более простой и быстрый способ №2 и его советую всем. Способ заключается в запуске комнсольной утилиты из поставки IIS7 appcmd.exe. Синткаксис выглядит так:

%systemroot%\system32\inetsrv\APPCMD.EXE migrate config "Default Web Site/YourSiteName"

Более подробно о всех ключах можно почитать в помощи:

%systemroot%\system32\inetsrv\APPCMD.EXE /?

4. После этих манипуляций должно всё заработать. Но я пошел немного дальше и захотел использовать Url Routing, который есть в ASP.NET 4 (более подробно о нем можно почитать в блоге Scott Guthrie) и при открытии страниц получал 404-ю ошибку - страница не найдена. Немного покопавшись в MSDN я понял, что причина проблемы - application pool, а вернее его Managed Pipilene Mode. После выбора пункта “Integrated” всё заработало. Более подробно почитать можно на сайте IIS7.

Я описал лишь те проблемы, которые были у меня. Возможно, у вас они будут другие или вам повезёт и всё пройдет так, как рассказывает Microsoft - 2-3 клика и проект работает без проблем. Мне не повезло :(.


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

Ситуация была довольно таки простая: есть некий wizard, шаги которого хранятся в поле типа Stack для удобного перемещения вперёд-назад. Естественно, в зависимости от текущего номера шага, нужно было выполнять определённые действия. И вот, после очередного багфикса, пришлось с помощь дебаггера разбираться что же там происходит... Но, при дебагге время от времени (в начале мне было абсолютно непонятно из-за чего это происходит) я получал: System.InvalidOperationException: Stack empty. И это при том, что без дебаггера всё работало! Чтоб разобраться в чем дело, я решил написать маленькое консольное приложение (исходники, как всегда, прикреплены к посту), которое состояло из объявления стека, его наполнения и свойства класса StackItem:

 

Те, кто сталкивался с этим раньше, наверное, уже поняли в чем проблема. Запускаем дебаггер, и после наполнения стека смотрим значения свойства StackItem:

 

Второй раз смотрим значения свойства StackItem:

И здесь произошло самое интересное: при просмотре значения свойства дебаггер выполняет весь код, который находится в конструкции get. Таким образом, при каждом просмотре зачения свойства StackItem, в нашем стеке находилось все меньше и меньше данных. Возможно, для кого-то этот подход и является вполне логичным и очевидным, но не для меня. Ведь могли же в Microsoft предусмотреть это? Но нет, теперь у нас есть такая фича дебаггера, или баг...

А пока, для дебагга подобного кода, приходится использовать конструкцию вида:

 

В этом случае, если переменной i не присвоить другого значения, но она всегда будет хранить в себе число 5.

P.S. Проверял как в VS2008, так и в VS2010. Скорее всего, это фича такая...

DebuggerTest.rar (2.74 kb)


Свершилось! Visual Studio 2010 and .NET Framework 4 Beta 1 качаем здесь, а отсюда качаем Visual Studio 2010 and .NET Framework 4 Training Kit - May Preview.

Вчера столкнулся с интересным моментом при генерации *.designer.cs-файлов. Сначала подумал что это баг, но после недолгих размышлений пришел к выводу, что это не баг, а фича ("багофича" (с)). 

Всё началось с того, что на одной из страниц сайта нужно было создать некоторую функциональность, которая уже была доступна дадругой странице. Сразу же было решено поместить эу функциональность в UserControl.После достаточно стандартных действий, copy&pase нужной разметки из страницы в только что созданный контрол, уже собирался писать в нём логину, но... Но студия ругалась на любое упоминание о вебконтроле, расположенном в mycontrol.ascx. "Странно" - подумал я и посмотрел содеримое файла mycontrol.ascx.designer.cs. В нём оказалось пусто. Тут же вспомнились похожие баги Visual Studio 2005 без установки на неё Service Pack 1 и было принято решение (как озакалось потом - ошибочное) руками дописать нужный код. Дописал. Сайт скомпилировался и даже попытался запуститься, но run-time ошибка не дала ему нормально функционировать.

Ошибка достаточно ясно указывала на источник проблемы: при копипасте я забыл добавить в контол директиву <%@ Register TagPrefix="pref" TagName="Popup" src="~/UserControl/Popup.ascx" %>

После добавление этой директивы в mycontrol.ascx, mycontrol.ascx.designer.cs был успешно сгенерирован автоматически и всё заработало, а я ещё раз убедился, что от copy&pase не стоит дать ничего хорошего.


Осталось реализовать ещё несколько методов... Сейчас допишу последнюю строчку... Проект пока что не компилируется... Ура! Вот эта заветная строчка в окне Output Visual Studio:

========== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ==========
 

 Теперь предстоит запустить проект. Но все не так просто. Часто для проектов, которые больше чем знаменитый "Неllo World!" и который пишет команда из нескольких человек, необходимы какие-то условия для запуска:

  • скопировать конфигурационный файл;
  • запустить веб-службу;
  • отправить письмо о успешном билде;
  • и т.д. и т.п.

 

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

Но тут к нам приходит на помощь всеми (не)любимая компания Microsoft со своей утилитой MS Build, которая достаточно хорошо интегрирована в Visual Studio. Msbuild.exe запускается каждый раз при сборке проекта или солюшена. 

Посмотрим на свойства проекта на вкладке Build Events.  

 

 
Здесь мы видим две основные его части: Pre-build event command line и Post-build event command line. Для запуска команд до и после билда соответственно. Замечу, что Post-build может выполняться всегда, только при успешном билде или же в случае когда проект при сборке изменяет Output.
При редактировании  Pre-build Post-build событий, нам доступны некоторые макросы, часть из которых можно просто выбрать в окне радактирования события, а остальные детально описаны в MSDN.
 
 



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

 


  
Вот только, почему-то, MyClass из проекта MyClassLibrary использует CustomData.xml, предполагая что этот файл будет находится в той же папке, где находится наш solution. Поэтому, если просто добавить ссылку в проекте BuildEvents на проект MyClassLibrary, то ничего у нас работать не будет. Исправляем это добавлением Post Build event commad в проект MyClassLibrary:

 copy $(ProjectDir)\CustomData.xml $(SolutionDir)

Собираем solution и получаем:

------ Build started: Project: BuildEvents, Configuration: Debug Any CPU ------
BuildEvents -> d:\Projects\Samples\BuildEvents\BuildEvents\bin\Debug\BuildEvents.exe
------ Build started: Project: MyClassLibrary, Configuration: Debug Any CPU ------

...

Compile complete -- 0 errors, 0 warnings
MyClassLibrary -> d:\Projects\Samples\BuildEvents\MyClassLibrary\bin\Debug\MyClassLibrary.dll
copy d:\Projects\Samples\BuildEvents\MyClassLibrary\\CustomData.xml d:\Projects\Samples\BuildEvents\
        1 file(s) copied.
========== Build: 2 succeeded or up-to-date, 0 failed, 0 skipped ==========

 Все успешно собралось и файл скопировался - в этом можно убедиться, открыв в проводнике папку с нашим solution. Теперь, если посмотреть содержимое файла MyClassLibrary.csproj, то увидем такие строчки:

<PropertyGroup>
    <PostBuildEvent>copy $(ProjectDir)\CustomData.xml $(SolutionDir)</PostBuildEvent>
  </PropertyGroup>
 

Выше была описана выдуманная мной ситуация. Из практики столкнулся с таким: все config-файлы хранились в одной папке, а при сборке они копировались в Bin-папки проектов.


При разработке unit тестов в Visual Studio часто хочется создать какой-то базовый клас для тестирования базовой лоники. Например у нас есть такой класс:

 

        [TestClass]
        public class PersonTestBase
        {
            [TestMethod]
            public
virtual void GetNameTest()
            {

                //...
            }
        }

 и его класс наследник:
        [TestClass]
    public class CustomerTest: PersonTestBase
    {
        [TestMethod]
        public override void GetNameTest()
        {
            base.GetNameTest();
        }
    }

 Плюсы такого подхода:

  • полная поддержка визуальных средств Visual Studio (Test List Editor);
  • простота реализации.
Минусы:
  • избыточность кода;
  • создание наследника является по сути copy&paste.
Сразу необходимо заметить, что PersonTestBase и CustomerTest должны находится в одной сборке, иначе тесты в PersonTestBase работать не будут - это ограничение unit тестов. Подробнее смотрите в msdn. Кроме описанных в msdn способов можно поступить так:
  •  создаётся два проекта: BaseTests и CustomTests;
  • в проект CustomTests добавляются необходимые файлы из BaseTests таким образом: Project -> Add Existing Item -> Выбираем необходимые файлы -> Add As Link.
Таким образом физически файлы находятся в разных проектах, но при компиляции необходимые классы оказываются в одной сборке.
 
 Теперь пришло время изменить наш CustomerTest.
        [TestClass]
    public class CustomerTest: PersonTestBase
    {
        [TestMethod]
        public override void CustomerTestMethod()
        {
            //...
        }
    }
Мы добавили новый, специфический для Customer, метод и удалили переопределения метода из базового класса, т.к. его функциональность нас полность устраивает. Что мы из этого получили:
  • фактически, в классе у реализоано 2 тестовы метода: один перешел из базового класса и один мы реализовали сами.
  • Visual Studio Test List Editor говорит что у нас только один тетовый медов - метода из бащового класс не отображается и, соответственно, не запускается.
Обидно, но не смертельно. На помощь нам приходит штатная утилита MSTest , которая решает все, или почти все, наши проблемы.
Плюсы такого метода:
  • мы избавились от минусов предыдущего метода;
Минусы:
  • нету интеграции с Visual Studio. 

Почти год назад я писал о плагине для Visual Studio, под названием Visual Local History 2005 - мини система контролей версий на локальном компьютере. Не так давно на www.codeplex.com был найден очередной интересный, а главное - полезный, проект под названием Configuration Section Designer

 

После установки плагин добавляет новый тип проекта в Visual Studio, к котором подобно class diagram можно "рисовать" диаграммы конфигурационных файлов. После чего, плагин автоматически сгенерирует класс, который будет содержать в себе конфигурационную секцию, сделанную в диаграмме, доступ к настройкам будет состоять из доступа к свойствам класса.

Пример: с помощью диаграммы мы описали такой конфигурационный файл:

 

<CustomSettingsSection>
     <ConnectionStrings>
        <ConnectionString Name="LocalConnectionStrings" value="connectionString="Data Source=localhost;..." />
      </ConnectionStrings>
<CustomSettingsSection>

 После этого, чтобы прочитать значение, необходимо только обратиться к свойству класса CustomSettingsSection.ConnectionStrings.LocalConnectionStrings, которое вам вернёт строку подключения к базе данных.

Кроме этого, плагин генерирует xsd-схему, что добавит intellisense для конфигурационного файла. Таким образом работа с файлами настроек становится легкой и быстрой.


При миграции unit-тестов с Visual Studio 2005 на 2008 (.net 2.0) обнаружил интересный баг. Студия радостно отрапортовала об успешной конвертиции проектов, но при запустке тесты проваливались с такой ошибкой:

Method SampleTest.ClassDBTest.MyClassInitialize has wrong signature. Parameter 1 should be of type Microsoft.VisualStudio.TestTools.UnitTesting.TestContext.

Проверив инициализатор убедился что сигнатура метода правильная ещё раз запустил тесты, но они категорически отказывались работать. На компьюторе стояли две среды разработки: VS2005 и VS2008.
После внимательного изучения проекта выяснилось, что после миграции остался старый reference на сборку Microsoft.VisualStudio.QualityTools.UnitTestFramework версии 8.0. После изменения на новую версию 9.0 всё стало на свои места.
Надо отметить что в отчёте о конвертиции проектов никаких замечаний по этому поводу не было.

В новой студии, по умолчанию, при создании веб-приложения на master page добавляется ещё один ContentPlaceHolder:


<head runat="server">
<asp:ContentPlaceHolder ID="head" runat="server">
</asp:ContentPlaceHolder>
</head>


Таким образом на любой странице можно легко добавить теги title, meta, link.
Также, по умолчанию, у страницы задан атрибут Title="Untitled Page", из-за чего при использовании <title>My page</title> заголовок страницы не меняется, а исходных html-код получается таким:

<title> Untitled Page </title> <title>My page</title>

Приоритет, у способов установки названия странице, оказался таким (от большего к меньшему):

  • Явно присвоить в коде свойству Title страницы название:
    Title = “My page”;

  • Указать атрибут страице:
    <%@ Page Language="C#" MasterPageFile="~/MyMaster.Master" AutoEventWireup="true" CodeBehind="MyPage.aspx.cs" Inherits="MySite.Pages" Title="My Page" %>

  • Установить тег title:

    <asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
    <title>My Page

Если над проектом работает более одного человека, то рано или поздно наступает момент , когда им необходимо как-то обмениваться исходниками. Тут на помощь приходят CVS, SVN, Visual Source Safe, Team Foundation Server и другие. Одной из особенностей этих систем является то, что они хранят разные версии одних и тех же файлов. Стандартая ситуация: сдать файл в хранилище, сделать в нём изменения и... и достать себе более старую, но правильную версию. Эта система работает до одного момента: исходники не компилируются, а текущее их состояние надо зафиксировать. Раньше мне приходилось копировать изменяемые файлы в отдельные папки, чтобы иметь возможность в любой момент выбрать нужную мне версию.
Недавно на www.codeplex.com/ нашел маленький (41), но очень полезный прокет - Visual Local History 2005. Он хранит все изменения файлов и даёт возможность в любой момент восстановить любую из доступных версий.
После установки плагина для VS (поддерживается как Visual Studio 2005, так и 2008 Beta) в меню Tools появляется новый пункт меню, после выбора которого перед нами появляется окно плагина.

При открытии или создании проекта или solution в его папке создаётска скрытая папка с именем ".history", куда
после каждого сохранения файла записывается его предыдущее состояние.

Для восстановления нужной копии досточно лишь сделать правый клик по нужному файлу в окне Local History выбрать Restore.
Простой интерфейс, скромный набор функций, но очень полезный плагин. Пользуюсь им уже около месяца и теперь не представляю как я раньше работал без него. Вместе с intellisense он экономит не только время, но и нервы. Всегда есть возможность посмотреть на то, как изменялся файл и вернуться к предыдущей точке.
Как говорится, лучше один рах попробывать самому, а потом пользоваться этим каждый день.