I am running a v4 Azure Function in an isolated process. It is triggered by a message coming from a Service Bus queue. I have created a simple middleware and would like to get my hands on the incoming data (a simple string). How can I do that from the middleware itself? It doesn't seem FunctionContext is of use in this case.
public class SimpleMiddleware : IFunctionsWorkerMiddleware
{
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
await next(context);
}
}
The service bus message data and metadata is extracted and placed into the BindingData dictionary. Try looking at context.BindingContext.BindingData and find the key that exposes your message's data.
Related
I'm asking about a situation where the consumer gets the message, processes it and then the result of that processing is another message, so something like:
class MyConsumer : IConsumer<MyMessage> {
public async Task Consume(ConsumeContext<MyMessage> context) {
// ...do the processing and then:
await context.Publish<MyResponse>(new()
{
Data = "some data"
});
}
}
So the question is - does using context.Publish have any benefits over injecting IPublishEndpoint? It would be done if the processing would require another component to be separated from the consumer - another class. Then the result of that component processing would be a message which could be published by the injected IPublishEndpoint.
In a consumer (and any of the consumers dependencies), messages published and/or sent should use:
ConsumeContext - easiest in the consumer, since it already has it as part of the method signature.
ISendEndpointProvider or IPublishEndpoint - should be injected into any dependencies of the consumer that need to produce messages. MassTransit is essentially redirecting these two interfaces to the current ConsumeContext behind the scenes.
This is also covered in the documentation.
I'm currently trying to update application that was originally .NET Core 3.1 using MassTransit 6.3.2. It is now configured to use .NET 6.0 and MassTransit 7.3.0
Our application uses MassTransit to send messages via Azure Service Bus, publishing messages to Topics, which then have other Subscribers listening to those Topic.
Cut down, it was implemented like so:
// Program.cs
services.AddMassTransit(config =>
{
config.AddConsumer<AppointmentBookedMessageConsumer>();
config.AddBus(BusControlFactory.ConfigureAzureServiceBus);
});
// BusControlFactory.cs
public static class BusControlFactory
{
public static IBusControl ConfigureAzureServiceBus(IRegistrationContext<IServiceProvider> context)
{
var config = context.Container.GetService<AppConfiguration>();
var azureServiceBus = Bus.Factory.CreateUsingAzureServiceBus(busFactoryConfig =>
{
busFactoryConfig.Host("Endpoint=sb://REDACTED-queues.servicebus.windows.net/;SharedAccessKeyName=MyMessageQueuing;SharedAccessKey=MyKeyGoesHere");
busFactoryConfig.Message<AppointmentBookedMessage>(m => m.SetEntityName("appointment-booked"));
busFactoryConfig.SubscriptionEndpoint<AppointmentBookedMessage>(
"my-subscriber-name",
configurator =>
{
configurator.UseMessageRetry(r => r.Interval(5, TimeSpan.FromSeconds(60)));
configurator.Consumer<AppointmentBookedMessageConsumer>(context.Container);
});
return azureServiceBus;
}
}
}
It has now been changed and upgraded to the latest MassTransit and is implemented like:
// Program.cs
services.AddMassTransit(config =>
{
config.AddConsumer<AppointmentBookedMessageConsumer, AppointmentBookedMessageConsumerDefinition>();
config.UsingAzureServiceBus((context, cfg) =>
{
cfg.Host("Endpoint=sb://REDACTED-queues.servicebus.windows.net/;SharedAccessKeyName=MyMessageQueuing;SharedAccessKey=MyKeyGoesHere");
cfg.Message<AppointmentBookedMessage>(m => m.SetEntityName("appointment-booked"));
cfg.ConfigureEndpoints(context);
});
// AppointmentBookedMessageConsumerDefinition.cs
public class AppointmentBookedMessageConsumerDefinition: ConsumerDefinition<AppointmentBookedMessageConsumer>
{
public AppointmentBookedMessageConsumerDefinition()
{
EndpointName = "testharness.subscriber";
}
protected override void ConfigureConsumer(IReceiveEndpointConfigurator endpointConfigurator, IConsumerConfigurator<AppointmentBookedMessageConsumer> consumerConfigurator)
{
endpointConfigurator.UseMessageRetry(r => r.Interval(5, TimeSpan.FromSeconds(60)));
}
}
The issue if it can be considered one, is that I can't bind to a subscription that already exists.
In the example above, you can see that the EndpointName is set as "testharness.subscriber". There was already a subscription to the Topic "appointment-booked" from prior to me upgrading. However, when the application runs, it does not error, but it receives no messages.
If I change the EndpointName to "testharness.subscriber2". Another subscriber appears in the Azure Service Bus topic (via the Azure Portal) and I start receiving messages. I can see no difference in the names (other than the change that I placed, in this case: the "2" suffix).
Am I missing something here? Is there something else I need to do to get these to bind? Is my configuration wrong? Was it wrong? While I'm sure I can get around this by managing the release more closely and removing unneeded queues once they're using new ones - it feels like the wrong approach.
With Azure Service Bus, ForwardTo on a subscription can be a bit opaque.
While the subscription may indeed visually indicate that it is forwarding to the correctly named queue, it might be that the queue was deleted and recreated at some point without deleting the subscription. This results in a subscription that will build up messages, as it is unable to forward them to a queue that no longer exists.
Why? Internally, a subscription maintains the ForwardTo as an object id, which after the queue is deleted points to an object that doesn't exist – resulting in messages building up in the subscription.
If you have messages in the subscription, you may need to go into the portal and update that subscription to point to the new queue (even though it has the same name), at which point the messages should flow through to the queue.
If there aren't any messages in the subscription (or if they aren't important), you can just delete the subscription and it will be recreated by MassTransit when you restart the bus.
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?
I have a .Net Core Web MVC Application and I want to send a notification to a client with Azure SignalR, when in CosmosDB the change feed is triggered.
FeedToSignalR trigger on new data in CosmosDB and broadcast it through SignalR to a clients.
SignalRConfiguration initialize the SignalR Websocket connection.
The problem is I don’t know how I can call this methods.
Can I call the methods in my Program.cs or in the Startup.cs?
public static class SignalRConfiguration
{
private static AzureSignalR signalR = new AzureSignalR(Environment.GetEnvironmentVariable("AzureSignalRConnectionString"));
/// <summary>
/// This HttpTriggered function returns the SignalR configuration to the web client.
/// </summary>
[FunctionName("SignalRConfiguration")]
public static HttpResponseMessage Run([HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequestMessage req, TraceWriter log)
{
return req.CreateResponse(HttpStatusCode.OK,
new {
hubUrl = signalR.GetClientHubUrl("cosmicServerlessHub"),
accessToken = signalR.GenerateAccessToken("cosmicServerlessHub")
});
}
}
public static class FeedToSignalR
{
private static AzureSignalR signalR = new AzureSignalR(Environment.GetEnvironmentVariable("AzureSignalRConnectionString"));
/// <summary>
/// This function Triggers upon new documents in the Cosmos DB database and broadcasts them to SignalR connected clients.
/// </summary>
[FunctionName("FeedToSignalR")]
public static async Task Run([CosmosDBTrigger(
databaseName: "ToDoList",
collectionName: "Items",
ConnectionStringSetting = "AzureCosmosDBConnectionString",
LeaseConnectionStringSetting = "AzureCosmosDBConnectionString",
CreateLeaseCollectionIfNotExists = true,
LeaseCollectionName = "leases")]IReadOnlyList<Document> documents, TraceWriter log)
{
if (documents != null && documents.Count > 0)
{
var broadcast = documents.Select((d) => new
{
id = d.GetPropertyValue<string>("id"),
price = d.GetPropertyValue<string>("price")
});
await signalR.SendAsync("cosmicServerlessHub", "NewMessages", JsonConvert.SerializeObject(broadcast));
}
}
}
These are two Functions that each get triggered by different events.
Your FeedToSignalR is using the Cosmos DB Trigger which will trigger itself automatically when a new document is added in the Items collection as described in the comment above the function's code.
SignalRConfiguration on the other hand is using an HTTP Trigger which gets called whenever there is an HTTP call to it's HTTP endpoint.
If I recognize the code correctly, it is coming from https://github.com/ealsur/serverlessnotifications
Based on the explanation:
The browser will do an HTTP call to the SignalRConfiguration function to get the configuration
When a new message is typed, it will save that message in Cosmos DB by invoking another Function.
That new document in the collection, will then trigger the Cosmos DB Trigger Function who will in turn send it through Signal R.
Yes, this is exactly the code. I think the problem is that I don't have the json “function.json” like here "https://learn.microsoft.com/de-de/azure/azure-functions/functions-bindings-cosmosdb-v2#trigger---c-script-example" to bind the Cosmos DB Trigger and the other json to bind HTTP Trigger.
This “https://anthonychu.ca/post/cosmosdb-real-time-azure-functions-signalr-service/” example does exactly what I want, but Mr. Chu used the .Net Standard, I need do this in .Net Core MVC or better .Net Framework MVC. On the webpage is a picture of the architecture, that describes what I want to do, I tried do reproduced the Step 3 to 5 with your example.
The only thing that I need to do is, to put the two json files in my project to bind the HTTP Trigger and the Cosmos DB Trigger? How the .Net Core project know to call that json files?
I am working to build an API using WebAPI, and have been using NLog for logging throughout the stack. My API solution has two main projects including:
The website layer itself that implements the controllers and webapi stuff
A service layer that implements "async" commands and handlers in a CQRS-like fashion
What I'm trying to achieve is to automatically generate a unique ID that I can attach to log statements so that any logs written while servicing a single request, no matter what layer they came from, can be linked back to that original request. I'd also like this to work without passing the unique ID around, or having the log statements themselves be concerned with including it in their calls.
With that goal in mind I started looking into writing a custom delegating handler to intercept each request (following this post for guidance) and add a unique ID as a property within NLog. I ended up with the following:
/// <summary>
/// This class is a WebAPI message handler that helps establish the data and operations needed
/// to associate log statements through the entire stack back to the originating request.
///
/// Help from here: http://weblogs.asp.net/fredriknormen/log-message-request-and-response-in-asp-net-webapi
/// </summary>
public class InitializeLoggingMessageHandler : DelegatingHandler
{
private ILogger _logger;
// The logger is injected with Autofac
//
public InitializeLoggingMessageHandler(ILogger logger)
{
_logger = logger;
}
protected async override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
// Get a unique ID for this request
//
var uniqueId = Guid.NewGuid().ToString();
// Now that we have a unique ID for this request, we add it to NLog's MDC as a property
// we can use in the log layouts. We do NOT use the global diagnostic context because
// WebAPI is multi-threaded, and we want the ID to be scoped to just the thread servicing
// this request.
//
NLog.MappedDiagnosticsContext.Set("UniqueId", uniqueId);
// Capture some details about the request for logging
//
var requestInfo = string.Format("{0} {1}", request.Method, request.RequestUri);
var requestMessage = await request.Content.ReadAsByteArrayAsync();
_logger.Info("Request: {0} - {1}", requestInfo, Encoding.UTF8.GetString(requestMessage));
var response = await base.SendAsync(request, cancellationToken);
return response;
}
}
With this code I can then use the unique ID in log layouts like so:
<target xsi:type="Debugger" name="DebugLogger"
layout="${longdate} ${logger} ${mdc:item=UniqueId} ${message}" />
The problem with this approach is that I'm using NLog's MappedDiagnosticsContext to try to save the unique ID as a property that can be used within layouts (so my code doing the logging doesn't need to know). This is a thread-local mechanism for storing values, so it breaks down when you have async code since the thread that starts a request, may not be the one that executes all of it.
So what happens is the first log messages have the unique ID included, but the ones later on could be missing it since they're on a different thread and can't access the value. I also can't use the GlobalDiagnosticsContext within NLog because it's truly global, so multiple requests in WebAPI would easily overwrite the unique ID, and the data would be useless.
So with the goal of associating all log messages back to the request that originated within WebAPI, is there another mechanism that I should be considering?
Take a look at LogicalCallContext. As of .NET 4.5, it supports async scenarios.
Mr. Jeffrey Richter:
The .NET Framework has a little-known facility that allows you to associate data with a “logical” thread-of-execution. This facility is called logical
call context and it allows data to flow to other threads, AppDomains, and even to threads in other processes.
NLog.Extension.Logging ver. 1.0 is able to capture context-properties created with ILogger.BeginScope. These can be extracted using NLog ${mdlc}.
Microsoft engine will by default inject properties like RequestId, RequestPath, etc.
See also: https://github.com/NLog/NLog.Extensions.Logging/wiki/NLog-properties-with-Microsoft-Extension-Logging
If you're using Application Insights they automatically set System.Diagnostics.Activity.Current to an object that has all the Application Insights info you could want and more, including RootId and Id that lets you correlate with other events.
See this answer for more details and how to log it easily with nlog.