devr24 / Cloud.Core

Cloud core package, contains interfaces used in all other packages.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Cloud.Core

Build status Code Coverage Cloud.Core package in Cloud.Core feed in Azure Artifacts

Cross package functionality for all Cloud Core projects. Contains the main interface contracts used as a top level API for other packages, this allows us to easily build cross-package functionality without creating a massive dependency chain.

Read full Api documentation

Usage

The Cloud.Core package contains the following interfaces:

Logging

  • ITelemetryLogger - Contract for ILogger implementations.

State / Features

  • IFeatureFlag - Feature flag contract - can be used with services such as launch darkly.
  • IStateStorage - State storage contract - implemented within Cloud Functionality providers to enable State save/load within the AppHost.
  • IAuditLogger - Common audit logging contract.
  • INamedInstance - Provides accessibility to use NamedInstanceFactory (see below).

Cloud Functionality

  • IAuthentication - Contract for bearer token authentication implementations.
  • IIdentityProvider - Contract for identity providers to supply identity information.
  • IBlobStorage - Contract for a blob storage implementations.
  • ISecureVault - Contract for a secure vault implementations.
  • IMessenger & IReactiveMessenger - Contract for defining a message queuing implementations.
  • ITableStorage - Contract for data table storage (not relational table storage).

Integrations

  • IAddressLookup - Address search contract.
  • IEmailProvider - Email provider contract.
  • ISmsProvider - Sms provider contract.
  • ITemplateMapper - Template injector service contract.
  • IUrlShortener - Url shorten service contract.

Various extensions methods exist within this API. Any general purpose reuable extension methods should be added to this package.

Cloud.Core specific exceptions also exist within this API.

Code Samples

DataAnnotations - Attribute Validation

Take this model:

private class TestClass
{
	[Required]
	public string RequiredTest { get; set; }

	[StringLength(10)]
	public string StringLengthTest { get; set; }

	[Range(1, 100)]
	public int RangeTest { get; set; }

	[DataType(DataType.EmailAddress)]
	public string DataTypeTest { get; set; }

	[MaxLength(10)]
	public string MaxLengthTest { get; set; }

	// Alpha characters only (max length 25).
	[RegularExpression(@"^[a-zA-Z]{1,25}$")]
	public string RegExTest { get; set; }
}

We can see it's decorated using DataAnnotation attributes that can be used for validation in an Asp.Net MVC application. In MVC the model state (along with the controller base) will work out the valid state of any model passed to a controller. A code sample as follows:

[HttpPost]
public IActionResult Create(TestClass example)
{
	if (ModelState.IsValid == false)
	{
		return BadRequest(new ApiErrorResult(ModelState));
	}

	... do work
}

Unfortunately, its not possible to do this in normal applications without rolling your own implementation of the validation. Cloud.Core has an easy to use base class that will do just that. When the base class AttributeValidator is inherited by any model, it allows the same DataAnnotations validation using attributes to be carried out on any class (outside of MVC applications). Here's a code sample...

Model updated to inherit from AttributeValidator:

private class TestClass : AttributeValidator
{
	[Required]
	public string RequiredTest { get; set; }

	[StringLength(10)]
	public string StringLengthTest { get; set; }

	[Range(1, 100)]
	public int RangeTest { get; set; }

	[DataType(DataType.EmailAddress)]
	public string DataTypeTest { get; set; }

	[MaxLength(10)]
	public string MaxLengthTest { get; set; }

	// Alpha characters only (max length 25).
	[RegularExpression(@"^[a-zA-Z]{1,25}$")]
	public string RegExTest { get; set; }
}

Validation code is as follows:

public void CheckModelIsValid(TestClass example)
{
	var validationResult = example.Validate();

	if (validationResult.IsValid == false) 
	{
		foreach(var err in validationResult.Errors) 
		{
			Console.WriteLine(err.Message);
		}
	}
	
	...
}

Another validation example could be:

try
{
    example.ThrowIfInvalid();
}
catch (ValidateException vx)
{
    foreach(var err in vx.Errors)
	{
	    Console.WriteLine($"Members: {string.Join(",", err.MemberNames)}, Message: {err.ErrorMessage}");
	}
}

This now saves manually implementing validation methods in your POCO classes.

Named Instance Factory

We regularly use IServiceCollection within Cloud.Core applications. One problem we needed to solve was how to have multiple instances of the same class implementation of any of our interfaces. It's a problem that the Factory design pattern can help us with.

Taking this example class:

    public interface ITestInterface : INamedInstance {
    }

    public class TestInstance1: ITestInterface
    {
        public TestInstance1(string name) {
            Name = name;
        }
        public string Name { get; set; }
    }

    public class TestInstance2: ITestInterface
    {
        public TestInstance2(string name) {
            Name = name;
        }
        public string Name { get; set; }
    }

    public class TestInstance3: ITestInterface
    {
        public TestInstance3(string name) {
            Name = name;
        }
        public string Name { get; set; }
    }

