I'm running VS2017 on Azure VM, Windows 2016 with a local Service Fabric from the SDK current as of (4/29/17), single node. Trying to simply create an environment verification app before digging in. Using the VS tools to create an ASP.NET Core web application. Right out of the box it builds, deploys, and runs fine. This example of multiple endpoints fails: https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-service-manifest-resources All I have been able to find are code fragments; nothing that works on the local cluster. Can someone point me to a working sample that is an ASP.NET Core web app on a local cluster with both http and https endpoints. Thanks!
1 Answers
Trying to be brief about getting http and https to both work in local machine in a ASP.NET Core project with SF5.5 & SDK2.5 (3/2017). I show the code and configurations and list all the pages as references to where the information is sourced. I could not have done this alone. Thanks to the community!
Step1:
Create a certificate for the local SF cluster to use. I liked this article for creating a certificate. I then extracted the certificate thumbprint using certmgr to get the certificate details.
HINT: When you copy the thumbprint from the certificate store it will not work and I copied it to both Notepad and Visual Studio Code before pasting it into the configuration. This reference here by TChaing indicates that there are hidden characters. I simply typed it in (without spaces). I knew something was wrong with the cert because the event viewer had error messages that the cert could not be parsed which led me to this answer.
Step2:
For my application and service manifest, Matt Kotsenas's article https://matt.kotsenas.com/posts/https-in-service-fabric-web-api is great. He also has an elegant solution for creating the listeners it only needs a small tweak to work with the ASP.Net Core which I have in my code listing.
Code:
ApplicationManifest file
<ServiceManifestImport>
<ServiceManifestRef ServiceManifestName="Web1Pkg" ServiceManifestVersion="1.0.0" />
<ConfigOverrides />
<!-- Add policies for service endpoints -->
<Policies>
<EndpointBindingPolicy EndpointRef="ServiceEndpointHttps" CertificateRef="MyCert" />
</Policies>
...
</DefaultServices>
<!-- Add the certificate. In production use parameter references to the thumbprints. -->
<Certificates>
<EndpointCertificate X509FindValue="6e6a28c083c1b8114c4e2279b37cf6d684668aad" Name="MyCert" />
</Certificates>
</ApplicationManifest>
ServiceManifest file ...
<Resources>
<Endpoints>
<!-- This endpoint is used by the communication listener to obtain the port on which to
listen. Please note that if your service is partitioned, this port is shared with
replicas of different partitions that are placed in your code. -->
<Endpoint Protocol="http" Name="ServiceEndpoint" Type="Input" Port="8340" />
<Endpoint Protocol="https" Name="ServiceEndpointHttps" Type="Input" Port="8343" />
</Endpoints>
C# code from my service class "Web1" in this case. There are two different ways to create the Listeners shown below. The commented code directly names the listeners such as "ServiceEndpoint" and notice "HttpListner1". This is new in ASP.NET Core. If you have more than one endpoint it MUST be named specifically. ServiceEndpoint is NOT the name automatically if no name is supplied.
The code not commented uses an extraction to fetch the service names and then create listners. In this case I used the "endpoint" variable to also add the explicit name for the listener as required.
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
//return new ServiceInstanceListener[]
//{
// new ServiceInstanceListener(serviceContext =>
// new WebListenerCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
// {
// ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting WebListener on {url}");
// return new WebHostBuilder().UseWebListener()
// .ConfigureServices(
// services => services
// .AddSingleton<StatelessServiceContext>(serviceContext))
// .UseContentRoot(Directory.GetCurrentDirectory())
// .UseStartup<Startup>()
// .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
// .UseUrls(url)
// .Build();
// }), "HttpListner1"),
// new ServiceInstanceListener(serviceContext =>
// new WebListenerCommunicationListener(serviceContext, "ServiceEndpointHttps", (url, listener) =>
// {
// ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting WebListener on {url}");
// return new WebHostBuilder().UseWebListener()
// .ConfigureServices(
// services => services
// .AddSingleton<StatelessServiceContext>(serviceContext))
// .UseContentRoot(Directory.GetCurrentDirectory())
// .UseStartup<Startup>()
// .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
// .UseUrls(url)
// .Build();
// }), "HttpsListner1")
//};
var endpoints = Context.CodePackageActivationContext.GetEndpoints()
.Where(endpoint => endpoint.Protocol == EndpointProtocol.Http || endpoint.Protocol == EndpointProtocol.Https)
.Select(endpoint => endpoint.Name);
var eps = endpoints.Select(endpoint =>
new ServiceInstanceListener(serviceContext =>
new WebListenerCommunicationListener(serviceContext, endpoint, (url, listener) =>
{
ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting WebListener on {url}");
return new WebHostBuilder().UseWebListener()
.ConfigureServices(
services => services
.AddSingleton<StatelessServiceContext>(serviceContext))
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
.UseUrls(url)
.Build();
}
), endpoint));
return eps;
}
I hope this brings all the missing parts together for you.