Beginning EPiServer Unit Testing: Page Filtering

I managed to solve the problem with ContentLanguage.PreferredCulture as described in my previous post. But six unit tests still failed. It turned a filter had been added to display only published pages like this:

new FilterPublished(PagePublishedStatus.Published).Filter(pages);

For me, this was harder than expected to fix. Pages were created in unit tests like this:

new BeslutsunderlagPage()

(We’re using PageTypeBuilder.) But these have version status “NotCreated” and are filtered out by the above code. After a lot of experimenting (because I’m a complete beginner at EPiServer) I came up with the following way to create these pages that works:

        private static TPage CreatePublishedPage<TPage>() where TPage : PageTypeBuilder.TypedPageData, new()
        {
            var page = new TPage();
            var prop = new PropertyNumber((int)VersionStatus.Published);
            page.Property.Add("PageWorkStatus", prop);
            return page;
        }
Advertisements

Beginning EPiServer Unit Testing: ContentLanguage.PreferredCulture

Yes, we had 348 working unit tests in our EPiServer solution. Then a developer had to solve a bug where pages weren’t correctly sorted. (Å and Ä were sorted as A and Ö as O.) The following code

new FilterPropertySort(“PageName”).Filter(pages);

was changed to

FilterPropertySort comparer = new FilterPropertySort(“PageName”);
comparer.CompareInfo = ContentLanguage.PreferredCulture.CompareInfo;
pages.Sort(comparer);

But now, unit tests started failing. They threw “EPiServer.BaseLibrary.ClassFactoryException: ClassFactory not initialized”. It was not hard to understand why, but how to solve it. It turned out to be relatively simple after looking at Unit test an EPiServer 6 website using Visual Studio 2010 and MSTest. Luckily, I didn’t have to initialize the whole hosting environment. The following code was sufficient:

        [AssemblyInitialize]
        public static void Initialize(TestContext context)
        {
            EPiServer.BaseLibrary.ClassFactory.Instance = new EPiServer.Implementation.DefaultBaseLibraryFactory(String.Empty);
            EPiServer.BaseLibrary.ClassFactory.RegisterClass(typeof(EPiServer.BaseLibrary.IRuntimeCache), typeof(EPiServer.Implementation.DefaultRuntimeCache));
        }

        [TestInitialize]
        public void Setup()
        {
            ...
            EPiServer.Globalization.ContentLanguage.PreferredCulture = new System.Globalization.CultureInfo("sv-SE");
        }

In the Life of a TFS Configuration Manager Part 4: Building Deploy Scripts for Database projects (.dbproj) with TFS

This was not supposed to be a problem. In Visual Studio, you can easily generate a database deployment script by setting project properties on the Deploy tab.

image

However, when using TFS/MSBuild automated build, this did not work – no .sql file was generated. I found a clue in the documentation, An Overview of Database Build and Deployment. There is an example to deploy a database:

MSBuild /target:Deploy /p:UseSandboxSettings=false /p:TargetDatabase=UpdatedTargetDatabase;TargetConnectionString="Data Source=(local)\SQLEXPRESS;Integrated Security=True;Pooling=False" MyProjectName.dbproj

The important parameter here is the first one, /target:Deploy. Specifying it on the command line is fine if you’re just building this project, but what if it is part of a larger solution? Turns out to be relatively easy. Just edit the project file and add Deploy as a default target:

<Project DefaultTargets="Build;Deploy" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">

In the Life of a TFS Configuration Manager Part 3: Configuration Transforms and Visual Studio Installer Projects

In part 2, I described how to add configuration transforms for non-web projects. Now comes the really difficult part – making it work with Visual Studio Installer projects. The problem is that installer projects include the untransformed App.config rather than the transformed <YourProgram>.exe.config. So we have to make a new copy of the transformed file and include that in the installer project.

1. Edit TransformFiles.targets in the following way. Below

<TransformXml Source="@(_AppConfigToTransform->'%(FullPath)')"
              Transform="%(RelativeDir)%(Filename).$(Configuration)%(Extension)"
              Destination="$(_AppConfigDest)"
              Condition=" Exists('%(RelativeDir)%(Filename).$(Configuration)%(Extension)') " />

add the following:

<TransformXml Source="@(_FilesToTransformNotAppConfig->'%(FullPath)')"
              Transform="%(RelativeDir)%(Filename).$(Configuration)%(Extension)"
              Destination="@(_FilesToTransformNotAppConfig->'$(OutDir)%(RelativeDir)%(Filename)%(Extension)')"
              Condition=" Exists('%(RelativeDir)%(Filename).$(Configuration)%(Extension)') " />

This will create a file called app.transformed.config.

2. In your installation project, right-click Primary output from your project and select Exclude Filter. Add App.config. This prevents the untransformed App.config to be added to the installation package.

3. To add the transformed configuration file, right-click on the installer project and select Add –> File… Select app.transformed.config. Select it and change the TargetName property to <YourProgram>.exe.config.

The only problem now is that app.transformed.config is write protected during the build, so TransformFiles.targets will fail (“Could not write Destination file: Access to the path ‘…\app.transformed.config’ is denied.”). I solved this with a pre-build event command:

ATTRIB -R $(ProjectDir)app.transformed.config

Now build the installation package and test it.

In the Life of a TFS Configuration Manager Part 2: Configuration Transforms in Non-Web Projects

It was great that Microsoft added support for configuration transforms in web projects in Visual Studio 2010, but what about non-web projects like console applications? Well, it can be accomplished but not without some careful editing of the project file. It is described in an MSDN blog called XDT (web.config) Transforms in non-web projects.

Add a new item to the project and choose the Application Configuration File template. Call it e.g. App.Prod.config.

Code your transform. Here is an example:

<?xml version="1.0"?>
    <!-- For more information on using web.config transformation visit http://go.microsoft.com/fwlink/?LinkId=125889 –>
    <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    <appSettings>
     <add key="ZipFilesStorage" value="\\INE-PIR-FSCL01\RGS\CountyCouncilAccessLogs" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
   </appSettings>
</configuration>

Do this for all configurations in your project.

I have stored the TransformFiles.targets file (linked from the blog mentioned above) in source control rather than in $(MSBuildExtensionsPath)\Custom (%ProgramFiles (x86)%\MSBuild\Custom). To make sure this file is downloaded when you get the project from source control, add the file as a link in the project.

Edit your project file by right-clicking on the project and choosing Edit Project File.

Add an import of the transform target before the closing </Project> tag. Make sure the number of parent path symbols (..) reflects your folder structure.

  <Import Project="$(ProjectDir)..\..\TransformFiles.targets" />

Replace e.g.

    <None Include="App.AT.config" />
    <None Include="App.config">
        <SubType>Designer</SubType>
    </None>
    <None Include="App.Prod.config" />
    <None Include="App.ST.config" />

with e.g.

    <None Include="App.config">
       <TransformOnBuild>true</TransformOnBuild>
     </None>
     <None Include="App.Dev.config">
       <DependentUpon>App.config</DependentUpon>
     </None>
     <None Include="App.Prod.config">
       <DependentUpon>App.config</DependentUpon>
     </None>
     <None Include="App.AT.config">
       <DependentUpon>App.config</DependentUpon>
     </None>
     <None Include="App.ST.config">
       <DependentUpon>App.config</DependentUpon>
     </None>

This should transform your configuration file both when building using Visual Studio and MSBuild (Team Foundation Server). However, it will not work if you’re using Visual Studio Installer projects to create MSI packages for installation. The next part of this series will cover that.

In the Life of a TFS Configuration Manager Part 1: Building Projects that are not Supported by MSBuild

You have probably had the following error:

The project file … is not supported by MSBuild and cannot be built.

Well, it is a common problem that Visual Studio Installation projects (.vdproj) cannot be built using MSBuild, and neither can Business Intelligence Development Studio (BIDS) projects (.dwproj, .dtproj, .rptproj). A common solution seems to be to install Visual Studio on the build server and use DevEnv by modifying the build template. I used the following blog post as a starting point: I need to build a project that is not supported by MSBuild

I made the following modifications:

1. Create a string new variable called ProgramToInvoke in the Compile the Project scope. Set default to “”.

2. Extend the switch with .rptproj, .dwproj and .dtproj. For these, set ProgramToInvoke to “C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\devenv.com” . (Include the .com extension to be sure that is run rather tan DevEnv.exe. The devenv.com utility provides for the delivery of output through standard system streams, such as stdout and stderr.) For .vdproj, set ProgramToInvoke to “C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\devenv.com” .

