Paul Selles

Computers and cats

XAML Formatting: Programmatically Tabify XAML and XML Files in C#

With many developers working together, formatting styles can sometimes be an issue. This is most noticeable when working with XAML files, and the biggest culprit for formatting issues is poor tabbing and inconsistent tab characters. Luckily there is an easy way to standardize XAML formatting through that implantation of a custom check-in policy that can programmatically fix all XAML files prior to being checked in.

Using XmlWriter in conjunction with the XmlWriterSettings class we can easily customize our output [1][2]. The XmlWriterSettings properties give us a lot of control as to how we want the XML or XAML to look and our settings are documented and are worth looking over. I am also using XmlReader and XmlReaderSettings to convert the text into an XmlDocument, were the XmlReaderSettings class is used to ensure that we ignore any potentially invalid XML characters in our XAML [3][4][5][6]. I encountered one tricky bit were the XmlReader will break on decimal and hex character reference, this was solved by doing a text replace on all “&” with “&” pre-XmlReader and converting back post-XmlWriter.

public static class Tabify
{
	// Tabify XML document
    public static void Xml(string filename)
    {
        DoTabify(filename, false);
    }

	// Tabify Xaml document
    public static void Xaml(string filename)
    {
        DoTabify(filename, true);
    }

	// Tabify
    private static void DoTabify(string filename, bool xaml=false)
    {
        // XmlDocument container
        XmlDocument xmlDocument = new XmlDocument();

        // We want to make sure that decimal and hex character references are not lost
        string xmlString = File.ReadAllText(filename);
        xmlString = xmlString.Replace("&", "&");
        
        // Xml Reader settings 
        XmlReaderSettings xmlReadSettings = new XmlReaderSettings()
        {
            CheckCharacters = false,     // We have some invalid characters we want to ignore
        };
  
        // Use XML reader to load content to XmlDocument container
        using (XmlReader xmReader = XmlReader.Create(new StringReader(xmlString), xmlReadSettings))
        {
            xmReader.MoveToContent();
            xmlDocument.Load(xmReader);
        }

        // Customize how our XML will look, we want tabs, UTF8 encoding and new line on attributes
        XmlWriterSettings xmlWriterSettings = new XmlWriterSettings()
        {
            Indent = true,                              // Indent elements
            IndentChars = "\t",                         // Indent with tabs
            CheckCharacters =  false,                   // Ignore invalid characters
            NewLineChars = Environment.NewLine,         // Set newline character
            NewLineHandling = NewLineHandling.None,     // Normalize line breaks
            Encoding = new UTF8Encoding()               // UTF8 encoding
        };

        // We do not want the xml declaration for xaml files
        if (xaml)
            xmlWriterSettings.OmitXmlDeclaration = true;    // For XAML this must be false!!!!

        StringBuilder xmlStringBuilder = new StringBuilder();
        
        // Write xml to file using saved settings
        using (XmlWriter xmlWriter = XmlWriter.Create(xmlStringBuilder, xmlWriterSettings))
        {
            xmlWriter.Flush();
            xmlDocument.WriteContentTo(xmlWriter);
        }

        // Restore decimal and hex character references
        xmlString = xmlStringBuilder.ToString().Replace("&", "&");
        File.WriteAllText(filename, xmlString);
    }
}

Paul

References

[1] XmlWriter Class. MSDN Library.

[2] XmlWriterSettings Class. MSDN Library.

[3] XmlReader ClassXmlReader Class. MSDN Library.

[4] XmlReaderSettings Class. MSDN Library.

[5] XmlDocument Class. MSDN Library.

[6] Parsing Xml with Invalid Characters in C#. Paul Selles

TFS API: TFS User Email Address Lookup and Reverse Lookup

Occasionally I need to develop a tool that requires sending and receiving emails to and from developers. To do this I will need to lookup email addresses based on a TFS user’s display or account name and vice versa. Lucky for us this is painfully easy to do using IIdentityManagementService.ReadIdentity and the TeamFoundationIdentity class [1][2]:

private static readonly string TeamProjectUriString = "http://tfs.yourtfsurl.com";

