Decoupling formatters from endpoints
Moving formatters out from Sockets
This commit is contained in:
parent
8b905907fe
commit
fb387ed03d
|
|
@ -12,7 +12,6 @@ namespace SocketsSample
|
||||||
{
|
{
|
||||||
public ChatEndPoint()
|
public ChatEndPoint()
|
||||||
{
|
{
|
||||||
Console.Write(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task OnConnected(Connection connection)
|
public override async Task OnConnected(Connection connection)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Channels;
|
using Channels;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using SocketsSample.Hubs;
|
using SocketsSample.Hubs;
|
||||||
|
|
@ -78,7 +79,9 @@ namespace SocketsSample
|
||||||
foreach (var connection in _endPoint.Connections)
|
foreach (var connection in _endPoint.Connections)
|
||||||
{
|
{
|
||||||
// TODO: separate serialization from writing to stream
|
// TODO: separate serialization from writing to stream
|
||||||
var formatter = _endPoint.GetFormatter<InvocationDescriptor>((string)connection.Metadata["formatType"]);
|
var formatter = _endPoint._serviceProvider.GetRequiredService<SocketFormatters>()
|
||||||
|
.GetFormatter<InvocationDescriptor>((string)connection.Metadata["formatType"]);
|
||||||
|
|
||||||
tasks.Add(formatter.WriteAsync(message, connection.Channel.GetStream()));
|
tasks.Add(formatter.WriteAsync(message, connection.Channel.GetStream()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,18 +36,13 @@ namespace SocketsSample
|
||||||
RegisterRPCEndPoint(typeof(Echo));
|
RegisterRPCEndPoint(typeof(Echo));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IFormatter<T> GetFormatter<T>(string format)
|
|
||||||
{
|
|
||||||
return _serviceProvider
|
|
||||||
.GetRequiredService<SocketFormatters>().GetEndPointFormatters(GetType())
|
|
||||||
.GetFormatter<T>(format);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task OnConnected(Connection connection)
|
public override async Task OnConnected(Connection connection)
|
||||||
{
|
{
|
||||||
// TODO: Dispatch from the caller
|
// TODO: Dispatch from the caller
|
||||||
await Task.Yield();
|
await Task.Yield();
|
||||||
var formatter = GetFormatter<InvocationDescriptor>((string)connection.Metadata["formatType"]);
|
|
||||||
|
var formatter = _serviceProvider.GetRequiredService<SocketFormatters>()
|
||||||
|
.GetFormatter<InvocationDescriptor>((string)connection.Metadata["formatType"]);
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
|
@ -98,7 +93,8 @@ namespace SocketsSample
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultFormatter = GetFormatter<InvocationResultDescriptor>((string)connection.Metadata["formatType"]);
|
var resultFormatter = _serviceProvider.GetRequiredService<SocketFormatters>().
|
||||||
|
GetFormatter<InvocationResultDescriptor>((string)connection.Metadata["formatType"]);
|
||||||
await resultFormatter.WriteAsync(result, connection.Channel.GetStream());
|
await resultFormatter.WriteAsync(result, connection.Channel.GetStream());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Sockets;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace SocketsSample
|
||||||
|
{
|
||||||
|
public static class FormatterExtensions
|
||||||
|
{
|
||||||
|
public static IApplicationBuilder UseFormatters(this IApplicationBuilder app, Action<FormatterBuilder> registerFormatters)
|
||||||
|
{
|
||||||
|
var formatters = app.ApplicationServices.GetRequiredService<SocketFormatters>();
|
||||||
|
registerFormatters(new FormatterBuilder(formatters));
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FormatterBuilder
|
||||||
|
{
|
||||||
|
private SocketFormatters _socketFormatters;
|
||||||
|
|
||||||
|
public FormatterBuilder(SocketFormatters socketFormatters)
|
||||||
|
{
|
||||||
|
_socketFormatters = socketFormatters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MapFormatter<T, TFormatterType>(string format)
|
||||||
|
where TFormatterType : IFormatter<T>
|
||||||
|
{
|
||||||
|
_socketFormatters.RegisterFormatter<T, TFormatterType>(format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SocketsSample
|
||||||
|
{
|
||||||
|
// TODO: Is this name too generic?
|
||||||
|
public interface IFormatter<T>
|
||||||
|
{
|
||||||
|
Task<T> ReadAsync(Stream stream);
|
||||||
|
Task WriteAsync(T value, Stream stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Sockets;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace SocketsSample
|
||||||
|
{
|
||||||
|
public class SocketFormatters
|
||||||
|
{
|
||||||
|
private IServiceProvider _serviceProvider;
|
||||||
|
private Dictionary<string, Dictionary<Type, Type>> _formatters = new Dictionary<string, Dictionary<Type, Type>>();
|
||||||
|
|
||||||
|
public SocketFormatters(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFormatter<T, TFormatterType>(string format)
|
||||||
|
where TFormatterType : IFormatter<T>
|
||||||
|
{
|
||||||
|
Dictionary<Type, Type> formatFormatters;
|
||||||
|
if (!_formatters.TryGetValue(format, out formatFormatters))
|
||||||
|
{
|
||||||
|
formatFormatters = _formatters[format] = new Dictionary<Type, Type>();
|
||||||
|
}
|
||||||
|
|
||||||
|
formatFormatters[typeof(T)] = typeof(TFormatterType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IFormatter<T> GetFormatter<T>(string format)
|
||||||
|
{
|
||||||
|
Dictionary<Type, Type> formatters;
|
||||||
|
Type targetFormatterType;
|
||||||
|
|
||||||
|
if (_formatters.TryGetValue(format, out formatters) && formatters.TryGetValue(typeof(T), out targetFormatterType))
|
||||||
|
{
|
||||||
|
return (IFormatter<T>)_serviceProvider.GetRequiredService(targetFormatterType);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"No formatter register for format '{format}' and type '{typeof(T).GetType().FullName}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -39,17 +39,20 @@ namespace SocketsSample
|
||||||
app.UseDeveloperExceptionPage();
|
app.UseDeveloperExceptionPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseSockets(endpoints =>
|
|
||||||
{
|
|
||||||
endpoints.Configure<HubEndpoint>()
|
|
||||||
.MapRoute("/hubs")
|
|
||||||
.MapFormatter<InvocationDescriptor, InvocationDescriptorLineFormatter>("line")
|
|
||||||
.MapFormatter<InvocationResultDescriptor, InvocationResultDescriptorLineFormatter>("line")
|
|
||||||
.MapFormatter<InvocationDescriptor, RpcJSonFormatter<InvocationDescriptor>>("json")
|
|
||||||
.MapFormatter<InvocationResultDescriptor, RpcJSonFormatter<InvocationResultDescriptor>>("json");
|
|
||||||
|
|
||||||
endpoints.Configure<ChatEndPoint>().MapRoute("/chat");
|
app.UseSockets(routes =>
|
||||||
endpoints.Configure<RpcEndpoint>().MapRoute("/jsonrpc");
|
{
|
||||||
|
routes.MapSocketEndpoint<HubEndpoint>("/hubs");
|
||||||
|
routes.MapSocketEndpoint<ChatEndPoint>("/chat");
|
||||||
|
routes.MapSocketEndpoint<RpcEndpoint>("/jsonrpc");
|
||||||
|
});
|
||||||
|
|
||||||
|
app.UseFormatters(formatters=>
|
||||||
|
{
|
||||||
|
formatters.MapFormatter<InvocationDescriptor, InvocationDescriptorLineFormatter>("line");
|
||||||
|
formatters.MapFormatter<InvocationResultDescriptor, InvocationResultDescriptorLineFormatter>("line");
|
||||||
|
formatters.MapFormatter<InvocationDescriptor, RpcJSonFormatter<InvocationDescriptor>>("json");
|
||||||
|
formatters.MapFormatter<InvocationResultDescriptor, RpcJSonFormatter<InvocationResultDescriptor>>("json");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,13 +81,13 @@
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
let connectButton = document.getElementById('connect');
|
let connectButton = document.getElementById('connect');
|
||||||
connectButton.addEventListener('click', () => {
|
connectButton.addEventListener('click', () => {
|
||||||
run(document.getElementById('format').value, document.getElementById('formatType').value);
|
run(document.getElementById('formatType').value);
|
||||||
connectButton.disabled = true;
|
connectButton.disabled = true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function run(format, formatType) {
|
function run(formatType) {
|
||||||
var conn = new hubConnection(`ws://${document.location.host}/hubs/ws?format=${format}&formatType=${formatType}`);
|
var conn = new hubConnection(`ws://${document.location.host}/hubs/ws?formatType=${formatType}`);
|
||||||
|
|
||||||
conn.on('Send', function (message) {
|
conn.on('Send', function (message) {
|
||||||
var child = document.createElement('li');
|
var child = document.createElement('li');
|
||||||
|
|
@ -114,11 +114,6 @@
|
||||||
<body>
|
<body>
|
||||||
<h1>WebSockets</h1>
|
<h1>WebSockets</h1>
|
||||||
<div>
|
<div>
|
||||||
<select id="format">
|
|
||||||
<option value="text">text</option>
|
|
||||||
<option value="binary">binary</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="formatType">
|
<select id="formatType">
|
||||||
<option value="json">json</option>
|
<option value="json">json</option>
|
||||||
<option value="line">line</option>
|
<option value="line">line</option>
|
||||||
|
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.AspNetCore.Sockets;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
public class EndPointFormatters
|
|
||||||
{
|
|
||||||
private IServiceProvider _serviceProvider;
|
|
||||||
private Dictionary<string, Dictionary<Type, Type>> _formatters = new Dictionary<string, Dictionary<Type, Type>>();
|
|
||||||
|
|
||||||
public EndPointFormatters(IServiceProvider serviceProvider)
|
|
||||||
{
|
|
||||||
_serviceProvider = serviceProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterFormatter<T, TFormatterType>(string format)
|
|
||||||
where TFormatterType : IFormatter<T>
|
|
||||||
{
|
|
||||||
Dictionary<Type, Type> formatFormatters;
|
|
||||||
if (!_formatters.TryGetValue(format, out formatFormatters))
|
|
||||||
{
|
|
||||||
formatFormatters = _formatters[format] = new Dictionary<Type, Type>();
|
|
||||||
}
|
|
||||||
|
|
||||||
formatFormatters[typeof(T)] = typeof(TFormatterType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IFormatter<T> GetFormatter<T>(string format)
|
|
||||||
{
|
|
||||||
Dictionary<Type, Type> formatters;
|
|
||||||
Type targetFormatterType;
|
|
||||||
|
|
||||||
if (_formatters.TryGetValue(format, out formatters) && formatters.TryGetValue(typeof(T), out targetFormatterType))
|
|
||||||
{
|
|
||||||
return (IFormatter<T>)_serviceProvider.GetRequiredService(targetFormatterType);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InvalidOperationException($"No formatter register for format '{format}' and type '{typeof(T).GetType().FullName}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,20 +3,19 @@ using Channels;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Sockets;
|
using Microsoft.AspNetCore.Sockets;
|
||||||
using Microsoft.AspNetCore.Sockets.Routing;
|
using Microsoft.AspNetCore.Sockets.Routing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Builder
|
namespace Microsoft.AspNetCore.Builder
|
||||||
{
|
{
|
||||||
public static class HttpDispatcherAppBuilderExtensions
|
public static class HttpDispatcherAppBuilderExtensions
|
||||||
{
|
{
|
||||||
public static IApplicationBuilder UseSockets(this IApplicationBuilder app, Action<EndPointBuilder> callback)
|
public static IApplicationBuilder UseSockets(this IApplicationBuilder app, Action<SocketRouteBuilder> callback)
|
||||||
{
|
{
|
||||||
var manager = new ConnectionManager();
|
var manager = new ConnectionManager();
|
||||||
var factory = new ChannelFactory();
|
var factory = new ChannelFactory();
|
||||||
var dispatcher = new HttpConnectionDispatcher(manager, factory);
|
var dispatcher = new HttpConnectionDispatcher(manager, factory);
|
||||||
var routes = new RouteBuilder(app);
|
var routes = new RouteBuilder(app);
|
||||||
|
|
||||||
callback(new EndPointBuilder(routes, dispatcher, app.ApplicationServices));
|
callback(new SocketRouteBuilder(routes, dispatcher));
|
||||||
|
|
||||||
// TODO: Use new low allocating websocket API
|
// TODO: Use new low allocating websocket API
|
||||||
app.UseWebSockets();
|
app.UseWebSockets();
|
||||||
|
|
@ -24,53 +23,6 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class EndPointBuilder
|
|
||||||
{
|
|
||||||
private readonly HttpConnectionDispatcher _dispatcher;
|
|
||||||
private readonly RouteBuilder _routes;
|
|
||||||
private readonly IServiceProvider _serviceProvider;
|
|
||||||
|
|
||||||
public EndPointBuilder(RouteBuilder routes, HttpConnectionDispatcher dispatcher, IServiceProvider serviceProvider)
|
|
||||||
{
|
|
||||||
_routes = routes;
|
|
||||||
_dispatcher = dispatcher;
|
|
||||||
_serviceProvider = serviceProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EndPointConfiguration<TEndPoint> Configure<TEndPoint>() where TEndPoint : EndPoint
|
|
||||||
{
|
|
||||||
var socketFormatters = _serviceProvider.GetRequiredService<SocketFormatters>();
|
|
||||||
return new EndPointConfiguration<TEndPoint>(_routes, _dispatcher, socketFormatters.GetEndPointFormatters<TEndPoint>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class EndPointConfiguration<TEndPoint> where TEndPoint : EndPoint
|
|
||||||
{
|
|
||||||
private readonly HttpConnectionDispatcher _dispatcher;
|
|
||||||
private readonly RouteBuilder _routes;
|
|
||||||
private readonly EndPointFormatters _formatters;
|
|
||||||
|
|
||||||
public EndPointConfiguration(RouteBuilder routes, HttpConnectionDispatcher dispatcher, EndPointFormatters formatters)
|
|
||||||
{
|
|
||||||
_routes = routes;
|
|
||||||
_dispatcher = dispatcher;
|
|
||||||
_formatters = formatters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EndPointConfiguration<TEndPoint> MapRoute(string path)
|
|
||||||
{
|
|
||||||
_routes.AddPrefixRoute(path, new RouteHandler(c => _dispatcher.Execute<TEndPoint>(path, c)));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EndPointConfiguration<TEndPoint> MapFormatter<T, TFormatterType>(string format)
|
|
||||||
where TFormatterType : IFormatter<T>
|
|
||||||
{
|
|
||||||
_formatters.RegisterFormatter<T, TFormatterType>(format);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SocketRouteBuilder
|
public class SocketRouteBuilder
|
||||||
{
|
{
|
||||||
private readonly HttpConnectionDispatcher _dispatcher;
|
private readonly HttpConnectionDispatcher _dispatcher;
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Sockets
|
|
||||||
{
|
|
||||||
public class SocketFormatters
|
|
||||||
{
|
|
||||||
private Dictionary<Type, EndPointFormatters> _formatters = new Dictionary<Type, EndPointFormatters>();
|
|
||||||
|
|
||||||
private IServiceProvider _serviceProvider;
|
|
||||||
|
|
||||||
public SocketFormatters(IServiceProvider serviceProvider)
|
|
||||||
{
|
|
||||||
_serviceProvider = serviceProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EndPointFormatters GetEndPointFormatters<TEndPoint>()
|
|
||||||
{
|
|
||||||
return GetEndPointFormatters(typeof(TEndPoint));
|
|
||||||
}
|
|
||||||
|
|
||||||
public EndPointFormatters GetEndPointFormatters(Type endPointType)
|
|
||||||
{
|
|
||||||
EndPointFormatters endPointFormatters;
|
|
||||||
if (_formatters.TryGetValue(endPointType, out endPointFormatters))
|
|
||||||
{
|
|
||||||
return endPointFormatters;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _formatters[endPointType] = new EndPointFormatters(_serviceProvider);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue