diff --git a/Unosquare.Labs.EmbedIO.Samples/Unosquare.Labs.EmbedIO.Samples.csproj b/Unosquare.Labs.EmbedIO.Samples/Unosquare.Labs.EmbedIO.Samples.csproj index 4b02da994..76af54d27 100644 --- a/Unosquare.Labs.EmbedIO.Samples/Unosquare.Labs.EmbedIO.Samples.csproj +++ b/Unosquare.Labs.EmbedIO.Samples/Unosquare.Labs.EmbedIO.Samples.csproj @@ -47,7 +47,7 @@ True - ..\packages\Tubular.ServerSide.0.9.7\lib\net45\Unosquare.Tubular.dll + ..\packages\Tubular.ServerSide.0.9.14\lib\net45\Unosquare.Tubular.dll True diff --git a/Unosquare.Labs.EmbedIO.Samples/packages.config b/Unosquare.Labs.EmbedIO.Samples/packages.config index debd2d4a5..3df7feace 100644 --- a/Unosquare.Labs.EmbedIO.Samples/packages.config +++ b/Unosquare.Labs.EmbedIO.Samples/packages.config @@ -2,6 +2,6 @@ - - + + \ No newline at end of file diff --git a/Unosquare.Labs.EmbedIO.Tests/FluentTest.cs b/Unosquare.Labs.EmbedIO.Tests/FluentTest.cs index c0da544f8..9e1f30b03 100644 --- a/Unosquare.Labs.EmbedIO.Tests/FluentTest.cs +++ b/Unosquare.Labs.EmbedIO.Tests/FluentTest.cs @@ -48,7 +48,7 @@ public void FluentWithWebApi() Assert.AreEqual(webServer.Modules.Count, 1, "It has 1 modules loaded"); Assert.IsNotNull(webServer.Module(), "It has WebApiModule"); - Assert.AreEqual(webServer.Module().ControllersCount, 1, "It has one controller"); + Assert.AreEqual(webServer.Module().ControllersCount, 2, "It has two controllers"); webServer.Dispose(); } @@ -74,7 +74,7 @@ public void FluentLoadWebApiControllers() Assert.AreEqual(webServer.Modules.Count, 1, "It has 1 modules loaded"); Assert.IsNotNull(webServer.Module(), "It has WebApiModule"); - Assert.AreEqual(webServer.Module().ControllersCount, 1, "It has one controller"); + Assert.AreEqual(webServer.Module().ControllersCount, 2, "It has two controllers"); webServer.Dispose(); } diff --git a/Unosquare.Labs.EmbedIO.Tests/TestObjects/TestController.cs b/Unosquare.Labs.EmbedIO.Tests/TestObjects/TestController.cs index 5ef792422..7c42b1e57 100644 --- a/Unosquare.Labs.EmbedIO.Tests/TestObjects/TestController.cs +++ b/Unosquare.Labs.EmbedIO.Tests/TestObjects/TestController.cs @@ -74,4 +74,20 @@ public bool PostPeople(WebServer server, HttpListenerContext context) } } } + + public class TestControllerWithConstructor : WebApiController + { + public string WebName { get; set; } + + public TestControllerWithConstructor(string name) + { + WebName = name; + } + + [WebApiHandler(HttpVerbs.Get, "/name")] + public bool GetPeople(WebServer server, HttpListenerContext context) + { + return context.JsonResponse(WebName); + } + } } diff --git a/Unosquare.Labs.EmbedIO.Tests/Unosquare.Labs.EmbedIO.Tests.csproj b/Unosquare.Labs.EmbedIO.Tests/Unosquare.Labs.EmbedIO.Tests.csproj index 5b88c9b3b..2183a1831 100644 --- a/Unosquare.Labs.EmbedIO.Tests/Unosquare.Labs.EmbedIO.Tests.csproj +++ b/Unosquare.Labs.EmbedIO.Tests/Unosquare.Labs.EmbedIO.Tests.csproj @@ -30,8 +30,8 @@ 4 - - ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll + + ..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll True diff --git a/Unosquare.Labs.EmbedIO.Tests/WebApiModuleTest.cs b/Unosquare.Labs.EmbedIO.Tests/WebApiModuleTest.cs index 8bc5d114c..19c8a64e9 100644 --- a/Unosquare.Labs.EmbedIO.Tests/WebApiModuleTest.cs +++ b/Unosquare.Labs.EmbedIO.Tests/WebApiModuleTest.cs @@ -102,6 +102,25 @@ public void PostJsonData() } } + [Test] + public void TestWebApiWithConstructor() + { + const string name = "Test"; + + WebServer.Module().RegisterController(() => new TestControllerWithConstructor(name)); + + var request = (HttpWebRequest) WebRequest.Create(Resources.ServerAddress + "name"); + + using (var response = (HttpWebResponse) request.GetResponse()) + { + Assert.AreEqual(response.StatusCode, HttpStatusCode.OK, "Status Code OK"); + + var body = new StreamReader(response.GetResponseStream()).ReadToEnd(); + + Assert.AreEqual(body, name); + } + } + [TearDown] public void Kill() { diff --git a/Unosquare.Labs.EmbedIO.Tests/packages.config b/Unosquare.Labs.EmbedIO.Tests/packages.config index 24073f522..3b829b9ff 100644 --- a/Unosquare.Labs.EmbedIO.Tests/packages.config +++ b/Unosquare.Labs.EmbedIO.Tests/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/Unosquare.Labs.EmbedIO/Modules/WebApiModule.cs b/Unosquare.Labs.EmbedIO/Modules/WebApiModule.cs index 6f5c41ae8..8634a0bfb 100644 --- a/Unosquare.Labs.EmbedIO/Modules/WebApiModule.cs +++ b/Unosquare.Labs.EmbedIO/Modules/WebApiModule.cs @@ -1,263 +1,263 @@ -namespace Unosquare.Labs.EmbedIO.Modules -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - using System.Threading.Tasks; - using Unosquare.Labs.EmbedIO; - - /// - /// A very simple module to register class methods as handlers. - /// Public instance methods that match the WebServerModule.ResponseHandler signature, and have the WebApi handler attribute - /// will be used to respond to web server requests - /// - public class WebApiModule : WebModuleBase - { - private readonly List ControllerTypes = new List(); - - private readonly Dictionary, MethodInfo>>> DelegateMap - = - new Dictionary, MethodInfo>>>( - StringComparer.InvariantCultureIgnoreCase); - - /// - /// Initializes a new instance of the class. - /// - public WebApiModule() - : base() - { - this.AddHandler(ModuleMap.AnyPath, HttpVerbs.Any, (server, context) => - { - var path = context.RequestPath(); - var verb = context.RequestVerb(); - var wildcardPaths = DelegateMap.Keys - .Where(k => k.EndsWith("/" + ModuleMap.AnyPath)) - .Select(s => s.ToLowerInvariant()) - .ToArray(); - - var wildcardMatch = wildcardPaths.FirstOrDefault(p => path.StartsWith(p.Substring(0, p.Length - 1))); - - if (string.IsNullOrWhiteSpace(wildcardMatch) == false) - path = wildcardMatch; - - if (DelegateMap.ContainsKey(path) == false) - return false; - - if (DelegateMap[path].ContainsKey(verb) == false) // TODO: Fix Any Verb - { - var originalPath = context.RequestPath(); - if (DelegateMap.ContainsKey(originalPath) && - DelegateMap[originalPath].ContainsKey(verb)) - { - path = originalPath; - } - else - return false; - } - - var methodPair = DelegateMap[path][verb]; - var controller = methodPair.Item1(); - - if (methodPair.Item2.ReturnType == typeof(Task)) - { - var method = Delegate.CreateDelegate(typeof(AsyncResponseHandler), controller, methodPair.Item2); - - server.Log.DebugFormat("Handler: {0}.{1}", method.Method.DeclaringType.FullName, method.Method.Name); - context.NoCache(); - var returnValue = Task.Run(async () => - { - var task = await (Task)method.DynamicInvoke(server, context); - return task; - }); - - return returnValue.Result; - } - else - { - var method = Delegate.CreateDelegate(typeof(ResponseHandler), controller, methodPair.Item2); - - server.Log.DebugFormat("Handler: {0}.{1}", method.Method.DeclaringType.FullName, method.Method.Name); - context.NoCache(); - var returnValue = (bool)method.DynamicInvoke(server, context); - return returnValue; - } - }); - } - - /// - /// Gets the name of this module. - /// - /// - /// The name. - /// - public override string Name - { - get { return "Web API Module"; } - } - - /// - /// Gets the controllers count - /// - public int ControllersCount - { - get { return ControllerTypes.Count; } - } - - /// - /// Registers the controller. - /// - /// - /// Controller types must be unique within the module - public void RegisterController() - where T : WebApiController, new() - { - if (ControllerTypes.Contains(typeof(T))) - throw new ArgumentException("Controller types must be unique within the module"); - - RegisterController(typeof(T)); - } - - /// - /// Registers the controller. - /// - /// - /// - /// Controller types must be unique within the module - public void RegisterController(Func controllerFactory) - where T : WebApiController - { - if (ControllerTypes.Contains(typeof(T))) - throw new ArgumentException("Controller types must be unique within the module"); - - RegisterController(typeof(T), controllerFactory); - } - - /// - /// Registers the controller. - /// - /// Type of the controller. - public void RegisterController(Type controllerType) - { - Func controllerFactory = () => Activator.CreateInstance(controllerType); - this.RegisterController(controllerType, controllerFactory); - } - - - /// - /// Registers the controller. - /// - /// Type of the controller. - /// The controller factory method. - public void RegisterController(Type controllerType, Func controllerFactory) - { - var protoDelegate = new ResponseHandler((server, context) => true); - var protoAsyncDelegate = new AsyncResponseHandler((server, context) => Task.FromResult(true)); - - var methods = controllerType - .GetMethods(BindingFlags.Instance | BindingFlags.Public) - .Where( - m => (m.ReturnType == protoDelegate.Method.ReturnType - || m.ReturnType == protoAsyncDelegate.Method.ReturnType) - && m.GetParameters() - .Select(pi => pi.ParameterType) - .SequenceEqual(protoDelegate.Method.GetParameters() - .Select(pi => pi.ParameterType))); - - foreach (var method in methods) - { - var attribute = - method.GetCustomAttributes(typeof(WebApiHandlerAttribute), true).FirstOrDefault() as - WebApiHandlerAttribute; - if (attribute == null) continue; - - foreach (var path in attribute.Paths) - { - var delegatePath = new Dictionary, MethodInfo>>(); - if (DelegateMap.ContainsKey(path)) - delegatePath = DelegateMap[path]; // update - else - DelegateMap.Add(path, delegatePath); // add - - var delegatePair = new Tuple, MethodInfo>(controllerFactory, method); - if (DelegateMap[path].ContainsKey(attribute.Verb)) - DelegateMap[path][attribute.Verb] = delegatePair; // update - else - DelegateMap[path].Add(attribute.Verb, delegatePair); // add - } - } - - ControllerTypes.Add(controllerType); - } - } - - /// - /// Decorate methods within controllers with this attribute in order to make them callable from the Web API Module - /// Method Must match the WebServerModule. - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public class WebApiHandlerAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - /// The verb. - /// The paths. - /// The argument 'paths' must be specified. - public WebApiHandlerAttribute(HttpVerbs verb, string[] paths) - { - if (paths == null || paths.Length == 0) - throw new ArgumentException("The argument 'paths' must be specified."); - - this.Verb = verb; - this.Paths = paths; - } - - /// - /// Initializes a new instance of the class. - /// - /// The verb. - /// The path. - /// The argument 'path' must be specified. - public WebApiHandlerAttribute(HttpVerbs verb, string path) - { - if (path == null || string.IsNullOrWhiteSpace(path)) - throw new ArgumentException("The argument 'path' must be specified."); - - this.Verb = verb; - this.Paths = new string[] { path }; - } - - /// - /// Gets or sets the verb. - /// - /// - /// The verb. - /// - public HttpVerbs Verb { get; protected set; } - - /// - /// Gets or sets the paths. - /// - /// - /// The paths. - /// - public string[] Paths { get; protected set; } - } - - /// - /// 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 - { - /// - /// Initializes a new instance of the class. - /// - public WebApiController() - { - // placeholder - } - } - -} +namespace Unosquare.Labs.EmbedIO.Modules +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Threading.Tasks; + using Unosquare.Labs.EmbedIO; + + /// + /// A very simple module to register class methods as handlers. + /// Public instance methods that match the WebServerModule.ResponseHandler signature, and have the WebApi handler attribute + /// will be used to respond to web server requests + /// + public class WebApiModule : WebModuleBase + { + private readonly List ControllerTypes = new List(); + + private readonly Dictionary, MethodInfo>>> DelegateMap + = + new Dictionary, MethodInfo>>>( + StringComparer.InvariantCultureIgnoreCase); + + /// + /// Initializes a new instance of the class. + /// + public WebApiModule() + : base() + { + this.AddHandler(ModuleMap.AnyPath, HttpVerbs.Any, (server, context) => + { + var path = context.RequestPath(); + var verb = context.RequestVerb(); + var wildcardPaths = DelegateMap.Keys + .Where(k => k.EndsWith("/" + ModuleMap.AnyPath)) + .Select(s => s.ToLowerInvariant()) + .ToArray(); + + var wildcardMatch = wildcardPaths.FirstOrDefault(p => path.StartsWith(p.Substring(0, p.Length - 1))); + + if (string.IsNullOrWhiteSpace(wildcardMatch) == false) + path = wildcardMatch; + + if (DelegateMap.ContainsKey(path) == false) + return false; + + if (DelegateMap[path].ContainsKey(verb) == false) // TODO: Fix Any Verb + { + var originalPath = context.RequestPath(); + if (DelegateMap.ContainsKey(originalPath) && + DelegateMap[originalPath].ContainsKey(verb)) + { + path = originalPath; + } + else + return false; + } + + var methodPair = DelegateMap[path][verb]; + var controller = methodPair.Item1(); + + if (methodPair.Item2.ReturnType == typeof(Task)) + { + var method = Delegate.CreateDelegate(typeof(AsyncResponseHandler), controller, methodPair.Item2); + + server.Log.DebugFormat("Handler: {0}.{1}", method.Method.DeclaringType.FullName, method.Method.Name); + context.NoCache(); + var returnValue = Task.Run(async () => + { + var task = await (Task)method.DynamicInvoke(server, context); + return task; + }); + + return returnValue.Result; + } + else + { + var method = Delegate.CreateDelegate(typeof(ResponseHandler), controller, methodPair.Item2); + + server.Log.DebugFormat("Handler: {0}.{1}", method.Method.DeclaringType.FullName, method.Method.Name); + context.NoCache(); + var returnValue = (bool)method.DynamicInvoke(server, context); + return returnValue; + } + }); + } + + /// + /// Gets the name of this module. + /// + /// + /// The name. + /// + public override string Name + { + get { return "Web API Module"; } + } + + /// + /// Gets the controllers count + /// + public int ControllersCount + { + get { return ControllerTypes.Count; } + } + + /// + /// Registers the controller. + /// + /// + /// Controller types must be unique within the module + public void RegisterController() + where T : WebApiController, new() + { + if (ControllerTypes.Contains(typeof(T))) + throw new ArgumentException("Controller types must be unique within the module"); + + RegisterController(typeof(T)); + } + + /// + /// Registers the controller. + /// + /// + /// + /// Controller types must be unique within the module + public void RegisterController(Func controllerFactory) + where T : WebApiController + { + if (ControllerTypes.Contains(typeof(T))) + throw new ArgumentException("Controller types must be unique within the module"); + + RegisterController(typeof(T), controllerFactory); + } + + /// + /// Registers the controller. + /// + /// Type of the controller. + public void RegisterController(Type controllerType) + { + Func controllerFactory = () => Activator.CreateInstance(controllerType); + this.RegisterController(controllerType, controllerFactory); + } + + + /// + /// Registers the controller. + /// + /// Type of the controller. + /// The controller factory method. + public void RegisterController(Type controllerType, Func controllerFactory) + { + var protoDelegate = new ResponseHandler((server, context) => true); + var protoAsyncDelegate = new AsyncResponseHandler((server, context) => Task.FromResult(true)); + + var methods = controllerType + .GetMethods(BindingFlags.Instance | BindingFlags.Public) + .Where( + m => (m.ReturnType == protoDelegate.Method.ReturnType + || m.ReturnType == protoAsyncDelegate.Method.ReturnType) + && m.GetParameters() + .Select(pi => pi.ParameterType) + .SequenceEqual(protoDelegate.Method.GetParameters() + .Select(pi => pi.ParameterType))); + + foreach (var method in methods) + { + var attribute = + method.GetCustomAttributes(typeof(WebApiHandlerAttribute), true).FirstOrDefault() as + WebApiHandlerAttribute; + if (attribute == null) continue; + + foreach (var path in attribute.Paths) + { + var delegatePath = new Dictionary, MethodInfo>>(); + if (DelegateMap.ContainsKey(path)) + delegatePath = DelegateMap[path]; // update + else + DelegateMap.Add(path, delegatePath); // add + + var delegatePair = new Tuple, MethodInfo>(controllerFactory, method); + if (DelegateMap[path].ContainsKey(attribute.Verb)) + DelegateMap[path][attribute.Verb] = delegatePair; // update + else + DelegateMap[path].Add(attribute.Verb, delegatePair); // add + } + } + + ControllerTypes.Add(controllerType); + } + } + + /// + /// Decorate methods within controllers with this attribute in order to make them callable from the Web API Module + /// Method Must match the WebServerModule. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class WebApiHandlerAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The verb. + /// The paths. + /// The argument 'paths' must be specified. + public WebApiHandlerAttribute(HttpVerbs verb, string[] paths) + { + if (paths == null || paths.Length == 0) + throw new ArgumentException("The argument 'paths' must be specified."); + + this.Verb = verb; + this.Paths = paths; + } + + /// + /// Initializes a new instance of the class. + /// + /// The verb. + /// The path. + /// The argument 'path' must be specified. + public WebApiHandlerAttribute(HttpVerbs verb, string path) + { + if (string.IsNullOrWhiteSpace(path)) + throw new ArgumentException("The argument 'path' must be specified."); + + this.Verb = verb; + this.Paths = new string[] { path }; + } + + /// + /// Gets or sets the verb. + /// + /// + /// The verb. + /// + public HttpVerbs Verb { get; protected set; } + + /// + /// Gets or sets the paths. + /// + /// + /// The paths. + /// + public string[] Paths { get; protected set; } + } + + /// + /// 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 + { + /// + /// Initializes a new instance of the class. + /// + public WebApiController() + { + // placeholder + } + } + +} diff --git a/Unosquare.Labs.EmbedIO/Unosquare.Labs.EmbedIO.csproj b/Unosquare.Labs.EmbedIO/Unosquare.Labs.EmbedIO.csproj index b767cfabf..21742db9f 100644 --- a/Unosquare.Labs.EmbedIO/Unosquare.Labs.EmbedIO.csproj +++ b/Unosquare.Labs.EmbedIO/Unosquare.Labs.EmbedIO.csproj @@ -38,9 +38,9 @@ - - False - ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll + + ..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll + True diff --git a/Unosquare.Labs.EmbedIO/packages.config b/Unosquare.Labs.EmbedIO/packages.config index 747efc53e..505e58836 100644 --- a/Unosquare.Labs.EmbedIO/packages.config +++ b/Unosquare.Labs.EmbedIO/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file