// Team Project Collection getter
private static TfsTeamProjectCollection _tfsTeamProjectCollection;
public static TfsTeamProjectCollection TfsTeamProjectCollection
{
    get
    {
        return _tfsTeamProjectCollection ??
               (_tfsTeamProjectCollection = new TfsTeamProjectCollection(new Uri(TeamProjectUriString)));
    }
}

// Identity Management Service getter
private static IIdentityManagementService _identityManagementService;
public static IIdentityManagementService IdentityManagementService
{
    get
    {
        return _identityManagementService ??
               (_identityManagementService = TfsTeamProjectCollection.GetService<IIdentityManagementService>());
    }
}

// Get Email Address from TFS Account or Display Name
public static string GetEmailAddress(string userName)
{
    TeamFoundationIdentity teamFoundationIdentity =
        IdentityManagementService.ReadIdentity(
            IdentitySearchFactor.AccountName | IdentitySearchFactor.DisplayName,
            userName,
            MembershipQuery.None,
            ReadIdentityOptions.None);

    return teamFoundationIdentity.GetAttribute("Mail", null);
}

// Get TFS Display Name from and Email Address
public static string GetDisplayName(string emailAddress)
{
    TeamFoundationIdentity teamFoundationIdentity =
        IdentityManagementService.ReadIdentity(
            IdentitySearchFactor.MailAddress,
            emailAddress,
            MembershipQuery.None,
            ReadIdentityOptions.None);

    return teamFoundationIdentity.DisplayName;
}

References

[1] IIdentityManagementService.ReadIdentity Method. MSDN

[2] TeamFoundationIdentity Class. MSDN

VS2013: Opening Build In Browser

I have just started working with VS2013 and the ability to open a build in a browser is my favorite new feature so far.

vs2013_OpenInBrowser

Paul

Task Parallelism: Passing Values into a Tasks

One road block that I stubble upon when starting to work with Task Parallelism is passing values into my Tasks. Here is a quick guide to help get you head around this issue.

 

We can easily create a single task that takes in non-changing values within a function and run it getting expected results:

int i = 0;
int j = 1;
int k = 2;

Task task = Task.Factory.StartNew(() =>
    {
        Console.WriteLine(string.Format("i='{1}'{0}j='{2}'{0}k='{3}'{0}{0}", Environment.NewLine, i, j, k));
    });

task.Wait();
Console.ReadLine();

Results:

i='0'
j='1'
k='2'

 

Once the the values start changing then the Tasks will run against the latest values rather than the original value when the Task was created. The values are not fixed within the scope of the Task itself but rather in the scope of the parent function, so the variables do not preserve the value from when the Task was created:

int i = 0;
int j = 1;
int k = 2;

Task task = Task.Factory.StartNew(() =>
    {
        Console.WriteLine(string.Format("i='{1}'{0}j='{2}'{0}k='{3}'{0}{0}", Environment.NewLine, i, j, k));
    });

i = -1;
j = -1;
k = -1;

task.Wait();
Console.ReadLine();

Results:

i='2'
j='2'
k='2'

i='2'
j='2'
k='2'

i='2'
j='2'
k='2'

i='2'
j='2'
k='2'

i='2'
j='2'
k='2'

i='2'
j='2'
k='2'

i='2'
j='2'
k='2'

i='2'
j='2'
k='2'

What happened here is that all the loops completed before any of the tasks had a chance to run (all the loops incrementing incremented to 2 before failing the for condition).

 

We can get around the problem a couple of ways, first we can reassign the values to an unchanging variable that shares the same scope as our Task. This will ensure that when ours Tasks are created the values that they use will be assigned uniquely for that instance. If we choose this route then our code will look like this:

List<Task> tasks = new List<Task>();

for (int i = 0; i < 2; i++)
{
	for (int j = 0; j < 2; j++)
	{
		for (int k = 0; k < 2; k++)
		{
			int I = i;
			int J = j;
			int K = k;

			tasks.Add(Task.Factory.StartNew(() =>
			{
				Console.WriteLine(string.Format("i='{1}'{0}j='{2}'{0}k='{3}'{0}{0}", Environment.NewLine, I, K, J));
			}));
		}
	}
}

Results:

