Working with ASP.NET MVC

This commit is contained in:
Frank A. Krueger 2017-11-09 21:00:15 -08:00
parent 5690adac90
commit ebf1d60bde
7 changed files with 167 additions and 27 deletions

View File

@ -6,20 +6,21 @@ using Microsoft.AspNetCore.Mvc;
namespace Ooui.AspNetCore namespace Ooui.AspNetCore
{ {
public class ElementResult : ActionResult public class ElementResult : ActionResult
{ {
readonly Element element;
public ElementResult (Element element) public ElementResult (Element element)
{ {
this.element = element;
} }
public override async Task ExecuteResultAsync (ActionContext context) public override async Task ExecuteResultAsync (ActionContext context)
{ {
var path = context.HttpContext.Request.Path;
var response = context.HttpContext.Response; var response = context.HttpContext.Response;
response.StatusCode = 200; response.StatusCode = 200;
response.ContentType = "text/html"; response.ContentType = "text/html; charset=utf-8";
var html = Encoding.UTF8.GetBytes (UI.RenderTemplate (path)); var sessionId = WebSocketHandler.BeginSession (context.HttpContext, element);
var html = Encoding.UTF8.GetBytes (UI.RenderTemplate (WebSocketHandler.WebSocketPath + "?id=" + sessionId));
response.ContentLength = html.Length; response.ContentLength = html.Length;
using (var s = response.Body) { using (var s = response.Body) {
await s.WriteAsync (html, 0, html.Length).ConfigureAwait (false); await s.WriteAsync (html, 0, html.Length).ConfigureAwait (false);

View File

@ -6,6 +6,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ooui\Ooui.csproj" /> <ProjectReference Include="..\Ooui\Ooui.csproj" />

View File

@ -0,0 +1,55 @@
using System;
using Ooui.AspNetCore;
namespace Microsoft.AspNetCore.Builder
{
public static class OouiMiddlewareExtensions
{
public static void UseOoui (this IApplicationBuilder app, string jsPath = "/ooui.js", string webSocketPath = "/ooui.ws", TimeSpan? sessionTimeout = null)
{
if (string.IsNullOrWhiteSpace (webSocketPath))
throw new ArgumentException ("A path to be used for Ooui web sockets must be specified", nameof (webSocketPath));
if (string.IsNullOrWhiteSpace (jsPath))
throw new ArgumentException ("A path to be used for Ooui JavaScript must be specified", nameof (jsPath));
WebSocketHandler.WebSocketPath = webSocketPath;
if (sessionTimeout.HasValue) {
WebSocketHandler.SessionTimeout = sessionTimeout.Value;
}
var webSocketOptions = new WebSocketOptions () {
KeepAliveInterval = WebSocketHandler.SessionTimeout,
ReceiveBufferSize = 4 * 1024
};
app.UseWebSockets (webSocketOptions);
app.Use (async (context, next) =>
{
if (context.Request.Path == jsPath) {
var response = context.Response;
var clientJsBytes = Ooui.UI.ClientJsBytes;
response.StatusCode = 200;
response.ContentLength = clientJsBytes.Length;
response.ContentType = "application/javascript; charset=utf-8";
response.Headers.Add ("Cache-Control", "public, max-age=3600");
using (var s = response.Body) {
await s.WriteAsync (clientJsBytes, 0, clientJsBytes.Length).ConfigureAwait (false);
}
}
else if (context.Request.Path == WebSocketHandler.WebSocketPath) {
if (context.WebSockets.IsWebSocketRequest) {
await WebSocketHandler.HandleWebSocketRequestAsync (context).ConfigureAwait (false);
}
else {
context.Response.StatusCode = 400;
}
}
else {
await next ().ConfigureAwait (false);
}
});
}
}
}

View File

@ -0,0 +1,82 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace Ooui.AspNetCore
{
public static class WebSocketHandler
{
public static string WebSocketPath { get; set; } = "/ooui.ws";
public static TimeSpan SessionTimeout { get; set; } = TimeSpan.FromMinutes (5);
static readonly ConcurrentDictionary<string, PendingSession> pendingSessions =
new ConcurrentDictionary<string, PendingSession> ();
public static string BeginSession (HttpContext context, Element element)
{
var id = Guid.NewGuid ().ToString ("N");
var s = new PendingSession {
Element = element,
RequestTimeUtc = DateTime.UtcNow,
};
if (!pendingSessions.TryAdd (id, s)) {
throw new Exception ("Failed to schedule pending session");
}
return id;
}
public static async Task HandleWebSocketRequestAsync (HttpContext context)
{
//
// Make sure we get a good ID
//
if (!context.Request.Query.TryGetValue ("id", out var idValues)) {
context.Response.StatusCode = StatusCodes.Status400BadRequest;
return;
}
var id = idValues.FirstOrDefault ();
if (id == null || id.Length != 32) {
context.Response.StatusCode = StatusCodes.Status400BadRequest;
return;
}
//
// Find the pending session
//
if (!pendingSessions.TryRemove (id, out var pendingSession)) {
context.Response.StatusCode = StatusCodes.Status400BadRequest;
return;
}
//
// Reject the session if it's old
//
if ((DateTime.UtcNow - pendingSession.RequestTimeUtc) > SessionTimeout) {
context.Response.StatusCode = StatusCodes.Status400BadRequest;
return;
}
//
// OK, Run
//
var token = CancellationToken.None;
var webSocket = await context.WebSockets.AcceptWebSocketAsync ("ooui");
var session = new Ooui.UI.Session (webSocket, pendingSession.Element, token);
await session.RunAsync ().ConfigureAwait (false);
}
class PendingSession
{
public Element Element;
public DateTime RequestTimeUtc;
}
}
}

View File

@ -27,17 +27,19 @@ namespace Ooui
static readonly byte[] clientJsBytes; static readonly byte[] clientJsBytes;
public static byte[] ClientJsBytes => clientJsBytes;
public static string Template { get; set; } = $@"<!DOCTYPE html> public static string Template { get; set; } = $@"<!DOCTYPE html>
<html> <html>
<head> <head>
<title>@ElementPath</title> <title>@Title</title>
<meta name=""viewport"" content=""width=device-width, initial-scale=1"" /> <meta name=""viewport"" content=""width=device-width, initial-scale=1"" />
<style>@Styles</style> <style>@Styles</style>
</head> </head>
<body> <body>
<div id=""ooui-body""></div> <div id=""ooui-body""></div>
<script src=""/ooui.js""></script> <script src=""/ooui.js""></script>
<script>ooui(""@ElementPath"");</script> <script>ooui(""@WebSocketPath"");</script>
</body> </body>
</html>"; </html>";
@ -297,9 +299,9 @@ namespace Ooui
} }
} }
public static string RenderTemplate (string elementPath) public static string RenderTemplate (string webSocketPath)
{ {
return Template.Replace ("@ElementPath", elementPath).Replace ("@Styles", rules.ToString ()); return Template.Replace ("@WebSocketPath", webSocketPath).Replace ("@Styles", rules.ToString ());
} }
class DataHandler : RequestHandler class DataHandler : RequestHandler
@ -454,7 +456,7 @@ namespace Ooui
Console.ResetColor (); Console.ResetColor ();
} }
class Session public class Session
{ {
readonly WebSocket webSocket; readonly WebSocket webSocket;
readonly Element element; readonly Element element;

View File

@ -15,8 +15,8 @@ namespace AspNetCoreMvc.Controllers
{ {
public IActionResult Index() public IActionResult Index()
{ {
var button = new Button (); var element = new Label { Text = "Hello Oooooui from Controller" };
return new ElementResult (button); return new ElementResult (element);
} }
public IActionResult About() public IActionResult About()

View File

@ -11,7 +11,7 @@ namespace AspNetCoreMvc
{ {
public class Startup public class Startup
{ {
public Startup(IConfiguration configuration) public Startup (IConfiguration configuration)
{ {
Configuration = configuration; Configuration = configuration;
} }
@ -19,29 +19,28 @@ namespace AspNetCoreMvc
public IConfiguration Configuration { get; } public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container. // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) public void ConfigureServices (IServiceCollection services)
{ {
services.AddMvc(); services.AddMvc ();
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env) public void Configure (IApplicationBuilder app, IHostingEnvironment env)
{ {
if (env.IsDevelopment()) if (env.IsDevelopment ()) {
{ app.UseDeveloperExceptionPage ();
app.UseDeveloperExceptionPage();
} }
else else {
{ app.UseExceptionHandler ("/Home/Error");
app.UseExceptionHandler("/Home/Error");
} }
app.UseStaticFiles(); app.UseStaticFiles ();
app.UseMvc(routes => app.UseOoui ();
{
routes.MapRoute( app.UseMvc (routes => {
routes.MapRoute (
name: "default", name: "default",
template: "{controller=Home}/{action=Index}/{id?}"); template: "{controller=Home}/{action=Index}/{id?}");
}); });