WIP
This commit is contained in:
parent
325c909dff
commit
1694f2b791
|
|
@ -39,6 +39,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "WebSocketsTestApp", "test\W
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ChatSample", "samples\ChatSample\ChatSample.xproj", "{300979F6-A02E-407A-B8DF-F6200806C18D}"
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ChatSample", "samples\ChatSample\ChatSample.xproj", "{300979F6-A02E-407A-B8DF-F6200806C18D}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SocialWeather", "samples\PersisitentConnection\SocialWeather.xproj", "{8D789F94-CB74-45FD-ACE7-92AF6E55042E}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{300979F6-A02E-407A-B8DF-F6200806C18D}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -110,5 +116,6 @@ Global
|
||||||
{8CBC1C71-AF0B-44E2-AEE9-D8024C07634D} = {6A35B453-52EC-48AF-89CA-D4A69800F131}
|
{8CBC1C71-AF0B-44E2-AEE9-D8024C07634D} = {6A35B453-52EC-48AF-89CA-D4A69800F131}
|
||||||
{58E771EC-8454-4558-B61A-C9D049065911} = {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}
|
{300979F6-A02E-407A-B8DF-F6200806C18D} = {C4BC9889-B49F-41B6-806B-F84941B2549B}
|
||||||
|
{8D789F94-CB74-45FD-ACE7-92AF6E55042E} = {C4BC9889-B49F-41B6-806B-F84941B2549B}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
||||||
|
|
@ -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<T>
|
||||||
|
{
|
||||||
|
Task<T> ReadAsync(Stream stream);
|
||||||
|
Task WriteAsync(T value, Stream stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace PersisitentConnection
|
||||||
|
{
|
||||||
|
public class JsonStreamFormatter<T> : IStreamFormatter<T>
|
||||||
|
{
|
||||||
|
private JsonSerializer _serializer = new JsonSerializer();
|
||||||
|
|
||||||
|
public async Task<T> ReadAsync(Stream stream)
|
||||||
|
{
|
||||||
|
var reader = new JsonTextReader(new StreamReader(stream));
|
||||||
|
// REVIEW: Task.Run()
|
||||||
|
return await Task.Run(() => _serializer.Deserialize<T>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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>(T data)
|
||||||
|
{
|
||||||
|
foreach (var connection in _connectionList)
|
||||||
|
{
|
||||||
|
// var formatType = connection.Metadata.Get<string>("formatType");
|
||||||
|
var formatter = new JsonStreamFormatter<T>();
|
||||||
|
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<string>());
|
||||||
|
lock (groups)
|
||||||
|
{
|
||||||
|
groups.Add(groupName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveGroupAsync(Connection connection, string groupName)
|
||||||
|
{
|
||||||
|
var groups = connection.Metadata.Get<HashSet<string>>("groups");
|
||||||
|
if (groups != null)
|
||||||
|
{
|
||||||
|
lock (groups)
|
||||||
|
{
|
||||||
|
groups.Remove(groupName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<Startup>()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
host.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace PersisitentConnection
|
||||||
|
{
|
||||||
|
public class ProtobufWeatherStreamFormatter : IStreamFormatter<Weather>
|
||||||
|
{
|
||||||
|
public Task<Weather> ReadAsync(Stream stream)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task WriteAsync(Weather value, Stream stream)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||||
|
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<ProjectGuid>8d789f94-cb74-45fd-ace7-92af6e55042e</ProjectGuid>
|
||||||
|
<RootNamespace>PersisitentConnection</RootNamespace>
|
||||||
|
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
|
||||||
|
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
|
||||||
|
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<DnxInvisibleContent Include="bower.json" />
|
||||||
|
<DnxInvisibleContent Include=".bowerrc" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(VSToolsPath)\DotNet.Web\Microsoft.DotNet.Web.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
</Project>
|
||||||
|
|
@ -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<SocialWeatherEndPoint> _logger;
|
||||||
|
private object _lockObj = new object();
|
||||||
|
private WeatherReport _lastWeatherReport;
|
||||||
|
|
||||||
|
public SocialWeatherEndPoint(ILogger<SocialWeatherEndPoint> 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<string>("formatType");
|
||||||
|
//var formatterRegistry = _serviceProvider.GetRequiredService<FormatterRegistry>();
|
||||||
|
//var formatter = formatterRegistry.GetFormatter(formatType);
|
||||||
|
var formatter = new JsonStreamFormatter<WeatherReport>();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var weatherReport = await formatter.ReadAsync(stream);
|
||||||
|
lock(_lockObj)
|
||||||
|
{
|
||||||
|
_lastWeatherReport = weatherReport;
|
||||||
|
}
|
||||||
|
await _lifetimeManager.SendToAllAsync(weatherReport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<SocialWeatherEndPoint>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<SocialWeatherEndPoint>("/weather"); });
|
||||||
|
app.UseStaticFiles();
|
||||||
|
|
||||||
|
app.Run(async (context) =>
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync("Hello World!");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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%" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380
|
||||||
|
-->
|
||||||
|
|
||||||
|
<system.webServer>
|
||||||
|
<handlers>
|
||||||
|
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
|
||||||
|
</handlers>
|
||||||
|
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/>
|
||||||
|
</system.webServer>
|
||||||
|
</configuration>
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Social weather</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
</div>
|
||||||
|
<form id="reportWeather" action="#">
|
||||||
|
<input type="number" id="temperature" />
|
||||||
|
<select id="weather">
|
||||||
|
<option value="Sunny">Sunny</option>
|
||||||
|
<option value="MostlySunny">Mostly Sunny</option>
|
||||||
|
<option value="PartlySunny">Partly Sunny</option>
|
||||||
|
<option value="PartlyCloudy">Partly Cloudy</option>
|
||||||
|
<option value="MostlyCloudy">Mostly Cloudy</option>
|
||||||
|
<option value="Cloudy">Cloudy</option>
|
||||||
|
</select>
|
||||||
|
<input type="submit" value="Send report" />
|
||||||
|
</form>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
|
let connectUrl = `ws://${document.location.host}/weather/ws?formatType=json`;
|
||||||
|
var webSocket = new WebSocket(connectUrl);
|
||||||
|
webSocket.onopen = event => {
|
||||||
|
console.log(`WebSocket connected to ${connectUrl}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
webSocket.onerror = event => {
|
||||||
|
};
|
||||||
|
|
||||||
|
webSocket.onmessage = message => {
|
||||||
|
}
|
||||||
|
|
||||||
|
webSocket.onclose = event => {
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('reportWeather').addEventListener('submit', event => {
|
||||||
|
|
||||||
|
webSocket.send(
|
||||||
|
JSON.stringify({
|
||||||
|
Temperature: '49',
|
||||||
|
Weather: 'Cloudy',
|
||||||
|
ReportTime: Date.now()
|
||||||
|
}));
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue