diff --git a/WebServer/AreaBase.cs b/WebServer/AreaBase.cs new file mode 100644 index 0000000..d422fad --- /dev/null +++ b/WebServer/AreaBase.cs @@ -0,0 +1,90 @@ +using Microsoft.Extensions.Hosting; +using RazorLight; +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using WebServer.Models; + +namespace WebServer { + public abstract class AreaBase { // Formally `HttpEndpointHandler` + // Provides razor pages, resources and relevent db tables and entities + public readonly HttpServer Server; + public readonly RazorLightEngine RazorEngine; + public readonly string Hostname; + public readonly string AreaPath; + public readonly AreaBase? ParentArea; + + public AreaBase(HttpServer server, string host) : this(server, host, string.Empty) { } + + public AreaBase(HttpServer server, string host, string areaPath) { + if (server == null) throw new ArgumentNullException(nameof(server)); + if (string.IsNullOrEmpty(host)) throw new ArgumentNullException(nameof(host)); + Server = server; + RazorEngine = server.GetOrCreateRazorEngine(host); + Hostname = host; + AreaPath = areaPath ?? string.Empty; + } + + public AreaBase(AreaBase parentArea, string relativeAreaPath) { // So you can build on top of other areas + ParentArea = parentArea; + Server = parentArea.Server; + RazorEngine = parentArea.RazorEngine; + Hostname = parentArea.Hostname; + AreaPath = $"{parentArea.AreaPath}/{relativeAreaPath.Trim(' ', '/', '\\')}"; + } + + public static Uri BuildUri(HttpListenerRequest request, string path) { + string uriString = $"{(request.IsSecureConnection ? "https" : "http")}://{request.UserHostName}/"; + if (string.IsNullOrEmpty(path)) + return new Uri(uriString); + path = path.Replace('\\', '/'); + if (path[0] == '/') + path = path.Substring(1); + uriString += path; + return new Uri(uriString); + } + + #region Razor Stuff + public async Task GetGenericStatusPageAsync(StatusPageModel model, ExpandoObject? viewBag = null) { + return await Server.GetGenericStatusPageAsync(model, Hostname, viewBag); + } + public async Task GetGenericStatusPageAsync(HttpStatusCode statusCode, ExpandoObject? viewBag = null) + => await GetGenericStatusPageAsync(new StatusPageModel(statusCode), viewBag); + + /// Renders a View with a T model + /// Type of the model + /// The view path relative to the AreaName, keep in mind that prepending a '/' (or '\') will go to root of the top-most area + /// The model supplied to the view + /// A string of the compiled view + public async Task RenderAsync(string viewPath, T model, ExpandoObject viewBag = null) { // Formally Try/GetTemplate + viewPath = viewPath.Trim().Replace('\\', '/'); + string root = viewPath[0] == '/' ? "" : AreaPath; + return await RazorEngine.CompileRenderAsync($"{root}{viewPath}", model, viewBag); + } + #endregion + + #region Event Binds + public Regex GetRegex(string regex) => new Regex(regex, RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant); + + public bool TryAddEventCallback(Regex regex, HttpServer.Callback callback) + => Server.TryAddEventCallback(Hostname, regex, callback); + + public void AddEventCallback(Regex regex, HttpServer.Callback callback) { + if (!TryAddEventCallback(regex, callback)) + throw new Exception($"Error in Area '{AreaPath}'! Failed to bind event callback to `{callback.Method.Name}`"); + } + + public bool TryAddEventCallback(string regexPattern, HttpServer.Callback callback) + => TryAddEventCallback(GetRegex(regexPattern), callback); + + public void AddEventCallback(string regexPattern, HttpServer.Callback callback) { + if (!TryAddEventCallback(regexPattern, callback)) + throw new Exception($"Error in Area '{AreaPath}'! Failed to bind event callback to `{callback.Method.Name}`"); + } + #endregion + } +} diff --git a/WebServer/HttpServer.cs b/WebServer/HttpServer.cs index 0319b67..81f4df2 100644 --- a/WebServer/HttpServer.cs +++ b/WebServer/HttpServer.cs @@ -24,6 +24,7 @@ namespace WebServer { public delegate Task Callback(HttpListenerContext context, CachedResponse? cache); Dictionary> HttpCallbacks = new Dictionary>(); + public List RegisteredAreas = new List(); Dictionary RazorEngines = new Dictionary(); public readonly RazorLightEngine DefaultRazorEngine; /* Instead of three directories (from previous render engine) @@ -357,5 +358,17 @@ namespace WebServer { } return removeCount > 0; } + + public bool TryRegisterArea(Func? areaInitilizer, out T area) where T : AreaBase { + area = areaInitilizer?.Invoke() ?? default; + if (area == null) return false; + RegisteredAreas.Add(area); + return true; + } + + public void RegisterArea(Func? areaInitializer, out T area) where T : AreaBase { + if (!TryRegisterArea(areaInitializer, out area)) + throw new Exception($"Failed to bind {typeof(T).FullName}! Is your initializer returning a valid area?"); + } } }