Generating AssemblyInfo files with Gulp

19 Nov 2015

When changing a project's build script over to Gulpjs, I ran into a problem with one step - creating an AssemblyInfo.cs file.

My projects have their version number in the package.json file, and I read that at compile time, pull in some information from the build server, and write that to an AssemblyVersion.cs file. This file is not tracked by git, and I don't want it showing up as a modification if you run the build script locally.

The problem is that the gulp-dotnet-assembly-info package doesn't support generation of files, only updating. To get around this I used the gulp-rename package to read a template file, and generate the non-tracked AssemblyVersion.cs file.

Steps

First, create an AssemblyVersion.base file, and save it somewhere in your repository. I usually put it next to the gulpfile, or in the projects Properties directory, depending on if the project has multiple assemblies or not. This file can be added and tracked by git - it won't get changed.

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

[assembly: AssemblyVersion("0.0.0")]
[assembly: AssemblyFileVersion("0.0.0")]
[assembly: AssemblyDescription("Build: 0, Commit Sha: 0")]

Next install the two gulp modules, and import into your gulpfile:

npm install gulp-rename --save
npm install gulp-dotnet-assembly-info --save
var rename = require('gulp-rename');
var assemblyInfo = require('gulp-dotnet-assembly-info');

In the gulp file, read the package.json file and the environment variables. I do this once at the begining of my gulpfile and use the config all over the place.

var project = JSON.parse(fs.readFileSync("./package.json"));

var config = {
  name: project.name,
  version: project.version,
  commit: process.env.APPVEYOR_REPO_COMMIT || "0",
  buildNumber: process.env.APPVEYOR_BUILD_VERSION || "0",
}

Then add a task to create a new AssemblyVersion.cs file. Change the src parameter to match where you saved the AssemblyVersion.base file.

gulp.task('version', function() {
  return gulp
    .src(config.name + '/Properties/AssemblyVersion.base')
    .pipe(rename("AssemblyVersion.cs"))
    .pipe(assemblyInfo({
      version: config.version,
      fileVersion: config.version,
      description: "Build: " +  config.buildNumber + ", Sha: " + config.commit
    }))
    .pipe(gulp.dest('./' + config.name + '/Properties'));
});

Don't forget to reference the AssemblyVersion.cs file in your csproj!

You can see a full gulpfile with this in here: Magistrate gulpfile.

code, net, gulp

---

Posting PlainText to Asp WebApi

21 Sep 2015

Recently I have been writing a WebApi project which needs to accept plaintext via the body of a PUT request, and did the logical thing of using the FromBodyAttribute

public HttpStatusCode PutKv([FromBody]string content, string keyGreedy)
{
  return HttpStatusCode.OK;
}

Which didn't work, with the useful error message of "Unsupported media type."

It turns out that to bind a value type with the FromBody attribute, you have to prefix the body of your request with an =. As I am emulating another Api's interface, this is not an option, so I set about figuring out how to override this requirement.

In the end I discovered that providing a new MediaTypeFormatter which handles plaintext is the answer:

public class PlainTextMediaTypeFormatter : MediaTypeFormatter
{
  public PlainTextMediaTypeFormatter()
  {
    SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
  }

  public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
  {
    var source = new TaskCompletionSource<object>();

    try
    {
      using (var memoryStream = new MemoryStream())
      {
        readStream.CopyTo(memoryStream);
        var text = Encoding.UTF8.GetString(memoryStream.ToArray());
        source.SetResult(text);
      }
    }
    catch (Exception e)
    {
      source.SetException(e);
    }

    return source.Task;
  }

  public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, System.Net.TransportContext transportContext, System.Threading.CancellationToken cancellationToken)
  {
    var bytes = Encoding.UTF8.GetBytes(value.ToString());
    return writeStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken);
  }

  public override bool CanReadType(Type type)
  {
    return type == typeof(string);
  }

  public override bool CanWriteType(Type type)
  {
    return type == typeof(string);
  }
}

This can then be added to the config.Formatters collection:

public static class WebApiConfig
{
  public static void Register(HttpConfiguration http)
  {
    http.Formatters.Add(new PlainTextMediaTypeFormatter());
  }
}

It really seems like something which should be supplied out of the box with WebApi to me, but at least it wasn't as complicated to implement as I was expecting it to be :)

code, net, webapi

---

Running pre-compiled microservices in Docker with Mono

15 Sep 2015

Last time we went through creating a Dockerfile for a microservice, with the service being compiled on creation of the container image, using xbuild.

However we might not want to compile the application to create the container image, and use an existing version (e.g. one created by a build server.)

Our original Dockerfile was this:

FROM mono:3.10-onbuild
RUN apt-get update && apt-get install mono-4.0-service -y

CMD [ "mono-service",  "./MicroServiceDemo.exe", "--no-daemon" ]
EXPOSE 12345

We only need to make a few modifications to use a pre-compiled application:

FROM mono:3.10.0
RUN apt-get update && apt-get install mono-4.0-service -y

RUN mkdir -p /usr/src/app
COPY . /usr/src/app
WORKDIR /usr/src/app

CMD [ "mono-service",  "./MicroServiceDemo.exe", "--no-daemon" ]
EXPOSE 12345

Asides from changing the base image to mono:3.10.0, the only changes made are to add the following lines:

RUN mkdir -p /usr/src/app
COPY . /usr/src/app
WORKDIR /usr/src/app

These lines create a new directory for our application, copy the contents of the current directory (e.g. the paths specified when you type docker build -t servicedemo .) and make the directory our working directory.

You can now create a container with the same commands as last time:

docker build -t servicedemo .
docker run -d -p 12345:12345 --name demo servicedemo

There is a demo project for all of this on my github: DockerMonoDemo.

design, code, microservices, docker, mono

---

Running microservices in Docker with Mono

05 Sep 2015

Getting a service running under Docker is fairly straight forward once you have all the working parts together. I have an app written (following my guide on service and console in one), which uses Owin to serve a web page as a demo:

install-package Microsoft.Owin.SelfHost
public partial class Service : ServiceBase
{
  //see the service console post for the rest of this

    protected override void OnStart(string[] args)
    {
        _app = WebApp.Start("http://*:12345", app =>
        {
            app.UseWelcomePage("/");
        });
    }

    protected override void OnStop()
    {
        _app.Dispose();
    }
}

To run this under docker/mono we just need to add a Dockerfile to the root directory of the solution, which is based off the documentation here.

Using mono-service instead of mono to run the application caused me a number of headaches to start with, as the container was exiting instantly. This is because Docker detects the process has exited, and stops the container. As we will be running the container detached from the console, we just need to supply the --no-daemon argument to mono-service.

FROM mono:3.10-onbuild
RUN apt-get update && apt-get install mono-4.0-service -y
CMD [ "mono-service",  "./MicroServiceDemo.exe", "--no-daemon" ]
EXPOSE 12345

You can then go to your solution directory, and run the following two commands to create your image, and start a container of it:

docker build -t servicedemo .
docker run -d -p 12345:12345 --name demo servicedemo

You can now open your browser and go to your Docker host's IP:12345 and see the Owin welcome page.

Improvements: Speed and lack of internet

Quite often I have no internet access, so having to apt-get install mono-4.0-service each time I build the image can be a pain. This however is also very easily resolved: by making another image with the package already installed.

Create a new directory (outside of your project directory), and create a Dockerfile. This Dockerfile is identical to the mono:3.10-onbuild image, but with the added apt-get line.

FROM mono:3.10.0

MAINTAINER Jo Shields <jo.shields@xamarin.com>

RUN apt-get update && apt-get install mono-4.0-service -y

RUN mkdir -p /usr/src/app/source /usr/src/app/build
WORKDIR /usr/src/app/source

ONBUILD COPY . /usr/src/app/source
ONBUILD RUN nuget restore -NonInteractive
ONBUILD RUN xbuild /property:Configuration=Release /property:OutDir=/usr/src/app/build/
ONBUILD WORKDIR /usr/src/app/build

Now run the build command to make your new base image:

docker build -t mono-service-onbuild .

Now you can go back to your project and update the Dockerfile to use this image base instead:

FROM mono-service-onbuild
CMD [ "mono-service",  "./MicroServiceDemo.exe", "--no-daemon" ]
EXPOSE 12345

Now when you run docker build -t <project name> . it will only need to do the compile steps.

Much faster :)

design, code, microservices, docker, mono

---

A single project Windows Service and Console

30 Aug 2015

I have found that when developing MicroServices, I often want to run them from within Visual Studio, or just as a console application, and not have to bother with the hassle of installing as windows services.

In the past I have seen this achieved by creating a Class Library project with all the actual implementation inside it, and then both a Console Application and Windows Service project referencing the library and doing nothing other than calling a .Start() method or similar.

While this works, it has always bugged me as there should be a straight forward way of achieving a single exe to do both roles. It turns out there is an easy way to do it too...

Creating the Project

First, create a WindowsService project in VisualStudio: New Windows Service

Then open the project properties, and change the project type to Console Application and set the startup object: Service Type

Next, open Service1.cs and add a new method (and rename it to Service if you feel the need!):

public void StartConsole()
{
    Console.WriteLine("Press any key to exit...");
    OnStart(new string[] { });

    Console.ReadKey();
    OnStop();
}

Finally open Program.cs and replace the Main method:

static void Main()
{
    var service = new Service();

    if (Environment.UserInteractive)
    {
        service.StartConsole();
    }
    else
    {
        ServiceBase.Run(new ServiceBase[] { service });
    }
}

Displaying Output

Calling Console.Write* and Console.Read* methods when running as a windows service will cause exceptions to be thrown, which suggest that you should redirect the console streams to use them under a windows service.

As a MicroService you shouldn't need to be reading keys from the console (other than the one in our StartConsole method), but writing output would be useful...

To do this I like to use my logging library of choice (Serilog), which I have setup to write to files and to a console:

private void InitializeLogging()
{
    var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
    var logs = Path.Combine(baseDirectory, "logs");

    Directory.CreateDirectory(logs);

    Log.Logger = new LoggerConfiguration()
        .MinimumLevel.Debug()
        .WriteTo.ColoredConsole()
        .WriteTo.RollingFile(Path.Combine(logs, "{Date}.log"))
        .CreateLogger();
}

And call this method inside the Service1 constructor:

public Service()
{
    InitializeComponent();
    InitializeLogging();
}

The Edge Case

There is one slight edge case which I am aware of, which is that the Environment.UserInteractive property can return true even when running as a windows service if when you install the service you tick Allow service to interact with desktop checkbox:

Service-Logon

My only solution to this is: Don't tick that box. I don't think I have ever used that option anyway!

Wrapping Up

Using this method means less code and projects to maintain, and a very easy path to go from running a service as a desktop application to service.

design, code, overseer, microservices, console, cli

---