mirror of
https://github.com/AlexMacocian/MTSC.git
synced 2026-07-15 14:59:33 +00:00
Extend and generalize data binding logic (#14)
Support for custom data binding attributes
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using MTSC.Common.Http.RoutingModules;
|
||||
using System;
|
||||
|
||||
namespace MTSC.Common.Http.Attributes
|
||||
{
|
||||
public abstract class RouteDataBindingBaseAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Perform the data binding logic and return the bindable value.
|
||||
/// This value will be attached to the decorated property.
|
||||
/// The value can be
|
||||
/// </summary>
|
||||
/// <param name="module"><see cref="HttpRouteBase"/> object.</param>
|
||||
/// <param name="routeContext"><see cref="RouteContext"/> for the current request.</param>
|
||||
/// <param name="propertyType"><see cref="Type"/> of property to be binded.</param>
|
||||
/// <returns>Object to be binded to the decorated property.</returns>
|
||||
public abstract object DataBind(HttpRouteBase route, RouteContext routeContext, Type propertyType);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string value that will later be converted to the decorated property <see cref="Type"/>.
|
||||
/// Conversion tries <see cref="TypeConverter"/> as well as <see cref="JsonConvert"/> if the property <see cref="Type"/> is not <see cref="string"/>.
|
||||
/// </summary>
|
||||
/// <param name="module"><see cref="HttpRouteBase"/> route.</param>
|
||||
/// <param name="routeContext"><see cref="RouteContext"/> of the request.</param>
|
||||
/// <returns>A string value to be converted to the decorated property <see cref="Type"/>.</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,18 +9,22 @@ namespace MTSC.Common.Http
|
||||
public Server Server { get; }
|
||||
public HttpRequest HttpRequest { get; }
|
||||
public ClientData Client { get; }
|
||||
public Dictionary<string, string> UrlValues { get; }
|
||||
public HttpResponse HttpResponse { get; set; }
|
||||
public CancellationToken CancelRequest => this.Client.CancellationToken;
|
||||
public Dictionary<string, object> Resources { get; set; } = new();
|
||||
|
||||
|
||||
public RouteContext(
|
||||
Server server,
|
||||
HttpRequest httpRequest,
|
||||
ClientData clientData)
|
||||
ClientData clientData,
|
||||
Dictionary<string, string> urlValues)
|
||||
{
|
||||
this.Server = server;
|
||||
this.HttpRequest = httpRequest;
|
||||
this.Client = clientData;
|
||||
this.UrlValues = urlValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -5,13 +5,13 @@
|
||||
<TargetFrameworks>net48;netstandard2.0;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<Version>5.1.6</Version>
|
||||
<Version>5.2.0</Version>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Authors>Alexandru-Victor Macocian</Authors>
|
||||
<Product>MTSC</Product>
|
||||
<Description>Modular TCP Server and Client</Description>
|
||||
<AssemblyVersion>5.1.6.0</AssemblyVersion>
|
||||
<FileVersion>5.1.6.0</FileVersion>
|
||||
<AssemblyVersion>5.2.0.0</AssemblyVersion>
|
||||
<FileVersion>5.2.0.0</FileVersion>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<PackageProjectUrl>https://github.com/AlexMacocian/MTSC</PackageProjectUrl>
|
||||
|
||||
@@ -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<HttpResponse> RouteHandleRequest(
|
||||
HttpRouteBase httpRouteBase,
|
||||
RouteContext routeContext,
|
||||
List<Type> filterTypes,
|
||||
List<UrlValue> urlValues)
|
||||
List<Type> 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<UrlValue> 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()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user