Azure Service IMessageSender with DI - c#

I am using .net core project .
I implemented DI like :
builder.Services.AddSingleton<IMessageSender>(x => new
MessageSender(Environment.GetEnvironmentVariable("ServiceBusConnection"),"queueName"));
var serviceprovider = builder.Services.BuildServiceProvider();
ServiceBusUtils.Configure(serviceprovider.GetService<IMessageSender>());
Also, I have a Utility class :
public static class ServiceBusUtils
{
private static IMessageSender _messageSender;
public static void Configure(IMessageSender messageSender)
{
_messageSender = messageSender;
}
public static async Task<bool> SendMessage(ExecuteMessage<Message> message ,string queueName,string Id)
{
var message = new Message(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(Message)))
{
SessionId = Id,
};
await _messageSender.SendAsync(message);
return true;
}
}
But I cannot set the queuename with the parameter.
I want to use implementation like
ServiceBusUtils.SendMessage(message,"quename");

You have to register named clients for using with multiple queue names. The following code will give you an idea.
public void ConfigureServices(IServiceCollection services)
{
var queueNames = new string[] { "q1", "q2" };
var queueFactory = new Dictionary<string, IMessageSender>();
foreach (var queueName in queueNames)
{
queueFactory.Add(queueName, new MessageSender(Environment.GetEnvironmentVariable("ServiceBusConnection"),queueName));
}
services.AddSingleton(queueFactory);
services.AddSingleton<IServiceBus,ServiceBus>();
}
public interface IServiceBus
{
public Task SendMessage(string message, string queue);
}
public class ServiceBus : IServiceBus
{
private readonly Dictionary<string, IMessageSender> _queueFactory;
public ServiceBus(Dictionary<string, IMessageSender> queueFactory)
{
_queueFactory = queueFactory;
}
public async Task SendMessage(string message, string queue)
{
var messageSender = _queueFactory[queue];
await messageSender.SendAysnc(message);
}
}
You can pass the queue name to your send message method. This will resolve your MessageSender instance for the named queue.
_serviceBus.SendMessage("message","q1");

Related

dependency injection multiple interface for the same storage (with different connectionString)

I want to add two or more(depends on how many azure storage container i want to add to my app) services in Startup.cs
My appsettings.json:
"AzureBlobStorageConfiguration": {
"Storages": {
"Storage1": {
"StorageName": "Storage1",
"ConnString": "connString",
"AzureBlobContainerName": "containerName"
},
"Storage2": {
"StorageName": "Storage2",
"ConnString": "connString",
"AzureBlobContainerName": "containerName"
},
"Storage3": {
"StorageName": "Storage3",
"ConnString": "connString",
"AzureBlobContainerName": "containerName"
}
}
Next in Startup.cs im adding service with method:
public static IServiceCollection AddAzureStorage1(this IServiceCollection services, IConfiguration configuration)
{
var options = new ABlobStorageConfigurationOptionsDTO();
configuration.GetSection("AzureBlobStorageConfiguration").GetSection("Storages").GetSection("Storage1").Bind(options);
services.AddTransient<IAzureBlobStorage1, AzureBlobStorage1>(isp =>
{
var client = new BlobServiceClient(options.ConnString);
var container = client.GetBlobContainerClient(options.AzureBlobContainerName);
var containerName = options.AzureBlobContainerName;
var storageName = options.StorageName;
return new AzureBlobStorage1(container, containerName, storageName);
}
);
return services;
}
My IAzureBlobStorage1 looks like:
public interface IAzureBlobStorage1
{
string AzureBlobContainerName { get; }
string StorageName { get; }
public Task<Stream> DownloadStreamAsyns(string fileName);
public Task Upload(string fileId, Stream stream);
}
and AzureBlobStorage1 :
public class AzureBlobStorage1 : IAzureBlobStorage1
{
private BlobContainerClient _client;
private string _containerName;
private string _storageName;
public string StorageName => _storageName;
public string AzureBlobContainerName => _containerName;
public AzureBlobStorage1(BlobContainerClient client, string containerName, string storageName)
{
_client = client;
_containerName = containerName;
_storageName = storageName;
}
public async Task<Stream> DownloadStreamAsyns(string fileName)
{
return await _client.GetBlobClient(fileName).OpenReadAsync();
}
public async Task Upload(string fileId, Stream stream)
{
await _client.GetBlobClient(fileId).UploadAsync(stream);
}
}
After this i can injection interface in my constructor controller class :
public Controller(IAzureBlobStorage1 azureStorage)
{
_azureStorage1 = azureStorage;
}
But if i want to add many storages (i have 3 in appsetings.json) i have to:
Create interface IAzureBlobStorage2 (looking the same like IAzureBlobStorage1 - only name change)
Create class AzureBlobStorage2 (looking the same like AzureBlobStorage1 - only name change)
copy-paste method with changed class names
public static IServiceCollection AddAzureStorage2(this IServiceCollection services, IConfiguration configuration)
{
var options = new ABlobStorageConfigurationOptionsDTO();
configuration.GetSection("AzureBlobStorageConfiguration").GetSection("Storages").GetSection("Storage2").Bind(options);
services.AddTransient<IAzureBlobStorage2, AzureBlobStorage2>(isp =>
{
var client = new BlobServiceClient(options.ConnString);
var container = client.GetBlobContainerClient(options.AzureBlobContainerName);
var containerName = options.AzureBlobContainerName;
var storageName = options.StorageName;
return new AzureBlobStorage2(container, containerName, storageName);
}
);
return services;
}
Now i can get it in controller by
public Controller(IAzureBlobStorage2 azureStorage)
{
_azureStorage2 = azureStorage;
}
If i want add my third storage i need to copy-paste third time my code.
For me this solution looks very bad and im thinking how i can resolve it and make my code clean.
Unsure if this is a best practice or not, but you could design a named service provider, maybe? Either that, or you could just a generic parameter to differentiate them, but that generic parameter wouldn't mean much except as a way to differentiate..
Anyways, here's a really basic implementation using some kind of named provider?:
public interface INamedService {
string Identifier { get; }
}
public interface IAzureBlobStorage : INamedService
{
string AzureBlobContainerName { get; }
string StorageName { get; }
Task<Stream> DownloadStreamAsyns(string fileName);
Task Upload(string fileId, Stream stream);
}
public class NamedServiceProvider<T>
where T : INamedService
{
readonly IReadOnlyDictionary<string, T> Instances;
public NamedServiceProvider(
IEnumerable<T> instances)
{
Instances = instances?.ToDictionary(x => x.Identifier) ??
throw new ArgumentNullException(nameof(instances));
}
public bool TryGetInstance(string identifier, out T instance) {
return Instances.TryGetValue(identifier, out instance);
}
}
public class AzureBlobStorage : IAzureBlobStorage
{
public string Identifier { get; }
private BlobContainerClient _client;
private string _containerName;
private string _storageName;
public string StorageName => _storageName;
public string AzureBlobContainerName => _containerName;
public AzureBlobStorage(string identifier, BlobContainerClient client, string containerName, string storageName)
{
Identifier = identifier;
_client = client;
_containerName = containerName;
_storageName = storageName;
}
public async Task<Stream> DownloadStreamAsyns(string fileName)
{
return await _client.GetBlobClient(fileName).OpenReadAsync();
}
public async Task Upload(string fileId, Stream stream)
{
await _client.GetBlobClient(fileId).UploadAsync(stream);
}
}
And then the static extension method:
public static IServiceCollection AddAzureStorage(
this IServiceCollection services,
IConfiguration configuration,
string identifier)
{
var options = new ABlobStorageConfigurationOptionsDTO();
configuration
.GetSection("AzureBlobStorageConfiguration")
.GetSection("Storages")
.GetSection(identifier)
.Bind(options);
return services
.TryAddTransient<NamedServiceProvider<IAzureBlobStorage>>()
.AddTransient<IAzureBlobStorage, AzureBlobStorage>(isp =>
{
var client = new BlobServiceClient(options.ConnString);
var container = client.GetBlobContainerClient(options.AzureBlobContainerName);
var containerName = options.AzureBlobContainerName;
var storageName = options.StorageName;
return new AzureBlobStorage(identifier, container, containerName, storageName);
});
}
Then you could call use it like so:
public Controller(NamedServiceProvider<IAzureBlobStorage> azureStorage)
{
_ = azureStorage ?? throw new ArgumentNullException(nameof(azureStorage));
_azureStorage2 = azureStorage.TryGetInstance("Storage2", out var instance) ? instance : throw new Exception("Something about the identifier not being found??");
}
I coded this outside of an intellisense environment, so sorry if there are any smaller mispellings or bugs. There may be a better way to do this, but this seemed at least somewhat ok-ish? Oh, and I only changed what I had to in order to make it work generically. I didn't want to touch any other logic..

Tenant isNull in BackgroundJob

I have a background job where i need to have tenantId.
I did authenticate and I tested the tenantId, it is not null. I used other endpoints and it works wonderful but when I test the backgroundjob tenantId is always null.
I don't know if I am missing something or I need to send tenantId in the args.
This is the BJ
public class BackgroundNotificationJob : AsyncBackgroundJob<NotificationArgs>, ITransientDependency
{
private readonly FirebaseAppService _firebaseAppService;
private readonly IUnitOfWorkManager _unitOfWorkManager;
public BackgroundNotificationJob (
FirebaseAppService firebaseAppService ,
IUnitOfWorkManager unitOfWorkManager)
{
_firebaseAppService = firebaseAppService;
_unitOfWorkManager = unitOfWorkManager;
}
public override async Task ExecuteAsync (NotificationArgs args)
{
foreach (var notification in args.Notifications)
{
await _firebaseAppService.CreateMessage(notification.Key, notification.Value.ToString(), args.UserId);
}
}
}
The config:
public override void ConfigureServices ( ServiceConfigurationContext context )
{
var configuration = context.Services.GetConfiguration();
ConfigureHangfire(context, configuration);
}
private void ConfigureHangfire (ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddHangfire(config =>
{
config.UseSqlServerStorage(configuration.GetConnectionString("Default"));
});
}

How to inject dependencies into a MassTransitStateMachine activity?

I'm stuck and the docks for the lib are unhelpful. Given the below saga definition:
public class GetOrdersStateMachine : MassTransitStateMachine<GetOrdersState>
{
public State? FetchingOrdersAndItems { get; private set; }
public Event<GetOrders>? GetOrdersIntegrationEventReceived { get; private set; }
public GetOrdersStateMachine()
{
Initially(
When(GetOrdersIntegrationEventReceived)
.Activity(AddAccountIdToState)
.TransitionTo(FetchingOrdersAndItems));
}
private EventActivityBinder<GetOrdersState, GetOrders> AddAccountIdToState(
IStateMachineActivitySelector<GetOrdersState, GetOrders> sel) =>
sel.OfType<AddAccountIdToStateActivity>();
}
And the below activity definition:
public class AddAccountIdToStateActivity : Activity<GetOrdersState, GetOrders>
{
private readonly IPartnerService _partnerService;
public AddAccountIdToStateActivity(IPartnerService partnerService) => _partnerService = partnerService;
public void Probe(ProbeContext context) =>
context.CreateScope($"GetOrders{nameof(AddAccountIdToStateActivity)}");
public void Accept(StateMachineVisitor visitor) => visitor.Visit(this);
public async Task Execute(
BehaviorContext<GetOrdersState, GetOrders> context,
Behavior<GetOrdersState, GetOrders> next)
{
context.Instance.AccountId = await _partnerService.GetAccountId(context.Data.PartnerId);
await next.Execute(context);
}
public Task Faulted<TException>(
BehaviorExceptionContext<GetOrdersState, GetOrders, TException> context,
Behavior<GetOrdersState, GetOrders> next) where TException : Exception =>
next.Faulted(context);
}
And the below test definition:
var machine = new GetOrdersStateMachine();
var harness = new InMemoryTestHarness();
var sagaHarness = harness.StateMachineSaga<GetOrdersState, GetOrdersStateMachine>(machine);
var #event = new GetOrders("1", new[] {MarketplaceCode.De}, DateTime.UtcNow);
await harness.Start();
try
{
await harness.Bus.Publish(#event);
await harness.Bus.Publish<ListOrdersErrorResponseReceived>(new
{
#event.CorrelationId,
AmazonError = "test"
});
var errorMessages = sagaHarness.Consumed.Select<ListOrdersErrorResponseReceived>().ToList();
var sagaResult = harness.Published.Select<AmazonOrdersReceived>().ToList();
var state = sagaHarness.Sagas.Contains(#event.CorrelationId);
harness.Consumed.Select<GetOrders>().Any().Should().BeTrue();
sagaHarness.Consumed.Select<GetOrders>().Any().Should().BeTrue();
harness.Consumed.Select<ListOrdersErrorResponseReceived>().Any().Should().BeTrue();
errorMessages.Any().Should().BeTrue();
sagaResult.First().Context.Message.IsFaulted.Should().BeTrue();
errorMessages.First().Context.Message.CorrelationId.Should().Be(#event.CorrelationId);
errorMessages.First().Context.Message.AmazonError.Should().Be("test");
state.IsFaulted.Should().BeTrue();
}
finally
{
await harness.Stop();
}
As you can see, the AddAccountToStateActivity has a dependency on the IPartnerService. I can't figure a way to configure that dependency.There's nothing in the docs and neither can I find anything on the github. How do I do it?
Thanks to the help of one of the library's authors I ended up writing this code:
private static (InMemoryTestHarness harness, IStateMachineSagaTestHarness<GetOrdersState, GetOrdersStateMachine> sagaHarness) ConfigureAndGetHarnesses()
{
var provider = new ServiceCollection()
.AddMassTransitInMemoryTestHarness(cfg =>
{
cfg.AddSagaStateMachine<GetOrdersStateMachine, GetOrdersState>().InMemoryRepository();
cfg.AddSagaStateMachineTestHarness<GetOrdersStateMachine, GetOrdersState>();
})
.AddLogging()
.AddSingleton(Mock.Of<IPartnerService>())
.AddSingleton(Mock.Of<IStorage>())
.BuildServiceProvider(true);
var harness = provider.GetRequiredService<InMemoryTestHarness>();
var sagaHarness = provider
.GetRequiredService<IStateMachineSagaTestHarness<GetOrdersState, GetOrdersStateMachine>>();
return (harness, sagaHarness);
}
As you can see I'm registering my mocks with the ServiceProvider.

Property injection

I'm trying make a telegram bot with reminder. I'm using Telegram.Bot 14.10.0, Quartz 3.0.7, .net core 2.0. The first version should : get message "reminder" from telegram, create job (using Quartz) and send meaasage back in 5 seconds.
My console app with DI looks like:
Program.cs
static IBot _botClient;
public static void Main(string[] args)
{
// it doesn't matter
var servicesProvider = BuildDi(connecionString, section);
_botClient = servicesProvider.GetRequiredService<IBot>();
_botClient.Start(appModel.BotConfiguration.BotToken, httpProxy);
var reminderJob = servicesProvider.GetRequiredService<IReminderJob>();
reminderJob.Bot = _botClient;
Console.ReadLine();
_botClient.Stop();
// it doesn't matter
}
private static ServiceProvider BuildDi(string connectionString, IConfigurationSection section)
{
var rJob = new ReminderJob();
var sCollection = new ServiceCollection()
.AddSingleton<IBot, Bot>()
.AddSingleton<ReminderJob>(rJob)
.AddSingleton<ISchedulerBot>(s =>
{
var schedBor = new SchedulerBot();
schedBor.StartScheduler();
return schedBor;
});
return sCollection.BuildServiceProvider();
}
Bot.cs
public class Bot : IBot
{
static TelegramBotClient _botClient;
public void Start(string botToken, WebProxy httpProxy)
{
_botClient = new TelegramBotClient(botToken, httpProxy);
_botClient.OnReceiveError += BotOnReceiveError;
_botClient.OnMessage += Bot_OnMessage;
_botClient.StartReceiving();
}
private static async void Bot_OnMessage(object sender, MessageEventArgs e)
{
var me = wait _botClient.GetMeAsync();
if (e.Message.Text == "reminder")
{
var map= new Dictionary<string, object> { { ReminderJobConst.ChatId, e.Message.Chat.Id.ToString() }, { ReminderJobConst.HomeWordId, 1} };
var job = JobBuilder.Create<ReminderJob>().WithIdentity($"{prefix}{rnd.Next()}").UsingJobData(new JobDataMap(map)).Build();
var trigger = TriggerBuilder.Create().WithIdentity($"{prefix}{rnd.Next()}").StartAt(DateTime.Now.AddSeconds(5).ToUniversalTime())
.Build();
await bot.Scheduler.ScheduleJob(job, trigger);
}
}
}
Quartz.net not allow use constructor with DI. That's why I'm trying to create property with DI.
ReminderJob.cs
public class ReminderJob : IJob
{
static IBot _bot;
public IBot Bot { get; set; }
public async Task Execute(IJobExecutionContext context)
{
var parameters = context.JobDetail.JobDataMap;
var userId = parameters.GetLongValue(ReminderJobConst.ChatId);
var homeWorkId = parameters.GetLongValue(ReminderJobConst.HomeWordId);
await System.Console.Out.WriteLineAsync("HelloJob is executing.");
}
}
How can I pass _botClient to reminderJob in Program.cs?
If somebody looks for answer, I have one:
Program.cs (in Main)
var schedBor = servicesProvider.GetRequiredService<ISchedulerBot>();
var logger = servicesProvider.GetRequiredService<ILogger<DIJobFactory>>();
schedBor.StartScheduler();
schedBor.Scheduler.JobFactory = new DIJobFactory(logger, servicesProvider);
DIJobFactory.cs
public class DIJobFactory : IJobFactory
{
static ILogger<DIJobFactory> _logger;
static IServiceProvider _serviceProvider;
public DIJobFactory(ILogger<DIJobFactory> logger, IServiceProvider sp)
{
_logger = logger;
_serviceProvider = sp;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
IJobDetail jobDetail = bundle.JobDetail;
Type jobType = jobDetail.JobType;
try
{
_logger.LogDebug($"Producing instance of Job '{jobDetail.Key}', class={jobType.FullName}");
if (jobType == null)
{
throw new ArgumentNullException(nameof(jobType), "Cannot instantiate null");
}
return (IJob)_serviceProvider.GetRequiredService(jobType);
}
catch (Exception e)
{
SchedulerException se = new SchedulerException($"Problem instantiating class '{jobDetail.JobType.FullName}'", e);
throw se;
}
}
// get from https://github.com/quartznet/quartznet/blob/139aafa23728892b0a5ebf845ce28c3bfdb0bfe8/src/Quartz/Simpl/SimpleJobFactory.cs
public void ReturnJob(IJob job)
{
var disposable = job as IDisposable;
disposable?.Dispose();
}
}
ReminderJob.cs
public interface IReminderJob : IJob
{
}
public class ReminderJob : IReminderJob
{
ILogger<ReminderJob> _logger;
IBot _bot;
public ReminderJob(ILogger<ReminderJob> logger, IBot bot)
{
_logger = logger;
_bot = bot;
}
public async Task Execute(IJobExecutionContext context)
{
var parameters = context.JobDetail.JobDataMap;
var userId = parameters.GetLongValue(ReminderJobConst.ChatId);
var homeWorkId = parameters.GetLongValue(ReminderJobConst.HomeWordId);
await _bot.Send(userId.ToString(), "test");
}
}

log4net.LogicalThreadContext.Properties does not work correctly with OwinMiddleware

I am trying to use log4net.LogicalThreadContext to store some data in the OwinMiddleware, so i can log it later in the ApiController, but it doesn't seem to work. The data stored in log4net.LogicalThreadContext doesn't seems to make it available in the ApiController. Here is my code snippet:
Created the ApiMiddleWare in order to inject some log data to LogicalThreadContext.Properties["logdata"]:
public class ApiMiddleWare : OwinMiddleware
{
public ApiMiddleWare(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
var loggers = new ConcurrentDictionary<string, object>();
if (!loggers.ContainsKey("CorellationId"))
{
var correlationId = new[] {Guid.NewGuid().ToString().Replace("-", "")};
loggers.TryAdd("CorellationId", correlationId[0]);
}
if (context.Request.Path.HasValue)
{
loggers.TryAdd("Route", context.Request.Uri.AbsoluteUri);
}
LogicalThreadContext.Properties["logdata"] = loggers;
await Next.Invoke(context);
}
}
Then ApiMiddleWare will be used in Startup.cs in ServiceHost as below:
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
app.Use<ApiMiddleWare>();
Log.Configure();
}
}
I created a custom RollingFileAppeanderEx to capture log data that was assigned in the middleware and log it:
public class RollingFileAppenderEx: RollingFileAppender
{
protected static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore,
DateFormatHandling = DateFormatHandling.IsoDateFormat
};
protected override void Append(LoggingEvent loggingEvent)
{
if (FilterEvent(loggingEvent))
{
var logdata = loggingEvent.GetLoggingEventData();
logdata.Message = GetLogData(loggingEvent);
loggingEvent = new LoggingEvent(loggingEvent.GetType(), loggingEvent.Repository, logdata, loggingEvent.Fix);
base.Append(loggingEvent);
}
}
protected string GetLogData(LoggingEvent logEvent)
{
IDictionary<string, object> logData = new Dictionary<string, object>();
var logD = logEvent.Properties["logdata"] as ConcurrentDictionary<string, object>;
if logD != null)
{
foreach (var log in logD)
{
logData.Add(log.Key, log.Value);
}
}
logData.Add("Message", logObject.Message);
var logString = JsonConvert.SerializeObject(logData, JsonSettings);
return logString;
}
}
From The ApiController, call Info function to log:
public class TestsController : ApiController
{
private static readonly ILogger Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public string Get(int id)
{
Log.Info("Something");
return id;
}
}
Here is my problem: Only "Something" was written to the log. However, CorellationId and Route were not. Debugging through the code, I found that "logEvent.Properties["logdata"] as ConcurrentDictionary" returned nullable value in the RollingFileAppenderEx. So i have a theory: it seems that TestsController class is not in the same thread or not a child thread from ApiMiddleWare. Therefore, data stored in LogicalThreadContext does not propagate all the way.
If anyone can help to see if there is a way to do this, or maybe there is a bug in my code. I would appreciate it. Thanks.
Maybe you have to call loggingEvent.GetLoggingEventData()?
I had:
public abstract class AwsVerboseLogsAppender : AppenderSkeleton
{
// ...
protected override void Append(LoggingEvent loggingEvent)
{
// ...
}
// ...
}
// ...
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log4net.config");
var fileinfo = new FileInfo(path);
XmlConfigurator.Configure(fileinfo);
var log = LogManager.GetLogger(GetType());
LogicalThreadContext.Properties["Test"] = "MyValue";
log.Debug("test");
And loggingEvent.Properties was empty inside Append.
However if I called loggingEvent.GetLoggingEventData(), then "Test" and MyValue showed up inside loggingEvent.Properties. So maybe you have to call that method.

Categories

Resources