Running an ASP.NET Core application targeting .NET Framework in Docker
Recently, I’ve came across an interesting challenge that was to dockerize an ASP.NET Core 2.2 application targeting .NET Framework 4.7.2. The reason this application was targeting .NET Framework was because it was using a library that unfortunately had no plans to move to the .NET Core platform. That library was a port from Java. As such I had to take a decision: rewrite the whole application in Java to support this library more natively, or try to find an alternative library that did the same thing. Sometimes it’s better to sleep on such decision. Well it paid off. I had forgotten that possibly I could use mono to run it.
I took this as a challenge and used one of my favorite tools to get cracking: Docker
Dockerizing the application
I started writing my Dockerfile and used the dotnet core SDK 2.2.401 as base image.
Since this image is based on Debian 9, we easily have access to the apt package manager tool. I proceeded to install mono devel (mono development) as per the documentation on the mono project. This is enough to compile the code. The latest mono version that is available is 6.4.0.198 (as of the writing of this post) so that’s the one I used.
1 2 3 4 5 6 7 8 9 10 11 |
ENV MONO_VERSION 6.4.0.198 RUN apt-get update \ && apt-get install -y apt-transport-https dirmngr gnupg ca-certificates \ && rm -rf /var/lib/apt/lists/* \ && apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF RUN echo "deb http://download.mono-project.com/repo/debian stable-stretch/snapshots/$MONO_VERSION main" > /etc/apt/sources.list.d/mono-official-stable.list \ && apt-get update \ && apt-get install -y mono-devel \ && rm -rf /var/lib/apt/lists/* |
I then proceeded to add some regular commands, in my Dockerfile, to build my ASP.NET Core application:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
WORKDIR /app # copy csproj and restore as distinct layers COPY ./src/MyProject/*.csproj ./MyProject/ WORKDIR /app/MyProject RUN dotnet restore # copy and publish app and libraries WORKDIR /app COPY ./src/MyProject/. ./MyProject/ WORKDIR /app/MyProject RUN dotnet publish -c Release -o out --no-restore |
Hold and behold, when it was trying to build the application, I got the following error:
error MSB3644: The reference assemblies for framework “.NETFramework,Version=v4.5” were not found. To resolve this, install the SDK or Targeting Pack …
The key here is to tell the compiler where the Framework assemblies are. For that you need to override FrameworkPathOverride that is used to resolved the assemblies location. I proceeded to add the following to my project file (csproj). Note that the override is only triggered when the application targets a Unix platform and .NET Framework 4 and up
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<PropertyGroup Condition="$(TargetFramework.StartsWith('net4')) and '$(OS)' == 'Unix'"> <!-- When compiling .NET SDK 2.0 projects targeting .NET 4.x on Mono using 'dotnet build' you --> <!-- have to teach MSBuild where the Mono copy of the reference asssemblies is --> <!-- Look in the standard install locations --> <BaseFrameworkPathOverrideForMono Condition="'$(BaseFrameworkPathOverrideForMono)' == '' AND EXISTS('/Library/Frameworks/Mono.framework/Versions/Current/lib/mono')">/Library/Frameworks/Mono.framework/Versions/Current/lib/mono</BaseFrameworkPathOverrideForMono> <BaseFrameworkPathOverrideForMono Condition="'$(BaseFrameworkPathOverrideForMono)' == '' AND EXISTS('/usr/lib/mono')">/usr/lib/mono</BaseFrameworkPathOverrideForMono> <BaseFrameworkPathOverrideForMono Condition="'$(BaseFrameworkPathOverrideForMono)' == '' AND EXISTS('/usr/local/lib/mono')">/usr/local/lib/mono</BaseFrameworkPathOverrideForMono> <!-- If we found Mono reference assemblies, then use them --> <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net40'">$(BaseFrameworkPathOverrideForMono)/4.0-api</FrameworkPathOverride> <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net45'">$(BaseFrameworkPathOverrideForMono)/4.5-api</FrameworkPathOverride> <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net451'">$(BaseFrameworkPathOverrideForMono)/4.5.1-api</FrameworkPathOverride> <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net452'">$(BaseFrameworkPathOverrideForMono)/4.5.2-api</FrameworkPathOverride> <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net46'">$(BaseFrameworkPathOverrideForMono)/4.6-api</FrameworkPathOverride> <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net461'">$(BaseFrameworkPathOverrideForMono)/4.6.1-api</FrameworkPathOverride> <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net462'">$(BaseFrameworkPathOverrideForMono)/4.6.2-api</FrameworkPathOverride> <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net47'">$(BaseFrameworkPathOverrideForMono)/4.7-api</FrameworkPathOverride> <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net471'">$(BaseFrameworkPathOverrideForMono)/4.7.1-api</FrameworkPathOverride> <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net472'">$(BaseFrameworkPathOverrideForMono)/4.7.2-api</FrameworkPathOverride> <EnableFrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != ''">true</EnableFrameworkPathOverride> <!-- Add the Facades directory. Not sure how else to do this. Necessary at least for .NET 4.5 --> <AssemblySearchPaths Condition="'$(BaseFrameworkPathOverrideForMono)' != ''">$(FrameworkPathOverride)/Facades;$(AssemblySearchPaths)</AssemblySearchPaths> </PropertyGroup> |
I reran my compilation. I got another error saying it couldn’t find base references to stuff like Object, etc.
The project was missing reference to the netstandard library. I again proceeded to add the reference to the netstandard library in my project file.
1 2 3 |
<ItemGroup Condition="$(TargetFramework.StartsWith('net4')) and '$(OS)' == 'Unix'"> <Reference Include="netstandard" /> </ItemGroup> |
Success! The project compiled successfully.
Running the application
To run the application, I targeted the same version of mono that I compiled my application with.
The entry point uses mono. I also passed a few parameters to mono that can be seen in the mono man pages:
1 2 3 4 5 6 |
--server Configures the virtual machine to be better suited for server operations (currently, allows a heavier threadpool initialization). --gc=sgen Selects the Garbage Collector engine. In this case SGen --gc-params=mode=throughput Set the mode for GC to be throughput |
1 2 3 4 5 |
FROM mono:6.4.0.198 AS runtime ENV ASPNETCORE_URLS http://+:80 WORKDIR /app COPY --from=build /app/MyProject/out ./ ENTRYPOINT ["mono", "--server", "--gc=sgen", "--gc-params=mode=throughput", "MyProject.exe"] |
Here is the final Dockerfile
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 |
FROM mcr.microsoft.com/dotnet/core/sdk:2.2.401 AS build ENV MONO_VERSION 6.4.0.198 # install mono RUN apt-get update \ && apt-get install -y apt-transport-https dirmngr gnupg ca-certificates \ && rm -rf /var/lib/apt/lists/* \ && apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF RUN echo "deb http://download.mono-project.com/repo/debian stable-stretch/snapshots/$MONO_VERSION main" > /etc/apt/sources.list.d/mono-official-stable.list \ && apt-get update \ && apt-get install -y mono-devel \ && rm -rf /var/lib/apt/lists/* WORKDIR /app # copy csproj and restore as distinct layers COPY ./src/MyProject/*.csproj ./MyProject/ WORKDIR /app/MyProject RUN dotnet restore # copy and publish app and libraries WORKDIR /app COPY ./src/MyProject/. ./MyProject/ WORKDIR /app/MyProject RUN dotnet publish -c Release -o out --no-restore FROM mono:6.4.0.198 AS runtime ENV ASPNETCORE_URLS http://+:80 WORKDIR /app COPY --from=build /app/MyProject/out ./ ENTRYPOINT ["mono", "--server", "--gc=sgen", "--gc-params=mode=throughput", "MyProject.exe"] |
Considerations
While compiling your application you may encounter warnings such as that some libraries, such as System.Net.Http, conflicts could not be automatically resolved. Even if .NET Framework 4.7.2 is netstandard compatible, there as still some quirks that make it so that sometimes the bindings of those libraries cannot be automatically resolved.
This doesn’t mean that your application will not work. It just means that one DLL got the best of the other in terms of version (netstandard base lib vs .net framework or vice version!) and it may not be there version you’re expecting.
Make sure to thoroughly test your application to make sure there aren’t any problems with it.