I am using this example https://webdev.dartlang.org/articles/get-data/json-web-service as a starting point to develop Dart app consuming API endpoints data:
void saveData() {
HttpRequest request = new HttpRequest(); // create a new XHR
// add an event handler that is called when the request finishes
request.onReadyStateChange.listen((_) {
if (request.readyState == HttpRequest.DONE &&
(request.status == 200 || request.status == 0)) {
// data saved OK.
print(request.responseText); // output the response from the server
}
});
// POST the data to the server
var url = "http://127.0.0.1:8080/programming-languages";
request.open("POST", url, async: false);
String jsonData = '{"language":"dart"}'; // etc...
request.send(jsonData); // perform the async POST
}
I see this as a traditional callback running when something happens. Here it is executed when the response is received.
Though, I want to try the other approach, like using Futures/Promises or async/await.
Is it possible to turn this example into any of these alternatives in browser?
If so, can you please show the example how it looks when implemented as a Future or async/await?
I concur with #Pacane about using the http package. It provides a much cleaner API for working with http requests, which allow you to easily use async/await.
However, you could write saveData using just the core libraries as follows (dartpad sample here: https://dartpad.dartlang.org/2ed9e39fd887b58532d42a70697ce9cd)
Future<Null> saveData() async {
var response = await HttpRequest.postFormData(
'http://127.0.0.1:8080/programming-languages',
{'language': 'Dart'});
print(response.responseText);
}
Related
I've scoured stackoverflow looking for ways to make synchronous API calls in Blazor WASM, and come up empty. The rest is a fairly length explanation of why I think I want to achieve this, but since Blazor WASM runs single-threaded, all of the ways I can find to achieve this are out of scope. If I've missed something or someone spots a better approach, I sincerely appreciate the effort to read through the rest of this...
I'm working on a Blazor WASM application that targets a GraphQL endpoint. Access to the GraphQL endpoint is granted by passing an appropriate Authorization JWT which has to be refreshed at least every 30 minutes from a login API. I'm using a 3rd party GraphQL library (strawberry-shake) which utilizes the singleton pattern to wrap an HttpClient that is used to make all of the calls to the GraphQL endpoint. I can configure the HttpClient using code like this:
builder.Services
.AddFxClient() // strawberry-shake client
.ConfigureHttpClient((sp, client) =>
{
client.BaseAddress =
new Uri(
"https://[application url]/graphql"); // GraphQL endpoint
var token = "[api token]"; // token retrieved from login API
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
});
The trick now is getting the API token from the login API at least every 30 minutes. To accomplish this, I created a service that tracks the age of the token and gets a new token from the login API when necessary. Pared down, the essential bits of the code to get a token look like this:
public async Task<string> GetAccessToken()
{
if ((_expirationDateTime ?? DateTime.Now).AddSeconds(-300) < DateTime.Now)
{
try
{
var jwt = new
{
token =
"[custom JWT for login API validation]"
};
var payload = JsonSerializer.Serialize(jwt);
var content = new StringContent(payload, Encoding.UTF8, "application/json");
var postResponse = await _httpClient.PostAsync("https://[login API url]/login", content);
var responseString = await postResponse.Content.ReadAsStringAsync();
_accessToken = JsonSerializer.Deserialize<AuthenticationResponse>(responseString).access_token;
_expirationDateTime = DateTime.Now.AddSeconds(1800);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
return _accessToken;
}
So, now I need to wire this up to the code which configures the HttpClient used by the GraphQL service. This is where I'm running into trouble. I started with code that looks like this:
// Add login service
builder.Services.AddSingleton<FxAuthClient>();
// Wire up GraphQL client
builder.Services
.AddFxClient()
.ConfigureHttpClient(async (sp, client) =>
{
client.BaseAddress =
new Uri(
"https://[application url]/graphql");
var token = await sp.GetRequiredService<FxAuthClient>().GetAccessToken();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
});
This "works" when the application is loaded [somewhat surprisingly, since notice I'm not "await"ing the GetAccessToken()]. But the behavior if I let the 30 minute timer run out is that the first attempt I make to access the GraphQL endpoint uses the expired token and not the new token. I can see that GetAccessToken() refreshes expired token properly, and is getting called every time I utilize the FxClient, but except for the first usage of FxClient, the GetAccessToken() code actually runs after the GraphQL request. So in essence, it always uses the previous token.
I can't seem to find anyway to ensure that GetAccessToken() happens first, since in Blazor WASM you are confined to a single thread, so all of the normal ways of enforcing synchronous behavior fails, and there isn't an asynchronous way to configure the FxClient's HttpClient.
Can anyone see a way to get this to work? I'm thinking I may need to resort to writing a wrapper around the strawberry FxClient, or perhaps an asynchronous extension method that wraps the ConfigureHttpClient() function, but so far I've tried to avoid this [mostly because I kept feeling like there must be an "easier" way to do this]. I'm wondering if anyone knows away to force synchronous behavior of the call to the login API in Blazor WASM, sees another approach that would work, or can offer any other suggestion?
Lastly, it occurs to me that it might be useful to see a little more detail of the ConfigureHttpClient method. It is autogenerated, so I can't really change it, but here it is:
public static IClientBuilder<T> ConfigureHttpClient<T>(
this IClientBuilder<T> clientBuilder,
Action<IServiceProvider, HttpClient> configureClient,
Action<IHttpClientBuilder>? configureClientBuilder = null)
where T : IStoreAccessor
{
if (clientBuilder == null)
{
throw new ArgumentNullException(nameof(clientBuilder));
}
if (configureClient == null)
{
throw new ArgumentNullException(nameof(configureClient));
}
IHttpClientBuilder builder = clientBuilder.Services
.AddHttpClient(clientBuilder.ClientName, (sp, client) =>
{
client.DefaultRequestHeaders.UserAgent.Add(
new ProductInfoHeaderValue(
new ProductHeaderValue(
_userAgentName,
_userAgentVersion)));
configureClient(sp, client);
});
configureClientBuilder?.Invoke(builder);
return clientBuilder;
}
I'm currently facing an issue with grpc-web, and a loadbalancer.
Trying to call our grpc webservices from our gateway API, results in the following error:
Status(StatusCode="Unknown", Detail="Bad gRPC response. HTTP status code: 411")
It appears that the either of the following headers are required, content-length or Transfer-Encoding.
I have a method for setting metadata in my client.
private async Task<Metadata> SetMetadata()
{
//More stuff here
headers.Add("Transfer-Encoding", "chunked");
return headers;
}
Here is how i create my client:
private async Task<Services.Protobuf.ServiceClient> CreateClient()
{
var httpMessageHandler = new HttpClientHandler();
_grpcChannel ??= GrpcChannel.ForAddress(
await _serviceAddressProvider.GetServiceAddress<ServiceClient>() ??
throw new InvalidOperationException(),
new GrpcChannelOptions()
{
HttpHandler = new GrpcWebHandler(httpMessageHandler)
});
return new(_grpcChannel);
}
And here is how i use the two
var serviceClient = await CreateClient();
var request = new Request
{
//Request stuff
};
var getListReply = await serviceClient.GetListReplyAsync(request, await SetMetadata());
Now. The issue is that I cannot set either Transfer-Encoding or Content-Lenght headers. They simply get stripped somewhere.
If fiddler is running they get added (by fiddler i assume), and the request actually works. But if fiddler is not running, the headers are not there, and i get the above error. (I honestly don't understand the part with fiddler, i'm only reporting what i'm seeing).
Does anyone have any idea why this happens? and if it's even possible to add the headers i'm trying to add with grpc-web?
I don't know much about grpc-web but grpc-gateway does strip HTTP headers if they don't have a grpcmetadata prefix when it forwards the HTTP request to the grpc server
You can take a look at this issue thread https://github.com/grpc-ecosystem/grpc-gateway/issues/1244
I'm creating an app that requires todo parallel http request, I'm using HttpClient for this.
I'm looping over the urls and foreach URl I start a new Task todo the request.
after the loop I wait untill every task finishes.
However when I check the calls being made with fiddler I see that the request are being called synchronously. It's not like a bunch of request are being made, but one by one.
I've searched for a solution and found that other people have experienced this too, but not with UWP. The solution was to increase the DefaultConnectionLimit on the ServicePointManager.
The problem is that ServicePointManager does not exist for UWP. I've looked in the API's and I thought I could set the DefaultConnectionLimit on HttpClientHandler, but no.
So I have a few Questions.
Is DefaultConnectionLimit still a property that could be set somewhere?
if so, where do i set it?
if not, how do I increase the connnectionlimit?
Is there still a connectionlimit in UWP?
this is my code:
var requests = new List<Task>();
var client = GetHttpClient();
foreach (var show in shows)
{
requests.Add(Task.Factory.StartNew((x) =>
{
((Show)x).NextEpisode = GetEpisodeAsync(((Show)x).NextEpisodeUri, client).Result;}, show));
}
}
await Task.WhenAll(requests.ToArray());
and this is the request:
public async Task<Episode> GetEpisodeAsync(string nextEpisodeUri, HttpClient client)
{
try
{
if (String.IsNullOrWhiteSpace(nextEpisodeUri)) return null;
HttpResponseMessage content; = await client.GetAsync(nextEpisodeUri);
if (content.IsSuccessStatusCode)
{
return JsonConvert.DeserializeObject<EpisodeWrapper>(await content.Content.ReadAsStringAsync()).Episode;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
return null;
}
Oke. I have the solution. I do need to use async/await inside the task. The problem was the fact I was using StartNew instead of Run. but I have to use StartNew because i'm passing along a state.
With the StartNew. The task inside the task is not awaited for unless you call Unwrap. So Task.StartNew(.....).Unwrap(). This way the Task.WhenAll() will wait untill the inner task is complete.
When u are using Task.Run() you don't have to do this.
Task.Run vs Task.StartNew
The stackoverflow answer
var requests = new List<Task>();
var client = GetHttpClient();
foreach (var show in shows)
{
requests.Add(Task.Factory.StartNew(async (x) =>
{
((Show)x).NextEpisode = await GetEpisodeAsync(((Show)x).NextEpisodeUri, client);
}, show)
.Unwrap());
}
Task.WaitAll(requests.ToArray());
I think an easier way to solve this is not "manually" starting requests but instead using linq with an async delegate to query the episodes and then set them afterwards.
You basically make it a two step process:
Get all next episodes
Set them in the for each
This also has the benefit of decoupling your querying code with the sideeffect of setting the show.
var shows = Enumerable.Range(0, 10).Select(x => new Show());
var client = new HttpClient();
(Show, Episode)[] nextEpisodes = await Task.WhenAll(shows
.Select(async show =>
(show, await GetEpisodeAsync(show.NextEpisodeUri, client))));
foreach ((Show Show, Episode Episode) tuple in nextEpisodes)
{
tuple.Show.NextEpisode = tuple.Episode;
}
Note that i am using the new Tuple syntax of C#7. Change to the old tuple syntax accordingly if it is not available.
I have a requirement to a batch a number of web service calls on the receipt of a single message appearing in a (MSMQ) queue.
Is "sagas" the way to go?
The interaction with the 3rd party web service is further complicated because I need to call it once, then poll subsequently for an acknowledgement with a correlation id, returned in the reply to the initial call to the web service.
Yes, sagas could be the way to coordinate the process of initiating the call, polling until the operation has ended, and then doing something else when all the work is done.
If you don't care too much about accidentally making the web service call more than once, you can easily use Rebus' async capabilities to implement the polling - I am currently in the process of building something that basically does this:
public async Task Handle(SomeMessage message)
{
var response = await _client.Get<SomeResponse>("https://someurl") ;
var pollUrl = response.PollUrl;
var resultUrl = response.ResultUrl;
while(true)
{
var result = await _client.Get<PollResult>(pollUrl);
if (result.Status == PollStatus.Processing)
{
await Task.Delay(TimeSpan.FromSeconds(2));
continue;
}
if (result.Status == PollStatus.Done)
{
var finalResult = await _client.Get<FinalResult>(resultUrl);
return new SomeReply(finalResult);;
}
throw new Exception($"Unexpected status while polling {pollUrl}: {result.Status}")
}
}
thus taking advantage of async/await to poll the external webservice while it is processing, while consuming minimal resources in our end.
I want to modify the response body from the token endpoint response.
I've tried to intercept the /Token request with a MessageHandler but it doesn't work.
I'm able to add some additional informations to the response by overriding the OAuthAuthorizationServerProvider.TokenEndpointmethod, but I'm not able to create my own response body.
Is there a way to intercept the /Token request?
Edit
I found out how to remove the response body content from the token endpoint response, like this: HttpContext.Current.Response.SuppressContent = true;
It seems the right way to achieve my goal, but now when I use the context.AdditionalResponseParameters.Add() method to add my custom information, the SuppressContent block any alterations.
Now I have something like this:
// Removing the body from the token endpoint response
HttpContext.Current.Response.SuppressContent = true;
// Add custom informations
context.AdditionalResponseParameters.Add("a", "test");
To simply add new items to the JSON token response, you can use TokenEndpointResponse instead of the TokenEndpoint notification.
If you're looking for a way to completely replace the token response prepared by the OAuth2 authorization server by your own one, there's sadly no easy way to do that because OAuthAuthorizationServerHandler.InvokeTokenEndpointAsync doesn't check the OAuthTokenEndpointContext.IsRequestCompleted property after invoking the TokenEndpointResponse notification.
https://github.com/aspnet/AspNetKatana/blob/dev/src/Microsoft.Owin.Security.OAuth/OAuthAuthorizationServerHandler.cs
This is a known issue, but it was too late to include it in Katana 3 when I suggested to fix it.
You should give Owin.Security.OpenIdConnect.Server a try: it's an a fork of the OAuthAuthorizationServerMiddleware designed for Katana 3.0 and 4.0.
https://www.nuget.org/packages/Owin.Security.OpenIdConnect.Server/1.0.2
Of course, it includes the correct check to allow bypassing the default token request processing (this was even one of the first things I fixed when forking it).
You were almost there +Samoji #Samoji and really helped/inspired me to get the answer.
// Add custom informations
context.AdditionalResponseParameters.Add("a", "test");
// Overwrite the old content
var newToken = context.AccessToken;
context.AdditionalResponseParameters.Add("access_token", newToken);
I found it just replaced my old token with my new.
This question is similar to How to extend IdentityServer4 workflow to run custom code
So you can create custom middleware and register it before OAuth2 service in Startup:
public void Configuration(IAppBuilder app)
{
....
app.Use(ResponseBodyEditorMiddleware.EditResponse);
app.UseOAuthAuthorizationServer(...);
...
}
where custom middleware is:
public static async Task EditResponse(IOwinContext context, Func<Task> next)
{
// get the original body
var body = context.Response.Body;
// replace the original body with a memory stream
var buffer = new MemoryStream();
context.Response.Body = buffer;
// invoke the next middleware from the pipeline
await next.Invoke();
// get a body as string
var bodyString = Encoding.UTF8.GetString(buffer.GetBuffer());
// make some changes to the body
bodyString = $"The body has been replaced!{Environment.NewLine}Original body:{Environment.NewLine}{bodyString}";
// update the memory stream
var bytes = Encoding.UTF8.GetBytes(bodyString);
buffer.SetLength(0);
buffer.Write(bytes, 0, bytes.Length);
// replace the memory stream with updated body
buffer.Position = 0;
await buffer.CopyToAsync(body);
context.Response.Body = body;
}
The best way to intercept request and response is via MessageHandler if you want to avoid doing so after a request has reached the IControllerFactory handler in the pipeline - obviously in that case use a custom 'Attribute'
I have used MessageHandlers in the past to intercept request to api/token, create a new request and get the response, create a new response.
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
//create a new auth request
var authrequest = new HttpRequestMessage();
authrequest.RequestUri = new Uri(string.Format("{0}{1}", customBaseUriFromConfig, yourApiTokenPathFromConfig));
//copy headers from the request into the new authrequest
foreach(var header in request.Headers)
{
authrequest.Headers.Add(header.Key, header.Value);
}
//add authorization header for your SPA application's client and secret verification
//this to avoid adding client id and secret in your SPA
var authorizationHeader =
Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Format("{0}:{1}", _clientIdFromConfig, _secretKeyFromConfig)));
//copy content from original request
authrequest.Content = request.Content;
//add the authorization header to the client for api token
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(request.Headers.Authorization.Scheme, authorizationHeader);
var response = await client.PostAsync(authrequest.RequestUri, authrequest.Content, cancellationToken);
if(response.StatusCode == HttpStatusCode.OK)
{
response.Headers.Add("MyCustomHeader", "Value");
//modify other attributes on the response
}
return response;
}
This works for me perfectly. There is, however, the configuration for this handler required in the WebApiConfig.cs file (RouteConfig.cs if you're using ASP.NET MVC).
Can you elaborate on what it is that does not work for you on the handler?