@@ -20,7 +20,7 @@ namespace Cap.Consistency.Internal | |||
} | |||
public ConsumerExecutorDescriptor SelectBestCandidate(string key, IReadOnlyList<ConsumerExecutorDescriptor> executeDescriptor) { | |||
return executeDescriptor.FirstOrDefault(x => x.Topic.Name == key); | |||
return executeDescriptor.FirstOrDefault(x => x.Attribute.Name == key); | |||
} | |||
public IReadOnlyList<ConsumerExecutorDescriptor> SelectCandidates(TopicRouteContext context) { | |||
@@ -50,7 +50,7 @@ namespace Cap.Consistency.Internal | |||
) { | |||
var descriptor = new ConsumerExecutorDescriptor(); | |||
descriptor.Topic = new TopicInfo(attr.Name); | |||
descriptor.Attribute = attr; | |||
descriptor.MethodInfo = methodInfo; | |||
descriptor.ImplTypeInfo = implType; | |||
@@ -4,6 +4,7 @@ using System.Reflection; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using Cap.Consistency.Abstractions; | |||
using Cap.Consistency.Abstractions.ModelBinding; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Microsoft.Extensions.Logging; | |||
using Newtonsoft.Json; | |||
@@ -14,15 +15,18 @@ namespace Cap.Consistency.Internal | |||
{ | |||
protected readonly ILogger _logger; | |||
protected readonly IServiceProvider _serviceProvider; | |||
private readonly IModelBinder _modelBinder; | |||
private readonly ObjectMethodExecutor _executor; | |||
protected readonly ConsumerContext _consumerContext; | |||
public ConsumerInvoker(ILogger logger, | |||
IServiceProvider serviceProvider, | |||
IModelBinder modelBinder, | |||
ConsumerContext consumerContext) { | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
_serviceProvider = serviceProvider; | |||
_modelBinder = modelBinder; | |||
_consumerContext = consumerContext ?? throw new ArgumentNullException(nameof(consumerContext)); | |||
_executor = ObjectMethodExecutor.Create(_consumerContext.ConsumerDescriptor.MethodInfo, | |||
_consumerContext.ConsumerDescriptor.ImplTypeInfo); | |||
@@ -33,20 +37,26 @@ namespace Cap.Consistency.Internal | |||
try { | |||
using (_logger.BeginScope("consumer invoker begin")) { | |||
_logger.LogDebug("Executing consumer Topic: {0}", _consumerContext.ConsumerDescriptor.Topic); | |||
_logger.LogDebug("Executing consumer Topic: {0}", _consumerContext.ConsumerDescriptor.Attribute); | |||
try { | |||
var obj = ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, _consumerContext.ConsumerDescriptor.ImplTypeInfo.AsType()); | |||
var bodyString = Encoding.UTF8.GetString(_consumerContext.DeliverMessage.Body); | |||
var firstParameter = _executor.MethodParameters[0]; | |||
object firstParameterObj = null; | |||
if (firstParameter != null) { | |||
firstParameterObj = JsonConvert.DeserializeObject(bodyString, firstParameter.ParameterType); | |||
} | |||
_executor.Execute(obj, firstParameterObj); | |||
if (_executor.MethodParameters.Length > 0) { | |||
var firstParameter = _executor.MethodParameters[0]; | |||
var bindingContext = ModelBindingContext.CreateBindingContext(bodyString, | |||
firstParameter.Name, firstParameter.ParameterType); | |||
_modelBinder.BindModelAsync(bindingContext); | |||
_executor.Execute(obj, bindingContext.Result); | |||
} | |||
else { | |||
_executor.Execute(obj); | |||
} | |||
return Task.CompletedTask; | |||
} | |||
finally { | |||
@@ -2,6 +2,7 @@ | |||
using System.Collections.Generic; | |||
using System.Text; | |||
using Cap.Consistency.Abstractions; | |||
using Cap.Consistency.Abstractions.ModelBinding; | |||
using Cap.Consistency.Infrastructure; | |||
using Microsoft.Extensions.Logging; | |||
@@ -11,12 +12,15 @@ namespace Cap.Consistency.Internal | |||
{ | |||
private readonly ILogger _logger; | |||
private readonly IServiceProvider _serviceProvider; | |||
private readonly IModelBinder _modelBinder; | |||
public ConsumerInvokerFactory( | |||
ILoggerFactory loggerFactory, | |||
IModelBinder modelBinder, | |||
IServiceProvider serviceProvider) { | |||
_logger = loggerFactory.CreateLogger<ConsumerInvokerFactory>(); | |||
_modelBinder = modelBinder; | |||
_serviceProvider = serviceProvider; | |||
} | |||
@@ -24,7 +28,7 @@ namespace Cap.Consistency.Internal | |||
var context = new ConsumerInvokerContext(consumerContext); | |||
context.Result = new ConsumerInvoker(_logger, _serviceProvider, consumerContext); | |||
context.Result = new ConsumerInvoker(_logger, _serviceProvider, _modelBinder, consumerContext); | |||
return context.Result; | |||
} | |||
@@ -0,0 +1,48 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq.Expressions; | |||
using System.Reflection; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using Cap.Consistency.Abstractions.ModelBinding; | |||
using Newtonsoft.Json; | |||
namespace Cap.Consistency.Internal | |||
{ | |||
public class DefaultModelBinder : IModelBinder | |||
{ | |||
private Func<object> _modelCreator; | |||
public Task BindModelAsync(ModelBindingContext bindingContext) { | |||
if (bindingContext.Model == null) { | |||
bindingContext.Model = CreateModel(bindingContext); | |||
} | |||
bindingContext.Result = JsonConvert.DeserializeObject(bindingContext.Values, bindingContext.ModelType); | |||
return Task.CompletedTask; | |||
} | |||
protected virtual object CreateModel(ModelBindingContext bindingContext) { | |||
if (bindingContext == null) { | |||
throw new ArgumentNullException(nameof(bindingContext)); | |||
} | |||
if (_modelCreator == null) { | |||
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(); | |||
} | |||
} | |||
} |
@@ -18,13 +18,13 @@ namespace Cap.Consistency.Internal | |||
public ConcurrentDictionary<string, ConsumerExecutorDescriptor> GetCandidatesMethods(TopicRouteContext routeContext) { | |||
if (Entries == null) { | |||
if (Entries.Count == 0) { | |||
var executorCollection = _selector.SelectCandidates(routeContext); | |||
foreach (var item in executorCollection) { | |||
Entries.GetOrAdd(item.Topic.Name, item); | |||
Entries.GetOrAdd(item.Attribute.Name, item); | |||
} | |||
} | |||
return Entries; | |||
@@ -0,0 +1,106 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Reflection; | |||
using System.Text; | |||
namespace Cap.Consistency.Internal | |||
{ | |||
/// <summary> | |||
/// Provides access to the combined list of attributes associated a <see cref="Type"/> or property. | |||
/// </summary> | |||
public class ModelAttributes | |||
{ | |||
/// <summary> | |||
/// Creates a new <see cref="ModelAttributes"/> for a <see cref="Type"/>. | |||
/// </summary> | |||
/// <param name="typeAttributes">The set of attributes for the <see cref="Type"/>.</param> | |||
public ModelAttributes(IEnumerable<object> typeAttributes) { | |||
if (typeAttributes == null) { | |||
throw new ArgumentNullException(nameof(typeAttributes)); | |||
} | |||
Attributes = typeAttributes.ToArray(); | |||
TypeAttributes = Attributes; | |||
} | |||
/// <summary> | |||
/// Creates a new <see cref="ModelAttributes"/> for a property. | |||
/// </summary> | |||
/// <param name="propertyAttributes">The set of attributes for the property.</param> | |||
/// <param name="typeAttributes"> | |||
/// The set of attributes for the property's <see cref="Type"/>. See <see cref="PropertyInfo.PropertyType"/>. | |||
/// </param> | |||
public ModelAttributes(IEnumerable<object> propertyAttributes, IEnumerable<object> typeAttributes) { | |||
if (propertyAttributes == null) { | |||
throw new ArgumentNullException(nameof(propertyAttributes)); | |||
} | |||
if (typeAttributes == null) { | |||
throw new ArgumentNullException(nameof(typeAttributes)); | |||
} | |||
PropertyAttributes = propertyAttributes.ToArray(); | |||
TypeAttributes = typeAttributes.ToArray(); | |||
Attributes = PropertyAttributes.Concat(TypeAttributes).ToArray(); | |||
} | |||
/// <summary> | |||
/// Gets the set of all attributes. If this instance represents the attributes for a property, the attributes | |||
/// on the property definition are before those on the property's <see cref="Type"/>. | |||
/// </summary> | |||
public IReadOnlyList<object> Attributes { get; } | |||
/// <summary> | |||
/// Gets the set of attributes on the property, or <c>null</c> if this instance represents the attributes | |||
/// for a <see cref="Type"/>. | |||
/// </summary> | |||
public IReadOnlyList<object> PropertyAttributes { get; } | |||
/// <summary> | |||
/// Gets the set of attributes on the <see cref="Type"/>. If this instance represents a property, | |||
/// then <see cref="TypeAttributes"/> contains attributes retrieved from | |||
/// <see cref="PropertyInfo.PropertyType"/>. | |||
/// </summary> | |||
public IReadOnlyList<object> TypeAttributes { get; } | |||
/// <summary> | |||
/// Gets the attributes for the given <paramref name="property"/>. | |||
/// </summary> | |||
/// <param name="type">The <see cref="Type"/> in which caller found <paramref name="property"/>. | |||
/// </param> | |||
/// <param name="property">A <see cref="PropertyInfo"/> for which attributes need to be resolved. | |||
/// </param> | |||
/// <returns>A <see cref="ModelAttributes"/> instance with the attributes of the property.</returns> | |||
public static ModelAttributes GetAttributesForProperty(Type type, PropertyInfo property) { | |||
if (type == null) { | |||
throw new ArgumentNullException(nameof(type)); | |||
} | |||
if (property == null) { | |||
throw new ArgumentNullException(nameof(property)); | |||
} | |||
var propertyAttributes = property.GetCustomAttributes(); | |||
var typeAttributes = property.PropertyType.GetTypeInfo().GetCustomAttributes(); | |||
return new ModelAttributes(propertyAttributes, typeAttributes); | |||
} | |||
/// <summary> | |||
/// Gets the attributes for the given <paramref name="type"/>. | |||
/// </summary> | |||
/// <param name="type">The <see cref="Type"/> for which attributes need to be resolved. | |||
/// </param> | |||
/// <returns>A <see cref="ModelAttributes"/> instance with the attributes of the <see cref="Type"/>.</returns> | |||
public static ModelAttributes GetAttributesForType(Type type) { | |||
if (type == null) { | |||
throw new ArgumentNullException(nameof(type)); | |||
} | |||
var attributes = type.GetTypeInfo().GetCustomAttributes(); | |||
return new ModelAttributes(attributes); | |||
} | |||
} | |||
} |
@@ -1,36 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Reflection; | |||
using System.Threading.Tasks; | |||
namespace Cap.Consistency | |||
{ | |||
public class TopicInfo | |||
{ | |||
public TopicInfo(string topicName) : this(topicName, 0) {} | |||
public TopicInfo(string topicName, int partition) : this(topicName, partition, 0) {} | |||
public TopicInfo(string topicName, int partition, long offset) { | |||
Name = topicName; | |||
Offset = offset; | |||
Partition = partition; | |||
} | |||
public string Name { get; } | |||
public int Partition { get; } | |||
public long Offset { get; } | |||
public bool IsPartition { get { return Partition == 0; } } | |||
public bool IsOffset { get { return Offset == 0; } } | |||
public override string ToString() { | |||
return Name; | |||
} | |||
} | |||
} |