Semantic versioning

This is an excerpt from ASP.NET Core documentation on semantic versioning. The full document can be found here

The .NET Core Runtime roughly adheres to Semantic Versioning (SemVer), adopting the use of MAJOR.MINOR.PATCH versioning, using the various parts of the version number to describe the degree and type of change.Copy

MAJOR.MINOR.PATCH[-PRERELEASE-BUILDNUMBER]

The optional PRERELEASE and BUILDNUMBER parts are never part of supported releases and only exist on nightly builds, local builds from source targets, and unsupported preview releases.

Understand runtime version number changes

MAJOR is incremented when:

  • Significant changes occur to the product, or a new product direction.
  • Breaking changes were taken. There’s a high bar to accepting breaking changes.
  • An old version is no longer supported.
  • A newer MAJOR version of an existing dependency is adopted.

MINOR is incremented when:

  • Public API surface area is added.
  • A new behavior is added.
  • A newer MINOR version of an existing dependency is adopted.
  • A new dependency is introduced.

PATCH is incremented when:

  • Bug fixes are made.
  • Support for a newer platform is added.
  • A newer PATCH version of an existing dependency is adopted.
  • Any other change doesn’t fit one of the previous cases.

When there are multiple changes, the highest element affected by individual changes is incremented, and the following ones are reset to zero. For example, when MAJOR is incremented, MINOR and PATCH are reset to zero. When MINOR is incremented, PATCH is reset to zero while MAJOR is left untouched.

Adventurous: Angular & .NET Core on Linux

Note: This article is not aiming on digging into details of setting up CI pipeline. It’s sole purpose is to explain my experience with deploying and running .NET Core app in Linux environment.

Another note: For the purpose of this article I created simple HelloWorld Web API (https://github.com/mattuu/HelloWorld.NetCoreApi) and simple HelloWorld Angular app (https://github.com/mattuu/HelloWorld.Angular) that calls this API.

Setup

This article will use following file system paths:

/usr/helloworld
|--> api (directory for .NET Core Web API)
|--> www (directory for Angular app)
|--> nginx.conf (config file for Nginx)

Get you code over to Linux server

The CI pipeline should push your code to file system on Linux machine.

Deploying .NET Core 2.1 Web API to CentOS7

Install .NET Core runtime on CentOS server

This guide describes steps to install .NET Core: https://www.microsoft.com/net/download/linux-package-manager/centos/runtime-current

To check if .NET Core installed successfully, simply run:

dotnet --version

Now you should be able to start API by typing:

dotnet /usr/helloworld/api/HelloWorld.dll
Create dotnet daemon

Every process in Linux needs to run it’s own daemon. In order to accomplish this, we will use SystemD utility.

First we create service file helloworld.service with following content:

[Unit]
Description=Hello World .NET Core App Service
After=network.target

[Service]
ExecStart=/usr/bin/dotnet /usr/helloworld/helloworld.dll 5000
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

Next we copy this file to SystemD location:

sudo cp helloworldapi.service /lib/systemd/system

Now we need to reload SystemD and enable the service:

sudo systemctl daemon-reload 
sudo systemctl enable helloworldapi

Finally we start the service and check the status:

sudo systemctl start helloworldapi 
systemctl status helloworldapi

More details of how to configure SystemD can be found here: https://www.freedesktop.org/wiki/Software/systemd/ or here: https://pmcgrath.net/running-a-simple-dotnet-core-linux-daemon

Install Proxy Server

Since .NET Core runs on Kestrel, we need to setup a web server that will act as reverse proxy for the API. Most commonly used are Nginx or Apache. I will use Nginx for this article.

Full instructions on how to install Nginx can be found here: http://nginx.org/en/docs/install.html

Configure Nginx to act as reverse proxy

Now we need to tell Nginx to forward requests to .NET Core application. By default .NET Core runs on port 5000 so below configuration should work fine (path: /usr/helloworld/nginx.conf)

location /api/ {
proxy_pass http://localhost:5000; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $http_host;
proxy_cache_bypass $http_upgrade;
}

This ensures forwarding all requests to http://localhost/api are forwarded to http://localhost:5000 which is where .NET Core Web API is hosted.

Note: Nginx needs to be configured to include our custom configuration file so the following change needs to be done to Nginx default configuration (most likely to be in /etc/nginx/nginx.conf):

include /usr/helloworld/nginx.conf;

More on Nginx configuration options can be found here: https://nginx.org/en/docs/

Deploying Angular app to Centos7 OS

Our Angular app will be located in following directory: /usr/helloworld/www

Configure Nginx to serve static pages

Since Angular app is simple index.html, we need to tell Nginx to serve it as static content. Following article describes how to achieve this: https://medium.com/@jgefroh/a-guide-to-using-nginx-for-static-websites-d96a9d034940

Sample configuration may look like this (path: /usr/helloworld/nginx.conf)

location / {
root /usr/helloworld/www;
try_files /index.html =404;
}

location ~* .(js|jpg|png|css|woff|eot|svg|ttf|ico)$ {
root /usr/helloworld/www;
expires 30d;
}

First block ensures index.html is default document served by Nginx. This is needed to load Angular app properly.

Second line tells Nginx what to do with assets like JavaScript, CSS, images, fonts etc.

Once that’s done we should be able to navigate to http://localhost and see Angular app successfully loading:

If we now change browser url to http://localhost/?name=Matt, our application should display personalized greeting:

Our Angular app successfully passed name parameter to API and displayed greeting message generated by .NET Core.

Wrap-up

I really think .NET Core on Linux has great potential and will be exploring it further in future. I am planning on creating some bash scripts for dealing with this setup.

Useful links

AutoFixture customization for ASP.Net Core Web API controller

Lets start with simple controller:

[Route("api/[controller]")
public class TestController : Controller
{
	[HttpGet("")]
	public IActionResult Get()
	{
		return Ok();
	}
}

Now we want to create unit test for this method using AutoFixture/Moq/Shouldly/XUnit stack:

public class TestControllerTests 
{
	[Theory, AutoMoqData]
	public void Get_ShouldReturn_HttpStatusOk(TestController sut)
	{
		// act..
		var actual = sut.Get();

		// assert..
		actual.ShouldNotBeNull();
		actual.ShouldBeOfType()
	}
}

Test runner will crash saying:

AutoFixture.ObjectCreationException : AutoFixture was unable to create an instance from Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary because creation unexpectedly failed with exception. Please refer to the inner exception to investigate the root cause of the failure.

Request path:
	  J2BI.Holidays.TravelDocs.Api.Controllers.AccommodationController sut --> 
	   J2BI.Holidays.TravelDocs.Api.Controllers.AccommodationController --> 
	    Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary ViewData --> 
	     Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary


---- System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation.
-------- Castle.DynamicProxy.InvalidProxyConstructorArgumentsException : Can not instantiate proxy of class: Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata.
Could not find a parameterless constructor.

The problem lies in lack of parameterless constructor for ModelMetadata class.
This can easily be fixed with AutoFixture customization:

public class ApiControllerCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Register(() => new CustomCompositeMetadataDetailsProvider());
        fixture.Inject(new ViewDataDictionary(fixture.Create(), fixture.Create()));
    }

    private class CustomCompositeMetadataDetailsProvider : ICompositeMetadataDetailsProvider
    {
        public void CreateBindingMetadata(BindingMetadataProviderContext context)
        {
            throw new System.NotImplementedException();
        }

        public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
        {
            throw new System.NotImplementedException();
        }

        public void CreateValidationMetadata(ValidationMetadataProviderContext context)
        {
            throw new System.NotImplementedException();
        }
    }
}

This now has to be wrapped in data attribute:

public class AutoApiMoqDataAttribute : AutoDataAttribute
{
    public AutoApiMoqDataAttribute()
        : base(() =>
        {
            var fixture = new Fixture();

            fixture.Customize(new ApiControllerCustomization())
                   .Customize(new AutoMoqCustomization())
                   .Behaviors.Add(new OmitOnRecursionBehavior());

            return fixture;
        })
    {
    }
}

Voila! Now the test will pass.

Check out this gist