February 21, 2008

Compiling solution in Visual Studio with generating project version based on SVN revision

Filed under: C#, Subversion, MSBuild

I’ve used to compile projects with NAnt. I’m using MSBuild currently. Main reason was to unify  build scripts during projects building inside and outside of Visual Studio. NAnt was helping me to create dynamically AssemblyInfo and than to compile into project. Attributes such as AssemblyTitle or AssemblyCompany were managed from one place and project version was generated with SVN revision based on. AssemblyInfo file wasn’t included into the project that’s why Solution Explorer didn’t even know about its existence. There was no problem with building project and messages about missing AssemblyInfo file despite of absence of this file in SVN repository. Now it is time MSBuild do this same for me :).

Let’s start with example of file compiling which is absent in Solution Explorer. Just add new AssemblyInfo.cs and then make it "Exclude From Project". Now we need to add it dynamicaly to the list of compiled files. Let’s modify our actual project file - .csproj, find target named "BeforeBuild" and put there some lines …

   1: <Target Name="BeforeBuild">
   2:     <CreateItem Include="AssemblyInfo.cs" Condition="Exists(’AssemblyInfo.cs’)">
   3:         <Output ItemName="Compile" TaskParameter="Include"/>
   4:     </CreateItem>
   5: </Target>

As you probably noticed AssemblyInfo.cs has been compiled with our project although it is not visible in Solution Explorer.

It will be good if all our projects in solution will have generated such information in the same manner. Let’s do some refactoring of our project file and make external helper file, which I usually call "Common.proj". Make "Common.proj" looks like this …

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
   3:     
   4:     <!– AssemblyInfo Properties –>
   5:     <PropertyGroup>
   6:         <AssemblyInfoFile>AssemblyInfo.cs</AssemblyInfoFile>
   7:     </PropertyGroup>
   8:     
   9:     <!– Add additional depends to Build target –>
  10:     <PropertyGroup>
  11:         <BuildDependsOn>
  12:             IncludeGeneratedAssemblyInfo;
  13:             $(BuildDependsOn)
  14:         </BuildDependsOn>
  15:     </PropertyGroup>
  16:     
  17:     <Target Name="IncludeGeneratedAssemblyInfo">
  18:         <CreateItem Include="$(AssemblyInfoFile)" Condition="Exists(’$(AssemblyInfoFile)’)">
  19:             <Output ItemName="Compile" TaskParameter="Include"/>
  20:         </CreateItem>
  21:     </Target>
  22: </Project>

