Removes sample project. Co-authored-by: Dave Smith <dave.smith@ventivtech.com>master
@@ -71,8 +71,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.NATS", "src\ | |||
EndProject | |||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.RabbitMQ.Postgres.DashboardAuth", "samples\Sample.RabbitMQ.Postgres.DashboardAuth\Sample.RabbitMQ.Postgres.DashboardAuth.csproj", "{54F6C206-2A23-4971-AE5A-FC47EB772452}" | |||
EndProject | |||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyConsumerSelector", "samples\MyConsumerSelector\MyConsumerSelector.csproj", "{1AE86784-0B64-4A73-8D81-D913C6E33D7D}" | |||
EndProject | |||
Global | |||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||
Debug|Any CPU = Debug|Any CPU | |||
@@ -167,10 +165,6 @@ Global | |||
{54F6C206-2A23-4971-AE5A-FC47EB772452}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{54F6C206-2A23-4971-AE5A-FC47EB772452}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{54F6C206-2A23-4971-AE5A-FC47EB772452}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{1AE86784-0B64-4A73-8D81-D913C6E33D7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{1AE86784-0B64-4A73-8D81-D913C6E33D7D}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{1AE86784-0B64-4A73-8D81-D913C6E33D7D}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{1AE86784-0B64-4A73-8D81-D913C6E33D7D}.Release|Any CPU.Build.0 = Release|Any CPU | |||
EndGlobalSection | |||
GlobalSection(SolutionProperties) = preSolution | |||
HideSolutionNode = FALSE | |||
@@ -198,7 +192,6 @@ Global | |||
{B187DD15-092D-4B72-9807-50856607D237} = {3A6B6931-A123-477A-9469-8B468B5385AF} | |||
{8B2FD3EA-E72B-4A82-B182-B87EC0C15D07} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} | |||
{54F6C206-2A23-4971-AE5A-FC47EB772452} = {3A6B6931-A123-477A-9469-8B468B5385AF} | |||
{1AE86784-0B64-4A73-8D81-D913C6E33D7D} = {3A6B6931-A123-477A-9469-8B468B5385AF} | |||
EndGlobalSection | |||
GlobalSection(ExtensibilityGlobals) = postSolution | |||
SolutionGuid = {2E70565D-94CF-40B4-BFE1-AC18D5F736AB} | |||
@@ -1,47 +0,0 @@ | |||
using System; | |||
using DotNetCore.CAP; | |||
namespace MyConsumerSelector | |||
{ | |||
public class CustomSubscriber : IMessageSubscriber, ICapSubscribe | |||
{ | |||
[MessageSubscription("string")] | |||
public void String(string message) | |||
{ | |||
Console.WriteLine($"String: {message}"); | |||
} | |||
[MessageSubscription("message.string")] | |||
public void String(Message<string> message) | |||
{ | |||
Console.WriteLine($"String: {System.Text.Json.JsonSerializer.Serialize(message)}"); | |||
} | |||
[MessageSubscription("message.datetime")] | |||
public void Date(Message<DateTime> message, [FromCap] CapHeader header) | |||
{ | |||
Console.WriteLine($"Date: {System.Text.Json.JsonSerializer.Serialize(message)}"); | |||
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(header)); | |||
} | |||
[MessageSubscription("message.bytes")] | |||
public void Bytes(Message<byte[]> message, [FromCap] CapHeader header) | |||
{ | |||
Console.WriteLine($"Bytes: {System.Text.Json.JsonSerializer.Serialize(message)}"); | |||
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(header)); | |||
} | |||
[CapSubscribe("cap")] | |||
public void Cap(string message, [FromCap] CapHeader header) | |||
{ | |||
Console.WriteLine($"Cap {message}"); | |||
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(header)); | |||
} | |||
} | |||
public class Message<T> | |||
{ | |||
public string Name { get; set; } | |||
public T Body { get; set; } | |||
} | |||
} |
@@ -1,19 +0,0 @@ | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<PropertyGroup> | |||
<OutputType>Exe</OutputType> | |||
<TargetFramework>net5.0</TargetFramework> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\..\src\DotNetCore.CAP.InMemoryStorage\DotNetCore.CAP.InMemoryStorage.csproj" /> | |||
<ProjectReference Include="..\..\src\DotNetCore.CAP.RabbitMQ\DotNetCore.CAP.RabbitMQ.csproj" /> | |||
<ProjectReference Include="..\..\src\DotNetCore.CAP\DotNetCore.CAP.csproj" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" /> | |||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="5.0.0" /> | |||
</ItemGroup> | |||
</Project> |
@@ -1,47 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using DotNetCore.CAP; | |||
using DotNetCore.CAP.Internal; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Microsoft.Extensions.Logging; | |||
namespace MyConsumerSelector | |||
{ | |||
public class Program | |||
{ | |||
private static bool _useCustomSelector = true; | |||
public static void Main(string[] args) | |||
{ | |||
var container = new ServiceCollection(); | |||
container.AddLogging(x => x.AddConsole()); | |||
if (_useCustomSelector) | |||
container.AddSingleton<IConsumerServiceSelector, GenericConsumerServiceSelector<IMessageSubscriber, MessageSubscriptionAttribute>>(); | |||
container.AddTransient<IMessageSubscriber, CustomSubscriber>(); | |||
container.AddTransient<ICapSubscribe, CustomSubscriber>(); | |||
container.AddCap(x => | |||
{ | |||
x.UseInMemoryStorage(); | |||
x.UseRabbitMQ(z => | |||
{ | |||
z.ExchangeName = "MyConsumerSelector.Generic"; | |||
z.HostName = "localhost"; | |||
z.UserName = "guest"; | |||
z.Password = "guest"; | |||
z.CustomHeaders = e => new List<KeyValuePair<string, string>> | |||
{ | |||
new(DotNetCore.CAP.Messages.Headers.MessageId, SnowflakeId.Default().NextId().ToString()), | |||
new(DotNetCore.CAP.Messages.Headers.MessageName, e.RoutingKey) | |||
}; | |||
}); | |||
}); | |||
var sp = container.BuildServiceProvider(); | |||
sp.GetRequiredService<IBootstrapper>().BootstrapAsync(default); | |||
Console.ReadLine(); | |||
} | |||
} | |||
} |
@@ -1,24 +0,0 @@ | |||
using System; | |||
namespace MyConsumerSelector | |||
{ | |||
/// <summary> | |||
/// Flags the implementer as a class that subscribes to messages. | |||
/// </summary> | |||
public interface IMessageSubscriber { } | |||
/// <summary> | |||
/// Names the message being subscribed to. | |||
/// </summary> | |||
public class MessageSubscriptionAttribute : Attribute, INamedGroup | |||
{ | |||
public MessageSubscriptionAttribute(string name) | |||
{ | |||
Name = name; | |||
} | |||
public string Name { get; } | |||
public string Group { get; set; } | |||
} | |||
} |
@@ -1,11 +1,7 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Reflection; | |||
using System.Threading.Tasks; | |||
using DotNetCore.CAP.Internal; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Microsoft.Extensions.Options; | |||
using Xunit; | |||
namespace DotNetCore.CAP.Test | |||
@@ -20,7 +16,8 @@ namespace DotNetCore.CAP.Test | |||
public CustomConsumerSubscribeTest() | |||
{ | |||
var services = new ServiceCollection(); | |||
services.AddSingleton<IConsumerServiceSelector, MyConsumerServiceSelector>(); | |||
// Declare subscribe interface and attribute when configuring services. | |||
services.AddSingleton<IConsumerServiceSelector, GenericConsumerServiceSelector<IMySubscribe, MySubscribeAttribute>>(); | |||
services.AddTransient<IMySubscribe, CustomInterfaceTypesClass>(); | |||
services.AddLogging(); | |||
services.AddCap(x => | |||
@@ -37,7 +34,7 @@ namespace DotNetCore.CAP.Test | |||
var selector = _provider.GetRequiredService<IConsumerServiceSelector>(); | |||
var candidates = selector.SelectCandidates(); | |||
Assert.Equal(2, candidates.Count); | |||
Assert.Equal(3, candidates.Count); | |||
} | |||
[Fact] | |||
@@ -53,87 +50,25 @@ namespace DotNetCore.CAP.Test | |||
Assert.StartsWith(TopicNamePrefix, bestCandidates.TopicName); | |||
Assert.Equal(typeof(Task), bestCandidates.MethodInfo.ReturnType); | |||
} | |||
} | |||
public class MyConsumerServiceSelector : ConsumerServiceSelector | |||
{ | |||
private readonly CapOptions _capOptions; | |||
public MyConsumerServiceSelector(IServiceProvider serviceProvider) | |||
: base(serviceProvider) | |||
{ | |||
_capOptions = serviceProvider.GetService<IOptions<CapOptions>>().Value; | |||
} | |||
protected override IEnumerable<ConsumerExecutorDescriptor> FindConsumersFromInterfaceTypes(IServiceProvider provider) | |||
{ | |||
var executorDescriptorList = new List<ConsumerExecutorDescriptor>(); | |||
using (var scoped = provider.CreateScope()) | |||
{ | |||
var scopedProvider = scoped.ServiceProvider; | |||
var consumerServices = scopedProvider.GetServices<IMySubscribe>(); | |||
foreach (var service in consumerServices) | |||
{ | |||
var typeInfo = service.GetType().GetTypeInfo(); | |||
if (!typeof(IMySubscribe).GetTypeInfo().IsAssignableFrom(typeInfo)) | |||
{ | |||
continue; | |||
} | |||
executorDescriptorList.AddRange(GetMyDescription(typeInfo)); | |||
} | |||
return executorDescriptorList; | |||
} | |||
} | |||
private IEnumerable<ConsumerExecutorDescriptor> GetMyDescription(TypeInfo typeInfo) | |||
[Fact] | |||
public void CanFindTopicWithParameters() | |||
{ | |||
foreach (var method in typeInfo.DeclaredMethods) | |||
{ | |||
var topicAttr = method.GetCustomAttributes<MySubscribeAttribute>(true); | |||
var topicAttributes = topicAttr as IList<MySubscribeAttribute> ?? topicAttr.ToList(); | |||
if (!topicAttributes.Any()) | |||
{ | |||
continue; | |||
} | |||
foreach (var attr in topicAttributes) | |||
{ | |||
if (attr.Group == null) | |||
{ | |||
attr.Group = _capOptions.DefaultGroupName + "." + _capOptions.Version; | |||
} | |||
else | |||
{ | |||
attr.Group = attr.Group + "." + _capOptions.Version; | |||
} | |||
if (!string.IsNullOrEmpty(_capOptions.GroupNamePrefix)) | |||
{ | |||
attr.Group = $"{_capOptions.GroupNamePrefix}.{attr.Group}"; | |||
} | |||
var selector = _provider.GetRequiredService<IConsumerServiceSelector>(); | |||
var candidates = selector.SelectCandidates(); | |||
var bestCandidates = selector.SelectBestCandidate($"{TopicNamePrefix}.Candidates.Foo3", candidates); | |||
yield return new ConsumerExecutorDescriptor | |||
{ | |||
Attribute = new CapSubscribeAttribute(attr.Name) | |||
{ | |||
Group = attr.Group | |||
}, | |||
MethodInfo = method, | |||
ImplTypeInfo = typeInfo, | |||
TopicNamePrefix = _capOptions.TopicNamePrefix | |||
}; | |||
} | |||
} | |||
Assert.NotNull(bestCandidates); | |||
Assert.NotNull(bestCandidates.Parameters); | |||
Assert.StartsWith(GroupNamePrefix, bestCandidates.Attribute.Group); | |||
Assert.StartsWith(TopicNamePrefix, bestCandidates.TopicName); | |||
Assert.Equal(typeof(Task), bestCandidates.MethodInfo.ReturnType); | |||
} | |||
} | |||
public interface IMySubscribe { } | |||
public class MySubscribeAttribute : Attribute | |||
public class MySubscribeAttribute : Attribute, INamedGroup | |||
{ | |||
public MySubscribeAttribute(string name) | |||
{ | |||
@@ -150,19 +85,26 @@ namespace DotNetCore.CAP.Test | |||
[MySubscribe("Candidates.Foo")] | |||
public Task GetFoo() | |||
{ | |||
Console.WriteLine("GetFoo() method has been excuted."); | |||
Console.WriteLine("GetFoo() method has been executed."); | |||
return Task.CompletedTask; | |||
} | |||
[MySubscribe("Candidates.Foo2")] | |||
public void GetFoo2() | |||
{ | |||
Console.WriteLine("GetFoo2() method has been excuted."); | |||
Console.WriteLine("GetFoo2() method has been executed."); | |||
} | |||
[MySubscribe("Candidates.Foo3")] | |||
public Task GetFoo3(string message) | |||
{ | |||
Console.WriteLine($"GetFoo3() received message {message}."); | |||
return Task.CompletedTask; | |||
} | |||
public void DistracterMethod() | |||
{ | |||
Console.WriteLine("DistracterMethod() method has been excuted."); | |||
Console.WriteLine("DistracterMethod() method has been executed."); | |||
} | |||
} | |||
} |
@@ -2,13 +2,17 @@ using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Reflection; | |||
using DotNetCore.CAP; | |||
using DotNetCore.CAP.Internal; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Microsoft.Extensions.Options; | |||
namespace MyConsumerSelector | |||
namespace DotNetCore.CAP.Test | |||
{ | |||
/// <summary> | |||
/// Allows caller to supply subscribe interface and attribute when adding services. | |||
/// </summary> | |||
/// <typeparam name="TSubscriber"></typeparam> | |||
/// <typeparam name="TSubscriptionAttribute"></typeparam> | |||
public class GenericConsumerServiceSelector<TSubscriber, TSubscriptionAttribute> : ConsumerServiceSelector | |||
where TSubscriptionAttribute : Attribute, INamedGroup | |||
{ | |||
@@ -20,6 +24,7 @@ namespace MyConsumerSelector | |||
_capOptions = serviceProvider.GetRequiredService<IOptions<CapOptions>>().Value; | |||
} | |||
/// <inheritdoc cref="ConsumerServiceSelector"/> | |||
protected override IEnumerable<ConsumerExecutorDescriptor> FindConsumersFromInterfaceTypes(IServiceProvider provider) | |||
{ | |||
var executorDescriptorList = new List<ConsumerExecutorDescriptor>(); | |||
@@ -31,8 +36,8 @@ namespace MyConsumerSelector | |||
var subscriberTypeInfo = typeof(TSubscriber).GetTypeInfo(); | |||
foreach (var service in subscribers) | |||
{ | |||
var serviceTypeInfo = service.GetType().GetTypeInfo(); | |||
if (!subscriberTypeInfo.IsAssignableFrom(serviceTypeInfo)) | |||
var serviceTypeInfo = service?.GetType().GetTypeInfo(); | |||
if (serviceTypeInfo == null || !subscriberTypeInfo.IsAssignableFrom(serviceTypeInfo)) | |||
{ | |||
continue; | |||
} |