Tuesday, May 31, 2011

Mashup: Team Foundation Server's API and Microsoft Active Directory

I wanted to know the userID and name of anyone that checked out a file in the last 2 months for edit on a specific path inside Tfs. Some of this code was tested to work fine on Tfs2010, but no guarantees.

So it's a nice little bit of code to connect to tfs, get the changes and then pull the names in from active directory with a max of 1 attempt per distinct userID.


Tuesday, May 24, 2011

.wdproj fails to copy down .refresh binaries

So I have recently been put on a team that utilizes web site projects and hooking up build automation.

Apparently the default target for copying down .dll.refresh files does in
C:\Program Files\MSBuild\Microsoft\WebDeployment\v10.0
this for its copy inside the _ResolveReferences target:

 <Copy 
      SourceFiles="@(References->'%(FullPath)')" 
      DestinationFolder="$(_FullSourceWebDir)\Bin\" 
      Condition="!Exists('%(References.Identity)')" 
      ContinueOnError="true"
      SkipUnchangedFiles="true"
      Retries="$(CopyRetryCount)"
      RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"/>

and References.Identity resolves to the source identity, not the target identity. So if the source exists it will not copy.

To work around it, and in case in some other usages for this target need this functionality I added a custom beforebuild override in my .wdproj file:

<Target Name="BeforeBuild">
  <Message Text="Doing custom BeforeBuild" />
<Copy 
      SourceFiles="@(References->'%(FullPath)')" 
      DestinationFolder="$(_FullSourceWebDir)\Bin\" 
      ContinueOnError="true"
      SkipUnchangedFiles="true"
      Retries="$(CopyRetryCount)"
      RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"/>
</Target>

Thursday, May 12, 2011

Workflow 4 Default types to designer initialization

Dependency Injection way overboard?

I've been reflectoring, googling, stackoverflowing for weeks and just now stumbled upon the attribute injector code that runs to give ForEachActivity and SequenceActivity a designer.

Why weren't these attributes placed on the classes themselves? I have no idea.

Point your reflector at System.Activities.Core.Presentation in %windir%\Microsoft.NET\Framework\(4 something)



It's in 



System.Activities.Core.Presentation.DesignerMetadata.Register();
//Sneak peak
{
  AttributeTableBuilder builder = new AttributeTableBuilder();
  builder.AddCustomAttributes(typeof(ActivityAction),
    new Attribute[] { new EditorReuseAttribute(false) });
  SequenceDesigner.RegisterMetadata(builder);
  ForEachDesigner.RegisterMetadata(builder);
}

Tuesday, May 10, 2011

Tfs2010 string collection build argument snags

So it's great that you can use custom types, or more advanced types in Tfs 2010 workflow builds than you could in MSBuild tasks. However the editor/designer support is severely lacking. If you want an editor or designer that everyone can open up and even see what's set there, a custom assembly or vsix package has to be run on every machine that would need to be able to see or edit that argument. Even for something as basic as List<string>

After weeks of posting, forums, research etc... And trying this post it turns out that the legacy string array is what functions.

I wanted to

  • Provide defaults in the template, so that the consumer could just leave it if they didn't want anything more specific.
  • Allow for full customizations, adding, editing, removing.
  • Visibility of what's set for anyone looking at the build definition, not just people with a special assembly loaded.
But if you have tried the others they may have saved to the tfs database and cause your attempts to edit or view them to fail as if the type you are now trying isn't working. Here's a list of things I tried:
  • Microsoft.TeamFoundation.Build.Workflow.Activities.StringList
  • System.Collections.Specialized.StringDictionary
    • creating my own custom editor and adding it to the metadata
  • System.Collections.Generic.IList<string>
  • System.Collections.Generic.List<string>
As it turned out, I have no idea how many of these may have worked had the edit attempts I made not saved into the tfs database. 

Once I finally changed the variable name and tried String[] Array. All of the sudden, everything worked just fine. Alternatively, clicking refresh in team explorer -> Edit Build Definition -> Process tab -> Show details arrow -> click refresh is supposed to work. I wound up renaming the variable. 
So I went back to try all the types I listed and a few more... and the first 3 worked:

  • Microsoft.TeamFoundation.Build.Workflow.Activities.StringList
    • No custom editor specified
    • Microsoft.TeamFoundation.Build.Controls.WpfStringListEditor
  • String[] Array
