Change environment variable RUN TIME - c#

How can I change the environment variable RUN TIME?
Using Login PAGE I am selecting Development or Production.
Based on that I have to use my ConnectionString device specifically.
"ConnectionStrings": {
"PROJECTNAME_Development": "SERVER_DETAILS",
"PROJECTNAME_Production": "SERVER_DETAILS",
}
Here are my LoginServices.CS file
public class LoginService: ILoginService
{
private readonly string _connectionString = string.Empty;
private readonly IConfiguration _configuration;
}
public LoginService (IConfiguration configuration)
{
_configuration = configuration;
//If ADMIN SELECT Development
_connectionString = _configuration.GetConnectionString("PROJECTNAME_Development");
//If ADMIN SELECT Production
_connectionString = _configuration.GetConnectionString("PROJECTNAME_Production");
}
UPDATE 1
I tried this code. It works fine but is there any other way like at one location I make a change and Environment Variable value gets updated.
My Controller
[HttpPost]
public IActionResult Login(LoginModel model)
{
try
{
if (ModelState.IsValid)
{
model.DevelopmentServer = "Development";
// model.DevelopmentServer = "Production";
...
var Result = ILoginService.AdminLogin(model);
public LoginService(IConfiguration configuration, IOptions<ConnectionStringDetails> connectionStrings)
{
_proDBCon = connectionStrings.Value.PROJECTNAME_Development;
_proDBConProduction = connectionStrings.Value.PROJECTNAME_Production;
}
In the same file LoginService.cs File
public LoginInfo AdminLogin(LoginModel model)
{
if (model.DevelopmentServer == "Development") {
_connectionString = _proDBCon;
}
else {
_connectionString = _proDBConProduction;
}
}

Assuming that you need to switch between two databases from UI at any cost, you won't be able to complete it entirely using containers/injections. You have to use the value passed from UI to determine which database connection string should be used.
public class LoginService {
// ...
private string GetConnectionString(string connectionStringNameFromUi){
return _configuration.GetConnectionString
}
}
Meanwhile, I would consider deploying two separate instances of an application, one configured for dev and another for prod. In such way, you would get more out of the box (injections container and environment configuration).

Related

C# fetch connection details inside a constructor

I have my DB class defined as below:
public class DbAdapterService : DbAdapterService
{
private readonly AppSettings _settings;
public DbAdapterService(IOptions<AppSettings> settings)
{
_settings = settings?.Value;
DbConnectionStringBuilder builder = new DbConnectionStringBuilder();
builder.ConnectionString = _settings.ConnectionString;
}
}
In the above class I am fetching my connection string from my appsettings.json and it works fine.
Now we need to fetch the connecting strings username and password from another method defined in our class. This method fetches these details from our stored vault. Example as below:
public class CredentialsService : ICredentialsService
{
public Credentials GetDetails()
{
//return credentials
}
}
My questions is can I call this method in my DbAdapterService constructor above or if there is a better way to handle this.
Thanks
--Updated--
public class DbAdapterService : DbAdapterService
{
private readonly AppSettings _settings;
public ICredentialsService _credentialsService;
private bool isInitialized = false;
public DbAdapterService(IOptions<AppSettings> settings, ICredentialsService credentialsService)
{
_settings = settings?.Value;
_credentialsService = credentialsService;
if (!isInitialized)
{
Initialize(_credentialsService);
}
DbConnectionStringBuilder builder = new DbConnectionStringBuilder();
builder.ConnectionString = _settings.ConnectionString;
}
public void Initialize(ICredentialsService credentialsService)
{
if (isInitialized)
return;
else
{
//credentialsService.GetDetails();
isInitialized = true;
}
}
}
You seem to want to initialize the connection string in the constructor. If you're reaching out to some external component (file system, database, or API for example) to retrieve a value, that's possibly going to be an async operation. There's no reliable way of calling an async method in a constructor.
So, what can we do? Well, there's no rule saying we must do it in the constructor. The constructor was a convenient place, because it ensures that by the time you invoke any other methods, the initialization will have taken place. But there are other patterns to accomplish this. Here's one:
public class DbAdapterService : IDbAdapterService
{
string _connectionString;
readonly AppSettings _settings;
readonly ICredentialsService _credentialsService;
public DbAdapterService(IOptions<AppSettings> settings,
ICredentialsService credentialsService)
{
_settings = settings.Value;
_credentialsService = credentialsService;
}
async Task EnsureInitializedAsync()
{
if (!string.IsNullOrEmpty(_connectionString))
{
//no need to reinitialize
//unless the credentials might change during the lifecycle of this object
return;
}
var credentials = await _credentialsService.GetDetailsAsync();
var builder = new DbConnectionStringBuilder(_settings.ConnectionString);
builder.Username = credentials.Username;
builder.Password = credentials.Password;
_connectionString = builder.ConnectionString;
}
public async Task DoSomethingAsync()
{
await EnsureInitializedAsync();
//now you can use _connectionString here
}
}
The key part is remembering to invoke EnsureInitializedAsync() in any method that needs to make use of the connection string. But at least code that consumed DbAdapterService won't have to know whether to initialize the connection string or not.
While this pattern isn't as necessary for non-async code, it's great for operations that might become async in the future, and the pattern makes more sense if the details of the connection might actually change at runtime, but your objects are constructed when you configure IoC container.
you can try this
public class DbAdapterService : DbAdapterService
{
private readonly AppSettings _settings;
private readonly ICredentialsService _credentialsService ;
public DbAdapterService(
IOptions<AppSettings> settings,
ICredentialsService credentialsService )
{
_credentialsService= credentialsService ;
_settings = settings?.Value;
DbConnectionStringBuilder builder = new DbConnectionStringBuilder();
builder.ConnectionString = _settings.ConnectionString;
}
}
after this you can call any method from the credentialService
or a little shorter if you only need credentials
private readonly AppSettings _settings;
private readonly Credentials _credentials;
public DbAdapterService(
IOptions<AppSettings> settings,
ICredentialsService credentialsService )
{
_credentials= credentialsService.GetDetails();
_settings = settings?.Value;
DbConnectionStringBuilder builder = new DbConnectionStringBuilder();
builder.ConnectionString = _settings.ConnectionString;
}

Add custom properties to telemetry request at controller level

I am trying to add specific properties to telemetry request for every route.
After digging a bit, I've found that I can create my own custom TelemetryInitializer by implementing ITelemetryInitializer.
By doing this I've managed to add global properties to the request.
However, I still need to add specific properties at the controller level.
Do you have any idea how can I achieve this?
I've tried to inject TelemetryClient into the controller, but if I use it the properties are shared between requests.
This is how I've tried to log in the controller:
private TelemetryClient telemetryClient;
public ValueController(TelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
}
[HttpGet]
public async Task<IActionResult> RouteOne([FromQuery(Name = "param1")]string param1, [FromQuery(Name = "param2")]string param2)
{
telemetryClient.Context.GlobalProperties["param1"] = param1;
telemetryClient.Context.GlobalProperties["param2"] = param2;
}
[HttpGet]
public async Task<IActionResult> RouteTwo([FromQuery(Name = "param3")]string param3, [FromQuery(Name = "param4")]string param4)
{
telemetryClient.Context.GlobalProperties["param3"] = param3;
telemetryClient.Context.GlobalProperties["param4"] = param4;
}
And this is the implementation of ITelemetryInitializer:
public class CustomPropertiesTelemetryInitializer : ITelemetryInitializer
{
private readonly IHttpContextAccessor httpContextAccessor;
public CustomPropertiesTelemetryInitializer(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
telemetry.Context.GlobalProperties["RequestId"] = httpContextAccessor.HttpContext.GetProperty("requestId");
telemetry.Context.GlobalProperties["Ip"] = httpContextAccessor.HttpContext?.Connection.RemoteIpAddress.ToString();
telemetry.Context.GlobalProperties["RoutePath"] = httpContextAccessor.HttpContext?.Request.Path;
}
}
If the properties you added are always like "paramxxx", then there is a workaround(but it's really not very elegant).
In the controller constructor, check the GlobalProperties if it contains key like "paramxxx":
public ValueController(TelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
var props = this.telemetryClient.Context.GlobalProperties;
foreach (var p in props)
{
if (p.Key.Contains("param"))
{
props.Remove(p.Key);
}
}
}
The key here is to use the DI framework. You can use it to get request-scoped data or services into your ITelemetryInitializer.
(These examples are based on the standard ASP.Net Dependency Injection framework. This pattern should work with any DI framework, but will need to be adjusted slightly.)
First, create a class to represent your request-scoped telemetry. I've used a simple DTO, but this could also be a service that knows how to fetch/generate the data itself. Register it using AddScoped. "Scoped" means that a new instance will be created for each HTTP request, and then that instance will be re-used within that request.
Because I used a DTO, I didn't bother with an interface--you should use an interface if the class contains any logic you'll want to mock in unit tests.
public class RequestScopedTelemetry
{
public string MyCustomProperty { get; set; }
}
services.AddScoped<RequestScopedTelemetry>();
Now, create the ITelemetryInitializer and register it as a singleton. App Insights will discover and use it through the DI framework.
class RequestScopedTelemetryInitializer : ITelemetryInitializer
{
readonly IHttpContextAccessor httpContextAccessor;
public RequestScopedTelemetryInitializer(IHttpContextAccessor httpContextAccessor)
=> this.httpContextAccessor = httpContextAccessor;
public void Initialize(ITelemetry telemetry)
{
// Attempt to resolve the request-scoped telemetry from the DI container
var requestScopedTelemetry = httpContextAccessor
.HttpContext?
.RequestServices?
.GetService<RequestScopedTelemetry>();
// RequestScopedTelemetry is only available within an active request scope
// If no telemetry available, just move along...
if (requestScopedTelemetry == null)
return;
// If telemetry was available, add it to the App Insights telemetry collection
telemetry.Context.GlobalProperties[nameof(RequestScopedTelemetry.MyCustomProperty)]
= requestScopedTelemetry.MyCustomProperty;
}
}
services.AddSingleton<ITelemetryInitializer, RequestScopedTelemetryInitializer>();
Finally, in your controller method, set your per-request values. This part isn't necessary if your telemetry class is able to fetch or generate the data itself.
public class ExampleController : ControllerBase
{
readonly RequestScopedTelemetry telemetry;
public ValuesController(RequestScopedTelemetry telemetry)
=> this.telemetry = telemetry;
[HttpGet]
public ActionResult Get()
{
telemetry.MyCustomProperty = "MyCustomValue";
// Do what you want to
return Ok();
}
}
In order to add per request data into telemetry, you need to have a way to share data within the request. A reliable way is by using HttpContent.Items property, which is basically a Dictionary.
You can create a service to keep a Dictionary inside HttpContent.Items with all custom data you want in telemetry (key prefix is used to ensure we only read the things we want later in Initializer):
public class LogTelemetryRequest
{
private const string KEY_PREFIX = "CustomTelemetryData_";
private readonly IHttpContextAccessor _httpContextAccessor;
public LogTelemetryRequest(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void AddProperty(string key, string value)
{
_httpContextAccessor.HttpContext.Items[KEY_PREFIX + key] = value;
}
}
Register this as scoped in Startup.cs:
services.AddScoped<LogTelemetryRequest>();
Use it in your controller:
private LogTelemetryRequest logTelemetryRequest;
public ValueController(LogTelemetryRequest logTelemetryRequest)
{
this.logTelemetryRequest = logTelemetryRequest;
}
[HttpGet]
public async Task<IActionResult> RouteOne([FromQuery(Name = "param1")]string param1, [FromQuery(Name = "param2")]string param2)
{
// telemetryClient.Context.GlobalProperties["param1"] = param1;
// telemetryClient.Context.GlobalProperties["param2"] = param2;
logTelemetryRequest.AddProperty("param1", param1);
logTelemetryRequest.AddProperty("param2", param2);
}
Then read it within initializer:
public class AddCustomTelemetryInitializer : ITelemetryInitializer
{
private const string KEY_PREFIX = "CustomTelemetryData_";
private readonly IHttpContextAccessor _httpContextAccessor;
public AddCustomTelemetryInitializer(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
if (requestTelemetry == null) return;
foreach (var item in _httpContextAccessor.HttpContext.Items)
{
if (item.Key is string key && key.StartsWith(KEY_PREFIX))
requestTelemetry.Properties.Add(key, item.Value.ToString());
}
}
}
Ideally LogTelemetryRequest should be registered using an interface, and the key prefix should be a single shared constant, didn't do for the sake of simplicity.

How to store data in cache?

I created a ViewComponent to display a List<Product>, the list is valorized taken data from a REST API service, this is my class implementation:
public class ProductsViewComponent : ViewComponent
{
private readonly HttpClient _client;
public ProductsViewComponent(HttpClient client)
{
_client = client ?? throw new ArgumentNullException(nameof(client));
}
public async Task<IViewComponentResult> InvokeAsync(string date)
{
using (var response = await _client.GetAsync($"/"product/get_products/{date}"))
{
response.EnsureSuccessStatusCode();
var products = await response.Content.ReadAsAsync<List<Product>>();
return View(products);
}
}
}
I load the List inside an html table which is available inside the Components folder: Views\Shared\Components\Products\Default.cshtml.
In each View that needs to display the Products I did:
#await Component.InvokeAsync("Products", new { date = myDate })
The REST API is called using the HttpClient configured in the Startup.cs as following:
services.AddHttpClient<ProductsViewComponent>(c =>
{
c.BaseAddress = new Uri('https://api.myservice.com');
});
This works well, but the main problem is each time the user reload the page or maybe go inside another View which require to display the list of products, then the app will make another API call.
Is possible store the list in something like a cache and prevent to call the API again if the date is equal than the previous date selected?
I'm learning ASP.NET Core so I'm not really expert on this argument.
Thanks in advance for any help.
As per microsoft documentation https://learn.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-2.1
you can use IMemoryCache to cache data
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public void Configure(IApplicationBuilder app)
{
app.UseMvcWithDefaultRoute();
}
}
and create instance of IMemoryCache. This is an example from Microsoft documentation. You can Create another class to handle this all together and In below example this is just saving DateTime But, you can save any object in cache and when you try to read that value from cache just need to cast that object into a Type.
I will strongly recommend you go through the above documentation.
public class HomeController : Controller
{
private IMemoryCache _cache;
public HomeController(IMemoryCache memoryCache)
{
_cache = memoryCache;
}
public IActionResult CacheTryGetValueSet()
{
DateTime cacheEntry;
// Look for cache key.
if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
{
// Key not in cache, so get data.
cacheEntry = DateTime.Now;
// Set cache options.
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Keep in cache for this time, reset time if accessed.
.SetSlidingExpiration(TimeSpan.FromSeconds(3));
// Save data in cache.
_cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions);
}
return View("Cache", cacheEntry);
}
}
Update: CacheKeys.Entry is a static class where all keys are defined. (Just coding standards). Please check the above documentation link.
public static class CacheKeys
{
public static string Entry { get { return "_Entry"; } }
public static string CallbackEntry { get { return "_Callback"; } }
public static string CallbackMessage { get { return "_CallbackMessage"; } }
public static string Parent { get { return "_Parent"; } }
public static string Child { get { return "_Child"; } }
public static string DependentMessage { get { return "_DependentMessage";} }
public static string DependentCTS { get { return "_DependentCTS"; } }
public static string Ticks { get { return "_Ticks"; } }
public static string CancelMsg { get { return "_CancelMsg"; } }
public static string CancelTokenSource { get { return "_CancelTokenSource";} }
}
You can use a distributed cache and so use Redis for example with a ConnectionMultiplexer.
And so foreach call you can call your redis for the cache which is implement thanks to an interface call here 'IDistributedCache'
You can find a lot of documentation to implement cache and use it.
: .Net framework
DotNet Core
Your controller X :
[HttpGet]
[Route("{itemId}")]
public async Task<IHttpActionResult> GetItemById(int eventId, [FromUri]EventTabs tabId)
{
ServiceResponse<ItemDto> result = await _itemDispatcher.GetItemById(itemId);
return WrapResponse(result);
}
Your dispatcher to get the item by id which use redis cache (already implement)
public class ItemDispatcher : ItemDispatcher
{
private readonly IUnitOfWork _unitOfWork;
private readonly IDistributedCache _distributedCache; // use interface of your implementation of redis cache
private readonly int _cacheDuration;
private readonly bool _isCacheEnabled;
public EventDispatcher(IUnitOfWork unitOfWork, IDistributedCache distCache)
{
_unitOfWork = unitOfWork;
_distributedCache = distCache; // init cache in constructor
_cacheDuration = _configuration.Get<int>("cache.duration"); // duration of your cache
_isCacheEnabled = _configuration.Get<bool>("cache.isEnable"); // if the cache is enable or not
}
public async Task<ServiceResponse<ItemDto>> GetItemById(int id)
{
// Add this for each Task call
var cacheKey = string.Empty;
if (_isCacheEnabled)
{
cacheKey = CacheUtils.GetCacheKey(CacheKeys.Item, id);
itemDto cacheResult = await _distributedCache.Get<ItemDto>(cacheKey);
if (cacheResult != null)
return new ServiceResponse<Item>(cacheResult);
}
}
Try This
Cache["KeyName"] = VariableOrTable; Cache.Insert("Key", VariableOrTable, null,
Cache.NoAbsoluteExpiration, ts);

Multi Database for Multi Tenant with Code first EF 6 and ASP.Net MVC 4

I am building a SAAS application and planned for one database per client. I am using Code First EF6 with ASP.Net MVC 4.
There will be 2 context i.e. MasterContext and TenantContext. User will first hit to MasterContext to authenticate user credentials and fetch its Tenant configuration.
Based on fetched Tenant configuration; TenantContext is set to Tenant specific database and used for Tenant CRUD operations.
Please advice how to achieve this.
The idea is to identify the current request tenant_id and use that to fetch the database configuration and create DbContext like the below code.
public AppDbContext : DbContext
{
private const string _defaultCS = "default app connection string";
public AppDbContext() : base(GetConnectionString())
{
}
private string GetConnectionString()
{
return TenantContext.ConnectionString ?? _defaultCS;
}
}
Sample usage
public class StudentRepo
{
public Student Get(Guid id)
{
using(var ctx = new AppDbContext())
{
return ctx.Students.FirstOrDefault(x=>x.Id == id);
}
}
}
This will automatically connect to the logged in tenant database.
You might need to store tenant_id in Auth cookie and read it after PostAuthenticate_Event and store in HttpContext.Current.Items
public static TenantContext
{
public static Guid TenantId
{
get
{
return (Guid)HttpContext.Current.Items["__TenantID"];
}
}
public static string ConnectionString
{
get
{
return TenantConfigService.GetConnectionString(TenantId);
}
}
}
In some HTTP module Init method
context.PostAuthenticateRequest += context_PostAuthenticateRequest;
void context_PostAuthenticateRequest(object sender, EventArgs e)
{
FormsIdentity identity = Thread.CurrentPrincipal.Identity as FormsIdentity;
if (identity != null)
{
HttpContext.Current.Items["__TenantID"] = GetTenantIdFromTicket(identity.Ticket.UserData); // returns tenant_id as guid type
}
}

Failover connection string in n-tier linq2sql

Scenario: ASP.Net web app (n-tier linq2sql) on local IIS with connection to SQL 2008 database over VPN. Some data is replicated to a local SQL 2008 express DB (different name). If the connection is down to VPN database, we would like to use the local instance for some parts of the web app.
My Question is, how can the following solution be improved. we have involves a lot of passing the connection string about. If this involved mirroring, we could set the failover partner but as it is a different database I don't think this is possible.
Current Code:
Check if we can connect remotely, put the working connection string in session
Session_Start()
{
//if can connect to remote db
Session["ConnStr"] = //remote connection
//else
Session["ConnStr"] = //local connection
}
Pass connection string in UI to BLL
protected void ods1_ObjectCreating(object sender, ObjectDataSourceEventArgs e)
{
TestManager myTestManager = new TestManager(Session["ConnString"].ToString());
e.ObjectInstance = myTestManager;
}
Pass to DAL
public class TestManager
{
private readonly string _connectionString;
public TestManager(string connectionString)
{
_connectionString = connectionString;
}
[DataObjectMethod(DataObjectMethodType.Select, true)]
public List<Test> GetAll()
{
TestDB testDB = new TestDB(_connectionString);
return testDB.GetAll();
}
}
Set connection in DAL creating DataContext in constructor
public class TestDB
{
public TestDB(string connectionString)
{
_connectionString = connectionString;
_dbContext = new TestDataContext(_connectionString);
}
private TestDataContext _dbContext;
private string _connectionString;
public string ConnectionString
{
get
{
return _connectionString;
}
set
{
_connectionString = value;
}
}
public TestDataContext DbContext
{
get
{
return _dbContext;
}
set
{
_dbContext = value;
}
}
public List<Test> GetAll()
{
var query = from t in DbContext.Tests
select new DTO.Test()
{
Id = t.Id,
Name = t.Name
};
return query.ToList();
}

Categories