Commit 9aaf1a6e authored by Savorboard's avatar Savorboard Committed by GitHub

Merge pull request #20 from dotnetcore/develop

Release 1.1.0
parents 3b5333a7 3740261a
...@@ -33,3 +33,4 @@ bin/ ...@@ -33,3 +33,4 @@ bin/
/.idea/.idea.CAP /.idea/.idea.CAP
/.idea/.idea.CAP /.idea/.idea.CAP
/.idea /.idea
Properties
\ No newline at end of file
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26430.15 VisualStudioVersion = 15.0.26430.16
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9B2AE124-6636-4DE9-83A3-70360DABD0C4}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9B2AE124-6636-4DE9-83A3-70360DABD0C4}"
EndProject EndProject
...@@ -35,8 +35,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP", "src\DotNe ...@@ -35,8 +35,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP", "src\DotNe
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{3A6B6931-A123-477A-9469-8B468B5385AF}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{3A6B6931-A123-477A-9469-8B468B5385AF}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Kafka", "samples\Sample.Kafka\Sample.Kafka.csproj", "{2F095ED9-5BC9-4512-9013-A47685FB2508}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.Kafka", "src\DotNetCore.CAP.Kafka\DotNetCore.CAP.Kafka.csproj", "{C42CDE33-0878-4BA0-96F2-4CB7C8FDEAAD}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.Kafka", "src\DotNetCore.CAP.Kafka\DotNetCore.CAP.Kafka.csproj", "{C42CDE33-0878-4BA0-96F2-4CB7C8FDEAAD}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.RabbitMQ", "src\DotNetCore.CAP.RabbitMQ\DotNetCore.CAP.RabbitMQ.csproj", "{9961B80E-0718-4280-B2A0-271B003DE26B}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.RabbitMQ", "src\DotNetCore.CAP.RabbitMQ\DotNetCore.CAP.RabbitMQ.csproj", "{9961B80E-0718-4280-B2A0-271B003DE26B}"
...@@ -59,6 +57,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.SqlServer", ...@@ -59,6 +57,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.SqlServer",
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.SqlServer.Test", "test\DotNetCore.CAP.SqlServer.Test\DotNetCore.CAP.SqlServer.Test.csproj", "{DA00FA38-C4B9-4F55-8756-D480FBC1084F}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.SqlServer.Test", "test\DotNetCore.CAP.SqlServer.Test\DotNetCore.CAP.SqlServer.Test.csproj", "{DA00FA38-C4B9-4F55-8756-D480FBC1084F}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.MySql", "src\DotNetCore.CAP.MySql\DotNetCore.CAP.MySql.csproj", "{FA15685A-778A-4D2A-A2FE-27FAD2FFA65B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.MySql.Test", "test\DotNetCore.CAP.MySql.Test\DotNetCore.CAP.MySql.Test.csproj", "{80A84F62-1558-427B-BA74-B47AA8A665B5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.RabbitMQ.MySql", "samples\Sample.RabbitMQ.MySql\Sample.RabbitMQ.MySql.csproj", "{9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Kafka.SqlServer", "samples\Sample.Kafka.SqlServer\Sample.Kafka.SqlServer.csproj", "{AF17B956-B79E-48B7-9B5B-EB15A386B112}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
...@@ -69,10 +75,6 @@ Global ...@@ -69,10 +75,6 @@ Global
{E8AF8611-0EA4-4B19-BC48-87C57A87DC66}.Debug|Any CPU.Build.0 = Debug|Any CPU {E8AF8611-0EA4-4B19-BC48-87C57A87DC66}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E8AF8611-0EA4-4B19-BC48-87C57A87DC66}.Release|Any CPU.ActiveCfg = Release|Any CPU {E8AF8611-0EA4-4B19-BC48-87C57A87DC66}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E8AF8611-0EA4-4B19-BC48-87C57A87DC66}.Release|Any CPU.Build.0 = Release|Any CPU {E8AF8611-0EA4-4B19-BC48-87C57A87DC66}.Release|Any CPU.Build.0 = Release|Any CPU
{2F095ED9-5BC9-4512-9013-A47685FB2508}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2F095ED9-5BC9-4512-9013-A47685FB2508}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F095ED9-5BC9-4512-9013-A47685FB2508}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2F095ED9-5BC9-4512-9013-A47685FB2508}.Release|Any CPU.Build.0 = Release|Any CPU
{C42CDE33-0878-4BA0-96F2-4CB7C8FDEAAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C42CDE33-0878-4BA0-96F2-4CB7C8FDEAAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C42CDE33-0878-4BA0-96F2-4CB7C8FDEAAD}.Debug|Any CPU.Build.0 = Debug|Any CPU {C42CDE33-0878-4BA0-96F2-4CB7C8FDEAAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C42CDE33-0878-4BA0-96F2-4CB7C8FDEAAD}.Release|Any CPU.ActiveCfg = Release|Any CPU {C42CDE33-0878-4BA0-96F2-4CB7C8FDEAAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
...@@ -92,6 +94,22 @@ Global ...@@ -92,6 +94,22 @@ Global
{DA00FA38-C4B9-4F55-8756-D480FBC1084F}.Debug|Any CPU.Build.0 = Debug|Any CPU {DA00FA38-C4B9-4F55-8756-D480FBC1084F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DA00FA38-C4B9-4F55-8756-D480FBC1084F}.Release|Any CPU.ActiveCfg = Release|Any CPU {DA00FA38-C4B9-4F55-8756-D480FBC1084F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DA00FA38-C4B9-4F55-8756-D480FBC1084F}.Release|Any CPU.Build.0 = Release|Any CPU {DA00FA38-C4B9-4F55-8756-D480FBC1084F}.Release|Any CPU.Build.0 = Release|Any CPU
{FA15685A-778A-4D2A-A2FE-27FAD2FFA65B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA15685A-778A-4D2A-A2FE-27FAD2FFA65B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA15685A-778A-4D2A-A2FE-27FAD2FFA65B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA15685A-778A-4D2A-A2FE-27FAD2FFA65B}.Release|Any CPU.Build.0 = Release|Any CPU
{80A84F62-1558-427B-BA74-B47AA8A665B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{80A84F62-1558-427B-BA74-B47AA8A665B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{80A84F62-1558-427B-BA74-B47AA8A665B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{80A84F62-1558-427B-BA74-B47AA8A665B5}.Release|Any CPU.Build.0 = Release|Any CPU
{9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873}.Release|Any CPU.Build.0 = Release|Any CPU
{AF17B956-B79E-48B7-9B5B-EB15A386B112}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF17B956-B79E-48B7-9B5B-EB15A386B112}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF17B956-B79E-48B7-9B5B-EB15A386B112}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF17B956-B79E-48B7-9B5B-EB15A386B112}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
...@@ -99,11 +117,14 @@ Global ...@@ -99,11 +117,14 @@ Global
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{9E5A7F49-8E31-4A71-90CC-1DA9AEDA99EE} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0} {9E5A7F49-8E31-4A71-90CC-1DA9AEDA99EE} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0}
{E8AF8611-0EA4-4B19-BC48-87C57A87DC66} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} {E8AF8611-0EA4-4B19-BC48-87C57A87DC66} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{2F095ED9-5BC9-4512-9013-A47685FB2508} = {3A6B6931-A123-477A-9469-8B468B5385AF}
{C42CDE33-0878-4BA0-96F2-4CB7C8FDEAAD} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} {C42CDE33-0878-4BA0-96F2-4CB7C8FDEAAD} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{9961B80E-0718-4280-B2A0-271B003DE26B} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} {9961B80E-0718-4280-B2A0-271B003DE26B} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{F608B509-A99B-4AC7-8227-42051DD4A578} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0} {F608B509-A99B-4AC7-8227-42051DD4A578} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0}
{3B577468-6792-4EF1-9237-15180B176A24} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} {3B577468-6792-4EF1-9237-15180B176A24} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{DA00FA38-C4B9-4F55-8756-D480FBC1084F} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0} {DA00FA38-C4B9-4F55-8756-D480FBC1084F} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0}
{FA15685A-778A-4D2A-A2FE-27FAD2FFA65B} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{80A84F62-1558-427B-BA74-B47AA8A665B5} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0}
{9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873} = {3A6B6931-A123-477A-9469-8B468B5385AF}
{AF17B956-B79E-48B7-9B5B-EB15A386B112} = {3A6B6931-A123-477A-9469-8B468B5385AF}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal
...@@ -4,8 +4,10 @@ environment: ...@@ -4,8 +4,10 @@ environment:
BUILDING_ON_PLATFORM: win BUILDING_ON_PLATFORM: win
BuildEnvironment: appveyor BuildEnvironment: appveyor
Cap_SqlServer_ConnectionStringTemplate: Server=(local)\SQL2014;Database={0};User ID=sa;Password=Password12! Cap_SqlServer_ConnectionStringTemplate: Server=(local)\SQL2014;Database={0};User ID=sa;Password=Password12!
Cap_MySql_ConnectionStringTemplate: Server=localhost;Database={0};Uid=root;Pwd=Password12!
services: services:
- mssql2014 - mssql2014
- mysql
build_script: build_script:
- ps: ./ConfigureMSDTC.ps1 - ps: ./ConfigureMSDTC.ps1
- ps: ./build.ps1 - ps: ./build.ps1
......
...@@ -14,8 +14,8 @@ ...@@ -14,8 +14,8 @@
<PackageIconUrl>https://avatars2.githubusercontent.com/u/19404084</PackageIconUrl> <PackageIconUrl>https://avatars2.githubusercontent.com/u/19404084</PackageIconUrl>
<PackageProjectUrl>https://github.com/dotnetcore/CAP</PackageProjectUrl> <PackageProjectUrl>https://github.com/dotnetcore/CAP</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/dotnetcore/CAP/blob/master/LICENSE.txt</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/dotnetcore/CAP/blob/master/LICENSE.txt</PackageLicenseUrl>
<PackageTags>aspnetcore;cap;consistency</PackageTags> <PackageTags>eventbus;rabbitmq;kafka;cap;transaction;</PackageTags>
<Description>Eventually consistency in distributed architectures.</Description> <Description>EventBus and eventually consistency in distributed architectures.</Description>
</PropertyGroup> </PropertyGroup>
</Project> </Project>
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<VersionMajor>1</VersionMajor> <VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor> <VersionMinor>1</VersionMinor>
<VersionPatch>1</VersionPatch> <VersionPatch>0</VersionPatch>
<VersionQuality></VersionQuality> <VersionQuality></VersionQuality>
<VersionPrefix>$(VersionMajor).$(VersionMinor).$(VersionPatch)</VersionPrefix> <VersionPrefix>$(VersionMajor).$(VersionMinor).$(VersionPatch)</VersionPrefix>
</PropertyGroup> </PropertyGroup>
......
...@@ -6,8 +6,8 @@ namespace Sample.Kafka ...@@ -6,8 +6,8 @@ namespace Sample.Kafka
{ {
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {
optionsBuilder.UseSqlServer("Server=192.168.2.206;Initial Catalog=Test;User Id=cmswuliu;Password=h7xY81agBn*Veiu3;MultipleActiveResultSets=True"); //optionsBuilder.UseSqlServer("Server=192.168.2.206;Initial Catalog=Test;User Id=cmswuliu;Password=h7xY81agBn*Veiu3;MultipleActiveResultSets=True");
//optionsBuilder.UseSqlServer("Server=DESKTOP-M9R8T31;Initial Catalog=Test;User Id=sa;Password=P@ssw0rd;MultipleActiveResultSets=True"); optionsBuilder.UseSqlServer("Server=DESKTOP-M9R8T31;Initial Catalog=Sample.Kafka.SqlServer;User Id=sa;Password=P@ssw0rd;MultipleActiveResultSets=True");
} }
} }
} }
using System; using System;
using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using DotNetCore.CAP; using DotNetCore.CAP;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Sample.Kafka.Controllers namespace Sample.Kafka.Controllers
...@@ -9,46 +9,39 @@ namespace Sample.Kafka.Controllers ...@@ -9,46 +9,39 @@ namespace Sample.Kafka.Controllers
[Route("api/[controller]")] [Route("api/[controller]")]
public class ValuesController : Controller, ICapSubscribe public class ValuesController : Controller, ICapSubscribe
{ {
private readonly ICapPublisher _producer; private readonly ICapPublisher _capBus;
private readonly AppDbContext _dbContext ; private readonly AppDbContext _dbContext;
public ValuesController(ICapPublisher producer, AppDbContext dbContext) public ValuesController(ICapPublisher producer, AppDbContext dbContext)
{ {
_producer = producer; _capBus = producer;
_dbContext = dbContext; _dbContext = dbContext;
} }
[Route("/")] [Route("~/publish")]
public IActionResult Index() public IActionResult PublishMessage()
{ {
_capBus.Publish("sample.rabbitmq.mysql", "");
return Ok(); return Ok();
} }
public string ServerPath => ((IHostingEnvironment)HttpContext.RequestServices.GetService(typeof(IHostingEnvironment))).ContentRootPath;
[CapSubscribe("zzwl.topic.finace.callBack", Group = "test")] [Route("~/publishWithTrans")]
public void KafkaTest(Person person) public async Task<IActionResult> PublishMessageWithTransaction()
{ {
Console.WriteLine(DateTime.Now); using (var trans = await _dbContext.Database.BeginTransactionAsync())
}
[Route("~/send")]
public async Task<IActionResult> SendTopic()
{
using (var trans = _dbContext.Database.BeginTransaction())
{ {
await _producer.PublishAsync("zzwl.topic.finace.callBack",""); await _capBus.PublishAsync("sample.rabbitmq.mysql", "");
trans.Commit(); trans.Commit();
} }
return Ok(); return Ok();
} }
public class Person [NonAction]
[CapSubscribe("sample.kafka.sqlserver", Group = "test")]
public void KafkaTest()
{ {
public string Name { get; set; } Console.WriteLine("[sample.kafka.sqlserver] message received");
Debug.WriteLine("[sample.kafka.sqlserver] message received");
public int Age { get; set; }
} }
} }
} }
\ No newline at end of file
using System.IO; using System.IO;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
namespace Sample.Kafka namespace Sample.Kafka
{ {
public class Program public class Program
{ {
public static void Main(string[] args) { public static void Main(string[] args)
{
var config = new ConfigurationBuilder()
.AddCommandLine(args)
.AddEnvironmentVariables("ASPNETCORE_")
.Build();
var host = new WebHostBuilder() var host = new WebHostBuilder()
.UseConfiguration(config)
.UseKestrel() .UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory()) .UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration() .UseIISIntegration()
......
...@@ -2,14 +2,9 @@ ...@@ -2,14 +2,9 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework> <TargetFramework>netcoreapp1.1</TargetFramework>
<AssemblyName>Sample.Kafka.SqlServer</AssemblyName>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Remove="wwwroot\**" />
<Content Remove="wwwroot\**" />
<EmbeddedResource Remove="wwwroot\**" />
<None Remove="wwwroot\**" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" /> <PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
...@@ -17,6 +12,7 @@ ...@@ -17,6 +12,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.2" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.2" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
......
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
...@@ -8,19 +7,6 @@ namespace Sample.Kafka ...@@ -8,19 +7,6 @@ namespace Sample.Kafka
{ {
public class Startup public class Startup
{ {
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddDbContext<AppDbContext>(); services.AddDbContext<AppDbContext>();
...@@ -28,18 +14,15 @@ namespace Sample.Kafka ...@@ -28,18 +14,15 @@ namespace Sample.Kafka
services.AddCap(x => services.AddCap(x =>
{ {
x.UseEntityFramework<AppDbContext>(); x.UseEntityFramework<AppDbContext>();
//x.UseSqlServer("Server=DESKTOP-M9R8T31;Initial Catalog=Test;User Id=sa;Password=P@ssw0rd;MultipleActiveResultSets=True");
x.UseKafka("localhost:9092"); x.UseKafka("localhost:9092");
}); });
// Add framework services.
services.AddMvc(); services.AddMvc();
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{ {
loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddConsole();
loggerFactory.AddDebug(); loggerFactory.AddDebug();
app.UseMvc(); app.UseMvc();
......
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:49909/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "api/values",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Sample.Kafka": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "api/values",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:49910"
}
}
}
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace Sample.RabbitMQ.MySql
{
public class AppDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySql("Server=localhost;Database=Sample.RabbitMQ.MySql;Uid=root;Pwd=123123;");
}
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using DotNetCore.CAP;
using Microsoft.AspNetCore.Mvc;
namespace Sample.RabbitMQ.MySql.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
private readonly AppDbContext _dbContext;
private readonly ICapPublisher _capBus;
public ValuesController(AppDbContext dbContext, ICapPublisher capPublisher)
{
_dbContext = dbContext;
_capBus = capPublisher;
}
[Route("~/publish")]
public IActionResult PublishMessage()
{
_capBus.Publish("sample.kafka.sqlserver", "");
return Ok();
}
[Route("~/publishWithTrans")]
public async Task<IActionResult> PublishMessageWithTransaction()
{
using (var trans = await _dbContext.Database.BeginTransactionAsync())
{
await _capBus.PublishAsync("sample.kafka.sqlserver", "");
trans.Commit();
}
return Ok();
}
[NonAction]
[CapSubscribe("sample.rabbitmq.mysql")]
public void ReceiveMessage()
{
Console.WriteLine("[sample.rabbitmq.mysql] message received");
Debug.WriteLine("[sample.rabbitmq.mysql] message received");
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
namespace Sample.RabbitMQ.MySql
{
public class Program
{
public static void Main(string[] args)
{
var config = new ConfigurationBuilder()
.AddCommandLine(args)
.AddEnvironmentVariables("ASPNETCORE_")
.Build();
var host = new WebHostBuilder()
.UseConfiguration(config)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.2" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="1.1.2" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql.Design" Version="1.1.2" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="1.0.1" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotNetCore.CAP.Kafka\DotNetCore.CAP.Kafka.csproj" />
<ProjectReference Include="..\..\src\DotNetCore.CAP.MySql\DotNetCore.CAP.MySql.csproj" />
<ProjectReference Include="..\..\src\DotNetCore.CAP\DotNetCore.CAP.csproj" />
</ItemGroup>
</Project>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Sample.RabbitMQ.MySql
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>();
services.AddCap(x =>
{
x.UseEntityFramework<AppDbContext>();
x.UseKafka("localhost:9092");
});
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
loggerFactory.AddDebug();
app.UseMvc();
app.UseCap();
}
}
}
...@@ -5,7 +5,7 @@ using Microsoft.Extensions.DependencyInjection; ...@@ -5,7 +5,7 @@ using Microsoft.Extensions.DependencyInjection;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace DotNetCore.CAP namespace DotNetCore.CAP
{ {
public class KafkaCapOptionsExtension : ICapOptionsExtension internal sealed class KafkaCapOptionsExtension : ICapOptionsExtension
{ {
private readonly Action<KafkaOptions> _configure; private readonly Action<KafkaOptions> _configure;
...@@ -16,12 +16,10 @@ namespace DotNetCore.CAP ...@@ -16,12 +16,10 @@ namespace DotNetCore.CAP
public void AddServices(IServiceCollection services) public void AddServices(IServiceCollection services)
{ {
services.Configure(_configure);
var kafkaOptions = new KafkaOptions(); var kafkaOptions = new KafkaOptions();
_configure(kafkaOptions); _configure?.Invoke(kafkaOptions);
services.AddSingleton(kafkaOptions); services.AddSingleton(kafkaOptions);
services.AddSingleton<IConsumerClientFactory, KafkaConsumerClientFactory>(); services.AddSingleton<IConsumerClientFactory, KafkaConsumerClientFactory>();
services.AddTransient<IQueueExecutor, PublishQueueExecutor>(); services.AddTransient<IQueueExecutor, PublishQueueExecutor>();
} }
......
...@@ -21,30 +21,34 @@ namespace DotNetCore.CAP ...@@ -21,30 +21,34 @@ namespace DotNetCore.CAP
/// Topic configuration parameters are specified via the "default.topic.config" sub-dictionary config parameter. /// Topic configuration parameters are specified via the "default.topic.config" sub-dictionary config parameter.
/// </para> /// </para>
/// </summary> /// </summary>
public IDictionary<string, object> MainConfig { get; private set; } public readonly IDictionary<string, object> MainConfig;
/// <summary> /// <summary>
/// The `bootstrap.servers` item config of `MainConfig`. /// The `bootstrap.servers` item config of <see cref="MainConfig"/>.
/// <para> /// <para>
/// Initial list of brokers as a CSV list of broker host or host:port. /// Initial list of brokers as a CSV list of broker host or host:port.
/// </para> /// </para>
/// </summary> /// </summary>
public string Servers { get; set; } public string Servers { get; set; }
internal IEnumerable<KeyValuePair<string, object>> AsRdkafkaConfig() internal IEnumerable<KeyValuePair<string, object>> AskafkaConfig()
{ {
if (MainConfig.ContainsKey("bootstrap.servers")) if (MainConfig.ContainsKey("bootstrap.servers"))
{
return MainConfig.AsEnumerable(); return MainConfig.AsEnumerable();
}
if (string.IsNullOrEmpty(Servers)) if (string.IsNullOrWhiteSpace(Servers))
{ {
throw new ArgumentNullException(nameof(Servers)); throw new ArgumentNullException(nameof(Servers));
} }
else
{ MainConfig.Add("bootstrap.servers", Servers);
MainConfig.Add("bootstrap.servers", Servers);
} MainConfig["queue.buffering.max.ms"] = "10";
MainConfig["socket.blocking.max.ms"] = "10";
MainConfig["enable.auto.commit"] = "false"; MainConfig["enable.auto.commit"] = "false";
return MainConfig.AsEnumerable(); return MainConfig.AsEnumerable();
} }
} }
......
...@@ -6,6 +6,10 @@ namespace Microsoft.Extensions.DependencyInjection ...@@ -6,6 +6,10 @@ namespace Microsoft.Extensions.DependencyInjection
{ {
public static class CapOptionsExtensions public static class CapOptionsExtensions
{ {
/// <summary>
/// Configuration to use kafka in CAP.
/// </summary>
/// <param name="bootstrapServers">Kafka bootstrap server urls.</param>
public static CapOptions UseKafka(this CapOptions options, string bootstrapServers) public static CapOptions UseKafka(this CapOptions options, string bootstrapServers)
{ {
return options.UseKafka(opt => return options.UseKafka(opt =>
...@@ -14,6 +18,11 @@ namespace Microsoft.Extensions.DependencyInjection ...@@ -14,6 +18,11 @@ namespace Microsoft.Extensions.DependencyInjection
}); });
} }
/// <summary>
/// Configuration to use kafka in CAP.
/// </summary>
/// <param name="configure">Provides programmatic configuration for the kafka .</param>
/// <returns></returns>
public static CapOptions UseKafka(this CapOptions options, Action<KafkaOptions> configure) public static CapOptions UseKafka(this CapOptions options, Action<KafkaOptions> configure)
{ {
if (configure == null) throw new ArgumentNullException(nameof(configure)); if (configure == null) throw new ArgumentNullException(nameof(configure));
......
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace DotNetCore.CAP namespace DotNetCore.CAP
{ {
/// <summary>
/// An attribute for subscribe Kafka messages.
/// </summary>
public class CapSubscribeAttribute : TopicAttribute public class CapSubscribeAttribute : TopicAttribute
{ {
public CapSubscribeAttribute(string name) public CapSubscribeAttribute(string name)
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Confluent.Kafka" Version="0.9.5" /> <PackageReference Include="Confluent.Kafka" Version="0.11.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
......
using System; using System;
using System.Collections.Generic;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using Confluent.Kafka; using Confluent.Kafka;
...@@ -6,36 +7,37 @@ using Confluent.Kafka.Serialization; ...@@ -6,36 +7,37 @@ using Confluent.Kafka.Serialization;
namespace DotNetCore.CAP.Kafka namespace DotNetCore.CAP.Kafka
{ {
public class KafkaConsumerClient : IConsumerClient internal sealed class KafkaConsumerClient : IConsumerClient
{ {
private readonly string _groupId; private readonly string _groupId;
private readonly KafkaOptions _kafkaOptions; private readonly KafkaOptions _kafkaOptions;
private Consumer<Null, string> _consumerClient; private Consumer<Null, string> _consumerClient;
public event EventHandler<MessageContext> MessageReceieved; public event EventHandler<MessageContext> OnMessageReceieved;
public event EventHandler<string> OnError;
public IDeserializer<string> StringDeserializer { get; set; } public IDeserializer<string> StringDeserializer { get; set; }
public KafkaConsumerClient(string groupId, KafkaOptions options) public KafkaConsumerClient(string groupId, KafkaOptions options)
{ {
_groupId = groupId; _groupId = groupId;
_kafkaOptions = options; _kafkaOptions = options ?? throw new ArgumentNullException(nameof(options));
StringDeserializer = new StringDeserializer(Encoding.UTF8); StringDeserializer = new StringDeserializer(Encoding.UTF8);
} }
public void Subscribe(string topic) public void Subscribe(IEnumerable<string> topics)
{ {
Subscribe(topic, 0); if (topics == null)
} throw new ArgumentNullException(nameof(topics));
public void Subscribe(string topicName, int partition)
{
if (_consumerClient == null) if (_consumerClient == null)
{ {
InitKafkaClient(); InitKafkaClient();
} }
_consumerClient.Assignment.Add(new TopicPartition(topicName, partition));
_consumerClient.Subscribe(topicName); //_consumerClient.Assign(topics.Select(x=> new TopicPartition(x, 0)));
_consumerClient.Subscribe(topics);
} }
public void Listening(TimeSpan timeout, CancellationToken cancellationToken) public void Listening(TimeSpan timeout, CancellationToken cancellationToken)
...@@ -63,10 +65,11 @@ namespace DotNetCore.CAP.Kafka ...@@ -63,10 +65,11 @@ namespace DotNetCore.CAP.Kafka
{ {
_kafkaOptions.MainConfig.Add("group.id", _groupId); _kafkaOptions.MainConfig.Add("group.id", _groupId);
var config = _kafkaOptions.AsRdkafkaConfig(); var config = _kafkaOptions.AskafkaConfig();
_consumerClient = new Consumer<Null, string>(config, null, StringDeserializer); _consumerClient = new Consumer<Null, string>(config, null, StringDeserializer);
_consumerClient.OnMessage += ConsumerClient_OnMessage; _consumerClient.OnMessage += ConsumerClient_OnMessage;
_consumerClient.OnError += ConsumerClient_OnError;
} }
private void ConsumerClient_OnMessage(object sender, Message<Null, string> e) private void ConsumerClient_OnMessage(object sender, Message<Null, string> e)
...@@ -77,7 +80,13 @@ namespace DotNetCore.CAP.Kafka ...@@ -77,7 +80,13 @@ namespace DotNetCore.CAP.Kafka
Name = e.Topic, Name = e.Topic,
Content = e.Value Content = e.Value
}; };
MessageReceieved?.Invoke(sender, message);
OnMessageReceieved?.Invoke(sender, message);
}
private void ConsumerClient_OnError(object sender, Error e)
{
OnError?.Invoke(sender, e.Reason);
} }
#endregion private methods #endregion private methods
......
using Microsoft.Extensions.Options; using System;
using Microsoft.Extensions.Options;
namespace DotNetCore.CAP.Kafka namespace DotNetCore.CAP.Kafka
{ {
public class KafkaConsumerClientFactory : IConsumerClientFactory internal sealed class KafkaConsumerClientFactory : IConsumerClientFactory
{ {
private readonly KafkaOptions _kafkaOptions; private readonly KafkaOptions _kafkaOptions;
public KafkaConsumerClientFactory(IOptions<KafkaOptions> kafkaOptions) public KafkaConsumerClientFactory(KafkaOptions kafkaOptions)
{ {
_kafkaOptions = kafkaOptions.Value; _kafkaOptions = kafkaOptions;
} }
public IConsumerClient Create(string groupId) public IConsumerClient Create(string groupId)
......
...@@ -2,52 +2,56 @@ ...@@ -2,52 +2,56 @@
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Confluent.Kafka; using Confluent.Kafka;
using Confluent.Kafka.Serialization;
using DotNetCore.CAP.Processor.States; using DotNetCore.CAP.Processor.States;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace DotNetCore.CAP.Kafka namespace DotNetCore.CAP.Kafka
{ {
public class PublishQueueExecutor : BasePublishQueueExecutor internal class PublishQueueExecutor : BasePublishQueueExecutor
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly KafkaOptions _kafkaOptions; private readonly KafkaOptions _kafkaOptions;
public PublishQueueExecutor(IStateChanger stateChanger, public PublishQueueExecutor(IStateChanger stateChanger,
IOptions<KafkaOptions> options, KafkaOptions options,
ILogger<PublishQueueExecutor> logger) ILogger<PublishQueueExecutor> logger)
: base(stateChanger, logger) : base(stateChanger, logger)
{ {
_logger = logger; _logger = logger;
_kafkaOptions = options.Value; _kafkaOptions = options;
} }
public override Task<OperateResult> PublishAsync(string keyName, string content) public override Task<OperateResult> PublishAsync(string keyName, string content)
{ {
try try
{ {
var config = _kafkaOptions.AsRdkafkaConfig(); var config = _kafkaOptions.AskafkaConfig();
using (var producer = new Producer<Null, string>(config, null, new StringSerializer(Encoding.UTF8))) var contentBytes = Encoding.UTF8.GetBytes(content);
using (var producer = new Producer(config))
{ {
producer.ProduceAsync(keyName, null, content); var message = producer.ProduceAsync(keyName, null, contentBytes).Result;
producer.Flush();
}
_logger.LogDebug($"kafka topic message [{keyName}] has been published."); if (!message.Error.HasError)
{
_logger.LogDebug($"kafka topic message [{keyName}] has been published.");
return Task.FromResult(OperateResult.Success); return Task.FromResult(OperateResult.Success);
}
else
{
return Task.FromResult(OperateResult.Failed(new OperateError
{
Code = message.Error.Code.ToString(),
Description = message.Error.Reason
}));
}
}
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError($"kafka topic message [{keyName}] has benn raised an exception of sending. the exception is: {ex.Message}"); _logger.LogError($"kafka topic message [{keyName}] has benn raised an exception of sending. the exception is: {ex.Message}");
return Task.FromResult(OperateResult.Failed(ex, return Task.FromResult(OperateResult.Failed(ex));
new OperateError()
{
Code = ex.HResult.ToString(),
Description = ex.Message
}));
} }
} }
} }
......
using System;
// ReSharper disable once CheckNamespace
namespace DotNetCore.CAP
{
public class EFOptions
{
/// <summary>
/// EF dbcontext type.
/// </summary>
internal Type DbContextType { get; set; }
}
}
\ No newline at end of file
using System;
using DotNetCore.CAP.MySql;
using DotNetCore.CAP.Processor;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
// ReSharper disable once CheckNamespace
namespace DotNetCore.CAP
{
internal class MySqlCapOptionsExtension : ICapOptionsExtension
{
private readonly Action<MySqlOptions> _configure;
public MySqlCapOptionsExtension(Action<MySqlOptions> configure)
{
_configure = configure;
}
public void AddServices(IServiceCollection services)
{
services.AddSingleton<IStorage, MySqlStorage>();
services.AddScoped<IStorageConnection, MySqlStorageConnection>();
services.AddScoped<ICapPublisher, CapPublisher>();
services.AddTransient<IAdditionalProcessor, DefaultAdditionalProcessor>();
var mysqlOptions = new MySqlOptions();
_configure(mysqlOptions);
if (mysqlOptions.DbContextType != null)
{
var provider = TempBuildService(services);
var dbContextObj = provider.GetService(mysqlOptions.DbContextType);
var dbContext = (DbContext)dbContextObj;
mysqlOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString;
}
services.AddSingleton(mysqlOptions);
}
private IServiceProvider TempBuildService(IServiceCollection services)
{
return services.BuildServiceProvider();
}
}
}
\ No newline at end of file
// ReSharper disable once CheckNamespace
namespace DotNetCore.CAP
{
public class MySqlOptions : EFOptions
{
/// <summary>
/// Gets or sets the database's connection string that will be used to store database entities.
/// </summary>
public string ConnectionString { get; set; }
public string TableNamePrefix { get; set; } = "cap";
}
}
\ No newline at end of file
using System;
using DotNetCore.CAP;
using Microsoft.EntityFrameworkCore;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
{
public static class CapOptionsExtensions
{
public static CapOptions UseMySql(this CapOptions options, string connectionString)
{
return options.UseMySql(opt =>
{
opt.ConnectionString = connectionString;
});
}
public static CapOptions UseMySql(this CapOptions options, Action<MySqlOptions> configure)
{
if (configure == null) throw new ArgumentNullException(nameof(configure));
options.RegisterExtension(new MySqlCapOptionsExtension(configure));
return options;
}
public static CapOptions UseEntityFramework<TContext>(this CapOptions options)
where TContext : DbContext
{
return options.UseEntityFramework<TContext>(opt =>
{
opt.DbContextType = typeof(TContext);
});
}
public static CapOptions UseEntityFramework<TContext>(this CapOptions options, Action<EFOptions> configure)
where TContext : DbContext
{
if (configure == null) throw new ArgumentNullException(nameof(configure));
var efOptions = new EFOptions { DbContextType = typeof(TContext) };
configure(efOptions);
options.RegisterExtension(new MySqlCapOptionsExtension(configure));
return options;
}
}
}
\ No newline at end of file
using System;
using System.Data;
using System.Threading.Tasks;
using Dapper;
using DotNetCore.CAP.Infrastructure;
using DotNetCore.CAP.Models;
using DotNetCore.CAP.Processor;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Logging;
namespace DotNetCore.CAP.MySql
{
public class CapPublisher : ICapPublisher
{
private readonly ILogger _logger;
private readonly MySqlOptions _options;
private readonly DbContext _dbContext;
protected bool IsCapOpenedTrans { get; set; }
protected bool IsUsingEF { get; }
protected IServiceProvider ServiceProvider { get; }
public CapPublisher(IServiceProvider provider,
ILogger<CapPublisher> logger,
MySqlOptions options)
{
ServiceProvider = provider;
_logger = logger;
_options = options;
if (_options.DbContextType != null)
{
IsUsingEF = true;
_dbContext = (DbContext)ServiceProvider.GetService(_options.DbContextType);
}
}
public void Publish<T>(string name, T contentObj)
{
CheckIsUsingEF(name);
var content = Serialize(contentObj);
PublishCore(name, content);
}
public Task PublishAsync<T>(string name, T contentObj)
{
CheckIsUsingEF(name);
var content = Serialize(contentObj);
return PublishCoreAsync(name, content);
}
public void Publish<T>(string name, T contentObj, IDbConnection dbConnection, IDbTransaction dbTransaction = null)
{
CheckIsAdoNet(name);
PrepareConnection(dbConnection, ref dbTransaction);
var content = Serialize(contentObj);
PublishWithTrans(name, content, dbConnection, dbTransaction);
}
public Task PublishAsync<T>(string name, T contentObj, IDbConnection dbConnection, IDbTransaction dbTransaction = null)
{
CheckIsAdoNet(name);
PrepareConnection(dbConnection, ref dbTransaction);
var content = Serialize(contentObj);
return PublishWithTransAsync(name, content, dbConnection, dbTransaction);
}
#region private methods
private string Serialize<T>(T obj)
{
string content = string.Empty;
if (Helper.IsComplexType(typeof(T)))
{
content = Helper.ToJson(obj);
}
else
{
content = obj?.ToString();
}
return content;
}
private void PrepareConnection(IDbConnection dbConnection, ref IDbTransaction dbTransaction)
{
if (dbConnection == null)
throw new ArgumentNullException(nameof(dbConnection));
if (dbConnection.State != ConnectionState.Open)
dbConnection.Open();
if (dbTransaction == null)
{
IsCapOpenedTrans = true;
dbTransaction = dbConnection.BeginTransaction(IsolationLevel.ReadCommitted);
}
}
private void CheckIsUsingEF(string name)
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (!IsUsingEF)
throw new InvalidOperationException("If you are using the EntityFramework, you need to configure the DbContextType first." +
" otherwise you need to use overloaded method with IDbConnection and IDbTransaction.");
}
private void CheckIsAdoNet(string name)
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (IsUsingEF)
throw new InvalidOperationException("If you are using the EntityFramework, you do not need to use this overloaded.");
}
private async Task PublishCoreAsync(string name, string content)
{
var connection = _dbContext.Database.GetDbConnection();
var transaction = _dbContext.Database.CurrentTransaction;
if (transaction == null)
{
IsCapOpenedTrans = true;
transaction = await _dbContext.Database.BeginTransactionAsync(IsolationLevel.ReadCommitted);
}
var dbTransaction = transaction.GetDbTransaction();
await PublishWithTransAsync(name, content, connection, dbTransaction);
}
private void PublishCore(string name, string content)
{
var connection = _dbContext.Database.GetDbConnection();
var transaction = _dbContext.Database.CurrentTransaction;
if (transaction == null)
{
IsCapOpenedTrans = true;
transaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted);
}
var dbTransaction = transaction.GetDbTransaction();
PublishWithTrans(name, content, connection, dbTransaction);
}
private async Task PublishWithTransAsync(string name, string content, IDbConnection dbConnection, IDbTransaction dbTransaction)
{
var message = new CapPublishedMessage
{
Name = name,
Content = content,
StatusName = StatusName.Scheduled
};
await dbConnection.ExecuteAsync(PrepareSql(), message, transaction: dbTransaction);
_logger.LogInformation("Message has been persisted in the database. name:" + name);
if (IsCapOpenedTrans)
{
dbTransaction.Commit();
dbTransaction.Dispose();
dbConnection.Dispose();
}
PublishQueuer.PulseEvent.Set();
}
private void PublishWithTrans(string name, string content, IDbConnection dbConnection, IDbTransaction dbTransaction)
{
var message = new CapPublishedMessage
{
Name = name,
Content = content,
StatusName = StatusName.Scheduled
};
var count = dbConnection.Execute(PrepareSql(), message, transaction: dbTransaction);
_logger.LogInformation("Message has been persisted in the database. name:" + name);
if (IsCapOpenedTrans)
{
dbTransaction.Commit();
dbTransaction.Dispose();
dbConnection.Dispose();
}
PublishQueuer.PulseEvent.Set();
}
private string PrepareSql()
{
return $"INSERT INTO `{_options.TableNamePrefix}.published` (`Name`,`Content`,`Retries`,`Added`,`ExpiresAt`,`StatusName`)VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName)";
}
#endregion private methods
}
}
\ No newline at end of file
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\build\common.props" />
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
<AssemblyName>DotNetCore.CAP.MySql</AssemblyName>
<PackageId>DotNetCore.CAP.MySql</PackageId>
<NetStandardImplicitPackageVersion>1.6.1</NetStandardImplicitPackageVersion>
<PackageTargetFallback>$(PackageTargetFallback);dnxcore50</PackageTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="1.50.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="1.1.2" />
<PackageReference Include="MySqlConnector" Version="0.23.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DotNetCore.CAP\DotNetCore.CAP.csproj" />
</ItemGroup>
</Project>
using DotNetCore.CAP.Models;
namespace DotNetCore.CAP.MySql
{
internal class FetchedMessage
{
public int MessageId { get; set; }
public MessageType MessageType { get; set; }
}
}
\ No newline at end of file
using System;
using System.Threading.Tasks;
using Dapper;
using DotNetCore.CAP.Processor;
using Microsoft.Extensions.Logging;
using MySql.Data.MySqlClient;
namespace DotNetCore.CAP.MySql
{
internal class DefaultAdditionalProcessor : IAdditionalProcessor
{
private readonly IServiceProvider _provider;
private readonly ILogger _logger;
private readonly MySqlOptions _options;
private const int MaxBatch = 1000;
private readonly TimeSpan _delay = TimeSpan.FromSeconds(1);
private readonly TimeSpan _waitingInterval = TimeSpan.FromHours(2);
public DefaultAdditionalProcessor(
IServiceProvider provider,
ILogger<DefaultAdditionalProcessor> logger,
MySqlOptions mysqlOptions)
{
_logger = logger;
_provider = provider;
_options = mysqlOptions;
}
public async Task ProcessAsync(ProcessingContext context)
{
_logger.LogDebug("Collecting expired entities.");
var tables = new string[]{
$"{_options.TableNamePrefix}.published",
$"{_options.TableNamePrefix}.received"
};
foreach (var table in tables)
{
var removedCount = 0;
do
{
using (var connection = new MySqlConnection(_options.ConnectionString))
{
removedCount = await connection.ExecuteAsync($@"DELETE FROM `{table}` WHERE ExpiresAt < @now limit @count;",
new { now = DateTime.Now, count = MaxBatch });
}
if (removedCount != 0)
{
await context.WaitAsync(_delay);
context.ThrowIfStopping();
}
} while (removedCount != 0);
}
await context.WaitAsync(_waitingInterval);
}
}
}
\ No newline at end of file
using System;
using System.Data;
using System.Threading;
using Dapper;
using DotNetCore.CAP.Models;
namespace DotNetCore.CAP.MySql
{
public class MySqlFetchedMessage : IFetchedMessage
{
private readonly IDbConnection _connection;
private readonly IDbTransaction _transaction;
private readonly Timer _timer;
private static readonly TimeSpan KeepAliveInterval = TimeSpan.FromMinutes(1);
private readonly object _lockObject = new object();
public MySqlFetchedMessage(int messageId,
MessageType type,
IDbConnection connection,
IDbTransaction transaction)
{
MessageId = messageId;
MessageType = type;
_connection = connection;
_transaction = transaction;
_timer = new Timer(ExecuteKeepAliveQuery, null, KeepAliveInterval, KeepAliveInterval);
}
public int MessageId { get; }
public MessageType MessageType { get; }
public void RemoveFromQueue()
{
lock (_lockObject)
{
_transaction.Commit();
}
}
public void Requeue()
{
lock (_lockObject)
{
_transaction.Rollback();
}
}
public void Dispose()
{
lock (_lockObject)
{
_timer?.Dispose();
_transaction.Dispose();
_connection.Dispose();
}
}
private void ExecuteKeepAliveQuery(object obj)
{
lock (_lockObject)
{
try
{
_connection?.Execute("SELECT 1", _transaction);
}
catch
{
// ignored
}
}
}
}
}
\ No newline at end of file
using System.Threading;
using System.Threading.Tasks;
using Dapper;
using Microsoft.Extensions.Logging;
using MySql.Data.MySqlClient;
namespace DotNetCore.CAP.MySql
{
public class MySqlStorage : IStorage
{
private readonly MySqlOptions _options;
private readonly ILogger _logger;
public MySqlStorage(ILogger<MySqlStorage> logger, MySqlOptions options)
{
_options = options;
_logger = logger;
}
public async Task InitializeAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested) return;
var sql = CreateDbTablesScript(_options.TableNamePrefix);
using (var connection = new MySqlConnection(_options.ConnectionString))
{
await connection.ExecuteAsync(sql);
}
_logger.LogDebug("Ensuring all create database tables script are applied.");
}
protected virtual string CreateDbTablesScript(string prefix)
{
var batchSql =
$@"
CREATE TABLE IF NOT EXISTS `{prefix}.queue` (
`MessageId` int(11) NOT NULL,
`MessageType` tinyint(4) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `{prefix}.received` (
`Id` int(127) NOT NULL AUTO_INCREMENT,
`Name` varchar(400) NOT NULL,
`Group` varchar(200) DEFAULT NULL,
`Content` longtext,
`Retries` int(11) DEFAULT NULL,
`Added` datetime(6) NOT NULL,
`ExpiresAt` datetime(6) DEFAULT NULL,
`StatusName` varchar(50) NOT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `{prefix}.published` (
`Id` int(127) NOT NULL AUTO_INCREMENT,
`Name` varchar(200) NOT NULL,
`Content` longtext,
`Retries` int(11) DEFAULT NULL,
`Added` datetime(6) NOT NULL,
`ExpiresAt` datetime(6) DEFAULT NULL,
`StatusName` varchar(40) NOT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;";
return batchSql;
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
using Dapper;
using DotNetCore.CAP.Infrastructure;
using DotNetCore.CAP.Models;
using MySql.Data.MySqlClient;
namespace DotNetCore.CAP.MySql
{
public class MySqlStorageConnection : IStorageConnection
{
private readonly MySqlOptions _options;
private readonly string _prefix;
public MySqlStorageConnection(MySqlOptions options)
{
_options = options;
_prefix = _options.TableNamePrefix;
}
public MySqlOptions Options => _options;
public IStorageTransaction CreateTransaction()
{
return new MySqlStorageTransaction(this);
}
public async Task<CapPublishedMessage> GetPublishedMessageAsync(int id)
{
var sql = $@"SELECT * FROM `{_prefix}.published` WHERE `Id`={id};";
using (var connection = new MySqlConnection(_options.ConnectionString))
{
return await connection.QueryFirstOrDefaultAsync<CapPublishedMessage>(sql);
}
}
public Task<IFetchedMessage> FetchNextMessageAsync()
{
//Last execute statement(FOR UPDATE to fix dirty read) :
//SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
//START TRANSACTION;
//SELECT MessageId,MessageType FROM `{_prefix}.queue` LIMIT 1 FOR UPDATE;
//DELETE FROM `{_prefix}.queue` LIMIT 1;
//COMMIT;
var sql = $@"
SELECT `MessageId`,`MessageType` FROM `{_prefix}.queue` LIMIT 1 FOR UPDATE;
DELETE FROM `{_prefix}.queue` LIMIT 1;";
return FetchNextMessageCoreAsync(sql);
}
public async Task<CapPublishedMessage> GetNextPublishedMessageToBeEnqueuedAsync()
{
var sql = $"SELECT * FROM `{_prefix}.published` WHERE `StatusName` = '{StatusName.Scheduled}' LIMIT 1;";
using (var connection = new MySqlConnection(_options.ConnectionString))
{
return await connection.QueryFirstOrDefaultAsync<CapPublishedMessage>(sql);
}
}
public async Task<IEnumerable<CapPublishedMessage>> GetFailedPublishedMessages()
{
var sql = $"SELECT * FROM `{_prefix}.published` WHERE `StatusName` = '{StatusName.Failed}';";
using (var connection = new MySqlConnection(_options.ConnectionString))
{
return await connection.QueryAsync<CapPublishedMessage>(sql);
}
}
// CapReceviedMessage
public async Task StoreReceivedMessageAsync(CapReceivedMessage message)
{
if (message == null) throw new ArgumentNullException(nameof(message));
var sql = $@"
INSERT INTO `{_prefix}.received`(`Name`,`Group`,`Content`,`Retries`,`Added`,`ExpiresAt`,`StatusName`)
VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);";
using (var connection = new MySqlConnection(_options.ConnectionString))
{
await connection.ExecuteAsync(sql, message);
}
}
public async Task<CapReceivedMessage> GetReceivedMessageAsync(int id)
{
var sql = $@"SELECT * FROM `{_prefix}.received` WHERE Id={id};";
using (var connection = new MySqlConnection(_options.ConnectionString))
{
return await connection.QueryFirstOrDefaultAsync<CapReceivedMessage>(sql);
}
}
public async Task<CapReceivedMessage> GetNextReceviedMessageToBeEnqueuedAsync()
{
var sql = $"SELECT * FROM `{_prefix}.received` WHERE `StatusName` = '{StatusName.Scheduled}' LIMIT 1;";
using (var connection = new MySqlConnection(_options.ConnectionString))
{
return await connection.QueryFirstOrDefaultAsync<CapReceivedMessage>(sql);
}
}
public async Task<IEnumerable<CapReceivedMessage>> GetFailedReceviedMessages()
{
var sql = $"SELECT * FROM `{_prefix}.received` WHERE `StatusName` = '{StatusName.Failed}';";
using (var connection = new MySqlConnection(_options.ConnectionString))
{
return await connection.QueryAsync<CapReceivedMessage>(sql);
}
}
public void Dispose()
{
}
private async Task<IFetchedMessage> FetchNextMessageCoreAsync(string sql, object args = null)
{
//here don't use `using` to dispose
var connection = new MySqlConnection(_options.ConnectionString);
await connection.OpenAsync();
var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted);
FetchedMessage fetchedMessage = null;
try
{
fetchedMessage = await connection.QueryFirstOrDefaultAsync<FetchedMessage>(sql, args, transaction);
}
catch (MySqlException)
{
transaction.Dispose();
throw;
}
if (fetchedMessage == null)
{
transaction.Rollback();
transaction.Dispose();
connection.Dispose();
return null;
}
return new MySqlFetchedMessage(fetchedMessage.MessageId, fetchedMessage.MessageType, connection, transaction);
}
}
}
\ No newline at end of file
using System;
using System.Data;
using System.Threading.Tasks;
using Dapper;
using DotNetCore.CAP.Models;
using MySql.Data.MySqlClient;
namespace DotNetCore.CAP.MySql
{
public class MySqlStorageTransaction : IStorageTransaction, IDisposable
{
private readonly string _prefix;
private readonly IDbTransaction _dbTransaction;
private readonly IDbConnection _dbConnection;
public MySqlStorageTransaction(MySqlStorageConnection connection)
{
var options = connection.Options;
_prefix = options.TableNamePrefix;
_dbConnection = new MySqlConnection(options.ConnectionString);
_dbConnection.Open();
_dbTransaction = _dbConnection.BeginTransaction(IsolationLevel.ReadCommitted);
}
public void UpdateMessage(CapPublishedMessage message)
{
if (message == null) throw new ArgumentNullException(nameof(message));
var sql = $"UPDATE `{_prefix}.published` SET `Retries` = @Retries,`ExpiresAt` = @ExpiresAt,`StatusName`=@StatusName WHERE `Id`=@Id;";
_dbConnection.Execute(sql, message, _dbTransaction);
}
public void UpdateMessage(CapReceivedMessage message)
{
if (message == null) throw new ArgumentNullException(nameof(message));
var sql = $"UPDATE `{_prefix}.received` SET `Retries` = @Retries,`ExpiresAt` = @ExpiresAt,`StatusName`=@StatusName WHERE `Id`=@Id;";
_dbConnection.Execute(sql, message, _dbTransaction);
}
public void EnqueueMessage(CapPublishedMessage message)
{
if (message == null) throw new ArgumentNullException(nameof(message));
var sql = $"INSERT INTO `{_prefix}.queue` values(@MessageId,@MessageType);";
_dbConnection.Execute(sql, new CapQueue { MessageId = message.Id, MessageType = MessageType.Publish }, _dbTransaction);
}
public void EnqueueMessage(CapReceivedMessage message)
{
if (message == null) throw new ArgumentNullException(nameof(message));
var sql = $"INSERT INTO `{_prefix}.queue` values(@MessageId,@MessageType);";
_dbConnection.Execute(sql, new CapQueue { MessageId = message.Id, MessageType = MessageType.Subscribe }, _dbTransaction);
}
public Task CommitAsync()
{
_dbTransaction.Commit();
return Task.CompletedTask;
}
public void Dispose()
{
_dbTransaction.Dispose();
_dbConnection.Dispose();
}
}
}
\ No newline at end of file
// ReSharper disable once CheckNamespace using System;
// ReSharper disable once CheckNamespace
namespace DotNetCore.CAP namespace DotNetCore.CAP
{ {
public class RabbitMQOptions public class RabbitMQOptions
...@@ -27,16 +29,16 @@ namespace DotNetCore.CAP ...@@ -27,16 +29,16 @@ namespace DotNetCore.CAP
public const string DefaultVHost = "/"; public const string DefaultVHost = "/";
/// <summary> /// <summary>
/// Default exchange name (value: "cap"). /// Default exchange name (value: "cap.default.topic").
/// </summary> /// </summary>
public const string DefaultExchangeName = "cap"; public const string DefaultExchangeName = "cap.default.topic";
/// <summary> The topic exchange type. </summary>
public const string ExchangeType = "topic";
/// <summary>The host to connect to.</summary> /// <summary>The host to connect to.</summary>
public string HostName { get; set; } = "localhost"; public string HostName { get; set; } = "localhost";
/// <summary> The topic exchange type. </summary>
internal const string ExchangeType = "topic";
/// <summary> /// <summary>
/// Password to use when authenticating to the server. /// Password to use when authenticating to the server.
/// </summary> /// </summary>
...@@ -76,5 +78,10 @@ namespace DotNetCore.CAP ...@@ -76,5 +78,10 @@ namespace DotNetCore.CAP
/// The port to connect on. /// The port to connect on.
/// </summary> /// </summary>
public int Port { get; set; } = -1; public int Port { get; set; } = -1;
/// <summary>
/// Gets or sets queue message automatic deletion time (in milliseconds). Default 864000000 ms (10 days).
/// </summary>
public int QueueMessageExpires { get; set; } = 864000000;
} }
} }
\ No newline at end of file
...@@ -5,7 +5,7 @@ using Microsoft.Extensions.DependencyInjection; ...@@ -5,7 +5,7 @@ using Microsoft.Extensions.DependencyInjection;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace DotNetCore.CAP namespace DotNetCore.CAP
{ {
public class RabbitMQCapOptionsExtension : ICapOptionsExtension internal sealed class RabbitMQCapOptionsExtension : ICapOptionsExtension
{ {
private readonly Action<RabbitMQOptions> _configure; private readonly Action<RabbitMQOptions> _configure;
...@@ -16,12 +16,9 @@ namespace DotNetCore.CAP ...@@ -16,12 +16,9 @@ namespace DotNetCore.CAP
public void AddServices(IServiceCollection services) public void AddServices(IServiceCollection services)
{ {
services.Configure(_configure); var options = new RabbitMQOptions();
_configure?.Invoke(options);
var rabbitMQOptions = new RabbitMQOptions(); services.AddSingleton(options);
_configure(rabbitMQOptions);
services.AddSingleton(rabbitMQOptions);
services.AddSingleton<IConsumerClientFactory, RabbitMQConsumerClientFactory>(); services.AddSingleton<IConsumerClientFactory, RabbitMQConsumerClientFactory>();
services.AddTransient<IQueueExecutor, PublishQueueExecutor>(); services.AddTransient<IQueueExecutor, PublishQueueExecutor>();
......
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace DotNetCore.CAP namespace DotNetCore.CAP
{ {
/// <summary>
/// An attribute for subscribe RabbitMQ messages.
/// </summary>
public class CapSubscribeAttribute : TopicAttribute public class CapSubscribeAttribute : TopicAttribute
{ {
public CapSubscribeAttribute(string name) : base(name) public CapSubscribeAttribute(string name) : base(name)
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="RabbitMQ.Client" Version="4.1.3" /> <PackageReference Include="RabbitMQ.Client" Version="5.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
......
...@@ -8,18 +8,18 @@ using RabbitMQ.Client; ...@@ -8,18 +8,18 @@ using RabbitMQ.Client;
namespace DotNetCore.CAP.RabbitMQ namespace DotNetCore.CAP.RabbitMQ
{ {
public class PublishQueueExecutor : BasePublishQueueExecutor internal sealed class PublishQueueExecutor : BasePublishQueueExecutor
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly RabbitMQOptions _rabbitMQOptions; private readonly RabbitMQOptions _rabbitMQOptions;
public PublishQueueExecutor(IStateChanger stateChanger, public PublishQueueExecutor(IStateChanger stateChanger,
IOptions<RabbitMQOptions> options, RabbitMQOptions options,
ILogger<PublishQueueExecutor> logger) ILogger<PublishQueueExecutor> logger)
: base(stateChanger, logger) : base(stateChanger, logger)
{ {
_logger = logger; _logger = logger;
_rabbitMQOptions = options.Value; _rabbitMQOptions = options;
} }
public override Task<OperateResult> PublishAsync(string keyName, string content) public override Task<OperateResult> PublishAsync(string keyName, string content)
...@@ -43,7 +43,7 @@ namespace DotNetCore.CAP.RabbitMQ ...@@ -43,7 +43,7 @@ namespace DotNetCore.CAP.RabbitMQ
{ {
var body = Encoding.UTF8.GetBytes(content); var body = Encoding.UTF8.GetBytes(content);
channel.ExchangeDeclare(_rabbitMQOptions.TopicExchangeName, RabbitMQOptions.ExchangeType); channel.ExchangeDeclare(_rabbitMQOptions.TopicExchangeName, RabbitMQOptions.ExchangeType, durable: true);
channel.BasicPublish(exchange: _rabbitMQOptions.TopicExchangeName, channel.BasicPublish(exchange: _rabbitMQOptions.TopicExchangeName,
routingKey: keyName, routingKey: keyName,
basicProperties: null, basicProperties: null,
......
using System; using System;
using System.Collections.Generic;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
...@@ -7,7 +8,7 @@ using RabbitMQ.Client.Events; ...@@ -7,7 +8,7 @@ using RabbitMQ.Client.Events;
namespace DotNetCore.CAP.RabbitMQ namespace DotNetCore.CAP.RabbitMQ
{ {
public class RabbitMQConsumerClient : IConsumerClient internal sealed class RabbitMQConsumerClient : IConsumerClient
{ {
private readonly string _exchageName; private readonly string _exchageName;
private readonly string _queueName; private readonly string _queueName;
...@@ -18,7 +19,9 @@ namespace DotNetCore.CAP.RabbitMQ ...@@ -18,7 +19,9 @@ namespace DotNetCore.CAP.RabbitMQ
private IModel _channel; private IModel _channel;
private ulong _deliveryTag; private ulong _deliveryTag;
public event EventHandler<MessageContext> MessageReceieved; public event EventHandler<MessageContext> OnMessageReceieved;
public event EventHandler<string> OnError;
public RabbitMQConsumerClient(string queueName, RabbitMQOptions options) public RabbitMQConsumerClient(string queueName, RabbitMQOptions options)
{ {
...@@ -45,31 +48,42 @@ namespace DotNetCore.CAP.RabbitMQ ...@@ -45,31 +48,42 @@ namespace DotNetCore.CAP.RabbitMQ
_connection = _connectionFactory.CreateConnection(); _connection = _connectionFactory.CreateConnection();
_channel = _connection.CreateModel(); _channel = _connection.CreateModel();
_channel.ExchangeDeclare(exchange: _exchageName, type: RabbitMQOptions.ExchangeType);
_channel.QueueDeclare(_queueName, exclusive: false); _channel.ExchangeDeclare(
exchange: _exchageName,
type: RabbitMQOptions.ExchangeType,
durable: true);
var arguments = new Dictionary<string, object> { { "x-message-ttl", (int)_rabbitMQOptions.QueueMessageExpires } };
_channel.QueueDeclare(_queueName,
durable: true,
exclusive: false,
autoDelete: false,
arguments: arguments);
}
public void Subscribe(IEnumerable<string> topics)
{
if (topics == null) throw new ArgumentNullException(nameof(topics));
foreach (var topic in topics)
{
_channel.QueueBind(_queueName, _exchageName, topic);
}
} }
public void Listening(TimeSpan timeout, CancellationToken cancellationToken) public void Listening(TimeSpan timeout, CancellationToken cancellationToken)
{ {
var consumer = new EventingBasicConsumer(_channel); var consumer = new EventingBasicConsumer(_channel);
consumer.Received += OnConsumerReceived; consumer.Received += OnConsumerReceived;
consumer.Shutdown += OnConsumerShutdown;
_channel.BasicConsume(_queueName, false, consumer); _channel.BasicConsume(_queueName, false, consumer);
while (true) while (true)
{ {
Task.Delay(timeout, cancellationToken).Wait(); Task.Delay(timeout, cancellationToken).GetAwaiter().GetResult();
} }
} }
public void Subscribe(string topic)
{
_channel.QueueBind(_queueName, _exchageName, topic);
}
public void Subscribe(string topic, int partition)
{
_channel.QueueBind(_queueName, _exchageName, topic);
}
public void Commit() public void Commit()
{ {
_channel.BasicAck(_deliveryTag, false); _channel.BasicAck(_deliveryTag, false);
...@@ -90,7 +104,12 @@ namespace DotNetCore.CAP.RabbitMQ ...@@ -90,7 +104,12 @@ namespace DotNetCore.CAP.RabbitMQ
Name = e.RoutingKey, Name = e.RoutingKey,
Content = Encoding.UTF8.GetString(e.Body) Content = Encoding.UTF8.GetString(e.Body)
}; };
MessageReceieved?.Invoke(sender, message); OnMessageReceieved?.Invoke(sender, message);
}
private void OnConsumerShutdown(object sender, ShutdownEventArgs e)
{
OnError?.Invoke(sender, e.Cause?.ToString());
} }
} }
} }
\ No newline at end of file
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
namespace DotNetCore.CAP.RabbitMQ namespace DotNetCore.CAP.RabbitMQ
{ {
public class RabbitMQConsumerClientFactory : IConsumerClientFactory internal sealed class RabbitMQConsumerClientFactory : IConsumerClientFactory
{ {
private readonly RabbitMQOptions _rabbitMQOptions; private readonly RabbitMQOptions _rabbitMQOptions;
public RabbitMQConsumerClientFactory(IOptions<RabbitMQOptions> rabbitMQOptions) public RabbitMQConsumerClientFactory(RabbitMQOptions rabbitMQOptions)
{ {
_rabbitMQOptions = rabbitMQOptions.Value; _rabbitMQOptions = rabbitMQOptions;
} }
public IConsumerClient Create(string groupId) public IConsumerClient Create(string groupId)
......
...@@ -16,6 +16,6 @@ namespace DotNetCore.CAP ...@@ -16,6 +16,6 @@ namespace DotNetCore.CAP
/// <summary> /// <summary>
/// EF dbcontext type. /// EF dbcontext type.
/// </summary> /// </summary>
public Type DbContextType { get; internal set; } internal Type DbContextType { get; set; }
} }
} }
\ No newline at end of file
...@@ -7,7 +7,7 @@ using Microsoft.Extensions.DependencyInjection; ...@@ -7,7 +7,7 @@ using Microsoft.Extensions.DependencyInjection;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace DotNetCore.CAP namespace DotNetCore.CAP
{ {
public class SqlServerCapOptionsExtension : ICapOptionsExtension internal class SqlServerCapOptionsExtension : ICapOptionsExtension
{ {
private readonly Action<SqlServerOptions> _configure; private readonly Action<SqlServerOptions> _configure;
...@@ -26,14 +26,13 @@ namespace DotNetCore.CAP ...@@ -26,14 +26,13 @@ namespace DotNetCore.CAP
var sqlServerOptions = new SqlServerOptions(); var sqlServerOptions = new SqlServerOptions();
_configure(sqlServerOptions); _configure(sqlServerOptions);
var provider = TempBuildService(services); if (sqlServerOptions.DbContextType != null)
var dbContextObj = provider.GetService(sqlServerOptions.DbContextType);
if (dbContextObj != null)
{ {
var provider = TempBuildService(services);
var dbContextObj = provider.GetService(sqlServerOptions.DbContextType);
var dbContext = (DbContext)dbContextObj; var dbContext = (DbContext)dbContextObj;
sqlServerOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString; sqlServerOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString;
} }
services.Configure(_configure);
services.AddSingleton(sqlServerOptions); services.AddSingleton(sqlServerOptions);
} }
......
...@@ -38,25 +38,11 @@ namespace DotNetCore.CAP.SqlServer ...@@ -38,25 +38,11 @@ namespace DotNetCore.CAP.SqlServer
} }
} }
public void Publish(string name, string content)
{
CheckIsUsingEF(name);
PublishCore(name, content);
}
public Task PublishAsync(string name, string content)
{
CheckIsUsingEF(name);
return PublishCoreAsync(name, content);
}
public void Publish<T>(string name, T contentObj) public void Publish<T>(string name, T contentObj)
{ {
CheckIsUsingEF(name); CheckIsUsingEF(name);
var content = Helper.ToJson(contentObj); var content = Serialize(contentObj);
PublishCore(name, content); PublishCore(name, content);
} }
...@@ -65,67 +51,62 @@ namespace DotNetCore.CAP.SqlServer ...@@ -65,67 +51,62 @@ namespace DotNetCore.CAP.SqlServer
{ {
CheckIsUsingEF(name); CheckIsUsingEF(name);
var content = Helper.ToJson(contentObj); var content = Serialize(contentObj);
return PublishCoreAsync(name, content); return PublishCoreAsync(name, content);
} }
public void Publish(string name, string content, IDbConnection dbConnection, IDbTransaction dbTransaction = null) public void Publish<T>(string name, T contentObj, IDbConnection dbConnection, IDbTransaction dbTransaction = null)
{ {
CheckIsAdoNet(name); CheckIsAdoNet(name);
PrepareConnection(dbConnection, ref dbTransaction);
if (dbConnection == null) var content = Serialize(contentObj);
throw new ArgumentNullException(nameof(dbConnection));
dbTransaction = dbTransaction ?? dbConnection.BeginTransaction(IsolationLevel.ReadCommitted);
IsCapOpenedTrans = true;
PublishWithTrans(name, content, dbConnection, dbTransaction); PublishWithTrans(name, content, dbConnection, dbTransaction);
} }
public Task PublishAsync(string name, string content, IDbConnection dbConnection, IDbTransaction dbTransaction = null) public Task PublishAsync<T>(string name, T contentObj, IDbConnection dbConnection, IDbTransaction dbTransaction = null)
{ {
CheckIsAdoNet(name); CheckIsAdoNet(name);
PrepareConnection(dbConnection, ref dbTransaction);
if (dbConnection == null) var content = Serialize(contentObj);
throw new ArgumentNullException(nameof(dbConnection));
dbTransaction = dbTransaction ?? dbConnection.BeginTransaction(IsolationLevel.ReadCommitted);
IsCapOpenedTrans = true;
return PublishWithTransAsync(name, content, dbConnection, dbTransaction); return PublishWithTransAsync(name, content, dbConnection, dbTransaction);
} }
public void Publish<T>(string name, T contentObj, IDbConnection dbConnection, IDbTransaction dbTransaction = null) #region private methods
{
CheckIsAdoNet(name);
if (dbConnection == null)
throw new ArgumentNullException(nameof(dbConnection));
var content = Helper.ToJson(contentObj);
dbTransaction = dbTransaction ?? dbConnection.BeginTransaction(IsolationLevel.ReadCommitted);
PublishWithTrans(name, content, dbConnection, dbTransaction); private string Serialize<T>(T obj)
{
string content = string.Empty;
if (Helper.IsComplexType(typeof(T)))
{
content = Helper.ToJson(obj);
}
else
{
content = obj.ToString();
}
return content;
} }
public Task PublishAsync<T>(string name, T contentObj, IDbConnection dbConnection, IDbTransaction dbTransaction = null) private void PrepareConnection(IDbConnection dbConnection, ref IDbTransaction dbTransaction)
{ {
CheckIsAdoNet(name);
if (dbConnection == null) if (dbConnection == null)
throw new ArgumentNullException(nameof(dbConnection)); throw new ArgumentNullException(nameof(dbConnection));
var content = Helper.ToJson(contentObj); if (dbConnection.State != ConnectionState.Open)
dbConnection.Open();
dbTransaction = dbTransaction ?? dbConnection.BeginTransaction(IsolationLevel.ReadCommitted);
return PublishWithTransAsync(name, content, dbConnection, dbTransaction); if (dbTransaction == null)
{
IsCapOpenedTrans = true;
dbTransaction = dbConnection.BeginTransaction(IsolationLevel.ReadCommitted);
}
} }
#region private methods
private void CheckIsUsingEF(string name) private void CheckIsUsingEF(string name)
{ {
if (name == null) throw new ArgumentNullException(nameof(name)); if (name == null) throw new ArgumentNullException(nameof(name));
...@@ -145,8 +126,11 @@ namespace DotNetCore.CAP.SqlServer ...@@ -145,8 +126,11 @@ namespace DotNetCore.CAP.SqlServer
{ {
var connection = _dbContext.Database.GetDbConnection(); var connection = _dbContext.Database.GetDbConnection();
var transaction = _dbContext.Database.CurrentTransaction; var transaction = _dbContext.Database.CurrentTransaction;
IsCapOpenedTrans = transaction == null; if (transaction == null)
transaction = transaction ?? await _dbContext.Database.BeginTransactionAsync(IsolationLevel.ReadCommitted); {
IsCapOpenedTrans = true;
transaction = await _dbContext.Database.BeginTransactionAsync(IsolationLevel.ReadCommitted);
}
var dbTransaction = transaction.GetDbTransaction(); var dbTransaction = transaction.GetDbTransaction();
await PublishWithTransAsync(name, content, connection, dbTransaction); await PublishWithTransAsync(name, content, connection, dbTransaction);
} }
...@@ -155,8 +139,11 @@ namespace DotNetCore.CAP.SqlServer ...@@ -155,8 +139,11 @@ namespace DotNetCore.CAP.SqlServer
{ {
var connection = _dbContext.Database.GetDbConnection(); var connection = _dbContext.Database.GetDbConnection();
var transaction = _dbContext.Database.CurrentTransaction; var transaction = _dbContext.Database.CurrentTransaction;
IsCapOpenedTrans = transaction == null; if (transaction == null)
transaction = transaction ?? _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted); {
IsCapOpenedTrans = true;
transaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted);
}
var dbTransaction = transaction.GetDbTransaction(); var dbTransaction = transaction.GetDbTransaction();
PublishWithTrans(name, content, connection, dbTransaction); PublishWithTrans(name, content, connection, dbTransaction);
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
namespace DotNetCore.CAP.SqlServer namespace DotNetCore.CAP.SqlServer
{ {
public class FetchedMessage internal class FetchedMessage
{ {
public int MessageId { get; set; } public int MessageId { get; set; }
......
...@@ -6,7 +6,6 @@ using System.Threading.Tasks; ...@@ -6,7 +6,6 @@ using System.Threading.Tasks;
using Dapper; using Dapper;
using DotNetCore.CAP.Infrastructure; using DotNetCore.CAP.Infrastructure;
using DotNetCore.CAP.Models; using DotNetCore.CAP.Models;
using Microsoft.EntityFrameworkCore;
namespace DotNetCore.CAP.SqlServer namespace DotNetCore.CAP.SqlServer
{ {
...@@ -102,7 +101,7 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; ...@@ -102,7 +101,7 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);";
public async Task<IEnumerable<CapReceivedMessage>> GetFailedReceviedMessages() public async Task<IEnumerable<CapReceivedMessage>> GetFailedReceviedMessages()
{ {
var sql = $"SELECT TOP (1) * FROM [{_options.Schema}].[Received] WITH (readpast) WHERE StatusName = '{StatusName.Failed}'"; var sql = $"SELECT * FROM [{_options.Schema}].[Received] WITH (readpast) WHERE StatusName = '{StatusName.Failed}'";
using (var connection = new SqlConnection(_options.ConnectionString)) using (var connection = new SqlConnection(_options.ConnectionString))
{ {
return await connection.QueryAsync<CapReceivedMessage>(sql); return await connection.QueryAsync<CapReceivedMessage>(sql);
...@@ -115,7 +114,7 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; ...@@ -115,7 +114,7 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);";
private async Task<IFetchedMessage> FetchNextMessageCoreAsync(string sql, object args = null) private async Task<IFetchedMessage> FetchNextMessageCoreAsync(string sql, object args = null)
{ {
//here don't use `using` to dispose //here don't use `using` to dispose
var connection = new SqlConnection(_options.ConnectionString); var connection = new SqlConnection(_options.ConnectionString);
await connection.OpenAsync(); await connection.OpenAsync();
var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted); var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted);
......
using System; using System;
using DotNetCore.CAP.Infrastructure;
namespace DotNetCore.CAP.Abstractions namespace DotNetCore.CAP.Abstractions
{ {
......
...@@ -7,15 +7,6 @@ namespace DotNetCore.CAP.Abstractions.ModelBinding ...@@ -7,15 +7,6 @@ namespace DotNetCore.CAP.Abstractions.ModelBinding
/// </summary> /// </summary>
public interface IModelBinder public interface IModelBinder
{ {
/// <summary> Task<ModelBindingResult> BindModelAsync(string content);
/// 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);
} }
} }
\ 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 DotNetCore.CAP.Internal;
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";
}
}
public override bool Equals(object obj)
{
var other = obj as ModelBindingResult?;
if (other == null)
{
return false;
}
else
{
return Equals(other.Value);
}
}
public override int GetHashCode()
{
var hashCodeCombiner = HashCodeCombiner.Start();
hashCodeCombiner.Add(IsSuccess);
hashCodeCombiner.Add(Model);
return hashCodeCombiner.CombinedHash;
}
public bool Equals(ModelBindingResult other)
{
return
IsSuccess == other.IsSuccess &&
object.Equals(Model, other.Model);
}
/// <summary>
/// Compares <see cref="ModelBindingResult"/> objects for equality.
/// </summary>
/// <param name="x">A <see cref="ModelBindingResult"/>.</param>
/// <param name="y">A <see cref="ModelBindingResult"/>.</param>
/// <returns><c>true</c> if the objects are equal, otherwise <c>false</c>.</returns>
public static bool operator ==(ModelBindingResult x, ModelBindingResult y)
{
return x.Equals(y);
}
/// <summary>
/// Compares <see cref="ModelBindingResult"/> objects for inequality.
/// </summary>
/// <param name="x">A <see cref="ModelBindingResult"/>.</param>
/// <param name="y">A <see cref="ModelBindingResult"/>.</param>
/// <returns><c>true</c> if the objects are not equal, otherwise <c>false</c>.</returns>
public static bool operator !=(ModelBindingResult x, ModelBindingResult y)
{
return !x.Equals(y);
}
}
}
\ No newline at end of file
...@@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Builder ...@@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Builder
if (marker == null) if (marker == null)
{ {
throw new InvalidOperationException("Add Consistency must be called on the service collection."); throw new InvalidOperationException("Add Cap must be called on the service collection.");
} }
var provider = app.ApplicationServices; var provider = app.ApplicationServices;
......
...@@ -13,34 +13,45 @@ namespace DotNetCore.CAP ...@@ -13,34 +13,45 @@ namespace DotNetCore.CAP
/// <summary> /// <summary>
/// Default value for polling delay timeout, in seconds. /// Default value for polling delay timeout, in seconds.
/// </summary> /// </summary>
public const int DefaultPollingDelay = 8; public const int DefaultPollingDelay = 15;
/// <summary>
/// Default processor count to process messages of cap.queue.
/// </summary>
public const int DefaultQueueProcessorCount = 2;
public CapOptions() public CapOptions()
{ {
PollingDelay = DefaultPollingDelay; PollingDelay = DefaultPollingDelay;
QueueProcessorCount = DefaultQueueProcessorCount;
Extensions = new List<ICapOptionsExtension>(); Extensions = new List<ICapOptionsExtension>();
} }
/// <summary> /// <summary>
/// Productor job polling delay time. Default is 5 sec. /// Productor job polling delay time. Default is 15 sec.
/// </summary>
public int PollingDelay { get; set; }
/// <summary>
/// Gets or sets the messages queue (Cap.Queue table) processor count.
/// </summary> /// </summary>
public int PollingDelay { get; set; } = 5; public int QueueProcessorCount { get; set; }
/// <summary> /// <summary>
/// Failed messages polling delay time. Default is 2 min. /// Failed messages polling delay time. Default is 3 min.
/// </summary> /// </summary>
public TimeSpan FailedMessageWaitingInterval = TimeSpan.FromMinutes(2); public int FailedMessageWaitingInterval { get; set; } = (int)TimeSpan.FromMinutes(3).TotalSeconds;
/// <summary> /// <summary>
/// We’ll send a POST request to the URL below with details of any subscribed events. /// We’ll invoke this call-back with message type,name,content when requeue failed message.
/// </summary> /// </summary>
public WebHook WebHook => throw new NotSupportedException(); public Action<Models.MessageType, string, string> FailedCallback { get; set; }
/// <summary> /// <summary>
/// Registers an extension that will be executed when building services. /// Registers an extension that will be executed when building services.
/// </summary> /// </summary>
/// <param name="extension"></param> /// <param name="extension"></param>
public void RegisterExtension(ICapOptionsExtension extension) public void RegisterExtension(ICapOptionsExtension extension)
{ {
if (extension == null) if (extension == null)
throw new ArgumentNullException(nameof(extension)); throw new ArgumentNullException(nameof(extension));
...@@ -48,11 +59,4 @@ namespace DotNetCore.CAP ...@@ -48,11 +59,4 @@ namespace DotNetCore.CAP
Extensions.Add(extension); Extensions.Add(extension);
} }
} }
public class WebHook
{
public string PayloadUrl { get; set; }
public string Secret { get; set; }
}
} }
\ No newline at end of file
...@@ -3,7 +3,6 @@ using System.Collections.Generic; ...@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using DotNetCore.CAP; using DotNetCore.CAP;
using DotNetCore.CAP.Abstractions; using DotNetCore.CAP.Abstractions;
using DotNetCore.CAP.Abstractions.ModelBinding;
using DotNetCore.CAP.Infrastructure; using DotNetCore.CAP.Infrastructure;
using DotNetCore.CAP.Internal; using DotNetCore.CAP.Internal;
using DotNetCore.CAP.Processor; using DotNetCore.CAP.Processor;
...@@ -35,7 +34,7 @@ namespace Microsoft.Extensions.DependencyInjection ...@@ -35,7 +34,7 @@ namespace Microsoft.Extensions.DependencyInjection
AddSubscribeServices(services); AddSubscribeServices(services);
services.TryAddSingleton<IConsumerServiceSelector, DefaultConsumerServiceSelector>(); services.TryAddSingleton<IConsumerServiceSelector, DefaultConsumerServiceSelector>();
services.TryAddSingleton<IModelBinder, DefaultModelBinder>(); services.TryAddSingleton<IModelBinderFactory, ModelBinderFactory>();
services.TryAddSingleton<IConsumerInvokerFactory, ConsumerInvokerFactory>(); services.TryAddSingleton<IConsumerInvokerFactory, ConsumerInvokerFactory>();
services.TryAddSingleton<MethodMatcherCache>(); services.TryAddSingleton<MethodMatcherCache>();
...@@ -60,7 +59,7 @@ namespace Microsoft.Extensions.DependencyInjection ...@@ -60,7 +59,7 @@ namespace Microsoft.Extensions.DependencyInjection
foreach (var serviceExtension in options.Extensions) foreach (var serviceExtension in options.Extensions)
{ {
serviceExtension.AddServices(services); serviceExtension.AddServices(services);
} }
services.AddSingleton(options); services.AddSingleton(options);
return new CapBuilder(services); return new CapBuilder(services);
......
...@@ -40,7 +40,7 @@ namespace DotNetCore.CAP ...@@ -40,7 +40,7 @@ namespace DotNetCore.CAP
_cts.Cancel(); _cts.Cancel();
try try
{ {
_bootstrappingTask?.Wait(); _bootstrappingTask?.GetAwaiter().GetResult();
} }
catch (OperationCanceledException ex) catch (OperationCanceledException ex)
{ {
......
...@@ -8,28 +8,6 @@ namespace DotNetCore.CAP ...@@ -8,28 +8,6 @@ namespace DotNetCore.CAP
/// </summary> /// </summary>
public interface ICapPublisher public interface ICapPublisher
{ {
/// <summary>
/// (EntityFramework) Asynchronous publish a message.
/// <para>
/// If you are using the EntityFramework, you need to configure the DbContextType first.
/// otherwise you need to use overloaded method with IDbConnection and IDbTransaction.
/// </para>
/// </summary>
/// <param name="name">the topic name or exchange router key.</param>
/// <param name="content">message body content.</param>
Task PublishAsync(string name, string content);
/// <summary>
/// (EntityFramework) Publish a message.
/// <para>
/// If you are using the EntityFramework, you need to configure the DbContextType first.
/// otherwise you need to use overloaded method with IDbConnection and IDbTransaction.
/// </para>
/// </summary>
/// <param name="name">the topic name or exchange router key.</param>
/// <param name="content">message body content.</param>
void Publish(string name, string content);
/// <summary> /// <summary>
/// (EntityFramework) Asynchronous publish a object message. /// (EntityFramework) Asynchronous publish a object message.
/// <para> /// <para>
...@@ -54,24 +32,6 @@ namespace DotNetCore.CAP ...@@ -54,24 +32,6 @@ namespace DotNetCore.CAP
/// <param name="contentObj">message body content, that will be serialized of json.</param> /// <param name="contentObj">message body content, that will be serialized of json.</param>
void Publish<T>(string name, T contentObj); void Publish<T>(string name, T contentObj);
/// <summary>
/// (ado.net) Asynchronous publish a message.
/// </summary>
/// <param name="name">the topic name or exchange router key.</param>
/// <param name="content">message body content</param>
/// <param name="dbConnection">the connection of <see cref="IDbConnection"/></param>
/// <param name="dbTransaction">the transaction of <see cref="IDbTransaction"/></param>
Task PublishAsync(string name, string content, IDbConnection dbConnection, IDbTransaction dbTransaction = null);
/// <summary>
/// (ado.net) Publish a message.
/// </summary>
/// <param name="name">the topic name or exchange router key.</param>
/// <param name="content">message body content.</param>
/// <param name="dbConnection">the connection of <see cref="IDbConnection"/></param>
/// <param name="dbTransaction">the transaction of <see cref="IDbTransaction"/></param>
void Publish(string name, string content, IDbConnection dbConnection, IDbTransaction dbTransaction = null);
/// <summary> /// <summary>
/// (ado.net) Asynchronous publish a object message. /// (ado.net) Asynchronous publish a object message.
/// </summary> /// </summary>
......
using System; using System;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using DotNetCore.CAP.Infrastructure;
namespace DotNetCore.CAP namespace DotNetCore.CAP
{ {
...@@ -9,14 +9,14 @@ namespace DotNetCore.CAP ...@@ -9,14 +9,14 @@ namespace DotNetCore.CAP
/// </summary> /// </summary>
public interface IConsumerClient : IDisposable public interface IConsumerClient : IDisposable
{ {
void Subscribe(string topic); void Subscribe(IEnumerable<string> topics);
void Subscribe(string topic, int partition);
void Listening(TimeSpan timeout, CancellationToken cancellationToken); void Listening(TimeSpan timeout, CancellationToken cancellationToken);
void Commit(); void Commit();
event EventHandler<MessageContext> MessageReceieved; event EventHandler<MessageContext> OnMessageReceieved;
event EventHandler<string> OnError;
} }
} }
\ No newline at end of file
using System; using System;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using DotNetCore.CAP.Infrastructure; using DotNetCore.CAP.Infrastructure;
...@@ -56,10 +57,7 @@ namespace DotNetCore.CAP ...@@ -56,10 +57,7 @@ namespace DotNetCore.CAP
{ {
RegisterMessageProcessor(client); RegisterMessageProcessor(client);
foreach (var item in matchGroup.Value) client.Subscribe(matchGroup.Value.Select(x => x.Attribute.Name));
{
client.Subscribe(item.Attribute.Name);
}
client.Listening(_pollingDelay, _cts.Token); client.Listening(_pollingDelay, _cts.Token);
} }
...@@ -95,7 +93,7 @@ namespace DotNetCore.CAP ...@@ -95,7 +93,7 @@ namespace DotNetCore.CAP
private void RegisterMessageProcessor(IConsumerClient client) private void RegisterMessageProcessor(IConsumerClient client)
{ {
client.MessageReceieved += (sender, message) => client.OnMessageReceieved += (sender, message) =>
{ {
_logger.EnqueuingReceivedMessage(message.Name, message.Content); _logger.EnqueuingReceivedMessage(message.Name, message.Content);
...@@ -106,6 +104,11 @@ namespace DotNetCore.CAP ...@@ -106,6 +104,11 @@ namespace DotNetCore.CAP
} }
Pulse(); Pulse();
}; };
client.OnError += (sender, reason) =>
{
_logger.LogError(reason);
};
} }
private CapReceivedMessage StoreMessage(IServiceScope serviceScope, MessageContext messageContext) private CapReceivedMessage StoreMessage(IServiceScope serviceScope, MessageContext messageContext)
...@@ -116,7 +119,7 @@ namespace DotNetCore.CAP ...@@ -116,7 +119,7 @@ namespace DotNetCore.CAP
{ {
StatusName = StatusName.Scheduled, StatusName = StatusName.Scheduled,
}; };
messageStore.StoreReceivedMessageAsync(receivedMessage).Wait(); messageStore.StoreReceivedMessageAsync(receivedMessage).GetAwaiter().GetResult();
return receivedMessage; return receivedMessage;
} }
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using DotNetCore.CAP.Abstractions; using DotNetCore.CAP.Abstractions;
using DotNetCore.CAP.Infrastructure;
using DotNetCore.CAP.Internal; using DotNetCore.CAP.Internal;
using DotNetCore.CAP.Models; using DotNetCore.CAP.Models;
using DotNetCore.CAP.Processor; using DotNetCore.CAP.Processor;
......
...@@ -56,6 +56,7 @@ namespace DotNetCore.CAP ...@@ -56,6 +56,7 @@ namespace DotNetCore.CAP
/// Returns executed failed message. /// Returns executed failed message.
/// </summary> /// </summary>
Task<IEnumerable<CapReceivedMessage>> GetFailedReceviedMessages(); Task<IEnumerable<CapReceivedMessage>> GetFailedReceviedMessages();
//----------------------------------------- //-----------------------------------------
/// <summary> /// <summary>
......
using System; using System;
using System.ComponentModel;
using System.Reflection; using System.Reflection;
using Newtonsoft.Json; using Newtonsoft.Json;
...@@ -68,5 +69,10 @@ namespace DotNetCore.CAP.Infrastructure ...@@ -68,5 +69,10 @@ namespace DotNetCore.CAP.Infrastructure
return !typeInfo.ContainsGenericParameters return !typeInfo.ContainsGenericParameters
&& typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase); && typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase);
} }
public static bool IsComplexType(Type type)
{
return !TypeDescriptor.GetConverter(type).CanConvertFrom(typeof(string));
}
} }
} }
\ No newline at end of file
using System; using System;
using System.Collections.Generic;
using System.Text;
namespace DotNetCore.CAP.Infrastructure namespace DotNetCore.CAP.Infrastructure
{ {
...@@ -11,4 +9,4 @@ namespace DotNetCore.CAP.Infrastructure ...@@ -11,4 +9,4 @@ namespace DotNetCore.CAP.Infrastructure
throw new NotImplementedException(); throw new NotImplementedException();
} }
} }
} }
\ No newline at end of file
using System; using System;
using DotNetCore.CAP.Abstractions; using DotNetCore.CAP.Abstractions;
using DotNetCore.CAP.Abstractions.ModelBinding;
using DotNetCore.CAP.Infrastructure;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace DotNetCore.CAP.Internal namespace DotNetCore.CAP.Internal
...@@ -10,15 +8,15 @@ namespace DotNetCore.CAP.Internal ...@@ -10,15 +8,15 @@ namespace DotNetCore.CAP.Internal
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly IModelBinder _modelBinder; private readonly IModelBinderFactory _modelBinderFactory;
public ConsumerInvokerFactory( public ConsumerInvokerFactory(
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
IModelBinder modelBinder, IModelBinderFactory modelBinderFactory,
IServiceProvider serviceProvider) IServiceProvider serviceProvider)
{ {
_logger = loggerFactory.CreateLogger<ConsumerInvokerFactory>(); _logger = loggerFactory.CreateLogger<ConsumerInvokerFactory>();
_modelBinder = modelBinder; _modelBinderFactory = modelBinderFactory;
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
} }
...@@ -26,7 +24,7 @@ namespace DotNetCore.CAP.Internal ...@@ -26,7 +24,7 @@ namespace DotNetCore.CAP.Internal
{ {
var context = new ConsumerInvokerContext(consumerContext) var context = new ConsumerInvokerContext(consumerContext)
{ {
Result = new DefaultConsumerInvoker(_logger, _serviceProvider, _modelBinder, consumerContext) Result = new DefaultConsumerInvoker(_logger, _serviceProvider, _modelBinderFactory, consumerContext)
}; };
return context.Result; 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);
}
}
}
\ No newline at end of file
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using DotNetCore.CAP.Abstractions; using DotNetCore.CAP.Abstractions;
using DotNetCore.CAP.Abstractions.ModelBinding;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
...@@ -11,16 +10,16 @@ namespace DotNetCore.CAP.Internal ...@@ -11,16 +10,16 @@ namespace DotNetCore.CAP.Internal
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly IModelBinder _modelBinder; private readonly IModelBinderFactory _modelBinderFactory;
private readonly ConsumerContext _consumerContext; private readonly ConsumerContext _consumerContext;
private readonly ObjectMethodExecutor _executor; private readonly ObjectMethodExecutor _executor;
public DefaultConsumerInvoker(ILogger logger, public DefaultConsumerInvoker(ILogger logger,
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
IModelBinder modelBinder, IModelBinderFactory modelBinderFactory,
ConsumerContext consumerContext) ConsumerContext consumerContext)
{ {
_modelBinder = modelBinder; _modelBinderFactory = modelBinderFactory;
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
...@@ -29,32 +28,41 @@ namespace DotNetCore.CAP.Internal ...@@ -29,32 +28,41 @@ namespace DotNetCore.CAP.Internal
_consumerContext.ConsumerDescriptor.ImplTypeInfo); _consumerContext.ConsumerDescriptor.ImplTypeInfo);
} }
public Task InvokeAsync() public async Task InvokeAsync()
{ {
using (_logger.BeginScope("consumer invoker begin")) using (_logger.BeginScope("consumer invoker begin"))
{ {
_logger.LogDebug("Executing consumer Topic: {0}", _consumerContext.ConsumerDescriptor.MethodInfo.Name); _logger.LogDebug("Executing consumer Topic: {0}", _consumerContext.ConsumerDescriptor.MethodInfo.Name);
var obj = ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, var obj = ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider,
_consumerContext.ConsumerDescriptor.ImplTypeInfo.AsType()); _consumerContext.ConsumerDescriptor.ImplTypeInfo.AsType());
var value = _consumerContext.DeliverMessage.Content; var value = _consumerContext.DeliverMessage.Content;
if (_executor.MethodParameters.Length > 0) if (_executor.MethodParameters.Length > 0)
{ {
var firstParameter = _executor.MethodParameters[0]; var firstParameter = _executor.MethodParameters[0];
try
var bindingContext = ModelBindingContext.CreateBindingContext(value, {
firstParameter.Name, firstParameter.ParameterType); var binder = _modelBinderFactory.CreateBinder(firstParameter);
var result = await binder.BindModelAsync(value);
_modelBinder.BindModelAsync(bindingContext); if (result.IsSuccess)
_executor.Execute(obj, bindingContext.Result); {
_executor.Execute(obj, result.Model);
}
else
{
_logger.LogWarning($"Parameters:{firstParameter.Name} bind failed! the content is:" + value);
}
}
catch (FormatException ex)
{
_logger.ModelBinderFormattingException(_executor.MethodInfo?.Name, firstParameter.Name, value, ex);
}
} }
else else
{ {
_executor.Execute(obj); _executor.Execute(obj);
} }
return Task.CompletedTask;
} }
} }
} }
......
...@@ -58,13 +58,7 @@ namespace DotNetCore.CAP.Internal ...@@ -58,13 +58,7 @@ namespace DotNetCore.CAP.Internal
continue; continue;
} }
foreach (var method in typeInfo.DeclaredMethods) executorDescriptorList.AddRange(GetTopicAttributesDescription(typeInfo));
{
var topicAttr = method.GetCustomAttribute<TopicAttribute>(true);
if (topicAttr == null) continue;
executorDescriptorList.Add(InitDescriptor(topicAttr, method, typeInfo));
}
} }
return executorDescriptorList; return executorDescriptorList;
} }
...@@ -82,18 +76,27 @@ namespace DotNetCore.CAP.Internal ...@@ -82,18 +76,27 @@ namespace DotNetCore.CAP.Internal
//double check //double check
if (!Helper.IsController(typeInfo)) continue; if (!Helper.IsController(typeInfo)) continue;
foreach (var method in typeInfo.DeclaredMethods) executorDescriptorList.AddRange(GetTopicAttributesDescription(typeInfo));
{
var topicAttr = method.GetCustomAttribute<TopicAttribute>(true);
if (topicAttr == null) continue;
executorDescriptorList.Add(InitDescriptor(topicAttr, method, typeInfo));
}
} }
return executorDescriptorList; return executorDescriptorList;
} }
private static IEnumerable<ConsumerExecutorDescriptor> GetTopicAttributesDescription(TypeInfo typeInfo)
{
foreach (var method in typeInfo.DeclaredMethods)
{
var topicAttrs = method.GetCustomAttributes<TopicAttribute>(true);
if (topicAttrs.Count() == 0) continue;
foreach (var attr in topicAttrs)
{
yield return InitDescriptor(attr, method, typeInfo);
}
}
}
private static ConsumerExecutorDescriptor InitDescriptor( private static ConsumerExecutorDescriptor InitDescriptor(
TopicAttribute attr, TopicAttribute attr,
MethodInfo methodInfo, MethodInfo methodInfo,
......
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.Runtime.ExceptionServices;
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;
try
{
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));
}
}
catch (Exception exception)
{
var isFormatException = exception is FormatException;
if (!isFormatException && exception.InnerException != null)
{
// TypeConverter throws System.Exception wrapping the FormatException,
// so we capture the inner exception.
exception = ExceptionDispatchInfo.Capture(exception.InnerException).SourceException;
}
throw exception;
}
}
private bool IsReferenceOrNullableType(Type type)
{
var isNullableValueType = Nullable.GetUnderlyingType(type) != null;
return !type.GetTypeInfo().IsValueType || isNullableValueType;
}
}
}
\ No newline at end of file
using System.Reflection;
using DotNetCore.CAP.Abstractions.ModelBinding;
namespace DotNetCore.CAP.Internal
{
public interface IModelBinderFactory
{
IModelBinder CreateBinder(ParameterInfo parameter);
}
}
\ No newline at end of file
using System;
using System.Collections.Concurrent;
using System.Reflection;
using System.Runtime.CompilerServices;
using DotNetCore.CAP.Abstractions.ModelBinding;
using DotNetCore.CAP.Infrastructure;
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;
}
if (!Helper.IsComplexType(parameterInfo.ParameterType))
{
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
...@@ -14,6 +14,7 @@ namespace DotNetCore.CAP ...@@ -14,6 +14,7 @@ namespace DotNetCore.CAP
private static readonly Action<ILogger, string, string, Exception> _enqueuingReceivdeMessage; private static readonly Action<ILogger, string, string, Exception> _enqueuingReceivdeMessage;
private static readonly Action<ILogger, string, Exception> _executingConsumerMethod; private static readonly Action<ILogger, string, Exception> _executingConsumerMethod;
private static readonly Action<ILogger, string, Exception> _receivedMessageRetryExecuting; private static readonly Action<ILogger, string, Exception> _receivedMessageRetryExecuting;
private static readonly Action<ILogger, string, string, string, Exception> _modelBinderFormattingException;
private static Action<ILogger, Exception> _jobFailed; private static Action<ILogger, Exception> _jobFailed;
private static Action<ILogger, Exception> _jobFailedWillRetry; private static Action<ILogger, Exception> _jobFailedWillRetry;
...@@ -46,12 +47,12 @@ namespace DotNetCore.CAP ...@@ -46,12 +47,12 @@ namespace DotNetCore.CAP
_enqueuingSentMessage = LoggerMessage.Define<string, string>( _enqueuingSentMessage = LoggerMessage.Define<string, string>(
LogLevel.Debug, LogLevel.Debug,
2, 2,
"Enqueuing a topic to the sent message store. NameKey: {NameKey}. Content: {Content}"); "Enqueuing a topic to the sent message store. NameKey: '{NameKey}' Content: '{Content}'.");
_enqueuingReceivdeMessage = LoggerMessage.Define<string, string>( _enqueuingReceivdeMessage = LoggerMessage.Define<string, string>(
LogLevel.Debug, LogLevel.Debug,
2, 2,
"Enqueuing a topic to the received message store. NameKey: {NameKey}. Content: {Content}"); "Enqueuing a topic to the received message store. NameKey: '{NameKey}. Content: '{Content}'.");
_executingConsumerMethod = LoggerMessage.Define<string>( _executingConsumerMethod = LoggerMessage.Define<string>(
LogLevel.Error, LogLevel.Error,
...@@ -63,6 +64,12 @@ namespace DotNetCore.CAP ...@@ -63,6 +64,12 @@ namespace DotNetCore.CAP
5, 5,
"Received message topic method '{topicName}' failed to execute."); "Received message topic method '{topicName}' failed to execute.");
_modelBinderFormattingException = LoggerMessage.Define<string, string, string>(
LogLevel.Error,
5,
"When call subscribe method, a parameter format conversion exception occurs. MethodName:'{MethodName}' ParameterName:'{ParameterName}' Content:'{Content}'."
);
_jobRetrying = LoggerMessage.Define<int>( _jobRetrying = LoggerMessage.Define<int>(
LogLevel.Debug, LogLevel.Debug,
3, 3,
...@@ -154,5 +161,10 @@ namespace DotNetCore.CAP ...@@ -154,5 +161,10 @@ namespace DotNetCore.CAP
{ {
_exceptionOccuredWhileExecutingJob(logger, jobId, ex); _exceptionOccuredWhileExecutingJob(logger, jobId, ex);
} }
public static void ModelBinderFormattingException(this ILogger logger, string methodName, string parameterName, string content, Exception ex)
{
_modelBinderFormattingException(logger, methodName, parameterName, content, ex);
}
} }
} }
\ No newline at end of file
using System; using System;
using DotNetCore.CAP.Infrastructure;
namespace DotNetCore.CAP.Models namespace DotNetCore.CAP.Models
{ {
......
using System; using System;
using DotNetCore.CAP.Infrastructure;
namespace DotNetCore.CAP.Models namespace DotNetCore.CAP.Models
{ {
......
...@@ -49,7 +49,7 @@ namespace DotNetCore.CAP.Processor ...@@ -49,7 +49,7 @@ namespace DotNetCore.CAP.Processor
try try
{ {
var worked = await Step(context); var worked = await Step(context);
context.ThrowIfStopping(); context.ThrowIfStopping();
Waiting = true; Waiting = true;
......
...@@ -39,8 +39,9 @@ namespace DotNetCore.CAP.Processor ...@@ -39,8 +39,9 @@ namespace DotNetCore.CAP.Processor
public void Start() public void Start()
{ {
var processorCount = Environment.ProcessorCount; var processorCount = _options.QueueProcessorCount;
_processors = GetProcessors(processorCount); _processors = GetProcessors(processorCount);
_logger.ServerStarting(processorCount, _processors.Length); _logger.ServerStarting(processorCount, _processors.Length);
_context = new ProcessingContext(_provider, _cts.Token); _context = new ProcessingContext(_provider, _cts.Token);
...@@ -61,7 +62,7 @@ namespace DotNetCore.CAP.Processor ...@@ -61,7 +62,7 @@ namespace DotNetCore.CAP.Processor
_logger.LogTrace("Pulsing the Queuer."); _logger.LogTrace("Pulsing the Queuer.");
PublishQueuer.PulseEvent.Set(); PublishQueuer.PulseEvent.Set();
} }
public void Dispose() public void Dispose()
...@@ -76,7 +77,7 @@ namespace DotNetCore.CAP.Processor ...@@ -76,7 +77,7 @@ namespace DotNetCore.CAP.Processor
_cts.Cancel(); _cts.Cancel();
try try
{ {
_compositeTask.Wait((int)TimeSpan.FromSeconds(60).TotalMilliseconds); _compositeTask.Wait((int)TimeSpan.FromSeconds(10).TotalMilliseconds);
} }
catch (AggregateException ex) catch (AggregateException ex)
{ {
......
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using DotNetCore.CAP.Processor.States; using DotNetCore.CAP.Processor.States;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
...@@ -30,7 +27,7 @@ namespace DotNetCore.CAP.Processor ...@@ -30,7 +27,7 @@ namespace DotNetCore.CAP.Processor
_logger = logger; _logger = logger;
_provider = provider; _provider = provider;
_stateChanger = stateChanger; _stateChanger = stateChanger;
_waitingInterval = _options.FailedMessageWaitingInterval; _waitingInterval = TimeSpan.FromSeconds(_options.FailedMessageWaitingInterval);
} }
public async Task ProcessAsync(ProcessingContext context) public async Task ProcessAsync(ProcessingContext context)
...@@ -56,14 +53,32 @@ namespace DotNetCore.CAP.Processor ...@@ -56,14 +53,32 @@ namespace DotNetCore.CAP.Processor
private async Task ProcessPublishedAsync(IStorageConnection connection, ProcessingContext context) private async Task ProcessPublishedAsync(IStorageConnection connection, ProcessingContext context)
{ {
var messages = await connection.GetFailedPublishedMessages(); var messages = await connection.GetFailedPublishedMessages();
var hasException = false;
foreach (var message in messages) foreach (var message in messages)
{ {
if (!hasException)
{
try
{
_options.FailedCallback?.Invoke(Models.MessageType.Publish, message.Name, message.Content);
}
catch (Exception ex)
{
hasException = true;
_logger.LogWarning("Failed call-back method raised an exception:" + ex.Message);
}
}
using (var transaction = connection.CreateTransaction()) using (var transaction = connection.CreateTransaction())
{ {
_stateChanger.ChangeState(message, new EnqueuedState(), transaction); _stateChanger.ChangeState(message, new EnqueuedState(), transaction);
await transaction.CommitAsync(); await transaction.CommitAsync();
} }
context.ThrowIfStopping(); context.ThrowIfStopping();
await context.WaitAsync(_delay); await context.WaitAsync(_delay);
} }
} }
...@@ -71,16 +86,34 @@ namespace DotNetCore.CAP.Processor ...@@ -71,16 +86,34 @@ namespace DotNetCore.CAP.Processor
private async Task ProcessReceivededAsync(IStorageConnection connection, ProcessingContext context) private async Task ProcessReceivededAsync(IStorageConnection connection, ProcessingContext context)
{ {
var messages = await connection.GetFailedReceviedMessages(); var messages = await connection.GetFailedReceviedMessages();
var hasException = false;
foreach (var message in messages) foreach (var message in messages)
{ {
if (!hasException)
{
try
{
_options.FailedCallback?.Invoke(Models.MessageType.Subscribe, message.Name, message.Content);
}
catch (Exception ex)
{
hasException = true;
_logger.LogWarning("Failed call-back method raised an exception:" + ex.Message);
}
}
using (var transaction = connection.CreateTransaction()) using (var transaction = connection.CreateTransaction())
{ {
_stateChanger.ChangeState(message, new EnqueuedState(), transaction); _stateChanger.ChangeState(message, new EnqueuedState(), transaction);
await transaction.CommitAsync(); await transaction.CommitAsync();
} }
context.ThrowIfStopping(); context.ThrowIfStopping();
await context.WaitAsync(_delay); await context.WaitAsync(_delay);
} }
} }
} }
} }
\ No newline at end of file
...@@ -18,8 +18,8 @@ namespace DotNetCore.CAP ...@@ -18,8 +18,8 @@ namespace DotNetCore.CAP
{ {
var queueExectors = _serviceProvider.GetServices<IQueueExecutor>(); var queueExectors = _serviceProvider.GetServices<IQueueExecutor>();
return messageType == MessageType.Publish return messageType == MessageType.Publish
? queueExectors.FirstOrDefault(x => x is BasePublishQueueExecutor) ? queueExectors.FirstOrDefault(x => x is BasePublishQueueExecutor)
: queueExectors.FirstOrDefault(x => !(x is BasePublishQueueExecutor)); : queueExectors.FirstOrDefault(x => !(x is BasePublishQueueExecutor));
} }
} }
......
using System;
using MySql.Data.MySqlClient;
namespace DotNetCore.CAP.MySql.Test
{
public static class ConnectionUtil
{
private const string DatabaseVariable = "Cap_MySql_DatabaseName";
private const string ConnectionStringTemplateVariable = "Cap_MySql_ConnectionStringTemplate";
private const string MasterDatabaseName = "information_schema";
private const string DefaultDatabaseName = @"DotNetCore.CAP.MySql.Test";
private const string DefaultConnectionStringTemplate =
@"Server=localhost;Database={0};Uid=root;Pwd=123123;";
public static string GetDatabaseName()
{
return Environment.GetEnvironmentVariable(DatabaseVariable) ?? DefaultDatabaseName;
}
public static string GetMasterConnectionString()
{
return string.Format(GetConnectionStringTemplate(), MasterDatabaseName);
}
public static string GetConnectionString()
{
return string.Format(GetConnectionStringTemplate(), GetDatabaseName());
}
private static string GetConnectionStringTemplate()
{
return
Environment.GetEnvironmentVariable(ConnectionStringTemplateVariable) ??
DefaultConnectionStringTemplate;
}
public static MySqlConnection CreateConnection(string connectionString = null)
{
connectionString = connectionString ?? GetConnectionString();
var connection = new MySqlConnection(connectionString);
connection.Open();
return connection;
}
}
}
\ No newline at end of file
using System.Data;
using System.Threading;
using Dapper;
using Microsoft.EntityFrameworkCore;
namespace DotNetCore.CAP.MySql.Test
{
public abstract class DatabaseTestHost : TestHost
{
private static bool _sqlObjectInstalled;
public static object _lock = new object();
protected override void PostBuildServices()
{
base.PostBuildServices();
lock (_lock)
{
if (!_sqlObjectInstalled)
{
InitializeDatabase();
}
}
}
public override void Dispose()
{
DeleteAllData();
base.Dispose();
}
private void InitializeDatabase()
{
using (CreateScope())
{
var storage = GetService<MySqlStorage>();
var token = new CancellationTokenSource().Token;
CreateDatabase();
storage.InitializeAsync(token).GetAwaiter().GetResult();
_sqlObjectInstalled = true;
}
}
private void CreateDatabase()
{
var masterConn = ConnectionUtil.GetMasterConnectionString();
var databaseName = ConnectionUtil.GetDatabaseName();
using (var connection = ConnectionUtil.CreateConnection(masterConn))
{
connection.Execute($@"
DROP DATABASE IF EXISTS `{databaseName}`;
CREATE DATABASE `{databaseName}`;");
}
}
private void DeleteAllData()
{
var conn = ConnectionUtil.GetConnectionString();
using (var connection = ConnectionUtil.CreateConnection(conn))
{
connection.Execute($@"
TRUNCATE TABLE `cap.published`;
TRUNCATE TABLE `cap.received`;
TRUNCATE TABLE `cap.queue`;");
}
}
}
}
\ No newline at end of file
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AssemblyName>DotNetCore.CAP.MySql.Test</AssemblyName>
<PackageId>DotNetCore.CAP.MySql.Test</PackageId>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<PackageTargetFallback>$(PackageTargetFallback);dnxcore50;portable-net451+win8</PackageTargetFallback>
<RuntimeFrameworkVersion>1.1.1</RuntimeFrameworkVersion>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Shared\*.cs" Exclude="bin\**;obj\**;**\*.xproj;packages\**" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotNetCore.CAP.MySql\DotNetCore.CAP.MySql.csproj" />
<ProjectReference Include="..\..\src\DotNetCore.CAP\DotNetCore.CAP.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="1.50.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="MySqlConnector" Version="0.24.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.63" />
<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" />
</ItemGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
</Project>
\ No newline at end of file
using System;
using System.Threading.Tasks;
using Dapper;
using DotNetCore.CAP.Infrastructure;
using DotNetCore.CAP.Models;
using Xunit;
namespace DotNetCore.CAP.MySql.Test
{
[Collection("MySql")]
public class MySqlStorageConnectionTest : DatabaseTestHost
{
private MySqlStorageConnection _storage;
public MySqlStorageConnectionTest()
{
var options = GetService<MySqlOptions>();
_storage = new MySqlStorageConnection(options);
}
[Fact]
public async Task GetPublishedMessageAsync_Test()
{
var sql = "INSERT INTO `cap.published`(`Name`,`Content`,`Retries`,`Added`,`ExpiresAt`,`StatusName`) VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName);SELECT @@IDENTITY;";
var publishMessage = new CapPublishedMessage
{
Name = "MySqlStorageConnectionTest",
Content = "",
StatusName = StatusName.Scheduled
};
var insertedId = default(int);
using (var connection = ConnectionUtil.CreateConnection())
{
insertedId = connection.QueryFirst<int>(sql, publishMessage);
}
var message = await _storage.GetPublishedMessageAsync(insertedId);
Assert.NotNull(message);
Assert.Equal("MySqlStorageConnectionTest", message.Name);
Assert.Equal(StatusName.Scheduled, message.StatusName);
}
[Fact]
public async Task FetchNextMessageAsync_Test()
{
var sql = "INSERT INTO `Cap.Queue`(`MessageId`,`MessageType`) VALUES(@MessageId,@MessageType);";
var queue = new CapQueue
{
MessageId = 3333,
MessageType = MessageType.Publish
};
using (var connection = ConnectionUtil.CreateConnection())
{
connection.Execute(sql, queue);
}
var fetchedMessage = await _storage.FetchNextMessageAsync();
fetchedMessage.Dispose();
Assert.NotNull(fetchedMessage);
Assert.Equal(MessageType.Publish, fetchedMessage.MessageType);
Assert.Equal(3333, fetchedMessage.MessageId);
}
[Fact]
public async Task StoreReceivedMessageAsync_Test()
{
var receivedMessage = new CapReceivedMessage
{
Name = "MySqlStorageConnectionTest",
Content = "",
Group = "mygroup",
StatusName = StatusName.Scheduled
};
Exception exception = null;
try
{
await _storage.StoreReceivedMessageAsync(receivedMessage);
}
catch (Exception ex)
{
exception = ex;
}
Assert.Null(exception);
}
[Fact]
public async Task GetReceivedMessageAsync_Test()
{
var sql = $@"
INSERT INTO `cap.received`(`Name`,`Group`,`Content`,`Retries`,`Added`,`ExpiresAt`,`StatusName`)
VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);SELECT @@IDENTITY;";
var receivedMessage = new CapReceivedMessage
{
Name = "MySqlStorageConnectionTest",
Content = "",
Group = "mygroup",
StatusName = StatusName.Scheduled
};
var insertedId = default(int);
using (var connection = ConnectionUtil.CreateConnection())
{
insertedId = connection.QueryFirst<int>(sql, receivedMessage);
}
var message = await _storage.GetReceivedMessageAsync(insertedId);
Assert.NotNull(message);
Assert.Equal(StatusName.Scheduled, message.StatusName);
Assert.Equal("MySqlStorageConnectionTest", message.Name);
Assert.Equal("mygroup", message.Group);
}
[Fact]
public async Task GetNextReceviedMessageToBeEnqueuedAsync_Test()
{
var receivedMessage = new CapReceivedMessage
{
Name = "MySqlStorageConnectionTest",
Content = "",
Group = "mygroup",
StatusName = StatusName.Scheduled
};
await _storage.StoreReceivedMessageAsync(receivedMessage);
var message = await _storage.GetNextReceviedMessageToBeEnqueuedAsync();
Assert.NotNull(message);
Assert.Equal(StatusName.Scheduled, message.StatusName);
Assert.Equal("MySqlStorageConnectionTest", message.Name);
Assert.Equal("mygroup", message.Group);
}
}
}
using Xunit;
using Dapper;
namespace DotNetCore.CAP.MySql.Test
{
[Collection("MySql")]
public class MySqlStorageTest : DatabaseTestHost
{
private readonly string _dbName;
private readonly string _masterDbConnectionString;
public MySqlStorageTest()
{
_dbName = ConnectionUtil.GetDatabaseName();
_masterDbConnectionString = ConnectionUtil.GetMasterConnectionString();
}
[Fact]
public void Database_IsExists()
{
using (var connection = ConnectionUtil.CreateConnection(_masterDbConnectionString))
{
var databaseName = ConnectionUtil.GetDatabaseName();
var sql = $@"SELECT SCHEMA_NAME FROM SCHEMATA WHERE SCHEMA_NAME = '{databaseName}'";
var result = connection.QueryFirstOrDefault<string>(sql);
Assert.NotNull(result);
Assert.True(databaseName.Equals(result, System.StringComparison.CurrentCultureIgnoreCase));
}
}
[Fact]
public void DatabaseTable_Published_IsExists()
{
var tableName = "cap.published";
using (var connection = ConnectionUtil.CreateConnection(_masterDbConnectionString))
{
var sql = $"SELECT TABLE_NAME FROM `TABLES` WHERE TABLE_SCHEMA='{_dbName}' AND TABLE_NAME = '{tableName}'";
var result = connection.QueryFirstOrDefault<string>(sql);
Assert.NotNull(result);
Assert.Equal(tableName, result);
}
}
[Fact]
public void DatabaseTable_Queue_IsExists()
{
var tableName = "cap.queue";
using (var connection = ConnectionUtil.CreateConnection(_masterDbConnectionString))
{
var sql = $"SELECT TABLE_NAME FROM `TABLES` WHERE TABLE_SCHEMA='{_dbName}' AND TABLE_NAME = '{tableName}'";
var result = connection.QueryFirstOrDefault<string>(sql);
Assert.NotNull(result);
Assert.Equal(tableName, result);
}
}
[Fact]
public void DatabaseTable_Received_IsExists()
{
var tableName = "cap.received";
using (var connection = ConnectionUtil.CreateConnection(_masterDbConnectionString))
{
var sql = $"SELECT TABLE_NAME FROM `TABLES` WHERE TABLE_SCHEMA='{_dbName}' AND TABLE_NAME = '{tableName}'";
var result = connection.QueryFirstOrDefault<string>(sql);
Assert.NotNull(result);
Assert.Equal(tableName, result);
}
}
}
}
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace DotNetCore.CAP.MySql.Test
{
public abstract class TestHost : IDisposable
{
protected IServiceCollection _services;
protected string _connectionString;
private IServiceProvider _provider;
private IServiceProvider _scopedProvider;
public TestHost()
{
CreateServiceCollection();
PreBuildServices();
BuildServices();
PostBuildServices();
}
protected IServiceProvider Provider => _scopedProvider ?? _provider;
private void CreateServiceCollection()
{
var services = new ServiceCollection();
services.AddOptions();
services.AddLogging();
_connectionString = ConnectionUtil.GetConnectionString();
services.AddSingleton(new MySqlOptions { ConnectionString = _connectionString });
services.AddSingleton<MySqlStorage>();
_services = services;
}
protected virtual void PreBuildServices()
{
}
private void BuildServices()
{
_provider = _services.BuildServiceProvider();
}
protected virtual void PostBuildServices()
{
}
public IDisposable CreateScope()
{
var scope = CreateScope(_provider);
var loc = scope.ServiceProvider;
_scopedProvider = loc;
return new DelegateDisposable(() =>
{
if (_scopedProvider == loc)
{
_scopedProvider = null;
}
scope.Dispose();
});
}
public IServiceScope CreateScope(IServiceProvider provider)
{
var scope = provider.GetService<IServiceScopeFactory>().CreateScope();
return scope;
}
public T GetService<T>() => Provider.GetService<T>();
public T Ensure<T>(ref T service)
where T : class
=> service ?? (service = GetService<T>());
public virtual void Dispose()
{
(_provider as IDisposable)?.Dispose();
}
private class DelegateDisposable : IDisposable
{
private Action _dispose;
public DelegateDisposable(Action dispose)
{
_dispose = dispose;
}
public void Dispose()
{
_dispose();
}
}
}
}
\ No newline at end of file
...@@ -35,7 +35,7 @@ namespace DotNetCore.CAP.SqlServer.Test ...@@ -35,7 +35,7 @@ namespace DotNetCore.CAP.SqlServer.Test
var storage = GetService<SqlServerStorage>(); var storage = GetService<SqlServerStorage>();
var token = new CancellationTokenSource().Token; var token = new CancellationTokenSource().Token;
CreateDatabase(); CreateDatabase();
storage.InitializeAsync(token).Wait(); storage.InitializeAsync(token).GetAwaiter().GetResult();
_sqlObjectInstalled = true; _sqlObjectInstalled = true;
} }
} }
......
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