Thursday, October 21, 2010

Simple MsBuild Custom Task

Here's a very simple custom task that reads in the number in a text file (which only contains a number) increments, passes it back out to the calling MsBuild, and then writes the new value to the text file.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
 
namespace BMsBuildTasks
{
  public class IncrementerTask:Task
  {
    [Output]
    public ITaskItem OutputFullPath { getset; }
    [Output]
    public int Value { getset; }
    public override bool Execute( )
    {
      
      var path =OutputFullPath==null?OutputFullPath.GetWellKnownMetaData(WellKnownMetaData35.FullPath ):
        @"c:\temp\version.txt";
 
      int value;
      using (var file=new System.IO.StreamReader(path))
      {
        
        if (!int.TryParse(file.ReadToEnd( ), out value))
          return false;
 
      }
      value++;
      Value=value;
      using (var file=new System.IO.StreamWriter(path,false))
      {
        file.Write(value);
 
      }
      OutputFullPath = new TaskItem(path);
      return true;
    }
  }
}



    public static string GetWellKnownMetaData(this ITaskItem item, WellKnownMetaData35 value)
    {
      return item.GetMetadata(value.ToString( ));
    }



public enum WellKnownMetaData35
  {
    FullPath,
    RootDir,
    Filename,
    Extension,
    RelativeDir,
    Directory,
    RecursiveDir,
    Identity,
    ModifiedTime,
    CreatedTime,
    AccessedTime
  }

Wednesday, October 20, 2010

EF4 whitelist/explicit eager loading

In linq to sql it was very simple:

    var loadOptions = new System.Data.Linq.DataLoadOptions();
    loadOptions.LoadWith<alliance>(a => a.Players);
    dc.LoadOptions = loadOptions;

where dc was the datacontext.

This specifies that any queries that are submitted to this DataContext that return an Alliance object/entity would also go ahead on the same db trip and fetch it's related Players.

In EF4.0 so far it looks like you have to resort to magic strings. Magic strings are a pet peeve of mine. Your compile time type safety goes out the window in this type of magic string use.

Here are 2 solutions I've found on my googling that can get rid of the magic strings for EF4.

http://j.mp/bub2FW

and

http://j.mp/bKX7X6

Wednesday, September 15, 2010

MSBuild: Copy a list of files to a list of directories

I have config files in a master directory (team does not allow svn:externals, which I believed was designed for this task, perhaps I did not set it up correctly) that I need to svn update and copy to my modules.


 <PropertyGroup>
  <LocalBranchPath>C:\branches\November<\LocalBranchPath>
  <LocalSourcePath>$(LocalBranchPath)\Source<\LocalSourcePath>
  <TortoisePath>C:\Program Files\TortoiseSVN\bin\TortoiseProc.exe<\TortoisePath>
  <ClientConfigPath>$(LocalSourcePath)\MasterDirectory<\ClientConfigPath>
  <TortoiseUpdate>&quot;$(TortoisePath)&quot; /closeonend:1 /command:update /path:<\TortoiseUpdate>
  <ReferencedAssembliesPath>$(LocalSourcePath)\ReferencedAssemblies<\ReferencedAssembliesPath>
  <DbProjLocalPath>$(LocalBranchPath)\Database\DBProjects<\DbProjLocalPath>
  <PropertyGroup>
<ItemGroup>
 <MyModules Include="$(LocalSourcePath)\CommonControls\LimitsInformation;
  $(LocalSourcePath)\CommonControls\BalanceInquiry;
  $(LocalSourcePath)\CommonControls\AuditTrail;
  $(LocalSourcePath)\Administration\Reassignment;
  $(LocalSourcePath)\IndividualControls\Configuration\ActivityList"
 />   
</ItemGroup>

 I need the config files in ClientConfigPath

to be copied to the subfolder of MyModules called ModuleName.UnitTest





let's get the svn update out of the way 





<Target Name="SvnUpdateClientConfigs">
 <Exec Command="$(TortoiseUpdate)&quot;$(ClientConfigPath)&quot;"
/><Target>

Very short and sweet, now the hard part, copying multiple files to multiple subdirectories. I had no idea FileName was a shortcut in this case for the final directory name only, it came in handy.





<Target Name="CopyClientConfigsBatched" Outputs="%(MyModules.FullPath)">
  <Message Text="@(MyModules -> '%(FullPath)\%(FileName).UnitTest')"/>
 <ItemGroup>
  <ClientConfigs
  Include="$(ClientConfigPath)\*.config"
  Exclude="$(ClientConfigPath)\P*.config" >
  </ClientConfigs>
 <ItemGroup>
 <Copy SourceFiles="@(ClientConfigs)" DestinationFolder="@(MyModules -> '%(FullPath)\%(FileName).UnitTest')"
   SkipUnchangedFiles="true"/>
<Target/>





The Outputs was necessary because Copy.DestinationFolder will not accept multiple items.
So first I build the list of files, which could have been done at the project level but this was the only target that needed it, and those files may not be present until the svn update was run.  The Exclude prevents PolicyCache.config from being part of the copy. SkipUnchangedFiles makes sure files that haven't changed aren't copied wasting time.




Thursday, September 9, 2010

My first Rx attempt

I think I overshot for a first attempt by far and I'm not sure how to validate the efficiency/concurrency of the code. However, here is Rx that so far as I've used it appears to correctly do recursive producer-consumer iteration of a call graph.



 public override ProblemCollection Check(TypeNode type)
    {
      Debug.WriteLine("Checking type:"+type.FullName);
      var initializer = type.Members.OfType<Method>( ).FirstOrDefault(x => x.FullName==type.FullName+".InitializeComponent");
 
      if (initializer==null)
        return null;
      Debug.WriteLine(initializer.FullName);
      var constructorsWithNoInitCall = type.Members.OfType<Method>( ).Where(m => m.NodeType==NodeType.InstanceInitializer).ToList( );
      var visitedMethods = new HashSet<string>( );
      var foundMethods = new ObservableHashSet<Method>( );
 
      var whenMethodsFound = Observable.FromEvent<NotifyCollectionChangedEventArgs>(foundMethods, "CollectionChanged");
 
      whenMethodsFound.Subscribe(
        e =>
        {
          switch (e.EventArgs.Action)
          {
            case NotifyCollectionChangedAction.Add:
              if (constructorsWithNoInitCall.Any( ))
                Parallel.ForEach(e.EventArgs.NewItems.Cast<Method>( ).Where(m => visitedMethods.Any(v => v==m.FullName)==false),
                  i =>
                  {
 
                    lock (visitedMethods)
                    {
                      if (visitedMethods.Contains(i.FullName))
                        return;
                      visitedMethods.Add(i.FullName);
 
                    }
                    Debug.WriteLine("Visiting:"+i.FullName);
                    var callers = (CallGraph.CallersFor(i));
                    constructorsWithNoInitCall.RemoveAll(x => callers.Any(c => x.FullName==c.FullName));
                    if (constructorsWithNoInitCall.Any( ))
                      foreach (var item in callers.Where(c => visitedMethods.Any(v => v==c.FullName)==false))
                      {
                        foundMethods.Add(item);
                      }
                  });
              break;
            default:
              break;
          }
        }
        );
      foundMethods.Add(initializer);
 
 
      ReportProblem(constructorsWithNoInitCall, type);
 
      return Problems;
    }

Vs2010 Code Analysis Custom Rules

I've written my first custom rules for visual studio to help me on my team with the written and unwritten rules. It was a long journey but I have the hang of it. These rules are set to run on every debug build I do and take about 8 seconds to run all.

 As I understand it custom rules ( Inherited from Microsoft.FxCop.Sdk) can also be used as code check in policies on TFS or in the build process to produce a nice xml report.

Here's the base class (.net 4.0 class library project type)


using Microsoft.FxCop.Sdk;
 
public abstract class BaseRule:BaseIntrospectionRule
{
 protected BaseRule(string ruleName)
  : base(
   ruleName,
   //typeof(BaseRule).Assembly.GetName().Name+".Rules"
   "ProjectNamespace.RuleMetadata",
   typeof(BaseRule).Assembly
   ) { }
}


This class was defined without a namespace, I believe it did not work while it was in one. It's also quite important to note that the string literal Must match your project namespace+xml filename without the .xml on it. The commented method may work as well if your xml file is called Rules.xml. This file must be set to Embedded Resource.

Rules can be set as Messages, Warnings, or Errors and show up in the standard Error List control if code analysis is turned on in your project.

The general classes of rules I have written so far include:

  • Do not use method - Things like GC.AddMemoryPressure, MessageBox.Show (we use a custom dialog with styling)
  • Do not inherit - We don't want anyone inheriting from Windows.Forms.Form
  • Do not raise- We don't want code that has NotImplementedExceptions still present.
  • UseHungarian for controls
    • uses a dictionary to determine if the control has a preferred name we use on the team.
  • Fields should be private
  • Control Property must be set to x
    • For example, in most cases we want all dialog border styles to be fixed - I check the Initialize Component method of anything that inherits from Form for the property to be set there.
  • MustOverride - For legacy reasons (old Visual Studio bug they tell me) we have methods/properties that are virtual instead of abstract, and if they aren't overridden will throw a runtime exception.