Basic implementation of temporary HttpClient. Currently only supports GET requests and doesn't return HTTP headers.

This commit is contained in:
Steve Sanderson 2018-02-23 12:29:53 +00:00
parent 8e57261167
commit 891f2a14d0
8 changed files with 178 additions and 3 deletions

View File

@ -0,0 +1,28 @@
@using Microsoft.AspNetCore.Blazor.Browser.Services.Temporary
@inject HttpClient Http
<h1>Fetch data</h1>
<strong>Response: </strong> @responseText
@functions {
private string responseText;
// TODO: Move to OnInitAsync
protected override void OnParametersSet()
{
Http.GetStringAsync("/").ContinueWith(task =>
{
try
{
responseText = task.Result;
}
catch (Exception ex)
{
Console.Error.WriteLine(ex.InnerException.Message);
Console.Error.WriteLine(ex.InnerException.StackTrace);
}
StateHasChanged();
});
}
}

View File

@ -23,6 +23,11 @@
<span class='glyphicon glyphicon-education'></span> Counter
</c:NavLink>
</li>
<li>
<c:NavLink href=@("/fetchdata")>
<span class='glyphicon glyphicon-th-list'></span> Fetch data
</c:NavLink>
</li>
</ul>
</div>
</div>

View File

@ -1,7 +1,8 @@
import { platform } from './Environment';
import { getAssemblyNameFromUrl } from './Platform/DotNet';
import './Rendering/Renderer';
import './Routing/UriHelper';
import './Services/Http';
import './Services/UriHelper';
import './GlobalExports';
async function boot() {

View File

@ -1,9 +1,9 @@
export interface Platform {
start(loadAssemblyUrls: string[]): Promise<void>;
callEntryPoint(assemblyName: string, entrypointMethod: string, args: System_Object[]);
callEntryPoint(assemblyName: string, entrypointMethod: string, args: (System_Object | null)[]);
findMethod(assemblyName: string, namespace: string, className: string, methodName: string): MethodHandle;
callMethod(method: MethodHandle, target: System_Object | null, args: System_Object[]): System_Object;
callMethod(method: MethodHandle, target: System_Object | null, args: (System_Object | null)[]): System_Object;
toJavaScriptString(dotNetString: System_String): string;
toDotNetString(javaScriptString: string): System_String;

View File

@ -0,0 +1,40 @@
import { registerFunction } from '../Interop/RegisteredFunction';
import { platform } from '../Environment';
import { MethodHandle } from '../Platform/Platform';
const httpClientAssembly = 'Microsoft.AspNetCore.Blazor.Browser';
const httpClientNamespace = `${httpClientAssembly}.Services.Temporary`;
const httpClientTypeName = 'HttpClient';
const httpClientFullTypeName = `${httpClientNamespace}.${httpClientTypeName}`;
let receiveResponseMethod: MethodHandle;
registerFunction(`${httpClientFullTypeName}.Send`, (id: number, requestUri: string) => {
sendAsync(id, requestUri);
});
async function sendAsync(id: number, requestUri: string) {
try {
const response = await fetch(requestUri);
const responseText = await response.text();
dispatchResponse(id, response.status, responseText, null);
} catch (ex) {
dispatchResponse(id, 0, null, ex.toString());
}
}
function dispatchResponse(id: number, statusCode: number, responseText: string | null, errorInfo: string | null) {
if (!receiveResponseMethod) {
receiveResponseMethod = platform.findMethod(
httpClientAssembly,
httpClientNamespace,
httpClientTypeName,
'ReceiveResponse'
);
}
platform.callMethod(receiveResponseMethod, null, [
platform.toDotNetString(id.toString()),
platform.toDotNetString(statusCode.toString()),
responseText === null ? null : platform.toDotNetString(responseText),
errorInfo === null ? null : platform.toDotNetString(errorInfo.toString())
]);
}

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Blazor.Browser.Services.Temporary;
using Microsoft.AspNetCore.Blazor.Services;
using Microsoft.Extensions.DependencyInjection;
using System;
@ -41,6 +42,7 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services
private void AddDefaultServices(ServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<IUriHelper>(new BrowserUriHelper());
serviceCollection.AddSingleton(new HttpClient());
}
}
}

View File

@ -0,0 +1,99 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Blazor.Browser.Interop;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Blazor.Browser.Services.Temporary
{
/// <summary>
/// Provides mechanisms for sending HTTP requests.
///
/// This is intended to serve as an equivalent to <see cref="System.Net.Http.HttpClient"/>
/// until we're able to use the real <see cref="System.Net.Http.HttpClient"/> inside Mono
/// for WebAssembly.
/// </summary>
public class HttpClient
{
static object _idLock = new object();
static int _nextRequestId = 0;
static IDictionary<int, TaskCompletionSource<HttpResponseMessage>> _pendingRequests
= new Dictionary<int, TaskCompletionSource<HttpResponseMessage>>();
// Making the constructor internal to be sure people only get instances from
// the service provider. It doesn't make any difference right now, but when
// we switch to System.Net.Http.HttpClient, there may be a period where it
// only works when you get an instance from the service provider because it
// has to be configured with a browser-specific HTTP handler. In the long
// term, it should be possible to use System.Net.Http.HttpClient directly
// without any browser-specific constructor args.
internal HttpClient()
{
}
/// <summary>
/// Sends a GET request to the specified URI and returns the response body as
/// a string in an asynchronous operation.
/// </summary>
/// <param name="requestUri">The URI the request is sent to.</param>
/// <returns>A task representing the asynchronous operation.</returns>
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();
}
// <summary>
/// Sends a GET request to the specified URI and returns the response as
/// an instance of <see cref="HttpResponseMessage"/> in an asynchronous
/// operation.
/// </summary>
/// <param name="requestUri">The URI the request is sent to.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public Task<HttpResponseMessage> GetAsync(string requestUri)
{
var tcs = new TaskCompletionSource<HttpResponseMessage>();
int id;
lock (_idLock)
{
id = _nextRequestId++;
_pendingRequests.Add(id, tcs);
}
RegisteredFunction.Invoke<object>($"{typeof(HttpClient).FullName}.Send", 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));
}
}
}
}