Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,36 @@ public SyncReturn DoSyncWorkParamsReturn(int i, string s)
Value = i,
};
}

public async Task DoAsyncWork()
{
await Task.Delay(1000);
Debug.WriteLine("DoAsyncWork");
}

public async Task DoAsyncWorkParams(int i, string s)
{
await Task.Delay(1000);
Debug.WriteLine($"DoAsyncWorkParams: {i}, {s}");
}

public async Task<string> DoAsyncWorkReturn()
{
await Task.Delay(1000);
Debug.WriteLine("DoAsyncWorkReturn");
return "Hello from C#!";
}

public async Task<SyncReturn> DoAsyncWorkParamsReturn(int i, string s)
{
await Task.Delay(1000);
Debug.WriteLine($"DoAsyncWorkParamsReturn: {i}, {s}");
return new SyncReturn
{
Message = "Hello from C#! " + s,
Value = i,
};
}
}

public class SyncReturn
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,30 @@
LogMessage("Invoked DoSyncWorkParamsReturn, return value: message=" + retValue.Message + ", value=" + retValue.Value);
}

async function InvokeDoAsyncWork() {
LogMessage("Invoking DoAsyncWork");
await window.HybridWebView.InvokeDotNet('DoAsyncWork');
LogMessage("Invoked DoAsyncWork");
}

async function InvokeDoAsyncWorkParams() {
LogMessage("Invoking DoAsyncWorkParams");
await window.HybridWebView.InvokeDotNet('DoAsyncWorkParams', [123, 'hello']);
LogMessage("Invoked DoAsyncWorkParams");
}

async function InvokeDoAsyncWorkReturn() {
LogMessage("Invoking DoAsyncWorkReturn");
const retValue = await window.HybridWebView.InvokeDotNet('DoAsyncWorkReturn');
LogMessage("Invoked DoAsyncWorkReturn, return value: " + retValue);
}

async function InvokeDoAsyncWorkParamsReturn() {
LogMessage("Invoking DoAsyncWorkParamsReturn");
const retValue = await window.HybridWebView.InvokeDotNet('DoAsyncWorkParamsReturn', [123, 'hello']);
LogMessage("Invoked DoAsyncWorkParamsReturn, return value: message=" + retValue.Message + ", value=" + retValue.Value);
}

</script>
</head>
<body>
Expand All @@ -84,6 +108,12 @@
<button onclick="InvokeDoSyncWorkReturn()">Call C# method (no params) and get simple return value</button>
<button onclick="InvokeDoSyncWorkParamsReturn()">Call C# method (params) and get complex return value</button>
</div>
<div>
<button onclick="InvokeDoAsyncWork()">Call C# async method (no params)</button>
<button onclick="InvokeDoAsyncWorkParams()">Call C# async method (params)</button>
<button onclick="InvokeDoAsyncWorkReturn()">Call C# async method (no params) and get simple return value</button>
<button onclick="InvokeDoAsyncWorkParamsReturn()">Call C# async method (params) and get complex return value</button>
</div>
<div>
Log: <textarea readonly id="messageLog" style="width: 80%; height: 10em;"></textarea>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,33 @@ public object Invoke_NoParam_ReturnNull()
return null;
}

public async Task Invoke_NoParam_ReturnTask()
{
await Task.Delay(1);
UpdateLastMethodCalled();
}

public async Task<object> Invoke_NoParam_ReturnTaskNull()
{
await Task.Delay(1);
UpdateLastMethodCalled();
return null;
}

public async Task<int> Invoke_NoParam_ReturnTaskValueType()
{
await Task.Delay(1);
UpdateLastMethodCalled();
return 2;
}

public async Task<ComputationResult> Invoke_NoParam_ReturnTaskComplex()
{
await Task.Delay(1);
UpdateLastMethodCalled();
return NewComplexResult;
}

