Skip to content

Commit

Permalink
[browser][wasm] Configuring request options in Browser WebAssembly (#…
Browse files Browse the repository at this point in the history
…39182)

* [browser][wasm] Initial addition of configuring request options in Browser WebAssembly

* Fix key code.  Not what was proposed

* Add TryGetValue<TValue> and Set<TValue> as per proposal

* Add missing obsolete attribute

* Address review comments

* Update tests to use Options and not obsolete Properties.

* Implement IDictionary<string, object?> explicitly

* Update tests to use Options and not obsolete Properties.

* Add HttpRequestOptions source to the System.Net.Http.Unit.Tests project to fix build.

* Address review comments - explicit

* Fix build error cannot convert from 'string' to 'System.Net.Http.HttpRequestOptionsKey<object>'

* Add tests for HttpRequestOptions

* Fix test build

* Add special case code for NETFRAMEWORK for API change.

* #endif out of place fix

* #endif out of place fix
  • Loading branch information
kjpou1 authored Jul 29, 2020
1 parent e671f57 commit 3eda592
Show file tree
Hide file tree
Showing 11 changed files with 591 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -948,13 +948,26 @@ public async Task AddHttpClient_MessageHandler_SingletonDependency()
var request = new HttpRequestMessage(HttpMethod.Get, "http://example.com/");
var response = await client.HttpClient.SendAsync(request);

#if NETFRAMEWORK
Assert.Same(
services.GetRequiredService<SingletonService>(),
request.Properties[nameof(SingletonService)]);

Assert.Same(
client.Service,
request.Properties[nameof(SingletonService)]);
#else
#nullable enable
request.Options.TryGetValue(new HttpRequestOptionsKey<SingletonService>(nameof(SingletonService)), out SingletonService? optService);
#nullable disable
Assert.Same(
services.GetRequiredService<SingletonService>(),
optService);

Assert.Same(
client.Service,
optService);
#endif
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
Expand All @@ -979,6 +992,7 @@ public async Task AddHttpClient_MessageHandler_Scope_SingletonDependency()
var request = new HttpRequestMessage(HttpMethod.Get, "http://example.com/");
var response = await client.HttpClient.SendAsync(request);

#if NETFRAMEWORK
Assert.Same(
services.GetRequiredService<SingletonService>(),
request.Properties[nameof(SingletonService)]);
Expand All @@ -990,6 +1004,23 @@ public async Task AddHttpClient_MessageHandler_Scope_SingletonDependency()
Assert.Same(
client.Service,
request.Properties[nameof(SingletonService)]);
#else
#nullable enable
request.Options.TryGetValue(new HttpRequestOptionsKey<SingletonService>(nameof(SingletonService)), out SingletonService? optService);
#nullable disable

Assert.Same(
services.GetRequiredService<SingletonService>(),
optService);

Assert.Same(
scope.ServiceProvider.GetRequiredService<SingletonService>(),
optService);

Assert.Same(
client.Service,
optService);
#endif
}
}

Expand Down Expand Up @@ -1035,6 +1066,7 @@ public async Task AddHttpClient_MessageHandler_Scope_ScopedDependency()
var request = new HttpRequestMessage(HttpMethod.Get, "http://example.com/");
var response = await client.HttpClient.SendAsync(request);

#if NETFRAMEWORK
Assert.NotSame(
scope.ServiceProvider.GetRequiredService<ScopedService>(),
request.Properties[nameof(ScopedService)]);
Expand All @@ -1046,6 +1078,22 @@ public async Task AddHttpClient_MessageHandler_Scope_ScopedDependency()
Assert.NotSame(
client.Service,
request.Properties[nameof(ScopedService)]);
#else
#nullable enable
request.Options.TryGetValue(new HttpRequestOptionsKey<ScopedService>(nameof(ScopedService)), out ScopedService? optService);
#nullable disable
Assert.NotSame(
scope.ServiceProvider.GetRequiredService<ScopedService>(),
optService);

Assert.Same(
scope.ServiceProvider.GetRequiredService<ScopedService>(),
client.Service);

Assert.NotSame(
client.Service,
optService);
#endif
}
}

Expand All @@ -1069,13 +1117,26 @@ public async Task AddHttpClient_MessageHandler_TransientDependency()
var request = new HttpRequestMessage(HttpMethod.Get, "http://example.com/");
var response = await client.HttpClient.SendAsync(request);

