diff --git a/MTSC/Common/Http/Attributes/FromBodyAttribute.cs b/MTSC/Common/Http/Attributes/FromBodyAttribute.cs index 4208a69..2e742a7 100644 --- a/MTSC/Common/Http/Attributes/FromBodyAttribute.cs +++ b/MTSC/Common/Http/Attributes/FromBodyAttribute.cs @@ -1,8 +1,12 @@ -using System; +using MTSC.Common.Http.RoutingModules; namespace MTSC.Common.Http.Attributes { - public sealed class FromBodyAttribute : Attribute + public sealed class FromBodyAttribute : StringRouteDataBindingBaseAttribute { + public override string GetStringValue(HttpRouteBase route, RouteContext routeContext) + { + return routeContext.HttpRequest.BodyString; + } } } diff --git a/MTSC/Common/Http/Attributes/FromHeadersAttribute.cs b/MTSC/Common/Http/Attributes/FromHeadersAttribute.cs index 2506559..565ac20 100644 --- a/MTSC/Common/Http/Attributes/FromHeadersAttribute.cs +++ b/MTSC/Common/Http/Attributes/FromHeadersAttribute.cs @@ -1,8 +1,8 @@ -using System; +using MTSC.Common.Http.RoutingModules; namespace MTSC.Common.Http.Attributes { - public sealed class FromHeadersAttribute : Attribute + public sealed class FromHeadersAttribute : StringRouteDataBindingBaseAttribute { public string HeaderName { get; } @@ -10,5 +10,15 @@ namespace MTSC.Common.Http.Attributes { this.HeaderName = headerName; } + + public override string GetStringValue(HttpRouteBase route, RouteContext routeContext) + { + if (routeContext.HttpRequest.Headers.ContainsHeader(this.HeaderName)) + { + return routeContext.HttpRequest.Headers[this.HeaderName]; + } + + return null; + } } } diff --git a/MTSC/Common/Http/Attributes/FromRouteContextResourcesAttribute.cs b/MTSC/Common/Http/Attributes/FromRouteContextResourcesAttribute.cs index cf42db5..1707e09 100644 --- a/MTSC/Common/Http/Attributes/FromRouteContextResourcesAttribute.cs +++ b/MTSC/Common/Http/Attributes/FromRouteContextResourcesAttribute.cs @@ -1,8 +1,9 @@ -using System; +using MTSC.Common.Http.RoutingModules; +using System; namespace MTSC.Common.Http.Attributes { - public sealed class FromRouteContextResourcesAttribute : Attribute + public sealed class FromRouteContextResourcesAttribute : RouteDataBindingBaseAttribute { public string ResourceKey { get; } @@ -10,5 +11,15 @@ namespace MTSC.Common.Http.Attributes { this.ResourceKey = resourceKey; } + + public override object DataBind(HttpRouteBase route, RouteContext routeContext, Type propertyType) + { + if (routeContext.Resources.TryGetValue(this.ResourceKey, out var resource)) + { + return resource; + } + + return null; + } } } diff --git a/MTSC/Common/Http/Attributes/FromUrlAttribute.cs b/MTSC/Common/Http/Attributes/FromUrlAttribute.cs index ba8a563..1345ff7 100644 --- a/MTSC/Common/Http/Attributes/FromUrlAttribute.cs +++ b/MTSC/Common/Http/Attributes/FromUrlAttribute.cs @@ -1,8 +1,8 @@ -using System; +using MTSC.Common.Http.RoutingModules; namespace MTSC.Common.Http.Attributes { - public class FromUrlAttribute : Attribute + public class FromUrlAttribute : StringRouteDataBindingBaseAttribute { public string Placeholder { get; } @@ -10,5 +10,15 @@ namespace MTSC.Common.Http.Attributes { this.Placeholder = placeholder; } + + public override string GetStringValue(HttpRouteBase route, RouteContext routeContext) + { + if (routeContext.UrlValues.TryGetValue(this.Placeholder, out var value)) + { + return value; + } + + return null; + } } } diff --git a/MTSC/Common/Http/Attributes/RouteDataBindingBaseAttribute.cs b/MTSC/Common/Http/Attributes/RouteDataBindingBaseAttribute.cs new file mode 100644 index 0000000..7357d07 --- /dev/null +++ b/MTSC/Common/Http/Attributes/RouteDataBindingBaseAttribute.cs @@ -0,0 +1,19 @@ +using MTSC.Common.Http.RoutingModules; +using System; + +namespace MTSC.Common.Http.Attributes +{ + public abstract class RouteDataBindingBaseAttribute : Attribute + { + /// + /// Perform the data binding logic and return the bindable value. + /// This value will be attached to the decorated property. + /// The value can be + /// + /// object. + /// for the current request. + /// of property to be binded. + /// Object to be binded to the decorated property. + public abstract object DataBind(HttpRouteBase route, RouteContext routeContext, Type propertyType); + } +} diff --git a/MTSC/Common/Http/Attributes/StringRouteDataBindingBaseAttribute.cs b/MTSC/Common/Http/Attributes/StringRouteDataBindingBaseAttribute.cs new file mode 100644 index 0000000..e674316 --- /dev/null +++ b/MTSC/Common/Http/Attributes/StringRouteDataBindingBaseAttribute.cs @@ -0,0 +1,63 @@ +using MTSC.Common.Http.RoutingModules; +using Newtonsoft.Json; +using System; +using System.ComponentModel; + +namespace MTSC.Common.Http.Attributes +{ + public abstract class StringRouteDataBindingBaseAttribute : RouteDataBindingBaseAttribute + { + public override sealed object DataBind(HttpRouteBase route, RouteContext routeContext, Type propertyType) + { + var stringValue = this.GetStringValue(route, routeContext); + return BindValue(propertyType, stringValue); + } + + /// + /// Returns a string value that will later be converted to the decorated property . + /// Conversion tries as well as if the property is not . + /// + /// route. + /// of the request. + /// A string value to be converted to the decorated property . + public abstract string GetStringValue(HttpRouteBase route, RouteContext routeContext); + + private object BindValue(Type propertyType, string value) + { + object finalValue = null; + if (propertyType == typeof(string)) + { + finalValue = value; + } + else if (this.TryConvertWithTypeConverter(propertyType, value, out var typeConvertedValue)) + { + finalValue = typeConvertedValue; + } + else if (this.TryConvertWithJsonConvert(propertyType, value, out var jsonConvertedValue)) + { + finalValue = jsonConvertedValue; + } + + return finalValue; + } + + private bool TryConvertWithTypeConverter(Type propertyType, string value, out object convertedValue) + { + var typeConverter = TypeDescriptor.GetConverter(propertyType); + if (typeConverter.CanConvertFrom(typeof(string))) + { + convertedValue = typeConverter.ConvertFrom(value); + return true; + } + + convertedValue = null; + return false; + } + + private bool TryConvertWithJsonConvert(Type propertyType, string value, out object convertedValue) + { + convertedValue = JsonConvert.DeserializeObject(value, propertyType); + return true; + } + } +} diff --git a/MTSC/Common/Http/RouteContext.cs b/MTSC/Common/Http/RouteContext.cs index 2f66ad8..a2a2a8f 100644 --- a/MTSC/Common/Http/RouteContext.cs +++ b/MTSC/Common/Http/RouteContext.cs @@ -9,18 +9,22 @@ namespace MTSC.Common.Http public Server Server { get; } public HttpRequest HttpRequest { get; } public ClientData Client { get; } + public Dictionary UrlValues { get; } public HttpResponse HttpResponse { get; set; } public CancellationToken CancelRequest => this.Client.CancellationToken; public Dictionary Resources { get; set; } = new(); + public RouteContext( Server server, HttpRequest httpRequest, - ClientData clientData) + ClientData clientData, + Dictionary urlValues) { this.Server = server; this.HttpRequest = httpRequest; this.Client = clientData; + this.UrlValues = urlValues; } } } diff --git a/MTSC/Exceptions/DataBindingException.cs b/MTSC/Exceptions/DataBindingException.cs index 61dfabe..d0db95b 100644 --- a/MTSC/Exceptions/DataBindingException.cs +++ b/MTSC/Exceptions/DataBindingException.cs @@ -1,24 +1,31 @@ -using System; +using MTSC.Common.Http; +using MTSC.Common.Http.Attributes; +using MTSC.Common.Http.RoutingModules; +using System; +using System.Reflection; using System.Runtime.Serialization; namespace MTSC.Exceptions { - public abstract class DataBindingException : Exception + public sealed class DataBindingException : Exception { - protected DataBindingException() - { - } + public HttpRouteBase Route { get; } + public RouteContext RouteContext { get; } + public RouteDataBindingBaseAttribute RouteDataBindingBaseAttribute { get; } + public PropertyInfo PropertyInfo { get; } - protected DataBindingException(string message) : base(message) - { - } - - protected DataBindingException(string message, Exception innerException) : base(message, innerException) - { - } - - protected DataBindingException(SerializationInfo info, StreamingContext context) : base(info, context) + public DataBindingException( + Exception innerException, + HttpRouteBase route, + RouteContext routeContext, + RouteDataBindingBaseAttribute routeDataBindingBaseAttribute, + PropertyInfo propertyInfo) + : base("Exception occurred during data binding. Check inner exception for details", innerException) { + this.Route = route; + this.RouteContext = routeContext; + this.RouteDataBindingBaseAttribute = routeDataBindingBaseAttribute; + this.PropertyInfo = propertyInfo; } } } diff --git a/MTSC/Exceptions/FromBodyDataBindingException.cs b/MTSC/Exceptions/FromBodyDataBindingException.cs deleted file mode 100644 index 2d598e1..0000000 --- a/MTSC/Exceptions/FromBodyDataBindingException.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace MTSC.Exceptions -{ - public sealed class FromBodyDataBindingException : Exception - { - public Type PropertyType { get; } - public string Value { get; } - - public FromBodyDataBindingException( - Exception innerException, - Type propertyType, - string value) - : base("Encountered exception when binding data from body", innerException) - { - this.PropertyType = propertyType; - this.Value = value; - } - } -} diff --git a/MTSC/Exceptions/FromHeadersDataBindingException.cs b/MTSC/Exceptions/FromHeadersDataBindingException.cs deleted file mode 100644 index 7ffcc25..0000000 --- a/MTSC/Exceptions/FromHeadersDataBindingException.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace MTSC.Exceptions -{ - public sealed class FromHeadersDataBindingException : Exception - { - public Type PropertyType { get; } - public string Header { get; } - public string Value { get; } - - public FromHeadersDataBindingException( - Exception innerException, - Type propertyType, - string header, - string value) - : base("Encountered exception when binding data from headers", innerException) - { - this.PropertyType = propertyType; - this.Header = header; - this.Value = value; - } - } -} diff --git a/MTSC/Exceptions/FromRouteContextResourcesDataBindingException.cs b/MTSC/Exceptions/FromRouteContextResourcesDataBindingException.cs deleted file mode 100644 index cb5c984..0000000 --- a/MTSC/Exceptions/FromRouteContextResourcesDataBindingException.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using MTSC.Common.Http; - -namespace MTSC.Exceptions -{ - public sealed class FromRouteContextResourcesDataBindingException : Exception - { - public Type PropertyType { get; } - public string Key { get; } - public RouteContext RouteContext { get; } - - public FromRouteContextResourcesDataBindingException( - Exception innerException, - Type propertyType, - string key, - RouteContext routeContext) - : base("Encountered exception when binding data from route context resources", innerException) - { - this.PropertyType = propertyType; - this.Key = key; - this.RouteContext = routeContext; - } - } -} diff --git a/MTSC/Exceptions/FromUrlDataBindingException.cs b/MTSC/Exceptions/FromUrlDataBindingException.cs deleted file mode 100644 index ffe3a32..0000000 --- a/MTSC/Exceptions/FromUrlDataBindingException.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace MTSC.Exceptions -{ - public sealed class FromUrlDataBindingException : DataBindingException - { - public Type PropertyType { get; } - public string Placeholder { get; } - public string Value { get; } - - public FromUrlDataBindingException( - Exception innerException, - Type propertyType, - string placeHolder, - string value) - : base("Encountered exception when binding data from url", innerException) - { - this.PropertyType = propertyType; - this.Placeholder = placeHolder; - this.Value = value; - } - } -} diff --git a/MTSC/MTSC.csproj b/MTSC/MTSC.csproj index 6219e01..abb42b2 100644 --- a/MTSC/MTSC.csproj +++ b/MTSC/MTSC.csproj @@ -5,13 +5,13 @@ net48;netstandard2.0;netcoreapp3.1;net5.0;net6.0 - 5.1.6 + 5.2.0 latest Alexandru-Victor Macocian MTSC Modular TCP Server and Client - 5.1.6.0 - 5.1.6.0 + 5.2.0.0 + 5.2.0.0 true AnyCPU;x64 https://github.com/AlexMacocian/MTSC diff --git a/MTSC/ServerSide/Handlers/HttpRoutingHandler.cs b/MTSC/ServerSide/Handlers/HttpRoutingHandler.cs index 7e15a3a..1d6d820 100644 --- a/MTSC/ServerSide/Handlers/HttpRoutingHandler.cs +++ b/MTSC/ServerSide/Handlers/HttpRoutingHandler.cs @@ -229,7 +229,11 @@ namespace MTSC.ServerSide.Handlers httpLogger.LogRequest(server, this, client, request); } - var routeContext = new RouteContext(server, request, client); + var routeContext = new RouteContext( + server, + request, + client, + urlValues.ToDictionary(u => u.Placeholder, u => u.Value)); foreach(var filterType in filterTypes) { var filter = server.ServiceManager.GetService(filterType) as RouteFilterAttribute; @@ -254,7 +258,7 @@ namespace MTSC.ServerSide.Handlers } - this.RouteHandleRequest(module, routeContext, filterTypes, urlValues).ContinueWith(task => + this.RouteHandleRequest(module, routeContext, filterTypes).ContinueWith(task => { this.QueueResponse(client, task.Result); }); @@ -368,12 +372,11 @@ namespace MTSC.ServerSide.Handlers private async Task RouteHandleRequest( HttpRouteBase httpRouteBase, RouteContext routeContext, - List filterTypes, - List urlValues) + List filterTypes) { try { - this.SetModuleProperties(httpRouteBase, routeContext, urlValues); + this.SetModuleProperties(httpRouteBase, routeContext); var response = await httpRouteBase.CallHandleRequest(routeContext.HttpRequest); routeContext.HttpResponse = response; foreach (var filterType in filterTypes) @@ -440,7 +443,7 @@ namespace MTSC.ServerSide.Handlers { foreach (var attribute in property.GetCustomAttributes(true)) { - if (attribute is FromUrlAttribute or FromBodyAttribute or FromHeadersAttribute) + if (attribute is RouteDataBindingBaseAttribute) { propertyAndAttributesList.Add(((Attribute)attribute, property)); } @@ -470,99 +473,29 @@ namespace MTSC.ServerSide.Handlers return false; } - private void SetModuleProperties(HttpRouteBase module, RouteContext routeContext, List urlValues) + private void SetModuleProperties(HttpRouteBase module, RouteContext routeContext) { - var propertiesAndAttributes = this.routePropertyCache[module.GetType()]; foreach ((var attribute, var propertyInfo) in this.routePropertyCache[module.GetType()]) { - if (attribute is FromUrlAttribute fromUrlAttribute) - { - var maybeValue = urlValues.Where(val => val.Placeholder == fromUrlAttribute.Placeholder).FirstOrDefault(); - if (maybeValue is not null) - { - try - { - this.TryAssignValue(propertyInfo, maybeValue.Value, module); - } - catch (Exception ex) - { - if (this.ThrowOnBindingErrors) - { - throw new FromUrlDataBindingException(ex, propertyInfo.PropertyType, maybeValue.Placeholder, maybeValue.Value); - } - } - } - } - else if (attribute is FromBodyAttribute) + if (attribute is RouteDataBindingBaseAttribute routeDataBindingBaseAttribute) { try { - this.TryAssignValue(propertyInfo, routeContext.HttpRequest.BodyString, module); + var value = routeDataBindingBaseAttribute.DataBind(module, routeContext, propertyInfo.PropertyType); + SetPropertyValue(propertyInfo, value, module); } catch (Exception ex) { if (this.ThrowOnBindingErrors) { - throw new FromBodyDataBindingException(ex, propertyInfo.PropertyType, routeContext.HttpRequest.BodyString); - } - } - } - else if (attribute is FromHeadersAttribute fromHeadersAttribute) - { - var maybeValue = routeContext.HttpRequest.Headers.Where(kvp => kvp.Key == fromHeadersAttribute.HeaderName).FirstOrDefault(); - if (maybeValue.Value is not null) - { - try - { - this.TryAssignValue(propertyInfo, maybeValue.Value, module); - } - catch (Exception ex) - { - if (this.ThrowOnBindingErrors) - { - throw new FromHeadersDataBindingException(ex, propertyInfo.PropertyType, fromHeadersAttribute.HeaderName, maybeValue.Value); - } - } - } - } - else if (attribute is FromRouteContextResourcesAttribute fromRouteContextResourcesAttribute) - { - if (routeContext.Resources.TryGetValue(fromRouteContextResourcesAttribute.ResourceKey, out var value)) - { - try - { - SetPropertyValue(propertyInfo, value, module); - } - catch (Exception ex) - { - if (this.ThrowOnBindingErrors) - { - throw new FromRouteContextResourcesDataBindingException(ex, propertyInfo.PropertyType, fromRouteContextResourcesAttribute.ResourceKey, routeContext); - } + throw new DataBindingException(ex, module, routeContext, routeDataBindingBaseAttribute, propertyInfo); } } } } } - private void TryAssignValue(PropertyInfo propertyInfo, string value, HttpRouteBase module) - { - object finalValue = null; - if (propertyInfo.PropertyType == typeof(string)) - { - finalValue = value; - } - else if (this.TryConvertWithTypeConverter(propertyInfo, value, out var typeConvertedValue)) - { - finalValue = typeConvertedValue; - } - else if (this.TryConvertWithJsonConvert(propertyInfo, value, out var jsonConvertedValue)) - { - finalValue = jsonConvertedValue; - } - - SetPropertyValue(propertyInfo, finalValue, module); - } + private static void SetPropertyValue(PropertyInfo propertyInfo, object value, HttpRouteBase module) { @@ -577,47 +510,7 @@ namespace MTSC.ServerSide.Handlers } } - private bool TryConvertWithTypeConverter(PropertyInfo propertyInfo, string value, out object convertedValue) - { - try - { - var typeConverter = TypeDescriptor.GetConverter(propertyInfo.PropertyType); - if (typeConverter.CanConvertFrom(typeof(string))) - { - convertedValue = typeConverter.ConvertFrom(value); - return true; - } - } - catch - { - if (this.ThrowOnBindingErrors is true) - { - throw; - } - } - - convertedValue = null; - return false; - } - private bool TryConvertWithJsonConvert(PropertyInfo propertyInfo, string value, out object convertedValue) - { - try - { - convertedValue = JsonConvert.DeserializeObject(value, propertyInfo.PropertyType); - return true; - } - catch - { - if (this.ThrowOnBindingErrors is true) - { - throw; - } - } - - convertedValue = null; - return false; - } private static HttpResponse NotFound404 => new() {