Wednesday, September 30, 2015

XDT Console Application

XML Document Transformations, or XTD, is a great way to transform your app and web config files between environments or builds. It is directly supported by Visual Studio and other third party tools, such as Octopus Deploy.

So how can you transform config files on your own? For starters uou can use great free tools like the Web.config Transformation Tester (which is open source) from AppHarbor.

Would you rather transform your files via command line? Then just do it yourself! Pull down the Microsoft.Web.Xdt package from NuGet, and then copy and paste this code to implement your own simple console app...

Program.cs

namespace XDT
{
  using System;
  using System.IO;
  using System.Text;
  using System.Xml;
 
  using Microsoft.Web.XmlTransform;
 
  public class Program
  {
    private static int Main(string[] args)
    {
      if (args.Length != 3)
      {
        Console.WriteLine("Required Arguments: [ConfigPath] [TransformPath] [TargetPath]");
        return 400;
      }
 
      var configPath = args[0];
      if (!File.Exists(configPath))
      {
        Console.WriteLine("Config not found");
        return 404;
      }
 
      var transformPath = args[1];
      if (!File.Exists(transformPath))
      {
        Console.WriteLine("Transform not found");
        return 404;
      }
 
      try
      {
        var targetPath = args[2];
        var configXml = File.ReadAllText(configPath);
        var transformXml = File.ReadAllText(transformPath);
 
        using (var document = new XmlTransformableDocument())
        {
          document.PreserveWhitespace = true;
          document.LoadXml(configXml);
 
          using (var transform = new XmlTransformation(transformXml, false, null))
          {
            if (transform.Apply(document))
            {
              var stringBuilder = new StringBuilder();
              var xmlWriterSettings = new XmlWriterSettings
              {
                Indent = true,
                IndentChars = "  "
              };
 
              using (var xmlTextWriter = XmlWriter.Create(stringBuilder, xmlWriterSettings))
              {
                document.WriteTo(xmlTextWriter);
              }
 
              var resultXml = stringBuilder.ToString();
              File.WriteAllText(targetPath, resultXml);
              return 0;
            }
 
            Console.WriteLine("Transformation failed for unknown reason");
          }
        }
      }
      catch (XmlTransformationException xmlTransformationException)
      {
        Console.WriteLine(xmlTransformationException.Message);
      }
      catch (XmlException xmlException)
      {
        Console.WriteLine(xmlException.Message);
      }
 
      return 500;
    }
  }
}

Enjoy,
Tom

Saturday, September 26, 2015

How to only Serialize Interface Properties with Json.NET

When you serialize an object with Newtonsoft's Json.NET it will resolve the serialization contract for the type being serialized. This means that if you want to serialize an object so that it matches one of the interfaces that it implements you will need to use a customized contract resolver.

When I first tried to do this I made a completely custom JsonConverter for the type that looked up the properties via reflection and just wrote their values out manually. Unfortunately had the side effect of bypassing all of the features the Newtonsoft provides with regard to decorating classes and customizing the serialization process for that object.

There was a good topic on Stack Overflow about this that led me to the custom contract resolver solution. However the sample implementation there is hard coded to only try to serialize one hard coded type for all serialization.

Below is an implementation (with tests) that allows you to specify a list of interfaces that you want to serialize by, and then if the object being serialized does implement that interface it will fall back on it's default contract.

InterfaceContractResolver Implementation

public class InterfaceContractResolver : DefaultContractResolver
{
    private readonly Type[] _interfaceTypes;
 
    private readonly ConcurrentDictionary<Type, Type> _typeToSerializeMap;
 
    public InterfaceContractResolver(params Type[] interfaceTypes)
    {
        _interfaceTypes = interfaceTypes;
 
        _typeToSerializeMap = new ConcurrentDictionary<Type, Type>();
    }
 
    protected override IList<JsonProperty> CreateProperties(
        Type type,
        MemberSerialization memberSerialization)
    {
        var typeToSerialize = _typeToSerializeMap.GetOrAdd(
            type,
            t => _interfaceTypes.FirstOrDefault(
                it => it.IsAssignableFrom(t)) ?? t);
 
        return base.CreateProperties(typeToSerialize, memberSerialization);
    }
}

Wednesday, September 16, 2015

Add Generated Files to your Project

I recently had to work with a small code generator and needed to dynamically add generated cs files to my csproj. This actually ended up being very simple!

You will need to use ProjectCollection in the Microsoft.Build assembly to load the project, and then once you have scanned the directory for files you can easily diff the two to find new files. The only "catch" is that you need be sure to match the files to the relative paths from the csproj.

Code

Here is a very simple sample console application that adds generated files to another csproj. (See the picture to the right for the exact file structure.)

public static void Main()
{
    const string relativePath = "../../../TestProj/";
 
    var files = Directory
        .GetFiles(relativePath + "Generated", "*.cs")
        .Select(r => r.Replace(relativePath, string.Empty));
 
    using (var engine = new ProjectCollection())
    {
        var project = engine.LoadProject(relativePath + "TestProj.csproj");
 
        var newFiles = files
            .Where(f => !project.Items.Any(i => i.UnevaluatedInclude == f))
            .ToList();
 
        foreach (var newFile in newFiles)
        {
            project.AddItem("Compile", newFile);
        }
 
        project.Save();
    }
}

Enjoy,
Tom

Real Time Web Analytics