Entirely or partially failed to function:
  • System.Collections.Specialized.StringDictionary
  • IList<string>
  • List<string>
  • IDictionary<string>
  • Dictionary<string>
  • Microsoft.TeamFoundation.Client.KeyValueOfStringString
  • KeyValuePair<string,string>[]

 What I want is a dictionary<string,string> but I'm getting pretty sure there's no support for it outside of a custom package deployed to all machines that want to view the arguments or edit them.

Copying a Tfs2010 Build template into same project

So, if you want to have a project where you work on TFS 2010 workflow build templates, and decide you want to conceptually branch a template into 2 parts based on an existing.  Copy, Paste, done?

Nope. If your project is set up as mine is with Build Action: XamlAppDef and Custom Tool MsBuild:Compile,
now there's a namespace/class conflict.



You get something like this



2 changes are needed. Right click the template and view code.
The first line that looks like



Which are the two points that need to be renamed. a sample rename would be:



Now your template compiles.

Thursday, May 5, 2011

My first Rx Attempt revisited

I have recently rediscovered my custom code analysis rules which included one that no longer compiles. The Rx team has made sweeping and breaking changes to the structure and namespaces in Rx. How difficult was the code update? 2 references removed, 1 added, and a single MethodName changed.

I cleaned up the code, and discovered many improvements along the way.
I do wish I could find a cleaner way to do this:

var whenMethodsFound = Observable.FromEventPattern<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>
 act => foundMethods.CollectionChanged += act,
  act => foundMethods.CollectionChanged -= act);

Idea:
Add a mapping extension in extension method class
Great for simple on the go, but manual for every class you want to do this with.

Which would look like this

public static IObservable<IEvent<PageEventArgs>> GetPublishingPageEvent(this DataFactory dataFactory)
   {
       return Observable.FromEvent((EventHandler<PageEventArgs> h) => new PageEventHandler(h), 
                                h => dataFactory.PublishingPage += h, 
                                h => dataFactory.PublishingPage -= h);
  }


Wednesday, May 4, 2011

ObjectDumper extended

This should be functionally equivalent to Microsoft's version just my own refactorings to support extensibility: No behavior should be different.


using System;
using System.IO;
using System.Collections;
using System.Reflection;

namespace Utilities
{
/// 
/// From Microsoft's http://archive.msdn.microsoft.com/cs2008samples/
/// modified for extensibility
/// 
public class ObjectDumper
{

/// 
/// StaticWrite
/// 
/// public static void Write(object element)
{
Write(element, 0);
}
protected static TextWriter DefaultWriter=Console.Out;
/// 
/// Static Write
/// 
/// /// public static void Write(object element, int depth)
{
Write(element, depth, DefaultWriter);
}
/// 
/// Static Write
/// 
/// /// /// public static void Write(object element, int depth, TextWriter log)
{
var dumper = new ObjectDumper(depth) {_writer = log};
dumper.WriteObject(null, element);
}


private TextWriter _writer;
int _pos;
int _level;
readonly int _depth;

protected ObjectDumper(int depth)
{
_depth = depth;
}
protected ObjectDumper(int depth, TextWriter log):this(depth)
{
_writer = log;
}
/// 
/// MethodWrite
/// 
/// protected void WriteString(string s)
{
if (s != null)
{
_writer.Write(s);
_pos += s.Length;
}
}

protected virtual void WriteIndent()
{
for (int i = 0; i < _level; i++) _writer.Write("  ");
        }

        protected void WriteLine()
        {
            _writer.WriteLine();
            _pos = 0;
        }
        
        protected virtual void WriteTab()
        {
            WriteString("  ");
            while (_pos % 8 != 0) WriteString(" ");
        }
        protected  void DescendIfDepthAllows(Action doIfDescend)
        {
            if (_level < _depth)
            {
                _level++;
                doIfDescend();
                _level--;
               
            }
        }

        protected virtual  void WriteObject(string prefix, object element)
        {
            if (element == null || element is ValueType || element is string)
            {
                WriteIndent();
                WriteString(prefix);
                WriteValue(element);
                WriteLine();
            }
            else
            {
                var enumerableElement = element as IEnumerable;
                if (enumerableElement != null)
                {
                    WriteEnumerable(prefix, enumerableElement);
                }
                else
                {
                    WriteWithReflection(element, prefix);
                }
            }
        }

        protected virtual void WriteWithReflection(object element, string prefix)
        {
            MemberInfo[] members = element.GetType().GetMembers(BindingFlags.Public | BindingFlags.Instance);
            WriteIndent();
            WriteString(prefix);
            bool propWritten = false;
            foreach (MemberInfo m in members)
            {
                var f = m as FieldInfo;
                var p = m as PropertyInfo;
                if (f != null || p != null)
                {
                    if (propWritten)
                    {
                        WriteTab();
                    }
                    else
                    {
                        propWritten = true;
                    }
                    WriteString(m.Name);
                    WriteString("=");
                    Type t = f != null ? f.FieldType : p.PropertyType;
                    
                    if (t.IsValueType || t == typeof(string))
                    {
                         WriteValue(GetValue(element, m, f, p)); 
                    }
                    else
                    {
                        WriteString(typeof (IEnumerable).IsAssignableFrom(t) ? "..." : "{ }");
                    }
                }
            }
            if (propWritten) WriteLine();
            WriteReflectionChildren(element, members);
        }
        protected virtual object GetValue(object element, MemberInfo m, FieldInfo f, PropertyInfo p)
        {
           return f != null ? f.GetValue(element) : p.GetValue(element, null);
        }
        

        protected void WriteReflectionChildren(object element, MemberInfo[] members)
        {
            if (_level < _depth)
            {
                foreach (MemberInfo m in members)
                {
                    var f = m as FieldInfo;
                    var p = m as PropertyInfo;
                    if (f != null || p != null)
                    {
                        Type t = f != null ? f.FieldType : p.PropertyType;
                        if (!(t.IsValueType || t == typeof(string)))
                        {
                            
                            object value =GetValue(element,m,f,p);
                            if (value != null)
                            {
                                _level++;
                                WriteObject(m.Name + ": ", value);
                                _level--;
                            }
                        }
                    }
                }
            }
        }

        protected virtual void WriteEnumerable(string prefix, IEnumerable enumerableElement)
        {
            foreach (object item in enumerableElement)
            {
                if (item is IEnumerable && !(item is string))
                {
                    WriteIndent();
                    WriteString(prefix);
                    WriteString("...");
                    WriteLine();
                    if (_level < _depth)
                    {
                        _level++;
                        WriteObject(prefix, item);
                        _level--;
                    }
                }
                else
                {
                    WriteObject(prefix, item);
                }
            }
        }
        
        protected virtual void WriteValue(object o)
        {
            if (o == null)
            {
                WriteString("null");
            }
            else if (o is DateTime)
            {
                WriteString(((DateTime)o).ToShortDateString());
            }
            else if (o is ValueType || o is string)
            {
                WriteString(o.ToString());
            }
            else if (o is IEnumerable)
            {
                WriteString("...");
            }
            else
            {
                WriteString("{ }");
            }
        }

       
    }
}

Note that this code currently throws an exception if the object passed into it is a Type, for example:

var type=typeof(string);
ObjectDumper.Write(type);


Throws: TargetInvocationException: Exception has been thrown by the target of an invocation. So... the refactoring commenced and the derived class that handles types, and also KeyValuePairs


Monday, May 2, 2011

An ActivityDesigner for InvokeAction

Building off of An ActivityDesigner for InvokeAction

The only changes needed to make it work for InvokeAction was in the Xaml

< WrapPanel name="ArgumentWrapPanel">
and the OnModelItemChanged override:

 //set ETB expression type
            var generics = invokeActionObj.GetType().GetGenericArguments();
            if (generics.Length > 0)
            {
                this.ArgumentETB.ExpressionType = generics[0];
            }
            else
            {
                this.ArgumentETB.IsEnabled = false;
                this.ArgumentETB.Expression = null;
                this.ArgumentWrapPanel.Visibility = System.Windows.Visibility.Collapsed;

            }