Discount for my course: High Performance Coding with .NET Core and C#

Gergely Kalapos


.csproj settings in ASP.NET Core

Posted on November 20, 2016



Intro

This post is an update from my original post  "project.json settings in ASP.NET Core" . Everything I write there is valid for the project.json based project system. That project system was deprecated by Microsoft: they decided to create an MSBuild compatible project system. This is the new .csproj based one. In this post I go through all the settings and show how they look like in this new project system. Btw. currently (20. November 2016) this is still in alpha. See the announcement here.

Although I show the settings with an ASP.NET Core application these apply to any .net core application, not only for web projects.

So..let's start!


What is this csproj deal at all?

Well, the .csproj file itself a very old concept. It is basically an XML based project file which keeps track of the settings within a .NET projects. These are settings like the included files, the assembly name, etc. MSBuild is also able to understand this file. But when .NET Core was created MSBuild was not cross platform and this XML file was extremely long and messy anyway, so the original decision was to create a new project system. This was project.json and the xproj file (see here for more.) But in the meantime MSBuild became opensource and cross-plattform. Plus some people were not happy with project.json, since it made moving to .NET Core with existing code base even harder. 

So finally Microsoft decided to move back to .csproj, but with a much more readable XML file. The goal is to keep every advantage of project.json and integrate it with MSBuild within the .csproj file. One example is that in the old .csproj file system you always had to list all the referenced files in .csproj. In combination with version control systems this was a pain, so in project.json the default was that every file which was in the folder was automatically part of the solution without referencing it. Now this feature is moved to .csproj, so you don't have to list all your .cs (or .vb or .fs, etc.)  files there.

Ok, so now let's dive into this!

How do I create an ASP.NET Core application?

Either you have Visual Studio, and you do the old "File" - > "New Project" - > etc. stuff, of you use command line (and this is what we will do in this post) and type 

dotnet new -t web

With the -t web we say that this will be an ASP.NET Core project. E.g. just 'dotnet new' creates a plain console application.

 

How do we start this ASP.NET Core application?


First we need to load the dependencies into the machine. For this you just type:

dotnet restore

This basically does a nuget restore. And then:

dotnet run

More about this later..

Next question:


Where is the framework located on my machine?


Well, it can be anywhere, there is no predefined installation folder for coreCLR (remember with full framework this was Windows\Microsoft.NET\... If you install the dotnet CLI via the installer, then at this point it is under C:\Program Files\dotnet. This is where the dotnet.exe resides, but it can be anywhere on your machine, and you can also set it to the PATH from anywhere (same like java).

There is an important folder in here:


The "Shared" folder is the equivalent of having a .net framework on your machine. In this folder you see all the files, which make up a dotnet framework (like coreclr.dll). So this is where coreclr is.
 
Now let’s see the .csproj file from the HelloWeb Project:

 
<Project ToolsVersion="15.0" xmlns ="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" / >

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp1.1</TargetFramework>
    <PreserveCompilationContext>true</PreserveCompilationContext>
  </PropertyGroup>

  <PropertyGroup>
    <PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include=" * *\*.cs" Exclude ="$(GlobalExclude)" / >
    <EmbeddedResource Include=" * *\*.resx" Exclude ="$(GlobalExclude)" / >
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NETCore.App" & gt;
      <Version>1.1.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.NET.Sdk.Web" & gt;
      <Version>1.0.0-alpha-20161104-2-112</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.AspNetCore.Diagnostics">
      <Version>1.0.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.AspNetCore.Mvc">
      <Version>1.0.1</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.AspNetCore.Razor.Tools">
      <Version>1.0.0-preview2-final</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.AspNetCore.Routing">
      <Version>1.0.1</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration">
      <Version>1.0.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel">
      <Version>1.0.1</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.AspNetCore.StaticFiles">
      <Version>1.0.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables">
      <Version>1.0.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.Configuration.Json">
      <Version>1.0.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.Logging">
      <Version>1.0.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.Logging.Console">
      <Version>1.0.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.Logging.Debug">
      <Version>1.0.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions">
      <Version>1.0.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink.Loader">
      <Version>14.0.0</Version>
    </PackageReference>
  </ItemGroup>

  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>



Let’s talk about the “TargetFramework” section: This is where you define on which framework your project will run on. The value you put here is the so called “Target Framework Moniker” (TFM). Sometimes my feeling is that there is a team at Microsoft, and the only thing they do is coming up with new names every week… joking aside, make sure you use the current values, since many of them were renamed from RC1 to RC2. Here is a very useful stackoverflow entry

netcoreapp1.1 means that this project targets .NET Core 1.1.

How do I start this application?

As already discussed by just typing 

dotnet run


If you look into the process with process explorer you see a few interesting thing:


First the process which runs is dotnet.exe and a csprojtest.dll from the bin\Debug\netcoreapp1.1\ folder is passed as a parameter to dotnet.exe.


So when you execute dotnet run it first builds the code (if it is not built yet), puts the dlls into the bin folder and passes it as a parameter. At the beginning of this ASP.NET Core jurney Microsoft talked a lot about in memory compilation and originally they did not want to create a dll on the disk in development time. RC1 did not emit dlls on the disk at this point, but this was dropped in RC2, so there is always a DLL.  

Btw. you can also just build your project (and not start it) with

dotnet build

If you put a dll as a parameter to dotnet.exe, what is the entry point of the application?


This is predefined and every application has to have a public static ...Main(…) method, and that is the entry point.

It’s also worth looking at the loaded dlls:


So coreclr.dll is loaded from C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.1.0. That’s where the framework is installed (see above, I told you!).


The ASP.NET Core specific dlls are loaded from my .nuget folder (this is the global nuget cache) and they were downloaded when we did a dotnet restore. So in sum: the core framework comes from the shared folder of the dotnet directory, and ASP.NET Core is loaded from the nuget cache.


How to switch between coreCLR and full framework? How to run this ASP.NET Core application on full framework?


Switching between CoreCLR and full framework is a common thing I want to do… so let’s say we want to modify this project (the one in the HelloWeb folder) to run on full framework and not on CoreClr. (Ok, there is another folder (HelloWebFull), but let’s ignore it and modify HelloWeb to run on the full framework).
As we already discussed this the framework on which the project runs is defined in the framework section. The current (and hopefully the final) Target Framework Moniker (TFM) for full framework 4.52 is net452, so

Change 1:

We change the TargetFramework to net452 and add a RuntimeIdentifier


<PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net452</TargetFramework>
    <PreserveCompilationContext>true</PreserveCompilationContext>
    <RuntimeIdentifier>win7-x86</RuntimeIdentifier>
  </PropertyGroup>

The other change is in the dependencies section.
Change 2:

You have to remove the dependency on "Microsoft.NETCore.App", so you dependencies are now these:


<ItemGroup>  
    
    <PackageReference Include="Microsoft.NET.Sdk.Web">
      <Version>1.0.0-alpha-20161104-2-112</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.AspNetCore.Diagnostics">
      <Version>1.0.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.AspNetCore.Mvc">
      <Version>1.0.1</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.AspNetCore.Razor.Tools">
      <Version>1.0.0-preview2-final</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.AspNetCore.Routing">
      <Version>1.0.1</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration">
      <Version>1.0.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel">
      <Version>1.0.1</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.AspNetCore.StaticFiles">
      <Version>1.0.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables">
      <Version>1.0.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.Configuration.Json">
      <Version>1.0.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.Logging">
      <Version>1.0.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.Logging.Console">
      <Version>1.0.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.Logging.Debug">
      <Version>1.0.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions">
      <Version>1.0.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink.Loader">
      <Version>14.0.0</Version>
    </PackageReference>
  </ItemGroup>

then dotnet.exe in this case will create a child process and it will be HelloWeb.exe. Bamm! So there is an exe file again! And this is, because it is a classical .net application.




It’s also interesting to look at the loaded images of the process


As you can see mscoree.dll and stuff from Windows/Microsft.NET are loaded plus the classical native images are also loaded from the native image cache. The ASP.NET Core framework is loaded from the /bin folder, they are copied there… so this is not fake… the application (the same application!) in this case really runs on the full framework.  


Of course this only works on Windows. The project with the modified project.json won’t compile on Linux, Mac or any other platform… net452 is Windows only.

How can I publish/deploy my ASP.NET Core application?

So there are two types of deployments in this new world: 1) portable applications and 2) well… not portable.

The huge benefit of the old .NET deployment was that you only had to install the framework once on the machine and it could be used by many applications. One framework installment (under windows\Microsoft.NET) shared by 10 or 20 application on a machine is a usual scenario. And Microsoft wanted to keep this. Think about Azure and the free tiers! Installing the framework with every single application would add a huge overhead. So the first option for deployment is to package the application without the framework. This is a cross platform deployment scenario, since it only contains .net dlls (with IL in it), but nothing platform specific. These are called portable applications. As we saw before, you can pass the dll with the main method to the dotnet command (on any platform.. not only on Windows) and it will start the installed runtime from the machine. (On my machine from  C:\Program Files\dotnet\shared)

Ok, so:

How do I create a portable deployment package from my ASP.NET Core application?

The answer is

dotnet publish

Now in the last part we modified the .csproj file, so the project targets .net 4.52. Before you do a publish I would suggest to revert it to the original version and target dotnet core (otherwise you will get a classical .net application).


After dotnet publish is finished you will get your deployment package (basically a bunch of dlls) under bin\Debug\netcoreapp1.1\publish. If you look into that you see that it contains your application as a dll and its dependencies. Since the whole ASP.NET Core framework is a dependency of the application all these dlls are there, but the runtime (the clr) is not there. The whole thing is only ~19.2mb.

This way you can xcopy this directory to any machine with a crl installed on it (this also includeds Mac, Linux, etc.) and run that with

dotnet HelloWeb.dll


So this package contains everything except the runtime and suitable for scenarios where there is already a runtime installed on the machine and it is shared by multiple application. (Similar to the old deployment model, which still absolutely makes sense for many scenarios!)


All right, but they told that this is similar to java! Can I publish a really self-contained asp.net application, which also ships the framework?

Update 26.02.2017: First of all, many thanks for Gaetan for the comment... When I originally wrote this post back in 2016 November there was 0 information about this on the web

So the answer is yes, this is possible with the runtimeidentifier tag.

Here is a .csproj file with this setting:

Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp1.1</TargetFramework>
   <RuntimeIdentifier>win10-x64</RuntimeIdentifier>
  </PropertyGroup>
</Project>

I use win10-x64, since I write this on Windows 10, but there is a RuntimeIdentifier for Mac, and for the different Linux distributions.

After you execute 'dotnet publish' you will get a publish folder with your RuntimeIdentifier. This folder contains everything (including the runtime with coreclr.dll) which is needed for the application.
As you see the size of this folder is bigger.

Another new thing is that this folder contains an executable (HelloWeb.exe). On Windows this is a .exe file, on other platforms this is the equivalent of .exe.

When you click on this executable the application will start and it loads the clr from the local folder.

One thing to note that this self-contained application (by application I mean the binaries and everything else in the win10-x64 folder) is not cross platform, since you already compiled it for a specific platform. You can of course recompile it with another RuntimeIdentifier to target another platform, or you can simply compile a cross platform version (without the framework, like it is described above). There is also a RuntimeIdentifiers (plural) setting where you can add multiple RuntimeIdentifiers at the same time.

 

Resources

Scott Hunter,.NET CLI, April 21, 2016 - DevDays Latam 2016
ASP.NET Community Standup
csproj reference by Microsoft

That's it for today.. Keep in mind that this is still not RTM and things can be changed! I try to keep this post up-to-date.

Cheers,

Gergö


;