Skip to content

Instantly share code, notes, and snippets.

@SteveSandersonMS
Created February 20, 2018 10:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save SteveSandersonMS/ba34011dd4ee8bb3021c8837ff68ef28 to your computer and use it in GitHub Desktop.
Save SteveSandersonMS/ba34011dd4ee8bb3021c8837ff68ef28 to your computer and use it in GitHub Desktop.
Manually implemented HTTP client
@using StandaloneApp.Util
@(Layout<StandaloneApp.Shared.MainLayout>())
<h1>Hello, world!</h1>
Welcome to your new app.
<button @onclick(DoRequest)>Do request</button>
<div><strong>Response: </strong>@responseText</div>
@functions {
string responseText;
private async void DoRequest()
{
responseText = await new MyHttpClient().GetStringAsync("/index.html");
StateHasChanged();
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Blazor standalone</title>
<link href="/css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="/css/site.css" rel="stylesheet" />
</head>
<body>
<app>Loading...</app>
<script src="/css/bootstrap/bootstrap-native.min.js"></script>
<script type="blazor-boot"></script>
<script src="myhttpclient.js"></script>
</body>
</html>
using Microsoft.AspNetCore.Blazor.Browser.Interop;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
namespace StandaloneApp.Util
{
public class MyHttpClient
{
static object _idLock = new object();
static int _nextRequestId = 0;
static IDictionary<int, TaskCompletionSource<HttpResponseMessage>> _pendingRequests
= new Dictionary<int, TaskCompletionSource<HttpResponseMessage>>();
public async Task<string> GetStringAsync(string requestUri)
{
var response = await GetAsync(requestUri);
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"The response status code was {response.StatusCode}");
}
return await response.Content.ReadAsStringAsync();
}
public Task<HttpResponseMessage> GetAsync(string requestUri)
{
var tcs = new TaskCompletionSource<HttpResponseMessage>();
int id;
lock (_idLock)
{
id = _nextRequestId++;
_pendingRequests.Add(id, tcs);
}
RegisteredFunction.Invoke<object>("HttpClientSend", id, requestUri);
return tcs.Task;
}
private static void ReceiveResponse(string id, string statusCode, string responseText, string errorText)
{
TaskCompletionSource<HttpResponseMessage> tcs;
var idVal = int.Parse(id);
lock (_idLock)
{
tcs = _pendingRequests[idVal];
_pendingRequests.Remove(idVal);
}
if (errorText == null)
{
tcs.SetResult(new HttpResponseMessage
{
StatusCode = (HttpStatusCode)int.Parse(statusCode),
Content = new StringContent(responseText)
});
}
else
{
tcs.SetException(new HttpRequestException(errorText));
}
}
}
}
(function () {
function dispatchResponse(id, statusCode, responseText, errorInfo) {
var method = Blazor.platform.findMethod('StandaloneApp', 'StandaloneApp.Util', 'MyHttpClient', 'ReceiveResponse');
Blazor.platform.callMethod(method, null, [
Blazor.platform.toDotNetString(id.toString()),
Blazor.platform.toDotNetString(statusCode.toString()),
responseText === null ? null : Blazor.platform.toDotNetString(responseText),
errorInfo === null ? null : Blazor.platform.toDotNetString(errorInfo.toString())
]);
}
Blazor.registerFunction('HttpClientSend', function (id, requestUri) {
fetch(requestUri).then(function (response) {
return response.text().then(function (responseText) {
dispatchResponse(id, response.status, responseText, null);
});
}).catch(function (errorInfo) { dispatchResponse(id, 0, null, errorInfo); });
});
})();
@schovan
Copy link

schovan commented Feb 20, 2018

It works, thank you very much!

@TylerBrinkley
Copy link

@SteveSandersonMS Just curious, will thread synchronization locks be needed as the browser is single threaded, correct?

@SteveSandersonMS
Copy link
Author

@TylerBrinkley You're right that the locks aren't strictly necessary. I still usually prefer to put them in for types that would be used on multiple threads simultaneously if that was supported.

@TylerBrinkley
Copy link

@SteveSandersonMS Thanks. Does that mean the runtime will be updated to ignore the locks for performance reasons?

@mlidbom
Copy link

mlidbom commented Feb 23, 2018

I just ran a quick test and my machine takes about 50 million locks a second. In this code we are making network calls through scripting interop. The overhead of the lock is exceedingly unlikely to be even remotely relevant.

@TylerBrinkley
Copy link

@mlidbom I agree in this instance the performance of the lock is not going to be significant but in the general case it would probably be good for the runtime to optimize locks away.

@mlidbom
Copy link

mlidbom commented Feb 23, 2018

@TylerBrinkley
Sure. I would guess that such an optimization would be pretty far down the backlog though. We are talking about client code here. I would assume that other aspects needs optimization more. Assembly size is likely to be rather important. JIT support would likely utterly eclipse any locking optimization etc.
And, of course, unless there is no contention for resources between the tasks, I would focus on API surfaces for now. Not performance.

@TylerBrinkley
Copy link

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment