I am trying to implement a request response pattern in C# with the ArtemisNetClient, but having a bit of trouble finding out how to do so in a more generic way in a real solution.
I was able to do something like this in two console applications based on some Java examples:
Sender
static async System.Threading.Tasks.Task Main(string[] args)
{
var connectionFactory = new ConnectionFactory();
var endpoint = Endpoint.Create("localhost", 5672, "guest", "guest");
var connection = await connectionFactory.CreateAsync(endpoint);
string guid = new Guid().ToString();
var requestAddress = "TRADE REQ1";
var responseAddress = "TRADE RESP";
Message message = new Message("BUY AMD 1000 SHARES");
message.SetCorrelationId(guid);
message.ReplyTo = responseAddress;
var producer = await connection.CreateProducerAsync(requestAddress, RoutingType.Anycast);
await producer.SendAsync(message);
var consumer = await connection.CreateConsumerAsync(responseAddress, RoutingType.Anycast);
var responseMessage = await consumer.ReceiveAsync();
Console.WriteLine(responseMessage.GetBody<string>());
}
Receiver
static async System.Threading.Tasks.Task Main(string[] args)
{
// Create connection
var connectionFactory = new ConnectionFactory();
var endpoint = Endpoint.Create("localhost", 5672, "guest", "guest");
var connection = await connectionFactory.CreateAsync(endpoint);
var requestAddress = "TRADE REQ1";
// Create consumer to receive trade request messages
var consumer = await connection.CreateConsumerAsync(requestAddress, RoutingType.Anycast);
var message = await consumer.ReceiveAsync();
Console.WriteLine($"Received message: {message.GetBody<string>()}");
// Confirm trade request and ssend response message
if (!string.IsNullOrEmpty(message.ReplyTo))
{
Message responseMessage = new Message("Confirmed trade request");
responseMessage.SetCorrelationId(message.CorrelationId);
var producer = await connection.CreateProducerAsync(message.ReplyTo);
await producer.SendAsync(responseMessage);
}
}
This worked as expected, but I'd like to have something more down the line of what is described in this article, except it doesn't have any examples of a request response pattern.
To elaborate, I currently have two services that I want to communicate across.
In service 1 I want to create and publish a message and then wait for a response to enrich the instance object and save it to a database. I currently have this, but it lacks the await response message.
public async Task<Instance> CreateInstance(Instance instance)
{
await _instanceCollection.InsertOneAsync(instance);
var #event = new InstanceCreated
{
Id = instance.Id,
SiteUrl = instance.SiteUrl
};
await _messageProducer.PublishAsync(#event);
return instance;
}
I figured I might need to setup a temporary queue/connection or something in the PublishAsync() and change it to e.g. Task<Message> to support returning a response message. But how would I go about doing that? Would I have to do a new connectionfactory + CreateConsumerAsync etc. like in the console app example?
public class MessageProducer
{
private readonly IAnonymousProducer _producer;
public MessageProducer(IAnonymousProducer producer)
{
_producer = producer;
}
public async Task PublishAsync<T>(T message, string replyTo = null, string correlationId = null)
{
var serialized = JsonSerializer.Serialize(message);
var address = typeof(T).Name;
var msg = new Message(serialized);
if (replyTo != null && correlationId != null)
{
msg.CorrelationId = correlationId;
msg.ReplyTo = replyTo;
}
await _producer.SendAsync(address, msg);
}
public async Task PublishAsync<T>(T message, string routeName, string replyTo = null, string correlationId = null)
{
var serialized = JsonSerializer.Serialize(message);
var address = routeName;
var msg = new Message(serialized);
if(replyTo != null && correlationId != null)
{
msg.CorrelationId = correlationId;
msg.ReplyTo = replyTo;
}
await _producer.SendAsync(address, msg);
}
}
In Service 2 I have a InstanceCreatedConsumer which receives messages, but again it lacks a way to return response messages.
public class InstanceCreatedConsumer : ITypedConsumer<InstanceCreated>
{
private readonly MessageProducer _messageProducer;
public InstanceCreatedConsumer(MessageProducer messageProducer)
{
_messageProducer = messageProducer;
}
public async Task ConsumeAsync(InstanceCreated message, CancellationToken cancellationToken)
{
// consume message and return response
}
}
I figured I might be able to extend the ActiveMqExtensions class with a ConsumeAsync and HandleMessage that handles the response message with a return value, but I haven't gotten as far yet.
public static IActiveMqBuilder AddTypedConsumer<TMessage, TConsumer>(this IActiveMqBuilder builder,
RoutingType routingType)
where TConsumer : class, ITypedConsumer<TMessage>
{
builder.Services.TryAddScoped<TConsumer>();
builder.AddConsumer(typeof(TMessage).Name, routingType, HandleMessage<TMessage, TConsumer>);
return builder;
}
private static async Task HandleMessage<TMessage, TConsumer>(Message message, IConsumer consumer, IServiceProvider serviceProvider, CancellationToken token)
where TConsumer : class, ITypedConsumer<TMessage>
{
try
{
var msg = JsonConvert.DeserializeObject<TMessage>(message.GetBody<string>());
using var scope = serviceProvider.CreateScope();
var typedConsumer = scope.ServiceProvider.GetService<TConsumer>();
await typedConsumer.ConsumeAsync(msg, token);
await consumer.AcceptAsync(message);
}
catch(Exception ex)
{
// todo
}
}
Am I totally wrong in what I am trying to achieve here, or is it just not possible with the ArtemisNetClient?
Maybe someone has an example or can confirm whether I am down the right path, or maybe I should be using a different framework.
I am new to this kind of communication through messages like ActiveMQ Artemis, so any guidance is appreciated.
I don't see anything in the ArtemisNetClient that would simplify the request/response pattern from your application's point of view. One might expect something akin to JMS' QueueRequestor, but I don't see anything like that in the code, and I don't see anything like that listed in the documentation.
I recommend you simply do in your application what you did in your example (i.e. manually create the consumer & producer to deal with the responses on each end respectively). The only change I would recommend is to re-use connections so you create as few as possible. A connection pool would be ideal here.
For what it's worth, it looks to me like the first release of ArtemisNetClient was just 3 months ago and according to GitHub all but 2 of the commits to the code-base came from one developer. ArtemisNetClient may grow into a very successful C# client implementation, but at this point it seems relatively immature. Even if the existing code is high quality if there isn't a solid community around the client then chances are it won't have the support necessary to get timely bug fixes, new features, etc. Only time will tell.
With version 2.7.0 ArtemisNetClient introduces IRequestReplyClient interface that can be used to implement a request-response messaging pattern. With ArtemisNetClient.Extensions.DependencyInjection this may look as follows:
Client Side:
First you need to register your typed request-reply client in DI:
public void ConfigureServices(IServiceCollection services)
{
/*...*/
var endpoints = new[] { Endpoint.Create(host: "localhost", port: 5672, "guest", "guest") };
services.AddActiveMq("bookstore-cluster", endpoints)
.AddRequestReplyClient<MyRequestReplyClient>();
/*...*/
}
MyRequestReplyClient is your custom class that expects the IRequestReplyClient to be injected via the constructor. Once you have your custom class, you can either expose the IRequestReplyClient directly or encapsulate sending logic inside of it:
public class MyRequestReplyClient
{
private readonly IRequestReplyClient _requestReplyClient;
public MyRequestReplyClient(IRequestReplyClient requestReplyClient)
{
_requestReplyClient = requestReplyClient;
}
public async Task<TResponse> SendAsync<TRequest, TResponse>(TRequest request, CancellationToken cancellationToken)
{
var serialized = JsonSerializer.Serialize(request);
var address = typeof(TRequest).Name;
var msg = new Message(serialized);
var response = await _requestReplyClient.SendAsync(address, msg, cancellationToken);
return JsonSerializer.Deserialize<TResponse>(response.GetBody<string>());
}
}
That's it regarding the client-side.
Worker side
To implement the worker side you can (as you suggested), change the ITypedConsumer interface to return the message that would be sent back, or you can provide the additional data (ReplyTo and CorrelationId headers) so you can send the response back as part of your consumer logic. I prefer the latter as it's a more flexible option in my opinion.
Modified ITypedConsumer might look like that:
public interface ITypedConsumer<in T>
{
public Task ConsumeAsync(T message, MessageContext context, CancellationToken cancellationToken);
}
Where MessageContext is just a simple dto:
public class MessageContext
{
public string ReplyTo { get; init; }
public string CorrelationId { get; init; }
}
HandleMessage extension method:
private static async Task HandleMessage<TMessage, TConsumer>(Message message, IConsumer consumer, IServiceProvider serviceProvider, CancellationToken token)
where TConsumer : class, ITypedConsumer<TMessage>
{
var msg = JsonSerializer.Deserialize<TMessage>(message.GetBody<string>());
using var scope = serviceProvider.CreateScope();
var typedConsumer = scope.ServiceProvider.GetService<TConsumer>();
var messageContext = new MessageContext
{
ReplyTo = message.ReplyTo,
CorrelationId = message.CorrelationId
};
await typedConsumer.ConsumeAsync(msg, messageContext, token);
await consumer.AcceptAsync(message);
}
MessageProducer has to be slightly changed as well, so you can explicitly pass address and CorrelationId:
public class MessageProducer
{
private readonly IAnonymousProducer _producer;
public MessageProducer(IAnonymousProducer producer)
{
_producer = producer;
}
public async Task PublishAsync<T>(string address, T message, MessageContext context, CancellationToken cancellationToken)
{
var serialized = JsonSerializer.Serialize(message);
var msg = new Message(serialized);
if (!string.IsNullOrEmpty(context.CorrelationId))
{
msg.CorrelationId = context.CorrelationId;
}
await _producer.SendAsync(address, msg, cancellationToken);
}
}
And finally, the exemplary consumer could work like that:
public class CreateBookConsumer : ITypedConsumer<CreateBook>
{
private readonly MessageProducer _messageProducer;
public CreateBookConsumer(MessageProducer messageProducer)
{
_messageProducer = messageProducer;
}
public async Task ConsumeAsync(CreateBook message, MessageContext context, CancellationToken cancellationToken)
{
var #event = new BookCreated
{
Id = Guid.NewGuid(),
Title = message.Title,
Author = message.Author,
Cost = message.Cost,
InventoryAmount = message.InventoryAmount,
UserId = message.UserId,
Timestamp = DateTime.UtcNow
};
await _messageProducer.PublishAsync(context.ReplyTo, #event, new MessageContext
{
CorrelationId = context.CorrelationId
}, cancellationToken);
}
}
I have problem with calling API in my Discord Bot, I am trying to learn async, await but it seems as I am stuck.
This is my Call API class where I am calling API.
public async Task<Root> GetInfoAsync()
{
string path = "https://onemocneni-aktualne.mzcr.cz/api/v2/covid-19/zakladni-prehled.json";
Root data = null;
HttpResponseMessage response = await client.GetAsync(path);
if (response.IsSuccessStatusCode)
{
string json = await response.Content.ReadAsStringAsync();
data = JsonConvert.DeserializeObject<Root>(json);
}
return data;
}
public async Task<string> VypisAsync()
{
Root data = await this.GetInfoAsync();
int deaths = data.data[0].umrti,
recovered = data.data[0].vyleceni,
positive = data.data[0].aktivni_pripady,
hospitalized = data.data[0].aktualne_hospitalizovani;
return $"Aktualni situace v ČR:\n" +
$"vyleceni: {recovered}\n" +
$"aktualne nemocni: {positive}\n" +
$"hospitalizovani: {hospitalized}\n" +
$"zemreli: {deaths}";
}
And here is my covid command
public class CovidModule : ModuleBase<SocketCommandContext>
{
// ~void -> current covid situation in CZ
[Command("covid")]
[Summary("shows current covid situation in CZ")]
public async Task<Task> CovidAsync()
{
CovidApi api = new CovidApi();
return ReplyAsync(await api.VypisAsync());
}
}
I know that all others commands return Task, but I don't know how to make it like that.
ReplyAsync() is an async method, so you need to await it.
Your CovidAsync() method isn't returning an actual value, so in the synchronous world its return value would be void. Since it's an async method you return Task instead.
public async Task CovidAsync()
{
CovidApi api = new CovidApi();
await ReplyAsync(await api.VypisAsync());
}
As an aside, it would be better to have CovidApi as a member of your CovidModule. That way you don't need to keep newing it up in each method.
Using Azure Bot framework v4 - C# ... xunit...
I have the following unit test for testing a Component Dialog that in turn uses an AdaptiveDialog.
As part of unit testing the dialog, I want to pass in options to the dialog - the options that get passed to the OnBeginDialogAsync overload of the Dialog class.
Any thoughts on how we can pass myDialogOptions as the options to the dialog?
Thank you
Regards
Athadu
public class ConfirmationDialog : ComponentDialog
{
public class Options
{
public string PromptTemplate { get; set; }
}
public ConfirmationDialog()
: base("test")
{
}
protected override Task<DialogTurnResult> OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default)
{
//
// Avoiding using BotState here. Instead ... use options...
// do something based on passed in options
//
}
}
[Fact]
public async Task TestMyComponentDialogThatUsesAdaptiveDialog()
{
//Arrange
Setup();
TestAdapter = (TestAdapter)new TestAdapter("my")
.UseStorage(memoryStorage)
.UseBotState(UserState, ConversationState)
.Use(Middlewares[0]);
var dialogState = ConversationState.CreateProperty<DialogState>("dialogState");
var dialogToTest = new ConfirmationDialog();
var dialogManager = new DialogManager(dialogToTest);
var myDialogOptions = new MyOptions { Name = "Jon Doe" };
await new TestFlow(TestAdapter, async (turnContext, cancellationToken) =>
{
<<<<<< How to pass in Dialog Options myDialogOptions to the dialog - need to access it within OnBeginDialogAsync >>>>>
<<<<<< of Dialog class override method OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default) >>>>>
var result = await dialogManager.OnTurnAsync(turnContext, cancellationToken);
})
//Act
.SendConversationUpdate()
//Assert
.AssertReply(activity =>
{
var resolvedActivity = activity.AsMessageActivity();
resolvedActivity.Text.Should().StartWith("Some Text");
}, null, 2100)
.StartTestAsync();
}
You can see in the source code that dialog managers don't pass any options to their root dialogs:
private async Task<DialogTurnResult> HandleBotOnTurnAsync(DialogContext dc, CancellationToken cancellationToken)
{
DialogTurnResult turnResult;
// the bot is running as a root bot.
if (dc.ActiveDialog == null)
{
// start root dialog
turnResult = await dc.BeginDialogAsync(_rootDialogId, cancellationToken: cancellationToken).ConfigureAwait(false);
}
else
{
// Continue execution
// - This will apply any queued up interruptions and execute the current/next step(s).
turnResult = await dc.ContinueDialogAsync(cancellationToken).ConfigureAwait(false);
if (turnResult.Status == DialogTurnStatus.Empty)
{
// restart root dialog
turnResult = await dc.BeginDialogAsync(_rootDialogId, cancellationToken: cancellationToken).ConfigureAwait(false);
}
}
await SendStateSnapshotTraceAsync(dc, "Bot State", cancellationToken).ConfigureAwait(false);
return turnResult;
}
If you want to pass options to BeginDialogAsync then you should call that or PromptAsync yourself.
I have 2 Entity and I want to copy some data from Second Entity to First Entity and after that I want to return a simple string saying Success.I am using Polly to make http request.I am planning to get data in json and then convert it in my Entity model and do the manipulation which I am able to do but Calling both the task which return differnt types(can be slight different data model) giving some error.I am not so good in Multithreading approach.
public interface IMyRepository
{
string ValidateData(MyData myData);
}
public class MyRepository :IMyRepository
{ private readonly RetryPolicy<HttpResponseMessage> _httpRequestPolicy;
public MyRepository()
{
_httpRequestPolicy = Policy.HandleResult<HttpResponseMessage>(
r => r.StatusCode == HttpStatusCode.InternalServerError)
.WaitAndRetryAsync(3,
retryAttempt => TimeSpan.FromSeconds(retryAttempt), (exception, timeSpan, retryCount, context1) =>
{
var msg = $"Retry {retryCount} implemented with Pollys RetryPolicy " +
$"of {context1.PolicyKey} " +
$"at {context1.ExecutionKey}, " +
$"due to: {exception}.";
});
}
public string ValidateData(MyData MyData)
{
var MyDataOne= Task<MyData>.Factory.StartNew(() => await MyRepository.getProfileOne());
var MyDataTwo= Task<MyData>.Factory.StartNew(() => await MyRepository.getProfileTwo());
//Update some property of MyDataOne on basis of MyDataTwo and return true or fasle in variable **result**
return result;
}
public static async Task<InsuranceCompanyData> getCusomerProfile()
{
var httpClient = GetHttpClient();
string requestEndpoint = "numbers/Get";
HttpResponseMessage httpResponse = await _httpRequestPolicy.ExecuteAsync(() => httpClient.GetAsync(requestEndpoint));
IEnumerable<int> numbers = await httpResponse.Content.ReadAsAsync<IEnumerable<int>>();
return new InsuranceCompanyData();
}
private static HttpClient GetHttpClient()
{
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(#"http://localhost:2351/api/");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return httpClient;
}
}
public static async Task<MyData> getProfileOne()
{
var httpClient = GetHttpClient();
string requestEndpoint = "/numbers/Get1";
HttpResponseMessage httpResponse = await _httpRequestPolicy.ExecuteAsync(() => httpClient.GetAsync(requestEndpoint));
// IEnumerable<string> data1= await httpResponse.Content.ReadAsAsync<IEnumerable<string>>();
return new MyData();
}
public static async Task<MyData> getProfileTwo()
{
var httpClient = GetHttpClient();
string requestEndpoint = "/numbers/Get2";
HttpResponseMessage httpResponse = await _httpRequestPolicy.ExecuteAsync(() => httpClient.GetAsync(requestEndpoint));
// IEnumerable<string> data2= await httpResponse.Content.ReadAsAsync<IEnumerable<string>>();
return new MyyData();
}
private static HttpClient GetHttpClient()
{
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(#"http://localhost:2351/api/");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return httpClient;
}
I get these errors:
The 'await' operator can only be used within an async lambda expression. Consider marking this lambda expression with the 'async' modifier.
And
An object reference is required for the non-static field, method, or property 'MyRepository._httpRequestPolicy'
Instead of using Task.Factory.StartNew which is not recommended and doesn't support async lambdas (which you don't even need here), use Task.Run:
var profileOneTask = Task.Run(() => getProfileOne());
var profileTwoTask = Task.Run(() => getProfileTwo());
Notice that I changed the variable names to reflect what they actually are. They are tasks that may, at some point, have a result. They are not the result of those operations.
For the second problem, you declared _httpRequestPolicy as an instance member when you should have declared it as a static member for it to be usable without an instance. As discussed in the comments, though, you could just make getProfileOne and getProfileTwo instance methods.
Why you don't change ValidateData signature and add async keyword to method ?
public async Task<string> ValidateDataAsync(MyData MyData)
{
var task1 = Task<MyData>.Factory.StartNew(() => MyRepository.getProfileOne());
var task2 = Task<MyData>.Factory.StartNew(() => MyRepository.getProfileTwo());
await Task.WhenAll(task1, task2)
//Update some property of MyDataOne on basis of MyDataTwo and return true or fasle in variable **result**
return result;
}
As #Camilo Terevinto said it's better to use Task.Run instead of TaskFactory.StartNew.
Task.Run vs Task.Factory.StartNew
Requirement
FormStateModel already contains FIRST input that users types.
Code
Simply I want to put the string that is in activity.Text inside FormStateModel:
private IDialog<FormStateModel> MakeRootDialog(string input)
{
return Chain.From(() => new FormDialog<FormStateModel>(
new FormStateModel() { Question = input },
ContactDetailsForm.BuildForm,
FormOptions.None));
}
=
public async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(
toBot: activity,
MakeRoot: () => this.MakeRootDialog(activity.Text));
}
else
{
await HandleSystemMessageAsync(activity);
}
var response = this.Request.CreateResponse(HttpStatusCode.OK);
return response;
}
On ConversationUpdate I start conversation simply by asking "Please type your Question:"
private static async Task<Activity> HandleSystemMessageAsync(Activity message)
{
switch (message.Type)
{
case ActivityTypes.DeleteUserData:
break;
case ActivityTypes.ConversationUpdate:
await Welcome(message);
break;
(...)
In that way:
private static async Task Welcome(Activity activity)
{
(...)
reply.Text = string.Format("Hello, how can we help you today? Please type your Question:");
await client.Conversations.ReplyToActivityAsync(reply);
(...)
}
But I can not find a way how to pass it. In this case this exception occurs:
anonymous method closures that capture the environment are not serializable, consider removing environment capture or using a reflection serialization surrogate:
Is there any way around that to populate state model at this step?
Solved by calling RootDialog inside MessagesController, then Calling new FormDialog by context.Call(form, (...));
public async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
await Conversation.SendAsync(activity, () => new LayerDialog());
}
LayerDialog:
[Serializable]
public class LayerDialog: IDialog<IMessageActivity>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(this.OnMessageReceivedAsync);
}
private async Task OnMessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var awaited = await result;
FormStateModel model = new FormStateModel();
model.Value = awaited.Text;
var form = new FormDialog<FormStateModel >(model ,
BuildForm , FormOptions.PromptInStart);
context.Call(form , this.AfterResume);
}