image

3. Under this activity, insert an if activity with condition ProgramToInvoke = “”. In the Then branch, place the Run MSBuild for Project activity.

image

4. In the Else branch, add a sequence activity called Build Using DevEnv. Here you place the InvokeProcess activity.

5. Set [ExitCode] as result to be able to test the result of the build.

image

6. Now add a condition that checks if [ExitCode] <> 0. If so, something went wrong. I discovered that sometimes it goes wrong without any good reason, and the solution is to retry the operation once more. Therefore, add the same Invoke DevEnv activity again.

image

If it fails a second time, set the build Status property to Microsoft.TeamFoundation.Build.Client.BuildStatus.Failed.

7. In the CopyDirectory activity, copy the results to BinariesDirectory plus the configuration to be built.

image

Here is the entire source code:

                                      <Sequence DisplayName="Compile the Project" sap:VirtualizedContainerService.HintSize="980,1635" mtbwt:BuildTrackingParticipant.Importance="Low">
                                        <Sequence.Variables>
                                          <Variable x:TypeArguments="x:String" Name="localProject" />
                                          <Variable x:TypeArguments="x:Int32" Name="ExitCode" />
                                          <Variable x:TypeArguments="x:String" Name="ProgramToInvoke">
                                            <Variable.Default>
                                              <Literal x:TypeArguments="x:String" Value="" />
                                            </Variable.Default>
                                          </Variable>
                                        </Sequence.Variables>
                                        <sap:WorkflowViewStateService.ViewState>
                                          <scg:Dictionary x:TypeArguments="x:String, x:Object">
                                            <x:Boolean x:Key="IsExpanded">True</x:Boolean>
                                            <x:Boolean x:Key="IsPinned">False</x:Boolean>
                                          </scg:Dictionary>
                                        </sap:WorkflowViewStateService.ViewState>
                                        <mtbwa:ConvertWorkspaceItem DisplayName="Convert Server Path to Local Path" sap:VirtualizedContainerService.HintSize="958,22" mtbwt:BuildTrackingParticipant.Importance="Low" Input="[serverBuildProjectItem]" Result="[localProject]" Workspace="[Workspace]" />
                                        <Switch x:TypeArguments="x:String" DisplayName="Determine project type" Expression="[(New System.IO.FileInfo(localProject)).Extension.ToLower()]" sap:VirtualizedContainerService.HintSize="958,298">
                                          <sap:WorkflowViewStateService.ViewState>
                                            <scg:Dictionary x:TypeArguments="x:String, x:Object">
                                              <x:Boolean x:Key="IsPinned">False</x:Boolean>
                                              <x:Boolean x:Key="IsExpanded">True</x:Boolean>
                                            </scg:Dictionary>
                                          </sap:WorkflowViewStateService.ViewState>
                                          <Assign x:Key=".vdproj" sap:VirtualizedContainerService.HintSize="453,100">
                                            <Assign.To>
                                              <OutArgument x:TypeArguments="x:String">[ProgramToInvoke]</OutArgument>
                                            </Assign.To>
                                            <Assign.Value>
                                              <InArgument x:TypeArguments="x:String">C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\devenv.com</InArgument>
                                            </Assign.Value>
                                          </Assign>
                                          <Assign x:Key=".rptproj" sap:VirtualizedContainerService.HintSize="453,100">
                                            <Assign.To>
                                              <OutArgument x:TypeArguments="x:String">[ProgramToInvoke]</OutArgument>
                                            </Assign.To>
                                            <Assign.Value>
                                              <InArgument x:TypeArguments="x:String">C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\devenv.com</InArgument>
                                            </Assign.Value>
                                          </Assign>
                                          <Assign x:Key=".dwproj" sap:VirtualizedContainerService.HintSize="453,100">
                                            <Assign.To>
                                              <OutArgument x:TypeArguments="x:String">[ProgramToInvoke]</OutArgument>
                                            </Assign.To>
                                            <Assign.Value>
                                              <InArgument x:TypeArguments="x:String">C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\devenv.com</InArgument>
                                            </Assign.Value>
                                          </Assign>
                                          <Assign x:Key=".dtproj" sap:VirtualizedContainerService.HintSize="453,100">
                                            <Assign.To>
                                              <OutArgument x:TypeArguments="x:String">[ProgramToInvoke]</OutArgument>
                                            </Assign.To>
                                            <Assign.Value>
                                              <InArgument x:TypeArguments="x:String">C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\devenv.com</InArgument>
                                            </Assign.Value>
                                          </Assign>
                                        </Switch>
                                        <If Condition="[ProgramToInvoke = &quot;&quot;]" sap:VirtualizedContainerService.HintSize="958,1111">
                                          <If.Then>
                                            <mtbwa:MSBuild CommandLineArguments="[String.Format(&quot;/p:SkipInvalidConfigurations=true {0}&quot;, MSBuildArguments)]" Configuration="[platformConfiguration.Configuration]" DisplayName="Run MSBuild for Project" GenerateVSPropsFile="[True]" sap:VirtualizedContainerService.HintSize="200,1010" LogFileDropLocation="[logFileDropLocation]" OutDir="[outputDirectory]" Platform="[platformConfiguration.Platform]" Project="[localProject]" RunCodeAnalysis="[RunCodeAnalysis]" mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces" TargetsNotLogged="[New String() {&quot;GetNativeManifest&quot;, &quot;GetCopyToOutputDirectoryItems&quot;, &quot;GetTargetPath&quot;}]" ToolPlatform="[MSBuildPlatform]" Verbosity="[Verbosity]" />
                                          </If.Then>
                                          <If.Else>
                                            <Sequence DisplayName="Build Using DevEnv" sap:VirtualizedContainerService.HintSize="733,1010">
                                              <sap:WorkflowViewStateService.ViewState>
                                                <scg:Dictionary x:TypeArguments="x:String, x:Object">
                                                  <x:Boolean x:Key="IsExpanded">True</x:Boolean>
                                                  <x:Boolean x:Key="IsPinned">False</x:Boolean>
                                                </scg:Dictionary>
                                              </sap:WorkflowViewStateService.ViewState>
                                              <mtbwa:InvokeProcess Arguments="[&quot;&quot;&quot;&quot; + localProject + &quot;&quot;&quot; /build &quot; + platformConfiguration.Configuration]" DisplayName="Invoke DevEnv" FileName="[ProgramToInvoke]" sap:VirtualizedContainerService.HintSize="711,190" Result="[[ExitCode]]">
                                                <mtbwa:InvokeProcess.ErrorDataReceived>
                                                  <ActivityAction x:TypeArguments="x:String">
                                                    <ActivityAction.Argument>
                                                      <DelegateInArgument x:TypeArguments="x:String" Name="errOutput" />
                                                    </ActivityAction.Argument>
                                                    <mtbwa:WriteBuildError sap:VirtualizedContainerService.HintSize="200,22" Message="[errOutput]" />
                                                  </ActivityAction>
                                                </mtbwa:InvokeProcess.ErrorDataReceived>
                                                <mtbwa:InvokeProcess.OutputDataReceived>
                                                  <ActivityAction x:TypeArguments="x:String">
                                                    <ActivityAction.Argument>
                                                      <DelegateInArgument x:TypeArguments="x:String" Name="stdOutput" />
                                                    </ActivityAction.Argument>
                                                    <mtbwa:WriteBuildMessage sap:VirtualizedContainerService.HintSize="200,22" Importance="[Microsoft.TeamFoundation.Build.Client.BuildMessageImportance.High]" Message="[stdOutput]" mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces" />
                                                  </ActivityAction>
                                                </mtbwa:InvokeProcess.OutputDataReceived>
                                                <sap:WorkflowViewStateService.ViewState>
                                                  <scg:Dictionary x:TypeArguments="x:String, x:Object">
                                                    <x:Boolean x:Key="IsPinned">False</x:Boolean>
                                                  </scg:Dictionary>
                                                </sap:WorkflowViewStateService.ViewState>
                                              </mtbwa:InvokeProcess>
                                              <If Condition="[[ExitCode] &lt;&gt; 0]" DisplayName="" sap:VirtualizedContainerService.HintSize="711,656">
                                                <sap:WorkflowViewStateService.ViewState>
                                                  <scg:Dictionary x:TypeArguments="x:String, x:Object">
                                                    <x:Boolean x:Key="IsPinned">False</x:Boolean>
                                                  </scg:Dictionary>
                                                </sap:WorkflowViewStateService.ViewState>
                                                <If.Then>
                                                  <Sequence sap:VirtualizedContainerService.HintSize="486,555">
                                                    <sap:WorkflowViewStateService.ViewState>
                                                      <scg:Dictionary x:TypeArguments="x:String, x:Object">
                                                        <x:Boolean x:Key="IsExpanded">True</x:Boolean>
                                                      </scg:Dictionary>
                                                    </sap:WorkflowViewStateService.ViewState>
                                                    <mtbwa:InvokeProcess Arguments="[&quot;&quot;&quot;&quot; + localProject + &quot;&quot;&quot; /build &quot; + platformConfiguration.Configuration]" DisplayName="Invoke DevEnv" FileName="[ProgramToInvoke]" sap:VirtualizedContainerService.HintSize="464,190" Result="[[ExitCode]]">
                                                      <mtbwa:InvokeProcess.ErrorDataReceived>
                                                        <ActivityAction x:TypeArguments="x:String">
                                                          <ActivityAction.Argument>
                                                            <DelegateInArgument x:TypeArguments="x:String" Name="errOutput" />
                                                          </ActivityAction.Argument>
                                                          <mtbwa:WriteBuildError sap:VirtualizedContainerService.HintSize="200,22" Message="[errOutput]" />
                                                        </ActivityAction>
                                                      </mtbwa:InvokeProcess.ErrorDataReceived>
                                                      <mtbwa:InvokeProcess.OutputDataReceived>
                                                        <ActivityAction x:TypeArguments="x:String">
                                                          <ActivityAction.Argument>
                                                            <DelegateInArgument x:TypeArguments="x:String" Name="stdOutput" />
                                                          </ActivityAction.Argument>
                                                          <mtbwa:WriteBuildMessage sap:VirtualizedContainerService.HintSize="200,22" Importance="[Microsoft.TeamFoundation.Build.Client.BuildMessageImportance.High]" Message="[stdOutput]" mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces" />
                                                        </ActivityAction>
                                                      </mtbwa:InvokeProcess.OutputDataReceived>
                                                      <sap:WorkflowViewStateService.ViewState>
                                                        <scg:Dictionary x:TypeArguments="x:String, x:Object">
                                                          <x:Boolean x:Key="IsPinned">False</x:Boolean>
                                                        </scg:Dictionary>
                                                      </sap:WorkflowViewStateService.ViewState>
                                                    </mtbwa:InvokeProcess>
                                                    <If Condition="[[ExitCode] &lt;&gt; 0]" DisplayName="" sap:VirtualizedContainerService.HintSize="464,201">
                                                      <sap:WorkflowViewStateService.ViewState>
                                                        <scg:Dictionary x:TypeArguments="x:String, x:Object">
                                                          <x:Boolean x:Key="IsPinned">False</x:Boolean>
                                                        </scg:Dictionary>
                                                      </sap:WorkflowViewStateService.ViewState>
                                                      <If.Then>
                                                        <mtbwa:SetBuildProperties DisplayName="Set Status to Failed" sap:VirtualizedContainerService.HintSize="219,100" mtbwt:BuildTrackingParticipant.Importance="Low" PropertiesToSet="Status" Status="[Microsoft.TeamFoundation.Build.Client.BuildStatus.Failed]" />
                                                      </If.Then>
                                                      <If.Else>
                                                        <mtbwa:CopyDirectory Destination="[BinariesDirectory + &quot;\&quot; + platformConfiguration.Configuration]" sap:VirtualizedContainerService.HintSize="220,100" Source="[(New System.IO.FileInfo(localProject)).DirectoryName + &quot;\&quot; + platformConfiguration.Configuration + &quot;\&quot;]" />
                                                      </If.Else>
                                                    </If>
                                                  </Sequence>
                                                </If.Then>
                                                <If.Else>
                                                  <mtbwa:CopyDirectory Destination="[BinariesDirectory + &quot;\&quot; + platformConfiguration.Configuration]" sap:VirtualizedContainerService.HintSize="200,555" Source="[(New System.IO.FileInfo(localProject)).DirectoryName + &quot;\&quot; + platformConfiguration.Configuration + &quot;\&quot;]" />
                                                </If.Else>
                                              </If>
                                            </Sequence>
                                          </If.Else>
                                        </If>
                                      </Sequence>