Design pattern for accessing static data - c#

I have a scenario where I have a set of credentials for each environment
e.g. for dev env username1/pwd1, for qa env username2/pwd2, for staging username3/pwd3 and so on.
Now I want to create a class which will return me a set of credentials based on the env I feed to it.
All the data has to go within code (as per my brilliant boss, no xml files and all), what design pattern I could use so that the code will be elegant and data can be made extensible in future?

Personally, I am used to create a channel attribute:
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
public sealed class AssemblyChannelAttribute : Attribute
{
public ChannelType Type { get; private set; }
public AssemblyChannelAttribute(ChannelType type)
{
this.Type = type;
}
}
public enum ChannelType
{
Dev,
Beta,
PreProd,
Prod
}
This attribute is set on the Assembly:
#if DEBUG
// In release mode, this attribute is set by the MSBuild script
[assembly: AssemblyChannel(ChannelType.Dev)]
#else
As the comment said, the value of the attribute is set on compile time by my MSBuild script (too tied to my project to show you this part).
Once you have setup all of this, you can create a simple singleton like this:
public class Credentials
{
private static readonly Lazy<Credentials> instanceHolder =
new Lazy<Credentials>(() => new Credentials());
public IReadOnlyDictionary<string, string> Passwords { get; private set; }
public Credentials Instance { get { return instanceHolder.Value; } }
private Credentials()
{
var channel = typeof(Credentials).Assembly
.GetCustomAttributes<AssemblyChannelAttribute>()
.ElementAt(0)
.Type;
switch (channel)
{
case ChannelType.Dev:
this.Passwords = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>
{
["User1"] = "Pwd1",
["User2"] = "Pwd2",
// etc
});
break;
case ChannelType.Beta:
// etc
break;
case ChannelType.PreProd:
// etc
break;
case ChannelType.Prod:
// etc
break;
}
}
}
Then you can access your credentials like this:
var password = Credentials.Instance.Passwords["User1"];

If you use .Net core, you could use the configuration techniques.
They are very powerful and work in asp .net as well as console programs
They are very configurable and composable (pass in config via cmd and json for example)
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration

What you're after is the Multiton design pattern.

Related

How to declare global variable in Program cs and use it in controllers in .NET 6.0 Web Api

I have default Program.cs file from Web Api template in .NET 6.0.
I am adding variable "test" so I can use its value in controllers.
var builder = WebApplication.CreateBuilder(args);
const string test = "test123";
builder.Configuration.Bind(test);
//rest of the file...
And now I want to use variable "test" outside Program.cs but I have no idea how to do it. I cannot just simply use it because when trying to read it in controller like this:
string localVar = test;
I am getting an error "'test' is not null here. Cannot use local variable or local function declared in a top-level statement in this context".
This is probably some stupid mistake but I can't figure it out...
Starting C# 9, we don't need to explicitly mention the Main method in Program.cs file as we can use the top-level statements feature. However, it doesn't mean that we shouldn't use the default Program class in the created file at all. In your case, you have a need to define the static/const property so you can change the newly created structure into the old one.
namespace WebApplication;
public class Program
{
public static string Test { get; private set; }
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
Program.Test = "approach1";
builder.Services.Configure<MyOptions>(x => x.Test = "approach2");
///
}
public class MyOptions
{
public string Test { get; set; }
}
I assumed that you have a need to set the value to the Program.Test field during runtime, so in the first approach, I used the static field with a private set; accessor instead of the constant.
In the second approach, I used the C# options feature to configure the MyOptions.Test field value, this will be very flexible and useful to write unit tests later. But, you need to inject the MyOptions class wherever is required.
In the below controller template, I specified how to access the configured values at Program.cs file, inside the Get method
public class TestController : ControllerBase
{
private readonly MyOptions _myOptions;
public TestController (IOptions<MyOptions> myOptions)
{
_myOptions = myOptions.Value;
}
public IActionResult Get()
{
string test1 = Program.Test;
string test2 = _myOptions.Test;
///
}
}
Add public partial class Program { } at the very end of your Program.cs file and add constant, property or whatever you like in there.

Running tests for different environment and different versions C#-MSTest

I am creating an automated test framework to test an API using .NetCore, RestSharp and MsTest. I am looking for ideas to run my tests for 4 different environments (2 different countries, test and live environment each). I will access test data through appsettings.json file (e.g appsettings.eutest.json, appsetings.detest.json, etc).
I could use something like [DynamicData()] to pass each test an argument with the key to access each individual .json file, but there is a subset of tests, compatibility tests, where I need to run also again different versions of the API, this is needed to ensure backward compatibility.
[DynamicData("NameOfPropertyWithEnvironments", typeof(BaseClass))]
[TestMethod]
public void RegularTest(string envToTest)
{
//-- Logic to access data on the .json file
}
[DynamicData("NameOfPropertyWithVersions", typeof(BaseClass))]
[TestMethod]
public void CompatibilityTest(int versionBackToTest)
{
}
So far, I have been using also [DynamicData()] to pass the versions to be tested as an argument of those particular tests, but I am lost as to how to combine different environments and different versions.
Thanks in advance!
To run these tests locally, you can create a local.settings.json file in your test project with APIEndpoint and APIVersion settings.
Next, implement a configuration manager that can firstly determine whether you are running the tests locally. If so, read the settings from local.settings.json. If not, read from environment variables. Here is an example of what I use:
namespace SomeIntegrationTests.configuration
{
class Configuration
{
public string API_ENDPOINT { get; set; }
public string API_VERSION { get; set; }
}
class ConfigurationManager
{
private const string LocalSettingsFile = "local.settings.json";
private static Configuration _configuration;
public static Configuration Get()
{
if (_configuration == null)
{
if (IsLocalRun())
{
var localSettings = ReadFile(LocalSettingsFile);
_configuration = JsonConvert.DeserializeObject<Configuration>(localSettings);
}
else
{
_configuration = GetFromEnvironmentVariables();
Console.WriteLine($"Retrieved configuration from environment variables.");
}
}
return _configuration;
}
private static Configuration GetFromEnvironmentVariables()
{
var environment = GetEnvironmentVariable("CURRENT_ENVIRONMENT");
Console.WriteLine($"Current environment is {environment}");
return new Configuration
{
API_ENDPOINT = GetEnvironmentVariable($"API_ENDPOINT.{environment}"),
API_VERSION = GetEnvironmentVariable($"API_VERSION.{environment}")
};
}
private static string GetEnvironmentVariable(string key)
{
return Environment.GetEnvironmentVariable(key);
}
private static bool IsLocalRun()
{
var environment = GetEnvironmentVariable("CURRENT_ENVIRONMENT");
return environment == "local";
}
private string ReadFile(string file)
{
var projectFolderLocation = // depends on the type of implementation get location of where the local.settings.json is located;
var filePath = Path.Combine(projectFolderLocation, "configuration", file);
return File.ReadAllText(filePath);
}
}
}
On your local pc, add an environment variable for "CURRENT_ENVIRONMENT" with value "local" and add local.appsettings.json in your project. In your pipeline add a step to set environment variable values accordingly. When you run the tests during pipeline, settings will be used from the environment variables settings.
Then from your test, you can load the Configuration via ConfigurationManager implementation and then call API_ENDPOINT and API_VERSION.
class SomethingTests
{
private readonly string APIEndpoint;
private readonly string APIVersion;
public SomethingTests()
{
var configuration = ConfigurationManager.Get();
APIEndpoint = configuration.API_ENDPOINT;
APIVersion = configuration.API_VERSION;
}
[TestMethod]
public void SomethingTests()
{
// use APIEndpoint & APIVersion
}
}
As for running tests against production environments, this doesn't seem like a good idea. You don't want to insert test data on production. Implement automated deployment steps to ensure you tested your API code on test environments and deploy to production after that.
Does this help?

Injecting configuration objects in a .NET application

I'm reviewing a code I wrote sometime before and I noticed I did in past
public class Linq2DbSettings : ILinqToDBSettings
{
public IEnumerable<IDataProviderSettings> DataProviders
{
get { yield break; }
}
public string DefaultConfiguration =>
"SqlServer"; // lets set your configuration as default, so you can call just new DataContext() or new DataConnection()
public string DefaultDataProvider => ProviderName.SqlServer; // and set default database type
public IEnumerable<IConnectionStringSettings> ConnectionStrings
{
get
{
yield return
new ConnectionStringSettings
{
Name = "SqlServer",
ProviderName = "SqlServer",
ConnectionString =ConfigurationManager.ConnectionStrings["default"].ConnectionString
};
}
}
}
public class ConnectionStringSettings : IConnectionStringSettings
{
public string ConnectionString { get; set; }
public string Name { get; set; }
public string ProviderName { get; set; }
public bool IsGlobal => false;
}
Even if it's related to Linq2Db it appies to all classes where I need to resolve the container.
As you can see I'm using here ConfigurationManager.ConnectionStrings["default"] while it would be best to use IConfiuration from Microsoft.Extensions.Configuration
To do so I should resolve the IConfiguration item, registered in SimpleInjector's Container.
In past I used a wrapper
public static class ContainerWrapper
{
public static Container Container { get; set; }
}
and I assigned it as
ContainerWrapper.Container = container;
container.Verify();
But I think it's a wrong approach, what's the best solution?
My advise is the following:
Keep your configuration objects narrow; don't create wide configuration objects that contain a large set of propertiesand are used by many consumers.
Prevent configuration objects from reading from the configuration system. Make them immutable behaviorless data objects instead, and supply them with configuration values in their constructor. This prevents the configuration object from becoming a Volatile Dependency.
Remove the interfaces on your configuration objects. Interfaces are meant to hide behavior, but the configuration objects should only contain data.
Load configuration values during application startup, and register those configuration objects as Singleton in the container.

Access App key data from class libraries in .NET Core / ASP.NET Core

To access App Keys in a class library, do we need to do the following code in every class library and class where we need to access a AppKey?
public static IConfigurationRoot Configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
This is what I found in Microsoft docs, but this looks very redundant.
Startup class in a project as below
public class Startup
{
public IConfigurationRoot Configuration { get; set; }
public Startup()
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json");
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework().AddEntityFrameworkSqlServer()
.AddDbContext<DbContext>(options =>
options.UseSqlServer(Configuration["Data:MyDb:ConnectionString"]));
}
}
Then how should I inject this "IConfigurationRoot" in each class of a project. And do I have to repeat this Startup class in each class Library? Why is this not part of .NET Core Framework?
The recommended way is to use the options pattern, provided by Microsoft and used heavily in ASP.NET Core.
Basically you create a strong typed class and configure it in the Startup.cs class.
public class MySettings
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
and initialize it in the Startup class.
// load it directly from the appsettings.json "mysettings" section
services.Configure<MySettings>(Configuration.GetSection("mysettings"));
// do it manually
services.Configure<MySettings>(new MySettings
{
Value1 = "Some Value",
Value2 = Configuration["somevalue:from:appsettings"]
});
then inject these options everywhere you need it.
public class MyService : IMyService
{
private readonly MySettings settings;
public MyService(IOptions<MySettings> mysettings)
{
this.settings = mySettings.Value;
}
}
By the principle of Information Hiding in Object-Oriented Programming, most classes should not need to have access to your application configuration. Only your main application class should need to directly have access to this information. Your class libraries should expose properties and methods to alter their behavior based on whatever criteria their callers deem necessary, and your application should use its configuration to set the right properties.
For example, a DateBox shouldn't need to know how timezone information is stored in your application configuration file - all it needs to know is that it has a DateBox.TimeZone property that it can check at runtime to see what timezone it is in.

How can a component provide other components?

I want to have a component register other components in the registry as / after it's constructed. Let's say I have the following components:
interface IConfiguration
{
string SourceDirectory { get; }
string TargetDirectory { get; }
// other primitive-typed configuration parameters
}
class FileConfiguration : IConfiguration
{
// read parameters from some config file
}
class SourceDirectoryWrapper
{
public byte[] ReadFile(string filename)
{
// read a file from the source directory
}
public string Directory { get; set; }
}
class TargetDirectoryWrapper
{
public byte[] WriteFile(string filename)
{
// write a file into the source directory
}
public string Directory { get; set; }
}
class DirectoryWrapperFactory
{
public DirectoryWrapperFactory(IConfiguration config)
{
var source = new SourceDirectoryWrapper {
Directory = config.SourceDirectory
};
var target = new TargetDirectoryWrapper {
Directory = config.SourceDirectory
};
}
}
The components FileConfiguration and DirectoryWrapperFactory can be registered as is usual.
However, what I'd like to accomplish is to somehow "outject" the source and target objects created in DirectoryWrapperFactory. The basic idea is that different environments might require different configuration providers. (And even if not, I think it's a good idea to put reading configuration parameters into a separate component.)
I'd also like to have SourceDirectoryWrapper and TargetDirectoryWrapper managed in the IoC container. In my case, mainly for convenience – I have an EventSource implementation that I need everywhere, so I inject it using property autowiring. Every object not in the IoC container needs to have it passed explicitly, which kind of bugs me.
So: is this possible with AutoFac? If so, how? I poked at the lifecycle events but most don't allow access to the registry after an object is built.
I don't quite understand why DirectoryWrapperFactory needs to exist. You could just register SourceDirectoryWrapper and TargetDirectoryWrapper directly as part of normal wireup:
builder.Register(c => new SourceDirectoryWrapper {
Directory = c.Resolve<IConfiguration>().SourceDirectory
});
builder.Register(c => new TargetDirectoryWrapper {
Directory = c.Resolve<IConfiguration>().SourceDirectory
});

Categories