diff --git a/SignalR.sln b/SignalR.sln index 7881e3b142..bf4087e65c 100644 --- a/SignalR.sln +++ b/SignalR.sln @@ -39,6 +39,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "WebSocketsTestApp", "test\W EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ChatSample", "samples\ChatSample\ChatSample.xproj", "{300979F6-A02E-407A-B8DF-F6200806C18D}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SocialWeather", "samples\PersisitentConnection\SocialWeather.xproj", "{8D789F94-CB74-45FD-ACE7-92AF6E55042E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -93,6 +95,10 @@ Global {300979F6-A02E-407A-B8DF-F6200806C18D}.Debug|Any CPU.Build.0 = Debug|Any CPU {300979F6-A02E-407A-B8DF-F6200806C18D}.Release|Any CPU.ActiveCfg = Release|Any CPU {300979F6-A02E-407A-B8DF-F6200806C18D}.Release|Any CPU.Build.0 = Release|Any CPU + {8D789F94-CB74-45FD-ACE7-92AF6E55042E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D789F94-CB74-45FD-ACE7-92AF6E55042E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D789F94-CB74-45FD-ACE7-92AF6E55042E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D789F94-CB74-45FD-ACE7-92AF6E55042E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -110,5 +116,6 @@ Global {8CBC1C71-AF0B-44E2-AEE9-D8024C07634D} = {6A35B453-52EC-48AF-89CA-D4A69800F131} {58E771EC-8454-4558-B61A-C9D049065911} = {6A35B453-52EC-48AF-89CA-D4A69800F131} {300979F6-A02E-407A-B8DF-F6200806C18D} = {C4BC9889-B49F-41B6-806B-F84941B2549B} + {8D789F94-CB74-45FD-ACE7-92AF6E55042E} = {C4BC9889-B49F-41B6-806B-F84941B2549B} EndGlobalSection EndGlobal diff --git a/samples/PersisitentConnection/IStreamFormatter.cs b/samples/PersisitentConnection/IStreamFormatter.cs new file mode 100644 index 0000000000..bdabddf04a --- /dev/null +++ b/samples/PersisitentConnection/IStreamFormatter.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace PersisitentConnection +{ + public interface IStreamFormatter + { + Task ReadAsync(Stream stream); + Task WriteAsync(T value, Stream stream); + } +} diff --git a/samples/PersisitentConnection/JSonStreamFormatter.cs b/samples/PersisitentConnection/JSonStreamFormatter.cs new file mode 100644 index 0000000000..d517b97995 --- /dev/null +++ b/samples/PersisitentConnection/JSonStreamFormatter.cs @@ -0,0 +1,26 @@ +using System.IO; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace PersisitentConnection +{ + public class JsonStreamFormatter : IStreamFormatter + { + private JsonSerializer _serializer = new JsonSerializer(); + + public async Task ReadAsync(Stream stream) + { + var reader = new JsonTextReader(new StreamReader(stream)); + // REVIEW: Task.Run() + return await Task.Run(() => _serializer.Deserialize(reader)); + } + + public Task WriteAsync(T value, Stream stream) + { + var writer = new JsonTextWriter(new StreamWriter(stream)); + _serializer.Serialize(writer, value); + writer.Flush(); + return Task.FromResult(0); + } + } +} diff --git a/samples/PersisitentConnection/PersistentConnectionLifeTimeManager.cs b/samples/PersisitentConnection/PersistentConnectionLifeTimeManager.cs new file mode 100644 index 0000000000..17514c5b2f --- /dev/null +++ b/samples/PersisitentConnection/PersistentConnectionLifeTimeManager.cs @@ -0,0 +1,70 @@ + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Channels; +using Microsoft.AspNetCore.Sockets; + +namespace PersisitentConnection +{ + public class PersistentConnectionLifeTimeManager + { + private readonly ConnectionList _connectionList = new ConnectionList(); + + public void OnConnectedAsync(Connection connection) + { + _connectionList.Add(connection); + } + + public void OnDisconnectedAsync(Connection connection) + { + _connectionList.Remove(connection); + } + + public async Task SendToAllAsync(T data) + { + foreach (var connection in _connectionList) + { +// var formatType = connection.Metadata.Get("formatType"); + var formatter = new JsonStreamFormatter(); + await formatter.WriteAsync(data, connection.Channel.GetStream()); + } + } + + public Task InvokeConnectionAsync(string connectionId, object data) + { + throw new NotImplementedException(); + } + + public Task InvokeGroupAsync(string groupName, object data) + { + throw new NotImplementedException(); + } + + public Task InvokeUserAsync(string userId, object data) + { + throw new NotImplementedException(); + } + + public void AddGroupAsync(Connection connection, string groupName) + { + var groups = connection.Metadata.GetOrAdd("groups", _ => new HashSet()); + lock (groups) + { + groups.Add(groupName); + } + } + + public void RemoveGroupAsync(Connection connection, string groupName) + { + var groups = connection.Metadata.Get>("groups"); + if (groups != null) + { + lock (groups) + { + groups.Remove(groupName); + } + } + } + } +} diff --git a/samples/PersisitentConnection/Program.cs b/samples/PersisitentConnection/Program.cs new file mode 100644 index 0000000000..e41cbe962e --- /dev/null +++ b/samples/PersisitentConnection/Program.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; + +namespace PersisitentConnection +{ + public class Program + { + public static void Main(string[] args) + { + var host = new WebHostBuilder() + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseStartup() + .Build(); + + host.Run(); + } + } +} diff --git a/samples/PersisitentConnection/ProtobufWeatherStreamFormatter.cs b/samples/PersisitentConnection/ProtobufWeatherStreamFormatter.cs new file mode 100644 index 0000000000..f9e27be53e --- /dev/null +++ b/samples/PersisitentConnection/ProtobufWeatherStreamFormatter.cs @@ -0,0 +1,19 @@ +using System; +using System.IO; +using System.Threading.Tasks; + +namespace PersisitentConnection +{ + public class ProtobufWeatherStreamFormatter : IStreamFormatter + { + public Task ReadAsync(Stream stream) + { + throw new NotImplementedException(); + } + + public Task WriteAsync(Weather value, Stream stream) + { + throw new NotImplementedException(); + } + } +} diff --git a/samples/PersisitentConnection/SocialWeather.xproj b/samples/PersisitentConnection/SocialWeather.xproj new file mode 100644 index 0000000000..acc78cdcb5 --- /dev/null +++ b/samples/PersisitentConnection/SocialWeather.xproj @@ -0,0 +1,25 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 8d789f94-cb74-45fd-ace7-92af6e55042e + PersisitentConnection + .\obj + .\bin\ + v4.6 + + + + 2.0 + + + + + + + diff --git a/samples/PersisitentConnection/SocialWeatherEndPoint.cs b/samples/PersisitentConnection/SocialWeatherEndPoint.cs new file mode 100644 index 0000000000..43536e5d82 --- /dev/null +++ b/samples/PersisitentConnection/SocialWeatherEndPoint.cs @@ -0,0 +1,46 @@ +using System.Threading.Tasks; +using Channels; +using Microsoft.AspNetCore.Sockets; +using Microsoft.Extensions.Logging; + +namespace PersisitentConnection +{ + public class SocialWeatherEndPoint : EndPoint + { + private readonly PersistentConnectionLifeTimeManager _lifetimeManager = new PersistentConnectionLifeTimeManager(); + private readonly ILogger _logger; + private object _lockObj = new object(); + private WeatherReport _lastWeatherReport; + + public SocialWeatherEndPoint(ILogger logger) + { + _logger = logger; + } + + public async override Task OnConnectedAsync(Connection connection) + { + _lifetimeManager.OnConnectedAsync(connection); + await DispatchMessagesAsync(connection); + _lifetimeManager.OnDisconnectedAsync(connection); + } + + public async Task DispatchMessagesAsync(Connection connection) + { + var stream = connection.Channel.GetStream(); + //var formatType = connection.Metadata.Get("formatType"); + //var formatterRegistry = _serviceProvider.GetRequiredService(); + //var formatter = formatterRegistry.GetFormatter(formatType); + var formatter = new JsonStreamFormatter(); + + while (true) + { + var weatherReport = await formatter.ReadAsync(stream); + lock(_lockObj) + { + _lastWeatherReport = weatherReport; + } + await _lifetimeManager.SendToAllAsync(weatherReport); + } + } + } +} diff --git a/samples/PersisitentConnection/Startup.cs b/samples/PersisitentConnection/Startup.cs new file mode 100644 index 0000000000..d3e9d3f410 --- /dev/null +++ b/samples/PersisitentConnection/Startup.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace PersisitentConnection +{ + public class Startup + { + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + services.AddRouting(); + services.AddSingleton(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseSockets(o => { o.MapEndpoint("/weather"); }); + app.UseStaticFiles(); + + app.Run(async (context) => + { + await context.Response.WriteAsync("Hello World!"); + }); + } + } +} diff --git a/samples/PersisitentConnection/WeatherReport.cs b/samples/PersisitentConnection/WeatherReport.cs new file mode 100644 index 0000000000..be24979446 --- /dev/null +++ b/samples/PersisitentConnection/WeatherReport.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace PersisitentConnection +{ + public enum Weather { Sunny, MostlySunny, PartlySunny, PartlyCloudy, MostlyCloudy, Cloudy } + + public class WeatherReport + { + public int Temperature { get; set; } + + public long ReportTime { get; set; } + + public Weather Weather { get; set; } + } +} diff --git a/samples/PersisitentConnection/project.json b/samples/PersisitentConnection/project.json new file mode 100644 index 0000000000..69eeb08e1a --- /dev/null +++ b/samples/PersisitentConnection/project.json @@ -0,0 +1,48 @@ +{ + "dependencies": { + "Microsoft.NETCore.App": { + "version": "1.1.0-preview1-001100-00", + "type": "platform" + }, + "Microsoft.AspNetCore.Diagnostics": "1.1.0-*", + "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0-*", + "Microsoft.AspNetCore.Server.Kestrel": "1.1.0-*", + "Microsoft.Extensions.Logging.Console": "1.1.0-*", + "Newtonsoft.Json": "9.0.1", + "Microsoft.AspNetCore.Sockets": { + "target": "project" + }, + "Microsoft.AspNetCore.StaticFiles": "1.1.0-*" + }, + + "tools": { + "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" + }, + + "frameworks": { + "netcoreapp1.1": { + } + }, + + "buildOptions": { + "emitEntryPoint": true, + "preserveCompilationContext": true + }, + + "runtimeOptions": { + "configProperties": { + "System.GC.Server": true + } + }, + + "publishOptions": { + "include": [ + "wwwroot", + "web.config" + ] + }, + + "scripts": { + "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] + } +} diff --git a/samples/PersisitentConnection/web.config b/samples/PersisitentConnection/web.config new file mode 100644 index 0000000000..dc0514fca5 --- /dev/null +++ b/samples/PersisitentConnection/web.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/samples/PersisitentConnection/wwwroot/weather.html b/samples/PersisitentConnection/wwwroot/weather.html new file mode 100644 index 0000000000..05128780fe --- /dev/null +++ b/samples/PersisitentConnection/wwwroot/weather.html @@ -0,0 +1,54 @@ + + + + + Social weather + + +
+
+
+ + + +
+ + + \ No newline at end of file