Commit 297cecfa authored by yangxiaodong's avatar yangxiaodong

fix issue #17.

parent 31b0c8c7
......@@ -7,15 +7,6 @@ namespace DotNetCore.CAP.Abstractions.ModelBinding
/// </summary>
public interface IModelBinder
{
/// <summary>
/// Attempts to bind a model.
/// </summary>
/// <param name="bindingContext">The <see cref="ModelBindingContext"/>.</param>
/// <returns>
/// <para>
/// A <see cref="Task"/> which will complete when the model binding process completes.
/// </para>
/// </returns>
Task BindModelAsync(ModelBindingContext bindingContext);
Task<ModelBindingResult> BindModelAsync(string content);
}
}
\ No newline at end of file
using System;
using Microsoft.Extensions.Primitives;
namespace DotNetCore.CAP.Abstractions.ModelBinding
{
/// <summary>
/// A context that contains operating information for model binding and validation.
/// </summary>
public class ModelBindingContext
{
/// <summary>
/// Gets or sets the model value for the current operation.
/// </summary>
/// <remarks>
/// The <see cref="Model"/> will typically be set for a binding operation that works
/// against a pre-existing model object to update certain properties.
/// </remarks>
public object Model { get; set; }
/// <summary>
/// Gets or sets the name of the model.
/// </summary>
public string ModelName { get; set; }
/// <summary>
/// Gets or sets the type of the model.
/// </summary>
public Type ModelType { get; set; }
/// <summary>
/// Gets or sets the values of the model.
/// </summary>
public StringValues Values { get; set; }
/// <summary>
/// <para>
/// Gets or sets a result which represents the result of the model binding process.
/// </para>
/// </summary>
public object Result { get; set; }
/// <summary>
/// Creates a new <see cref="ModelBindingContext"/> for top-level model binding operation.
/// </summary>
public static ModelBindingContext CreateBindingContext(string values, string modelName, Type modelType)
{
return new ModelBindingContext()
{
ModelName = modelName,
ModelType = modelType,
Values = values
};
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Text;
namespace DotNetCore.CAP.Abstractions.ModelBinding
{
/// <summary>
/// Contains the result of model binding.
/// </summary>
public struct ModelBindingResult
{
/// <summary>
/// Creates a <see cref="ModelBindingResult"/> representing a failed model binding operation.
/// </summary>
/// <returns>A <see cref="ModelBindingResult"/> representing a failed model binding operation.</returns>
public static ModelBindingResult Failed()
{
return new ModelBindingResult(model: null, isSuccess: false);
}
/// <summary>
/// Creates a <see cref="ModelBindingResult"/> representing a successful model binding operation.
/// </summary>
/// <param name="model">The model value. May be <c>null.</c></param>
/// <returns>A <see cref="ModelBindingResult"/> representing a successful model bind.</returns>
public static ModelBindingResult Success(object model)
{
return new ModelBindingResult(model, isSuccess: true);
}
private ModelBindingResult(object model, bool isSuccess)
{
Model = model;
IsSuccess = isSuccess;
}
/// <summary>
/// Gets the model associated with this context.
/// </summary>
public object Model { get; }
public bool IsSuccess { get; }
public override string ToString()
{
if (IsSuccess)
{
return $"Success '{Model}'";
}
else
{
return $"Failed";
}
}
}
}
......@@ -35,7 +35,7 @@ namespace Microsoft.Extensions.DependencyInjection
AddSubscribeServices(services);
services.TryAddSingleton<IConsumerServiceSelector, DefaultConsumerServiceSelector>();
services.TryAddSingleton<IModelBinder, DefaultModelBinder>();
services.TryAddSingleton<IModelBinderFactory, ModelBinderFactory>();
services.TryAddSingleton<IConsumerInvokerFactory, ConsumerInvokerFactory>();
services.TryAddSingleton<MethodMatcherCache>();
......
......@@ -10,15 +10,15 @@ namespace DotNetCore.CAP.Internal
{
private readonly ILogger _logger;
private readonly IServiceProvider _serviceProvider;
private readonly IModelBinder _modelBinder;
private readonly IModelBinderFactory _modelBinderFactory;
public ConsumerInvokerFactory(
ILoggerFactory loggerFactory,
IModelBinder modelBinder,
IModelBinderFactory modelBinderFactory,
IServiceProvider serviceProvider)
{
_logger = loggerFactory.CreateLogger<ConsumerInvokerFactory>();
_modelBinder = modelBinder;
_modelBinderFactory = modelBinderFactory;
_serviceProvider = serviceProvider;
}
......@@ -26,7 +26,7 @@ namespace DotNetCore.CAP.Internal
{
var context = new ConsumerInvokerContext(consumerContext)
{
Result = new DefaultConsumerInvoker(_logger, _serviceProvider, _modelBinder, consumerContext)
Result = new DefaultConsumerInvoker(_logger, _serviceProvider, _modelBinderFactory, consumerContext)
};
return context.Result;
......
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace DotNetCore.CAP.Internal
{
internal struct HashCodeCombiner
{
private long _combinedHash64;
public int CombinedHash
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get { return _combinedHash64.GetHashCode(); }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private HashCodeCombiner(long seed)
{
_combinedHash64 = seed;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(IEnumerable e)
{
if (e == null)
{
Add(0);
}
else
{
var count = 0;
foreach (object o in e)
{
Add(o);
count++;
}
Add(count);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator int(HashCodeCombiner self)
{
return self.CombinedHash;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(int i)
{
_combinedHash64 = ((_combinedHash64 << 5) + _combinedHash64) ^ i;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(string s)
{
var hashCode = (s != null) ? s.GetHashCode() : 0;
Add(hashCode);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(object o)
{
var hashCode = (o != null) ? o.GetHashCode() : 0;
Add(hashCode);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add<TValue>(TValue value, IEqualityComparer<TValue> comparer)
{
var hashCode = value != null ? comparer.GetHashCode(value) : 0;
Add(hashCode);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static HashCodeCombiner Start()
{
return new HashCodeCombiner(0x1505L);
}
}
}
using System;
using System.Threading.Tasks;
using DotNetCore.CAP.Abstractions;
using DotNetCore.CAP.Abstractions.ModelBinding;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
......@@ -11,16 +10,16 @@ namespace DotNetCore.CAP.Internal
{
private readonly ILogger _logger;
private readonly IServiceProvider _serviceProvider;
private readonly IModelBinder _modelBinder;
private readonly IModelBinderFactory _modelBinderFactory;
private readonly ConsumerContext _consumerContext;
private readonly ObjectMethodExecutor _executor;
public DefaultConsumerInvoker(ILogger logger,
IServiceProvider serviceProvider,
IModelBinder modelBinder,
IModelBinderFactory modelBinderFactory,
ConsumerContext consumerContext)
{
_modelBinder = modelBinder;
_modelBinderFactory = modelBinderFactory;
_serviceProvider = serviceProvider;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
......@@ -29,7 +28,7 @@ namespace DotNetCore.CAP.Internal
_consumerContext.ConsumerDescriptor.ImplTypeInfo);
}
public Task InvokeAsync()
public async Task InvokeAsync()
{
using (_logger.BeginScope("consumer invoker begin"))
{
......@@ -43,18 +42,21 @@ namespace DotNetCore.CAP.Internal
if (_executor.MethodParameters.Length > 0)
{
var firstParameter = _executor.MethodParameters[0];
var bindingContext = ModelBindingContext.CreateBindingContext(value,
firstParameter.Name, firstParameter.ParameterType);
_modelBinder.BindModelAsync(bindingContext);
_executor.Execute(obj, bindingContext.Result);
var binder = _modelBinderFactory.CreateBinder(firstParameter);
var result = await binder.BindModelAsync(value);
if (result.IsSuccess)
{
_executor.Execute(obj, result.Model);
}
else
{
_logger.LogWarning($"Parameters:{firstParameter.Name} bind failed!");
}
}
else
{
_executor.Execute(obj);
}
return Task.CompletedTask;
}
}
}
......
using System;
using System.Reflection;
using System.Threading.Tasks;
using DotNetCore.CAP.Abstractions.ModelBinding;
using DotNetCore.CAP.Infrastructure;
namespace DotNetCore.CAP.Internal
{
public class ComplexTypeModelBinder : IModelBinder
{
private readonly ParameterInfo _parameterInfo;
public ComplexTypeModelBinder(ParameterInfo parameterInfo)
{
_parameterInfo = parameterInfo;
}
public Task<ModelBindingResult> BindModelAsync(string content)
{
try
{
var type = _parameterInfo.ParameterType;
var value = Helper.FromJson(content, type);
return Task.FromResult(ModelBindingResult.Success(value));
}
catch (Exception)
{
return Task.FromResult(ModelBindingResult.Failed());
}
}
}
}
\ No newline at end of file
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using DotNetCore.CAP.Abstractions.ModelBinding;
using DotNetCore.CAP.Infrastructure;
namespace DotNetCore.CAP.Internal
{
public class DefaultModelBinder : IModelBinder
{
private Func<object> _modelCreator;
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext.Model == null)
{
bindingContext.Model = CreateModel(bindingContext);
}
bindingContext.Result = Helper.FromJson(bindingContext.Values, bindingContext.ModelType);
return Task.CompletedTask;
}
protected virtual object CreateModel(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
if (_modelCreator != null) return _modelCreator();
var modelTypeInfo = bindingContext.ModelType.GetTypeInfo();
if (modelTypeInfo.IsAbstract || modelTypeInfo.GetConstructor(Type.EmptyTypes) == null)
{
throw new InvalidOperationException();
}
_modelCreator = Expression
.Lambda<Func<object>>(Expression.New(bindingContext.ModelType))
.Compile();
return _modelCreator();
}
}
}
\ No newline at end of file
using System;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using System.Threading.Tasks;
using DotNetCore.CAP.Abstractions.ModelBinding;
namespace DotNetCore.CAP.Internal
{
public class SimpleTypeModelBinder : IModelBinder
{
private readonly ParameterInfo _parameterInfo;
private readonly TypeConverter _typeConverter;
public SimpleTypeModelBinder(ParameterInfo parameterInfo)
{
_parameterInfo = parameterInfo ?? throw new ArgumentNullException(nameof(parameterInfo));
_typeConverter = TypeDescriptor.GetConverter(parameterInfo.ParameterType);
}
public Task<ModelBindingResult> BindModelAsync(string content)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
var parameterType = _parameterInfo.ParameterType;
object model;
if (parameterType == typeof(string))
{
if (string.IsNullOrWhiteSpace(content))
{
model = null;
}
else
{
model = content;
}
}
else if (string.IsNullOrWhiteSpace(content))
{
// Other than the StringConverter, converters Trim() the value then throw if the result is empty.
model = null;
}
else
{
model = _typeConverter.ConvertFrom(
context: null,
culture: CultureInfo.CurrentCulture,
value: content);
}
if (model == null && !IsReferenceOrNullableType(parameterType))
{
return Task.FromResult(ModelBindingResult.Failed());
}
else
{
return Task.FromResult(ModelBindingResult.Success(model));
}
}
private bool IsReferenceOrNullableType(Type type)
{
var isNullableValueType = Nullable.GetUnderlyingType(type) != null;
return !type.GetTypeInfo().IsValueType || isNullableValueType;
}
}
}
using System.Reflection;
using DotNetCore.CAP.Abstractions.ModelBinding;
namespace DotNetCore.CAP.Internal
{
public interface IModelBinderFactory
{
IModelBinder CreateBinder(ParameterInfo parameter);
}
}
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.CompilerServices;
using DotNetCore.CAP.Abstractions.ModelBinding;
namespace DotNetCore.CAP.Internal
{
/// <summary>
/// A factory for <see cref="IModelBinder"/> instances.
/// </summary>
public class ModelBinderFactory : IModelBinderFactory
{
private readonly ConcurrentDictionary<Key, IModelBinder> _cache =
new ConcurrentDictionary<Key, IModelBinder>();
public IModelBinder CreateBinder(ParameterInfo parameter)
{
if (parameter == null)
{
throw new ArgumentNullException(nameof(parameter));
}
object token = parameter;
var binder = CreateBinderCoreCached(parameter, token);
if (binder == null)
{
throw new InvalidOperationException("Format Could Not Create IModelBinder");
}
return binder;
}
private IModelBinder CreateBinderCoreCached(ParameterInfo parameterInfo, object token)
{
IModelBinder binder;
if (TryGetCachedBinder(parameterInfo, token, out binder))
{
return binder;
}
var type = parameterInfo.ParameterType;
var isComplexType = !TypeDescriptor.GetConverter(type).CanConvertFrom(typeof(string));
if (!isComplexType)
{
binder = new SimpleTypeModelBinder(parameterInfo);
}
else
{
binder = new ComplexTypeModelBinder(parameterInfo);
}
AddToCache(parameterInfo, token, binder);
return binder;
}
private void AddToCache(ParameterInfo info, object cacheToken, IModelBinder binder)
{
if (cacheToken == null)
{
return;
}
_cache.TryAdd(new Key(info, cacheToken), binder);
}
private bool TryGetCachedBinder(ParameterInfo info, object cacheToken, out IModelBinder binder)
{
if (cacheToken == null)
{
binder = null;
return false;
}
return _cache.TryGetValue(new Key(info, cacheToken), out binder);
}
private struct Key : IEquatable<Key>
{
private readonly ParameterInfo _metadata;
private readonly object _token;
public Key(ParameterInfo metadata, object token)
{
_metadata = metadata;
_token = token;
}
public bool Equals(Key other)
{
return _metadata.Equals(other._metadata) && object.ReferenceEquals(_token, other._token);
}
public override bool Equals(object obj)
{
var other = obj as Key?;
return other.HasValue && Equals(other.Value);
}
public override int GetHashCode()
{
var hash = new HashCodeCombiner();
hash.Add(_metadata);
hash.Add(RuntimeHelpers.GetHashCode(_token));
return hash;
}
public override string ToString()
{
return $"{_token} (Property: '{_metadata.Name}' Type: '{_metadata.ParameterType.Name}')";
}
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment