How to Mock IMessageReceiver? - c#

Currently i have scenario where i need to unit test a Service Bus Trigger Function. Fa code as below
public async Task Run([ServiceBusTrigger("sample", Connection = "sample", IsSessionsEnabled = false)] Message message, IMessageReceiver messageReceiver, ILogger _log)
{
//Some code
}
I was primarily using MessageReceiver, but it's hard to unit test as it's not much flexible to Mock,so i switched to IMessageReceiver. Getting below Error
Microsoft.Azure.WebJobs.Host: Can't bind parameter 'messageReceiver' to type 'Microsoft.Azure.ServiceBus.Core.IMessageReceiver'.
NB:- It have a Weird issue that the variable name should be messageReceiver, while using MessageReceiver .
Is there anything that i need to follow for IMessageReceiver as well?

As of today, IMessageReceiver is not supported via dependency injection. You can only get MessageReceiver. You can upvote the request to add the support here.
Meanwhile, there's a workaround that you could use, showed here. The workaround is to have an additional, internal method, accepting IMessageReceiver and Function call that is injected MessageReceiver to pass the parameter to the internal method.

Related

Azure function - how to pass custom input parameter

I have a problem with passing class object as an input parameter to azure function, what is the proper way of doing it? I have function like posted below.
public async Task<IActionResult> GetClinics(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "clinics")] HttpRequest req, ILogger log, PersonDocument thePerson)
{
//do something
}
Error that i got:
The 'GetClinics' function is in error: Microsoft.Azure.WebJobs.Host: Error indexing method 'GetClinics'. Microsoft.Azure.WebJobs.Host: Cannot bind parameter 'thePerson' to type PersonDocument. Make sure the parameter Type is supported by the binding. If you're using binding extensions (e.g. Azure Storage, ServiceBus, Timers, etc.) make sure you've called the registration method for the extension(s) in your startup code (e.g. builder.AddAzureStorage(), builder.AddServiceBus(), builder.AddTimers(), etc.).
what is the simplest way to solve it? Do i have to prepare my own custom binding? PersonDocument is just a class with some properties that i would like to extract from body. The only information that i found show how to add custom binding with custom attribute, but I am curious if it is really required to add them to solve such an easy problem?
Method signatures developed by the azure function C # class library can only include these:
ILogger or TraceWriter for logging (v1 version only)
A CancellationToken parameter for graceful shutdown
Mark input and output bindings by using attribute decoration
Binding expressions parameters to get trigger metadata
For your question, what you need seems a input binding, so have a look of this doc:
https://learn.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings#supported-bindings
You can see that only a few input bindings is support by azure function by default. So I think if you want to achieve what you want, custom binding is needed.

Azure Service Bus Triggered Function - Bind to MessageReceiver

I'm trying to bind to MessageReceiver in an Azure Service Bus Triggered Function.
My goal is to handle dead letter queue messages and complete them.
public static class Function1
{
[FunctionName("Function1")]
public static async Task Run(
[ServiceBusTrigger(
"<topicName>",
"<subscriptionName>/$DeadLetterQueue",
Connection = "connectionstring")]
Message message,
ILogger logger,
MessageReceiver messageReceiver)
{
// TODO: Perform some actions
await messageReceiver.CompleteAsync(message.SystemProperties.LockToken);
}
The problem is that it fails to bind to the MessageReceiver class.
Microsoft.Azure.WebJobs.Host: Error indexing method 'Function1'. Microsoft.Azure.WebJobs.Host: Cannot bind parameter 'receiver' to type MessageReceiver. Make sure the parameter Type is supported by the binding. If you're using binding extensions (e.g. Azure Storage, ServiceBus, Timers, etc.) make sure you've called the registration method for the extension(s) in your startup code (e.g. builder.AddAzureStorage(), builder.AddServiceBus(), builder.AddTimers(), etc.).
Any ideas why the binding fails?
I figured out what was wrong. I was using 'receiver' as parameter name for MessageReceiver. It turned out that the parameter name has to be 'messageReceiver'. The example I was looking at first used 'receiver', so is this maybe something that has changed?

How to resolve HostedService in Controller

I am trying to add a Background Timer in ASP.NET Core 3.0, which periodically executes a task.
Google led me to this, where I implemented the 'Timed background tasks'.
However, I'm stuck in resolving the HostedService in the controller.
I need a specific instance of TimedHealthCheckService so I can call another public function called 'GetAvailableODataUrl()'.
In the startup.cs I use the following code:
services.AddHostedService<TimedHealthCheckService>();
The TimedHealthCheckService obviously implements IHostedService:
public class TimedHealthCheckService : IHostedService, IDisposable
In my controller, I have the following constructor:
public HealthCheckController(ILogger<HealthCheckController> logger, IHostedService hostedService)
{
this.logger = logger;
this.timedHealthCheckService = hostedService as TimedHealthCheckService;
}
However, when I start my WebAPI, the timedHealthCheckService is always null.
It seems another IHostedService gets injected into the constructor. By checking hostedService, it is actually an object of type GenericWebHostService.
If I change the controller's constructor to:
public HealthCheckController(ILogger<HealthCheckController> logger, TimedHealthCheckService hostedService)
I am getting the following error:
Unable to resolve service for type 'HealthCheck.Web.TimedHealthCheckService' while attempting to activate 'HealthCheck.Web.Controllers.HealthCheckController'.
I also tried services.AddSingleton<IHostedService, TimedHealthCheckService>(); with the same result.
Try these two lines in startup.cs:
services.AddSingleton<TimedHealthCheckService>();
services.AddHostedService<TimedHealthCheckService>(provider => provider.GetService<TimedHealthCheckService>());
The first line above tells the service provider to create a singleton and give it to anyone who wants a TimedHealthCheckService, like your controller's constructor. However, the service provider is unaware that the singleton is actually an IHostedService and that you want it to call StartAsync().
The second line tells the service provider that you want to add a hosted service, so it'll call StartAsync() when the application starts running. AddHostedService accepts a Func<IServiceProvider,THostedService> callback. The callback we provide fetches the singleton TimedHealthCheckService from the service provider and returns it back to the service provider as an IHostedService. The service provider then calls its StartAsync() function.
And in your controller:
public HealthCheckController(ILogger<HealthCheckController> logger, TimedHealthCheckService hostedService)

RegisterWithContext and Lifestyle Mismatch

I wanted to inject a logger to my controllers and I needed to pass extended info to the logger's constructor. For the purpose I've use RegisterWithContext:
container.RegisterWithContext<Common.Logging.ILogger>(context =>
{
if (context.ServiceType == null && !container.IsVerifying())
{
throw new InvalidOperationException(
"Can't request ILogger directly from container, " +
"it must be injected as a dependency.");
}
return new Common.Logging.NLogLogger(context.ImplementationType.FullName);
});
RegisterWithContext extension method explicitly registers the supplied delegate as Transient.
I need to inject the same Logger (Common.Logging.NLogLogger) in a service which happens to be singleton.
Before upgrading to SimpleInjector 3.0.6 things seemed to work as expected and container.Verify() was quite happy with the whole configuration.
After the upgrade the verifier returns a few errors:
[Lifestyle Mismatch] SearchEngineIndexerService (Singleton) depends on
ILogger (Transient). [Lifestyle Mismatch] MembershipService (Web
Request) depends on ILogger (Transient).
and it makes sense. I can understand why that happens and why it should be avoided.
I am trying to avoid the "Do I log too much" syndrome but, actually, I really need to do some logging in a couple of services.
I've tried to use RegisterConditional to register a different logger based on certain conditions but, of course, all the logger now should be registered conditional or I get this exception:
Type ILogger has already been registered as unconditional registration. For non-generic types, conditional and unconditional registrations can't be mixed.
What's the best approach to register a logger as transient for a controller and another one for a singleton service?
The reason you are seeing this exception now, is because v3.0.6 fixed some bugs that prevented the lifestyle mismatch warning from showing up in certain occasions.
It is best to ignore the RegisterWithContext extension method, because it has been superseded by the RegisterConditional method in v3. RegisterConditional however, only allows registering types; not delegates, because delegates allow you to make decisions based on runtime decisions, but it is bad practice to make runtime decisions during object graph resolution.
So instead, it is best to define a proxy logger class that allows forwarding the call to the real logger. For instance:
public sealed class Logger<T> : ILogger
{
private static readonly ILogger logger =
new Common.Logging.NLogLogger(typeof(T).FullName);
// Implement ILogger methods here
void ILogger.Log(string message) {
// Delegate to real logger
logger.Log(message);
}
}
This implementation can be registered as follows:
container.RegisterConditional(typeof(ILogger),
c => typeof(Logger<>).MakeGenericType(c.Consumer.ImplementationType),
Lifestyle.Singleton,
c => true);
The documentation describes this in more detail.

Alternative method for PublishRequest() in new Masstransit

I'm trying to update my code from old version of Masstransit to newer version (v. 3) of it with RabbitMQ, and I want to use a request/response mode that my old code is:
public static void SendCommand<TCommand>(this IServiceBus bus, TCommand command, Action<InlineRequestConfigurator<TCommand>> callback) where TCommand : CommandBase
{
command.Validate();
bus.PublishRequest(command, callback); // Here is my problem
}
I can't find any alternative method for PublishRequest() in IBusControl that I think it is defined instead of IServiceBus.
Any help will appreciated.
Do you truly need to publish your request (versus sending it to a specific endpoint)? Requests should typically be sent to a specific endpoint.
This is well described in the documentation:
http://docs.masstransit-project.com/en/latest/usage/request_response.html
The fact that your method is called SendCommand makes this resonate pretty well.
I believe what you want is on the IBus interface as the IServiceBus was removed in MT 3. IBus is just a collection of interfaces, so you could use the lowest common interface that supports the methods you need.

Categories