#if NETFRAMEWORK
Assert.NotSame(
services.GetRequiredService<TransientService>(),
request.Properties[nameof(TransientService)]);

Assert.NotSame(
client.Service,
request.Properties[nameof(TransientService)]);
#else
#nullable enable
request.Options.TryGetValue(new HttpRequestOptionsKey<TransientService>(nameof(TransientService)), out TransientService? optService);
#nullable disable
Assert.NotSame(
services.GetRequiredService<TransientService>(),
optService);

Assert.NotSame(
client.Service,
optService);
#endif
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
Expand All @@ -1100,13 +1161,26 @@ public async Task AddHttpClient_MessageHandler_Scope_TransientDependency()
var request = new HttpRequestMessage(HttpMethod.Get, "http://example.com/");
var response = await client.HttpClient.SendAsync(request);

#if NETFRAMEWORK
Assert.NotSame(
services.GetRequiredService<TransientService>(),
request.Properties[nameof(TransientService)]);

Assert.NotSame(
client.Service,
request.Properties[nameof(TransientService)]);
#else
#nullable enable
request.Options.TryGetValue(new HttpRequestOptionsKey<TransientService>(nameof(TransientService)), out TransientService? optService);
#nullable disable
Assert.NotSame(
services.GetRequiredService<TransientService>(),
optService);

Assert.NotSame(
client.Service,
optService);
#endif
}
}

Expand Down Expand Up @@ -1304,7 +1378,11 @@ public HandlerWithSingletonService(SingletonService service)

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
#if NETFRAMEWORK
request.Properties[nameof(SingletonService)] = Service;
#else
request.Options.Set(new HttpRequestOptionsKey<SingletonService>(nameof(SingletonService)), Service);
#endif
return Task.FromResult(new HttpResponseMessage());
}
}
Expand All @@ -1320,7 +1398,11 @@ public HandlerWithScopedService(ScopedService service)

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
#if NETFRAMEWORK
request.Properties[nameof(ScopedService)] = Service;
#else
request.Options.Set(new HttpRequestOptionsKey<ScopedService>(nameof(ScopedService)), Service);
#endif
return Task.FromResult(new HttpResponseMessage());
}
}
Expand All @@ -1336,7 +1418,11 @@ public HandlerWithTransientService(TransientService service)

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
#if NETFRAMEWORK
request.Properties[nameof(TransientService)] = Service;
#else
request.Options.Set(new HttpRequestOptionsKey<TransientService>(nameof(TransientService)), Service);
#endif
return Task.FromResult(new HttpResponseMessage());
}
}
Expand Down
33 changes: 33 additions & 0 deletions src/libraries/System.Net.Http/ref/System.Net.Http.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
// Changes to this file must follow the https://aka.ms/api-review process.
// ------------------------------------------------------------------------------

using System.Diagnostics.CodeAnalysis;

namespace System.Net.Http
{
public partial class ByteArrayContent : System.Net.Http.HttpContent
Expand Down Expand Up @@ -212,13 +214,44 @@ public HttpRequestMessage(System.Net.Http.HttpMethod method, System.Uri? request
public System.Net.Http.HttpContent? Content { get { throw null; } set { } }
public System.Net.Http.Headers.HttpRequestHeaders Headers { get { throw null; } }
public System.Net.Http.HttpMethod Method { get { throw null; } set { } }
[Obsolete("Use Options instead.")]
public System.Collections.Generic.IDictionary<string, object?> Properties { get { throw null; } }
public HttpRequestOptions Options { get { throw null; } }
public System.Uri? RequestUri { get { throw null; } set { } }
public System.Version Version { get { throw null; } set { } }
public void Dispose() { }
protected virtual void Dispose(bool disposing) { }
public override string ToString() { throw null; }
}

public readonly struct HttpRequestOptionsKey<TValue>
{
public HttpRequestOptionsKey(string key) {}
public string Key { get { throw null; } }
}

public sealed class HttpRequestOptions : System.Collections.Generic.IDictionary<string, object?>
{
void System.Collections.Generic.IDictionary<string, object?>.Add(string key, object? value) { throw null; }
System.Collections.Generic.ICollection<string> System.Collections.Generic.IDictionary<string, object?>.Keys { get { throw null; } }
System.Collections.Generic.ICollection<object?> System.Collections.Generic.IDictionary<string, object?>.Values { get { throw null; } }
bool System.Collections.Generic.IDictionary<string, object?>.Remove(string key) { throw null; }
bool System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<string, object?>>.Remove(System.Collections.Generic.KeyValuePair<string, object?> item) { throw null; }
bool System.Collections.Generic.IDictionary<string, object?>.TryGetValue(string key, out object? value) { throw null; }
object? System.Collections.Generic.IDictionary<string, object?>.this[string key] { get { throw null; } set { } }
void System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<string, object?>>.Add(System.Collections.Generic.KeyValuePair<string, object?> item) { throw null; }
void System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<string, object?>>.Clear() { throw null; }
bool System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<string, object?>>.Contains(System.Collections.Generic.KeyValuePair<string, object?> item) { throw null; }
bool System.Collections.Generic.IDictionary<string, object?>.ContainsKey(string key) { throw null; }
void System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<string, object?>>.CopyTo(System.Collections.Generic.KeyValuePair<string, object?>[] array, int arrayIndex) { throw null; }
int System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<string, object?>>.Count { get { throw null; } }
bool System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<string, object?>>.IsReadOnly { get { throw null; } }
System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<string, object?>> System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>.GetEnumerator() { throw null; }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
public bool TryGetValue<TValue>(HttpRequestOptionsKey<TValue> key, [MaybeNullWhen(false)] out TValue value) { throw null; }
public void Set<TValue>(HttpRequestOptionsKey<TValue> key, TValue value) { throw null; }
}

public partial class HttpResponseMessage : System.IDisposable
{
public HttpResponseMessage() { }
Expand Down
4 changes: 3 additions & 1 deletion src/libraries/System.Net.Http/src/System.Net.Http.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<WindowsRID>win</WindowsRID>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand Down Expand Up @@ -45,6 +45,8 @@
<Compile Include="System\Net\Http\HttpParseResult.cs" />
<Compile Include="System\Net\Http\HttpRequestException.cs" />
<Compile Include="System\Net\Http\HttpRequestMessage.cs" />
<Compile Include="System\Net\Http\HttpRequestOptions.cs" />
<Compile Include="System\Net\Http\HttpRequestOptionsKey.cs" />
<Compile Include="System\Net\Http\HttpResponseMessage.cs" />
<Compile Include="System\Net\Http\HttpRuleParser.cs" />
<Compile Include="System\Net\Http\HttpTelemetry.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ internal sealed class BrowserHttpHandler : HttpMessageHandler
private static readonly JSObject? s_fetch = (JSObject)System.Runtime.InteropServices.JavaScript.Runtime.GetGlobalObject("fetch");
private static readonly JSObject? s_window = (JSObject)System.Runtime.InteropServices.JavaScript.Runtime.GetGlobalObject("window");

private static readonly HttpRequestOptionsKey<bool> EnableStreamingResponse = new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingResponse");
private static readonly HttpRequestOptionsKey<IDictionary<string, object>> FetchOptions = new HttpRequestOptionsKey<IDictionary<string, object>>("WebAssemblyFetchOptions");

/// <summary>
/// Gets whether the current Browser supports streaming responses
/// </summary>
Expand Down Expand Up @@ -133,8 +136,7 @@ protected internal override async Task<HttpResponseMessage> SendAsync(HttpReques
{
var requestObject = new JSObject();

if (request.Properties.TryGetValue("WebAssemblyFetchOptions", out object? fetchOptionsValue) &&
fetchOptionsValue is IDictionary<string, object> fetchOptions)
if (request.Options.TryGetValue(FetchOptions, out IDictionary<string, object>? fetchOptions))
{
foreach (KeyValuePair<string, object> item in fetchOptions)
{
Expand Down Expand Up @@ -225,9 +227,13 @@ protected internal override async Task<HttpResponseMessage> SendAsync(HttpReques

HttpResponseMessage httpResponse = new HttpResponseMessage((HttpStatusCode)status.Status);

bool streamingEnabled = request.Properties.TryGetValue("WebAssemblyEnableStreamingResponse", out object? streamingEnabledValue) && (bool)(streamingEnabledValue ?? false);
bool streamingEnabled = false;
if (StreamingSupported)
{
request.Options.TryGetValue(EnableStreamingResponse, out streamingEnabled);
}

httpResponse.Content = StreamingSupported && streamingEnabled
httpResponse.Content = streamingEnabled
? new StreamContent(wasmHttpReadStream = new WasmHttpReadStream(status))
: (HttpContent)new BrowserHttpContent(status);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class HttpRequestMessage : IDisposable
private Version _version;
private HttpContent? _content;
private bool _disposed;
private IDictionary<string, object?>? _properties;
private HttpRequestOptions? _options;

public Version Version
{
Expand Down Expand Up @@ -111,17 +111,10 @@ public HttpRequestHeaders Headers

internal bool HasHeaders => _headers != null;

public IDictionary<string, object?> Properties
{
get
{
if (_properties == null)
{
_properties = new Dictionary<string, object?>();
}
return _properties;
}
}
[Obsolete("Use Options instead.")]
public IDictionary<string, object?> Properties => Options;

public HttpRequestOptions Options => _options ??= new HttpRequestOptions();

public HttpRequestMessage()
: this(HttpMethod.Get, (Uri?)null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace System.Net.Http
{
public sealed class HttpRequestOptions : IDictionary<string, object?>
{
private Dictionary<string, object?> Options { get; } = new Dictionary<string, object?>();
object? IDictionary<string, object?>.this[string key]
{
get
{
return Options[key];
}
set
{
Options[key] = value;
}
}
ICollection<string> IDictionary<string, object?>.Keys => Options.Keys;
ICollection<object?> IDictionary<string, object?>.Values => Options.Values;
int ICollection<KeyValuePair<string, object?>>.Count => Options.Count;
bool ICollection<KeyValuePair<string, object?>>.IsReadOnly => ((IDictionary<string, object?>)Options).IsReadOnly;
void IDictionary<string, object?>.Add(string key, object? value) => Options.Add(key, value);
void ICollection<KeyValuePair<string, object?>>.Add(KeyValuePair<string, object?> item) => ((IDictionary<string, object?>)Options).Add(item);
void ICollection<KeyValuePair<string, object?>>.Clear() => Options.Clear();
bool ICollection<KeyValuePair<string, object?>>.Contains(KeyValuePair<string, object?> item) => ((IDictionary<string, object?>)Options).Contains(item);
bool IDictionary<string, object?>.ContainsKey(string key) => Options.ContainsKey(key);
void ICollection<KeyValuePair<string, object?>>.CopyTo(KeyValuePair<string, object?>[] array, int arrayIndex) =>
((IDictionary<string, object?>)Options).CopyTo(array, arrayIndex);
IEnumerator<KeyValuePair<string, object?>> IEnumerable<KeyValuePair<string, object?>>.GetEnumerator() => Options.GetEnumerator();
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => ((System.Collections.IEnumerable)Options).GetEnumerator();
bool IDictionary<string, object?>.Remove(string key) => Options.Remove(key);
bool ICollection<KeyValuePair<string, object?>>.Remove(KeyValuePair<string, object?> item) => ((IDictionary<string, object?>)Options).Remove(item);
bool IDictionary<string, object?>.TryGetValue(string key, out object? value) => Options.TryGetValue(key, out value);
public bool TryGetValue<TValue>(HttpRequestOptionsKey<TValue> key, [MaybeNullWhen(false)] out TValue value)
{
if (Options.TryGetValue(key.Key, out object? _value) && _value is TValue tvalue)
{
value = tvalue;
return true;
}

value = default(TValue);
return false;
}

public void Set<TValue>(HttpRequestOptionsKey<TValue> key, TValue value)
{
Options[key.Key] = value;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

namespace System.Net.Http
{
public readonly struct HttpRequestOptionsKey<TValue>
{
public string Key { get; }
public HttpRequestOptionsKey(string key)
{
Key = key;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ public void Properties_SetPropertiesAndGetTheirValue_MatchingValues()
Assert.Equal(version, rm.Version);

Assert.NotNull(rm.Headers);
Assert.NotNull(rm.Properties);
Assert.NotNull(rm.Options);
}

[Fact]
Expand Down
Loading

0 comments on commit 3eda592

Please sign in to comment.