I am trying to do some integration tests for my asp.net Core 2.1 project.
I initialise on startup file my connection string but when i run the test it still empty in handler, what is wrong on my code?
[TestMethod]
public async Task Method1()
{
var webHostBuilder = new WebHostBuilder()
.UseEnvironment("Development")
.UseStartup<Startup>();
HttpRequestMessage getRequest = new HttpRequestMessage(HttpMethod.Get, "api/action")
{
};
getRequest.Headers.Add("userId", "4622");
getRequest.Headers.Add("clientId", "889");
using (var server = new TestServer(webHostBuilder))
using (var client = server.CreateClient())
{
var result = await client.SendAsync(getRequest);
...
}
}
Startup
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
...
services.Configure<SqlConfig>(options =>
{
options.ConnectionString = Configuration.GetConnectionString("DefaultConnection");
});
...
}
SqlConfig
public class SqlConfig
{
public string ConnectionString { get; set; }
}
Repository
public abstract class SqlServerQueryHandler<TQuery, TResult> : BaseQueryHandler<TQuery, TResult>
where TQuery : IQuery<TResult>
{
public SqlServerQueryHandler(IOptions<SqlConfig> connectionString)
{
this.ConnectionString = connectionString.Value.ConnectionString;
}
protected string ConnectionString { get; }
}
what solve my probleme is the following code :
public class TestStartup : Startup
{
public TestStartup(IConfiguration configuration, IHostingEnvironment env) : base(configuration)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
this.Configuration = builder.Build();
}
Include the appsettings.json via interface on the output directory
Related
I have tenant id in request header. I want to connect database string based on the tenant id , how can i achieve this? I am using .netcore6 webapi. Thanks in advance.
You can achieve this requirement through Middleware. Here is the test result and sample.
My Test files and picutres
Test Method in HomeController
public IActionResult GetConnectionStringByTenantID()
{
bool testResult = false;
string msg = string.Empty;
try
{
testResult = _dbcontext.Database.CanConnect();
if (testResult)
{
msg = "connect sucessfully";
}
else
{
msg = "connect failed";
}
}
catch (Exception e)
{
msg = e.ToString();
throw;
}
return Ok(msg);
}
TenantMiddleware.cs
using WebApplication7.Models;
namespace WebApplication7
{
public class TenantMiddleware
{
private readonly RequestDelegate _next;
public TenantMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// Read the tenant ID from the request header
var tenantId = context.Request.Headers["Tenant-Id"].FirstOrDefault();
// Get the connection string for the tenant
var connectionString = GetConnectionStringForTenant(tenantId);
// Create a Tenant object with the tenant ID and connection string
var tenant = new Tenant
{
Id = tenantId,
ConnectionString = connectionString
};
// Set the Tenant object in the context
context.Items["Tenant"] = tenant;
// Call the next middleware component in the pipeline
await _next(context);
}
private string GetConnectionStringForTenant(string tenantId)
{
// Implement logic to get the connection string for the tenant
// This can be from a configuration file or a database
// For example, you can have a dictionary of tenant IDs and connection strings
var connectionStrings = new Dictionary<string, string>
{
{ "tenant1", "Data Source=...My real test connectionstring..." },
{ "tenant2", "Server=server2;Database=database2;User Id=user2;Password=password2;" }
};
if (tenantId == null || tenantId.Equals(string.Empty))
{
tenantId = "tenant1";
}
// Return the connection string for the tenant ID
if (connectionStrings.TryGetValue(tenantId, out var connectionString))
{
return connectionString;
}
// If the tenant ID is not found, throw an exception
throw new ArgumentException($"Invalid tenant ID: {tenantId}");
}
}
}
MyDbContext.cs
using Microsoft.EntityFrameworkCore;
using WebApplication7.Models;
namespace WebApplication7
{
public class MyDbContext : DbContext
{
private readonly Tenant _tenant = null!;
public MyDbContext()
{
}
public MyDbContext(DbContextOptions<MyDbContext> options, IHttpContextAccessor httpContextAccessor)
: base(options)
{
_tenant = httpContextAccessor.HttpContext.Items["Tenant"] as Tenant;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_tenant.ConnectionString);
}
}
}
Program.cs
using Microsoft.EntityFrameworkCore;
namespace WebApplication7
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpContextAccessor();
builder.Services.AddDbContext<MyDbContext>();
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseMiddleware<TenantMiddleware>();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
}
}
}
Tenant.cs
namespace WebApplication7.Models
{
public class Tenant
{
public string? Id { get; set; }
public string? ConnectionString { get; set; }
}
}
All information about using Configuation starts with creating builder and
var builder = WebApplication.CreateBuilder(args);
subsequnetly using
builder.Configuration.
configuration ,but in Worker services WebApplication is not available.
How can i use Configuration in Microsoft.NET.Sdk.Worker type of project?
This might help https://medium.com/c-sharp-progarmming/how-to-set-appsettings-or-config-in-a-net-worker-service-cc2d70ab4e0c
It is by accessing the HostBuilderContext
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
IConfiguration configuration = hostContext.Configuration;
services.Configure<RabbitMQConfiguration>(configuration.GetSection(nameof(RabbitMQConfiguration)));
services.AddHostedService<Worker>();
});
First of all, you should add ConfigureAppConfiguration when CreateHostBuilder
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
})
.ConfigureAppConfiguration((hostContext, configBuilder) =>
{
configBuilder
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: true)
.Build();
});
Then, add configuration in appsettings.YourEnv.json
"worker": {
"id": 12345,
"name": "SimpleWorker",
"delay": 1000
}
Add IConfiguration in Worker constructor and use it.
Full example of Worker class:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly IConfiguration _configuration;
public Worker(ILogger<Worker> logger, IConfiguration configuration)
{
_logger = logger;
_configuration = configuration;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var workerConfig = _configuration.GetSection("worker").Get<SimpleWorkerConfig>();
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker {id}:{name} running at: {time} with delay {delay}",
workerConfig.Id, workerConfig.Name, DateTimeOffset.Now, workerConfig.Delay);
await Task.Delay(workerConfig.Delay, stoppingToken);
}
}
}
public class SimpleWorkerConfig
{
public int Id { get; set; }
public string Name { get; set; }
public int Delay { get; set; }
}
Result:
I try to get the complete log in my Azure function, including ALL the custom log I added in my code, currently I only have something like:
2020-11-28T00:56:59.614 [Information] Executing 'test-log' (Reason='This
function was programmatically called via the host APIs.',
Id=7f82a0c4-5ae9-416c-8f19-9c00722ded2f) 2020-11-28T00:56:59.848
[Information] Executed 'test-log' (Succeeded,
Id=7f82a0c4-5ae9-416c-8f19-9c00722ded2f, Duration=247ms)
Here is how I configure the azure function:
public class Startup : FunctionsStartup
{
public IConfiguration Configuration { get; private set; }
public Startup() { }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services
.AddMvcCore()
.AddNewtonsoftJson(jsonOptions =>
{
jsonOptions.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
});
builder.AddConfiguration((configBuilder) => configBuilder
.AddEnvironmentVariables()
.Build()
);
builder.Services.AddLogging();
Configuration = builder.GetCurrentConfiguration();
// explicitly call ConfigureServices to setup DI
ConfigureServices(builder.Services);
}
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped(typeof(ILoggerService<>), typeof(LoggerService<>));
services.AddTelemetryClient(Configuration);
}
My LoggerService:
public class LoggerService<T> : ILoggerService<T> where T : class
{
private readonly TelemetryClient _telemetryClient;
private readonly ILogger<T> _logger;
// Flag: Has Dispose already been called?
bool disposed = false;
public LoggerService(
ILogger<T> logger
)
{
_telemetryClient = new TelemetryClient(TelemetryConfiguration.CreateDefault());
_logger = logger;
}
public void LogTrace(string message)
{
_logger.LogTrace(message);
}
public void LogTrace(string message, IDictionary<string, string> properties = null)
{
TrackMessage(message, LogLevel.Trace, properties);
LogTrace(message);
}
private void TrackMessage(string message, LogLevel logLevel, IDictionary<string, string> properties = null)
{
_telemetryClient.TrackTrace($"{logLevel}:{message}", properties);
Flush();
}
[...]
And My function:
public class MyFunctions : BaseFunctions
{
internal readonly ILoggerService _logger;
public POSFunctions(
ILoggerService<POSFunctions> logger
)
{
_logger= logger;
}
[FunctionName("test-log")]
public async Task<IActionResult> TestLogAsync(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest request
)
{
_logger.LogInformation("This is log");
}
And in the Configuration of my Azure function I have:
Why I can not see "This is log" in the Azure Function Log?
The solution was to add in the host.json file:
"logging": {
"fileLoggingMode": "always",
"logLevel": {
"default": "Information"
},
"applicationInsights": {
"enableLiveMetrics": true,
"enableDependencyTracking": true,
}
},
I followed the example in google to make the code ,but can't get the value from appsettings.json.
.net-core version 2.2
The "_settings.TimerPeriod" value is return 0 ,I didn't know what is wrong,looking forward to your help.
running results(click to show img)
Program.cs
static async Task Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureLogging(factory =>
{
factory.AddNLog(new NLogProviderOptions
{
CaptureMessageTemplates = true,
CaptureMessageProperties = true
});
NLog.LogManager.LoadConfiguration("nlog.config");
})
.ConfigureAppConfiguration((hostContext, config) =>
{
var env = hostContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
config.AddEnvironmentVariables();
if (args != null)
{
config.AddCommandLine(args);
}
})
.ConfigureServices((hostContext, services) =>
{
services.AddOptions();
services.Configure<AppConfig>(hostContext.Configuration.GetSection("AppSettings"));
services.AddHostedService<TimerHostedService>();
});
await builder.RunConsoleAsync();
}
TimerHostedService.cs
class TimerHostedService: BackgroundService
{
private Timer _timer;
private ILogger _logger;
private readonly AppConfig _settings;
public TimerHostedService(ILogger<TimerHostedService> logger,IOptionsSnapshot<AppConfig> settings)
{
_logger = logger;
_settings = settings.Value;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
//Console.WriteLine($"Running, period:{_settings.TextOfStartToPrint}");
_timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(2));
return Task.CompletedTask;
}
private void DoWork(object state)
{
_logger.LogInformation($"Timer is working in {DateTime.Now.ToString()},period:{_settings.TimerPeriod}");
}
.....
}
appsettings.json
"AppSettings": {
"TimerPeriod": 2
}
AppConfig.cs
public class AppConfig
{
public int TimerPeriod { get; set; }
}
I am doing something like:
private static IServiceProvider serviceProvider;
public Program(IApplicationEnvironment env, IRuntimeEnvironment runtime)
{
var services = new ServiceCollection();
ConfigureServices(services);
serviceProvider = services.BuildServiceProvider();
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; set; }
private void ConfigureServices(IServiceCollection services)
{
//Console.WriteLine(Configuration["Data:DefaultConnection:ConnectionString"]);
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<DbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
}
I am struggling to get to use the program using an injected DbContext. Any idea? How do you instantiate the program and get everything injected? I don't know what to do in the static Main method.
Is there an equivalent for this?
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
Something like?
public static void Main(string[] args) => ConsoleApplication.Run<Program>(args);
This is how I did it:
public class Startup
{
public static IConfigurationRoot Configuration { get; set; }
public static void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<DbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
services.AddSingleton<IMyManager, Manager>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<Program, Program>();
}
public static void Main(string[] args)
{
var services = new ServiceCollection();
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json");
.AddEnvironmentVariables()
.AddUserSecrets();
Configuration = builder.Build();
ConfigureServices(services);
var provider = services.BuildServiceProvider();
CancellationTokenSource ctSource = new CancellationTokenSource();
CancellationToken ct = ctSource.Token;
Task task = Task.Run(async () =>
{
Program program = provider.GetRequiredService<Program>();
await program.Run(ct);
});
try
{
task.Wait();
}
catch (AggregateException e)
{
throw e.InnerException;
}
ctSource.Cancel();
ctSource.Dispose();
}
}
Then the program is just:
class Program
{
private IMyManager _myManager;
public Program(IMyManager myManager)
{
_myManager = myManager;
}
public async Task Run(CancellationToken cancelationToken)
{
while (true)
{
cancelationToken.ThrowIfCancellationRequested();
// My things using _myManager
await Task.Delay(10000, cancelationToken);
}
}
}
I deleted a bunch of stuff for the example so it probably crashes somewhere, but you get the idea.
Just in case anyone else is looking for a small and simple example to follow.
Here is a small console app I wrote recently for a an example. It"s only a small password generator demonstration of DI in an app with unit tests.
https://github.com/AnthonySB/PasswordApplication
using System;
using Microsoft.Extensions.DependencyInjection;
using PasswordExercise.Interfaces;
using PasswordExercise.Services;
namespace PasswordExercise
{
class Program
{
static void Main(string[] args)
{
//Dependency injection
var serviceProvider = new ServiceCollection()
.AddSingleton<IPasswordGeneratorService, PasswordGenerator>()
.AddSingleton<IPasswordService, PasswordService>()
.BuildServiceProvider();
//Get the required service
var passwordService = serviceProvider.GetService<IPasswordService>();
//For reading from the console
ConsoleKeyInfo key;
//Display the menu
passwordService.Menu();
do
{
//Read the console key, do not display on the screen
key = Console.ReadKey(true);
switch (key.KeyChar.ToString())
{
case "1":
Console.WriteLine("Simple password: {0}", passwordService.SimplePassword());
break;
case "2":
Console.WriteLine("Moderate password: {0}", passwordService.ModeratePassword());
break;
case "3":
Console.WriteLine("Strong password: {0}", passwordService.StrongPassword());
break;
}
} while (key.Key != ConsoleKey.Escape);
}
}
}
Hope this helps someone.