Asp.Net Core with Kestrel and Service Fabric

Service Fabric SDK 2.1.150 comes with an ASP.NET Core project template so you can easily include a web app or web service in your Service Fabric application. To get started follow this official article: Build a web service front end for your application, but for more advanced scenarios such as hosting your .Net Core web application outside Service Fabric (for those times you just don’t want to deploy), forcing Kestrel to listen to all machine assigned IP addresses, we’ll customise and extend the starter template generated code. Moreover, with .Net Core RC2 and RTM the ubiquitous dotnet.exe becomes our preferred tool of choice so let’s facilitate running your Service Fabric Web app for development and debugging with the same simple command dotnet.exe run.

As always and given I am still targeting .Net Core RC2, we’ll start with the required project.json dependencies which should look something like the below. For command line argument heavily lifting include the “CommandLineParser”: “2.0.275-beta” package.

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
"dependencies": {
"Microsoft.AspNetCore.Hosting": "1.0.0-rc2-final",
"Microsoft.AspNetCore.Authentication": "1.0.0-rc2-final",
"Microsoft.AspNetCore.Authentication.Cookies": "1.0.0-rc2-final",
"Microsoft.AspNetCore.Authentication.JwtBearer": "1.0.0-rc2-final",
"Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.0.0-rc2-final",
"Microsoft.AspNetCore.Diagnostics": "1.0.0-rc2-final",
"Microsoft.AspNetCore.SpaServices": "1.0.0-beta-000004",
"Microsoft.AspNetCore.StaticFiles": "1.0.0-rc2-final",
"Microsoft.AspNetCore.Mvc": "1.0.0-rc2-final",
"Microsoft.AspNetCore.Mvc.Formatters.Json": "1.0.0-rc2-final",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-final",
"Microsoft.Extensions.Configuration.Abstractions": "1.0.0-rc2-final",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-rc2-final",
"Microsoft.Extensions.Configuration.FileExtensions": "1.0.0-rc2-final",
"Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-final",
"Microsoft.IdentityModel.Clients.ActiveDirectory": "3.9.302261508-alpha",
"Microsoft.Extensions.Configuration.Binder": "1.0.0-rc2-final",
"Swashbuckle": "6.0.0-beta9",
"Swashbuckle.SwaggerUi": "6.0.0-beta9",
"Swashbuckle.SwaggerGen": "6.0.0-beta9",
"CommandLineParser": "2.0.275-beta",
"Microsoft.ServiceFabric": "5.1.150",
"Microsoft.ServiceFabric.Data": "2.1.150",
"Microsoft.ServiceFabric.Services": "2.1.150",
"Microsoft.AspNetCore.Http.Abstractions": "1.0.0-rc2-final"
}

In your Program.cs which contains the generated starter template code add the following usings:

1
2
3
4
5
6
7
8
9
using CommandLine;
using Microsoft.AspNetCore.Hosting;
using Microsoft.ServiceFabric.Services.Communication.Runtime;
using Microsoft.ServiceFabric.Services.Runtime;
using System;
using System.Collections.Generic;
using System.Fabric;
using System.Threading;
using System.Threading.Tasks;

Since we want to host our .Net Core web application both within Service Fabric and outside for quick turnaround during development and debugging without the hassle of always deploying, we modify Main to support both scenarios:

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 static void Main(string[] args)
{

var parser = new Parser(with => {
with.EnableDashDash = true;
with.HelpWriter = Console.Out;
});

var result = parser.ParseArguments<Options>(args);

result.MapResult(
options =>
{
if (options.Host.ToLower() == AcmeConstants.ServiceFabricHost)
{
ServiceRuntime.RegisterServiceAsync("WebType", context => new WebHostingService(context, "WebTypeEndpoint")).GetAwaiter().GetResult();
Thread.Sleep(Timeout.Infinite);
}
else if(options.Host.ToLower() == AcmeConstants.SelfHost)
{
using (var host = WebHostBuilderHelper.GetWebHost(new WebHostBuilder(), options.Protocol, options.Port))
{
host.Run();
}
}
return 0;
},
errors =>
{
return 1;
});
}

AcmeConstants.ServiceFabricHost - value of command line argument: service-fabric-host
AcmeConstants.SelfHost - value of command line argument: self-host

We then need to create an Options class to be used by the CommandLineParser:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
internal sealed class Options
{
[Option(Default = "self-host", HelpText = "The target host - Options [self-host] or [service-fabric-host]")]
public string Host { get; set; }

[Option(Default = "http", HelpText = "The target protocol - Options [http] or [https]")]
public string Protocol { get; set; }

[Option(Default = "localhost", HelpText = "The target IP Address or Uri - Example [localhost] or [127.0.0.1]")]
public string IpAddressOrFQDN { get; set; }

[Option(Default = "5000", HelpText = "The target port - Example [80] or [5000]")]
public string Port { get; set; }
}

We replace the generated OpenAsync code with the following version:

1
2
3
4
5
6
7
8
9
10
Task<string> ICommunicationListener.OpenAsync(CancellationToken cancellationToken)
{
var endpoint = FabricRuntime.GetActivationContext().GetEndpoint(_endpointName);
string serverUrl = $"{endpoint.Protocol}://{FabricRuntime.GetNodeContext().IPAddressOrFQDN}:{endpoint.Port}";

_webHost = WebHostBuilderHelper.GetWebHost(new WebHostBuilder(), endpoint.Protocol.ToString(), endpoint.Port.ToString());
_webHost.Start();

return Task.FromResult(serverUrl);
}

Lastly we create our common GetWebHost method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static class WebHostBuilderHelper
{
public static IWebHost GetWebHost(IWebHostBuilder webHostBuilder, string protocol, string port)
{

IWebHost webHost = webHostBuilder
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseWebRoot(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot"))
.UseUrls($"{protocol}://+:{port}")
.UseStartup<Startup>()
.Build();

return webHost;
}
}

Note, I prefer to use the following which instructs Kestrel to listen to all IP addresses assigned to the machine on the port specified:

.UseUrls($"{protocol}://+:{port}")

All that is now left to do is within your .Net Core Web Application PackageRoot, edit the ServiceManifest.xml CodePackage so that we tell Web.exe to “host” within Service Fabric in this scenario:

1
2
3
4
5
6
7
8
9
10
<CodePackage Name="C" Version="1.0.0">
<EntryPoint>
<ExeHost>
<Program>Web.exe</Program>
<Arguments>--host service-fabric-host</Arguments>
<WorkingFolder>CodePackage</WorkingFolder>
<ConsoleRedirection FileRetentionCount="5" FileMaxSizeInKb="2048" />
</ExeHost>
</EntryPoint>
</CodePackage>

Your .Net Core Web application will now run both within Service Fabric and in debug mode. To run from the command line, from within your Web application folder issue:

dotnet.exe run