Decoupling formatters from endpoints

Moving formatters out from Sockets
This commit is contained in:
moozzyk 2016-10-06 10:43:12 -07:00
parent 8b905907fe
commit fb387ed03d
11 changed files with 118 additions and 152 deletions

View File

@ -12,7 +12,6 @@ namespace SocketsSample
{
public ChatEndPoint()
{
Console.Write(0);
}
public override async Task OnConnected(Connection connection)

View File

@ -4,6 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Channels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using SocketsSample.Hubs;
@ -78,7 +79,9 @@ namespace SocketsSample
foreach (var connection in _endPoint.Connections)
{
// 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()));
}

View File

@ -36,18 +36,13 @@ namespace SocketsSample
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)
{
// TODO: Dispatch from the caller
await Task.Yield();
var formatter = GetFormatter<InvocationDescriptor>((string)connection.Metadata["formatType"]);
var formatter = _serviceProvider.GetRequiredService<SocketFormatters>()
.GetFormatter<InvocationDescriptor>((string)connection.Metadata["formatType"]);
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());
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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}'");
}
}
}

View File

@ -39,17 +39,20 @@ namespace SocketsSample
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");
endpoints.Configure<RpcEndpoint>().MapRoute("/jsonrpc");
app.UseSockets(routes =>
{
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");
});
}
}

View File

@ -81,13 +81,13 @@
document.addEventListener('DOMContentLoaded', () => {
let connectButton = document.getElementById('connect');
connectButton.addEventListener('click', () => {
run(document.getElementById('format').value, document.getElementById('formatType').value);
run(document.getElementById('formatType').value);
connectButton.disabled = true;
});
});
function run(format, formatType) {
var conn = new hubConnection(`ws://${document.location.host}/hubs/ws?format=${format}&formatType=${formatType}`);
function run(formatType) {
var conn = new hubConnection(`ws://${document.location.host}/hubs/ws?formatType=${formatType}`);
conn.on('Send', function (message) {
var child = document.createElement('li');
@ -114,11 +114,6 @@
<body>
<h1>WebSockets</h1>
<div>
<select id="format">
<option value="text">text</option>
<option value="binary">binary</option>
</select>
<select id="formatType">
<option value="json">json</option>
<option value="line">line</option>

View File

@ -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}'");
}
}

View File

@ -3,20 +3,19 @@ using Channels;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Sockets;
using Microsoft.AspNetCore.Sockets.Routing;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Builder
{
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 factory = new ChannelFactory();
var dispatcher = new HttpConnectionDispatcher(manager, factory);
var routes = new RouteBuilder(app);
callback(new EndPointBuilder(routes, dispatcher, app.ApplicationServices));
callback(new SocketRouteBuilder(routes, dispatcher));
// TODO: Use new low allocating websocket API
app.UseWebSockets();
@ -24,53 +23,6 @@ namespace Microsoft.AspNetCore.Builder
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
{
private readonly HttpConnectionDispatcher _dispatcher;

View File

@ -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);
}
}
}