Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Hosted-services / Azure

Azure Service Fabric demo

5.00/5 (33 votes)
29 Nov 2017CPOL25 min read 62.4K  
Azure Service Fabric Demo + IOC, logging, encryption

Introduction

In this article we will walk through using the Azure "Service Fabric". to my mind this is one of the most fascinating things in the Azure platform offerings.

We will also see how to use best practices such as dependency injection and proper logging, and how to keep sensitive data safe when deploying to what is essentially a public cloud. 

 

What Is The Azure Service Fabric

So before we get into the guts of what this article does and how it does it. Just what is the "Service Fabric"?

To quote the official docs (they always say things quite nicely, little point in trying to rewrite what is already a good explanation)

 

Overview Of Azure Service Fabric

Azure Service Fabric is a distributed systems platform that makes it easy to package, deploy, and manage scalable and reliable microservices and containers. Service Fabric also addresses the significant challenges in developing and managing cloud native applications. Developers and administrators can avoid complex infrastructure problems and focus on implementing mission-critical, demanding workloads that are scalable, reliable, and manageable. Service Fabric represents the next-generation platform for building and managing these enterprise-class, tier-1, cloud-scale applications running in containers.


This short video introduces Service Fabric and microservices:

Image 1

Applications Composed Of Microservices

service Fabric enables you to build and manage scalable and reliable applications composed of microservices that run at high density on a shared pool of machines, which is referred to as a cluster. It provides a sophisticated, lightweight runtime to build distributed, scalable, stateless, and tasteful micro services running in containers. It also provides comprehensive application management capabilities to provision, deploy, monitor, upgrade/patch, and delete deployed applications including containerized services.


Service Fabric powers many Microsoft services today, including Azure SQL Database, Azure Cosmos DB, Cortana, Microsoft Power BI, Microsoft In tune, Azure Event Hubs, Azure IoT Hub, Dynamics 365, Skype for Business, and many core Azure services.


Service Fabric is tailored to create cloud native services that can start small, as needed, and grow to massive scale with hundreds or thousands of machines.


Today's Internet-scale services are built of micro services. Examples of micro services include protocol gateways, user profiles, shopping carts, inventory processing, queues, and caches. Service Fabric is a micro services platform that gives every micro service (or container) a unique name that can be either stateless or stateful.


Service Fabric provides comprehensive runtime and lifecycle management capabilities to applications that are composed of these micro services. It hosts micro services inside containers that are deployed and activated across the Service Fabric cluster.

A move from virtual machines to containers makes possible an order-of-magnitude increase in density. Similarly, another order of magnitude in density becomes possible when you move from containers to micro services in these containers. For example, a single cluster for Azure SQL Database comprises hundreds of machines running tens of thousands of containers that host a total of hundreds of thousands of databases. Each database is a Service Fabric stateful micro service.
For more on the micro services approach, read Why a microservices approach to building applications?

Any OS, Any Cloud

Service Fabric runs everywhere. You can create clusters for Service Fabric in many environments, including Azure or on premises, on Windows Server, or on Linux. You can even create clusters on other public clouds. In addition, the development environment in the SDK is identical to the production environment, with no emulators involved. In other words, what runs on your local development cluster deploys to the clusters in other environments.

 

Taken from https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-overview up on date 27/11/17

 

My View

So the above is what the official Microsoft docs say, but what do I think?

Well yes its does all of the above, but what drove me to look and use the Service Fabric initially (A decision I have not regretted) is that is also offers these features

  • There is a concept of a cluster which is made up of a number of nodes (you pick this at creation time). So there is no central point of failure. Typically the cluster is not an even number
  • Side by side stateless/stateful/actors all running in same fabric cluster nodes (if you ever used the early Azure cloud services you will know this is a big win)
  • Self healing system, I would specify my intention via a config file, where I would say I want 3 instances of this, 5 of that, and the Service Fabric would take care of all of the provisions, maintenance to ensure that my requirements were met.
  • There is a nice web based dashboard that comes for free that shows the cluster/health/stats etc etc
  • Its fairly familiar apart from a few special methods it felt the same as creating something like a WebApi, or windows service. We will see more of this later.
  • I can scale the service us on demand via PowerShell and also via a simple .NET class that I wrote which uses the FabricClient .NET object to communicate with the cluster

 

Where Is The Code?

I have the code in my Github repo : https://github.com/sachabarber/MinimalAzureServiceFabricIOCSerilog

 

Prerequisites

You will need to have the following components installed to run the code associated with this article

  • .NET 4.6.2 developer pack
  • Azure Service Fabric 2.6 (or above) SDK
  • Visual Studio 2017

 

What Is This Article Going To Show Me?

This article will hopefully give you a taster of what is possible with the Azure Service Fabric, and will also show you how to perform common best practices such as:

  • Dependency injection to facilitate better testing
  • Introduce better logging system then relying on the one you get from a new Azure Service Fabric template
  • Talk about the fabric as a whole
  • Show you how to protect sensitive information (such as connection strings, its a public cloud after all)

 

Lets Crack On

Anatomy Of An Azure Service Fabric Offering

So we have talked a bit about the Service Fabric. But what sort of things can we run on the Service Fabric?

This is actually quite varied. For example this is what we see when we create a new Service Fabric app in Visual Studio.

Image 2

CLICK FOR BIGGER IMAGE

Whilst I will not be going into all these different type of services, I will give a 1 line strap lines for each of them

  • Stateless service : Is a service that does not need to maintain any state, so this could be a per request type of thing, or something that will create a new processing pipeline per message or something like that
  • Stateful service : Is a service where we need to store state. This is done via ReliableDictionary in the ServiceFabric
  • Actor service : Is intended for tiny discrete bits of functionality that send messages to each other. think 1000nds of actors all doing very small bits of work sending messages to each other
  • Guest executable : Allows you to package up virtually anything (exe, node app whatever) and have it run on the fabric
  • Container : Container that will run on the fabric
  • ASP Core : Either web Api type app or stateful http app

For this article we will ONLY be examining "stateless service" as the techniques would apply to other types of services shown above fairly well.

 

When you create a new Service Fabric app, you will see something like this

Image 3

 

There are 2 distinct things at play here

 

The Application

The actual Application contains several VERY important things, namely:

  • ApplicationManifest.xml : Which tells the fabric exactly what services, and their types and how many of each should be deployed. This is the requirements for the entire set of services to deploy. This may be 1 or more services
  • Application Parameter files : The parameters needed to run the services specified
  • Publish Profile files : The publish profile files describing where to deploy to

 

The Services

Each individual service is its own project. And has its own config, ServiceManifest.xml file. We will look at that more later. For now its important just to understand that each Service has its own project.

 

The main code for a fresh out the box Service will looks very similar to this. But we will discuss this more later too.

C#
using System;
using System.Collections.Generic;
using System.Fabric;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.ServiceFabric.Data.Collections;
using Microsoft.ServiceFabric.Services.Communication.Runtime;
using Microsoft.ServiceFabric.Services.Runtime;

namespace Stateful1
{
    /// <summary>
    /// An instance of this class is created for each service 
    /// replica by the Service Fabric runtime.
    /// </summary>
    internal sealed class Stateful1 : StatefulService
    {
        public Stateful1(StatefulServiceContext context)
            : base(context)
        { }

        /// <summary>
        /// Optional override to create listeners (e.g., HTTP, Service Remoting, WCF, etc.) 
		/// for this service replica to handle client or user requests.
        /// </summary>
        /// <remarks>
        /// For more information on service communication, 
        /// see https://aka.ms/servicefabricservicecommunication
        /// </remarks>
        /// <returns>A collection of listeners.</returns>
        protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners()
        {
            return new ServiceReplicaListener[0];
        }

        /// <summary>
        /// This is the main entry point for your service replica.
        /// This method executes when this replica of your service becomes primary and has write status.
        /// </summary>
        /// <param name="cancellationToken">Canceled when Service Fabric needs to
        ///	shut down this service replica.</param>
        protected override async Task RunAsync(CancellationToken cancellationToken)
        {
            // TODO: Replace the following sample code with your own logic 
            //       or remove this RunAsync override if it's not needed in your service.

            ...
        }
    }
}

 

As stated above you may have 1 or more services. But when you start you will only have 1. To add another service you right click on the Application project, and Add. This is shown in the screen shot below.

Image 4

CLICK FOR BIGGER IMAGE

 

Intentions

The service fabric works a bit like tools like Kubernetes in that you describe what you want to run, and how many of them, and the fabric takes care of the rest. This is done via the ApplicationManifest.xml file

 

Application

The application has a very important file that deals with intentions ApplicationManifest.xml. This file may look as shown below, and is the file that relays the intentions of the deployment to the fabric. It has things about what services should be deployed, what version they are, how many of each, overridden parameters, what certificates to use etc etc

 

This is a very important file to understand

 

XML
<?xml version="1.0" encoding="utf-8"?>
<ApplicationManifest ApplicationTypeName="Application1Type"
                     ApplicationTypeVersion="1.0.0"
                     xmlns="http://schemas.microsoft.com/2011/01/fabric"
                     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Parameters>
    <Parameter Name="Stateful1_MinReplicaSetSize" DefaultValue="3" />
    <Parameter Name="Stateful1_PartitionCount" DefaultValue="1" />
    <Parameter Name="Stateful1_TargetReplicaSetSize" DefaultValue="3" />
    <Parameter Name="Stateless1_InstanceCount" DefaultValue="-1" />
  </Parameters>
  <!-- Import the ServiceManifest from the ServicePackage. The ServiceManifestName and ServiceManifestVersion 
       should match the Name and Version attributes of the ServiceManifest element defined in the 
       ServiceManifest.xml file. -->
  <ServiceManifestImport>
    <ServiceManifestRef ServiceManifestName="Stateful1Pkg" ServiceManifestVersion="1.0.0" />
    <ConfigOverrides />
  </ServiceManifestImport>
  <ServiceManifestImport>
    <ServiceManifestRef ServiceManifestName="Stateless1Pkg" ServiceManifestVersion="1.0.0" />
    <ConfigOverrides />
  </ServiceManifestImport>
  <DefaultServices>
    <!-- The section below creates instances of service types, when an instance of this 
         application type is created. You can also create one or more instances of service type using the 
         ServiceFabric PowerShell module.
         
         The attribute ServiceTypeName below must match the name defined in the imported ServiceManifest.xml file. -->
    <Service Name="Stateful1">
      <StatefulService ServiceTypeName="Stateful1Type" 
                       TargetReplicaSetSize="[Stateful1_TargetReplicaSetSize]" 
                       MinReplicaSetSize="[Stateful1_MinReplicaSetSize]">
        <UniformInt64Partition PartitionCount="[Stateful1_PartitionCount]" 
                               LowKey="-9223372036854775808" HighKey="9223372036854775807" />
      </StatefulService>
    </Service>
    <Service Name="Stateless1">
      <StatelessService ServiceTypeName="Stateless1Type" 
                        InstanceCount="[Stateless1_InstanceCount]">
        <SingletonPartition />
      </StatelessService>
    </Service>
  </DefaultServices>
</ApplicationManifest>

 

 

Service

So above we mentioned that each service has 2 things that are important from an infrastructure point of view, it has information about what the main entry point class is, and what endpoints this service has.

ServiceManifest.xml which might look like this, that describes how to luanch THAT service

XML
<?xml version="1.0" encoding="utf-8"?>
<ServiceManifest Name="Stateful1Pkg"
                 Version="1.0.0"
                 xmlns="http://schemas.microsoft.com/2011/01/fabric"
                 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <ServiceTypes>
    <!-- This is the name of your ServiceType. 
         This name must match the string used in RegisterServiceType call in Program.cs. -->
    <StatefulServiceType ServiceTypeName="Stateful1Type" HasPersistedState="true" />
  </ServiceTypes>

  <!-- Code package is your service executable. -->
  <CodePackage Name="Code" Version="1.0.0">
    <EntryPoint>
      <ExeHost>
        <Program>Stateful1.exe</Program>
      </ExeHost>
    </EntryPoint>
  </CodePackage>

  <!-- Config package is the contents of the Config directoy under PackageRoot that contains an 
       independently-updateable and versioned set of custom configuration settings for your service. -->
  <ConfigPackage Name="Config" Version="1.0.0" />

  <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 Name="ServiceEndpoint" />

      <!-- This endpoint is used by the replicator for replicating the state of your service.
           This endpoint is configured through a ReplicatorSettings config section in the Settings.xml
           file under the ConfigPackage. -->
      <Endpoint Name="ReplicatorEndpoint" />
    </Endpoints>
  </Resources>
</ServiceManifest>

 

Settings.xml that might look like this, that describe what parameters THAT service expects

XML
<?xml version="1.0" encoding="utf-8" ?>
<Settings xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns="http://schemas.microsoft.com/2011/01/fabric">
  <!-- This is used by the StateManager's replicator. -->
  <Section Name="ReplicatorConfig">
    <Parameter Name="ReplicatorEndpoint" Value="ReplicatorEndpoint" />
  </Section>
  <!-- This is used for securing StateManager's replication traffic. -->
  <Section Name="ReplicatorSecurityConfig" />

  <!-- Add your custom configuration sections and parameters here. -->
  <!--
  <Section Name="MyConfigSection">
    <Parameter Name="MyParameter" Value="Value1" />
  </Section>
  -->
</Settings>

More on this later

 

 

 

Inter Service Comms

So the service fabric also allows you to open up ports to the outside world. Now this may not be something you need to do at all, for example your service could get work via polling a database table, reading a queue, or getting sent a message via a bus, but if you do need to expose a port that is done in this method. You can read more about this here : https://aka.ms/servicefabricservicecommunication 

C#
protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners()

To get a better understanding of this method, you could create a ASP .Net Core service fabric example that would fill this method in for you, so you could better understand it. For the code in this demo its just not relevant.

 

Where Do I Put My Code To Do Stuff?

So you might now be wondering where do I put my own code? This is a simple question to answer. You simply fill in this method. You MUST observe the CancellationToken, if you do not the platform will boot your services down in the most dramatic way known. Observing the CancellationToken is your ONLY chance to shutdown gracefully.

C#
protected override async Task RunAsync(CancellationToken cancellationToken)

 

Importance Of Correct Configuration

To run the Service Fabric you MUST ensure that it runs with x64 as the platform. An example is shown below for the code that accompanies this article

 

Image 5

CLICK FOR BIGGER IMAGE

 

Deployment/Publish Profiles

The Service Fabric builds apon previous Azure successes, and Publish profiles are one such success. These are a simple idea, where we have a profile matched with a deployment that specifies the requirements needed for THAT deployments via some dedicated config.

 

Here is an example of publish profile file

XML
<?xml version="1.0" encoding="utf-8"?>
<PublishProfile xmlns="http://schemas.microsoft.com/2015/05/fabrictools">
  <ClusterConnectionParameters ConnectionEndpoint="XXXXXXXX.westus.cloudapp.azure.com:19000" />
  <ApplicationParameterFile Path="..\ApplicationParameters\Cloud.xml" />
  <CopyPackageParameters CompressPackage="true" />
</PublishProfile>

Where this just points to the relevant cluster endpoint, and specifies another file for the parameters to use

Here is that extra parameters file

XML
<?xml version="1.0" encoding="utf-8"?>
<Application xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
             Name="fabric:/SimpleStatelessServiceFabricDemo" 
             xmlns="http://schemas.microsoft.com/2011/01/fabric">
  <Parameters>
    <Parameter Name="Stateless1_InstanceCount" Value="-1" />
    <Parameter Name="simpleStatelessServiceFabricDemo:EnvironmentName" 
               Value="Cloud" />
    <Parameter Name="simpleStatelessServiceFabricDemo:serilog:write-to:Seq.restrictedToMinimumLevel" 
               Value="Information" />
    <Parameter Name="simpleStatelessServiceFabricDemo:serilog:write-to:Seq.serverUrl" 
               Value="http://localhost:5341" />
    <Parameter Name="simpleStatelessServiceFabricDemo:UseServiceFabricEnhancements" 
               Value="true" />
    <Parameter Name="simpleStatelessServiceFabricDemo:SomeSafeKey" 
               Value="fdfdfdd" />
    <Parameter Name="simpleStatelessServiceFabricDemo:CertThumbPrint" 
               Value="ad0665f13101c6258cbfae6e091c3f3155fe50d0" />
  </Parameters>
</Application>

 

So we would normally have 1 x Publish Profile file, and 1 x Parameter file per environment that we wish to release to. The standard template that comes with Visual Studio for building Azure Service Fabric apps will allow you to specify which publish profile to release when you want to publish.

 

NOTE :

 

You must perform a "Package" step first before you "Publish", this creates the correct structure to release. This is avaiable via a right click in the main project (the one with the "ApplicationManifest.xml in it)

Image 6

You do not need to publish if running locally. If however you wish to publish to a real cluster you can do that from Visual Studio. In which case steps are

  1. Package the code
  2. Publish, at which point you will be prompted for which Publish Profile file to use

 

The Cluster Manager

As I stated in the introductory section of this article the Azure Service Fabric comes with a rather nice cluster manager (which I have to say looks like it stole a lot of inspiration from the DataStax Cassandra cluster manager). This cluster manager is accessible from the portal or via the system tray icon if you are running the cluster locally from Visual Studio.

 

From the cluster manager you can do these sort of things

  • Delete an app
  • Unprovision a type
  • Start a node
  • Stop a node (useful for testing failure code)
  • See what artifacts are deployed
  • Examine the configuration that was used for a particular deployment

 

As I say if running locally you can access this from the system tray icon, otherwise just grab the url from the Azure portal

Image 7

 

Right click that icon, and choose "Manage local cluster" to see the Fabric explorer. This is available at a url of this form (get the correct url from your own setup or the Azure portal) 

 

http://[YOUR HOST HERE]:19080/Explorer/Index.htm

 

Image 8

 

CLICK FOR BIGGER IMAGE

 

 

The Demo Code

So what exactly does the demo code do?

Ok now that we have covered the basics of what the fabric is, and looked at what a new project might look like and what methods we have, it's time to now turn our attention to the main part of this article which is to show how you can do all the normal stuff like:

  • Stateless service that does NOT expose a port to the outside world. If you want that you will need to do the reading suggested in the topic about "inter service comms" above
  • Proper IOC  / dependency injection
  • Logging
  • Protecting sensitive passwords in connection strings

The rest of this article will be covering these aspects

 

 

 

Bringing IOC Into The Fold

Like it or not, IOC/DI does play a part in providing a good separation of concerns, and allows for more testable pluggable code. To that end it is a good idea to apply IOC/DI to your own service fabric services.

But how, which container do I use, there are so many.

That's true there are a lot of containers, I looked around for the ones that have good support for service fabric, and then whittled that one down to the one I had used the most, which for me was Autofac (https://autofac.org/)

The service fabric support is not part of the main Autofac code base, but can be found here : https://github.com/autofac/Autofac.ServiceFabric with instructions on use here : https://alexmg.com/posts/introducing-the-autofac-integration-for-service-fabric

 

You could probably apply the code found here : https://github.com/autofac/Autofac.ServiceFabric  to your favorite container, but for me I am happy enough to just use Autofac, so lets see how to use it.

 

So it starts by including these 2 Nuget packages (these will bring in the other transitive dependencies you need)

XML
<package id="Autofac" version="4.6.2" targetFramework="net461" />
<package id="Autofac.ServiceFabric" version="1.0.0" targetFramework="net462" />

After you have that you will need build the Autofac container, and register the types you want, and also use the RegisterServiceFabricSupport() extension method

 

Here is an example that registers a stateless service as well as a few other classes that the stateless service in this demo code rely on

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Autofac;
using Autofac.Integration.ServiceFabric;
using Serilog;
using Stateless1.Serilog.ServiceFabric;

namespace Stateless1
{
    public class ContainerOperations
    {
        private static readonly Lazy<IContainer> _containerSingleton = 
        	new Lazy<IContainer>(CreateContainer);

        public static bool UseAzureWireup { get; set; }


        public static IContainer Container => _containerSingleton.Value;

        private static IContainer CreateContainer()
        {
            var builder = new ContainerBuilder();
            builder.RegisterModule(new GlobalAutofacModule());
            if (UseAzureWireup)
            {
                builder.RegisterServiceFabricSupport();
                // This needs to match the name of gthe type in 
                //PackageRoot\ServiceManifest.xml AND ApplicationPackageRoot\ApplicationManifest
                builder.RegisterStatelessService<Stateless1>("Stateless1Type");
            }
            return builder.Build();

        }
    }


    public class GlobalAutofacModule : Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            builder.Register<ILogger>(container => Program.LoggerSingleton.Value);

            builder.RegisterType<ServiceLoggerFactory>()
                .SingleInstance();

            builder.RegisterType<ServiceConfiguration>()
                .AsSelf()
                .SingleInstance();

            builder.Register(c => Log.Logger)
                .As<ILogger>()
                .SingleInstance();
        }
    }
}

NOTE: It is very important that you use the name of the service from the  ApplicationPackageRoot\ApplicationManifest when you register the services using Autofac, this allows the dynamic proxy that Autofac will build to find the correct type/version of the service to create

Here is the relevant code for this demo service thathas dependencies that need satisfying

C#
using System;
using System.Collections.Generic;
using System.Fabric;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.ServiceFabric.Services.Communication.Runtime;
using Microsoft.ServiceFabric.Services.Runtime;
using Serilog;
using Stateless1.Extensions;
using Stateless1.Serilog.ServiceFabric;

namespace Stateless1
{
    public class Stateless1 : StatelessService, IDisposable
    {
        private readonly ServiceConfiguration _configuration;
        private ILogger _log;

        public Stateless1(
            StatelessServiceContext context,
            ServiceLoggerFactory serviceLoggerFactory,
            ServiceConfiguration configuration
        ) : base(context)
        {
			.....
			.....
			.....
			.....
        }
    }
}

The final step for this Autofac example usage is the Program (main entry point) classes code, which is as shown below

C#
using System;
using System.Diagnostics;
using System.Fabric;
using System.Threading;
using System.Threading.Tasks;
using Autofac;
using Microsoft.ServiceFabric.Services.Runtime;
using Serilog;
using Serilog.Events;
using Stateless1.Extensions;

namespace Stateless1
{
    internal static class Program
    {

        private static ILogger _log = null;
        internal static readonly Lazy<ILogger> LoggerSingleton = 
        	new Lazy<ILogger>(ConfigureLogging);

        /// <summary>
        /// This is the entry point of the service host process.
        /// </summary>
        private static void Main()
        {
            _log = LoggerSingleton.Value;

            try
            {
                ContainerOperations.UseAzureWireup = true;

                using (var _container = ContainerOperations.Container)
                {
                    LogAutofacRegistrations(_container);

                    _log.Information("Starting {AppName} v{AppVersion} application", AppName, AppVersion);


                    // The ServiceManifest.XML file defines one or more service type names.
                    // Registering a service maps a service type name to a .NET type.
                    // When Service Fabric creates an instance of this service type,
                    // an instance of the class is created in this host process.

                    // Prevents this host process from terminating so services keep running.
                    Thread.Sleep(Timeout.Infinite);
                }
            }
            catch (Exception ex)
            {
                ServiceEventSource.Current.ServiceHostInitializationFailed(ex.ToString());
                _log.Fatal(ex, $"Error starting {typeof(Stateless1).Name} Service");
                throw;
            }
        }


        private static void LogAutofacRegistrations(IContainer container)
        {
            foreach (var registration in container.ComponentRegistry.Registrations)
            {
                foreach (var service in registration.Services)
                {
                    _log.Debug("Autofac: {dependency} resolves to {implementation}, {lifetime}, {sharing}, {ownership}",
                        service, registration.Activator.LimitType, registration.Lifetime, 
                        registration.Sharing, registration.Ownership);
                }
            }
        }
    }
}

It can be seen (or rather not seen) that the code above NEVER creates a new service directly. This in stark contrast to the "out of the box" template code you get when creating a new Service Fabric app in Visual Studio. So how do our required services get started?

Well like I said above the line where you register your required services in the Autofac module uses the special string values from the Application/ApplicationManifest.xml. So Autofac takes care of bringing up those instances, which keeps the deployment happy

 

And that is all there is to the IOC side of things with Autofac. Like I say if you have some other favorite IOC container you may have to see if it supports the service fabric, if not you may have to dig about in here : https://github.com/autofac/Autofac.ServiceFabric to see if you can apply the same techniques to your container of choice

 

 

Some Better Logging : Serilog/Seq

Ah logging, now what can you say you need to be able to log stuff right. Out of the box the Service Fabric template comes with this diagnostics writer (I have left that in the demo code for this article so you can see it), called "ServiceEventSource" which will have log messages turn up in the windows event log. Fair enough, but a bit crude for my taste.

There are many logging choices out there, but I do like the Serilog/Seq combination. I like Serilog cos it has lots of Sinks and like the concept of enrichers and it is a fully structured text logger, which is ace. I like Seq as it offers me a nice searchable web front end with pretty much 1 line of extra code.

Here is an example of Seq in action

Image 9

CLICK FOR BIGGER IMAGE

 

So what do we need to set this up?

Well you will need to start with these Nuget packages

XML
<package id="Serilog" version="2.6.0-dev-00925" targetFramework="net462" />
<package id="Serilog.Enrichers.Environment" version="2.1.3-dev-00736" targetFramework="net462" />
<package id="Serilog.Enrichers.Process" version="2.0.1" targetFramework="net462" />
<package id="Serilog.Formatting.Compact" version="1.0.1-dev-00922" targetFramework="net462" />
<package id="Serilog.Settings.AppSettings" version="2.1.2" targetFramework="net462" />
<package id="Serilog.Sinks.ColoredConsole" version="3.0.1" targetFramework="net462" />
<package id="Serilog.Sinks.Console" version="3.0.1" targetFramework="net462" />
<package id="Serilog.Sinks.File" version="4.0.0" targetFramework="net462" />
<package id="Serilog.Sinks.PeriodicBatching" version="2.1.1" targetFramework="net462" />
<package id="Serilog.Sinks.RollingFile" version="3.3.1-dev-00771" targetFramework="net462" />
<package id="Serilog.Sinks.Seq" version="4.0.0-dev-00150" targetFramework="net462" />

 

Then since we are using a cluster of nodes (recall the service fabric is a cluster of VMs), wouldn't it be nice if we could include the node that things were happening on in our log messages? I think so. How can we do that

 

Enrichers

Seq does allow you to supply runtime logging properties via things called "Enrichers" (see https://github.com/serilog/serilog/wiki/Enrichment). We can use these to add the extra logging values we want, lets see how

It starts with this simple helper class

C#
using System;
using System.Collections.Generic;
using System.Fabric;
using System.Linq;
using System.Runtime.Remoting.Contexts;
using System.Text;
using System.Threading.Tasks;
using Serilog.Core;
using Serilog.Events;

namespace Stateless1.Serilog.ServiceFabric
{
    public class ServiceEnricher<T> : ServiceFabricEnricher<T> where T : ServiceContext
    {
        private LogEventProperty _serviceName;
        private LogEventProperty _partitionId;
        private LogEventProperty _applicationName;

        public ServiceEnricher(T context) : base(context)
        {
        }

        public override void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
        {
            base.Enrich(logEvent, propertyFactory);

            if (_serviceName == null) _serviceName = 
                    propertyFactory.CreateProperty("ServiceName", Context.ServiceName);

            if (_partitionId == null) _partitionId = 
                    propertyFactory.CreateProperty("PartitionId", Context.PartitionId);

            if (_applicationName == null) _applicationName = 
                    propertyFactory.CreateProperty("ApplicationName", 
                        Context.CodePackageActivationContext.ApplicationName);

            logEvent.AddPropertyIfAbsent(_serviceName);
            logEvent.AddPropertyIfAbsent(_partitionId);
            logEvent.AddPropertyIfAbsent(_applicationName);
        }
    }
}

Where we use the service fabric ServiceContext object to give us the extra service values to log.

Ok so another helper class is up next, but this time this gives us "node" information to log.

C#
using System;
using System.Collections.Generic;
using System.Fabric;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Serilog.Core;
using Serilog.Events;

namespace Stateless1.Serilog.ServiceFabric
{
    public class ServiceFabricEnricher<T> : ILogEventEnricher where T : ServiceContext
    {
        protected T Context { get; }
        private LogEventProperty _nodeName;

        public ServiceFabricEnricher(T context)
        {
            Context = context;
        }

        public virtual void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
        {
            if (_nodeName == null)
            {
                _nodeName = propertyFactory.CreateProperty("NodeName", 
                    Context.NodeContext.NodeName);
            }
            logEvent.AddPropertyIfAbsent(_nodeName);
        }
    }
}

And then we need a custom logger factory

C#
using System;
using System.Collections.Generic;
using System.Fabric;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.ServiceFabric.Services.Runtime;
using Serilog;

namespace Stateless1.Serilog.ServiceFabric
{
    public class ServiceLoggerFactory
    {
        private ILogger _logger;
        private int _hasBeenEnriched;

        public ServiceLoggerFactory(ILogger logger)
        {
            _logger = logger;
        }

        public ILogger EnrichLoggerForStatelessServiceContext(StatelessService service)
        {
            if (Interlocked.Exchange(ref _hasBeenEnriched, 1) == 0)
            {
                _logger = _logger?.ForContext(new[] 
                    {
                        new ServiceEnricher<StatelessServiceContext<(service.Context)
                    }
                );
            }
            return _logger;
        }

        public ILogger GetEnrichedLogger()
        {
            return _logger;
        }
    }
}

We then need to apply this to the actual services such that they use the enrichers. We MUST only apply the enrichers in the services themselves as they ARE the things that will have information about the service/nodes, as the services ARE the things that ARE run on the service fabric cluster. Applying the enrichers any where else will not work. This work can be done in the service constructor code

C#
using System;
using System.Collections.Generic;
using System.Fabric;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.ServiceFabric.Services.Communication.Runtime;
using Microsoft.ServiceFabric.Services.Runtime;
using Serilog;
using Stateless1.Extensions;
using Stateless1.Serilog.ServiceFabric;

namespace Stateless1
{
    public class Stateless1 : StatelessService, IDisposable
    {
        private readonly ServiceConfiguration _configuration;
        private ILogger _log;

        public Stateless1(
            StatelessServiceContext context,
            ServiceLoggerFactory serviceLoggerFactory,
            ServiceConfiguration configuration
        ) : base(context)
        {
            if (configuration == null)
            {
                throw new ArgumentNullException(nameof(configuration));
            }


            _configuration = configuration;
			
			//ENRICH
            _log = serviceLoggerFactory.EnrichLoggerForStatelessServiceContext(this);

            _log.Information("Stateless1 constructed ok");
        }
    }
}

The very last thing to do is configure the logging. This is a standard thing to have to do with Serilog. This can be done in the Program (main entry point) code.

C#
using System;
using System.Diagnostics;
using System.Fabric;
using System.Threading;
using System.Threading.Tasks;
using Autofac;
using Microsoft.ServiceFabric.Services.Runtime;
using Serilog;
using Serilog.Events;
using Stateless1.Extensions;

namespace Stateless1
{
    internal static class Program
    {

        private static ILogger _log = null;
        internal static readonly Lazy<ILogger> LoggerSingleton = new Lazy<ILogger>(ConfigureLogging);

        /// <summary>
        /// This is the entry point of the service host process.
        /// </summary>
        private static void Main()
        {

            _log = LoggerSingleton.Value;

            .....
			.....
			.....
			.....
        }


        private static ILogger ConfigureLogging()
        {
            AppDomain.CurrentDomain.ProcessExit += (sender, args) => Log.CloseAndFlush();

            var configPackage = FabricRuntime.GetActivationContext().GetConfigurationPackageObject("Config");
            var environmentName = GetSetting(configPackage, "appSettings", 
				"simpleStatelessServiceFabricDemo:EnvironmentName");
            var seqRestrictedToMinimumLevel = GetSetting(configPackage, 
				"appSettings", "simpleStatelessServiceFabricDemo:serilog:write-to:Seq.restrictedToMinimumLevel");
            var seqServerUrl = GetSetting(configPackage, 
				"appSettings", "simpleStatelessServiceFabricDemo:serilog:write-to:Seq.serverUrl");
            var seqLogEventLevel = LogEventLevel.Verbose;
            Enum.TryParse(seqRestrictedToMinimumLevel, true, out seqLogEventLevel);

            var loggerConfiguration = new LoggerConfiguration()
                .WriteTo.Seq(seqServerUrl, restrictedToMinimumLevel: seqLogEventLevel)
                .Enrich.WithProperty("AppName", Program.AppName)
                .Enrich.WithProperty("AppVersion", Program.AppVersion)
                .Enrich.WithProperty("EnvName", environmentName);


            if (Environment.UserInteractive)
            {
                loggerConfiguration.WriteTo.ColoredConsole();
            }

            return Log.Logger = loggerConfiguration.CreateLogger();
        }


        private static string GetSetting(ConfigurationPackage configurationPackage, string section, string parameterName)
        {
            var param = configurationPackage.Settings.Sections[section].Parameters[parameterName];
            return param.IsEncrypted ? param.DecryptValue().ConvertToUnsecureString() : param.Value;
        }

        public static readonly string AppName = typeof(Program).Assembly.GetName().Name;
        public static readonly string AppVersion = typeof(Program).Assembly.GetInformationalVersion();
    }
}

Now there is some funky code in there that deals with getting values out of the many layers of service fabric config files. This is something we will be getting into below.

For now just understand that you may want "Verbose" in dev environment, and "Error" in production environment, which as stated right at the start of this article, would be handled by different "publish profile" / "parameter file" files. We will look at this next, but in order to get stuff out of these files we need to use the service fabric configuration system, which is why this code above looks a bit funky. It is essentially pulling values from the correct config location to match the specified environment, but as I say more on this later.

 

Getting To Know The New Config System

So we have seen several parts of this already above, we have seen that each Service within the Azure Service Fabric can have its own settings.xml file, and that these service(s) are owned by a overall application that has its own ApplicationManifest.xml file. So how do the parameters for the services work, can we provide parameters to them. Well yes we can.

The following diagram should illustrate the workflow I hope

Image 10

Lets have a look at these from top to bottom, as stated above it starts when you choose a publish profile file, which we looked at above but here is one anyway to remind you

XML
<?xml version="1.0" encoding="utf-8"?>
<PublishProfile xmlns="http://schemas.microsoft.com/2015/05/fabrictools">
  <ClusterConnectionParameters ConnectionEndpoint=""XXXXX.canadacentral.cloudapp.azure.com:19000 />
  <ApplicationParameterFile Path="..\ApplicationParameters\Cloud.xml" />
  <CopyPackageParameters CompressPackage="true" />
</PublishProfile>

This specifies which parameter file to use. In this case ApplicationParameters\Cloud.xml which looks like this

C#
<?xml version="1.0" encoding="utf-8"?>
<Application xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	Name="fabric:/SimpleStatelessServiceFabricDemo" 
	xmlns="http://schemas.microsoft.com/2011/01/fabric">
  <Parameters>
    <Parameter Name="Stateless1_InstanceCount" Value="-1" />
    <Parameter Name="simpleStatelessServiceFabricDemo:EnvironmentName" 
               Value="Cloud" />
    <Parameter Name="simpleStatelessServiceFabricDemo:serilog:write-to:Seq.restrictedToMinimumLevel" 
               Value="Information" />
    <Parameter Name="simpleStatelessServiceFabricDemo:serilog:write-to:Seq.serverUrl" 
               Value="http://localhost:5341" />
    <Parameter Name="simpleStatelessServiceFabricDemo:UseServiceFabricEnhancements" 
               Value="true" />
    <Parameter Name="simpleStatelessServiceFabricDemo:SomeSafeKey"
               Value="fdfdfdd" />
    <Parameter Name="simpleStatelessServiceFabricDemo:CertThumbPrint" 
               Value="ad0665f13101c6258cbfae6e091c3f3155fe50d0" />
  </Parameters>
</Application>

See how it simply provides parameters. These will then feed into the main ApplicationManifest.xml. Lets see that now

XML
<?xml version="1.0" encoding="utf-8"?>
<ApplicationManifest xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	ApplicationTypeName="SimpleStatelessServiceFabricDemoType" A
	pplicationTypeVersion="1.0.0" 
	xmlns="http://schemas.microsoft.com/2011/01/fabric">
  
  <Parameters>
    <Parameter Name="Stateless1_InstanceCount" 
		DefaultValue="-1" />
    <Parameter Name="simpleStatelessServiceFabricDemo:EnvironmentName" 
		DefaultValue="dev90" />
    <Parameter Name="simpleStatelessServiceFabricDemo:serilog:write-to:Seq.restrictedToMinimumLevel" 
		DefaultValue="" />
    <Parameter Name="simpleStatelessServiceFabricDemo:serilog:write-to:Seq.serverUrl" 
		DefaultValue="" />
    <Parameter Name="simpleStatelessServiceFabricDemo:UseServiceFabricEnhancements" 
		DefaultValue="false" />
    <Parameter Name="simpleStatelessServiceFabricDemo:SomeSafeKey" 
		DefaultValue="" />
    <Parameter Name="simpleStatelessServiceFabricDemo:CertThumbPrint" 
		DefaultValue="" />
  </Parameters>
  
  <!-- Import the ServiceManifest from the ServicePackage. The ServiceManifestName and ServiceManifestVersion 
       should match the Name and Version attributes of the ServiceManifest element defined in the 
       ServiceManifest.xml file. -->
  <ServiceManifestImport>
    <ServiceManifestRef ServiceManifestName="Stateless1Pkg" ServiceManifestVersion="1.0.0" />
    <ConfigOverrides>
      <ConfigOverride Name="Config">
        <Settings>
          <Section Name="appSettings">
            <Parameter Name="simpleStatelessServiceFabricDemo:EnvironmentName" 
				Value="[simpleStatelessServiceFabricDemo:EnvironmentName]" />
            <Parameter Name="simpleStatelessServiceFabricDemo:serilog:write-to:Seq.restrictedToMinimumLevel" 
				Value="[simpleStatelessServiceFabricDemo:serilog:write-to:Seq.restrictedToMinimumLevel]" />
            <Parameter Name="simpleStatelessServiceFabricDemo:serilog:write-to:Seq.serverUrl" 
				Value="[simpleStatelessServiceFabricDemo:serilog:write-to:Seq.serverUrl]" />
            <Parameter Name="simpleStatelessServiceFabricDemo:UseServiceFabricEnhancements" 
				Value="[simpleStatelessServiceFabricDemo:UseServiceFabricEnhancements]" />
          </Section>
          <Section Name="encryptedSettings">
            <Parameter Name="simpleStatelessServiceFabricDemo:SomeSafeKey" 
                       Value="[simpleStatelessServiceFabricDemo:SomeSafeKey]" 
                       IsEncrypted="true" />
          </Section>
        </Settings>
      </ConfigOverride>
    </ConfigOverrides>
  </ServiceManifestImport>
  ....
  ....
  ....
  ....
  
</ApplicationManifest>

There are a few points to note here

  • That we have a parameters section here
  • That we import stuff from the actual service file by using this line ServiceManifestName="Stateless1Pkg" ServiceManifestVersion="1.0.0"
  • That we override the parameters from the lower level Service. If there were more than 1 service in this application, we would expect to see more than one of these import/override sections (if parameters were needed for each service that is)
  • The parameters are actually used as substitutions for the override. The syntax for this is simply the name of the parameter in square braces, an example being [simpleStatelessServiceFabricDemo:SomeSafeKey] say

 

ok so now that we have seen how the parameters come in, and get overriden at the top level, we are nearly done and just need to examine how the very bottom layer (the actual service) declares the parameters to begin with. This is done per service in a Settings.xml file. Here is the demo apps one:

XML
<?xml version="1.0" encoding="utf-8" ?>
<Settings xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
          xmlns="http://schemas.microsoft.com/2011/01/fabric">
  <!-- Add your custom configuration sections and parameters here -->
  <Section Name="appSettings">
    <Parameter Name="simpleStatelessServiceFabricDemo:EnvironmentName" 
               Value="" MustOverride="true" 
               IsEncrypted="false"/>
    <Parameter Name="simpleStatelessServiceFabricDemo:serilog:write-to:Seq.restrictedToMinimumLevel" 
               Value="" MustOverride="true" 
               IsEncrypted="false"/>
    <Parameter Name="simpleStatelessServiceFabricDemo:serilog:write-to:Seq.serverUrl" 
               Value="" 
               MustOverride="true" 
               IsEncrypted="false"/>
    <Parameter Name="simpleStatelessServiceFabricDemo:UseServiceFabricEnhancements" 
               Value="" 
               MustOverride="true" 
               IsEncrypted="false"/>
  </Section>
  <Section Name="encryptedSettings">
    <Parameter Name="simpleStatelessServiceFabricDemo:SomeSafeKey" 
               Value="" 
               MustOverride="false" 
               IsEncrypted="true"/>
  </Section>
</Settings>

And thats really how the new config system hangs together.

Lets Keep Those Details Safe

Ok so we have seen that we can have encrypted/non-encrypted parameters. It is probably a good idea to encrypt the connection strings (in my opinion). This article is a MUST read as this is what we will be doing next

https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-application-secret-management

 

The basic idea is that we use a cipher certificate to encrypt our sensitive data, and have the service fabric be aware of the cerifcate we used. This means telling it what cert to try and find, and telling it which parameters are encrypted. The certificates are typically created in Azure KeyVault and then MUST be installed on ALL nodes in the cluster (and your dev VM to if you wish to run the service with the IsEncrypted="true" for your parameters. For my dev work, I regularly change the params to IsEncrypted="false" and just change them back just before I check in. But it's your call.

 

This diagram shows the basic idea

Image 11

CLICK FOR BIGGER IMAGE

 

 

 

Creating A Self Sign Cert

A data encipherment certificate is used strictly for encryption and decryption of configuration values in a service's Settings.xml and is not used for authentication or signing of cipher text. The certificate must meet the following requirements:

  • The certificate must contain a private key.
  • The certificate must be created for key exchange, exportable to a Personal Information Exchange (.pfx) file.
  • The certificate key usage must include Data Encipherment (10), and should not include Server Authentication or Client Authentication.

You can create a self sign certificate to test stuff out, though for a proper production release you should use KeyVault/PowerShell to create the certificates in the portal, and then download them as a .pfx file and install them in the local machine store.

For now though lets create a new cipher certificate to test with. This can be done with the following PowerShell command

New-SelfSignedCertificate -Type DocumentEncryptionCert -KeyUsage DataEncipherment -Subject mydataenciphermentcert -Provider 'Microsoft Enhanced Cryptographic Provider v1.0'

If you run this in PowerShell and it will install a new certificate in the your local machine store. This is shown here

Image 12

To clarify this has been installed you can open the local machine store.

To do this (I am using Windows 10 so instructions may vary)

Click on the windows icon

Image 13

Then start typing "cert" and you will see the following

Image 14

click that, then navigate to the following location you should see a new certificate created at same time as the PowerShell command was run above

Image 15

CLICK FOR BIGGER IMAGE

 

The Importance Of Powershell (current/latest versions)

We had an issue at work where we were using PowerShell to create the encryption certificates, and we would always get some weird "ACL certificate" exception. This turned out to be due to an older version of the Azure PowerShell encryption module being used. So make sure you have the latest module in use

 

Installing The Self Sign Cert

Ok so lets say you did things for a production setup, or non playground experiment, and you have the .pfx file (remember the type MUST be "DocumentEncryptionCert") you will need to install that into the local machine store. This can be done as follows. Please bear in mind that a proper environment (non self signed certificate) should be password protected.

Image 16

 

Using The Self Sign Cert

So now that we have seen the nice new shiny config system described above, how do we make sure our connection strings are nice and safe? There are a couple of steps to make this happen.

 

Step 1 : Grab the certificate thumbprint

The easiest way it to grab the thumbprint from the Azure KeyVault portal blade if you went that way (which you should). However if you have the certificate installed into the local machine store, you can grab the thumbprint as follows

Image 17

Copy the value from the thumbprint area. There is a bug with this dialog though, and a hidden character exists at the beginning of the thumbprint. The best way to see that is just copy the text and paste it into PowerShell window, and remove the special character and spaces, then keep final string safe

Image 18

So for this example we would end up with final thumbprint value of

b92961f2fd7ad32d8f3b1a83b216e79f296857af

Step 2 : Use the certificate thumbprint in the ApplicationManifest.xml

You will now need to use the thumbprint from step 1 above to add to the ApplicationManifest.xml (remember this certificate WILL need to be available on all node VMs in the cluster)

XML
<?xml version="1.0" encoding="utf-8"?>
<ApplicationManifest xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ApplicationTypeName="SimpleStatelessServiceFabricDemoType" ApplicationTypeVersion="1.0.0" xmlns="http://schemas.microsoft.com/2011/01/fabric">
  .....
  .....
  .....
  
  <Principals>
    <Users>
      <User Name="SfUser" AccountType="NetworkService" />
    </Users>
  </Principals>
  <Policies>
    <SecurityAccessPolicies>
      <SecurityAccessPolicy ResourceRef="EnvCipherCert" PrincipalRef="SfUser" ResourceType="Certificate" />
    </SecurityAccessPolicies>
  </Policies>
  <Certificates>
    <SecretsCertificate X509FindValue="[simpleStatelessServiceFabricDemo:CertThumbPrint]" Name="EnvCipherCert" X509FindType="FindByThumbprint" />
  </Certificates>
</ApplicationManifest>

Now make sure you have updated the relevant Parameters file. Here is an example of one that I updated with the thumbprint

Image 19

The advantage of using a parameter file for this rather than hardcode it in ApplicationManifest.xml is that you can have a different one per environment should you wish to.

 

Step 3 : Make sure the parameters you wish to use are encrypted

Ok so now we need to make sure the parameters you want to encrypt are actually specified as IsEncrypted="true". We do this in both the

ApplicationManifest.xml
and Setting.xml that the ApplicationManifest.xml parameters override.

 

ApplicationManifest.xml

Image 20

Example service Settings.xml

Image 21

 

Step 4 : Encrypt the values

Now we need to encrypt the values using the certificate that has been used. I have provided a little PowerShell script in the code that you can use

Encrypt-Secret.ps1
which you simply call from powershell like this

.\Encrypt-Secret.ps1 "b92961f2fd7ad32d8f3b1a83b216e79f296857af" "I want this text encrypted"

The result will be something like this

Image 22

Now copy all the text inside the <START ENCRYPTED TEXT> to <END ENCRYPTED TEXT> and make sure its all one line with no spaces. Then we need to copy it to the relevant parameter file. Here is an example of where an encrypted parameter value may go

Image 23

Step 5 : Decrypt the values

Ok so now we just need to unencrypt the values. How do we do that?

Well this is what I came up with. For me I am using a ServiceConfiguration class that looks like this to store all my settings. So I need to update this one with the unencrypted values at startup.

Here is my ServiceConfiguration property bag kind of class

C#
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Stateless1
{
    public class ServiceConfiguration
    {
        public ServiceConfiguration()
        {
            SomeDummyValue = ConfigurationManager.AppSettings["SomeDummyValue"];
            SomeSafeKey = ConfigurationManager.AppSettings["SomeSafeKey"];
        }


        public string SomeDummyValue { get; set; }
        public string SomeSafeKey { get; set; }
    }
}

So all that's left is to get the unencrypted values to put into this helper class. Here is how I do that

C#
namespace Stateless1
{
    public class Stateless1 : StatelessService, IDisposable
    {
        private readonly ServiceConfiguration _configuration;
        private ILogger _log;

        public Stateless1(
            StatelessServiceContext context,
            ServiceLoggerFactory serviceLoggerFactory,
            ServiceConfiguration configuration
        ) : base(context)
        {
            if (configuration == null)
            {
                throw new ArgumentNullException(nameof(configuration));
            }


            _configuration = configuration;
            _log = serviceLoggerFactory.EnrichLoggerForStatelessServiceContext(this);

            EnhanceConfigurationForServiceFabric(context, configuration);

            _log.Information("Stateless1 constructed ok");
        }

        private void EnhanceConfigurationForServiceFabric(StatelessServiceContext context, ServiceConfiguration configuration)
        {
            try
            {
                var configPackage = context.CodePackageActivationContext.GetConfigurationPackageObject("Config");
                var appSettings = configPackage.Settings.Sections["appSettings"];
                var useServiceFabricEnhancements = bool.Parse(appSettings.Parameters["simpleStatelessServiceFabricDemo:UseServiceFabricEnhancements"].Value);
                var environmentName = appSettings.Parameters["simpleStatelessServiceFabricDemo:EnvironmentName"].Value;

                if (useServiceFabricEnhancements)
                {
                    UpdateEncryptedValue(configPackage, "simpleStatelessServiceFabricDemo:SomeSafeKey", 
                    	(someSafeKey) => configuration.SomeSafeKey = someSafeKey);
                }
                else
                {
                    _log.Information("Stateless1 configuration set-up using default Environment='{EnvironmentName}", environmentName);
                }
            }
            catch (Exception ex)
            {
                _log.Error(ex, "Failed to contruct Stateless1");
                throw;
            }
        }


        private void UpdateEncryptedValue(ConfigurationPackage configurationPackage, 
        	string connectString, Action<String> updateServiceConfigurationAction)
        {
            var param = configurationPackage.Settings.Sections["encryptedSettings"].Parameters[connectString];
            updateServiceConfigurationAction(param.IsEncrypted
                ? param.DecryptValue().ConvertToUnsecureString()
                : param.Value);
        }
    }
}

What About Real Life Cluster?

Yes so far we have been talking about testing a local cluster, that is one running locally on your own box. So for a real cluster you will need to get remote access to the actual nodes that form the real cluster (as created in Portal/PowerShell) and install the certificate on those VMs as described in steps above.

 

 

Conclusion

We are using the Azure Service Fabric a fair bit, and it has not let us down so far. We mainly use it for siloed micro services that don't interact with each other that much, so we have not really needed to open any ports. However the scaling aspect of it, and the fact that this can be done via C# FabricClient and PowerShell on demand should we need to scal further is very sweet

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)