Skip to content
This repository has been archived by the owner on Feb 5, 2020. It is now read-only.

[WIP] Server-side blazor support #12

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
1fff4b1
Moved wwwroot to server project
CatoLeanTruetschel Mar 13, 2019
a694437
Prepared project for running server-side
CatoLeanTruetschel Mar 15, 2019
7d1876d
Added razor components prototype
CatoLeanTruetschel Mar 15, 2019
6db616f
Replaced obsolete IHostingEnvironment with IWebHostEnvironment
CatoLeanTruetschel Mar 15, 2019
363995c
Cleanup and code style
CatoLeanTruetschel Mar 16, 2019
e39849e
Fixed typo
CatoLeanTruetschel Mar 16, 2019
0836843
Merge remote-tracking branch 'upstream/master' into master
CatoLeanTruetschel May 18, 2019
5bd330a
Reenable pre-rendering
CatoLeanTruetschel May 18, 2019
7f1a495
Re-enable client-side logging
CatoLeanTruetschel May 18, 2019
4f94347
Add missing JSRuntime service to HubConnectionBuilder services
CatoLeanTruetschel May 18, 2019
31aa5dc
Implement client-side HttpMessageHandler that is usable server-side
CatoLeanTruetschel May 19, 2019
e4bbeea
Replace server-side http dispatch with new client-side HttpMessageHan…
CatoLeanTruetschel May 19, 2019
726d626
Replace synchronous JS->.Net invokation with async alternative
CatoLeanTruetschel May 19, 2019
be4dba0
Add new server-side test project
CatoLeanTruetschel May 19, 2019
20b72ca
Ensure backwards compatibility
CatoLeanTruetschel May 19, 2019
f7c9680
Revert temporary changes
CatoLeanTruetschel May 19, 2019
e2b4d9e
Await the JS->.Net invokations in event handlers
CatoLeanTruetschel May 23, 2019
1d33089
Revert "Ensure backwards compatibility"
CatoLeanTruetschel May 23, 2019
e1d6ccf
Add comment to explain pre-rendering short-circuit
CatoLeanTruetschel May 23, 2019
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
19 changes: 17 additions & 2 deletions BlazorSignalR.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
# Visual Studio Version 16
VisualStudioVersion = 16.0.28711.60
MinimumVisualStudioVersion = 15.0.26124.0
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B286BCBD-DAD8-4DE7-9334-3DE18DF233AF}"
EndProject
Expand All @@ -20,6 +20,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorSignalR.Test.Client",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorSignalR.Test.Server", "test\BlazorSignalR.Test.Server\BlazorSignalR.Test.Server.csproj", "{8952910D-6251-48A0-8A00-369553370547}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorSignalR.Test.ServerSide", "test\BlazorSignalR.Test.ServerSide\BlazorSignalR.Test.ServerSide.csproj", "{85B78D00-A3E6-4E9F-8364-DFA658E9A049}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -78,6 +80,18 @@ Global
{8952910D-6251-48A0-8A00-369553370547}.Release|x64.Build.0 = Release|Any CPU
{8952910D-6251-48A0-8A00-369553370547}.Release|x86.ActiveCfg = Release|Any CPU
{8952910D-6251-48A0-8A00-369553370547}.Release|x86.Build.0 = Release|Any CPU
{85B78D00-A3E6-4E9F-8364-DFA658E9A049}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{85B78D00-A3E6-4E9F-8364-DFA658E9A049}.Debug|Any CPU.Build.0 = Debug|Any CPU
{85B78D00-A3E6-4E9F-8364-DFA658E9A049}.Debug|x64.ActiveCfg = Debug|Any CPU
{85B78D00-A3E6-4E9F-8364-DFA658E9A049}.Debug|x64.Build.0 = Debug|Any CPU
{85B78D00-A3E6-4E9F-8364-DFA658E9A049}.Debug|x86.ActiveCfg = Debug|Any CPU
{85B78D00-A3E6-4E9F-8364-DFA658E9A049}.Debug|x86.Build.0 = Debug|Any CPU
{85B78D00-A3E6-4E9F-8364-DFA658E9A049}.Release|Any CPU.ActiveCfg = Release|Any CPU
{85B78D00-A3E6-4E9F-8364-DFA658E9A049}.Release|Any CPU.Build.0 = Release|Any CPU
{85B78D00-A3E6-4E9F-8364-DFA658E9A049}.Release|x64.ActiveCfg = Release|Any CPU
{85B78D00-A3E6-4E9F-8364-DFA658E9A049}.Release|x64.Build.0 = Release|Any CPU
{85B78D00-A3E6-4E9F-8364-DFA658E9A049}.Release|x86.ActiveCfg = Release|Any CPU
{85B78D00-A3E6-4E9F-8364-DFA658E9A049}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -87,6 +101,7 @@ Global
{1C49147F-7C73-4962-A71C-6A193970D058} = {B286BCBD-DAD8-4DE7-9334-3DE18DF233AF}
{752EC32C-EC70-4E7E-B2C3-610B0893249F} = {20DAA632-F8AD-4C5F-9E5F-FC82B7CB56A7}
{8952910D-6251-48A0-8A00-369553370547} = {20DAA632-F8AD-4C5F-9E5F-FC82B7CB56A7}
{85B78D00-A3E6-4E9F-8364-DFA658E9A049} = {20DAA632-F8AD-4C5F-9E5F-FC82B7CB56A7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A97C0A4B-E309-4485-BB76-898B37BFBFFF}
Expand Down
67 changes: 67 additions & 0 deletions src/BlazorSignalR.JS/src/BlazorHttpMessageHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
export class BlazorHttpMessageHandler {
async sendAsync(body: number[], jsonFetchArgs: string): Promise<ResponseDescriptor> {
let response: Response;
let responseData: ArrayBuffer;

const fetchOptions: FetchOptions = JSON.parse(jsonFetchArgs);
const requestInit: RequestInit = Object.assign(fetchOptions.requestInit, fetchOptions.requestInitOverrides);

if (body) {
requestInit.body = new Uint8Array(body);
}

try {
response = await fetch(fetchOptions.requestUri, requestInit);
responseData = await response.arrayBuffer();
} catch (ex) {
return getErrorResponse(ex.toString());
}

return getSuccessResponse(response, responseData);
}
}

function getSuccessResponse(response: Response, responseData: ArrayBuffer): ResponseDescriptor {
const typedResponseData = new Uint8Array(responseData);

const responseDescriptor: ResponseDescriptor = {
statusCode: response.status,
statusText: response.statusText,
headers: [],
bodyData: Array.from(typedResponseData),
errorText: null
};

response.headers.forEach((value, name) => {
responseDescriptor.headers.push([name, value]);
});

return responseDescriptor;
}

function getErrorResponse(errorMessage: string): ResponseDescriptor {
const responseDescriptor: ResponseDescriptor = {
statusCode: null,
statusText: null,
headers: [],
bodyData: null,
errorText: errorMessage
};

return responseDescriptor;
}

// Keep these in sync with the .NET equivalent in BlazorHttpMessageHandler.cs
interface FetchOptions {
requestUri: string;
requestInit: RequestInit;
requestInitOverrides: RequestInit;
}

interface ResponseDescriptor {
statusCode: number | null;
statusText: string | null;
headers: string[][];
bodyData: number[] | null;
errorText: string | null;
}
4 changes: 3 additions & 1 deletion src/BlazorSignalR.JS/src/InitializeSignalR.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { ServerSentEventsTransport } from './ServerSentEventsTransport'
import { WebSocketsTransport } from "./WebSocketsTransport";
import { BlazorHttpMessageHandler } from "./BlazorHttpMessageHandler";

namespace SignalR {
const blazorSignalR: string = 'BlazorSignalR';
// define what this extension adds to the window object inside BlazorSignalR
const extensionObject = {
ServerSentEventsTransport: new ServerSentEventsTransport(),
WebSocketsTransport: new WebSocketsTransport()
WebSocketsTransport: new WebSocketsTransport(),
BlazorHttpMessageHandler: new BlazorHttpMessageHandler()
};

export function initialize(): void {
Expand Down
16 changes: 8 additions & 8 deletions src/BlazorSignalR.JS/src/ServerSentEventsTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
export class ServerSentEventsTransport {
private connections: Map<string, EventSource> = new Map<string, EventSource>();

public CreateConnection = (url: string, managedObj: DotNetReferenceType): void => {
const id = managedObj.invokeMethod<string>("get_InternalSSEId");
const token = managedObj.invokeMethod<string>("get_SSEAccessToken");
public async CreateConnection (url: string, managedObj: DotNetReferenceType): Promise<void> {
const id = await managedObj.invokeMethodAsync<string>("get_InternalSSEId");
const token = await managedObj.invokeMethodAsync<string>("get_SSEAccessToken");

if (token) {
url += (url.indexOf("?") < 0 ? "?" : "&") + `access_token=${encodeURIComponent(token)}`;
Expand All @@ -15,21 +15,21 @@ export class ServerSentEventsTransport {
this.connections.set(id, eventSource);

eventSource.onmessage = (e: MessageEvent) => {
managedObj.invokeMethod<void>("HandleSSEMessage", btoa(e.data));
managedObj.invokeMethodAsync<void>("HandleSSEMessage", btoa(e.data));
CatoLeanTruetschel marked this conversation as resolved.
Show resolved Hide resolved
};

eventSource.onerror = (e: MessageEvent) => {
const error = new Error(e.data || "Error occurred");
managedObj.invokeMethod<void>("HandleSSEError", error.message);
managedObj.invokeMethodAsync<void>("HandleSSEError", error.message);
};

eventSource.onopen = () => {
managedObj.invokeMethod<void>("HandleSSEOpened");
managedObj.invokeMethodAsync<void>("HandleSSEOpened");
};
}

public CloseConnection = (managedObj: DotNetReferenceType): void => {
const id = managedObj.invokeMethod<string>("get_InternalSSEId");
public async CloseConnection (managedObj: DotNetReferenceType): Promise<void> {
const id = await managedObj.invokeMethodAsync<string>("get_InternalSSEId");

const eventSource = this.connections.get(id);

Expand Down
22 changes: 11 additions & 11 deletions src/BlazorSignalR.JS/src/WebSocketsTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
export class WebSocketsTransport {
private static connections: Map<string, WebSocket> = new Map<string, WebSocket>();

public CreateConnection = (url: string, binary: boolean, managedObj: DotNetReferenceType): void => {
const id = managedObj.invokeMethod<string>("get_InternalWebSocketId");
const token = managedObj.invokeMethod<string>("get_WebSocketAccessToken");
public async CreateConnection (url: string, binary: boolean, managedObj: DotNetReferenceType): Promise<void> {
const id = await managedObj.invokeMethodAsync<string>("get_InternalWebSocketId");
const token = await managedObj.invokeMethodAsync<string>("get_WebSocketAccessToken");

if (token) {
url += (url.indexOf("?") < 0 ? "?" : "&") + `access_token=${encodeURIComponent(token)}`;
Expand All @@ -21,25 +21,25 @@ export class WebSocketsTransport {
}

webSocket.onopen = (_event: Event) => {
managedObj.invokeMethod<void>("HandleWebSocketOpened");
managedObj.invokeMethodAsync<void>("HandleWebSocketOpened");
CatoLeanTruetschel marked this conversation as resolved.
Show resolved Hide resolved
};

webSocket.onerror = (event: Event) => {
const error = (event instanceof ErrorEvent) ? event.error : new Error("Error occured");
managedObj.invokeMethod<void>("HandleWebSocketError", error.message);
managedObj.invokeMethodAsync<void>("HandleWebSocketError", error.message);
};

webSocket.onmessage = (message: MessageEvent) => {
managedObj.invokeMethod<void>("HandleWebSocketMessage", btoa(message.data));
managedObj.invokeMethodAsync<void>("HandleWebSocketMessage", btoa(message.data));
};

webSocket.onclose = (event: CloseEvent) => {
managedObj.invokeMethod<void>("HandleWebSocketClosed");
managedObj.invokeMethodAsync<void>("HandleWebSocketClosed");
};
}

public Send = (data: string, managedObj: DotNetReferenceType): void => {
const id = managedObj.invokeMethod<string>("get_InternalWebSocketId");
public async Send (data: string, managedObj: DotNetReferenceType): Promise<void> {
const id = await managedObj.invokeMethodAsync<string>("get_InternalWebSocketId");
const webSocket = WebSocketsTransport.connections.get(id);

if (!webSocket)
Expand All @@ -48,8 +48,8 @@ export class WebSocketsTransport {
webSocket.send(atob(data));
}

public CloseConnection = (managedObj: DotNetReferenceType): void => {
const id = managedObj.invokeMethod<string>("get_InternalWebSocketId");
public async CloseConnection(managedObj: DotNetReferenceType): Promise<void> {
const id = await managedObj.invokeMethodAsync<string>("get_InternalWebSocketId");

const webSocket = WebSocketsTransport.connections.get(id);

Expand Down
61 changes: 53 additions & 8 deletions src/BlazorSignalR/BlazorSignalRExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using BlazorSignalR.Internal;
using Microsoft.AspNetCore.Blazor.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -9,14 +11,44 @@ namespace BlazorSignalR
{
public static class BlazorSignalRExtensions
{
public static IHubConnectionBuilder WithUrlBlazor(this IHubConnectionBuilder hubConnectionBuilder, string url, IJSRuntime jsRuntime,
HttpTransportType? transports = null, Action<BlazorHttpConnectionOptions> options = null)
public static IHubConnectionBuilder WithUrlBlazor(
this IHubConnectionBuilder hubConnectionBuilder,
string url,
IJSRuntime jsRuntime,
HttpTransportType? transports = null,
Action<BlazorHttpConnectionOptions> options = null)
{
return WithUrlBlazor(hubConnectionBuilder, new Uri(url), jsRuntime, transports, options);
return WithUrlBlazor(hubConnectionBuilder, new Uri(url, UriKind.Relative), jsRuntime, GetUriHelper(jsRuntime), transports, options);
}

public static IHubConnectionBuilder WithUrlBlazor(this IHubConnectionBuilder hubConnectionBuilder, Uri url, IJSRuntime jsRuntime,
HttpTransportType? transports = null, Action<BlazorHttpConnectionOptions> options = null)
public static IHubConnectionBuilder WithUrlBlazor(
this IHubConnectionBuilder hubConnectionBuilder,
Uri url,
IJSRuntime jsRuntime,
HttpTransportType? transports = null,
Action<BlazorHttpConnectionOptions> options = null)
{
return WithUrlBlazor(hubConnectionBuilder, url, jsRuntime, GetUriHelper(jsRuntime), transports, options);
}

public static IHubConnectionBuilder WithUrlBlazor(
this IHubConnectionBuilder hubConnectionBuilder,
string url,
IJSRuntime jsRuntime,
IUriHelper uriHelper,
HttpTransportType? transports = null,
Action<BlazorHttpConnectionOptions> options = null)
{
return WithUrlBlazor(hubConnectionBuilder, new Uri(url, UriKind.Relative), jsRuntime, uriHelper, transports, options);
}

public static IHubConnectionBuilder WithUrlBlazor(
this IHubConnectionBuilder hubConnectionBuilder,
Uri url,
IJSRuntime jsRuntime,
IUriHelper uriHelper,
HttpTransportType? transports = null,
Action<BlazorHttpConnectionOptions> options = null)
{
if (hubConnectionBuilder == null)
throw new ArgumentNullException(nameof(hubConnectionBuilder));
Expand All @@ -33,15 +65,28 @@ public static IHubConnectionBuilder WithUrlBlazor(this IHubConnectionBuilder hub
});
if (options != null)
hubConnectionBuilder.Services.Configure(options);
hubConnectionBuilder.Services.AddSingleton(provider => BuildBlazorHttpConnectionFactory(provider, jsRuntime));
hubConnectionBuilder.Services.AddSingleton(provider => BuildBlazorHttpConnectionFactory(provider, jsRuntime, uriHelper));
return hubConnectionBuilder;
}

private static IConnectionFactory BuildBlazorHttpConnectionFactory(IServiceProvider provider, IJSRuntime jsRuntime)
private static IUriHelper GetUriHelper(IJSRuntime jSRuntime)
CatoLeanTruetschel marked this conversation as resolved.
Show resolved Hide resolved
{
// We are running on WASM and can safely use WebAssemblyUriHelper
if (jSRuntime is IJSInProcessRuntime)
{
return WebAssemblyUriHelper.Instance;
}

throw new PlatformNotSupportedException("This overload is supported only when running on the WASM platform.");
}

private static IConnectionFactory BuildBlazorHttpConnectionFactory(
IServiceProvider provider, IJSRuntime jsRuntime, IUriHelper uriHelper)
{
return ActivatorUtilities.CreateInstance<BlazorHttpConnectionFactory>(
provider,
jsRuntime);
jsRuntime,
uriHelper);
}
}
}
Loading