Moving to Windows 10: AutoMapper and .NET Native

Tags: Windows 8.1, Windows 10, UWP

You’re finally done moving from Windows 8/8.1 to Windows 10 and got your code compiling. Done! Not quite yet …

When you’re building your app in debug mode, you’re compiling the bits to Intermediate Language (IL) like you used to do for Windows 8/8.1 and you do with regular .NET code. However, Windows 10 introduces .NET Native and this setting is enabled by default for release builds. There’s quite a bit to be read/told on .NET Native, but in short it comes down to this:

  • Precompilation technology that links parts of the .NET Framework statically into your app.
  • Faster startup times and better overall performance.
  • Comes at the price of longer compilations (easily up to a minute for a small app).

Compiling to .NET Native sometimes gives you errors you didn’t have in debug builds. But quite often the errors will pop up when you try to run the app. And if you’re heavily using Reflection, you’re up for a challenge.

AutoMapper

For those who don’t know AutoMapper, it’s a library that performs magic to relieve your from writing mapping code to copy values between your entities, domain models, dtos, … Whenever you hear magic, you read trouble in .NET Native. Trying to run my application resulted in following error:

Exception thrown: 'System.Reflection.MissingRuntimeArtifactException' in System.Private.Reflection.Core.dll
Additional information: MakeGenericMethod() cannot create this generic method instantiation because the instantiation was not metadata-enabled:
   'System.Linq.Enumerable.SingleOrDefault<System.Char>(System.Collections.Generic.IEnumerable<System.Char>)'
For more information, please visit http://go.microsoft.com/fwlink/?LinkID=616868

Using Reflection? Check. Trying to run in .NET Native? Check. Having troubles? Double check.

The fix

There wouldn’t be many apps running on Windows 10 if you have to deal these ‘limitations’. You can tell .NET Native what types to not cut when the compilation process is trying to optimize all the dependencies by using a Runtime Directives (.rd.xml) configuration file. In this file you define the assemblies, namespaces and types that should be kept in. For AutoMapper I upgraded to version 4.1.1 and used this file:

<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
  <Application>
    <!--
      An Assembly element with Name="*Application*" applies to all assemblies in
      the application package. The asterisks are not wildcards.
    -->
    <Assembly Name="*Application*" Dynamic="Required All" />
    
    <!-- Add your application specific runtime directives here. -->

    <!-- Necessary for Automapper -->
    <Assembly Name="System.Linq">
      <Type Name="System.Linq.Enumerable" Browse="Required PublicAndInternal">
        <MethodInstantiation Name="ToArray" Arguments="System.Char" Dynamic="Required"/>
        <MethodInstantiation Name="Distinct" Arguments="System.Char" Dynamic="Required"/>
        <MethodInstantiation Name="Reverse" Arguments="System.Char" Dynamic="Required"/>
        <MethodInstantiation Name="AsEnumerable" Arguments="System.Char" Dynamic="Required"/>
        <MethodInstantiation Name="ToList" Arguments="System.Char" Dynamic="Required"/>
        <MethodInstantiation Name="DefaultIfEmpty" Arguments="System.Char" Dynamic="Required"/>
        <MethodInstantiation Name="First" Arguments="System.Char" Dynamic="Required"/>
        <MethodInstantiation Name="FirstOrDefault" Arguments="System.Char" Dynamic="Required"/>
        <MethodInstantiation Name="Last" Arguments="System.Char" Dynamic="Required"/>
        <MethodInstantiation Name="LastOrDefault" Arguments="System.Char" Dynamic="Required"/>
        <MethodInstantiation Name="Single" Arguments="System.Char" Dynamic="Required"/>
        <MethodInstantiation Name="SingleOrDefault" Arguments="System.Char" Dynamic="Required"/>
        <MethodInstantiation Name="Any" Arguments="System.Char" Dynamic="Required"/>
        <MethodInstantiation Name="Count" Arguments="System.Char" Dynamic="Required"/>
        <MethodInstantiation Name="LongCount" Arguments="System.Char" Dynamic="Required"/>
        <MethodInstantiation Name="Min" Arguments="System.Char" Dynamic="Required"/>
        <MethodInstantiation Name="Max" Arguments="System.Char" Dynamic="Required"/>
      
        <MethodInstantiation Name="ToArray" Arguments="System.Decimal" Dynamic="Required"/>
        <MethodInstantiation Name="Distinct" Arguments="System.Decimal" Dynamic="Required"/>
        <MethodInstantiation Name="Reverse" Arguments="System.Decimal" Dynamic="Required"/>
        <MethodInstantiation Name="AsEnumerable" Arguments="System.Decimal" Dynamic="Required"/>
        <MethodInstantiation Name="ToList" Arguments="System.Decimal" Dynamic="Required"/>
        <MethodInstantiation Name="DefaultIfEmpty" Arguments="System.Decimal" Dynamic="Required"/>
        <MethodInstantiation Name="First" Arguments="System.Decimal" Dynamic="Required"/>
        <MethodInstantiation Name="FirstOrDefault" Arguments="System.Decimal" Dynamic="Required"/>
        <MethodInstantiation Name="Last" Arguments="System.Decimal" Dynamic="Required"/>
        <MethodInstantiation Name="LastOrDefault" Arguments="System.Decimal" Dynamic="Required"/>
        <MethodInstantiation Name="Single" Arguments="System.Decimal" Dynamic="Required"/>
        <MethodInstantiation Name="SingleOrDefault" Arguments="System.Decimal" Dynamic="Required"/>
        <MethodInstantiation Name="Any" Arguments="System.Decimal" Dynamic="Required"/>
        <MethodInstantiation Name="Count" Arguments="System.Decimal" Dynamic="Required"/>
        <MethodInstantiation Name="LongCount" Arguments="System.Decimal" Dynamic="Required"/>
        <MethodInstantiation Name="Min" Arguments="System.Decimal" Dynamic="Required"/>
        <MethodInstantiation Name="Max" Arguments="System.Decimal" Dynamic="Required"/>
      </Type>
    </Assembly>
    
    <Assembly Name="Lex.Db">
      <TypeInstantiation Name="Lex.Db.Serialization.ListSerializers" Arguments="System.Guid" Dynamic="Required All" Activate="Required All" Browse="Required All" />
      <TypeInstantiation Name="Lex.Db.Serialization.DictSerializers" Arguments="System.String,System.Guid" Dynamic="Required All" Activate="Required All" Browse="Required All" />
    </Assembly>

  </Application>
</Directives>

Notice the double block for both Char and Decimal types, you might need to add for other basic/complex types as well.

As a bonus I added my directives for Lex.Db (version 1.2.6).

Update: AutoMapper 4.2.0 gives issues even with these directives, so I downgraded to 4.1.1 again.

Comments

comments powered by Disqus