We used property "BuildDependsOn" in lines (10-15), which is used by "Build" target and contains list of depended  targets. Now we don’t need to modify "BeforeBuild" target in .csproj file it will be enough to include "Common.proj" in .csproj file. You can read more about "BuildDependsOn" in - How To: Add Custom Process at Specific Points During Build (Method #2)

Let’s edit beginning of our project file .csproj like this …

   1: <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
   2:     <PropertyGroup>
   3:         <RootPath Condition=" ‘$(RootPath)’ == ‘’ ">$(MSBuildProjectDirectory)..\..\..\..\</RootPath>
   4:     </PropertyGroup>
   5:     <PropertyGroup>
   6:         <Configuration Condition=" ‘$(Configuration)’ == ‘’ ">Debug</Configuration>
   7: … cut …

"RootPath" property defined in line (3) is a helper property which points out on folder root of our solution tree where "Common.proj" and other helper files are located. As I mentioned earlier we don’t need to modify "BeforeBuild" so part of .cproj file will look like this …

   1: … cut …
   2: <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   3: <Import Project="$(RootPath)\Common.proj" />
   4: <!– To modify your build process, add your task inside one of the targets below and uncomment it. 
   5:      Other similar extension points exist, see Microsoft.Common.targets.
   6: <Target Name="BeforeBuild">
   7: </Target>
   8: <Target Name="AfterBuild">
   9: </Target>
  10: –>
  11: … cut …

"Common.proj" is just included in line (3). From this time only this file is needed to modify.

We will use target "AssemblyInfo" for generating assembly info file and target "SvnInfo" to reach for actual svn’s revision number. These tasks are included in MSBuild Community Tasks.

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
   3:     
   4:     <!– AssemblyInfo Properties –>
   5:     <PropertyGroup>
   6:         <AssemblyInfoFile>AssemblyInfo.cs</AssemblyInfoFile>
   7:         <AssemblyTitle>Title of my library.</AssemblyTitle>
   8:         <AssemblyDescription>My Product made by Me.</AssemblyDescription>
   9:         <AssemblyCompany>My Company Ltd.</AssemblyCompany>
  10:         <AssemblyProduct>My Product</AssemblyProduct>
  11:         <AssemblyVersion>1.0.0</AssemblyVersion>
  12:     </PropertyGroup>
  13:     
  14:     <!– Add additional depends to Build target –>
  15:     <PropertyGroup>
  16:         <BuildDependsOn>
  17:             IncludeGeneratedAssemblyInfo;
  18:             $(BuildDependsOn)
  19:         </BuildDependsOn>
  20:     </PropertyGroup>
  21:  
  22:     <Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
  23:  
  24:     <Target Name="IncludeGeneratedAssemblyInfo">
  25:         
  26:         <!– Get the revision number of the local working copy –>
  27:         <SvnInfo LocalPath="$(MSBuildProjectDirectory)">
  28:             <Output TaskParameter="Revision" PropertyName="SvnRevision"/>
  29:         </SvnInfo>
  30:         
  31:         <AssemblyInfo CodeLanguage="CS"
  32:             OutputFile="$(AssemblyInfoFile)"
  33:             AssemblyTitle="$(AssemblyTitle)"
  34:             AssemblyDescription="$(AssemblyDescription)"
  35:             AssemblyCompany="$(AssemblyCompany)"
  36:             AssemblyProduct="$(AssemblyProduct)"
  37:             AssemblyVersion="$(AssemblyVersion).$(SvnRevision)"
  38:             AssemblyFileVersion="$(AssemblyVersion).$(SvnRevision)" />
  39:  
  40:         <CreateItem Include="$(AssemblyInfoFile)" Condition="Exists(’$(AssemblyInfoFile)’)">
  41:             <Output ItemName="Compile" TaskParameter="Include"/>
  42:         </CreateItem>
  43:     </Target>
  44: </Project>

.. and that’s the final version.

kick it on DotNetKicks.com

February 14, 2008

Applying ASP.NET 2.0 CSS Themes for Web Controls from DevExpress

Filed under: DevExpress, ASP.NET

I’m using DXperience v2007 vol 3 web controls from DevExpress in my project. These controls rendering is based on CSS delivered by vendor. Of course every developer can easily deliver his own CSS files for custom rendering. All is described in "How to use a sample CSS file together with the CssFilePath and CssPostfix properties" knowledge base article. I recommend to read it first before following the rest of this text.

CSS files for DevExpress controls are passed to the browser by WebResource.axd if any of controls on page has empty CSSFilePath and CSSPostfix. Here is HTML head rendered for ASP.NET’s StyleSheetTheme "WarmSky".

   1: <head>
   2:   <link rel="stylesheet" type="text/css" href="/WebResource.axd?d=0pW-VO1NI7dfSm0O0Zq2ACMk4iRYek_Z5HOc04vPAnw8rKCun3Q8F4Pym8tFdctHHl4XQbUWFljcY6QXnl3p7siLCGDJ1iKxufw0w7iv7o1SPl7T7o3UZQUTi-mf3HfnnTK_ucsD_MJr-bXTL61n0g2&amp;t=633379322277157280" />
   3:   <link rel="stylesheet" type="text/css" href="/WebResource.axd?d=0pW-VO1NI7dfSm0O0Zq2ADLPYMn7C88nFaD1LYCGrG_m7LNXR4VkbF6eCu4o_f7P_mjdqJx3sBZZrQ4bq76FtHEcj1_39ooWlWE2tPILiLqxFiQpwhIOKyztP9HejXbuTlRxa7U-fgC-XpX8zwnYmQ2&amp;t=633379322285068656" />
   4:   <link rel="stylesheet" type="text/css" href="/WebResource.axd?d=0pW-VO1NI7dfSm0O0Zq2AMHyrkIXBazF7PZQtbA9x6A1Th7TVoM38ntOqyETlkyMPdYqI3W_YqfVOkNGOxuNNsjpxsJ17m3w-X4OU9YXQdI1&amp;t=633379322298387808" />
   5:   <link href="../App_Themes/WarmSky/Styles/main.css" type="text/css" rel="stylesheet" />
   6:   <title>Some Title</title>
   7: </head>

Good solution is to prepare CSS files for DevExpress controls and put them in theme folder. Then just make sure that all DevExpress controls in page has set CSSPostfix property. Of course it is quite boring to set properties on every DevExpress control in design time. To make it more automatic let’s create simple DXControlsHelper static class and put there method which applies theme to control.

   1: /// <summary>
   2: /// Applies theme for DevExpressControl.
   3: /// </summary>
   4: /// <param name="control">The control.</param>
   5: /// <param name="themeName">Name of the theme.</param>
   6: internal static void ApplyThemeToControl(ASPxWebControl control, string themeName)
   7: {
   8:     if(control == null)
   9:         return;
  10:  
  11:     if(String.IsNullOrEmpty(themeName))
  12:     {
  13:         control.CssPostfix = null;
  14:         control.CssFilePath = null;
  15:     }
  16:     else
  17:     {
  18:         control.CssPostfix = themeName;
  19:         // control.CssFilePath = "~/App_Themes/" + themeName + "/DXControls/{0}/styles.css";
  20:     }
  21: }

This method sets only CssPostfix property because we’ve put DXControls CSS files in Theme folder so they will be automatically included during page rendering. That’s why line (19) is commented out.

Now it’s is time to deliver all DXControls from the page. To make sure that we will deliver all controls let’s make some recursive method to find all of them. To make it more universal let’s use generics.

   1: /// <summary>
   2: /// Finds recursively all controls of defined type.
   3: /// </summary>
   4: /// <typeparam name="TControlType">The type of the control type.</typeparam>
   5: /// <param name="root">The root.</param>
   6: /// <returns>List of controls.</returns>
   7: internal static IEnumerable<TControlType> FindAllControlsRecursively<TControlType>(Control root)
   8:     where TControlType : Control
   9: {
  10:     if(root == null)
  11:         yield return null;
  12:  
  13:     foreach(Control item in root.Controls)
  14:     {
  15:         if (item is TControlType)
  16:             yield return item as TControlType;
  17:         foreach (TControlType itemChild in FindAllControlsRecursively<TControlType>(item))
  18:             yield return itemChild;
  19:     }
  20: }

Now it is time to apply theme to all controls. Hmm … maybe not to all …

   1: /// <summary>
   2: /// Applies theme to all child DXControls from root.
   3: /// </summary>
   4: /// <param name="root">The root.</param>
   5: /// <param name="themeName">Name of the theme.</param>
   6: internal static void ApplyThemeToControlsRecursively(Control root, string themeName)
   7: {
   8:     foreach (ASPxWebControl item in FindAllControlsRecursively<ASPxWebControl>(root))
   9:     {
  10:         if(String.IsNullOrEmpty(item.CssPostfix) && String.IsNullOrEmpty(item.CssFilePath))
  11:             ApplyThemeToControl(item, themeName);
  12:     }
  13: }

Line (10) checks if control has already set one of property, for example in design mode then theme will not be applied to such control. Last step is to modify BasePage like this …

   1: /// <summary>
   2: /// Handles the Render event of the Page control.
   3: /// </summary>
   4: /// <param name="sener">The source of the event.</param>
   5: /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
   6: protected virtual void Page_PreRender(object sender, EventArgs e)
   7: {
   8:     DXControlsHelper.ApplyThemeToControlsRecursively(this.Master, this.Page.StyleSheetTheme);
   9: }

.. now HTML header of rendered page looks like this.

   1: <head>
   2:   <link href="../App_Themes/WarmSky/DXControls/Editors/styles.css" type="text/css" rel="stylesheet" />
   3:   <link href="../App_Themes/WarmSky/DXControls/GridView/styles.css" type="text/css" rel="stylesheet" />
   4:   <link href="../App_Themes/WarmSky/DXControls/Web/styles.css" type="text/css" rel="stylesheet" />
   5:   <link href="../App_Themes/WarmSky/Styles/main.css" type="text/css" rel="stylesheet" />
   6:   <title>Title</title>
   7: </head>

February 13, 2008

Incompatibility among Framework’s build versions and problems with Rhino.Mocks

Filed under: .NET, C#, TDD, Rhino Mocks

I’ve written some mocking tests for my actual project lately. These tests use library Rhino.Mocks 3.3.0.906. The project is compiled and runs under .NET 2.0. I noticed today that some tests run failed on developers machine with Vista as operation system. There has been some difference between frameworks. My developer station with Windows XP on board had .NET 2.0.50727.1433  but Vista station had 2.0.50727.312

I’ve written some tests after error analysis. I’ll try to prove an error.

   1: public interface IMyInterface
   2: {
   3:     T GetTypedValue<T>(string key);
   4: }
   5:  
   6: [Test("Test which is correct for Framework 2.0.50727.312 + RhinoMock 3.3.")]
   7: public void TestWithStringTypeAndIgnoredArguments()
   8: {
   9:     MockRepository mocks = new MockRepository();
  10:     IMyInterface service = mocks.CreateMock<IMyInterface>();
  11:  
  12:     Expect
  13:         .Call(service.GetTypedValue<string>("any2"))
  14:         .IgnoreArguments()
  15:         .Return(null);
  16:  
  17:     mocks.ReplayAll();
  18:  
  19:     service.GetTypedValue<string>("my attribute2");
  20:  
  21:     mocks.VerifyAll();
  22: }
  23: [Test("Test which is correct for Framework 2.0.50727.312 + RhinoMock 3.3.")]
  24: public void TestWithStringTypeAndSpecifiedArguments()
  25: {
  26:     MockRepository mocks = new MockRepository();
  27:     IMyInterface service = mocks.CreateMock<IMyInterface>();
  28:  
  29:     Expect
  30:         .Call(service.GetTypedValue<string>("any2"))
  31:         .Return(null);
  32:  
  33:     mocks.ReplayAll();
  34:  
  35:     service.GetTypedValue<string>("any2");
  36:  
  37:     mocks.VerifyAll();
  38: }

These tests above runs correctly for both NET framework versions, 2.0.50727.1433 and 2.0.50727.312 .

Two next tests are quite similar. The only difference is type for generic method "GetTypedValue", "bool?" instead of "string".

   1: [Test("Test proves error for Framework 2.0.50727.312 + RhinoMock 3.3.")]
   2: public void TestWithNullableTypeAndIgnoredArguments()
   3: {
   4:     MockRepository mocks = new MockRepository();
   5:     IMyInterface service = mocks.CreateMock<IMyInterface>();
   6:  
   7:     Expect
   8:         .Call(service.GetTypedValue<bool?>("any2"))
   9:         .IgnoreArguments()
  10:         .Return(null);
  11:  
  12:     mocks.ReplayAll();
  13:  
  14:     service.GetTypedValue<bool?>("my attribute");
  15:  
  16:     mocks.VerifyAll();
  17: }
  18:  
  19: [Test("Test which is correct for Framework 2.0.50727.312 + RhinoMock 3.3.")]
  20: public void TestWithNullableTypeAndSpecifiedArguments()
  21: {
  22:     MockRepository mocks = new MockRepository();
  23:     IMyInterface service = mocks.CreateMock<IMyInterface>();
  24:  
  25:     Expect
  26:         .Call(service.GetTypedValue<bool?>("any2"))
  27:         .Return(null);
  28:  
  29:     mocks.ReplayAll();
  30:  
  31:     service.GetTypedValue<bool?>("any2");
  32:  
  33:     mocks.VerifyAll();
  34: }

Second test works correctly but first one has problem with arguments for expected call. IgnoreArguments method in line (9) doesn’t make this call to ignore arguments anymore. It will start working after installing  NET 3.5 on Vista which upgrades also NET 2.0 to version 2.0.50727.1433.

As you can see, little differences in build version can mess a lot.

Get free blog up and running in minutes with Blogsome | Theme designs available here