With v5 PublishRequest extension was removed from the IBus interface.
We used the callback to handle multiple response types that could be returned from the consumer ( Faults, Validations, actual responses, etc )
What is the equivalent way of publishing a message and wiring up multiple response types ?
// Request/Response contracts, may also return validation failure or fault contract
Request<TMessage> request = await bus.PublishRequest<TMessage>( msg, context => {
context.Handle<TResponse>( value => ... );
context.Handle<TValidation>( value => ... );
context.Handle<Fault>( value => ... );
context.CorrelationId = ...
context.Headers.Set( ... );
});
await request.Task;
You can use the new syntax, which is much cleaner overall.
var client = Bus.CreateRequestClient<RegisterMember>();
var (registered, existing) =
await client.GetResponse<MemberRegistered, ExistingMemberFound>(
new RegisterMember() {MemberId = "Johnny5"});
This will return either of the two responses, and if a fault occurs, either will throw the faulted request exception.
You can also use a request handle to add headers, etc.
var client = Bus.CreateRequestClient<RegisterMember>();
var request = client.Create(new RegisterMember()
{MemberId = "Johnny5"});
// the request is also the send pipe configurator, so...
request.UseExecute(context => context.CorrelationId = someId);
var (registered, existing) =
await request.GetResponse<MemberRegistered, ExistingMemberFound>();
You can see a working test case in the future tests:
https://github.com/MassTransit/MassTransit/blob/develop/src/MassTransit.Futures.Tests/Request_Specs.cs#L170
Related
After much toil and trial and error I managed to issue a "request" from my saga and see it handle the response. My jubilation was cut short however by the appearance of a message in my states' skipped queue. (i'm using azure service bus)
It is of type "urn:message:MassTransit.Scheduling:CancelScheduledMessage".
I am a complete newbie at with mass transit and I'm just trying to get a contrived example going.
My saga calls TaxiToRunway/TaxiingComplete. My bit of saga code
Request(()=>TaxiToRunway, config =>
{
config.Timeout = TimeSpan.FromSeconds(30);
});
...
public Request<PlaneState, TaxiToRunway, TaxiingComplete> TaxiToRunway { get; private set; }
...
Initially(
When(ReadyToDepart)
.Then(context =>
{
context.Saga.Altitude = 0;
context.Saga.Speed = 0;
context.Saga.FlightNo = context.Message.FlightNo;
context.Saga.CorrelationId = context.Message.CorrelationId;
Console.WriteLine($"Flight {context.Message.FlightNo} is ready to depart.");
})
.TransitionTo(Taxiing)
.Request(TaxiToRunway,
(context) => context.Init<TaxiToRunway>(new {CorrelationId = context.Saga.CorrelationId}))
...
During(Taxiing,
Ignore(ReadyToDepart),
When(TaxiToRunway.Completed)
.Then(x =>
{
x.ToString();
})
.TransitionTo(TakingOff),
With a debugger attached I hit the x.ToString() line.
The consumer (in a different host):
public class TaxiToRunwayConsumer: IConsumer<TaxiToRunway>
{
public async Task Consume(ConsumeContext<TaxiToRunway> context)
{
await context.RespondAsync<TaxiingComplete>(new
{
context.Message.CorrelationId
});
}
}
Saga startup config:
cfg.AddSagaStateMachine<PlaneStateMachine, PlaneState>()
.MessageSessionRepository();
cfg.AddServiceBusMessageScheduler();
cfg.UsingAzureServiceBus((context, sbCfg) =>
{
var connectionString = appConfig.ServiceBus.ConnectionString;
sbCfg.Host(connectionString);
EndpointConvention.Map<TaxiToRunway>(new Uri("sb://xxx.servicebus.windows.net/taxi-to-runway"));
sbCfg.UseServiceBusMessageScheduler();
sbCfg.ReceiveEndpoint("plane-state", e =>
{
e.UseInMemoryOutbox();
e.RequiresSession = true;
e.PrefetchCount = 50;
e.MaxConcurrentCalls = 50;
e.ConfigureSaga<PlaneState>(context);
});
sbCfg.ConfigureEndpoints(context);
});
I can see this in the log output:
dbug: MassTransit.Messages[0]
SEND sb://dbpdf-us-dev-sam.servicebus.windows.net/plane-state 80d90000-5d7b-2cf0-7a6b-08da0fd3e7b7 MassTransit.Scheduling.CancelScheduledMessage
Am I supposed to be handling this as an event??
Learning curve on this sure is steep! My question is what do I need to do to not have these messages go to skipped?
So, the reason this doesn't work:
The message session saga repository can only correlate by the SessionId, since it's session-stored data.
The requestId, therefore, MUST equal the saga instance correlationId (aka, the SessionId)
The timeout message, sent by the request, gets a tokenId based upon the sequence number of the scheduled message
Which isn't saved anywhere
So the request timeout isn't canceled
The proper approach, in this scenario, is to use a Request/Response that doesn't have a timeout and use a separate Schedule to schedule the timeout yourself.
I have an async member which ultimately needs to invoke some UI updates, after getting some data from a server.
I think I need something like BeginInvokeOnMainThread, or Dispatcher.Invoke, but neither of these appear to be recognized in the Uno context.
Here's the essence of what I have:
public async Task LoadList()
{
...
// get data
Uri uri = new Uri("https://...");
response = await httpClient.GetAsync(uri);
// display
BeginInvokeOnMainThread () =>
{
... update the UI ...
});
}
But I get the error CS0103 The name 'BeginInvokeOnMainThread' does not exist in the current context UnoTest.Droid, UnoTest.UWP, UnoTest.Wasm
BeginInvokeOnMainThread / Dispatcher.Invoke / Control.Invoke were never a good idea.
Uno should have a SynchronizationContext that is automatically used by await, so manual thread marshaling should not be necessary:
public async Task LoadList()
{
...
// get data
Uri uri = new Uri("https://...");
response = await httpClient.GetAsync(uri);
// display
... update the UI ...
}
I want to do an integration test for the below action.
How can I pass my requestDto object in the integration test?
Neither the GetAsync nor SendAsync method has an overload parameter to pass a custom object to the server.
[Route("{startDate:datetime}")]
[HttpGet]
public HttpResponseMessage Get(DateTime startDate, [FromBody]LessonplannerGetRequest request)
{
request.StartDate = startDate;
var lessonplannerResponse = _service.GetPeriodsByWeekStartDate(request);
return Request.CreateResponse<LessonplannerResponse>(HttpStatusCode.OK, lessonplannerResponse);
}
[Test]
public void Get_Lessons_By_Date()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Get, _server.BaseAddress + "/api/lessonplanner/2014-01-14");
var myRequestDto = new LessonplannerGetRequest();
// Act => QUESTION: HOW do I pass the myRequestDto ???
var response = _client.SendAsync(request, new CancellationToken()).Result;
// Assert
Assert.That(response.StatusCode == HttpStatusCode.OK);
}
UPDATE
As Darrel Miller said:"Technically HTTP says you can send a body, it just says the body doesn't mean anything and cannot be used. HttpClient won't let you send one."
I post here my integration test with HttpClient doing a Get request with complex type + FromBody:
// Arrange
var request = new HttpRequestMessage(HttpMethod.Get, _server.BaseAddress + "/api/lessonplanner/2014-01-14");
var myRequestDto = new LessonplannerGetRequest{ FirstDayOfWeek = DayOfWeek.Sunday, SchoolyearId = 1, StartDate = DateTime.Today};
request.Content = new ObjectContent<LessonplannerGetRequest>(myRequestDto, new JsonMediaTypeFormatter());
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Act
var response = _client.SendAsync(request, new CancellationToken()).Result;
// Assert
Assert.That(response.StatusCode == HttpStatusCode.OK);
Of course is this is not the Http way some might consider doing it differentlly sending complex type via FromUri/query string.
HTML specifications says you cannot send a GET with a body.
HTTP specs allows it.
WebAPI allows it, because it is a service/REST and implements HTTP but not HTML, but many clients and browser won't allow it because they implement both specs and try to be strict.
As for the specifications (RFC1866, page 46; HTML 4.x section 17.13.3) itself, it states:
If the method is "get" and the action is an HTTP URI, the user agent takes the value of action, appends a `?' to it, then appends the form data set, encoded using the "application/x-www-form-urlencoded" content type.
(e.g. if you do a <form> with GET, it will parse all the form params and set them in the query string ?a=b).
In term of pure HTTP and in the context of REST services, nothing prevents that behavior, but not all clients will be able to handle it. It's mostly a best-practice advise when it comes to REST/WebAPI to not handle body data from HttpGet, only URI data (the opposite, POST /action?filter=all is usually tolerated for metadata/action qualifiers, but that's another discussion).
So yeah, it's at your own risk, even if used only internally. As not all clients handle it (e.g. HttpRequestMessage), so you might run into trouble like you have.
You should NOT pass a GET body with HTTPClient.
This question is related to the excellent answer by Youssef. I love OnSendingHeaders callback. I can now add the response headers without worrying about switching streams. Anyways, here is my question. Is it possible to read the response body inside the callback, like so.
public override async Task Invoke(OwinRequest request, OwinResponse response)
{
request.OnSendingHeaders(state =>
{
var resp = (OwinResponse)state;
// Here, I want to convert resp, which is OwinResponse
// to HttpResponseMessage so that when Content.ReadAsStringAsync
// is called off this HttpResponseMessage object, I want the
// response body as string.
var responseMessage = new HttpResponseMessage();
responseMessage.Content = new StreamContent(resp.Body);
// Here I would like to call
// responseMessage.Content.ReadAsStringAsync()
}, response);
await Next.Invoke(request, response);
}
The methods I want to call from the callback are part of classes that depend on HttpResponseMessage and do not want to change them.
If I set the response body to memory stream before the pipeline processing starts (as was initially suggested by Youssef in the linked answer), I'm able to get this working. Is there a better way to do this here in the callback instead of that?
EDIT:
Is this okay?
public override async Task Invoke(OwinRequest request, OwinResponse response)
{
// Do something with request
Stream originalStream = response.Body;
var buffer = new MemoryStream();
response.Body = buffer;
await Next.Invoke(request, response);
var responseMessage = new HttpResponseMessage();
response.Body.Seek(0, SeekOrigin.Begin);
responseMessage.Content = new StreamContent(response.Body);
// Pass responseMessage to other classes for the
// response body to be read like this
// responseMessage.Content.ReadAsStringAsyn()
// Add more response headers
if (buffer != null && buffer.Length > 0)
{
buffer.Seek(0, SeekOrigin.Begin);
await buffer.CopyToAsync(originalStream);
}
}
What do you want to do with the response body?
This callback is invoked on first write, so it's too late to replace the stream. You also can't read from the response stream as there is nothing stored in it normally. This is normally a write-only stream that goes out to the network.
Replacing the response stream earlier is the correct approach here.
when calling rest services using the System.Net.Http.HttpClient i have code like
var response = client.GetAsync("api/MyController").Result;
if(response.IsSuccessStatusCode)
...
is that proper or should i be doing
client.GetAsync("api/MyController").ContinueWith(task => { var response = task.Result; ...}
It is much safer to do the second. There are a variety of scenarios where the first option can cause a deadlock.