i='0'
j='0'
k='0'

i='0'
j='1'
k='0'

i='0'
j='1'
k='1'

i='1'
j='0'
k='0'

i='1'
j='1'
k='0'

i='1'
j='0'
k='1'

i='0'
j='0'
k='1'

i='1'
j='1'
k='1'

Though the Tasks may not have completed in order, all the tasks are present and all the values have been accounted for.

 

Finally, my favourite solution, passing all the variables as an Anonymous Type to the Task [1]. Within the Task the object will have to be casted to a dynamic type to access all the Anonymous Type’s members [2]. The reason why I like this solution is that it lends itself well to working with more complicated data types without having to reassign a large number of variables locally. For our above example our solution will look like this:

List<Task> tasks = new List<Task>();

for (int i = 0; i < 2; i++)
{
    for (int j = 0; j < 2; j++)
    {
        for (int k = 0; k < 2; k++)
        {
            tasks.Add(Task.Factory.StartNew((Object obj) =>
            {
                var data = (dynamic)obj;
                Console.WriteLine(string.Format("i='{1}'{0}j='{2}'{0}k='{3}'{0}{0}", Environment.NewLine, data.i, data.k, data.j));
            }, new { i = i, j = j, k = k }));
        }
    }
}

Task.WaitAll(tasks.ToArray());
Console.ReadLine();

And the results will be similar to those above.

 

Paul

 

References

 

[1] Anonymous Types (C# Programming Guide). MSDN Library.

[2] Using Type dynamic (C# Programming Guide). MSDN Library.

Renaming File with Team Foundation Server API: Extending Workspace to Rename a Large Group of Files

The Problem

We have TFS check-in policies that will enforce naming conventions for certain files. The policy will allow the developer to programmatically rename any files that do not match the acceptable naming convention. Behind the scene the renaming is done using the WorkSpace.PendRename method [1]. This policy works fine for one or two files but for a large number of files we start having performance issues. Anyone who has experience renaming multiple files on TFS knows that it is a painfully slow ordeal. So how can we speed it up.

The Solution

I have solved this issue through the use of extending methods in the Microsoft.TeamFoundation.VersionControl.Client.Workspace class, and incorporating Task Parallelism [2][3][4]. Since the original Workspace.PendRename returns the number of files that it has renamed as an Int32, we want to make sure that we preserve that functionality.  I have only created a solution for the Workspace.PendRename(string,string) case since this is the only variation of the method that I am currently using; to expand this to all Workspace.PendRename implementations is trivial.

This the extended method:

public static class WorkSpaceExtension
{
    // Extend PendRename(String, String)
    public static int PendRename(this Workspace workspace,
        string[] oldPaths,
        string[] newPaths)
    {
        // Make sure that the oldPath and new Paths match
        if (oldPaths.Count() != newPaths.Count())
            throw new ArgumentException("Every oldPath must have corresponding newPath");

        // This list will contain all our tasks
        List pendRenameTasks = new List();

        // Loop throuh all newPath and oldPath pairs
        for (int i = 0; i < oldPaths.Count(); i++)
        {
            // Add each new task in out task container
            pendRenameTasks.Add(Task.Factory.StartNew((Object obj) =>
            {
                // We are going to pass the current old path and the
                // current new path into the path as an anonymous type
                var path = (dynamic)obj;
                return workspace.PendRename(path.oldPath, path.newPath);
            }, new { oldPath = oldPaths[i], newPath = newPaths[i] }));
        }

        // Wait for all tasks to complete
        Task.WaitAll(pendRenameTasks.ToArray());

        // Sum up the result of all the original PendRename method
        int taskCount=0;
        foreach (Task pendRenameTask in pendRenameTasks)
        {
            taskCount += pendRenameTask.Result;
        }

        // Return the number of file names changed
        return taskCount;
    }
}

References

[1] Workspace.PendRename Method. MSDN Library.

[2] Extension Methods (C# Programming Guide). MSDN Library.

[3] WorkSpace Class. MSDN Library.

[4] Extension Methods (C# Programming Guide). MSDN Library.

Follow

Get every new post delivered to your Inbox.

Join 27 other followers

%d bloggers like this: