ZeroSharp

Robert Anderson's ones and zeros

ELMAH With DevExpress XAF

| Comments

ELMAH (Error Logging Modules and Handlers) is an open source library for logging unhandled exceptions. This post explains how to get it running with the DevExpress XAF main demo.

A couple of amazing facts about ELMAH.

  • It has been around since 2004!
  • It was written by Atif Aziz who happens to be an old school-friend from the International School of Geneva.

XAF provides quite extensive error handling options out of the box, but I have found Elmah better suited to production environments because of the ability to remotely view the full error log.

Setting up

First, get the ELMAH package via NuGet into the MainDemo.Web project. ELMAH provides dozens of different methods of persisting the error log. For this example we’ll choose one of the simplest. Make sure you select the ELMAH on XML Log package.

NuGet makes several automatic modifications to the web.config. Unfortunately, these are not quite accurate enough for XAF. The changes you need to make are detailed below:

Add a <configSection> for ELMAH as alongside the existing devExpress one.

web.config
1
2
3
4
5
6
7
8
9
  <configSections>
    <sectionGroup name="devExpress">...</sectionGroup> <!-- this should already exist-->
    <sectionGroup name="elmah"> <!-- this is new-->
      <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah" />
      <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
      <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
      <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah" />
     </sectionGroup>
  </configSections>

Your <system.webServer> section should look like this:

web.config
1
2
3
4
5
6
7
8
9
10
  <system.webServer>
    <handlers>...</handlers> <!-- This is unchanged -->
    <validation validateIntegratedModeConfiguration="false" />
    <modules>
      <add name="ASPxHttpHandlerModule" type="DevExpress.Web.ASPxClasses.ASPxHttpHandlerModule, DevExpress.Web.v14.1, Version=14.1.7.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" />
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler" />
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" preCondition="managedHandler" />
    </modules>
  </system.webServer>

Add a <location> for the path elmah.axd (alongside the existing <location> tags).

web.config
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  <location path="elmah.axd" inheritInChildApplications="false">
    <system.web>
      <httpHandlers>
        <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
      </httpHandlers>
      <!-- 
        See http://code.google.com/p/elmah/wiki/SecuringErrorLogPages for 
        more information on using ASP.NET authorization securing ELMAH.

      <authorization>
        <allow roles="admin" />
        <deny users="*" />  
      </authorization>
      -->
    </system.web>
    <system.webServer>
      <handlers>
        <add name="ELMAH" verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" preCondition="integratedMode" />
      </handlers>
    </system.webServer>
  </location>

Add a new <elmah> section. I put mine just before the final </configuration> tag.

web.config
1
2
3
4
5
6
7
8
  <elmah>
    <errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="~/App_Data/Elmah.Errors" />
    <!--
        See http://code.google.com/p/elmah/wiki/SecuringErrorLogPages for 
        more information on remote access and securing ELMAH.
    -->
    <security allowRemoteAccess="false" />
  </elmah>

Now modify HttpModules.Web.Config to look like this:

HttpModules.Web.Config
1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<httpModules>
  <add name="ASPxHttpHandlerModule" type="DevExpress.Web.ASPxClasses.ASPxHttpHandlerModule, DevExpress.Web.v14.1, Version=14.1.7.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" />
  <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" />
  <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
  <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
</httpModules>

Now we need to extend XAF’s standard error handling. Create a new class in the web application.

1
2
3
4
5
6
7
8
9
10
public class ElmahErrorHandling : ErrorHandling
{
    protected override void LogException(ErrorInfo errorInfo)
    {
        base.LogException(errorInfo);

        if (errorInfo.Exception != null)
            Elmah.ErrorSignal.FromCurrentContext().Raise(errorInfo.Exception);
    }
}

And then modify Global.asax.cs to instantiate the new class

1
2
3
4
5
6
7
    protected void Application_Start(object sender, EventArgs e) {
        ErrorHandling.Instance = new ElmahErrorHandling(); // <---this line is new
        ASPxWebControl.CallbackError += new EventHandler(Application_Error);
#if DEBUG
        TestScriptsManager.EasyTestEnabled = true;
#endif
    }

The complete files are available with the source code.

Now run the application and trigger an unhandled exception. Change the URL to something that does not exist. Or open any detail view and modify the URL so that the Guid in the ShortcutObjectKey is invalid (replace a digit with an ‘X’). Then the application error page appears.

Then return to the application and change the URL to Elmah.axd. You are looking at the log of all unhandled exceptions.

And for each exception, you can view the full details of any logged exception including coloured stack trace and full server variables.

ELMAH options

By default, ELMAH is configured to disallow remote access to the error logs - only a local user can get to elmah.axd. If you take care of the security implications it can be very useful to enable remote access and monitor the logs on your production servers.

We chose to use an XML file for each error but ELMAH is entirely pluggable. There are dozens of alternatives for persisting the error log including Sql Server, an RSS feeds, to Twitter, even to an iPhone app. There are even third party sites such as elmah.io who will host your error logs for you.

One of the advantages of using XML files is that the files can be copied to another machine. If you look in MainDemo.Web\App_Data\Elmah.Errors, you will find the resulting xml files.

You can just copy these files to another installation’s Elmah.Errors folder and the log will show up when you visit Elmah.axd.

One final note. ELMAH was developed for ASP.NET applications and web services, but it is possible to get it to work with other types of applications such as Windows Forms, Windows Service or console applications. Check out this StackOverflow question.

The source code for this example is on GitHub.

Replacing a Class at Runtime Using Ninject and Roslyn - Part 4: Roslyn

| Comments

This is the fourth and final part of a series about using Roslyn with dependency injection to create a flexible and powerful plug-in framework. Here I review the parts of the solution that deal with the Roslyn runtime compilation of plug-ins. Check out the working example on GitHub.

Previously

Roslyn

Let’s look at some of the main classes used to compile plug-in code at runtime.

The PluginSnippetCompiler.Compile() method takes a string (for instance, the contents of an uploaded raw C# file) and converts it into an in-memory assembly with the same assembly references as the main project.

The Roslyn compiler is still in beta, and the Microsoft team have recently removed some syntactic sugar which made the code in the Compile() routine look cleaner. Hopefully they will include something similar soon. The code below works with version 0.7.0.0.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public class PluginSnippetCompiler
{
    public PluginSnippetCompiler(IAssemblyReferenceCollector assemblyReferenceCollector)
    {
        if (assemblyReferenceCollector == null)
            throw new ArgumentNullException("assemblyReferenceCollector");

        _AssemblyReferenceCollector = assemblyReferenceCollector;
    }

    private readonly IAssemblyReferenceCollector _AssemblyReferenceCollector;

    private IEnumerable<Diagnostic> _Diagnostics = Enumerable.Empty<Diagnostic>();

    public IEnumerable<Diagnostic> Errors
    {
        get
        {
            return _Diagnostics
                .Where(d => d.Severity == DiagnosticSeverity.Error);
        }
    }

    public IEnumerable<Diagnostic> Warnings
    {
        get
        {
            return _Diagnostics
                .Where(d => d.Severity == DiagnosticSeverity.Warning);
        }
    }

    private string GetOutputAssemblyName(string name)
    {
        return String.Format("RoslynPlugins.Snippets.{0}", name);
    }

    /// <summary>
    /// Compiles source code at runtime into an assembly. The assembly will automatically include all
    /// the same assembly references as the main RoslynPlugins assembly, so you can call any function which is
    /// available from within the deployed RoslynPlugins. Compilation errors and warnings can be obtained from 
    /// the Errors and Warnings properties.
    /// </summary>
    /// <param name="name">The name of the class, e.g., HelloWorldGenerator</param>
    /// <param name="script">Source code such as the contents of HelloWorldGenerator.cs</param>
    /// <returns>The compiled assembly in memory. If there were errors, it will return null.</returns>
    public Assembly Compile(string name, string script)
    {
        if (name == null)
            throw new ArgumentNullException("name");

        if (script == null)
            throw new ArgumentNullException("script");

        string outputAssemblyName = GetOutputAssemblyName(name);

        var defaultImplementationAssembly = typeof(HelloWorldGenerator).Assembly;
        var assemblyReferences = _AssemblyReferenceCollector.CollectMetadataReferences(defaultImplementationAssembly);

        // Parse the script to a SyntaxTree
        var syntaxTree = CSharpSyntaxTree.ParseText(script);

        // Compile the SyntaxTree to an in memory assembly
        var compilation = CSharpCompilation.Create(outputAssemblyName,
            new[] { syntaxTree },
            assemblyReferences,
            new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

        using (var outputStream = new MemoryStream())
        {
            using (var pdbStream = new MemoryStream())
            {
                // Emit assembly to streams. Throw an exception if there are any compilation errors
                var result = compilation.Emit(outputStream, pdbStream: pdbStream);

                // Populate the _diagnostics property in order to read Errors and Warnings
                _Diagnostics = result.Diagnostics;

                if (result.Success)
                {
                    return Assembly.Load(outputStream.ToArray(), pdbStream.ToArray());
                }
                else
                {
                    return null;
                }
            }
        }
    }
}

In this demo, I have not included any user feedback about compilation errors, but they are easily obtainable from the Errors and Warnings properties. At present, if there is an error, the plug-in will be ignored and the original implementation will be used.

The class above depends on an AssemblyReferenceCollector which is responsible for enumerating the references to add to the runtime-generated plug-in assembly. We want exactly the same assembly references as the assembly which contains the original implementation so that we can reference any dependencies within those references.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class AssemblyReferenceCollector : IAssemblyReferenceCollector
{
    public IEnumerable<MetadataReference> CollectMetadataReferences(Assembly assembly)
    {
        var referencedAssemblyNames = assembly.GetReferencedAssemblies();

        var references = new List<MetadataReference>();
        foreach (AssemblyName assemblyName in referencedAssemblyNames)
        {
            var loadedAssembly = Assembly.Load(assemblyName);
            references
                .Add(new MetadataFileReference(loadedAssembly.Location));
        }

        references
            .Add(new MetadataFileReference(assembly.Location)); // add a reference to 'self', i.e., NetMWC

        return references;
    }
}

Connecting the pieces

We need the PluginLocator class to connect the Ninject resolution root to the runtime-generated assembly (if one exists). It just looks for classes with the correct interface IGenerator within the PluginAssemblyCache.

Here’s how it looks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class PluginLocator
{
    public PluginLocator(PluginAssemblyCache pluginAssemblyCache)
    {
        if (pluginAssemblyCache == null)
            throw new ArgumentNullException("pluginAssemblyCache");

        _PluginAssemblyCache = pluginAssemblyCache;
    }

    private readonly PluginAssemblyCache _PluginAssemblyCache;

    public Type Locate<T>()
    {
        return Locate(new[] { typeof(T) });
    }

    protected Type Locate(IEnumerable<Type> serviceTypes)
    {
        var implementingClasses = AssemblyExplorer.GetImplementingClasses(_PluginAssemblyCache.GetAssemblies(), serviceTypes);

        if (implementingClasses.Any())
        {
            if (implementingClasses.Count() > 1)
                throw new Exception("More than one plugin class found which implements " + String.Join(" + ", serviceTypes.Select(t => t.ToString())));
            else
                return implementingClasses.Single();
        }
        return null;
    }
}

The PluginAssemblyCache avoids having to run the Compile() routine more than once by maintaining a dictionary of previously compiled plug-ins. It has the following dependencies:

  • an IPluginSnippetProvider which (in this case) reads the existing snippets from the database (not shown here)
  • a PluginLoader which uses the above PluginSnippetCompiler to convert a snippet into a runtime assembly.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/// <summary>
/// This class maintains a list of runtime-compiled in memory assemblies loaded from the plugins
/// available via the provider. It is a singleton class.
/// </summary>
public class PluginAssemblyCache
{
    public PluginAssemblyCache(IPluginSnippetProvider pluginSnippetProvider, PluginLoader pluginLoader)
    {
        if (pluginSnippetProvider == null)
            throw new ArgumentNullException("pluginSnippetProvider");
        _PluginSnippetProvider = pluginSnippetProvider;

        if (pluginLoader == null)
            throw new ArgumentNullException("pluginLoader");
        _PluginLoader = pluginLoader;
    }

    private class CacheEntry
    {
        public string Name { get; set; }
        public Version Version { get; set; }
        public Assembly Assembly { get; set; }
    }

    private readonly IPluginSnippetProvider _PluginSnippetProvider;
    private readonly PluginLoader _PluginLoader;

    private List<CacheEntry> _Cache = new List<CacheEntry>();

    private void Add(string name, string version, Assembly assembly)
    {
        var cacheEntry =
            new CacheEntry()
            {
                Name = name,
                Version = new Version(version),
                Assembly = assembly
            };
        _Cache.Add(cacheEntry);
    }

    private void RefreshCache()
    {
        var pluginScriptContainers = _PluginSnippetProvider.GetPlugins();

        // Add a new assembly for any new or updated plugin
        foreach (var pluginScriptContainer in pluginScriptContainers)
        {
            var name = pluginScriptContainer.Name;
            var version = pluginScriptContainer.Version;
            if (!_Cache.Any(a => a.Name == name && a.Version == new Version(version)))
            {
                var assembly = _PluginLoader.Load(pluginScriptContainer);
                Add(name, version, assembly);
            }
        }

        // Remove any assemblies which we no longer have a plugin for.
        _Cache
            .RemoveAll(cacheEntry =>
                !pluginScriptContainers
                    .Select(plugin => plugin.Name)
                    .Contains(cacheEntry.Name));
    }

    public IEnumerable<Assembly> GetAssemblies()
    {
        RefreshCache();

        // Return only the assemblies with the highest version numbers
        return _Cache
            .GroupBy(d => d.Name)
            .Select(g => g
                    .OrderByDescending(d => d.Version)
                    .First()
                    .Assembly);
    }
}

So whenever the SomeGenerator class is resolved by Ninject, it will now

  • Check whether there are any new plug-ins and compile them into runtime assemblies and add them to the PluginAssemblyCache.
  • Then the PluginLocator will search these assemblies for a newer version of SomeGenerator.
  • If it finds one, it will be resolved along with any constructor dependencies, otherwise it will use the original SomeGenerator.

Version numbers

The version number of the plug-in is a key part of our solution. Let’s say you have version 1.0 in production. Then you fix some bugs in staging (version 1.1). You create a plug-in from this staging code and upload it into production. Then much later, you decide to upgrade production to 1.2. Then, with the query in GetAssemblies(), the 1.1 plug-in will automatically be ignored and be superseded by whatever was shipped with 1.2 since that is newer code. So we do not have to remember to remove obsolete plug-ins after an upgrade - they will automatically be ignored because of the version number.

Security

Obviously, security is a chief concern and you may have to secure the plug-ins. In this demo project, I just created a simple view for the IPlugin object, but in our production environment we handle the creation of plug-ins differently. We use a combination of role-based security (to control who has permission to upload plugins) and encryption with checksumming. No user can directly enter arbitrary code - instead, we send the user a zip file which contains the code (encrypted), the version number and a checksum and our application verifies the checksum and builds the IPlugin object from the contents of the zip. A Powershell script running on our build server is responsible for creating the checksummed plug-in directly from the source code used in our staging environment.

Conclusions - the ultimate plug-in framework?

The strength of the Roslyn approach is that it is easy to maintain while being extremely versatile. In our case, it provides us with the ability to restrict the number of major releases approximately one per annum while catering for the inevitable little fixes to output formats and reports.

In the example we replaced an existing class, but it would be straightforward to add the concept of discovery and use the same Roslyn features to make any new plug-in classes available to your application. Ninject, makes it easy to instantiate, say, every implementor of IGenerator, so you could enumerate all available plug-ins instead of replacing a single one.

So here’s a basic plug-in framework which is very flexible and very powerful without many of the versioning headaches of MEF or MAF. It’s also easy to maintain, since the plug-in code is identical to the ‘normal’ code in staging (just packaged, delivered and compiled in a different way to production).

Replacing a Class at Runtime Using Ninject and Roslyn - Part 3: Dependency Injection

| Comments

This is the third part of a series about using Roslyn with dependency injection to create a flexible and powerful plug-in framework. Here I review the parts of the solution that deal with dependency injection. Check out the working example on GitHub.

Previously

Dependency injection

The first trick is to use dependency injection to create any instance of the HelloWorldGenerator class. Then if we need to add a new dependency to the class, we can just add it to the constructor without breaking anything.

HelloWorldGenerator.cs
1
2
3
4
5
6
7
8
9
public class HelloWorldGenerator : IGenerator
{
    public HelloWorldGenerator(
        ISomeDependency dependency,
        IAnotherDependency another,
        INewDependency new // a new dependency!!!)
    {
        ...
    }

We’ll use Ninject here, but you ought to be able to achieve the same with any dependency injection framework.

So normally, we’d have a binding something like:

1
Bind<IGenerator>().To<HelloWorldGenerator>();

Instead we’ll replace this with a binding to a factory method instead.

1
Bind<IGenerator>().ToMethod(context => CreatePluginInstance(context));

The CreatePluginInstance(context) method will try to find an IGenerator class within any available plug-ins. If it finds one, it will ask the Ninject framework to create an instance of the plug-in class. Otherwise it falls back to the default type (the original implementation of the generator). The PluginLocator it is responsible for searching any runtime-compiled assemblies for candidate plug-ins. We’ll look at it in more detail later.

1
2
3
4
5
6
7
8
9
10
11
private IGenerator CreatePluginInstance(IContext context)
{
    var pluginLocator = context.Kernel.Get<PluginLocator>();
    Type roslynPluginType = pluginLocator.Locate<IGenerator>();

    /// if we found a plug-in, create an instance of it
    if (roslynPluginType != null)
        return (IGenerator)context.Kernel.Get(roslynPluginType);
    else ///otherwise create an instance of the original implementation
        return context.Kernel.Get<HelloWorldGenerator>();
}

By convention

Of course, you might have dozens of IGenerator descendants, in which case you can use Ninject’s convention-based binding module. (Don’t forget to add it with NuGet). My version looks something like the following.

1
2
3
4
5
6
7
8
9
10
11
/// If you have a lot of IGenerator subclasses, you can use Ninject's
/// convention based module.
/// 
///   For each Generator, bind to IGenerator. 
///   For example, Bind<IGenerator>.To<SomeGenerator>();
/// 
Kernel.Bind(scanner => scanner
    .FromThisAssembly()
    .SelectAllClasses()
    .InheritedFrom(typeof(IGenerator))
    .BindToPluginOtherwiseDefaultInterfaces()); //This is a custom extension method (see below)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static class ConventionSyntaxExtensions
{
    public static IConfigureSyntax BindToPluginOtherwiseDefaultInterfaces(this IJoinFilterWhereExcludeIncludeBindSyntax syntax)
    {
        return syntax.BindWith(new DefaultInterfacesBindingGenerator(new BindableTypeSelector(), new PluginOtherwiseDefaultBindingCreator()));
    }
}

/// <summary>
/// Returns a Ninject binding to a method which returns the plug-in type if one exists, otherwise returns the default type.
/// </summary>
public class PluginOtherwiseDefaultBindingCreator : IBindingCreator
{
    public IEnumerable<IBindingWhenInNamedWithOrOnSyntax<object>> CreateBindings(IBindingRoot bindingRoot, IEnumerable<Type> serviceTypes, Type implementationType)
    {
        if (bindingRoot == null)
        {
            throw new ArgumentNullException("bindingRoot");
        }

        return !serviceTypes.Any()
         ? Enumerable.Empty<IBindingWhenInNamedWithOrOnSyntax<object>>()
         : new[] { bindingRoot.Bind(serviceTypes.ToArray()).ToMethod(context => context.Kernel.Get(context.Kernel.Get<PluginLocator>().Locate(serviceTypes) ?? implementationType)) };
    }
}

Next we’ll look at the Roslyn part in more detail.

Replacing a Class at Runtime Using Ninject and Roslyn - Part 2: The Solution

| Comments

Previously: Part 1: The Goal

The solution

The code for the example is available on GitHub.

How it looks

So here’s the Hello World page in production:

.

We navigate to the plugins view and create a new replacement for the HelloWorldGenerator:

.

Without restarting, we can return to the HelloWorld page and see that the new class is being used because the output has changed.

.

If you delete the row from the plugins page, the behaviour reverts to the original implementation (the code that was originally shipped with production).

Basic project setup

First, I created a new ASP.NET MVC 5 application. I added a HelloWorldContrroller and a View. I added a Plugin model and corresponding views. To get started I followed the tutorial here (http://www.asp.net/mvc/tutorials/mvc-5/introduction/getting-started). Once I had the basics in place, I added the following NuGet packages.

Stable

  • EntityFramework
  • Ninject
  • Ninject.MVC5
  • Ninject.Conventions

Pre-release

  • Microsoft.CodeAnalysis.CSharp

The Microsoft.CodeAnalysis.CSharp is the ‘Roslyn’ package. It is still in beta, so you have to switch to the pre-release.

Next we’ll look at the dependency injection part in more detail.

Replacing a Class at Runtime Using Ninject and Roslyn - Part 1: The Goal

| Comments

The goal

How can we replace a given class’s code with new code at runtime? In particular, how we can we do this while allowing dependency injection and sidestepping assembly versioning issues.

Let’s say you have bunch of classes like this:

SomeGenerator.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
public class SomeGenerator : IGenerator
{
    public SomeGenerator(ISomeDependency dependency, IAnotherDependency another)
    {
        ...
    }

    public void Generate()
    {
        ...
        // generate some output
    }
}

Now let’s assume that you need the ability to modify the behaviour of these classes at runtime without upgrading. And change the dependencies. Without restarting the application.

Old school - The MEF approach (and most other plug-in frameworks)

One approach would be to place each generator in a separate assembly and then you could load them at runtime. (This was my first effort - oh how I struggled).

You can make use of something like MEF to help with the grunt work, but can still be very complex.

One difficulty is the dependencies. The dependencies are often defined in other assemblies and you have to be very careful to avoid ‘dll hell’. It is very easy to get message like:

Could not load file or assembly 'SomeAssembly, Version=1.2.9.1, Culture=neutral, PublicKeyToken=ad2d246d2bace800' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference.

Or even exceptions like

Object (of type 'SomeGenerator') is not of type 'SomeGenerator'.

You either have to write your plug-in code so that it is totally independent (i.e., has no dependencies), or you need to resort to a heap of <bindingRedirect> tags in your web.config.

Also, with one assembly per format, you can end up with a huge proliferation of assemblies. If you have 50 different formats, that would be 50 assemblies.

New school - The Rosyln approach

An alternative is to use the compiler-as-a-service features of Roslyn.

Can we upload a modified SomeGenerator.cs and get it to reference the deployed assemblies and thereby avoid dll hell? With Roslyn we can do this.

If the compilation fails, we can immediately inform the user that the file is not compatible. If it succeeds, we can use it in lieu of the version that was originally deployed.

Also, you do not need separate assemblies for the plug-ins. Your production code contains, within it somewhere a class named SomeGenerator. At runtime, we are going to create an in-memory assembly which contains only a single class (still named SomeGenerator), but which can nevertheless reference any other class available to the original implementation. Then we will get the dependency injection container to ‘replace’ the old generator with the new one.

The plan

  • Build an ASP.NET MVC 5 web application. It will use an instance of HelloWorldGenerator to generate some output. (This is the original implementation).
  • Allow a replacement for the HelloWorldGenerator class to be uploaded into the application as raw C# code. (This is the plug-in implementation.)
  • Store the C# code in a database. If the application is restarted, the plug-in code will be reloaded.
  • When the output is next requested, compile the new C# class. Any dependencies will be instantiated by the IoC container. If there are any compilation errors, these will be displayed.
  • Show that the plug-in class is now being used and the output has changed. The originally shipped HelloWorldGenerator class has been replaced by our plug-in.
  • Delete the plug-in from the table and show the output has reverted to the default (the originally implementation code).

Over next few posts I’ll guide you through building the application and demonstrate the runtime replacement of the generator class.

See Part 2 for screen shots of the working application and an overview of the basic project set up.

Persisting Changes to Config Files Within NuGet Packages

| Comments

Whenever NuGet updates or restores a NuGet package, the config files within it are overwritten. Here’s a method to make sure the changes are reapplied via a config transform whenever the solution is built.

I’m using the NUnit.Runners NuGet packages. To get our coverage tool to play nicely, I need to replace <supportedRuntime "v2.0.50727"> with <supportedRuntime "v4.0.30319"> within the NUnit-console-x86.exe.config.

Normally, a config transform is for modifying the web.config or app.config files. Here, we need to modify a config file within the packages subdirectory.

In my .csproj file, I have added the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  <PropertyGroup>
   <NUnitRunnerDir>$(SolutionDir)packages\NUnit.Runners.2.6.3\tools\</NUnitRunnerDir>
  <PropertyGroup>

  <!-- Default NUnit test runner requires a modification to the config file-->
  <UsingTask
    TaskName="TransformXml"
    AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0\Web\Microsoft.Web.Publishing.Tasks.dll" />

  <Target Name="AfterBuild" Condition="exists('$(NUnitRunnerDir)NUnit-console-x86.exe.config')">
    <TransformXml
        Source="$(NUnitRunnerDir)NUnit-console-x86.exe.config"
        Destination="$(NUnitRunnerDir)NUnit-console-x86.exe.config"
        Transform="$(SolutionDir)UnitTests\Transforms\NUnit-console-x86.exe.CLR4.config" />
  </Target>

And the transform file itself looks like this:

NUnit-console-x86.exe.CLR4.config
1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <startup>
    <supportedRuntime version="v4.0.30319" xdt:Transform="Replace" />
  </startup>
</configuration>

By the way, Web.config Transformation Tester is a handy tool!

Now whenever I build the project, the AfterBuild event ensures the supportedRuntime version is set correctly.

Pardon the Interruption

| Comments

I left my laptop on a train a couple of months ago. Good thing I remembered my brain. I’m back now. This blog still lives.

A Web UI Performance Tip for XAF Web Applications

| Comments

The purpose of this post is to raise your awareness of a toggle which exists in the DevExpress XAF framework which can significantly improve UI performance in the web application.

The biggest XAF project I work with has one very complex business object. The layout for this screen includes about 100 properties, several nested tabs, some custom editors, several collection properties and a whole lot of Conditional Appearance rules. It was very sluggish to navigate - it was taking several seconds to load the detail view and then it was very slow switching between tabs. Switching to edit mode was also slow.

Last week, I almost accidentally changed the value of DelayedViewItemsInitialization to false and noticed that the UI speed was much much better. In fact the general responsiveness of the entire web-application seems much better.

In order to give it a whirl, navigate to the WebApplication.cs file (normally in the ApplicationCode subfolder of your web project) and modify the constructor as follows:

1
2
3
4
public MainDemoWebApplication() {
    InitializeComponent();
    this.DelayedViewItemsInitialization = false;
}

Certainly this is not without consequences, and I would urge a careful reading of the relevant documentation. To be honest, I still don’t really understand why my detail view is so much slower without this change. I have tried to isolate the cause without much success and I will update this post if I find anything new. But if some of your detail views seem overly slow, certainly try it out.

Provisioning a New Development Machine With BoxStarter

| Comments

I’ve been playing around with Boxstarter to configure my entire development environment with hardly any user intervention.

Here are the steps:

  1. Install Windows 8.1 on a new machine.
  2. Login.
  3. Open a command prompt and enter the following.
1
START http://boxstarter.org/package/nr/url?http://bit.ly/1kapDXI

That’s it!

Boxstarter will self-install via ClickOnce, asking for various confirmations and ultimately it will prompt you for your login password. (This gets saved and encrypted to allow for unattended reboots and re-logins during the installation). Then the real magic begins. Boxstarter downloads and installs all your tools and configures your environment, rebooting as necessary. An hour later your full development setup is installed, including Visual Studio 2013, any VS extensions, any other programs and tools, all the browsers you need, all critical Windows updates, etc. You just saved yourself a couple of days of work and a lot of hassle.

How does Boxstarter know what to install? There’s a Powershell script located at that bitly address. Let’s take a look at the script.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# Boxstarter options
$Boxstarter.RebootOk=$true # Allow reboots?
$Boxstarter.NoPassword=$false # Is this a machine with no login password?
$Boxstarter.AutoLogin=$true # Save my password securely and auto-login after a reboot

# Basic setup
Update-ExecutionPolicy Unrestricted
Set-ExplorerOptions -showHidenFilesFoldersDrives -showProtectedOSFiles -showFileExtensions
Enable-RemoteDesktop
Disable-InternetExplorerESC
Disable-UAC
Set-TaskbarSmall

if (Test-PendingReboot) { Invoke-Reboot }

# Update Windows and reboot if necessary
Install-WindowsUpdate -AcceptEula
if (Test-PendingReboot) { Invoke-Reboot }

# Install Visual Studio 2013 Professional 
cinstm VisualStudio2013Professional -InstallArguments WebTools
if (Test-PendingReboot) { Invoke-Reboot }

# Visual Studio SDK required for PoshTools extension
cinstm VS2013SDK
if (Test-PendingReboot) { Invoke-Reboot }

cinstm DotNet3.5 # Not automatically installed with VS 2013. Includes .NET 2.0. Uses Windows Features to install.
if (Test-PendingReboot) { Invoke-Reboot }

# VS extensions
Install-ChocolateyVsixPackage PowerShellTools http://visualstudiogallery.msdn.microsoft.com/c9eb3ba8-0c59-4944-9a62-6eee37294597/file/112013/6/PowerShellTools.vsix
Install-ChocolateyVsixPackage WebEssentials2013 http://visualstudiogallery.msdn.microsoft.com/56633663-6799-41d7-9df7-0f2a504ca361/file/105627/31/WebEssentials2013.vsix
Install-ChocolateyVsixPackage T4Toolbox http://visualstudiogallery.msdn.microsoft.com/791817a4-eb9a-4000-9c85-972cc60fd5aa/file/116854/1/T4Toolbox.12.vsix
Install-ChocolateyVsixPackage StopOnFirstBuildError http://visualstudiogallery.msdn.microsoft.com/91aaa139-5d3c-43a7-b39f-369196a84fa5/file/44205/3/StopOnFirstBuildError.vsix

# AWS Toolkit is now an MSI available here http://sdk-for-net.amazonwebservices.com/latest/AWSToolsAndSDKForNet.msi (no chocolatey package as of FEB 2014)
# Install-ChocolateyVsixPackage AwsToolkit http://visualstudiogallery.msdn.microsoft.com/175787af-a563-4306-957b-686b4ee9b497

#Other dev tools
cinstm fiddler4
cinstm beyondcompare
cinstm ProcExp #cinstm sysinternals
cinstm NugetPackageExplorer
cinstm windbg
cinstm Devbox-Clink
cinstm TortoiseHg
#cinstm VisualHG # Chocolatey package is corrupt as of Feb 2014 
cinstm linqpad4
cinstm TestDriven.Net
cinstm ncrunch2.vs2013

#Browsers
cinstm googlechrome
cinstm firefox

#Other essential tools
cinstm 7zip
cinstm adobereader
cinstm javaruntime

#cinst Microsoft-Hyper-V-All -source windowsFeatures
cinst IIS-WebServerRole -source windowsfeatures
cinst IIS-HttpCompressionDynamic -source windowsfeatures
cinst IIS-ManagementScriptingTools -source windowsfeatures
cinst IIS-WindowsAuthentication -source windowsfeatures

Install-ChocolateyPinnedTaskBarItem "$($Boxstarter.programFiles86)\Google\Chrome\Application\chrome.exe"
Install-ChocolateyPinnedTaskBarItem "$($Boxstarter.programFiles86)\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe"

Boxstarter works with Chocolatey and you can install anything with a Chocolatey package very easily. As you can see, most of the lines begin with cinstm which is a shortcut for install with Chocolatey if missing. You will notice there are also commands for configuring Windows and IIS options. There is plenty of additional information on the Boxstarter documentation.

What about DevExpress?

Want to install your registered CodeRush and DexExpress components? Easy. Since the installation packages are not available on chocolatey, you will have to put them on a network share accessible from the newly provisioned machine.

Then add the following to your boxstarter script:

1
2
3
4
5
6
7
8
9
10
11
12
13
# Set the following to the network location of the DevExpress installers
$pathToDevExpressComponentsSetup = "\\somewhere\DevExpressComponents-13.2.7.exe"
$pathToDevExpressComponentsSetup = "\\somewhere\DevExpressCodeRush-13.2.7.exe"

# Command line options for unattended installation
# (/EULA is not required for versions earlier than 10.2.10)
$silentArgs = "/Q /EMAIL:myaddress@company.com /CUSTOMERID:A1111 /PASSWORD:MYPASSWORD /DEBUG /EULA:accept"

# Install .NET Components
Install-ChocolateyInstallPackage "DevExpressComponents_13.2" "EXE" $silentArgs $pathToDevExpressComponentsSetup

# Install CodeRush
Install-ChocolateyInstallPackage "DevExpressCodeRush_13.2" "EXE" $silentArgs $pathToDevExpressCodeRushSetup

Warning! don’t put your DevExpress passwords on a public website.

There are plenty of other ways of launching Boxstarter. You can install Boxstarter via Chocolatey. You can run Boxstarter remotely. If you are putting passwords in the installation script, you should choose one of the other options.

Advantages

  • I save time!
  • I can now version control the exact development environment along with the source code!
  • Onboarding new and junior developers is much quicker.
  • In a pinch, I can use this method to quickly provision a development machine with Azure or Amazon Web Services

One Last Hiccup

While I was testing my BoxStarter script I used the Windows 8.1 image from http://www.modern.ie/. Much later in the process I realised that Modern.IE only supplies 32-bit images and the chocolatey installer for Visual Studio extensions currently works only with 64-bit Windows.

CruiseControl Notifications on Your iPhone

| Comments

CCWatcher is a great iPhone app for any developer who is using CruiseControl.NET, Jenkins or Hudson for their continuous integration.

We have used CruiseControl.NET for several years to automate all builds. The build kicks off automatically whenever we push changes and this frequently happens a few times a day. We aim to have everything green at the end of every day.

A full build with unit tests and functional tests takes about an hour, so often, I will leave the office while the build is still running. But then I wouldn’t know if I’d broken the build.

For a long while I was looking for a better way of being informed of build failures on my phone.

Enter CCWatcher. It’s a simple application which allows you to enter the address of your build server and it will tell you the status of the projects you’ve configured.

Whenever I need to check whether I’ve broken the build, I can just pull to refresh.

When I first discovered the application it had a problem with CruiseControl.NET’s remote services (which we needed because the actual build server is on a network machine not visible from the internet). I contacted SixAfter and was very impressed with the helpful response from Michael Primeaux. Within a couple of weeks there was a new version of the app with support for the remote services configuration. Hats off.