diff --git a/src/Unosquare.Labs.EmbedIO.Samples/PeopleController.cs b/src/Unosquare.Labs.EmbedIO.Samples/PeopleController.cs index a58ce9bf2..6e23ea7ab 100644 --- a/src/Unosquare.Labs.EmbedIO.Samples/PeopleController.cs +++ b/src/Unosquare.Labs.EmbedIO.Samples/PeopleController.cs @@ -38,11 +38,11 @@ public async Task GetPeople(string id = null) { // if it ends with a / means we need to list people if (string.IsNullOrWhiteSpace(id)) - return await this.JsonResponseAsync(_dbContext.People.SelectAll()); + return await JsonResponseAsync(_dbContext.People.SelectAll()); // if it ends with "first" means we need to show first record of people if (id == "first") - return await this.JsonResponseAsync(_dbContext.People.SelectAll().First()); + return await JsonResponseAsync(_dbContext.People.SelectAll().First()); // otherwise, we need to parse the key and respond with the entity accordingly if (int.TryParse(id, out var key)) @@ -50,7 +50,7 @@ public async Task GetPeople(string id = null) var single = await _dbContext.People.SingleAsync(key); if (single != null) - return await this.JsonResponseAsync(single); + return await JsonResponseAsync(single); } throw new KeyNotFoundException($"Key Not Found: {id}"); @@ -62,7 +62,7 @@ public async Task GetPeople(string id = null) /// [WebApiHandler(HttpVerbs.Post, RelativePath + "people/")] public Task PostPeople() => - this.TransformJson(async (model, ct) => + TransformJson(async (model, ct) => model.CreateGridDataResponse((await _dbContext.People.SelectAllAsync()).AsQueryable())); /// @@ -74,7 +74,7 @@ public async Task Echo() { var content = await this.RequestFormDataDictionaryAsync(); - return await this.JsonResponseAsync(content); + return await JsonResponseAsync(content); } } } \ No newline at end of file diff --git a/src/Unosquare.Labs.EmbedIO/Abstractions/IHttpContext.cs b/src/Unosquare.Labs.EmbedIO/Abstractions/IHttpContext.cs index 73b4dbdd5..92470a7b1 100644 --- a/src/Unosquare.Labs.EmbedIO/Abstractions/IHttpContext.cs +++ b/src/Unosquare.Labs.EmbedIO/Abstractions/IHttpContext.cs @@ -2,6 +2,7 @@ { using System.Threading.Tasks; using System.Security.Principal; + using System.Collections.Generic; /// /// Interface to create a HTTP Context. @@ -40,6 +41,14 @@ public interface IHttpContext /// IWebServer WebServer { get; set; } + /// + /// Gets or sets the dictionary of data to pass trough the EmbedIO pipeline. + /// + /// + /// The items. + /// + IDictionary Items { get; set; } + /// /// Accepts the web socket asynchronous. /// diff --git a/src/Unosquare.Labs.EmbedIO/Core/ReverseOrdinalStringComparer.cs b/src/Unosquare.Labs.EmbedIO/Core/ReverseOrdinalStringComparer.cs index 39ab8eccc..914cf0fa1 100644 --- a/src/Unosquare.Labs.EmbedIO/Core/ReverseOrdinalStringComparer.cs +++ b/src/Unosquare.Labs.EmbedIO/Core/ReverseOrdinalStringComparer.cs @@ -6,7 +6,7 @@ // Sorts strings in reverse order to obtain the evaluation order of virtual paths internal sealed class ReverseOrdinalStringComparer : IComparer { - private static readonly IComparer _directComparer = StringComparer.Ordinal; + private static readonly IComparer DirectComparer = StringComparer.Ordinal; private ReverseOrdinalStringComparer() { @@ -14,6 +14,6 @@ private ReverseOrdinalStringComparer() public static IComparer Instance { get; } = new ReverseOrdinalStringComparer(); - public int Compare(string x, string y) => _directComparer.Compare(y, x); + public int Compare(string x, string y) => DirectComparer.Compare(y, x); } } \ No newline at end of file diff --git a/src/Unosquare.Labs.EmbedIO/HttpContext.cs b/src/Unosquare.Labs.EmbedIO/HttpContext.cs index a4fdebcf5..b89b90b90 100644 --- a/src/Unosquare.Labs.EmbedIO/HttpContext.cs +++ b/src/Unosquare.Labs.EmbedIO/HttpContext.cs @@ -1,9 +1,10 @@ #if !NETSTANDARD1_3 namespace Unosquare.Labs.EmbedIO { - using System.Security.Principal; - using System.Net; using System; + using System.Collections.Generic; + using System.Net; + using System.Security.Principal; using System.Threading.Tasks; /// @@ -13,6 +14,8 @@ namespace Unosquare.Labs.EmbedIO public class HttpContext : IHttpContext { private readonly HttpListenerContext _context; + private Lazy> _items = + new Lazy>(() => new Dictionary(), true); /// /// Initializes a new instance of the class. @@ -38,6 +41,13 @@ public HttpContext(HttpListenerContext context) /// public IWebServer WebServer { get; set; } + /// + public IDictionary Items + { + get => _items.Value; + set => _items = new Lazy>(() => value, true); + } + /// public async Task AcceptWebSocketAsync(int receiveBufferSize) => new WebSocketContext(await _context.AcceptWebSocketAsync(subProtocol: null, diff --git a/src/Unosquare.Labs.EmbedIO/Modules/ResourceFilesModule.cs b/src/Unosquare.Labs.EmbedIO/Modules/ResourceFilesModule.cs index 7bd7b49bd..a60849afd 100644 --- a/src/Unosquare.Labs.EmbedIO/Modules/ResourceFilesModule.cs +++ b/src/Unosquare.Labs.EmbedIO/Modules/ResourceFilesModule.cs @@ -2,14 +2,14 @@ { using Constants; using EmbedIO; + using Swan; using System; using System.Collections.Generic; using System.IO; using System.Linq; - using Swan; + using System.Reflection; using System.Threading; using System.Threading.Tasks; - using System.Reflection; /// /// Represents a simple module to server resource files from the .NET assembly. diff --git a/src/Unosquare.Labs.EmbedIO/Modules/StaticFilesModule.cs b/src/Unosquare.Labs.EmbedIO/Modules/StaticFilesModule.cs index 0bb1ba33c..a36f347d5 100644 --- a/src/Unosquare.Labs.EmbedIO/Modules/StaticFilesModule.cs +++ b/src/Unosquare.Labs.EmbedIO/Modules/StaticFilesModule.cs @@ -117,7 +117,8 @@ public StaticFilesModule( #endif headers?.ForEach(DefaultHeaders.Add); - additionalPaths?.ForEach((virtualPath, physicalPath) => { + additionalPaths?.ForEach((virtualPath, physicalPath) => + { if (virtualPath != "/") RegisterVirtualPath(virtualPath, physicalPath); }); @@ -196,7 +197,7 @@ public string DefaultExtension public ReadOnlyDictionary VirtualPaths => _virtualPathManager.VirtualPaths; /// - public override string Name => nameof(StaticFilesModule); + public override string Name { get; } = nameof(StaticFilesModule); /// /// Private collection holding the contents of the RAM Cache. @@ -358,9 +359,9 @@ private async Task HandleFile( } await WriteFileAsync( - partialHeader, - context.Response, - buffer, + partialHeader, + context.Response, + buffer, context.AcceptGzip(buffer.Length), ct) .ConfigureAwait(false); diff --git a/src/Unosquare.Labs.EmbedIO/Modules/WebApiController.cs b/src/Unosquare.Labs.EmbedIO/Modules/WebApiController.cs index bbeba0239..9c8800b9c 100644 --- a/src/Unosquare.Labs.EmbedIO/Modules/WebApiController.cs +++ b/src/Unosquare.Labs.EmbedIO/Modules/WebApiController.cs @@ -1,45 +1,60 @@ namespace Unosquare.Labs.EmbedIO.Modules { using System; - using System.Security.Principal; + using System.Text; + using System.Collections.Generic; + using System.Threading; using System.Threading.Tasks; + using System.Security.Principal; - /// /// /// Inherit from this class and define your own Web API methods /// You must RegisterController in the Web API Module to make it active. /// - public abstract class WebApiController : IHttpContext + public abstract class WebApiController { + private readonly IHttpContext _context; + /// /// Initializes a new instance of the class. /// /// The context. protected WebApiController(IHttpContext context) { - Request = context.Request; - Response = context.Response; - User = context.User; - WebServer = context.WebServer; + _context = context; } - /// - public IHttpRequest Request { get; internal set; } - - /// - public IHttpResponse Response { get; internal set; } + /// + /// Gets the HTTP Request. + /// + /// + /// The request. + /// + public IHttpRequest Request => _context.Request; - /// - public IPrincipal User { get; } + /// + /// Gets the HTTP Response. + /// + /// + /// The response. + /// + public IHttpResponse Response => _context.Response; - /// - public IWebServer WebServer { get; set; } + /// + /// Gets the user. + /// + /// + /// The user. + /// + public IPrincipal User => _context.User; - /// - public Task AcceptWebSocketAsync(int receiveBufferSize) - { - throw new NotImplementedException(); - } + /// + /// Gets or sets the web server. + /// + /// + /// The web server. + /// + public IWebServer WebServer => _context.WebServer; /// /// Sets the default headers to the Web API response. @@ -52,6 +67,88 @@ public Task AcceptWebSocketAsync(int receiveBufferSize) /// /// Previous values are defined to avoid caching from client. /// - public virtual void SetDefaultHeaders() => this.NoCache(); + public virtual void SetDefaultHeaders() => _context.NoCache(); + + /// + /// Outputs async a Json Response given a data object. + /// + /// The data. + /// The cancellation token. + /// + /// A true value if the response output was set. + /// + public virtual Task JsonResponseAsync(object data, CancellationToken cancellationToken = default) => + _context.JsonResponseAsync(data, cancellationToken); + + /// + /// Transforms the response body as JSON and write a new JSON to the request. + /// + /// The type of the input. + /// The type of the output. + /// The transform function. + /// The cancellation token. + /// + /// A task for writing the output stream. + /// + public virtual Task TransformJson(Func> transformFunc, + CancellationToken cancellationToken = default) + where TIn : class + => _context.TransformJson(transformFunc, cancellationToken); + + /// + /// Outputs a JSON Response given an exception. + /// + /// The ex. + /// The status code. + /// if set to true [use gzip]. + /// The cancellation token. + /// + /// A task for writing the output stream. + /// + public virtual Task JsonExceptionResponseAsync( + Exception ex, + System.Net.HttpStatusCode statusCode = System.Net.HttpStatusCode.InternalServerError, + bool useGzip = true, + CancellationToken cancellationToken = default) + => _context.JsonExceptionResponseAsync(ex, statusCode, useGzip, cancellationToken); + + /// + /// Outputs async a string response given a string. + /// + /// The content. + /// Type of the content. + /// The encoding. + /// if set to true [use gzip]. + /// The cancellation token. + /// + /// A task for writing the output stream. + /// + public virtual Task StringResponseAsync( + string content, + string contentType = "application/json", + Encoding encoding = null, + bool useGzip = true, + CancellationToken cancellationToken = default) => + _context.StringResponseAsync(content, contentType, encoding, useGzip, cancellationToken); + + /// + /// Returns dictionary from Request POST data + /// Please note the underlying input stream is not rewindable. + /// + /// A task with a collection that represents KVPs from request data. + public virtual Task> RequestFormDataDictionaryAsync() => + _context.RequestFormDataDictionaryAsync(); + + /// + /// Deletes the session object associated to the current context. + /// + public virtual void DeleteSession() => _context.DeleteSession(); + + /// + /// Gets the session object associated to the current context. + /// Returns null if the LocalSessionWebModule has not been loaded. + /// + /// A session object for the given server context. + public virtual SessionInfo GetSession() => _context.GetSession(); } } \ No newline at end of file diff --git a/src/Unosquare.Labs.EmbedIO/Modules/WebApiModule.cs b/src/Unosquare.Labs.EmbedIO/Modules/WebApiModule.cs index ca403dc4b..f443ae566 100644 --- a/src/Unosquare.Labs.EmbedIO/Modules/WebApiModule.cs +++ b/src/Unosquare.Labs.EmbedIO/Modules/WebApiModule.cs @@ -1,13 +1,13 @@ namespace Unosquare.Labs.EmbedIO.Modules { + using Constants; + using EmbedIO; + using Swan; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading.Tasks; - using Constants; - using EmbedIO; - using Swan; /// /// A very simple module to register class methods as handlers. diff --git a/src/Unosquare.Labs.EmbedIO/System.Net/HttpListenerContext.cs b/src/Unosquare.Labs.EmbedIO/System.Net/HttpListenerContext.cs index 89c1c474a..cff9de7fb 100644 --- a/src/Unosquare.Labs.EmbedIO/System.Net/HttpListenerContext.cs +++ b/src/Unosquare.Labs.EmbedIO/System.Net/HttpListenerContext.cs @@ -1,6 +1,7 @@ namespace Unosquare.Net { using System; + using System.Collections.Generic; using System.Security.Principal; using System.Threading.Tasks; using Labs.EmbedIO; @@ -12,6 +13,8 @@ public sealed class HttpListenerContext : IHttpContext { private WebSocketContext _websocketContext; + private Lazy> _items = + new Lazy>(() => new Dictionary(), true); internal HttpListenerContext(HttpConnection cnc) { @@ -33,6 +36,13 @@ internal HttpListenerContext(HttpConnection cnc) /// public IWebServer WebServer { get; set; } + + /// + public IDictionary Items + { + get => _items.Value; + set => _items = new Lazy>(() => value, true); + } internal HttpListenerRequest HttpListenerRequest => Request as HttpListenerRequest; diff --git a/src/Unosquare.Labs.EmbedIO/Tests/TestHttpContext.cs b/src/Unosquare.Labs.EmbedIO/Tests/TestHttpContext.cs index 6c0bb78f0..e48655699 100644 --- a/src/Unosquare.Labs.EmbedIO/Tests/TestHttpContext.cs +++ b/src/Unosquare.Labs.EmbedIO/Tests/TestHttpContext.cs @@ -1,14 +1,19 @@ namespace Unosquare.Labs.EmbedIO.Tests { + using System; + using System.Collections.Generic; using System.Threading.Tasks; using System.Security.Principal; /// /// Represents a Test Http Context. /// - /// + /// public class TestHttpContext : IHttpContext { + private Lazy> _items = + new Lazy>(() => new Dictionary(), true); + /// /// Initializes a new instance of the class. /// @@ -22,7 +27,7 @@ public TestHttpContext(IHttpRequest request, IWebServer webserver) /// public IHttpRequest Request { get; } - + /// public IHttpResponse Response { get; } = new TestHttpResponse(); @@ -33,9 +38,14 @@ public TestHttpContext(IHttpRequest request, IWebServer webserver) public IWebServer WebServer { get; set; } /// - public Task AcceptWebSocketAsync(int receiveBufferSize) + public IDictionary Items { - throw new System.NotImplementedException(); + get => _items.Value; + set => _items = new Lazy>(() => value, true); } + + /// + /// + public Task AcceptWebSocketAsync(int receiveBufferSize) => throw new NotImplementedException(); } } \ No newline at end of file diff --git a/test/Unosquare.Labs.EmbedIO.Tests/TestObjects/TestController.cs b/test/Unosquare.Labs.EmbedIO.Tests/TestObjects/TestController.cs index d52643b3c..49b4c975d 100644 --- a/test/Unosquare.Labs.EmbedIO.Tests/TestObjects/TestController.cs +++ b/test/Unosquare.Labs.EmbedIO.Tests/TestObjects/TestController.cs @@ -33,7 +33,7 @@ public Task GetPerson() } catch (Exception ex) { - return this.JsonExceptionResponseAsync(ex); + return JsonExceptionResponseAsync(ex); } } @@ -47,12 +47,12 @@ public Task GetPeople() // if it ends with a / means we need to list people return lastSegment.EndsWith("/") - ? this.JsonResponseAsync(PeopleRepository.Database) + ? JsonResponseAsync(PeopleRepository.Database) : CheckPerson(lastSegment); } catch (Exception ex) { - return this.JsonExceptionResponseAsync(ex); + return JsonExceptionResponseAsync(ex); } } @@ -61,7 +61,7 @@ public Task PostPeople() { try { - return this.TransformJson(async (x, ct) => + return TransformJson(async (x, ct) => { await Task.Delay(0, ct); @@ -70,7 +70,7 @@ public Task PostPeople() } catch (Exception ex) { - return this.JsonExceptionResponseAsync(ex); + return JsonExceptionResponseAsync(ex); } } @@ -79,13 +79,13 @@ public async Task PostEcho() { try { - var content = await this.RequestFormDataDictionaryAsync(); + var content = await RequestFormDataDictionaryAsync(); - return await this.JsonResponseAsync(content); + return await JsonResponseAsync(content); } catch (Exception ex) { - return await this.JsonExceptionResponseAsync(ex); + return await JsonExceptionResponseAsync(ex); } } @@ -93,7 +93,7 @@ private Task CheckPerson(string personKey) { if (int.TryParse(personKey, out var key) && PeopleRepository.Database.Any(p => p.Key == key)) { - return this.JsonResponseAsync(PeopleRepository.Database.FirstOrDefault(p => p.Key == key)); + return JsonResponseAsync(PeopleRepository.Database.FirstOrDefault(p => p.Key == key)); } throw new KeyNotFoundException($"Key Not Found: {personKey}"); @@ -115,15 +115,15 @@ public TestControllerWithConstructor(IHttpContext context, string name = "Test") [WebApiHandler(HttpVerbs.Get, "/name")] public Task GetName() { - this.NoCache(); - return this.JsonResponseAsync(WebName); + Response.NoCache(); + return JsonResponseAsync(WebName); } [WebApiHandler(HttpVerbs.Get, "/namePublic")] public Task GetNamePublic() { Response.AddHeader("Cache-Control", "public"); - return this.JsonResponseAsync(WebName); + return JsonResponseAsync(WebName); } public override void SetDefaultHeaders() diff --git a/test/Unosquare.Labs.EmbedIO.Tests/TestObjects/TestLocalSessionController.cs b/test/Unosquare.Labs.EmbedIO.Tests/TestObjects/TestLocalSessionController.cs index 91687a7d3..862eb01d3 100644 --- a/test/Unosquare.Labs.EmbedIO.Tests/TestObjects/TestLocalSessionController.cs +++ b/test/Unosquare.Labs.EmbedIO.Tests/TestObjects/TestLocalSessionController.cs @@ -25,32 +25,30 @@ public Task GetCookieC() var cookie = new System.Net.Cookie(CookieName, CookieName); Response.Cookies.Add(cookie); - return this.JsonResponseAsync(Response.Cookies[CookieName]); + return JsonResponseAsync(Response.Cookies[CookieName]); } [WebApiHandler(HttpVerbs.Get, "/deletesession")] public Task DeleteSessionC() { - this.DeleteSession(); + DeleteSession(); - return this.JsonResponseAsync("Deleted"); + return JsonResponseAsync("Deleted"); } [WebApiHandler(HttpVerbs.Get, "/putdata")] public Task PutDataSession() { - this.GetSession()?.Data.TryAdd("sessionData", MyData); + GetSession()?.Data.TryAdd("sessionData", MyData); - return this.JsonResponseAsync(this.GetSession().Data["sessionData"].ToString()); + return JsonResponseAsync(GetSession().Data["sessionData"].ToString()); } [WebApiHandler(HttpVerbs.Get, "/getdata")] - public Task GetDataSession() - { - return this.JsonResponseAsync(this.GetSession().Data.TryGetValue("sessionData", out var data) + public Task GetDataSession() => + JsonResponseAsync(GetSession().Data.TryGetValue("sessionData", out var data) ? data.ToString() : string.Empty); - } [WebApiHandler(HttpVerbs.Get, "/geterror")] public bool GetError() => false; diff --git a/test/Unosquare.Labs.EmbedIO.Tests/TestObjects/TestRegexController.cs b/test/Unosquare.Labs.EmbedIO.Tests/TestObjects/TestRegexController.cs index da79c742f..0e012167c 100644 --- a/test/Unosquare.Labs.EmbedIO.Tests/TestObjects/TestRegexController.cs +++ b/test/Unosquare.Labs.EmbedIO.Tests/TestObjects/TestRegexController.cs @@ -17,7 +17,7 @@ public TestRegexController(IHttpContext context) } [WebApiHandler(HttpVerbs.Get, "/" + RelativePath + "big")] - public Task GetBigJson() => this.JsonResponseAsync(Enumerable.Range(1, 100).Select(x => new + public Task GetBigJson() => JsonResponseAsync(Enumerable.Range(1, 100).Select(x => new { x, y = TimeZoneInfo.GetSystemTimeZones() @@ -25,18 +25,18 @@ public TestRegexController(IHttpContext context) })); [WebApiHandler(HttpVerbs.Get, "/" + RelativePath + "empty")] - public Task GetEmpty() => this.JsonResponseAsync(new { Ok = true }); + public Task GetEmpty() => JsonResponseAsync(new { Ok = true }); [WebApiHandler(HttpVerbs.Get, "/" + RelativePath + "regex")] public Task GetPeople() { try { - return this.JsonResponseAsync(PeopleRepository.Database); + return JsonResponseAsync(PeopleRepository.Database); } catch (Exception ex) { - return this.JsonExceptionResponseAsync(ex); + return JsonExceptionResponseAsync(ex); } } @@ -49,7 +49,7 @@ public Task GetPerson(int id) } catch (Exception ex) { - return this.JsonExceptionResponseAsync(ex); + return JsonExceptionResponseAsync(ex); } } @@ -58,11 +58,11 @@ public Task GetPerson(int? id) { try { - return id.HasValue ? CheckPerson(id.Value) : this.JsonResponseAsync(PeopleRepository.Database); + return id.HasValue ? CheckPerson(id.Value) : JsonResponseAsync(PeopleRepository.Database); } catch (Exception ex) { - return this.JsonExceptionResponseAsync(ex); + return JsonExceptionResponseAsync(ex); } } @@ -75,7 +75,7 @@ public Task GetPersonAsync(int id) } catch (Exception ex) { - return this.JsonExceptionResponseAsync(ex); + return JsonExceptionResponseAsync(ex); } } @@ -88,14 +88,14 @@ public Task GetPerson(DateTime date) if (item != null) { - return this.JsonResponseAsync(item); + return JsonResponseAsync(item); } throw new KeyNotFoundException($"Key Not Found: {date}"); } catch (Exception ex) { - return this.JsonExceptionResponseAsync(ex); + return JsonExceptionResponseAsync(ex); } } @@ -109,14 +109,14 @@ public Task GetPerson(string skill, int age) if (item != null) { - return this.JsonResponseAsync(item); + return JsonResponseAsync(item); } throw new KeyNotFoundException($"Key Not Found: {skill}-{age}"); } catch (Exception ex) { - return this.JsonExceptionResponseAsync(ex); + return JsonExceptionResponseAsync(ex); } } @@ -131,14 +131,14 @@ public Task GetOptionalPerson(string skill, int? age = null) if (item != null) { - return this.JsonResponseAsync(item); + return JsonResponseAsync(item); } throw new KeyNotFoundException($"Key Not Found: {skill}-{age}"); } catch (Exception ex) { - return this.JsonExceptionResponseAsync(ex); + return JsonExceptionResponseAsync(ex); } } @@ -147,7 +147,7 @@ private Task CheckPerson(int id) var item = PeopleRepository.Database.FirstOrDefault(p => p.Key == id); if (item == null) throw new KeyNotFoundException($"Key Not Found: {id}"); - return this.JsonResponseAsync(item); + return JsonResponseAsync(item); } } } \ No newline at end of file