Browse Source

1

undefined
Savorboard 7 years ago
parent
commit
276b8ee8d9
31 changed files with 602 additions and 539 deletions
  1. +2
    -2
      src/Cap.Consistency.EntityFrameworkCore/Cap.Consistency.EntityFrameworkCore.csproj
  2. +21
    -3
      src/Cap.Consistency.Server/Cap.Consistency.Server.csproj
  3. +2
    -0
      src/Cap.Consistency/Abstractions/ConsumerContext.cs
  4. +1
    -0
      src/Cap.Consistency/Abstractions/ConsumerInvokerContext.cs
  5. +0
    -10
      src/Cap.Consistency/BrokerOptions.cs
  6. +24
    -0
      src/Cap.Consistency/Builder/ConsistencyMiddleware.cs
  7. +0
    -31
      src/Cap.Consistency/BuilderExtensions.cs
  8. +5
    -5
      src/Cap.Consistency/Cap.Consistency.csproj
  9. +0
    -81
      src/Cap.Consistency/ConsistencyBuilder.cs
  10. +0
    -7
      src/Cap.Consistency/ConsistencyMarkerService.cs
  11. +0
    -147
      src/Cap.Consistency/ConsistencyMessageManager.cs
  12. +0
    -26
      src/Cap.Consistency/ConsistencyOptions.cs
  13. +11
    -14
      src/Cap.Consistency/Consumer/ConsumerHandler.cs
  14. +2
    -5
      src/Cap.Consistency/Consumer/IConsumerHandler.cs
  15. +0
    -1
      src/Cap.Consistency/Consumer/Kafka/RdKafkaClient.cs
  16. +21
    -0
      src/Cap.Consistency/Extensions/BuilderExtensions.cs
  17. +8
    -0
      src/Cap.Consistency/GlobalSuppressions.cs
  18. +0
    -55
      src/Cap.Consistency/IConsistencyMessageStore.cs
  19. +1
    -1
      src/Cap.Consistency/Infrastructure/IConsumerExcutorSelector.cs
  20. +139
    -0
      src/Cap.Consistency/Internal/ConsumerInvoker.cs
  21. +33
    -0
      src/Cap.Consistency/Internal/ConsumerMethodExecutor.cs
  22. +285
    -0
      src/Cap.Consistency/Internal/ObjectMethodExecutor.cs
  23. +7
    -2
      src/Cap.Consistency/KafkaConsistency.cs
  24. +0
    -14
      src/Cap.Consistency/QMessageAttribute.cs
  25. +0
    -48
      src/Cap.Consistency/QMessageFinder.cs
  26. +0
    -17
      src/Cap.Consistency/QMessageMethodInfo.cs
  27. +12
    -0
      src/Cap.Consistency/Routing/ITopicRoute.cs
  28. +7
    -2
      src/Cap.Consistency/Routing/TopicRouteContext.cs
  29. +0
    -47
      src/Cap.Consistency/ServiceCollectionExtensions.cs
  30. +14
    -14
      test/Cap.Consistency.EntityFrameworkCore.Test/Cap.Consistency.EntityFrameworkCore.Test.csproj
  31. +7
    -7
      test/Cap.Consistency.Test/Cap.Consistency.Test.csproj

+ 2
- 2
src/Cap.Consistency.EntityFrameworkCore/Cap.Consistency.EntityFrameworkCore.csproj View File

@@ -16,8 +16,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="1.1.2" />
<PackageReference Include="System.ComponentModel.TypeConverter" Version="4.3.0" />
</ItemGroup>



+ 21
- 3
src/Cap.Consistency.Server/Cap.Consistency.Server.csproj View File

@@ -16,9 +16,27 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="1.1.1" />
<Compile Remove="x64\**" />
<Compile Remove="x86\**" />
<Content Remove="x64\**" />
<Content Remove="x86\**" />
<EmbeddedResource Remove="x64\**" />
<EmbeddedResource Remove="x86\**" />
<None Remove="x64\**" />
<None Remove="x86\**" />
</ItemGroup>

<ItemGroup>
<Content Remove="C:\Users\yangxiaodong\.nuget\packages\rdkafka.internal.librdkafka\0.9.2-ci-28\build\..\runtimes\win7-x64\native\librdkafka.dll" />
<Content Remove="C:\Users\yangxiaodong\.nuget\packages\rdkafka.internal.librdkafka\0.9.2-ci-28\build\..\runtimes\win7-x64\native\zlib.dll" />
<Content Remove="C:\Users\yangxiaodong\.nuget\packages\rdkafka.internal.librdkafka\0.9.2-ci-28\build\..\runtimes\win7-x86\native\librdkafka.dll" />
<Content Remove="C:\Users\yangxiaodong\.nuget\packages\rdkafka.internal.librdkafka\0.9.2-ci-28\build\..\runtimes\win7-x86\native\zlib.dll" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Options" Version="1.1.2" />
<PackageReference Include="RdKafka" Version="0.9.2-ci-189" />
</ItemGroup>



+ 2
- 0
src/Cap.Consistency/Abstractions/ConsumerContext.cs View File

@@ -13,6 +13,8 @@ namespace Cap.Consistency.Abstractions
}

public ConsumerExecutorDescriptor ConsumerDescriptor { get; set; }


}
}

+ 1
- 0
src/Cap.Consistency/Abstractions/ConsumerInvokerContext.cs View File

@@ -8,6 +8,7 @@ namespace Cap.Consistency.Abstractions
{
public ConsumerInvokerContext(ConsumerContext consumerContext) {
ConsumerContext = consumerContext ?? throw new ArgumentNullException(nameof(consumerContext));

}

public ConsumerContext ConsumerContext { get; set; }


+ 0
- 10
src/Cap.Consistency/BrokerOptions.cs View File

@@ -1,10 +0,0 @@
namespace Cap.Consistency
{
public class BrokerOptions
{
public string HostName { get; set; }


}
}

+ 24
- 0
src/Cap.Consistency/Builder/ConsistencyMiddleware.cs View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Cap.Consistency.Routing;

namespace Cap.Consistency.Builder
{
public class ConsistencyMiddleware
{
private readonly ITopicRoute _router;

public ConsistencyMiddleware(ITopicRoute router) {
_router = router;
}

public async Task Invoke() {
var context = new TopicRouteContext();
context.Routes.Add(_router);

await _router.RouteAsync(context);
}
}
}

+ 0
- 31
src/Cap.Consistency/BuilderExtensions.cs View File

@@ -1,31 +0,0 @@
using System;
using Cap.Consistency;
using Microsoft.Extensions.DependencyInjection;

// ReSharper disable once CheckNamespace
namespace Microsoft.AspNetCore.Builder
{
/// <summary>
/// Consistence extensions for <see cref="IApplicationBuilder"/>
/// </summary>
public static class BuilderExtensions
{
/// <summary>
/// Enables Consistence for the current application
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/> instance this method extends.</param>
/// <returns>The <see cref="IApplicationBuilder"/> instance this method extends.</returns>
public static IApplicationBuilder UseConsistency(this IApplicationBuilder app) {
if (app == null) {
throw new ArgumentNullException(nameof(app));
}

var marker = app.ApplicationServices.GetService<ConsistencyMarkerService>();
if (marker == null) {
throw new InvalidOperationException("Add Consistency must be called on the service collection.");
}

return app;
}
}
}

+ 5
- 5
src/Cap.Consistency/Cap.Consistency.csproj View File

@@ -13,11 +13,11 @@

<ItemGroup>
<PackageReference Include="Confluent.Kafka" Version="0.9.5" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Options" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
</ItemGroup>



+ 0
- 81
src/Cap.Consistency/ConsistencyBuilder.cs View File

@@ -1,81 +0,0 @@
using System;
using System.Reflection;
using System.Collections.Concurrent;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Generic;

namespace Cap.Consistency
{
/// <summary>
/// Helper functions for configuring consistency services.
/// </summary>
public class ConsistencyBuilder
{
/// <summary>
/// Creates a new instance of <see cref="ConsistencyBuilder"/>.
/// </summary>
/// <param name="message">The <see cref="Type"/> to use for the message.</param>
/// <param name="service">The <see cref="IServiceCollection"/> to attach to.</param>
public ConsistencyBuilder(Type message, IServiceCollection service) {
MessageType = message;
Services = service;
}

/// <summary>
/// Gets the <see cref="IServiceCollection"/> services are attached to.
/// </summary>
/// <value>
/// The <see cref="IServiceCollection"/> services are attached to.
/// </value>
public IServiceCollection Services { get; private set; }

/// <summary>
/// Gets the <see cref="Type"/> used for messages.
/// </summary>
/// <value>
/// The <see cref="Type"/> used for messages.
/// </value>
public Type MessageType { get; private set; }

/// <summary>
/// Adds a <see cref="IConsistencyMessageStore{TMessage}"/> for the <seealso cref="MessageType"/>.
/// </summary>
/// <typeparam name="T">The role type held in the store.</typeparam>
/// <returns>The current <see cref="ConsistencyBuilder"/> instance.</returns>
public virtual ConsistencyBuilder AddMessageStore<T>() where T : class {
return AddScoped(typeof(IConsistencyMessageStore<>).MakeGenericType(MessageType), typeof(T));
}

public virtual ConsistencyBuilder AddMessageMethodTable() {
var provider = Services.BuildServiceProvider();

var finder = provider.GetRequiredService<QMessageFinder>();
finder.GetQMessageMethods(Services);
return null;
// Services.AddSingleton(serviceType, concreteType);
// return Add(typeof(IConsistencyMessageStore<>).MakeGenericType(MessageType), typeof(T));
}

/// <summary>
/// Adds a <see cref="ConsistencyMessageManager{TUser}"/> for the <seealso cref="MessageType"/>.
/// </summary>
/// <typeparam name="TMessageManager">The type of the message manager to add.</typeparam>
/// <returns>The current <see cref="ConsistencyBuilder"/> instance.</returns>
public virtual ConsistencyBuilder AddConsistencyMessageManager<TMessageManager>() where TMessageManager : class {
var messageManagerType = typeof(ConsistencyMessageManager<>).MakeGenericType(MessageType);
var customType = typeof(TMessageManager);
if (messageManagerType == customType ||
!messageManagerType.GetTypeInfo().IsAssignableFrom(customType.GetTypeInfo())) {
throw new InvalidOperationException($"Type {customType.Name} must be derive from ConsistencyMessageManager<{MessageType.Name}>");
}
Services.AddScoped(customType, services => services.GetRequiredService(messageManagerType));
return AddScoped(messageManagerType, customType);
}

private ConsistencyBuilder AddScoped(Type serviceType, Type concreteType) {
Services.AddScoped(serviceType, concreteType);
return this;
}
}
}

+ 0
- 7
src/Cap.Consistency/ConsistencyMarkerService.cs View File

@@ -1,7 +0,0 @@
namespace Cap.Consistency
{
/// <summary>
/// Used to verify Consistency service was called on a ServiceCollection
/// </summary>
public class ConsistencyMarkerService { }
}

+ 0
- 147
src/Cap.Consistency/ConsistencyMessageManager.cs View File

@@ -1,147 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Cap.Consistency
{
/// <summary>
/// Provides the APIs for managing message in a persistence store.
/// </summary>
/// <typeparam name="TMessage">The type encapsulating a message.</typeparam>
public class ConsistencyMessageManager<TMessage> : IDisposable where TMessage : class
{
private bool _disposed;
private readonly HttpContext _context;
private CancellationToken CancellationToken => _context?.RequestAborted ?? CancellationToken.None;

/// <summary>
/// Constructs a new instance of <see cref="ConsistencyMessageManager{TMessage}"/>.
/// </summary>
/// <param name="store">The persistence store the manager will operate over.</param>
/// <param name="services">The <see cref="IServiceProvider"/> used to resolve services.</param>
/// <param name="logger">The logger used to log messages, warnings and errors.</param>
public ConsistencyMessageManager(IConsistencyMessageStore<TMessage> store,
IServiceProvider services,
ILogger<ConsistencyMessageManager<TMessage>> logger) {
if (store == null) {
throw new ArgumentNullException(nameof(store));
}

Store = store;
Logger = logger;

if (services != null) {
_context = services.GetService<IHttpContextAccessor>()?.HttpContext;
}
}

/// <summary>
/// Gets or sets the persistence store the manager operates over.
/// </summary>
/// <value>The persistence store the manager operates over.</value>
protected internal IConsistencyMessageStore<TMessage> Store { get; set; }

/// <summary>
/// Gets the <see cref="ILogger"/> used to log messages from the manager.
/// </summary>
/// <value>
/// The <see cref="ILogger"/> used to log messages from the manager.
/// </value>
protected internal virtual ILogger Logger { get; set; }

/// <summary>
/// Creates the specified <paramref name="message"/> in the backing store.
/// </summary>
/// <param name="message">The message to create.</param>
/// <returns>
/// The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="OperateResult"/>
/// of the operation.
/// </returns>
public virtual Task<OperateResult> CreateAsync(TMessage message) {
ThrowIfDisposed();
//todo: validation message fileds is correct

return Store.CreateAsync(message, CancellationToken);
}

/// <summary>
/// Updates the specified <paramref name="message"/> in the backing store.
/// </summary>
/// <param name="message">The message to update.</param>
/// <returns>
/// The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="OperateResult"/>
/// of the operation.
/// </returns>
public virtual Task<OperateResult> UpdateAsync(TMessage message) {
ThrowIfDisposed();
//todo: validation message fileds is correct

return Store.UpdateAsync(message, CancellationToken);
}

/// <summary>
/// Deletes the specified <paramref name="message"/> in the backing store.
/// </summary>
/// <param name="message">The message to delete.</param>
/// <returns>
/// The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="OperateResult"/>
/// of the operation.
/// </returns>
public virtual Task<OperateResult> DeleteAsync(TMessage message) {
ThrowIfDisposed();

if (message == null) {
throw new ArgumentNullException(nameof(message));
}

return Store.DeleteAsync(message, CancellationToken);
}

/// <summary>
/// Finds and returns a message, if any, who has the specified <paramref name="messageId"/>.
/// </summary>
/// <param name="messageId">The message ID to search for.</param>
/// <returns>
/// The <see cref="Task"/> that represents the asynchronous operation, containing the user matching the specified <paramref name="messageId"/> if it exists.
/// </returns>
public virtual Task<TMessage> FindByIdAsync(string messageId) {
ThrowIfDisposed();
return Store.FindByIdAsync(messageId, CancellationToken);
}

/// <summary>
/// Gets the message identifier for the specified <paramref name="message"/>.
/// </summary>
/// <param name="message">The message whose identifier should be retrieved.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation, containing the identifier for the specified <paramref name="message"/>.</returns>
public virtual async Task<string> GetMessageIdAsync(TMessage message) {
ThrowIfDisposed();
return await Store.GetMessageIdAsync(message, CancellationToken);
}

public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}

/// <summary>
/// Releases the unmanaged resources used by the message manager and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing) {
if (disposing && !_disposed) {
Store.Dispose();
_disposed = true;
}
}

protected void ThrowIfDisposed() {
if (_disposed) {
throw new ObjectDisposedException(GetType().Name);
}
}
}
}

+ 0
- 26
src/Cap.Consistency/ConsistencyOptions.cs View File

@@ -1,26 +0,0 @@
using Cap.Consistency;

namespace Microsoft.AspNetCore.Builder
{
/// <summary>
/// Represents all the options you can use to configure the system.
/// </summary>
public class ConsistencyOptions
{
/// <summary>
/// Gets or sets the <see cref="BrokerOptions"/> for the consistency system.
/// </summary>
public BrokerOptions Broker { get; set; } = new BrokerOptions();

public long MaxPendingEventNumber { get; set; }

public int MaxPendingEventNumber32 {
get {
if (this.MaxPendingEventNumber < int.MaxValue) {
return (int)this.MaxPendingEventNumber;
}
return int.MaxValue;
}
}
}
}

+ 11
- 14
src/Cap.Consistency/Consumer/ConsumerHandler.cs View File

@@ -4,7 +4,7 @@ using System.Text;
using System.Threading.Tasks;
using Cap.Consistency.Abstractions;
using Cap.Consistency.Infrastructure;
using Cap.Consistency.Route;
using Cap.Consistency.Routing;
using Microsoft.Extensions.Logging;

namespace Cap.Consistency.Consumer
@@ -12,6 +12,7 @@ namespace Cap.Consistency.Consumer
public class ConsumerHandler : IConsumerHandler
{

private readonly IServiceProvider _serviceProvider;
private readonly IConsumerInvokerFactory _consumerInvokerFactory;
private readonly IConsumerExcutorSelector _selector;
private readonly ILoggerFactory _loggerFactory;
@@ -19,30 +20,35 @@ namespace Cap.Consistency.Consumer


public ConsumerHandler(
IServiceProvider serviceProvider,
IConsumerInvokerFactory consumerInvokerFactory,
IConsumerExcutorSelector selector,
ILoggerFactory loggerFactory) {

_serviceProvider = serviceProvider;
_consumerInvokerFactory = consumerInvokerFactory;
_loggerFactory = loggerFactory;
_selector = selector;
_logger = loggerFactory.CreateLogger<ConsumerHandler>();
}

public Task Start(TopicRouteContext context) {
public Task RouteAsync(TopicRouteContext context) {

if (context == null) {
throw new ArgumentNullException(nameof(context));
}

context.ServiceProvider = _serviceProvider;

var matchs = _selector.SelectCandidates(context);

if (matchs == null || matchs.Count==0) {
if (matchs == null || matchs.Count == 0) {
_logger.LogInformation("can not be fond topic route");
return Task.CompletedTask;
}

var executeDescriptor = _selector.SelectBestCandidate(context, matchs);
context.Handler = c => {

var consumerContext = new ConsumerContext(executeDescriptor);
@@ -52,17 +58,8 @@ namespace Cap.Consistency.Consumer

return invoker.InvokeAsync();
};
return Task.CompletedTask;
}


public void Start(IEnumerable<IConsumerService> consumers) {
throw new NotImplementedException();
}

public void Stop() {
throw new NotImplementedException();
}
}
}

+ 2
- 5
src/Cap.Consistency/Consumer/IConsumerHandler.cs View File

@@ -1,15 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
using Cap.Consistency.Routing;

namespace Cap.Consistency.Consumer
{
public interface IConsumerHandler
public interface IConsumerHandler : ITopicRoute
{
void Start(IEnumerable<IConsumerService> consumers);

void Stop();
}

}

+ 0
- 1
src/Cap.Consistency/Consumer/Kafka/RdKafkaClient.cs View File

@@ -17,7 +17,6 @@ namespace Cap.Consistency.Consumer.Kafka

}