We can use the factory in the following way:

    var i1 = new TestInstance1("testInstance1");
    var i2 = new TestInstance2("testInstance2");
    var i3 = new TestInstance3("testInstance3");

    var factory = new NamedInstanceFactory<ITestInterface>(new List<ITestInterface> { i1, i2, i3 });
    var lookup1 = factory["testInstance1"];
    var lookup2 = factory["testInstance2"];
    var lookup3 = factory["testInstance3"];

Typically though this will all be used with .net's Dependency Injection (IServiceCollection), as follows:

services.AddSingleton<TestInstance>("x");
services.AddSingleton<TestInstance>("y");
services.AddSingleton<TestInstance>("z");
servuces.AddSingleton<NamedInstanceFactory<TestInterface>>();

In the sample above with Dependency Injection, the NamedInstanceFactory will resolve instance X/Y/Z automatically, as they'll be injected into the constuctor during DI initialisation.

Sensitive Data Masking

There are a number of useful attributes that can be added to classes to mark properties of the classes as personal or sensitive. When used in conjuntion with the logging implementations, personal/sensitive data fields will be masked when the log files are being written.

Example, take this class:

public class PersonAccount {

    public string PersonId { get; set; }
	 
	[PersonalData]
	public string Name { get; set; }
	 
	[PersonalData]
	public DateTime Dob { get; set; }
	 
	[PersonalData]
	public string PhoneNumber { get; set; }

	[SensitiveInfo]
	public string Password { get; set; }
}

We can see fields have been marked as Cloud.Core.Attributes.PersonalData or Cloud.Core.Attributes.SensitiveInfo. When used with an ITelemetryLogger implementation, these properties are masked automatically. Read more here

Here's an example of the masking in action (taken from one of the tests for this package):

var personDetails = new PersonAccount {
	PersonId = "MY IDENTITY",
	Name = "Robert",
	Dob = new DateTime(1990, 1, 1),
	PhoneNumber = "+44289012345",
	Password = "Password123"
};

var objAsDictionary = personDetails.AsFlatDictionary(StringCasing.Unchanged, true); // where true is specifying masking turned on...

// Giving this result:
objAsDictionary["ShopperId"].Should().Be("MY IDENTITY");

// Automatically masked fields
objAsDictionary["Name"].Should().Be("*****"); 
objAsDictionary["PhoneNumber"].Should().Be("*****");
objAsDictionary["Password"].Should().Be("*****");

// Masking for any type that is not string reverts to the default value for the type
objAsDictionary["Dob"].Should().Be(new DateTime()); 

This masking automatically works for Microsoft.AspNetCore.Identity.PersonalData attribute also, if you want to use something which is standard.

NOTE: You can treat all values as strings by using the AsFlatStringDictionary instead, so that instead of using the default value for the type, it would be an emtpy string.

Design

The Interfaces held in this package allow for abstraction of services. Each service can have a "factory" implementation. This is known as the Abstract Factory design pattern, as covered in the link:

Cloud.Core Project Templates

The Cloud.Core project templates can be found in the Cloud.Core Templates folder - read more about modifying and using the templates here.

Test Coverage

A threshold will be added to this package to ensure the test coverage is above 80% for branches, functions and lines. If it's not above the required threshold (threshold that will be implemented on ALL of the core repositories to gurantee a satisfactory level of testing), then the build will fail.

Compatibility

This package has has been written in .net Standard and can be therefore be referenced from a .net Core or .net Framework application. The advantage of utilising from a .net Core application, is that it can be deployed and run on a number of host operating systems, such as Windows, Linux or OSX. Unlike referencing from the a .net Framework application, which can only run on Windows (or Linux using Mono).

Setup

This package is built using .net Standard 2.1 and requires the .net Core 3.1 SDK, it can be downloaded here: https://www.microsoft.com/net/download/dotnet-core/

IDE of Visual Studio or Visual Studio Code, can be downloaded here: https://visualstudio.microsoft.com/downloads/

How to access this package

All of the Cloud.Core.* packages are published to a public NuGet feed. To consume this on your local development machine, please add the following feed to your feed sources in Visual Studio: https://dev.azure.com/cloudcoreproject/CloudCore/_packaging?_a=feed&feed=Cloud.Core

For help setting up, follow this article: https://docs.microsoft.com/en-us/vsts/package/nuget/consume?view=vsts

About

Cloud core package, contains interfaces used in all other packages.

License:MIT License


Languages

Language:C# 100.0%