public int Invoke_OneParam_ReturnValueType(Dictionary<string, int> dict)
{
Assert.NotNull(dict);
Expand Down Expand Up @@ -440,6 +467,10 @@ public IEnumerator<object[]> GetEnumerator()
// 3. Methods with different return values (none, simple, complex, etc.)
yield return new object[] { "Invoke_NoParam_NoReturn", null };
yield return new object[] { "Invoke_NoParam_ReturnNull", null };
yield return new object[] { "Invoke_NoParam_ReturnTask", null };
yield return new object[] { "Invoke_NoParam_ReturnTaskNull", null };
yield return new object[] { "Invoke_NoParam_ReturnTaskValueType", ValueTypeResult };
yield return new object[] { "Invoke_NoParam_ReturnTaskComplex", ComplexResult };
yield return new object[] { "Invoke_OneParam_ReturnValueType", ValueTypeResult };
yield return new object[] { "Invoke_OneParam_ReturnDictionary", DictionaryResult };
yield return new object[] { "Invoke_NullParam_ReturnComplex", ComplexResult };
Expand Down
100 changes: 52 additions & 48 deletions src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,77 +105,84 @@ private async void OnWebResourceRequested(CoreWebView2 sender, CoreWebView2WebRe
// Get a deferral object so that WebView2 knows there's some async stuff going on. We call Complete() at the end of this method.
using var deferral = eventArgs.GetDeferral();

var requestUri = HybridWebViewQueryStringHelper.RemovePossibleQueryString(eventArgs.Request.Uri);
var (stream, contentType, statusCode, reason) = await GetResponseStreamAsync(eventArgs.Request.Uri);
var contentLength = stream?.Size ?? 0;
var headers =
$"""
Content-Type: {contentType}
Content-Length: {contentLength}
""";

eventArgs.Response = sender.Environment!.CreateWebResourceResponse(
Content: stream,
StatusCode: statusCode,
ReasonPhrase: reason,
Headers: headers);

// Notify WebView2 that the deferred (async) operation is complete and we set a response.
deferral.Complete();
}

private async Task<(IRandomAccessStream Stream, string ContentType, int StatusCode, string Reason)> GetResponseStreamAsync(string url)
{
var requestUri = HybridWebViewQueryStringHelper.RemovePossibleQueryString(url);

if (new Uri(requestUri) is Uri uri && AppOriginUri.IsBaseOf(uri))
{
var relativePath = AppOriginUri.MakeRelativeUri(uri).ToString().Replace('/', '\\');

string? contentType = null;
Stream? contentStream = null;

// 1. Try special InvokeDotNet path
if (relativePath == InvokeDotNetPath)
{
var fullUri = new Uri(eventArgs.Request.Uri);
var fullUri = new Uri(url);
var invokeQueryString = HttpUtility.ParseQueryString(fullUri.Query);
(var contentBytes, contentType) = InvokeDotNet(invokeQueryString);
var contentBytes = await InvokeDotNetAsync(invokeQueryString);
if (contentBytes is not null)
{
contentStream = new MemoryStream(contentBytes);
var bytesStream = new MemoryStream(contentBytes);
var ras = await CopyContentToRandomAccessStreamAsync(bytesStream);
return (Stream: ras, ContentType: "application/json", StatusCode: 200, Reason: "OK");
}
}

string contentType;

// 2. If nothing found yet, try to get static content from the asset path
if (contentStream is null)
if (string.IsNullOrEmpty(relativePath))
{
if (string.IsNullOrEmpty(relativePath))
{
relativePath = VirtualView.DefaultFile;
contentType = "text/html";
}
else
relativePath = VirtualView.DefaultFile;
contentType = "text/html";
}
else
{
if (!ContentTypeProvider.TryGetContentType(relativePath, out contentType!))
{
if (!ContentTypeProvider.TryGetContentType(relativePath, out contentType!))
{
// TODO: Log this
contentType = "text/plain";
}
// TODO: Log this
contentType = "text/plain";
}

var assetPath = Path.Combine(VirtualView.HybridRoot!, relativePath!);
contentStream = await GetAssetStreamAsync(assetPath);
}

if (contentStream is null)
{
// 3.a. If still nothing is found, return a 404
var notFoundContent = "Resource not found (404)";
eventArgs.Response = sender.Environment!.CreateWebResourceResponse(
Content: null,
StatusCode: 404,
ReasonPhrase: "Not Found",
Headers: GetHeaderString("text/plain", notFoundContent.Length)
);
}
else
var assetPath = Path.Combine(VirtualView.HybridRoot!, relativePath!);
using var contentStream = await GetAssetStreamAsync(assetPath);

if (contentStream is not null)
{
// 3.b. Otherwise, return the content
eventArgs.Response = sender.Environment!.CreateWebResourceResponse(
Content: await CopyContentToRandomAccessStreamAsync(contentStream),
StatusCode: 200,
ReasonPhrase: "OK",
Headers: GetHeaderString(contentType ?? "text/plain", (int)contentStream.Length)
);
// 3.a. If something was found, return the content
var ras = await CopyContentToRandomAccessStreamAsync(contentStream);
return (Stream: ras, ContentType: contentType, StatusCode: 200, Reason: "OK");
}
}

contentStream?.Dispose();
// 3.b. Otherwise, return a 404
var ras404 = new InMemoryRandomAccessStream();
using (var writer = new StreamWriter(ras404.AsStreamForWrite()))
{
writer.WriteLine("Resource not found (404)");
}

// Notify WebView2 that the deferred (async) operation is complete and we set a response.
deferral.Complete();
return (Stream: ras404, ContentType: "text/plain", StatusCode: 404, Reason: "Not Found");

async Task<IRandomAccessStream> CopyContentToRandomAccessStreamAsync(Stream content)
static async Task<IRandomAccessStream> CopyContentToRandomAccessStreamAsync(Stream content)
{
using var memStream = new MemoryStream();
await content.CopyToAsync(memStream);
Expand All @@ -185,9 +192,6 @@ async Task<IRandomAccessStream> CopyContentToRandomAccessStreamAsync(Stream cont
}
}

private protected static string GetHeaderString(string contentType, int contentLength) =>
$@"Content-Type: {contentType}
Content-Length: {contentLength}";

[RequiresUnreferencedCode(DynamicFeatures)]
#if !NETSTANDARD
Expand Down
Loading
Loading