public void Start(TopicRouteContext routeContext ) {

string brokerList = null;// args[0];


+ 21
- 0
src/Cap.Consistency/Extensions/BuilderExtensions.cs View File

@@ -1,5 +1,6 @@
using System;
using Cap.Consistency;
using Cap.Consistency.Routing;
using Microsoft.Extensions.DependencyInjection;

// ReSharper disable once CheckNamespace
@@ -27,5 +28,25 @@ namespace Microsoft.AspNetCore.Builder

return app;
}

public static IApplicationBuilder UserRouter(this IApplicationBuilder builder, ITopicRoute router) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}

if (router == null) {
throw new ArgumentNullException(nameof(router));
}

var marker = builder.ApplicationServices.GetService<ConsistencyMarkerService>();
if (marker == null) {
throw new InvalidOperationException("Add Consistency must be called on the service collection.");
}

var context = new TopicRouteContext();


}
}
}

+ 8
- 0
src/Cap.Consistency/GlobalSuppressions.cs View File

@@ -0,0 +1,8 @@

// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.

[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0016:Use 'throw' expression", Justification = "<Pending>", Scope = "member", Target = "~M:Cap.Consistency.Internal.ConsumerInvoker.#ctor(Microsoft.Extensions.Logging.ILogger,Cap.Consistency.Abstractions.ConsumerContext,Cap.Consistency.Internal.ObjectMethodExecutor)")]


+ 0
- 55
src/Cap.Consistency/IConsistencyMessageStore.cs View File

@@ -1,55 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Cap.Consistency
{
/// <summary>
/// Provides an abstraction for a store which manages consistent message.
/// </summary>
/// <typeparam name="TMessage"></typeparam>
public interface IConsistencyMessageStore<TMessage> : IDisposable where TMessage : class
{
/// <summary>
/// Finds and returns a message, if any, who has the specified <paramref name="messageId"/>.
/// </summary>
/// <param name="messageId">The message ID to search for.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>
/// The <see cref="Task"/> that represents the asynchronous operation, containing the message matching the specified <paramref name="messageId"/> if it exists.
/// </returns>
Task<TMessage> FindByIdAsync(string messageId, CancellationToken cancellationToken);

/// <summary>
/// Creates a new message in a store as an asynchronous operation.
/// </summary>
/// <param name="message">The message to create in the store.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>A <see cref="Task{TResult}"/> that represents the <see cref="OperateResult"/> of the asynchronous query.</returns>
Task<OperateResult> CreateAsync(TMessage message, CancellationToken cancellationToken);

/// <summary>
/// Updates a message in a store as an asynchronous operation.
/// </summary>
/// <param name="message">The message to update in the store.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>A <see cref="Task{TResult}"/> that represents the <see cref="OperateResult"/> of the asynchronous query.</returns>
Task<OperateResult> UpdateAsync(TMessage message, CancellationToken cancellationToken);

/// <summary>
/// Deletes a message from the store as an asynchronous operation.
/// </summary>
/// <param name="message">The message to delete in the store.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>A <see cref="Task{TResult}"/> that represents the <see cref="OperateResult"/> of the asynchronous query.</returns>
Task<OperateResult> DeleteAsync(TMessage message, CancellationToken cancellationToken);

/// <summary>
/// Gets the ID for a message from the store as an asynchronous operation.
/// </summary>
/// <param name="message">The message whose ID should be returned.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>A <see cref="Task{TResult}"/> that contains the ID of the message.</returns>
Task<string> GetMessageIdAsync(TMessage message, CancellationToken cancellationToken);
}
}

+ 1
- 1
src/Cap.Consistency/Infrastructure/IConsumerExcutorSelector.cs View File

@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Text;
using Cap.Consistency.Abstractions;
using Cap.Consistency.Route;
using Cap.Consistency.Routing;

namespace Cap.Consistency.Infrastructure
{


+ 139
- 0
src/Cap.Consistency/Internal/ConsumerInvoker.cs View File

@@ -0,0 +1,139 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Cap.Consistency.Abstractions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Cap.Consistency.Internal
{
public class ConsumerInvoker : IConsumerInvoker
{
protected readonly ILogger _logger;
protected readonly IServiceProvider _serviceProvider;
private readonly ObjectMethodExecutor _executor;
protected readonly ConsumerContext _consumerContext;

private Dictionary<string, object> _arguments;

public ConsumerInvoker(ILogger logger,
IServiceProvider serviceProvider,
ConsumerContext consumerContext,
ObjectMethodExecutor objectMethodExecutor) {
if (logger == null) {
throw new ArgumentNullException(nameof(logger));
}

if (consumerContext == null) {
throw new ArgumentNullException(nameof(consumerContext));
}

if (objectMethodExecutor == null) {
throw new ArgumentNullException(nameof(objectMethodExecutor));
}

_logger = logger;
_serviceProvider = serviceProvider;
_consumerContext = consumerContext;
_executor = ObjectMethodExecutor.Create(_consumerContext.ConsumerDescriptor.MethodInfo,
_consumerContext.ConsumerDescriptor.ImplType.GetTypeInfo());
}


public Task InvokeAsync() {
try {
using (_logger.BeginScope("consumer invoker begin")) {

_logger.LogDebug("Executing consumer Topic: {0}", _consumerContext.ConsumerDescriptor.Topic);

try {

var obj = ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, _consumerContext.ConsumerDescriptor.ImplType);
_executor.Execute(obj, null);
return Task.CompletedTask;
}
finally {

_logger.LogDebug("Executed consumer method .");
}
}
}
finally {

}
}

private object _controller;

private async Task InvokeConsumerMethodAsync() {
var controllerContext = _consumerContext;
var executor = _executor;
var controller = _controller;
var arguments = _arguments;
var orderedArguments = ConsumerMethodExecutor.PrepareArguments(arguments, executor);

var logger = _logger;

object result = null;
try {

var returnType = executor.MethodReturnType;
if (returnType == typeof(void)) {
executor.Execute(controller, orderedArguments);
result = new object();
}
else if (returnType == typeof(Task)) {
await (Task)executor.Execute(controller, orderedArguments);
result = new object();
}
//else if (executor.TaskGenericType == typeof(IActionResult)) {
// result = await (Task<IActionResult>)executor.Execute(controller, orderedArguments);
// if (result == null) {
// throw new InvalidOperationException(
// Resources.FormatActionResult_ActionReturnValueCannotBeNull(typeof(IActionResult)));
// }
//}
//else if (executor.IsTypeAssignableFromIActionResult) {
// if (_executor.IsMethodAsync) {
// result = (IActionResult)await _executor.ExecuteAsync(controller, orderedArguments);
// }
// else {
// result = (IActionResult)_executor.Execute(controller, orderedArguments);
// }

// if (result == null) {
// throw new InvalidOperationException(
// Resources.FormatActionResult_ActionReturnValueCannotBeNull(_executor.TaskGenericType ?? returnType));
// }
//}
//else if (!executor.IsMethodAsync) {
// var resultAsObject = executor.Execute(controller, orderedArguments);
// result = resultAsObject as IActionResult ?? new ObjectResult(resultAsObject) {
// DeclaredType = returnType,
// };
//}
//else if (executor.TaskGenericType != null) {
// var resultAsObject = await executor.ExecuteAsync(controller, orderedArguments);
// result = resultAsObject as IActionResult ?? new ObjectResult(resultAsObject) {
// DeclaredType = executor.TaskGenericType,
// };
//}
//else {
// // This will be the case for types which have derived from Task and Task<T> or non Task types.
// throw new InvalidOperationException(Resources.FormatActionExecutor_UnexpectedTaskInstance(
// executor.MethodInfo.Name,
// executor.MethodInfo.DeclaringType));
//}

//_result = result;
// logger.ActionMethodExecuted(controllerContext, result);
}
finally {

}
}

}
}

+ 33
- 0
src/Cap.Consistency/Internal/ConsumerMethodExecutor.cs View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Cap.Consistency.Internal
{
public class ConsumerMethodExecutor
{
public static object[] PrepareArguments(
IDictionary<string, object> actionParameters,
ObjectMethodExecutor actionMethodExecutor) {
var declaredParameterInfos = actionMethodExecutor.ActionParameters;
var count = declaredParameterInfos.Length;
if (count == 0) {
return null;
}

var arguments = new object[count];
for (var index = 0; index < count; index++) {
var parameterInfo = declaredParameterInfos[index];
object value;

if (!actionParameters.TryGetValue(parameterInfo.Name, out value)) {
value = actionMethodExecutor.GetDefaultValueForParameter(index);
}

arguments[index] = value;
}

return arguments;
}
}
}

+ 285
- 0
src/Cap.Consistency/Internal/ObjectMethodExecutor.cs View File

@@ -0,0 +1,285 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Cap.Consistency.Internal
{
public class ObjectMethodExecutor
{
private readonly object[] _parameterDefaultValues;
private readonly ConsumerMethodExecutorAsync _executorAsync;
private readonly ConsumerMethodExecutor _executor;

private static readonly MethodInfo _convertOfTMethod =
typeof(ObjectMethodExecutor).GetRuntimeMethods().Single(methodInfo => methodInfo.Name == nameof(ObjectMethodExecutor.Convert));

private ObjectMethodExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo) {
if (methodInfo == null) {
throw new ArgumentNullException(nameof(methodInfo));
}

MethodInfo = methodInfo;
TargetTypeInfo = targetTypeInfo;
ActionParameters = methodInfo.GetParameters();
MethodReturnType = methodInfo.ReturnType;
IsMethodAsync = typeof(Task).IsAssignableFrom(MethodReturnType);
TaskGenericType = IsMethodAsync ? GetTaskInnerTypeOrNull(MethodReturnType) : null;
//IsTypeAssignableFromIActionResult = typeof(IActionResult).IsAssignableFrom(TaskGenericType ?? MethodReturnType);

if (IsMethodAsync && TaskGenericType != null) {
// For backwards compatibility we're creating a sync-executor for an async method. This was
// supported in the past even though MVC wouldn't have called it.
_executor = GetExecutor(methodInfo, targetTypeInfo);
_executorAsync = GetExecutorAsync(TaskGenericType, methodInfo, targetTypeInfo);
}
else {
_executor = GetExecutor(methodInfo, targetTypeInfo);
}

_parameterDefaultValues = GetParameterDefaultValues(ActionParameters);
}

private delegate Task<object> ConsumerMethodExecutorAsync(object target, object[] parameters);

private delegate object ConsumerMethodExecutor(object target, object[] parameters);

private delegate void VoidActionExecutor(object target, object[] parameters);

public MethodInfo MethodInfo { get; }

public ParameterInfo[] ActionParameters { get; }

public TypeInfo TargetTypeInfo { get; }

public Type TaskGenericType { get; }

// This field is made internal set because it is set in unit tests.
public Type MethodReturnType { get; internal set; }

public bool IsMethodAsync { get; }

//public bool IsTypeAssignableFromIActionResult { get; }

public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo) {
var executor = new ObjectMethodExecutor(methodInfo, targetTypeInfo);
return executor;
}

public Task<object> ExecuteAsync(object target, object[] parameters) {
return _executorAsync(target, parameters);
}

public object Execute(object target, object[] parameters) {
return _executor(target, parameters);
}

public object GetDefaultValueForParameter(int index) {
if (index < 0 || index > ActionParameters.Length - 1) {
throw new ArgumentOutOfRangeException(nameof(index));
}

return _parameterDefaultValues[index];
}

private static ConsumerMethodExecutor GetExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo) {
// Parameters to executor
var targetParameter = Expression.Parameter(typeof(object), "target");
var parametersParameter = Expression.Parameter(typeof(object[]), "parameters");

// Build parameter list
var parameters = new List<Expression>();
var paramInfos = methodInfo.GetParameters();
for (int i = 0; i < paramInfos.Length; i++) {
var paramInfo = paramInfos[i];
var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType);

// valueCast is "(Ti) parameters[i]"
parameters.Add(valueCast);
}

// Call method
var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType());
var methodCall = Expression.Call(instanceCast, methodInfo, parameters);

// methodCall is "((Ttarget) target) method((T0) parameters[0], (T1) parameters[1], ...)"
// Create function
if (methodCall.Type == typeof(void)) {
var lambda = Expression.Lambda<VoidActionExecutor>(methodCall, targetParameter, parametersParameter);
var voidExecutor = lambda.Compile();
return WrapVoidAction(voidExecutor);
}
else {
// must coerce methodCall to match ActionExecutor signature
var castMethodCall = Expression.Convert(methodCall, typeof(object));
var lambda = Expression.Lambda<ConsumerMethodExecutor>(castMethodCall, targetParameter, parametersParameter);
return lambda.Compile();
}
}

private static ConsumerMethodExecutor WrapVoidAction(VoidActionExecutor executor) {
return delegate (object target, object[] parameters) {
executor(target, parameters);
return null;
};
}

private static ConsumerMethodExecutorAsync GetExecutorAsync(Type taskInnerType, MethodInfo methodInfo, TypeInfo targetTypeInfo) {
// Parameters to executor
var targetParameter = Expression.Parameter(typeof(object), "target");
var parametersParameter = Expression.Parameter(typeof(object[]), "parameters");

// Build parameter list
var parameters = new List<Expression>();
var paramInfos = methodInfo.GetParameters();
for (int i = 0; i < paramInfos.Length; i++) {
var paramInfo = paramInfos[i];
var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType);

// valueCast is "(Ti) parameters[i]"
parameters.Add(valueCast);
}

// Call method
var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType());
var methodCall = Expression.Call(instanceCast, methodInfo, parameters);

var coerceMethodCall = GetCoerceMethodCallExpression(taskInnerType, methodCall, methodInfo);
var lambda = Expression.Lambda<ConsumerMethodExecutorAsync>(coerceMethodCall, targetParameter, parametersParameter);
return lambda.Compile();
}

// We need to CoerceResult as the object value returned from methodInfo.Invoke has to be cast to a Task<T>.
// This is necessary to enable calling await on the returned task.
// i.e we need to write the following var result = await (Task<ActualType>)mInfo.Invoke.
// Returning Task<object> enables us to await on the result.
private static Expression GetCoerceMethodCallExpression(
Type taskValueType,
MethodCallExpression methodCall,
MethodInfo methodInfo) {
var castMethodCall = Expression.Convert(methodCall, typeof(object));
// for: public Task<T> Action()
// constructs: return (Task<object>)Convert<T>((Task<T>)result)
var genericMethodInfo = _convertOfTMethod.MakeGenericMethod(taskValueType);
var genericMethodCall = Expression.Call(null, genericMethodInfo, castMethodCall);
var convertedResult = Expression.Convert(genericMethodCall, typeof(Task<object>));
return convertedResult;
}

/// <summary>
/// Cast Task of T to Task of object
/// </summary>
private static async Task<object> CastToObject<T>(Task<T> task) {
return (object)await task;
}

private static Type GetTaskInnerTypeOrNull(Type type) {
var genericType = ExtractGenericInterface(type, typeof(Task<>));

return genericType?.GenericTypeArguments[0];
}

public static Type ExtractGenericInterface(Type queryType, Type interfaceType) {
if (queryType == null) {
throw new ArgumentNullException(nameof(queryType));
}

if (interfaceType == null) {
throw new ArgumentNullException(nameof(interfaceType));
}

if (IsGenericInstantiation(queryType, interfaceType)) {
// queryType matches (i.e. is a closed generic type created from) the open generic type.
return queryType;
}

// Otherwise check all interfaces the type implements for a match.
// - If multiple different generic instantiations exists, we want the most derived one.
// - If that doesn't break the tie, then we sort alphabetically so that it's deterministic.
//
// We do this by looking at interfaces on the type, and recursing to the base type
// if we don't find any matches.
return GetGenericInstantiation(queryType, interfaceType);
}

private static bool IsGenericInstantiation(Type candidate, Type interfaceType) {
return
candidate.GetTypeInfo().IsGenericType &&
candidate.GetGenericTypeDefinition() == interfaceType;
}

private static Type GetGenericInstantiation(Type queryType, Type interfaceType) {
Type bestMatch = null;
var interfaces = queryType.GetInterfaces();
foreach (var @interface in interfaces) {
if (IsGenericInstantiation(@interface, interfaceType)) {
if (bestMatch == null) {
bestMatch = @interface;
}
else if (StringComparer.Ordinal.Compare(@interface.FullName, bestMatch.FullName) < 0) {
bestMatch = @interface;
}
else {
// There are two matches at this level of the class hierarchy, but @interface is after
// bestMatch in the sort order.
}
}
}

if (bestMatch != null) {
return bestMatch;
}

// BaseType will be null for object and interfaces, which means we've reached 'bottom'.
var baseType = queryType?.GetTypeInfo().BaseType;
if (baseType == null) {
return null;
}
else {
return GetGenericInstantiation(baseType, interfaceType);
}
}


private static Task<object> Convert<T>(object taskAsObject) {
var task = (Task<T>)taskAsObject;
return CastToObject<T>(task);
}

private static object[] GetParameterDefaultValues(ParameterInfo[] parameters) {
var values = new object[parameters.Length];

for (var i = 0; i < parameters.Length; i++) {
var parameterInfo = parameters[i];
object defaultValue;

if (parameterInfo.HasDefaultValue) {
defaultValue = parameterInfo.DefaultValue;
}
else {
var defaultValueAttribute = parameterInfo
.GetCustomAttribute<DefaultValueAttribute>(inherit: false);

if (defaultValueAttribute?.Value == null) {
defaultValue = parameterInfo.ParameterType.GetTypeInfo().IsValueType
? Activator.CreateInstance(parameterInfo.ParameterType)
: null;
}
else {
defaultValue = defaultValueAttribute.Value;
}
}

values[i] = defaultValue;
}

return values;
}
}
}

+ 7
- 2
src/Cap.Consistency/KafkaConsistency.cs View File

@@ -4,10 +4,11 @@ using System.Text;
using System.Linq;
using Cap.Consistency.Consumer;
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;

namespace Cap.Consistency
{
public class KafkaConsistency
public class KafkaConsistency:IRoute
{
private IServiceProvider _serviceProvider;
private IEnumerable<IConsumerHandler> _handlers;
@@ -29,5 +30,9 @@ namespace Cap.Consistency
handler.Stop();
}
}
}

public async Task Start() {

}
}

+ 0
- 14
src/Cap.Consistency/QMessageAttribute.cs View File

@@ -1,14 +0,0 @@
using System;

namespace Cap.Consistency
{
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
sealed class QMessageAttribute : Attribute
{
public QMessageAttribute(string messageName) {
MessageName = messageName;
}
public string MessageName { get; private set; }
}
}

+ 0
- 48
src/Cap.Consistency/QMessageFinder.cs View File

@@ -1,48 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using Cap.Consistency.Extensions;
using Microsoft.Extensions.DependencyInjection;

namespace Cap.Consistency
{
public class QMessageFinder
{

public ConcurrentDictionary<string, QMessageMethodInfo> GetQMessageMethods(IServiceCollection serviceColloection) {

if (serviceColloection == null) {
throw new ArgumentNullException(nameof(serviceColloection));
}

var qMessageTypes = new ConcurrentDictionary<string, QMessageMethodInfo>();

foreach (var serviceDescriptor in serviceColloection) {

foreach (var method in serviceDescriptor.ServiceType.GetTypeInfo().DeclaredMethods) {

var messageMethodInfo = new QMessageMethodInfo();

if (method.IsPropertyBinding()) {
continue;
}

var qMessageAttr = method.GetCustomAttribute<QMessageAttribute>();
if (qMessageAttr == null) {
continue;
}

messageMethodInfo.MessageName = qMessageAttr.MessageName;
messageMethodInfo.ImplType = method.DeclaringType;
messageMethodInfo.MethodInfo = method;

qMessageTypes.AddOrUpdate(qMessageAttr.MessageName, messageMethodInfo, (x, y) => y);
}
}
return qMessageTypes;
}
}
}

+ 0
- 17
src/Cap.Consistency/QMessageMethodInfo.cs View File

@@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace Cap.Consistency
{
public class QMessageMethodInfo
{
public MethodInfo MethodInfo { get; set; }

public Type ImplType { get; set; }

public string MessageName { get; set; }
}
}

+ 12
- 0
src/Cap.Consistency/Routing/ITopicRoute.cs View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace Cap.Consistency.Routing
{
public interface ITopicRoute
{
Task RouteAsync(TopicRouteContext context);
}
}

src/Cap.Consistency/Route/TopicRouteContext.cs → src/Cap.Consistency/Routing/TopicRouteContext.cs View File

@@ -6,12 +6,15 @@ using Cap.Consistency.Abstractions;
using Cap.Consistency.Consumer;
using Cap.Consistency.Infrastructure;

namespace Cap.Consistency.Route
namespace Cap.Consistency.Routing
{
public delegate Task HandlerConsumer(ConsumerExecutorDescriptor context);

public class TopicRouteContext
{
public TopicRouteContext() {

}

public TopicRouteContext(DeliverMessage message) {
Message = message;
@@ -23,9 +26,11 @@ namespace Cap.Consistency.Route

public HandlerConsumer Handler { get; set; }

public IList<IConsumerHandler> Consumers { get; set; }

public IServiceProvider ServiceProvider { get; set; }

public IList<IConsumerHandler> Consumers { get; set; }
public IList<ITopicRoute> Routes { get; set; }

}
}

+ 0
- 47
src/Cap.Consistency/ServiceCollectionExtensions.cs View File

@@ -1,47 +0,0 @@
using System;
using Cap.Consistency;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection.Extensions;

// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Contains extension methods to <see cref="IServiceCollection"/> for configuring consistence services.
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds and configures the consistence services for the consitence.
/// </summary>
/// <param name="services">The services available in the application.</param>
/// <returns>An <see cref="ConsistencyBuilder"/> for application services.</returns>
public static ConsistencyBuilder AddConsistency<TMessage>(this IServiceCollection services)
where TMessage : class {
return services.AddConsistency<TMessage>(setupAction: null);
}

/// <summary>
/// Adds and configures the consistence services for the consitence.
/// </summary>
/// <param name="services">The services available in the application.</param>
/// <param name="setupAction">An action to configure the <see cref="ConsistencyOptions"/>.</param>
/// <returns>An <see cref="ConsistencyBuilder"/> for application services.</returns>
public static ConsistencyBuilder AddConsistency<TMessage>(this IServiceCollection services, Action<ConsistencyOptions> setupAction)
where TMessage : class {
services.TryAddSingleton<ConsistencyMarkerService>();

services.TryAddScoped<ConsistencyMessageManager<TMessage>, ConsistencyMessageManager<TMessage>>();


services.AddSingleton<QMessageFinder>();


if (setupAction != null) {
services.Configure(setupAction);
}

return new ConsistencyBuilder(typeof(TMessage), services);
}
}
}

+ 14
- 14
test/Cap.Consistency.EntityFrameworkCore.Test/Cap.Consistency.EntityFrameworkCore.Test.csproj View File

@@ -23,20 +23,20 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20170106-08" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0-beta5-build1225" />
<PackageReference Include="xunit" Version="2.2.0-beta5-build3474" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.0" />
<PackageReference Include="Moq" Version="4.6.36-*" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.1" />
<PackageReference Include="System.Data.SqlClient" Version="4.3.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
<PackageReference Include="xunit" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" />
<PackageReference Include="Moq" Version="4.7.10" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.2" />
<PackageReference Include="System.Data.SqlClient" Version="4.3.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="1.2.0-preview1-22815" />
</ItemGroup>



+ 7
- 7
test/Cap.Consistency.Test/Cap.Consistency.Test.csproj View File

@@ -19,13 +19,13 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20170106-08" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0-beta5-build1225" />
<PackageReference Include="xunit" Version="2.2.0-beta5-build3474" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.0" />
<PackageReference Include="Moq" Version="4.6.36-*" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
<PackageReference Include="xunit" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" />
<PackageReference Include="Moq" Version="4.7.10" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" />
</ItemGroup>

</Project>

Loading…
Cancel
Save