mass transit publisher winth custom bind to queue - masstransit

I want to publish messages to a client that is not a mass transit library, and I want to have a custom naming of the exchange point for my entity, and have my own custom connection of this exchange point with the queue, tell me how can I do this?
I try this, but it is not working =(
StartUp
public void ConfigureServices(IServiceCollection services)
{
services.AddMassTransit(busConfig =>
{
busConfig.UsingRabbitMq((context, cfg) =>
{
cfg.Host("localhost", 5672, "/", configurator =>
{
configurator.Username("guest");
configurator.Password("guest");
});
cfg.UsePrometheusMetrics(serviceName: "publisher_service");
cfg.ConfigurePublish(x =>
{
x.UseExecute(publishContext =>
{
publishContext.Serializer =
new RawJsonMessageSerializer(RawJsonSerializerOptions.AnyMessageType);
publishContext.ContentType.MediaType = "application/json";
});
});
cfg.Message<Message>(configurator => configurator.SetEntityName("mass_transit_exchange"));
cfg.Publish<Message>(configurator =>
{
configurator.BindQueue("mass_transit_exchange", "testQUEUE", bindingConfigurator =>
{
bindingConfigurator.ExchangeType = ExchangeType.Direct;
bindingConfigurator.RoutingKey = "key";
});
});
});
});
services.AddMassTransitHostedService();
services.AddControllers();
}
Controller Method
[HttpPost]
public async Task<ActionResult> SentMessage([FromBody] Message message, CancellationToken cancellationToken)
{
message.CreatedOn = DateTimeOffset.Now;
await _endpoint.Publish<Message>(message,
context =>
{
context.Durable = true;
var a = context.Headers.GetAll();
foreach (var keyValuePair in a)
{
context.Headers.Set(keyValuePair.Key, null);
}
context.MessageId = null;
context.TimeToLive = TimeSpan.FromMilliseconds(30000);
},
cancellationToken);
_logger.LogInformation($"message was sent: {JsonConvert.SerializeObject(message, Formatting.Indented)}");
return Ok();
}
I want create this topology in publisher
topology
error
MassTransit.RabbitMqTransport.RabbitMqAddressException: The entity name must be a sequence of these characters: letters, digits, hyphen, underscore, period, or colon.
at MassTransit.RabbitMqTransport.Topology.RabbitMqEntityNameValidator.ThrowIfInvalidEntityName(String name)
at MassTransit.RabbitMqTransport.RabbitMqEndpointAddress..ctor(Uri hostAddress, Uri address)
at MassTransit.RabbitMqTransport.Integration.ConnectionContextSupervisor.NormalizeAddress(Uri address)
at MassTransit.RabbitMqTransport.Transport.RabbitMqSendTransportProvider.NormalizeAddress(Uri address)
at MassTransit.Transports.SendEndpointProvider.GetSendEndpoint(Uri address)
at MassTransit.Transports.ReceiveEndpoint.GetSendEndpoint(Uri address)
at MassTransit.MassTransitBus.MassTransit.ISendEndpointProvider.GetSendEndpoint(Uri address)
at MassTransit.Scoping.ScopedSendEndpointProvider`1.MassTransit.ISendEndpointProvider.GetSendEndpoint(Uri address)
at publisherMassTransit.Controllers.ApiController.SentMessage(Message message, CancellationToken cancellationToken) in C:\Users\qualita\Desktop\examples\publisherMassTransit\Controllers\ApiController.cs:line 32
at lambda_method(Closure , Object )
at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
HEADERS
=======
Connection: keep-alive
Content-Type: application/json
Accept: */*
Accept-Encoding: gzip, deflate, br
Host: localhost:5000
User-Agent: PostmanRuntime/7.28.4
Content-Length: 29
Postman-Token: f93cd39a-0757-4929-9c1a-4bc63393f1cf

You can use the raw JSON serializer, and only need to configure the exchange type for the published message.
public void ConfigureServices(IServiceCollection services)
{
services.AddMassTransit(busConfig =>
{
busConfig.UsingRabbitMq((context, cfg) =>
{
cfg.Host("localhost", 5672, "/", configurator =>
{
configurator.Username("guest");
configurator.Password("guest");
});
cfg.UseRawJsonSerializer();
cfg.UsePrometheusMetrics(serviceName: "publisher_service");
});
});
services.AddMassTransitHostedService();
}
And instead of wiring up all that confusing publish topology, just send the message:
public class Controller
{
readonly ISendEndpointProvider _sendEndpointProvider;
public Controller(ISendEndpointProvider sendEndpointProvider)
{
_sendEndpointProvider = sendEndpointProvider;
}
[HttpPost]
public async Task<ActionResult> SentMessage([FromBody] Message message, CancellationToken cancellationToken)
{
message.CreatedOn = DateTimeOffset.Now;
var endpoint = await _sendEndpointProvider.GetSendEndpoint(new Uri('queue:name&type=direct'));
await endpoint.Send<Message>(message,
context =>
{
var a = context.Headers.GetAll();
foreach (var keyValuePair in a)
{
context.Headers.Set(keyValuePair.Key, null);
}
context.MessageId = null;
context.TimeToLive = TimeSpan.FromMilliseconds(30000);
},
cancellationToken);
_logger.LogInformation($"message was sent: {JsonConvert.SerializeObject(message, Formatting.Indented)}");
return Ok();
}
}
Or something along those lines. You can specify additional query string parameters as needed to tweak the exchange type.

Related

how to delete the posted adaptive messge in MSteams using c# code

the below code is getting error while deleting the message based on the channel id
public async Task DeleteSentNotification(
string conversationId,
string recipientId,
string serviceUrl,
string tenantId,
string name)
{
// Set the service URL in the trusted list to ensure the SDK includes the token in the request.
MicrosoftAppCredentials.TrustServiceUrl(serviceUrl);
var conversationReference = new ConversationReference
{
ServiceUrl = serviceUrl,
Conversation = new ConversationAccount
{
TenantId = tenantId,
Id = conversationId,
name = name,(AdaptiveCard Json)
},
};
await this.botAdapter.ContinueConversationAsync(
botAppId: this.microsoftAppId,
reference: conversationReference,
callback: async (turnContext, cancellationToken) =>
{
try
{
// Delete message.
await turnContext.DeleteActivityAsync(conversationReference);
}
catch (ErrorResponseException e)
{
var errorMessage = $"{e.GetType()}: {e.Message}";
}
},
cancellationToken: CancellationToken.None);
// return response;
}
DeleteActivityAsync has two overloads, but they're both basically aimed towards deleting an "Activity" (i.e. a specific message or thread). If you want to use the overload that takes in a conversationReference, then that means your conversationReference needs to have the ActivityId set (see here for more on the property). Alternatively, you can use the overload that takes in an activityId (it looks like this: public System.Threading.Tasks.Task DeleteActivityAsync (string activityId, System.Threading.CancellationToken cancellationToken = default);), and then you need to pass in the activityId.
Essentially, as stated at the beginning of my answer, you're telling it to delete an "Activity", but you're not telling it which Activity. See here for the docs: https://learn.microsoft.com/en-us/dotnet/api/microsoft.bot.builder.turncontext.deleteactivityasync?view=botbuilder-dotnet-stable

Dialog stack doesn't reset

I have a scorable dialog that will check if the incoming message is equal to "resetconversation". When true, the dialog resets the dialog stack in the PostAsync method.
When the postasync method is hit, the dialog stack resets as expected but when I again send a message the stack reappears and the conversation proceeds as if it was never reset.
[note]: I have noticed that this issue happens when a PromptDialog.Choice() is expecting an input.
Below is the scorable registration and the scorable dialog class.
Thanks!
Registering my scorable dialog:
Conversation.UpdateContainer(
builder =>
{
builder.RegisterModule(new AzureModule(Assembly.GetExecutingAssembly()));
string conn = ConfigurationManager.ConnectionStrings["BotDataContextConnectionString"].ConnectionString;
var store = new SqlBotDataStore(conn);
builder.Register(c => store)
.Keyed<IBotDataStore<BotData>>(AzureModule.Key_DataStore)
.AsSelf()
.SingleInstance();
//Scorable to check if dialog stack reset has been requested
builder.RegisterType<HandleResetRequestScorable>()
.AsSelf()
.As<IScorable<IActivity, double>>();
});
Scorable Dialog:
[Serializable]
public class HandleResetRequestScorable : ScorableBase<IActivity, bool, double>
{
private readonly IBotToUser _botToUser;
private readonly IDialogStack _dialogStack;
private bool _isMessageType { get; set; }
public HandleResetRequestScorable(IBotToUser botToUser, IDialogStack dialogTask)
{
SetField.NotNull(out _botToUser, nameof(_botToUser), botToUser);
//SetField.NotNull(out _dialogStack, nameof(_dialogStack), dialogTask);
}
protected override async Task<bool> PrepareAsync(IActivity activity, CancellationToken token)
{
string message = string.Empty;
if (activity.Type == ActivityTypes.Event)
{
_isMessageType = false;
message = activity.AsEventActivity().Name.ToLower();
}
else
{
_isMessageType = true;
message = activity.AsMessageActivity().Text.ToLower();
}
if (message == "resetconversation")
return true;
return false;
}
protected override bool HasScore(IActivity item, bool state)
{
return state;
}
protected override double GetScore(IActivity item, bool state)
{
return state ? 1.0 : 0;
}
protected override async Task PostAsync(IActivity activity, bool state, CancellationToken token)
{
try
{
var message = activity as IMessageActivity;
string[] data = message.Value.ToString().Split('|');
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, activity as Activity))
{
//get the conversation related data
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(default(CancellationToken));
//get the dialog stack
var convoStack = scope.Resolve<IDialogStack>();
convoStack.Reset();
if (_isMessageType)
{
await _botToUser.PostAsync("Conversation reset complete.");
await _botToUser.PostAsync("Hi");
}
await botData.FlushAsync(default(CancellationToken));
}
}
catch
{
throw;
}
}
protected override Task DoneAsync(IActivity item, bool state, CancellationToken token)
{
//throw new NotImplementedException();
return Task.CompletedTask;
}
}
}

Nested dialogs: message being sent twice

I have a bot where I am doing something like this:
1) A new Activity (message) arrives.
2) I dispatch the message to a RootDialog.
3) Depending on some Logic, RootDialog can:
a) Call a LuisDialog (handling natural language)
b) Call a CustomDialog (handles some business logic).
But when the user state is reset, and the flow leads to an intention inside the LuisDialog, it calls the intention method twice. Just the first time the state is empty, then it works fine.
Let me show you the code:
MessagesController:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
try
{
await Conversation.SendAsync(activity, () => new RootDialog());
}
catch (HttpRequestException e)
{
...
}
}
RootDialog:
public class RootDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
await MessageReceivedAsync(context, Awaitable.FromItem(context.Activity.AsMessageActivity()));
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> awaitable)
{
bool value = DoSomeCustomLogic();
if (value)
{
string message = DoSomething();
await context.PostAsync(message);
} else {
bool value2 = DoSomeCustomLogic2();
if (value2)
{
var answerValidationDialog = new ValidateAnswerWithUserDialog();
context.Call(answerValidationDialog, ValidateAnswerWithUserDialogCompletedCallback);
} else {
var luisDialog = new LuisDialog();
await context.Forward(luisDialog,LuisDialogCompletedCallback, context.Activity, CancellationToken.None);
}
}
}
Callbacks only do context.Done(true);
And LuisDialog has an Intention which goes like this:
[LuisIntent(LuisUtils.INTENT_MENU_SALUTE)]
public async Task SaluteOrMenu(IDialogContext context, LuisResult result)
{
if (LuisUtils.IntentScoreIsHighEnough(result))
{
string userName = context.Activity.From.Name;
ContextHelper helper = new ContextHelper(MessageReceived);
await helper.AskUserToDoSomeInitialAction(context, saluteWord, userName);
context.Done(true);
}
else
{
await None(context, result);
}
}
And finally class ContextHelper:
public class ContextHelper
{
private Func<IDialogContext, IAwaitable<IMessageActivity>, Task> MessageReceived;
public ContextHelper(Func<IDialogContext, IAwaitable<IMessageActivity>, Task> messageReceived)
{
MessageReceived = messageReceived;
}
public async Task AskUserToDoSomeInitialAction(IDialogContext context, string saluteWord, string userName)
{
var reply = context.MakeMessage();
List<CardAction> buttons = BuildInitialOptionActions();
List<CardImage> images = BuildInitialOptionImages();
string initialText = $"Hi stranger!"
var card = new HeroCard
{
Title = "Hello!"
Text = initialText,
Buttons = buttons,
Images = images
};
reply.Attachments = new List<Attachment> { card.ToAttachment() };
await context.PostAsync(reply);
context.Wait(AfterUserChoseOptionInSalute);
}
private async Task AfterUserChoseOptionInSalute(IDialogContext context, IAwaitable<IMessageActivity> result)
{
await ReDispatchMessageReceivedToDialog(context);
}
private async Task ReDispatchMessageReceivedToDialog(IDialogContext context)
{
await MessageReceived(context, Awaitable.FromItem(context.Activity.AsMessageActivity()));
}
}
The SaluteOrMenu Intention gets called twice (only the first time I interact with the bot or when I delete the state. After Debugging I saw that after doing context.Wait(AfterUserChoseOptionInSalute);, the bot calls that function (instead of waiting for an event to call it)
Any ideas?
Thanks in advance.
I found the line that was wrong. It was on the first dialog (the RootDialog):
public async Task StartAsync(IDialogContext context)
{
await MessageReceivedAsync(context, Awaitable.FromItem(context.Activity.AsMessageActivity()));
}
That is a line that re dispatches a message with the incoming activity. I had it somewhere in some chunk of code and (don't know why), thought it was a good idea to use it on StartAsync. So, because of that, two calls were occurring.
Dumb me.
I just changed it to this and worked:
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}

Get HttpHeaders from HttpRequestException?

I have a Web API, When the incoming request is not valid then the API sends back a HttpStatusCode.BadRequest and API would also add a CorrelationId into Response's HttpHeader. Something like below
public class ValidateRequestAttribute : ActionFilterAttribute
{
public ValidateRequestAttribute()
{
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.ModelState.IsValid == false)
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
context.HttpContext.Response.Headers.Add("x-correlationid", "someid");
context.Result = new ContentResult()
{
Content = "bad request."
};
}
}
}
On client side im using HttpClient to access the API. I am not sure how client would retrieve HttpStatusCode and HttpHeader here. Here is my client code
public bool Process(url)
{
bool result = false;
try
{
Task.Run(async () => await _httpClient.GetStringAsync(url).ConfigureAwait(false)).Result;
}
catch (Exception ex)
{
if(ex is AggregateException)
{
var aggregateException = ex as AggregateException;
foreach(var innerException in aggregateException.InnerExceptions)
{
if (innerException is HttpRequestException)
{
var httpRequestException = innerException as HttpRequestException;
// how do i get StatusCode and HttpHeader values here??
}
}
}
}
return result;
}
I have already gone through SO post here and MSDN article here and also Stephen Cleary's article here
Even though its recommended to make async all the way down, I this case Client and API are both disconnected from each other and client is synchronous. Note that Client's Process method is synchronous method.
Like this:
public bool Process(string url)
{
var result = _httpClient.GetAsync(url).ConfigureAwait(false).GetAwaiter().GetResult();
if (result.StatusCode == HttpStatusCode.BadRequest)
{
IEnumerable<string> values;
if (result.Headers.TryGetValues("x-correlationid", out values))
{
// Should print out "someid"
Console.WriteLine(values.First());
}
}
return result.IsSuccessStatusCode;
}
Also note that doing .GetAwaiter().GetResult(); vs .Result; is recommended since it makes the code easier to work with because it does not throw an AggregateException.
If you want to read the response content as a string just do:
var content = result.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
If you want to make your code async though you should use the async/await keyword and skip the .GetAwaiter().GetResult();.

How to access the underlying object in SetDefaultContentHeaders?

I have a web api where I return a object. When I use the accept header "image/jpg" i want the image representation of that object, but I want to set the file name based on the object I'm returning. I have implemented a BufferedMediaTypeFormatter and thought I should do this in the method SetDefaultContentHeaders like such:
public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
{
base.SetDefaultContentHeaders(type, headers, mediaType);
var myObject = // How do I get this from the response?
var contentDispositionHeader = new ContentDispositionHeaderValue("attachment")
{ FileName = myObject.FileName };
headers.ContentDisposition = contentDispositionHeader;
}
So the problem is how do I get the underlying object when I am in the SetDefaultContentHeaders? I was able to do it in the beta by reading it from the HttpResponseMessage that was passed in to the method, but that has been removed.
You can't get the object instance there.
The only place in the formatter where you can access the object is the WriteToStreamAsync, and by that time you can't modify the headers anymore as they are already sent.
You have two options, either save the filename in the request.Properties in your controller and retrieve in the formatter by overriding GetPerRequestFormatterInstance (because it runs before SetDefaultContentHeaders). Then you can use this value in SetDefaultContentHeaders
//Controller
public Url Get(int id)
{
Request.Properties.Add("name", _repo.Get(id).Name);
return _repo.Get(id);
}
//Formatter
public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, System.Net.Http.HttpRequestMessage request, MediaTypeHeaderValue mediaType)
{
//here save the request.Properties["name"] to some local field which you can use later
return base.GetPerRequestFormatterInstance(type, request, mediaType);
}
Another is to use a delegating handler at the end of the pipeline:
I.e. (of course you have filter out when you want to deserialize and so on):
public class RenameHandler : DelegatingHandler
{
protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken).ContinueWith<HttpResponseMessage>(t =>
{
var msg = t.Result;
var myobj = msg.Content.ReadAsAsync<IMobi>().Result;
msg.Content.Headers.ContentDisposition.FileName = myobj.Name + ".mobi";
return msg;
});
}
}

Resources