From 816fb33cdf1b78d2cf222b7753736793de352f73 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Sun, 13 Aug 2017 01:08:45 +0800 Subject: [PATCH 01/68] upgrade to .net standard 2.0. --- .gitignore | 3 +- .../DotNetCore.CAP.Kafka.csproj | 7 +- .../DotNetCore.CAP.MySql.csproj | 12 +- .../DotNetCore.CAP.PostgreSql.csproj | 10 +- .../DotNetCore.CAP.RabbitMQ.csproj | 2 +- .../DotNetCore.CAP.SqlServer.csproj | 10 +- src/DotNetCore.CAP/DotNetCore.CAP.csproj | 21 +- src/DotNetCore.CAP/Infrastructure/ObjectId.cs | 539 ++++++++++++++++++ .../DotNetCore.CAP.MySql.Test.csproj | 33 +- .../DotNetCore.CAP.SqlServer.Test.csproj | 37 +- .../Properties/AssemblyInfo.cs | 18 - .../SqlServerStorageTest.cs | 8 +- .../ConsumerServiceSelectorTest.cs | 2 +- .../DotNetCore.CAP.Test.csproj | 18 +- .../Processor/StateChangerTest.cs | 4 +- .../QueueExecutorFactoryTest.cs | 2 +- 16 files changed, 611 insertions(+), 115 deletions(-) create mode 100644 src/DotNetCore.CAP/Infrastructure/ObjectId.cs delete mode 100644 test/DotNetCore.CAP.SqlServer.Test/Properties/AssemblyInfo.cs diff --git a/.gitignore b/.gitignore index 96cda33..d7fe0b1 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,5 @@ bin/ /.idea/.idea.CAP /.idea/.idea.CAP /.idea -Properties \ No newline at end of file +Properties +/pack.bat diff --git a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj index 89bd201..f27aee1 100644 --- a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj +++ b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj @@ -3,10 +3,15 @@ - netstandard1.6; + netstandard2.0; DotNetCore.CAP.Kafka $(PackageTags);Kafka + + + + + diff --git a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj index d750aaf..9ee70d2 100644 --- a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj +++ b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj @@ -3,20 +3,16 @@ - netstandard1.6;netstandard2.0; + netstandard2.0; DotNetCore.CAP.MySql $(PackageTags);MySQL - - TRACE;DEBUG - - - - - + + + diff --git a/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj b/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj index a49961e..f34d60b 100644 --- a/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj +++ b/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj @@ -3,19 +3,15 @@ - netstandard1.6;netstandard2.0; + netstandard2.0; DotNetCore.CAP.PostgreSql $(PackageTags);PostgreSQL - - TRACE;DEBUG - - - - + + diff --git a/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj b/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj index 52e7844..c92e44b 100644 --- a/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj +++ b/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj @@ -3,7 +3,7 @@ - netstandard1.6; + netstandard2.0; DotNetCore.CAP.RabbitMQ $(PackageTags);RabbitMQ diff --git a/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj b/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj index 7592ae4..8dc779f 100644 --- a/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj +++ b/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj @@ -3,19 +3,15 @@ - netstandard1.6;netstandard2.0; + netstandard2.0; DotNetCore.CAP.SqlServer $(PackageTags);SQL Server - - TRACE;DEBUG - - - - + + diff --git a/src/DotNetCore.CAP/DotNetCore.CAP.csproj b/src/DotNetCore.CAP/DotNetCore.CAP.csproj index 8b9c660..ab566f9 100644 --- a/src/DotNetCore.CAP/DotNetCore.CAP.csproj +++ b/src/DotNetCore.CAP/DotNetCore.CAP.csproj @@ -3,28 +3,21 @@ - netstandard1.6;netstandard2.0; + netstandard2.0; DotNetCore.CAP $(PackageTags); - - - - + + + + - - - - - + + - - - - diff --git a/src/DotNetCore.CAP/Infrastructure/ObjectId.cs b/src/DotNetCore.CAP/Infrastructure/ObjectId.cs new file mode 100644 index 0000000..138690e --- /dev/null +++ b/src/DotNetCore.CAP/Infrastructure/ObjectId.cs @@ -0,0 +1,539 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; +using System.Text; +using System.Threading; + +namespace DotNetCore.CAP +{ + /// Represents an ObjectId + /// + [Serializable] + public struct ObjectId : IComparable, IEquatable + { + // private static fields + private static readonly DateTime __unixEpoch; + private static readonly long __dateTimeMaxValueMillisecondsSinceEpoch; + private static readonly long __dateTimeMinValueMillisecondsSinceEpoch; + private static ObjectId __emptyInstance = default(ObjectId); + private static int __staticMachine; + private static short __staticPid; + private static int __staticIncrement; // high byte will be masked out when generating new ObjectId + private static uint[] _lookup32 = Enumerable.Range(0, 256).Select(i => + { + string s = i.ToString("x2"); + return ((uint)s[0]) + ((uint)s[1] << 16); + }).ToArray(); + + // we're using 14 bytes instead of 12 to hold the ObjectId in memory but unlike a byte[] there is no additional object on the heap + // the extra two bytes are not visible to anyone outside of this class and they buy us considerable simplification + // an additional advantage of this representation is that it will serialize to JSON without any 64 bit overflow problems + private int _timestamp; + private int _machine; + private short _pid; + private int _increment; + + // static constructor + static ObjectId() + { + __unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + __dateTimeMaxValueMillisecondsSinceEpoch = (DateTime.MaxValue - __unixEpoch).Ticks / 10000; + __dateTimeMinValueMillisecondsSinceEpoch = (DateTime.MinValue - __unixEpoch).Ticks / 10000; + __staticMachine = GetMachineHash(); + __staticIncrement = (new Random()).Next(); + __staticPid = (short)GetCurrentProcessId(); + } + + // constructors + /// + /// Initializes a new instance of the ObjectId class. + /// + /// The bytes. + public ObjectId(byte[] bytes) + { + if (bytes == null) + { + throw new ArgumentNullException("bytes"); + } + Unpack(bytes, out _timestamp, out _machine, out _pid, out _increment); + } + + /// + /// Initializes a new instance of the ObjectId class. + /// + /// The timestamp (expressed as a DateTime). + /// The machine hash. + /// The PID. + /// The increment. + public ObjectId(DateTime timestamp, int machine, short pid, int increment) + : this(GetTimestampFromDateTime(timestamp), machine, pid, increment) + { + } + + /// + /// Initializes a new instance of the ObjectId class. + /// + /// The timestamp. + /// The machine hash. + /// The PID. + /// The increment. + public ObjectId(int timestamp, int machine, short pid, int increment) + { + if ((machine & 0xff000000) != 0) + { + throw new ArgumentOutOfRangeException("machine", "The machine value must be between 0 and 16777215 (it must fit in 3 bytes)."); + } + if ((increment & 0xff000000) != 0) + { + throw new ArgumentOutOfRangeException("increment", "The increment value must be between 0 and 16777215 (it must fit in 3 bytes)."); + } + + _timestamp = timestamp; + _machine = machine; + _pid = pid; + _increment = increment; + } + + /// + /// Initializes a new instance of the ObjectId class. + /// + /// The value. + public ObjectId(string value) + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + Unpack(ParseHexString(value), out _timestamp, out _machine, out _pid, out _increment); + } + + // public static properties + /// + /// Gets an instance of ObjectId where the value is empty. + /// + public static ObjectId Empty + { + get { return __emptyInstance; } + } + + // public properties + /// + /// Gets the timestamp. + /// + public int Timestamp + { + get { return _timestamp; } + } + + /// + /// Gets the machine. + /// + public int Machine + { + get { return _machine; } + } + + /// + /// Gets the PID. + /// + public short Pid + { + get { return _pid; } + } + + /// + /// Gets the increment. + /// + public int Increment + { + get { return _increment; } + } + + /// + /// Gets the creation time (derived from the timestamp). + /// + public DateTime CreationTime + { + get { return __unixEpoch.AddSeconds(_timestamp); } + } + + // public operators + /// + /// Compares two ObjectIds. + /// + /// The first ObjectId. + /// The other ObjectId + /// True if the first ObjectId is less than the second ObjectId. + public static bool operator <(ObjectId lhs, ObjectId rhs) + { + return lhs.CompareTo(rhs) < 0; + } + + /// + /// Compares two ObjectIds. + /// + /// The first ObjectId. + /// The other ObjectId + /// True if the first ObjectId is less than or equal to the second ObjectId. + public static bool operator <=(ObjectId lhs, ObjectId rhs) + { + return lhs.CompareTo(rhs) <= 0; + } + + /// + /// Compares two ObjectIds. + /// + /// The first ObjectId. + /// The other ObjectId. + /// True if the two ObjectIds are equal. + public static bool operator ==(ObjectId lhs, ObjectId rhs) + { + return lhs.Equals(rhs); + } + + /// + /// Compares two ObjectIds. + /// + /// The first ObjectId. + /// The other ObjectId. + /// True if the two ObjectIds are not equal. + public static bool operator !=(ObjectId lhs, ObjectId rhs) + { + return !(lhs == rhs); + } + + /// + /// Compares two ObjectIds. + /// + /// The first ObjectId. + /// The other ObjectId + /// True if the first ObjectId is greather than or equal to the second ObjectId. + public static bool operator >=(ObjectId lhs, ObjectId rhs) + { + return lhs.CompareTo(rhs) >= 0; + } + + /// + /// Compares two ObjectIds. + /// + /// The first ObjectId. + /// The other ObjectId + /// True if the first ObjectId is greather than the second ObjectId. + public static bool operator >(ObjectId lhs, ObjectId rhs) + { + return lhs.CompareTo(rhs) > 0; + } + + // public static methods + /// + /// Generates a new ObjectId with a unique value. + /// + /// An ObjectId. + public static ObjectId GenerateNewId() + { + return GenerateNewId(GetTimestampFromDateTime(DateTime.UtcNow)); + } + + /// + /// Generates a new ObjectId with a unique value (with the timestamp component based on a given DateTime). + /// + /// The timestamp component (expressed as a DateTime). + /// An ObjectId. + public static ObjectId GenerateNewId(DateTime timestamp) + { + return GenerateNewId(GetTimestampFromDateTime(timestamp)); + } + + /// + /// Generates a new ObjectId with a unique value (with the given timestamp). + /// + /// The timestamp component. + /// An ObjectId. + public static ObjectId GenerateNewId(int timestamp) + { + int increment = Interlocked.Increment(ref __staticIncrement) & 0x00ffffff; // only use low order 3 bytes + return new ObjectId(timestamp, __staticMachine, __staticPid, increment); + } + + /// + /// Generates a new ObjectId string with a unique value. + /// + /// The string value of the new generated ObjectId. + public static string GenerateNewStringId() + { + return GenerateNewId().ToString(); + } + + /// + /// Packs the components of an ObjectId into a byte array. + /// + /// The timestamp. + /// The machine hash. + /// The PID. + /// The increment. + /// A byte array. + public static byte[] Pack(int timestamp, int machine, short pid, int increment) + { + if ((machine & 0xff000000) != 0) + { + throw new ArgumentOutOfRangeException("machine", "The machine value must be between 0 and 16777215 (it must fit in 3 bytes)."); + } + if ((increment & 0xff000000) != 0) + { + throw new ArgumentOutOfRangeException("increment", "The increment value must be between 0 and 16777215 (it must fit in 3 bytes)."); + } + + byte[] bytes = new byte[12]; + bytes[0] = (byte)(timestamp >> 24); + bytes[1] = (byte)(timestamp >> 16); + bytes[2] = (byte)(timestamp >> 8); + bytes[3] = (byte)(timestamp); + bytes[4] = (byte)(machine >> 16); + bytes[5] = (byte)(machine >> 8); + bytes[6] = (byte)(machine); + bytes[7] = (byte)(pid >> 8); + bytes[8] = (byte)(pid); + bytes[9] = (byte)(increment >> 16); + bytes[10] = (byte)(increment >> 8); + bytes[11] = (byte)(increment); + return bytes; + } + + /// + /// Parses a string and creates a new ObjectId. + /// + /// The string value. + /// A ObjectId. + public static ObjectId Parse(string s) + { + if (s == null) + { + throw new ArgumentNullException("s"); + } + if (s.Length != 24) + { + throw new ArgumentOutOfRangeException("s", "ObjectId string value must be 24 characters."); + } + return new ObjectId(ParseHexString(s)); + } + + /// + /// Unpacks a byte array into the components of an ObjectId. + /// + /// A byte array. + /// The timestamp. + /// The machine hash. + /// The PID. + /// The increment. + public static void Unpack(byte[] bytes, out int timestamp, out int machine, out short pid, out int increment) + { + if (bytes == null) + { + throw new ArgumentNullException("bytes"); + } + if (bytes.Length != 12) + { + throw new ArgumentOutOfRangeException("bytes", "Byte array must be 12 bytes long."); + } + timestamp = (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3]; + machine = (bytes[4] << 16) + (bytes[5] << 8) + bytes[6]; + pid = (short)((bytes[7] << 8) + bytes[8]); + increment = (bytes[9] << 16) + (bytes[10] << 8) + bytes[11]; + } + + // private static methods + /// + /// Gets the current process id. This method exists because of how CAS operates on the call stack, checking + /// for permissions before executing the method. Hence, if we inlined this call, the calling method would not execute + /// before throwing an exception requiring the try/catch at an even higher level that we don't necessarily control. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private static int GetCurrentProcessId() + { + return Process.GetCurrentProcess().Id; + } + + private static int GetMachineHash() + { + var hostName = Environment.MachineName; // use instead of Dns.HostName so it will work offline + var md5 = MD5.Create(); + var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(hostName)); + return (hash[0] << 16) + (hash[1] << 8) + hash[2]; // use first 3 bytes of hash + } + + private static int GetTimestampFromDateTime(DateTime timestamp) + { + return (int)Math.Floor((ToUniversalTime(timestamp) - __unixEpoch).TotalSeconds); + } + + // public methods + /// + /// Compares this ObjectId to another ObjectId. + /// + /// The other ObjectId. + /// A 32-bit signed integer that indicates whether this ObjectId is less than, equal to, or greather than the other. + public int CompareTo(ObjectId other) + { + int r = _timestamp.CompareTo(other._timestamp); + if (r != 0) { return r; } + r = _machine.CompareTo(other._machine); + if (r != 0) { return r; } + r = _pid.CompareTo(other._pid); + if (r != 0) { return r; } + return _increment.CompareTo(other._increment); + } + + /// + /// Compares this ObjectId to another ObjectId. + /// + /// The other ObjectId. + /// True if the two ObjectIds are equal. + public bool Equals(ObjectId rhs) + { + return + _timestamp == rhs._timestamp && + _machine == rhs._machine && + _pid == rhs._pid && + _increment == rhs._increment; + } + + /// + /// Compares this ObjectId to another object. + /// + /// The other object. + /// True if the other object is an ObjectId and equal to this one. + public override bool Equals(object obj) + { + if (obj is ObjectId) + { + return Equals((ObjectId)obj); + } + else + { + return false; + } + } + + /// + /// Gets the hash code. + /// + /// The hash code. + public override int GetHashCode() + { + int hash = 17; + hash = 37 * hash + _timestamp.GetHashCode(); + hash = 37 * hash + _machine.GetHashCode(); + hash = 37 * hash + _pid.GetHashCode(); + hash = 37 * hash + _increment.GetHashCode(); + return hash; + } + + /// + /// Converts the ObjectId to a byte array. + /// + /// A byte array. + public byte[] ToByteArray() + { + return Pack(_timestamp, _machine, _pid, _increment); + } + + /// + /// Returns a string representation of the value. + /// + /// A string representation of the value. + public override string ToString() + { + return ToHexString(ToByteArray()); + } + + /// + /// Parses a hex string into its equivalent byte array. + /// + /// The hex string to parse. + /// The byte equivalent of the hex string. + public static byte[] ParseHexString(string s) + { + if (s == null) + { + throw new ArgumentNullException("s"); + } + + if (s.Length % 2 == 1) + { + throw new Exception("The binary key cannot have an odd number of digits"); + } + + byte[] arr = new byte[s.Length >> 1]; + + for (int i = 0; i < s.Length >> 1; ++i) + { + arr[i] = (byte)((GetHexVal(s[i << 1]) << 4) + (GetHexVal(s[(i << 1) + 1]))); + } + + return arr; + } + /// + /// Converts a byte array to a hex string. + /// + /// The byte array. + /// A hex string. + public static string ToHexString(byte[] bytes) + { + if (bytes == null) + { + throw new ArgumentNullException("bytes"); + } + var result = new char[bytes.Length * 2]; + for (int i = 0; i < bytes.Length; i++) + { + var val = _lookup32[bytes[i]]; + result[2 * i] = (char)val; + result[2 * i + 1] = (char)(val >> 16); + } + return new string(result); + } + /// + /// Converts a DateTime to number of milliseconds since Unix epoch. + /// + /// A DateTime. + /// Number of seconds since Unix epoch. + public static long ToMillisecondsSinceEpoch(DateTime dateTime) + { + var utcDateTime = ToUniversalTime(dateTime); + return (utcDateTime - __unixEpoch).Ticks / 10000; + } + /// + /// Converts a DateTime to UTC (with special handling for MinValue and MaxValue). + /// + /// A DateTime. + /// The DateTime in UTC. + public static DateTime ToUniversalTime(DateTime dateTime) + { + if (dateTime == DateTime.MinValue) + { + return DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc); + } + else if (dateTime == DateTime.MaxValue) + { + return DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc); + } + else + { + return dateTime.ToUniversalTime(); + } + } + + private static int GetHexVal(char hex) + { + int val = (int)hex; + //For uppercase A-F letters: + //return val - (val < 58 ? 48 : 55); + //For lowercase a-f letters: + //return val - (val < 58 ? 48 : 87); + //Or the two combined, but a bit slower: + return val - (val < 58 ? 48 : (val < 97 ? 55 : 87)); + } + } +} diff --git a/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj b/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj index cf89b39..1694331 100644 --- a/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj +++ b/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj @@ -1,16 +1,11 @@  - netcoreapp1.1 + netcoreapp2.0 true DotNetCore.CAP.MySql.Test DotNetCore.CAP.MySql.Test true - $(PackageTargetFallback);dnxcore50;portable-net451+win8 - 1.1.1 - false - false - false @@ -24,19 +19,19 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj b/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj index 177d825..bd29d9b 100644 --- a/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj +++ b/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj @@ -1,16 +1,11 @@  - netcoreapp1.1 + netcoreapp2.0 true DotNetCore.CAP.SqlServer.Test DotNetCore.CAP.SqlServer.Test true - $(PackageTargetFallback);dnxcore50;portable-net451+win8 - 1.1.1 - false - false - false @@ -24,21 +19,21 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/test/DotNetCore.CAP.SqlServer.Test/Properties/AssemblyInfo.cs b/test/DotNetCore.CAP.SqlServer.Test/Properties/AssemblyInfo.cs deleted file mode 100644 index a995715..0000000 --- a/test/DotNetCore.CAP.SqlServer.Test/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("DotNetCore.CAP.EntityFrameworkCore.Test")] -[assembly: AssemblyTrademark("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("7442c942-1ddc-40e4-8f1b-654e721eaa45")] \ No newline at end of file diff --git a/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageTest.cs b/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageTest.cs index 38fb6c1..af1fdac 100644 --- a/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageTest.cs +++ b/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageTest.cs @@ -19,7 +19,7 @@ SELECT 'True' ELSE SELECT 'False'"; var result = connection.QueryFirst(sql); - Assert.Equal(true, result); + Assert.True(result); } } @@ -34,7 +34,7 @@ SELECT 'True' ELSE SELECT 'False'"; var result = connection.QueryFirst(sql); - Assert.Equal(true, result); + Assert.True(result); } } @@ -49,7 +49,7 @@ SELECT 'True' ELSE SELECT 'False'"; var result = connection.QueryFirst(sql); - Assert.Equal(true, result); + Assert.True(result); } } @@ -64,7 +64,7 @@ SELECT 'True' ELSE SELECT 'False'"; var result = connection.QueryFirst(sql); - Assert.Equal(true, result); + Assert.True(result); } } } diff --git a/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs b/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs index 1fc5ca8..97dd29b 100644 --- a/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs +++ b/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs @@ -42,7 +42,7 @@ namespace DotNetCore.CAP.Test Assert.NotNull(bestCandidates); Assert.NotNull(bestCandidates.MethodInfo); - Assert.Equal(bestCandidates.MethodInfo.ReturnType, typeof(Task)); + Assert.Equal(typeof(Task), bestCandidates.MethodInfo.ReturnType); } } diff --git a/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj b/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj index 81b5e2d..27bf741 100644 --- a/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj +++ b/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj @@ -1,13 +1,11 @@  - netcoreapp1.1 + netcoreapp2.0 true DotNetCore.CAP.Test DotNetCore.CAP.Test true - $(PackageTargetFallback);dnxcore50;portable-net451+win8 - 1.1.1 @@ -15,14 +13,14 @@ - + - - - - - - + + + + + + diff --git a/test/DotNetCore.CAP.Test/Processor/StateChangerTest.cs b/test/DotNetCore.CAP.Test/Processor/StateChangerTest.cs index 51101bd..b57692b 100644 --- a/test/DotNetCore.CAP.Test/Processor/StateChangerTest.cs +++ b/test/DotNetCore.CAP.Test/Processor/StateChangerTest.cs @@ -25,7 +25,7 @@ namespace DotNetCore.CAP.Test fixture.ChangeState(message, state, mockTransaction.Object); // Assert - Assert.Equal(message.StatusName, "s"); + Assert.Equal("s", message.StatusName); Assert.Null(message.ExpiresAt); Mock.Get(state).Verify(s => s.Apply(message, mockTransaction.Object), Times.Once); mockTransaction.Verify(t => t.UpdateMessage(message), Times.Once); @@ -48,7 +48,7 @@ namespace DotNetCore.CAP.Test fixture.ChangeState(message, state, mockTransaction.Object); // Assert - Assert.Equal(message.StatusName, "s"); + Assert.Equal("s", message.StatusName); Assert.NotNull(message.ExpiresAt); mockTransaction.Verify(t => t.UpdateMessage(message), Times.Once); mockTransaction.Verify(t => t.CommitAsync(), Times.Never); diff --git a/test/DotNetCore.CAP.Test/QueueExecutorFactoryTest.cs b/test/DotNetCore.CAP.Test/QueueExecutorFactoryTest.cs index f1bf1e3..a5fef69 100644 --- a/test/DotNetCore.CAP.Test/QueueExecutorFactoryTest.cs +++ b/test/DotNetCore.CAP.Test/QueueExecutorFactoryTest.cs @@ -39,7 +39,7 @@ namespace DotNetCore.CAP.Test Assert.NotNull(queueExecutorFactory); var publishExecutor = queueExecutorFactory.GetInstance(Models.MessageType.Publish); - Assert.Equal(null, publishExecutor); + Assert.Null(publishExecutor); } From 34045448e1ae7fdf4b2419022bd3183fa0fa4228 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Sun, 13 Aug 2017 13:43:53 +0800 Subject: [PATCH 02/68] add postgresql sample project. --- .../AppDbContext.cs | 16 +++++ .../Controllers/ValuesController.cs | 59 +++++++++++++++++++ samples/Sample.RabbitMQ.PostgreSql/Program.cs | 25 ++++++++ .../Sample.RabbitMQ.PostgreSql.csproj | 18 ++++++ samples/Sample.RabbitMQ.PostgreSql/Startup.cs | 28 +++++++++ 5 files changed, 146 insertions(+) create mode 100644 samples/Sample.RabbitMQ.PostgreSql/AppDbContext.cs create mode 100644 samples/Sample.RabbitMQ.PostgreSql/Controllers/ValuesController.cs create mode 100644 samples/Sample.RabbitMQ.PostgreSql/Program.cs create mode 100644 samples/Sample.RabbitMQ.PostgreSql/Sample.RabbitMQ.PostgreSql.csproj create mode 100644 samples/Sample.RabbitMQ.PostgreSql/Startup.cs diff --git a/samples/Sample.RabbitMQ.PostgreSql/AppDbContext.cs b/samples/Sample.RabbitMQ.PostgreSql/AppDbContext.cs new file mode 100644 index 0000000..4fefe5c --- /dev/null +++ b/samples/Sample.RabbitMQ.PostgreSql/AppDbContext.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; + +namespace Sample.RabbitMQ.PostgreSql +{ + public class AppDbContext : DbContext + { + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseNpgsql("Server=localhost;Database=Sample.RabbitMQ.PostgreSql;UserId=postgre;Password=123123;"); + } + } +} diff --git a/samples/Sample.RabbitMQ.PostgreSql/Controllers/ValuesController.cs b/samples/Sample.RabbitMQ.PostgreSql/Controllers/ValuesController.cs new file mode 100644 index 0000000..10ea6d1 --- /dev/null +++ b/samples/Sample.RabbitMQ.PostgreSql/Controllers/ValuesController.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading.Tasks; +using DotNetCore.CAP; +using Microsoft.AspNetCore.Mvc; + +namespace Sample.RabbitMQ.PostgreSql.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.rabbitmq.mysql", DateTime.Now); + + return Ok(); + } + + + [Route("~/publish2")] + public IActionResult PublishMessage2() + { + _capBus.Publish("sample.kafka.sqlserver4", DateTime.Now); + + return Ok(); + } + + [Route("~/publishWithTrans")] + public async Task 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"); + } + } +} diff --git a/samples/Sample.RabbitMQ.PostgreSql/Program.cs b/samples/Sample.RabbitMQ.PostgreSql/Program.cs new file mode 100644 index 0000000..3cb0755 --- /dev/null +++ b/samples/Sample.RabbitMQ.PostgreSql/Program.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Sample.RabbitMQ.PostgreSql +{ + public class Program + { + public static void Main(string[] args) + { + BuildWebHost(args).Run(); + } + + public static IWebHost BuildWebHost(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup() + .Build(); + } +} diff --git a/samples/Sample.RabbitMQ.PostgreSql/Sample.RabbitMQ.PostgreSql.csproj b/samples/Sample.RabbitMQ.PostgreSql/Sample.RabbitMQ.PostgreSql.csproj new file mode 100644 index 0000000..44a8a52 --- /dev/null +++ b/samples/Sample.RabbitMQ.PostgreSql/Sample.RabbitMQ.PostgreSql.csproj @@ -0,0 +1,18 @@ + + + + netcoreapp2.0 + + + + + + + + + + + + + + diff --git a/samples/Sample.RabbitMQ.PostgreSql/Startup.cs b/samples/Sample.RabbitMQ.PostgreSql/Startup.cs new file mode 100644 index 0000000..fa963b1 --- /dev/null +++ b/samples/Sample.RabbitMQ.PostgreSql/Startup.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Sample.RabbitMQ.PostgreSql +{ + public class Startup + { + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + 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) + { + app.UseMvc(); + } + } +} From 8bf92138a0e333274c2b42292d935279f5ef407d Mon Sep 17 00:00:00 2001 From: Savorboard Date: Sun, 13 Aug 2017 13:44:16 +0800 Subject: [PATCH 03/68] rename project. --- .../AppDbContext.cs | 4 ++-- .../Controllers/ValuesController.cs | 7 ++++-- .../Program.cs | 2 +- .../Sample.RabbitMQ.SqlServer.csproj} | 24 +++++++++---------- .../Startup.cs | 4 ++-- 5 files changed, 21 insertions(+), 20 deletions(-) rename samples/{Sample.Kafka.SqlServer => Sample.RabbitMQ.SqlServer}/AppDbContext.cs (64%) rename samples/{Sample.Kafka.SqlServer => Sample.RabbitMQ.SqlServer}/Controllers/ValuesController.cs (89%) rename samples/{Sample.Kafka.SqlServer => Sample.RabbitMQ.SqlServer}/Program.cs (95%) rename samples/{Sample.Kafka.SqlServer/Sample.Kafka.SqlServer.csproj => Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj} (55%) rename samples/{Sample.Kafka.SqlServer => Sample.RabbitMQ.SqlServer}/Startup.cs (90%) diff --git a/samples/Sample.Kafka.SqlServer/AppDbContext.cs b/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs similarity index 64% rename from samples/Sample.Kafka.SqlServer/AppDbContext.cs rename to samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs index 53cecb7..6e3ea9c 100644 --- a/samples/Sample.Kafka.SqlServer/AppDbContext.cs +++ b/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs @@ -1,13 +1,13 @@ using Microsoft.EntityFrameworkCore; -namespace Sample.Kafka +namespace Sample.RabbitMQ.SqlServer { public class AppDbContext : DbContext { 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=DESKTOP-M9R8T31;Initial Catalog=Sample.Kafka.SqlServer;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"); } } } diff --git a/samples/Sample.Kafka.SqlServer/Controllers/ValuesController.cs b/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs similarity index 89% rename from samples/Sample.Kafka.SqlServer/Controllers/ValuesController.cs rename to samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs index 61b511a..59a9b17 100644 --- a/samples/Sample.Kafka.SqlServer/Controllers/ValuesController.cs +++ b/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using DotNetCore.CAP; using Microsoft.AspNetCore.Mvc; -namespace Sample.Kafka.Controllers +namespace Sample.RabbitMQ.SqlServer.Controllers { [Route("api/[controller]")] public class ValuesController : Controller, ICapSubscribe @@ -22,6 +22,7 @@ namespace Sample.Kafka.Controllers public IActionResult PublishMessage() { _capBus.Publish("sample.rabbitmq.mysql", ""); + return Ok(); } @@ -31,13 +32,15 @@ namespace Sample.Kafka.Controllers using (var trans = await _dbContext.Database.BeginTransactionAsync()) { await _capBus.PublishAsync("sample.rabbitmq.mysql", ""); + trans.Commit(); } return Ok(); } [NonAction] - [CapSubscribe("sample.kafka.sqlserver", Group = "test")] + [CapSubscribe("sample.kafka.sqlserver3")] + [CapSubscribe("sample.kafka.sqlserver4")] public void KafkaTest() { Console.WriteLine("[sample.kafka.sqlserver] message received"); diff --git a/samples/Sample.Kafka.SqlServer/Program.cs b/samples/Sample.RabbitMQ.SqlServer/Program.cs similarity index 95% rename from samples/Sample.Kafka.SqlServer/Program.cs rename to samples/Sample.RabbitMQ.SqlServer/Program.cs index 37d3089..2393f73 100644 --- a/samples/Sample.Kafka.SqlServer/Program.cs +++ b/samples/Sample.RabbitMQ.SqlServer/Program.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; -namespace Sample.Kafka +namespace Sample.RabbitMQ.SqlServer { public class Program { diff --git a/samples/Sample.Kafka.SqlServer/Sample.Kafka.SqlServer.csproj b/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj similarity index 55% rename from samples/Sample.Kafka.SqlServer/Sample.Kafka.SqlServer.csproj rename to samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj index 675a95b..9fa7e09 100644 --- a/samples/Sample.Kafka.SqlServer/Sample.Kafka.SqlServer.csproj +++ b/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj @@ -1,26 +1,24 @@  - netcoreapp1.1 - Sample.Kafka.SqlServer + netcoreapp2.0 + Sample.RabbitMQ.SqlServer - - - - - - - - + + + + + + + - - + - + diff --git a/samples/Sample.Kafka.SqlServer/Startup.cs b/samples/Sample.RabbitMQ.SqlServer/Startup.cs similarity index 90% rename from samples/Sample.Kafka.SqlServer/Startup.cs rename to samples/Sample.RabbitMQ.SqlServer/Startup.cs index 08291c5..523e2d9 100644 --- a/samples/Sample.Kafka.SqlServer/Startup.cs +++ b/samples/Sample.RabbitMQ.SqlServer/Startup.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Sample.Kafka +namespace Sample.RabbitMQ.SqlServer { public class Startup { @@ -14,7 +14,7 @@ namespace Sample.Kafka services.AddCap(x => { x.UseEntityFramework(); - x.UseKafka("localhost:9092"); + x.UseRabbitMQ("localhost"); }); services.AddMvc(); From cfa02281ea25d335d5bd79da17e97e69873ec675 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Sun, 13 Aug 2017 13:45:32 +0800 Subject: [PATCH 04/68] upgrade project PackageReference version. --- .../DotNetCore.CAP.MySql.Test.csproj | 10 +--------- .../DotNetCore.CAP.SqlServer.Test.csproj | 11 +---------- test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj | 9 --------- 3 files changed, 2 insertions(+), 28 deletions(-) diff --git a/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj b/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj index 1694331..3f8a3c3 100644 --- a/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj +++ b/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj @@ -8,10 +8,6 @@ true - - - - @@ -33,9 +29,5 @@ - - - - - + \ No newline at end of file diff --git a/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj b/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj index bd29d9b..536be78 100644 --- a/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj +++ b/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj @@ -4,15 +4,10 @@ netcoreapp2.0 true DotNetCore.CAP.SqlServer.Test - DotNetCore.CAP.SqlServer.Test true - - - - - + @@ -36,8 +31,4 @@ - - - - diff --git a/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj b/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj index 27bf741..b4d7bae 100644 --- a/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj +++ b/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj @@ -4,14 +4,9 @@ netcoreapp2.0 true DotNetCore.CAP.Test - DotNetCore.CAP.Test true - - - - @@ -27,8 +22,4 @@ - - - - From 85577e3812b707cc7edaf237f6839aff8d051699 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Sun, 13 Aug 2017 13:46:05 +0800 Subject: [PATCH 05/68] update sampels. --- samples/Sample.RabbitMQ.MySql/AppDbContext.cs | 2 +- .../Controllers/ValuesController.cs | 14 +++++++-- .../Sample.RabbitMQ.MySql.csproj | 31 ++++++++++--------- samples/Sample.RabbitMQ.MySql/Startup.cs | 2 +- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/samples/Sample.RabbitMQ.MySql/AppDbContext.cs b/samples/Sample.RabbitMQ.MySql/AppDbContext.cs index 5a60da7..20e01eb 100644 --- a/samples/Sample.RabbitMQ.MySql/AppDbContext.cs +++ b/samples/Sample.RabbitMQ.MySql/AppDbContext.cs @@ -10,7 +10,7 @@ namespace Sample.RabbitMQ.MySql { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - optionsBuilder.UseMySql("Server=localhost;Database=Sample.RabbitMQ.MySql;Uid=root;Pwd=123123;"); + optionsBuilder.UseMySql("Server=localhost;Database=Sample.RabbitMQ.MySql;UserId=root;Password=123123;"); } } } diff --git a/samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs b/samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs index eec782e..ed4ac06 100644 --- a/samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs +++ b/samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs @@ -23,7 +23,16 @@ namespace Sample.RabbitMQ.MySql.Controllers [Route("~/publish")] public IActionResult PublishMessage() { - _capBus.Publish("sample.kafka.sqlserver", ""); + _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now); + + return Ok(); + } + + + [Route("~/publish2")] + public IActionResult PublishMessage2() + { + _capBus.Publish("sample.kafka.sqlserver4", DateTime.Now); return Ok(); } @@ -34,11 +43,12 @@ namespace Sample.RabbitMQ.MySql.Controllers 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() diff --git a/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj b/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj index e90171f..2ae522d 100644 --- a/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj +++ b/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj @@ -1,27 +1,30 @@  - netcoreapp1.1 + netcoreapp2.0 + + + + 1701;1702;1705;3277; + NU1605;MSB3277 - - - - - - - - - + + + + + + + + - - + - - + + diff --git a/samples/Sample.RabbitMQ.MySql/Startup.cs b/samples/Sample.RabbitMQ.MySql/Startup.cs index 5a3d92f..6a9951f 100644 --- a/samples/Sample.RabbitMQ.MySql/Startup.cs +++ b/samples/Sample.RabbitMQ.MySql/Startup.cs @@ -18,7 +18,7 @@ namespace Sample.RabbitMQ.MySql services.AddCap(x => { x.UseEntityFramework(); - x.UseKafka("localhost:9092"); + x.UseRabbitMQ("localhost"); }); services.AddMvc(); From 15808012c39ed004ff8ec58597586a0798975c3a Mon Sep 17 00:00:00 2001 From: Savorboard Date: Sun, 13 Aug 2017 13:46:31 +0800 Subject: [PATCH 06/68] upgrade System.Data.SqlClient version. --- src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj b/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj index 8dc779f..84d6acc 100644 --- a/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj +++ b/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj @@ -11,7 +11,8 @@ - + + From bc2db684c9da71c5e2749ef98fac9d59e83f4b20 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Sun, 13 Aug 2017 13:47:03 +0800 Subject: [PATCH 07/68] remove shared floder. --- CAP.sln | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/CAP.sln b/CAP.sln index b7e380d..45bd3bc 100644 --- a/CAP.sln +++ b/CAP.sln @@ -22,15 +22,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.zh-cn.md = README.zh-cn.md EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{9E5A7F49-8E31-4A71-90CC-1DA9AEDA99EE}" - ProjectSection(SolutionItems) = preProject - test\Shared\MessageManagerTestBase.cs = test\Shared\MessageManagerTestBase.cs - test\Shared\TestLogger.cs = test\Shared\TestLogger.cs - EndProjectSection - ProjectSection(FolderStartupServices) = postProject - {82A7F48D-3B50-4B1E-B82E-3ADA8210C358} = {82A7F48D-3B50-4B1E-B82E-3ADA8210C358} - EndProjectSection -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP", "src\DotNetCore.CAP\DotNetCore.CAP.csproj", "{E8AF8611-0EA4-4B19-BC48-87C57A87DC66}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{3A6B6931-A123-477A-9469-8B468B5385AF}" @@ -63,10 +54,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.MySql.Test", 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}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.RabbitMQ.SqlServer", "samples\Sample.RabbitMQ.SqlServer\Sample.RabbitMQ.SqlServer.csproj", "{AF17B956-B79E-48B7-9B5B-EB15A386B112}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.PostgreSql", "src\DotNetCore.CAP.PostgreSql\DotNetCore.CAP.PostgreSql.csproj", "{82C403AB-ED68-4084-9A1D-11334F9F08F9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.RabbitMQ.PostgreSql", "samples\Sample.RabbitMQ.PostgreSql\Sample.RabbitMQ.PostgreSql.csproj", "{A17E8E72-DFFC-4822-BB38-73D59A8B264E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -116,12 +109,15 @@ Global {82C403AB-ED68-4084-9A1D-11334F9F08F9}.Debug|Any CPU.Build.0 = Debug|Any CPU {82C403AB-ED68-4084-9A1D-11334F9F08F9}.Release|Any CPU.ActiveCfg = Release|Any CPU {82C403AB-ED68-4084-9A1D-11334F9F08F9}.Release|Any CPU.Build.0 = Release|Any CPU + {A17E8E72-DFFC-4822-BB38-73D59A8B264E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A17E8E72-DFFC-4822-BB38-73D59A8B264E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A17E8E72-DFFC-4822-BB38-73D59A8B264E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A17E8E72-DFFC-4822-BB38-73D59A8B264E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {9E5A7F49-8E31-4A71-90CC-1DA9AEDA99EE} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0} {E8AF8611-0EA4-4B19-BC48-87C57A87DC66} = {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} @@ -133,6 +129,7 @@ Global {9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873} = {3A6B6931-A123-477A-9469-8B468B5385AF} {AF17B956-B79E-48B7-9B5B-EB15A386B112} = {3A6B6931-A123-477A-9469-8B468B5385AF} {82C403AB-ED68-4084-9A1D-11334F9F08F9} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} + {A17E8E72-DFFC-4822-BB38-73D59A8B264E} = {3A6B6931-A123-477A-9469-8B468B5385AF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2E70565D-94CF-40B4-BFE1-AC18D5F736AB} From 1263252910064ab59025fc94ae620be733a85272 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Sun, 13 Aug 2017 13:50:05 +0800 Subject: [PATCH 08/68] modify ci config. --- .travis.yml | 6 +++--- build.sh | 2 +- build/version.props | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index ec4e8c6..52f2ebc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,13 +6,13 @@ matrix: include: - os: linux dist: trusty # Ubuntu 14.04 - dotnet: 1.0.1 + dotnet: 2.0.0 mono: none env: DOTNETCORE=1 sudo: required - os: osx - osx_image: xcode7.3 # macOS 10.11 - dotnet: 1.0.1 + osx_image: xcode8.3 # macOS 10.12 + dotnet: 2.0.0 mono: none env: DOTNETCORE=1 diff --git a/build.sh b/build.sh index 91b52b0..e70f996 100644 --- a/build.sh +++ b/build.sh @@ -1,3 +1,3 @@ dotnet --info dotnet restore -dotnet test test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj -f netcoreapp1.1 \ No newline at end of file +dotnet test test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj -f netcoreapp2.0 \ No newline at end of file diff --git a/build/version.props b/build/version.props index a5d60c8..604d215 100644 --- a/build/version.props +++ b/build/version.props @@ -1,7 +1,7 @@ - 1 - 2 + 2 + 0 0 $(VersionMajor).$(VersionMinor).$(VersionPatch) From 7bcc2e9f1df33434eaf5a12b820c14896d73896e Mon Sep 17 00:00:00 2001 From: Savorboard Date: Sun, 13 Aug 2017 14:11:06 +0800 Subject: [PATCH 09/68] modify unit tests. --- src/DotNetCore.CAP.SqlServer/SqlServerStorage.cs | 1 - .../DatabaseTestHost.cs | 12 ++++++------ .../DotNetCore.CAP.SqlServer.Test.csproj | 2 -- test/DotNetCore.CAP.SqlServer.Test/TestDbContext.cs | 13 ------------- test/DotNetCore.CAP.SqlServer.Test/TestHost.cs | 4 +--- 5 files changed, 7 insertions(+), 25 deletions(-) delete mode 100644 test/DotNetCore.CAP.SqlServer.Test/TestDbContext.cs diff --git a/src/DotNetCore.CAP.SqlServer/SqlServerStorage.cs b/src/DotNetCore.CAP.SqlServer/SqlServerStorage.cs index 57d7e5d..9ef0247 100644 --- a/src/DotNetCore.CAP.SqlServer/SqlServerStorage.cs +++ b/src/DotNetCore.CAP.SqlServer/SqlServerStorage.cs @@ -2,7 +2,6 @@ using System.Data.SqlClient; using System.Threading; using System.Threading.Tasks; using Dapper; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace DotNetCore.CAP.SqlServer diff --git a/test/DotNetCore.CAP.SqlServer.Test/DatabaseTestHost.cs b/test/DotNetCore.CAP.SqlServer.Test/DatabaseTestHost.cs index d759384..5bf5a7d 100644 --- a/test/DotNetCore.CAP.SqlServer.Test/DatabaseTestHost.cs +++ b/test/DotNetCore.CAP.SqlServer.Test/DatabaseTestHost.cs @@ -1,4 +1,5 @@ using System.Data; +using System.Data.SqlClient; using System.Threading; using Dapper; using Microsoft.EntityFrameworkCore; @@ -54,21 +55,20 @@ CREATE DATABASE [{databaseName}];"); private void DeleteAllData() { - using (CreateScope()) + var conn = ConnectionUtil.GetConnectionString(); + using (var connection = new SqlConnection(conn)) { - var context = GetService(); - - var commands = new[] - { + var commands = new[] { "DISABLE TRIGGER ALL ON ?", "ALTER TABLE ? NOCHECK CONSTRAINT ALL", "DELETE FROM ?", "ALTER TABLE ? CHECK CONSTRAINT ALL", "ENABLE TRIGGER ALL ON ?" }; + foreach (var command in commands) { - context.Database.GetDbConnection().Execute( + connection.Execute( "sp_MSforeachtable", new { command1 = command }, commandType: CommandType.StoredProcedure); diff --git a/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj b/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj index 536be78..c2e8b43 100644 --- a/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj +++ b/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj @@ -27,8 +27,6 @@ - - diff --git a/test/DotNetCore.CAP.SqlServer.Test/TestDbContext.cs b/test/DotNetCore.CAP.SqlServer.Test/TestDbContext.cs deleted file mode 100644 index d59bdf1..0000000 --- a/test/DotNetCore.CAP.SqlServer.Test/TestDbContext.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace DotNetCore.CAP.SqlServer.Test -{ - public class TestDbContext : DbContext - { - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - var connectionString = ConnectionUtil.GetConnectionString(); - optionsBuilder.UseSqlServer(connectionString); - } - } -} \ No newline at end of file diff --git a/test/DotNetCore.CAP.SqlServer.Test/TestHost.cs b/test/DotNetCore.CAP.SqlServer.Test/TestHost.cs index 31cbfd1..5922c17 100644 --- a/test/DotNetCore.CAP.SqlServer.Test/TestHost.cs +++ b/test/DotNetCore.CAP.SqlServer.Test/TestHost.cs @@ -1,5 +1,4 @@ using System; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; namespace DotNetCore.CAP.SqlServer.Test @@ -30,8 +29,7 @@ namespace DotNetCore.CAP.SqlServer.Test _connectionString = ConnectionUtil.GetConnectionString(); services.AddSingleton(new SqlServerOptions { ConnectionString = _connectionString }); - services.AddSingleton(); - services.AddDbContext(options => options.UseSqlServer(_connectionString)); + services.AddSingleton(); _services = services; } From 9ceb6922327dc6e16f167e2e341b6a1a0abf6733 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Sun, 13 Aug 2017 14:40:13 +0800 Subject: [PATCH 10/68] downgrading System.Data.SqlClient to 4.3.1 --- .../DotNetCore.CAP.SqlServer.csproj | 2 +- .../DotNetCore.CAP.SqlServer.Test.csproj | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj b/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj index 84d6acc..a5f76e0 100644 --- a/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj +++ b/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj @@ -12,7 +12,7 @@ - + diff --git a/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj b/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj index c2e8b43..b854027 100644 --- a/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj +++ b/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj @@ -2,9 +2,7 @@ netcoreapp2.0 - true - DotNetCore.CAP.SqlServer.Test - true + false @@ -15,6 +13,7 @@ + @@ -26,7 +25,6 @@ - From b960c0490c0c6788ae76005d5d59eae1d57b4f91 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Sun, 13 Aug 2017 18:36:01 +0800 Subject: [PATCH 11/68] modify creates time stamp function. --- CODE_OF_CONDUCT.md | 46 ---------------------------------------------- build/util.cake | 3 +-- 2 files changed, 1 insertion(+), 48 deletions(-) delete mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 7f29ea1..0000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,46 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at yangxiaodong1214@126.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] - -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ diff --git a/build/util.cake b/build/util.cake index 4a18e87..48c19de 100644 --- a/build/util.cake +++ b/build/util.cake @@ -19,7 +19,6 @@ Configuration: {Build.Configuration} public static string CreateStamp() { - var seconds = (long)(DateTime.UtcNow - new DateTime(2017, 1, 1)).TotalSeconds; - return seconds.ToString().PadLeft(11, (char)'0'); + return DateTime.Now.ToString("yyMMddHM"); } } From c4a465cdd774169b1f693a1adb2e8ff357f7eb61 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Sun, 13 Aug 2017 18:37:22 +0800 Subject: [PATCH 12/68] --- build/util.cake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/util.cake b/build/util.cake index 48c19de..db42eb7 100644 --- a/build/util.cake +++ b/build/util.cake @@ -19,6 +19,7 @@ Configuration: {Build.Configuration} public static string CreateStamp() { - return DateTime.Now.ToString("yyMMddHM"); + var seconds = (long)(DateTime.UtcNow - new DateTime(2017, 1, 1)).TotalSeconds; + return seconds.ToString(); } } From 730507d099e25d2e002e1ea75f29bbd21a546569 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Mon, 14 Aug 2017 18:28:24 +0800 Subject: [PATCH 13/68] fixed issue #25. --- .../Abstractions/CapPublisherBase.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs b/src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs index 257f881..93f0b6c 100644 --- a/src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs +++ b/src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs @@ -12,9 +12,10 @@ namespace DotNetCore.CAP.Abstractions protected IDbConnection DbConnection { get; set; } protected IDbTransaction DbTranasaction { get; set; } protected bool IsCapOpenedTrans { get; set; } + protected bool IsCapOpenedConn { get; set; } protected bool IsUsingEF { get; set; } protected IServiceProvider ServiceProvider { get; set; } - + public void Publish(string name, T contentObj) { CheckIsUsingEF(name); @@ -83,7 +84,10 @@ namespace DotNetCore.CAP.Abstractions throw new ArgumentNullException(nameof(dbConnection)); if (dbConnection.State != ConnectionState.Open) + { + IsCapOpenedConn = true; dbConnection.Open(); + } if (dbTransaction == null) { @@ -122,8 +126,13 @@ namespace DotNetCore.CAP.Abstractions { dbTransaction.Commit(); dbTransaction.Dispose(); + } + + if (IsCapOpenedConn) + { dbConnection.Dispose(); } + PublishQueuer.PulseEvent.Set(); } @@ -142,8 +151,12 @@ namespace DotNetCore.CAP.Abstractions { dbTransaction.Commit(); dbTransaction.Dispose(); + } + if (IsCapOpenedConn) + { dbConnection.Dispose(); } + PublishQueuer.PulseEvent.Set(); } From af8c37cfed6a72f9be990d116b399fa5c7b5f255 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Mon, 14 Aug 2017 18:29:35 +0800 Subject: [PATCH 14/68] add MongoDB ObjectId --- src/DotNetCore.CAP/Infrastructure/ObjectId.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/DotNetCore.CAP/Infrastructure/ObjectId.cs b/src/DotNetCore.CAP/Infrastructure/ObjectId.cs index 138690e..d148126 100644 --- a/src/DotNetCore.CAP/Infrastructure/ObjectId.cs +++ b/src/DotNetCore.CAP/Infrastructure/ObjectId.cs @@ -8,7 +8,8 @@ using System.Threading; namespace DotNetCore.CAP { - /// Represents an ObjectId + /// + /// Represents an ObjectId /// [Serializable] public struct ObjectId : IComparable, IEquatable From b298c943b1d76d4a145f9944e52dcaed459f7579 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Mon, 14 Aug 2017 18:30:21 +0800 Subject: [PATCH 15/68] refactor of DI. --- .../CAP.MySqlCapOptionsExtension.cs | 27 +++++++------------ .../CAP.PostgreSqlCapOptionsExtension.cs | 27 +++++++------------ .../CAP.RabbitMQCapOptionsExtension.cs | 4 +++ 3 files changed, 24 insertions(+), 34 deletions(-) diff --git a/src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs b/src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs index aa825a2..ce6fd34 100644 --- a/src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs @@ -28,24 +28,17 @@ namespace DotNetCore.CAP 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(x => + { + var dbContext = (DbContext)x.GetService(mysqlOptions.DbContextType); + mysqlOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString; + return mysqlOptions; + }); + } + else + { + services.AddSingleton(mysqlOptions); } - services.AddSingleton(mysqlOptions); - } - -#if NETSTANDARD1_6 - private IServiceProvider TempBuildService(IServiceCollection services) - { - return services.BuildServiceProvider(); - } -#else - private ServiceProvider TempBuildService(IServiceCollection services) - { - return services.BuildServiceProvider(); } -#endif } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs b/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs index d3d8b14..9038dff 100644 --- a/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs @@ -28,24 +28,17 @@ namespace DotNetCore.CAP if (postgreSqlOptions.DbContextType != null) { - var provider = TempBuildService(services); - var dbContextObj = provider.GetService(postgreSqlOptions.DbContextType); - var dbContext = (DbContext)dbContextObj; - postgreSqlOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString; + services.AddSingleton(x => + { + var dbContext = (DbContext)x.GetService(postgreSqlOptions.DbContextType); + postgreSqlOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString; + return postgreSqlOptions; + }); + } + else + { + services.AddSingleton(postgreSqlOptions); } - services.AddSingleton(postgreSqlOptions); - } - -#if NETSTANDARD1_6 - private IServiceProvider TempBuildService(IServiceCollection services) - { - return services.BuildServiceProvider(); - } -#else - private ServiceProvider TempBuildService(IServiceCollection services) - { - return services.BuildServiceProvider(); } -#endif } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.RabbitMQ/CAP.RabbitMQCapOptionsExtension.cs b/src/DotNetCore.CAP.RabbitMQ/CAP.RabbitMQCapOptionsExtension.cs index 4202570..22e2af2 100644 --- a/src/DotNetCore.CAP.RabbitMQ/CAP.RabbitMQCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.RabbitMQ/CAP.RabbitMQCapOptionsExtension.cs @@ -21,6 +21,10 @@ namespace DotNetCore.CAP services.AddSingleton(options); services.AddSingleton(); + + services.AddSingleton(); + services.AddScoped(x => x.GetService().Rent()); + services.AddTransient(); } } From deee0bc74e996fcb10c2aef23d72f7d4a78d528e Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Mon, 14 Aug 2017 18:31:21 +0800 Subject: [PATCH 16/68] add rabbitmq connection pool. --- src/DotNetCore.CAP.RabbitMQ/ConnectionPool.cs | 91 +++++++++++++++++++ .../IConnectionPool.cs | 14 +++ 2 files changed, 105 insertions(+) create mode 100644 src/DotNetCore.CAP.RabbitMQ/ConnectionPool.cs create mode 100644 src/DotNetCore.CAP.RabbitMQ/IConnectionPool.cs diff --git a/src/DotNetCore.CAP.RabbitMQ/ConnectionPool.cs b/src/DotNetCore.CAP.RabbitMQ/ConnectionPool.cs new file mode 100644 index 0000000..83116bf --- /dev/null +++ b/src/DotNetCore.CAP.RabbitMQ/ConnectionPool.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Threading; +using RabbitMQ.Client; + +namespace DotNetCore.CAP.RabbitMQ +{ + public class ConnectionPool : IConnectionPool, IDisposable + { + private const int DefaultPoolSize = 32; + + private readonly ConcurrentQueue _pool = new ConcurrentQueue(); + + private readonly Func _activator; + + private int _maxSize; + private int _count; + + public ConnectionPool(RabbitMQOptions options) + { + _maxSize = DefaultPoolSize; + + _activator = CreateActivator(options); + } + + private static Func CreateActivator(RabbitMQOptions options) + { + var factory = new ConnectionFactory() + { + HostName = options.HostName, + UserName = options.UserName, + Port = options.Port, + Password = options.Password, + VirtualHost = options.VirtualHost, + RequestedConnectionTimeout = options.RequestedConnectionTimeout, + SocketReadTimeout = options.SocketReadTimeout, + SocketWriteTimeout = options.SocketWriteTimeout + }; + + return () => factory.CreateConnection(); + } + + public virtual IConnection Rent() + { + if (_pool.TryDequeue(out IConnection connection)) + { + Interlocked.Decrement(ref _count); + + Debug.Assert(_count >= 0); + + return connection; + } + + connection = _activator(); + + return connection; + } + + public virtual bool Return(IConnection connection) + { + if (Interlocked.Increment(ref _count) <= _maxSize) + { + _pool.Enqueue(connection); + + return true; + } + + Interlocked.Decrement(ref _count); + + Debug.Assert(_maxSize == 0 || _pool.Count <= _maxSize); + + return false; + } + + IConnection IConnectionPool.Rent() => Rent(); + + bool IConnectionPool.Return(IConnection connection) => Return(connection); + + public void Dispose() + { + _maxSize = 0; + + IConnection context; + while (_pool.TryDequeue(out context)) + { + context.Dispose(); + } + } + } +} diff --git a/src/DotNetCore.CAP.RabbitMQ/IConnectionPool.cs b/src/DotNetCore.CAP.RabbitMQ/IConnectionPool.cs new file mode 100644 index 0000000..9097f28 --- /dev/null +++ b/src/DotNetCore.CAP.RabbitMQ/IConnectionPool.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; +using RabbitMQ.Client; + +namespace DotNetCore.CAP.RabbitMQ +{ + public interface IConnectionPool + { + IConnection Rent(); + + bool Return(IConnection context); + } +} From 9740e5353dd414e3c67e10d70eb21c80913d7198 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Mon, 14 Aug 2017 18:31:34 +0800 Subject: [PATCH 17/68] refactor of DI. --- .../CAP.SqlServerCapOptionsExtension.cs | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs b/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs index f36a4bc..5795b3b 100644 --- a/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs @@ -24,29 +24,22 @@ namespace DotNetCore.CAP services.AddTransient(); var sqlServerOptions = new SqlServerOptions(); + _configure(sqlServerOptions); if (sqlServerOptions.DbContextType != null) { - var provider = TempBuildService(services); - var dbContextObj = provider.GetService(sqlServerOptions.DbContextType); - var dbContext = (DbContext)dbContextObj; - sqlServerOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString; + services.AddSingleton(x => + { + var dbContext = (DbContext)x.GetService(sqlServerOptions.DbContextType); + sqlServerOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString; + return sqlServerOptions; + }); + } + else + { + services.AddSingleton(sqlServerOptions); } - services.AddSingleton(sqlServerOptions); - } - -#if NETSTANDARD1_6 - private IServiceProvider TempBuildService(IServiceCollection services) - { - return services.BuildServiceProvider(); - } -#else - private ServiceProvider TempBuildService(IServiceCollection services) - { - return services.BuildServiceProvider(); } -#endif - } } \ No newline at end of file From d6f72d1d1c75851a250691b5c53b346654716630 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Mon, 14 Aug 2017 18:31:55 +0800 Subject: [PATCH 18/68] refactor with new connection pool. --- .../PublishQueueExecutor.cs | 23 +++++-------------- .../RabbitMQConsumerClient.cs | 19 ++++----------- .../RabbitMQConsumerClientFactory.cs | 8 +++++-- 3 files changed, 16 insertions(+), 34 deletions(-) diff --git a/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs b/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs index 27b777b..794b636 100644 --- a/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs +++ b/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs @@ -3,7 +3,6 @@ using System.Text; using System.Threading.Tasks; using DotNetCore.CAP.Processor.States; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; using RabbitMQ.Client; namespace DotNetCore.CAP.RabbitMQ @@ -11,35 +10,25 @@ namespace DotNetCore.CAP.RabbitMQ internal sealed class PublishQueueExecutor : BasePublishQueueExecutor { private readonly ILogger _logger; + private readonly IConnection _connection; private readonly RabbitMQOptions _rabbitMQOptions; public PublishQueueExecutor(IStateChanger stateChanger, - RabbitMQOptions options, + IConnection connection, + RabbitMQOptions rabbitMQOptions, ILogger logger) : base(stateChanger, logger) { _logger = logger; - _rabbitMQOptions = options; + _connection = connection; + _rabbitMQOptions = rabbitMQOptions; } public override Task PublishAsync(string keyName, string content) { - var factory = new ConnectionFactory() - { - HostName = _rabbitMQOptions.HostName, - UserName = _rabbitMQOptions.UserName, - Port = _rabbitMQOptions.Port, - Password = _rabbitMQOptions.Password, - VirtualHost = _rabbitMQOptions.VirtualHost, - RequestedConnectionTimeout = _rabbitMQOptions.RequestedConnectionTimeout, - SocketReadTimeout = _rabbitMQOptions.SocketReadTimeout, - SocketWriteTimeout = _rabbitMQOptions.SocketWriteTimeout - }; - try { - using (var connection = factory.CreateConnection()) - using (var channel = connection.CreateModel()) + using (var channel = _connection.CreateModel()) { var body = Encoding.UTF8.GetBytes(content); diff --git a/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClient.cs b/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClient.cs index 11d888a..a6720fd 100644 --- a/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClient.cs +++ b/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClient.cs @@ -14,7 +14,6 @@ namespace DotNetCore.CAP.RabbitMQ private readonly string _queueName; private readonly RabbitMQOptions _rabbitMQOptions; - private IConnectionFactory _connectionFactory; private IConnection _connection; private IModel _channel; private ulong _deliveryTag; @@ -23,9 +22,12 @@ namespace DotNetCore.CAP.RabbitMQ public event EventHandler OnError; - public RabbitMQConsumerClient(string queueName, RabbitMQOptions options) + public RabbitMQConsumerClient(string queueName, + IConnection connection, + RabbitMQOptions options) { _queueName = queueName; + _connection = connection; _rabbitMQOptions = options; _exchageName = options.TopicExchangeName; @@ -34,19 +36,6 @@ namespace DotNetCore.CAP.RabbitMQ private void InitClient() { - _connectionFactory = new ConnectionFactory() - { - HostName = _rabbitMQOptions.HostName, - UserName = _rabbitMQOptions.UserName, - Port = _rabbitMQOptions.Port, - Password = _rabbitMQOptions.Password, - VirtualHost = _rabbitMQOptions.VirtualHost, - RequestedConnectionTimeout = _rabbitMQOptions.RequestedConnectionTimeout, - SocketReadTimeout = _rabbitMQOptions.SocketReadTimeout, - SocketWriteTimeout = _rabbitMQOptions.SocketWriteTimeout - }; - - _connection = _connectionFactory.CreateConnection(); _channel = _connection.CreateModel(); _channel.ExchangeDeclare( diff --git a/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs b/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs index fcd267d..5fc9d8f 100644 --- a/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs +++ b/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs @@ -1,19 +1,23 @@ using Microsoft.Extensions.Options; +using RabbitMQ.Client; namespace DotNetCore.CAP.RabbitMQ { internal sealed class RabbitMQConsumerClientFactory : IConsumerClientFactory { private readonly RabbitMQOptions _rabbitMQOptions; + private readonly IConnection _connection; - public RabbitMQConsumerClientFactory(RabbitMQOptions rabbitMQOptions) + + public RabbitMQConsumerClientFactory(RabbitMQOptions rabbitMQOptions, IConnection connection) { _rabbitMQOptions = rabbitMQOptions; + _connection = connection; } public IConsumerClient Create(string groupId) { - return new RabbitMQConsumerClient(groupId, _rabbitMQOptions); + return new RabbitMQConsumerClient(groupId, _connection, _rabbitMQOptions); } } } \ No newline at end of file From 1248facd3a6d279fdadeb012fe1167eef21ad739 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Tue, 15 Aug 2017 11:18:14 +0800 Subject: [PATCH 19/68] upgrade nuget reference package version. --- src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj | 2 +- src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj | 2 +- test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj | 2 +- .../DotNetCore.CAP.SqlServer.Test.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj index 9ee70d2..ce900b2 100644 --- a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj +++ b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj b/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj index a5f76e0..84d6acc 100644 --- a/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj +++ b/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj @@ -12,7 +12,7 @@ - + diff --git a/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj b/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj index 3f8a3c3..dee994f 100644 --- a/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj +++ b/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj @@ -16,7 +16,7 @@ - + diff --git a/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj b/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj index b854027..1a8cc9a 100644 --- a/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj +++ b/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj @@ -13,7 +13,7 @@ - + From a9e0743f07c1c778c1ef0fde2553e5ed03c5f976 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Tue, 15 Aug 2017 17:26:23 +0800 Subject: [PATCH 20/68] fixed issue #25 --- src/DotNetCore.CAP.MySql/CapPublisher.cs | 12 ++++++++---- src/DotNetCore.CAP.PostgreSql/CapPublisher.cs | 12 ++++++++---- src/DotNetCore.CAP.SqlServer/CapPublisher.cs | 14 +++++++++----- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/DotNetCore.CAP.MySql/CapPublisher.cs b/src/DotNetCore.CAP.MySql/CapPublisher.cs index 6b8476a..59801df 100644 --- a/src/DotNetCore.CAP.MySql/CapPublisher.cs +++ b/src/DotNetCore.CAP.MySql/CapPublisher.cs @@ -34,13 +34,17 @@ namespace DotNetCore.CAP.MySql protected override void PrepareConnectionForEF() { DbConnection = _dbContext.Database.GetDbConnection(); - var transaction = _dbContext.Database.CurrentTransaction; - if (transaction == null) + var dbContextTransaction = _dbContext.Database.CurrentTransaction; + var dbTrans = dbContextTransaction?.GetDbTransaction(); + //DbTransaction is dispose in original + if (dbTrans?.Connection == null) { IsCapOpenedTrans = true; - transaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted); + dbContextTransaction?.Dispose(); + dbContextTransaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted); + dbTrans = dbContextTransaction.GetDbTransaction(); } - DbTranasaction = transaction.GetDbTransaction(); + DbTranasaction = dbTrans; } protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message) diff --git a/src/DotNetCore.CAP.PostgreSql/CapPublisher.cs b/src/DotNetCore.CAP.PostgreSql/CapPublisher.cs index f141b1e..4e3c9cd 100644 --- a/src/DotNetCore.CAP.PostgreSql/CapPublisher.cs +++ b/src/DotNetCore.CAP.PostgreSql/CapPublisher.cs @@ -34,13 +34,17 @@ namespace DotNetCore.CAP.PostgreSql protected override void PrepareConnectionForEF() { DbConnection = _dbContext.Database.GetDbConnection(); - var transaction = _dbContext.Database.CurrentTransaction; - if (transaction == null) + var dbContextTransaction = _dbContext.Database.CurrentTransaction; + var dbTrans = dbContextTransaction?.GetDbTransaction(); + //DbTransaction is dispose in original + if (dbTrans?.Connection == null) { IsCapOpenedTrans = true; - transaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted); + dbContextTransaction?.Dispose(); + dbContextTransaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted); + dbTrans = dbContextTransaction.GetDbTransaction(); } - DbTranasaction = transaction.GetDbTransaction(); + DbTranasaction = dbTrans; } protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message) diff --git a/src/DotNetCore.CAP.SqlServer/CapPublisher.cs b/src/DotNetCore.CAP.SqlServer/CapPublisher.cs index 3acddb2..500a82a 100644 --- a/src/DotNetCore.CAP.SqlServer/CapPublisher.cs +++ b/src/DotNetCore.CAP.SqlServer/CapPublisher.cs @@ -1,5 +1,6 @@ using System; using System.Data; +using System.Data.SqlClient; using System.Threading.Tasks; using Dapper; using DotNetCore.CAP.Abstractions; @@ -34,13 +35,17 @@ namespace DotNetCore.CAP.SqlServer protected override void PrepareConnectionForEF() { DbConnection = _dbContext.Database.GetDbConnection(); - var transaction = _dbContext.Database.CurrentTransaction; - if (transaction == null) + var dbContextTransaction = _dbContext.Database.CurrentTransaction; + var dbTrans = dbContextTransaction?.GetDbTransaction(); + //DbTransaction is dispose in original + if (dbTrans?.Connection == null) { IsCapOpenedTrans = true; - transaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted); + dbContextTransaction?.Dispose(); + dbContextTransaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted); + dbTrans = dbContextTransaction.GetDbTransaction(); } - DbTranasaction = transaction.GetDbTransaction(); + DbTranasaction = dbTrans; } protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message) @@ -53,7 +58,6 @@ namespace DotNetCore.CAP.SqlServer protected override async Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message) { await dbConnection.ExecuteAsync(PrepareSql(), message, dbTransaction); - _logger.LogInformation("Published Message has been persisted in the database. name:" + message.ToString()); } From ac0fc62a8d4df8ad7db48ab69886e963f319eafd Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Tue, 15 Aug 2017 18:33:45 +0800 Subject: [PATCH 21/68] add feature of #22. --- src/DotNetCore.CAP.SqlServer/CapPublisher.cs | 11 +- .../Abstractions/CapPublisherBase.cs | 102 +++++++++--------- src/DotNetCore.CAP/ICallbackPublisher.cs | 12 +++ src/DotNetCore.CAP/ICapPublisher.cs | 12 ++- .../Internal/IConsumerInvoker.Default.cs | 30 ++++-- .../Internal/IModelBinder.ComplexType.cs | 7 +- src/DotNetCore.CAP/Models/Message.cs | 26 +++++ 7 files changed, 135 insertions(+), 65 deletions(-) create mode 100644 src/DotNetCore.CAP/ICallbackPublisher.cs create mode 100644 src/DotNetCore.CAP/Models/Message.cs diff --git a/src/DotNetCore.CAP.SqlServer/CapPublisher.cs b/src/DotNetCore.CAP.SqlServer/CapPublisher.cs index 500a82a..7a0da8f 100644 --- a/src/DotNetCore.CAP.SqlServer/CapPublisher.cs +++ b/src/DotNetCore.CAP.SqlServer/CapPublisher.cs @@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging; namespace DotNetCore.CAP.SqlServer { - public class CapPublisher : CapPublisherBase + public class CapPublisher : CapPublisherBase, ICallbackPublisher { private readonly ILogger _logger; private readonly SqlServerOptions _options; @@ -61,6 +61,14 @@ namespace DotNetCore.CAP.SqlServer _logger.LogInformation("Published Message has been persisted in the database. name:" + message.ToString()); } + public Task PublishAsync(string name, object contentObj) + { + using (var conn = new SqlConnection(_options.ConnectionString)) + { + return conn.ExecuteAsync(PrepareSql(), contentObj); + } + } + #region private methods private string PrepareSql() @@ -68,7 +76,6 @@ namespace DotNetCore.CAP.SqlServer return $"INSERT INTO {_options.Schema}.[Published] ([Name],[Content],[Retries],[Added],[ExpiresAt],[StatusName])VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName)"; } - #endregion private methods } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs b/src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs index 93f0b6c..34aba5b 100644 --- a/src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs +++ b/src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs @@ -7,7 +7,7 @@ using DotNetCore.CAP.Processor; namespace DotNetCore.CAP.Abstractions { - public abstract class CapPublisherBase : ICapPublisher + public abstract class CapPublisherBase : ICapPublisher, IDisposable { protected IDbConnection DbConnection { get; set; } protected IDbTransaction DbTranasaction { get; set; } @@ -16,44 +16,46 @@ namespace DotNetCore.CAP.Abstractions protected bool IsUsingEF { get; set; } protected IServiceProvider ServiceProvider { get; set; } - public void Publish(string name, T contentObj) + public void Publish(string name, T contentObj, string callbackName = null) { CheckIsUsingEF(name); PrepareConnectionForEF(); - var content = Serialize(contentObj); + var content = Serialize(contentObj, callbackName); - PublishWithTrans(name, content, DbConnection, DbTranasaction); + PublishWithTrans(name, content); } - public Task PublishAsync(string name, T contentObj) + public Task PublishAsync(string name, T contentObj, string callbackName = null) { CheckIsUsingEF(name); PrepareConnectionForEF(); - var content = Serialize(contentObj); + var content = Serialize(contentObj, callbackName); - return PublishWithTransAsync(name, content, DbConnection, DbTranasaction); + return PublishWithTransAsync(name, content); } - public void Publish(string name, T contentObj, IDbConnection dbConnection, IDbTransaction dbTransaction = null) + public void Publish(string name, T contentObj, IDbConnection dbConnection, + string callbackName = null, IDbTransaction dbTransaction = null) { CheckIsAdoNet(name); - PrepareConnectionForAdo(dbConnection, ref dbTransaction); + PrepareConnectionForAdo(dbConnection, dbTransaction); - var content = Serialize(contentObj); + var content = Serialize(contentObj, callbackName); - PublishWithTrans(name, content, dbConnection, dbTransaction); + PublishWithTrans(name, content); } - public Task PublishAsync(string name, T contentObj, IDbConnection dbConnection, IDbTransaction dbTransaction = null) + public Task PublishAsync(string name, T contentObj, IDbConnection dbConnection, + string callbackName = null, IDbTransaction dbTransaction = null) { CheckIsAdoNet(name); - PrepareConnectionForAdo(dbConnection, ref dbTransaction); + PrepareConnectionForAdo(dbConnection, dbTransaction); - var content = Serialize(contentObj); + var content = Serialize(contentObj, callbackName); - return PublishWithTransAsync(name, content, dbConnection, dbTransaction); + return PublishWithTransAsync(name, content); } protected abstract void PrepareConnectionForEF(); @@ -64,35 +66,29 @@ namespace DotNetCore.CAP.Abstractions #region private methods - private string Serialize(T obj) + private string Serialize(T obj, string callbackName = null) { - string content = string.Empty; - if (Helper.IsComplexType(typeof(T))) + var message = new Message(obj) { - content = Helper.ToJson(obj); - } - else - { - content = obj.ToString(); - } - return content; + CallbackName = callbackName + }; + + return Helper.ToJson(message); } - private void PrepareConnectionForAdo(IDbConnection dbConnection, ref IDbTransaction dbTransaction) + private void PrepareConnectionForAdo(IDbConnection dbConnection, IDbTransaction dbTransaction) { - if (dbConnection == null) - throw new ArgumentNullException(nameof(dbConnection)); - - if (dbConnection.State != ConnectionState.Open) + DbConnection = dbConnection ?? throw new ArgumentNullException(nameof(dbConnection)); + if (DbConnection.State != ConnectionState.Open) { IsCapOpenedConn = true; - dbConnection.Open(); + DbConnection.Open(); } - - if (dbTransaction == null) + DbTranasaction = dbTransaction; + if (DbTranasaction == null) { IsCapOpenedTrans = true; - dbTransaction = dbConnection.BeginTransaction(IsolationLevel.ReadCommitted); + DbTranasaction = dbConnection.BeginTransaction(IsolationLevel.ReadCommitted); } } @@ -111,7 +107,7 @@ namespace DotNetCore.CAP.Abstractions throw new InvalidOperationException("If you are using the EntityFramework, you do not need to use this overloaded."); } - private async Task PublishWithTransAsync(string name, string content, IDbConnection dbConnection, IDbTransaction dbTransaction) + private async Task PublishWithTransAsync(string name, string content) { var message = new CapPublishedMessage { @@ -120,23 +116,14 @@ namespace DotNetCore.CAP.Abstractions StatusName = StatusName.Scheduled }; - await ExecuteAsync(dbConnection, dbTransaction, message); - - if (IsCapOpenedTrans) - { - dbTransaction.Commit(); - dbTransaction.Dispose(); - } + await ExecuteAsync(DbConnection, DbTranasaction, message); - if (IsCapOpenedConn) - { - dbConnection.Dispose(); - } + ClosedCap(); PublishQueuer.PulseEvent.Set(); } - private void PublishWithTrans(string name, string content, IDbConnection dbConnection, IDbTransaction dbTransaction) + private void PublishWithTrans(string name, string content) { var message = new CapPublishedMessage { @@ -145,19 +132,30 @@ namespace DotNetCore.CAP.Abstractions StatusName = StatusName.Scheduled }; - Execute(dbConnection, dbTransaction, message); + Execute(DbConnection, DbTranasaction, message); + ClosedCap(); + + PublishQueuer.PulseEvent.Set(); + } + + private void ClosedCap() + { if (IsCapOpenedTrans) { - dbTransaction.Commit(); - dbTransaction.Dispose(); + DbTranasaction.Commit(); + DbTranasaction.Dispose(); } if (IsCapOpenedConn) { - dbConnection.Dispose(); + DbConnection.Dispose(); } + } - PublishQueuer.PulseEvent.Set(); + public void Dispose() + { + DbTranasaction?.Dispose(); + DbConnection?.Dispose(); } #endregion private methods diff --git a/src/DotNetCore.CAP/ICallbackPublisher.cs b/src/DotNetCore.CAP/ICallbackPublisher.cs new file mode 100644 index 0000000..d0a0456 --- /dev/null +++ b/src/DotNetCore.CAP/ICallbackPublisher.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace DotNetCore.CAP +{ + public interface ICallbackPublisher + { + Task PublishAsync(string name, object obj); + } +} diff --git a/src/DotNetCore.CAP/ICapPublisher.cs b/src/DotNetCore.CAP/ICapPublisher.cs index a3a7210..fc05bed 100644 --- a/src/DotNetCore.CAP/ICapPublisher.cs +++ b/src/DotNetCore.CAP/ICapPublisher.cs @@ -18,7 +18,8 @@ namespace DotNetCore.CAP /// The type of conetent object. /// the topic name or exchange router key. /// message body content, that will be serialized of json. - Task PublishAsync(string name, T contentObj); + /// callback subscriber name + Task PublishAsync(string name, T contentObj, string callbackName = null); /// /// (EntityFramework) Publish a object message. @@ -30,24 +31,27 @@ namespace DotNetCore.CAP /// The type of conetent object. /// the topic name or exchange router key. /// message body content, that will be serialized of json. - void Publish(string name, T contentObj); + /// callback subscriber name + void Publish(string name, T contentObj, string callbackName = null); /// /// (ado.net) Asynchronous publish a object message. /// /// the topic name or exchange router key. /// message body content, that will be serialized of json. + /// callback subscriber name /// the connection of /// the transaction of - Task PublishAsync(string name, T contentObj, IDbConnection dbConnection, IDbTransaction dbTransaction = null); + Task PublishAsync(string name, T contentObj, IDbConnection dbConnection, string callbackName = null, IDbTransaction dbTransaction = null); /// /// (ado.net) Publish a object message. /// /// the topic name or exchange router key. /// message body content, that will be serialized of json. + /// callback subscriber name /// the connection of /// the transaction of - void Publish(string name, T contentObj, IDbConnection dbConnection, IDbTransaction dbTransaction = null); + void Publish(string name, T contentObj, IDbConnection dbConnection, string callbackName = null, IDbTransaction dbTransaction = null); } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/Internal/IConsumerInvoker.Default.cs b/src/DotNetCore.CAP/Internal/IConsumerInvoker.Default.cs index 3009f73..d164a0f 100644 --- a/src/DotNetCore.CAP/Internal/IConsumerInvoker.Default.cs +++ b/src/DotNetCore.CAP/Internal/IConsumerInvoker.Default.cs @@ -1,6 +1,8 @@ using System; using System.Threading.Tasks; using DotNetCore.CAP.Abstractions; +using DotNetCore.CAP.Infrastructure; +using DotNetCore.CAP.Models; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -37,31 +39,47 @@ namespace DotNetCore.CAP.Internal var obj = ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, _consumerContext.ConsumerDescriptor.ImplTypeInfo.AsType()); - var value = _consumerContext.DeliverMessage.Content; + var jsonConent = _consumerContext.DeliverMessage.Content; + + var message = Helper.FromJson(jsonConent); + + object returnObj = null; if (_executor.MethodParameters.Length > 0) { var firstParameter = _executor.MethodParameters[0]; try { var binder = _modelBinderFactory.CreateBinder(firstParameter); - var result = await binder.BindModelAsync(value); + var result = await binder.BindModelAsync(message.Content.ToString()); if (result.IsSuccess) { - _executor.Execute(obj, result.Model); + returnObj = _executor.Execute(obj, result.Model); } else { - _logger.LogWarning($"Parameters:{firstParameter.Name} bind failed! the content is:" + value); + _logger.LogWarning($"Parameters:{firstParameter.Name} bind failed! the content is:" + jsonConent); } } catch (FormatException ex) { - _logger.ModelBinderFormattingException(_executor.MethodInfo?.Name, firstParameter.Name, value, ex); + _logger.ModelBinderFormattingException(_executor.MethodInfo?.Name, firstParameter.Name, jsonConent, ex); } } else { - _executor.Execute(obj); + returnObj = _executor.Execute(obj); + } + + //TODO :refactor + if (returnObj != null && !string.IsNullOrEmpty(message.CallbackName)) + { + var publisher = _serviceProvider.GetRequiredService(); + var callbackMessage = new Message(returnObj) + { + Id = message.Id, + Timestamp = DateTime.Now + }; + await publisher.PublishAsync(message.CallbackName, callbackMessage); } } } diff --git a/src/DotNetCore.CAP/Internal/IModelBinder.ComplexType.cs b/src/DotNetCore.CAP/Internal/IModelBinder.ComplexType.cs index c25f7c6..f4362f2 100644 --- a/src/DotNetCore.CAP/Internal/IModelBinder.ComplexType.cs +++ b/src/DotNetCore.CAP/Internal/IModelBinder.ComplexType.cs @@ -3,6 +3,7 @@ using System.Reflection; using System.Threading.Tasks; using DotNetCore.CAP.Abstractions.ModelBinding; using DotNetCore.CAP.Infrastructure; +using DotNetCore.CAP.Models; namespace DotNetCore.CAP.Internal { @@ -20,7 +21,11 @@ namespace DotNetCore.CAP.Internal try { var type = _parameterInfo.ParameterType; - var value = Helper.FromJson(content, type); + + var message = Helper.FromJson(content); + + var value = Helper.FromJson(message.Content.ToString(), type); + return Task.FromResult(ModelBindingResult.Success(value)); } catch (Exception) diff --git a/src/DotNetCore.CAP/Models/Message.cs b/src/DotNetCore.CAP/Models/Message.cs new file mode 100644 index 0000000..d739886 --- /dev/null +++ b/src/DotNetCore.CAP/Models/Message.cs @@ -0,0 +1,26 @@ +using System; + +namespace DotNetCore.CAP.Models +{ + public class Message + { + public string Id { get; set; } + + public DateTime Timestamp { get; set; } + + public object Content { get; set; } + + public string CallbackName { get; set; } + + public Message() + { + Id = ObjectId.GenerateNewStringId(); + Timestamp = DateTime.Now; + } + + public Message(object content) : this() + { + Content = content; + } + } +} From a4ec4773faa1ccbd89101777db91213b4267ac6b Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Tue, 15 Aug 2017 18:33:59 +0800 Subject: [PATCH 22/68] update unit tests. --- test/DotNetCore.CAP.Test/CAP.BuilderTest.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/DotNetCore.CAP.Test/CAP.BuilderTest.cs b/test/DotNetCore.CAP.Test/CAP.BuilderTest.cs index 0bd3c8f..69968de 100644 --- a/test/DotNetCore.CAP.Test/CAP.BuilderTest.cs +++ b/test/DotNetCore.CAP.Test/CAP.BuilderTest.cs @@ -61,47 +61,47 @@ namespace DotNetCore.CAP.Test private class MyProducerService : ICapPublisher { - public void Publish(string name, string content) + public void Publish(string name, T contentObj, string callbackName = null) { throw new NotImplementedException(); } - public void Publish(string name, T contentObj) + public void Publish(string name, T contentObj, IDbConnection dbConnection, string callbackName = null, IDbTransaction dbTransaction = null) { throw new NotImplementedException(); } - public void Publish(string name, string content, IDbConnection dbConnection, IDbTransaction dbTransaction = null) + public Task PublishAsync(string topic, string content) { throw new NotImplementedException(); } - public void Publish(string name, T contentObj, IDbConnection dbConnection, IDbTransaction dbTransaction = null) + public Task PublishAsync(string topic, T contentObj) { throw new NotImplementedException(); } - public Task PublishAsync(string topic, string content) + public Task PublishAsync(string topic, string content, IDbConnection dbConnection) { throw new NotImplementedException(); } - public Task PublishAsync(string topic, T contentObj) + public Task PublishAsync(string topic, string content, IDbConnection dbConnection, IDbTransaction dbTransaction) { throw new NotImplementedException(); } - public Task PublishAsync(string topic, string content, IDbConnection dbConnection) + public Task PublishAsync(string name, T contentObj, IDbConnection dbConnection, IDbTransaction dbTransaction = null) { throw new NotImplementedException(); } - public Task PublishAsync(string topic, string content, IDbConnection dbConnection, IDbTransaction dbTransaction) + public Task PublishAsync(string name, T contentObj, string callbackName = null) { throw new NotImplementedException(); } - public Task PublishAsync(string name, T contentObj, IDbConnection dbConnection, IDbTransaction dbTransaction = null) + public Task PublishAsync(string name, T contentObj, IDbConnection dbConnection, string callbackName = null, IDbTransaction dbTransaction = null) { throw new NotImplementedException(); } From bb124384863ed059eb64719fbc167df6b3487a75 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 15 Aug 2017 20:50:23 +0800 Subject: [PATCH 23/68] remove unused file. --- test/Shared/MessageManagerTestBase.cs | 113 -------------------------- test/Shared/TestConsistencyMessage.cs | 6 -- test/Shared/TestLogger.cs | 40 --------- 3 files changed, 159 deletions(-) delete mode 100644 test/Shared/MessageManagerTestBase.cs delete mode 100644 test/Shared/TestConsistencyMessage.cs delete mode 100644 test/Shared/TestLogger.cs diff --git a/test/Shared/MessageManagerTestBase.cs b/test/Shared/MessageManagerTestBase.cs deleted file mode 100644 index 21f641a..0000000 --- a/test/Shared/MessageManagerTestBase.cs +++ /dev/null @@ -1,113 +0,0 @@ -//using System; -//using System.Threading.Tasks; -//using DotNetCore.CAP.Infrastructure; -//using DotNetCore.CAP.Models; -//using Microsoft.AspNetCore.Http; -//using Microsoft.Extensions.DependencyInjection; -//using Microsoft.Extensions.Logging; -//using Xunit; - -//namespace DotNetCore.CAP.Test -//{ -// public abstract class MessageManagerTestBase -// { -// private const string NullValue = "(null)"; - -// protected virtual bool ShouldSkipDbTests() -// { -// return false; -// } - -// protected virtual void SetupMessageServices(IServiceCollection services, object context = null) -// { -// services.AddSingleton(); -// services.AddCap(); -// AddMessageStore(services, context); - -// services.AddSingleton>(new TestLogger()); -// } - -// protected virtual ICapMessageStore CreateManager(object context = null, IServiceCollection services = null, -// Action configureServices = null) -// { -// if (services == null) -// { -// services = new ServiceCollection(); -// } -// if (context == null) -// { -// context = CreateTestContext(); -// } -// SetupMessageServices(services, context); - -// configureServices?.Invoke(services); - -// return services.BuildServiceProvider().GetService(); -// } - -// protected abstract object CreateTestContext(); - -// protected abstract CapSentMessage CreateTestSentMessage(string content = ""); -// protected abstract CapReceivedMessage CreateTestReceivedMessage(string content = ""); - -// protected abstract void AddMessageStore(IServiceCollection services, object context = null); - -// [Fact] -// public async Task CanDeleteSentMessage() -// { -// if (ShouldSkipDbTests()) -// { -// return; -// } - -// var manager = CreateManager(); -// var message = CreateTestSentMessage(); -// var operateResult = await manager.StoreSentMessageAsync(message); -// Assert.NotNull(operateResult); -// Assert.True(operateResult.Succeeded); - -// // operateResult = await manager.RemoveSentMessageAsync(message); -// // Assert.NotNull(operateResult); -// // Assert.True(operateResult.Succeeded); -// } - -// //[Fact] -// //public async Task CanUpdateReceivedMessage() -// //{ -// // if (ShouldSkipDbTests()) -// // { -// // return; -// // } - -// // var manager = CreateManager(); -// // var message = CreateTestReceivedMessage(); -// // // var operateResult = await manager.StoreReceivedMessageAsync(message); -// // // Assert.NotNull(operateResult); -// // // Assert.True(operateResult.Succeeded); - -// // // message.StatusName = StatusName.Processing; -// // // operateResult = await manager.UpdateReceivedMessageAsync(message); -// // // Assert.NotNull(operateResult); -// // // Assert.True(operateResult.Succeeded); -// //} - -// [Fact] -// public async Task CanGetNextSendMessage() -// { -// if (ShouldSkipDbTests()) -// { -// return; -// } -// var manager = CreateManager(); -// var message = CreateTestSentMessage(); - -// var operateResult = await manager.StoreSentMessageAsync(message); -// Assert.NotNull(operateResult); -// Assert.True(operateResult.Succeeded); - -// // var storeMessage = await manager.GetNextSentMessageToBeEnqueuedAsync(); - -// // Assert.Equal(message, storeMessage); -// } -// } -//} \ No newline at end of file diff --git a/test/Shared/TestConsistencyMessage.cs b/test/Shared/TestConsistencyMessage.cs deleted file mode 100644 index 30b92e7..0000000 --- a/test/Shared/TestConsistencyMessage.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System; -using DotNetCore.CAP.Infrastructure; - -namespace DotNetCore.CAP.Test -{ -} \ No newline at end of file diff --git a/test/Shared/TestLogger.cs b/test/Shared/TestLogger.cs deleted file mode 100644 index e41c9fa..0000000 --- a/test/Shared/TestLogger.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.Logging; - -namespace DotNetCore.CAP.Test -{ - public interface ITestLogger - { - IList LogMessages { get; } - } - - public class TestLogger : ILogger, ITestLogger - { - public IList LogMessages { get; } = new List(); - - public IDisposable BeginScope(TState state) - { - LogMessages.Add(state?.ToString()); - return null; - } - - public bool IsEnabled(LogLevel logLevel) - { - return true; - } - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, - Func formatter) - { - if (formatter == null) - { - LogMessages.Add(state.ToString()); - } - else - { - LogMessages.Add(formatter(state, exception)); - } - } - } -} \ No newline at end of file From 81171e00e02f1df42728444e5f75789ded728d46 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 15 Aug 2017 22:21:37 +0800 Subject: [PATCH 24/68] refactor --- src/DotNetCore.CAP/ICallbackPublisher.cs | 8 +- .../Internal/IConsumerInvoker.Default.cs | 109 ++++++++++++------ 2 files changed, 76 insertions(+), 41 deletions(-) diff --git a/src/DotNetCore.CAP/ICallbackPublisher.cs b/src/DotNetCore.CAP/ICallbackPublisher.cs index d0a0456..1743b52 100644 --- a/src/DotNetCore.CAP/ICallbackPublisher.cs +++ b/src/DotNetCore.CAP/ICallbackPublisher.cs @@ -1,12 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; +using System.Threading.Tasks; +using DotNetCore.CAP.Models; namespace DotNetCore.CAP { public interface ICallbackPublisher { - Task PublishAsync(string name, object obj); + Task PublishAsync(CapPublishedMessage obj); } } diff --git a/src/DotNetCore.CAP/Internal/IConsumerInvoker.Default.cs b/src/DotNetCore.CAP/Internal/IConsumerInvoker.Default.cs index d164a0f..9acca77 100644 --- a/src/DotNetCore.CAP/Internal/IConsumerInvoker.Default.cs +++ b/src/DotNetCore.CAP/Internal/IConsumerInvoker.Default.cs @@ -23,64 +23,101 @@ namespace DotNetCore.CAP.Internal { _modelBinderFactory = modelBinderFactory; _serviceProvider = serviceProvider; - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _logger = logger; + _consumerContext = consumerContext; - _consumerContext = consumerContext ?? throw new ArgumentNullException(nameof(consumerContext)); _executor = ObjectMethodExecutor.Create(_consumerContext.ConsumerDescriptor.MethodInfo, _consumerContext.ConsumerDescriptor.ImplTypeInfo); } public async Task InvokeAsync() { - 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, - _consumerContext.ConsumerDescriptor.ImplTypeInfo.AsType()); + var obj = ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, + _consumerContext.ConsumerDescriptor.ImplTypeInfo.AsType()); - var jsonConent = _consumerContext.DeliverMessage.Content; + var jsonConent = _consumerContext.DeliverMessage.Content; + var message = Helper.FromJson(jsonConent); - var message = Helper.FromJson(jsonConent); + object result = null; + if (_executor.MethodParameters.Length > 0) + { + result = await ExecuteWithParameterAsync(obj, message.Content.ToString()); + } + else + { + result = await ExecuteAsync(obj); + } - object returnObj = null; - if (_executor.MethodParameters.Length > 0) + if (!string.IsNullOrEmpty(message.CallbackName)) + { + await SentCallbackMessage(message.Id, message.CallbackName, result); + } + } + + private async Task ExecuteAsync(object @class) + { + if (_executor.IsMethodAsync) + { + return await _executor.ExecuteAsync(@class); + } + else + { + return _executor.Execute(@class); + } + } + + private async Task ExecuteWithParameterAsync(object @class, string parameterString) + { + var firstParameter = _executor.MethodParameters[0]; + try + { + var binder = _modelBinderFactory.CreateBinder(firstParameter); + var bindResult = await binder.BindModelAsync(parameterString); + if (bindResult.IsSuccess) { - var firstParameter = _executor.MethodParameters[0]; - try + if (_executor.IsMethodAsync) { - var binder = _modelBinderFactory.CreateBinder(firstParameter); - var result = await binder.BindModelAsync(message.Content.ToString()); - if (result.IsSuccess) - { - returnObj = _executor.Execute(obj, result.Model); - } - else - { - _logger.LogWarning($"Parameters:{firstParameter.Name} bind failed! the content is:" + jsonConent); - } + return await _executor.ExecuteAsync(@class, bindResult.Model); } - catch (FormatException ex) + else { - _logger.ModelBinderFormattingException(_executor.MethodInfo?.Name, firstParameter.Name, jsonConent, ex); + return _executor.Execute(@class, bindResult.Model); } } else { - returnObj = _executor.Execute(obj); + throw new MethodBindException($"Parameters:{firstParameter.Name} bind failed! ParameterString is: {parameterString} "); } + } + catch (FormatException ex) + { + _logger.ModelBinderFormattingException(_executor.MethodInfo?.Name, firstParameter.Name, parameterString, ex); + return null; + } + } - //TODO :refactor - if (returnObj != null && !string.IsNullOrEmpty(message.CallbackName)) + private async Task SentCallbackMessage(string messageId, string topicName, object bodyObj) + { + var callbackMessage = new Message + { + Id = messageId, + Content = bodyObj + }; + + using (var scope = _serviceProvider.CreateScope()) + { + var provider = scope.ServiceProvider; + var publisher = provider.GetRequiredService(); + + var publishedMessage = new CapPublishedMessage { - var publisher = _serviceProvider.GetRequiredService(); - var callbackMessage = new Message(returnObj) - { - Id = message.Id, - Timestamp = DateTime.Now - }; - await publisher.PublishAsync(message.CallbackName, callbackMessage); - } + Name = topicName, + Content = Helper.ToJson(callbackMessage), + StatusName = StatusName.Scheduled + }; + await publisher.PublishAsync(publishedMessage); } } } From 123a3bbe3c65c6a7d4e53ea1ad0117ba97d7bb78 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 15 Aug 2017 22:21:46 +0800 Subject: [PATCH 25/68] add exception class. --- src/DotNetCore.CAP/Internal/MethodBindException.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/DotNetCore.CAP/Internal/MethodBindException.cs diff --git a/src/DotNetCore.CAP/Internal/MethodBindException.cs b/src/DotNetCore.CAP/Internal/MethodBindException.cs new file mode 100644 index 0000000..de9fe86 --- /dev/null +++ b/src/DotNetCore.CAP/Internal/MethodBindException.cs @@ -0,0 +1,12 @@ +using System; + +namespace DotNetCore.CAP.Internal +{ + [Serializable] + public class MethodBindException : Exception + { + public MethodBindException() { } + public MethodBindException(string message) : base(message) { } + public MethodBindException(string message, Exception inner) : base(message, inner) { } + } +} From 3e3d2723faa242c0d86e785e0582951d751570f9 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 15 Aug 2017 22:22:01 +0800 Subject: [PATCH 26/68] add ICallbackPublisher to DI. --- src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs b/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs index 5795b3b..3053d76 100644 --- a/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs @@ -21,6 +21,7 @@ namespace DotNetCore.CAP services.AddSingleton(); services.AddScoped(); services.AddScoped(); + services.AddTransient(); services.AddTransient(); var sqlServerOptions = new SqlServerOptions(); From dc36c7e4de4127c8072671ed3f2cb37e3be88d29 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 15 Aug 2017 22:22:13 +0800 Subject: [PATCH 27/68] refactor --- src/DotNetCore.CAP.SqlServer/CapPublisher.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/DotNetCore.CAP.SqlServer/CapPublisher.cs b/src/DotNetCore.CAP.SqlServer/CapPublisher.cs index 7a0da8f..60bafa4 100644 --- a/src/DotNetCore.CAP.SqlServer/CapPublisher.cs +++ b/src/DotNetCore.CAP.SqlServer/CapPublisher.cs @@ -58,14 +58,15 @@ namespace DotNetCore.CAP.SqlServer protected override async Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message) { await dbConnection.ExecuteAsync(PrepareSql(), message, dbTransaction); + _logger.LogInformation("Published Message has been persisted in the database. name:" + message.ToString()); } - public Task PublishAsync(string name, object contentObj) + public async Task PublishAsync(CapPublishedMessage message) { using (var conn = new SqlConnection(_options.ConnectionString)) { - return conn.ExecuteAsync(PrepareSql(), contentObj); + await conn.ExecuteAsync(PrepareSql(), message); } } From 1ebc5644781aa60b26f144316a96eac5ffb08f31 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 15 Aug 2017 22:25:08 +0800 Subject: [PATCH 28/68] update TargetFrameworks with single .net standard 2.0 TargetFramework. --- src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj | 2 +- src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj | 2 +- src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj | 2 +- src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj | 2 +- src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj | 2 +- src/DotNetCore.CAP/DotNetCore.CAP.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj index f27aee1..7287e2d 100644 --- a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj +++ b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj @@ -3,7 +3,7 @@ - netstandard2.0; + netstandard2.0 DotNetCore.CAP.Kafka $(PackageTags);Kafka diff --git a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj index ce900b2..26a6926 100644 --- a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj +++ b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj @@ -3,7 +3,7 @@ - netstandard2.0; + netstandard2.0 DotNetCore.CAP.MySql $(PackageTags);MySQL diff --git a/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj b/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj index f34d60b..d3884ce 100644 --- a/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj +++ b/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj @@ -3,7 +3,7 @@ - netstandard2.0; + netstandard2.0 DotNetCore.CAP.PostgreSql $(PackageTags);PostgreSQL diff --git a/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj b/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj index c92e44b..55a0deb 100644 --- a/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj +++ b/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj @@ -3,7 +3,7 @@ - netstandard2.0; + netstandard2.0 DotNetCore.CAP.RabbitMQ $(PackageTags);RabbitMQ diff --git a/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj b/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj index 84d6acc..f2c293b 100644 --- a/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj +++ b/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj @@ -3,7 +3,7 @@ - netstandard2.0; + netstandard2.0 DotNetCore.CAP.SqlServer $(PackageTags);SQL Server diff --git a/src/DotNetCore.CAP/DotNetCore.CAP.csproj b/src/DotNetCore.CAP/DotNetCore.CAP.csproj index ab566f9..e0e473f 100644 --- a/src/DotNetCore.CAP/DotNetCore.CAP.csproj +++ b/src/DotNetCore.CAP/DotNetCore.CAP.csproj @@ -3,7 +3,7 @@ - netstandard2.0; + netstandard2.0 DotNetCore.CAP $(PackageTags); From 3fd7a834ff42bebffc54c64b7e6c66c87a2bd514 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 15 Aug 2017 22:25:34 +0800 Subject: [PATCH 29/68] update samples. --- .../Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj b/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj index 9fa7e09..2f9fdaf 100644 --- a/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj +++ b/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj @@ -10,6 +10,7 @@ + From 42c856fc5d5c1527bda890b8c1c9f4338dfe1c38 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Wed, 16 Aug 2017 17:24:30 +0800 Subject: [PATCH 30/68] modify CapPublisher to implement ICallbackPublisher --- src/DotNetCore.CAP.MySql/CapPublisher.cs | 12 +++++++++--- src/DotNetCore.CAP.PostgreSql/CapPublisher.cs | 17 +++++++++++++++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/DotNetCore.CAP.MySql/CapPublisher.cs b/src/DotNetCore.CAP.MySql/CapPublisher.cs index 59801df..aac528f 100644 --- a/src/DotNetCore.CAP.MySql/CapPublisher.cs +++ b/src/DotNetCore.CAP.MySql/CapPublisher.cs @@ -7,10 +7,11 @@ using DotNetCore.CAP.Models; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Logging; +using MySql.Data.MySqlClient; namespace DotNetCore.CAP.MySql { - public class CapPublisher : CapPublisherBase + public class CapPublisher : CapPublisherBase, ICallbackPublisher { private readonly ILogger _logger; private readonly MySqlOptions _options; @@ -61,6 +62,13 @@ namespace DotNetCore.CAP.MySql _logger.LogInformation("Published Message has been persisted in the database. name:" + message.ToString()); } + public async Task PublishAsync(CapPublishedMessage message) + { + using (var conn = new MySqlConnection(_options.ConnectionString)) + { + await conn.ExecuteAsync(PrepareSql(), message); + } + } #region private methods private string PrepareSql() @@ -68,8 +76,6 @@ namespace DotNetCore.CAP.MySql 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 diff --git a/src/DotNetCore.CAP.PostgreSql/CapPublisher.cs b/src/DotNetCore.CAP.PostgreSql/CapPublisher.cs index 4e3c9cd..5a81931 100644 --- a/src/DotNetCore.CAP.PostgreSql/CapPublisher.cs +++ b/src/DotNetCore.CAP.PostgreSql/CapPublisher.cs @@ -7,10 +7,11 @@ using DotNetCore.CAP.Abstractions; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Logging; +using Npgsql; namespace DotNetCore.CAP.PostgreSql { - public class CapPublisher : CapPublisherBase + public class CapPublisher : CapPublisherBase, ICallbackPublisher { private readonly ILogger _logger; private readonly PostgreSqlOptions _options; @@ -61,9 +62,21 @@ namespace DotNetCore.CAP.PostgreSql _logger.LogInformation("Published Message has been persisted in the database. name:" + message.ToString()); } + public async Task PublishAsync(CapPublishedMessage message) + { + using (var conn = new NpgsqlConnection(_options.ConnectionString)) + { + await conn.ExecuteAsync(PrepareSql(), message); + } + } + + #region private methods + private string PrepareSql() { return $"INSERT INTO \"{_options.Schema}\".\"published\" (\"Name\",\"Content\",\"Retries\",\"Added\",\"ExpiresAt\",\"StatusName\")VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName)"; - } + } + + #endregion } } \ No newline at end of file From 7a7370b5abb1bcd5f041dabbbcbb32e37b8b3f43 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Wed, 16 Aug 2017 18:22:53 +0800 Subject: [PATCH 31/68] add feature of #24 --- .../PublishQueueExecutor.cs | 10 ++++--- .../PublishQueueExecutor.cs | 6 +++-- src/DotNetCore.CAP/CAP.Options.cs | 27 ++++++++++++++++--- .../IQueueExecutor.Publish.Base.cs | 8 ++++-- src/DotNetCore.CAP/IQueueExecutor.Subscibe.cs | 6 +++-- .../Processor/States/IState.Succeeded.cs | 12 ++++++++- 6 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/DotNetCore.CAP.Kafka/PublishQueueExecutor.cs b/src/DotNetCore.CAP.Kafka/PublishQueueExecutor.cs index a3f4c80..8b455d7 100644 --- a/src/DotNetCore.CAP.Kafka/PublishQueueExecutor.cs +++ b/src/DotNetCore.CAP.Kafka/PublishQueueExecutor.cs @@ -12,13 +12,15 @@ namespace DotNetCore.CAP.Kafka private readonly ILogger _logger; private readonly KafkaOptions _kafkaOptions; - public PublishQueueExecutor(IStateChanger stateChanger, - KafkaOptions options, + public PublishQueueExecutor( + CapOptions options, + IStateChanger stateChanger, + KafkaOptions kafkaOptions, ILogger logger) - : base(stateChanger, logger) + : base(options, stateChanger, logger) { _logger = logger; - _kafkaOptions = options; + _kafkaOptions = kafkaOptions; } public override Task PublishAsync(string keyName, string content) diff --git a/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs b/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs index 794b636..791985d 100644 --- a/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs +++ b/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs @@ -13,11 +13,13 @@ namespace DotNetCore.CAP.RabbitMQ private readonly IConnection _connection; private readonly RabbitMQOptions _rabbitMQOptions; - public PublishQueueExecutor(IStateChanger stateChanger, + public PublishQueueExecutor( + CapOptions options, + IStateChanger stateChanger, IConnection connection, RabbitMQOptions rabbitMQOptions, ILogger logger) - : base(stateChanger, logger) + : base(options, stateChanger, logger) { _logger = logger; _connection = connection; diff --git a/src/DotNetCore.CAP/CAP.Options.cs b/src/DotNetCore.CAP/CAP.Options.cs index 0d17e67..3196c1e 100644 --- a/src/DotNetCore.CAP/CAP.Options.cs +++ b/src/DotNetCore.CAP/CAP.Options.cs @@ -20,27 +20,48 @@ namespace DotNetCore.CAP /// public const int DefaultQueueProcessorCount = 2; + /// + /// Default successed message expriation timespan, in seconds. + /// + public const int DefaultSuccessMessageExpirationTimeSpan = 3600; + + /// + /// Failed message retry waiting interval. + /// + public const int DefaultFailedMessageWaitingInterval = 180; + public CapOptions() { PollingDelay = DefaultPollingDelay; QueueProcessorCount = DefaultQueueProcessorCount; + SuccessedMessageExpiredTimeSpan = DefaultSuccessMessageExpirationTimeSpan; + FailedMessageWaitingInterval = DefaultFailedMessageWaitingInterval; Extensions = new List(); } /// - /// Productor job polling delay time. Default is 15 sec. + /// Productor job polling delay time. + /// Default is 15 sec. /// public int PollingDelay { get; set; } /// /// Gets or sets the messages queue (Cap.Queue table) processor count. + /// Default is 2 processor. /// public int QueueProcessorCount { get; set; } /// - /// Failed messages polling delay time. Default is 3 min. + /// Sent or received successed message due timespan, then the message will be deleted at due time. + /// Dafault is 3600 seconds. + /// + public int SuccessedMessageExpiredTimeSpan { get; set; } + + /// + /// Failed messages polling delay time. + /// Default is 180 seconds. /// - public int FailedMessageWaitingInterval { get; set; } = (int)TimeSpan.FromMinutes(3).TotalSeconds; + public int FailedMessageWaitingInterval { get; set; } /// /// We’ll invoke this call-back with message type,name,content when requeue failed message. diff --git a/src/DotNetCore.CAP/IQueueExecutor.Publish.Base.cs b/src/DotNetCore.CAP/IQueueExecutor.Publish.Base.cs index a032af6..9d5baab 100644 --- a/src/DotNetCore.CAP/IQueueExecutor.Publish.Base.cs +++ b/src/DotNetCore.CAP/IQueueExecutor.Publish.Base.cs @@ -10,12 +10,16 @@ namespace DotNetCore.CAP { public abstract class BasePublishQueueExecutor : IQueueExecutor { + private readonly CapOptions _options; private readonly IStateChanger _stateChanger; private readonly ILogger _logger; - protected BasePublishQueueExecutor(IStateChanger stateChanger, + protected BasePublishQueueExecutor( + CapOptions options, + IStateChanger stateChanger, ILogger logger) { + _options = options; _stateChanger = stateChanger; _logger = logger; } @@ -54,7 +58,7 @@ namespace DotNetCore.CAP } else { - newState = new SucceededState(); + newState = new SucceededState(_options.SuccessedMessageExpiredTimeSpan); } await _stateChanger.ChangeStateAsync(message, newState, connection); diff --git a/src/DotNetCore.CAP/IQueueExecutor.Subscibe.cs b/src/DotNetCore.CAP/IQueueExecutor.Subscibe.cs index e8fd93c..adcfd7a 100644 --- a/src/DotNetCore.CAP/IQueueExecutor.Subscibe.cs +++ b/src/DotNetCore.CAP/IQueueExecutor.Subscibe.cs @@ -15,16 +15,18 @@ namespace DotNetCore.CAP private readonly IConsumerInvokerFactory _consumerInvokerFactory; private readonly IStateChanger _stateChanger; private readonly ILogger _logger; - + private readonly CapOptions _options; private readonly MethodMatcherCache _selector; public SubscibeQueueExecutor( IStateChanger stateChanger, MethodMatcherCache selector, + CapOptions options, IConsumerInvokerFactory consumerInvokerFactory, ILogger logger) { _selector = selector; + _options = options; _consumerInvokerFactory = consumerInvokerFactory; _stateChanger = stateChanger; _logger = logger; @@ -62,7 +64,7 @@ namespace DotNetCore.CAP } else { - newState = new SucceededState(); + newState = new SucceededState(_options.SuccessedMessageExpiredTimeSpan); } await _stateChanger.ChangeStateAsync(message, newState, connection); diff --git a/src/DotNetCore.CAP/Processor/States/IState.Succeeded.cs b/src/DotNetCore.CAP/Processor/States/IState.Succeeded.cs index 294591c..0a3f240 100644 --- a/src/DotNetCore.CAP/Processor/States/IState.Succeeded.cs +++ b/src/DotNetCore.CAP/Processor/States/IState.Succeeded.cs @@ -7,10 +7,20 @@ namespace DotNetCore.CAP.Processor.States { public const string StateName = "Succeeded"; - public TimeSpan? ExpiresAfter => TimeSpan.FromHours(1); + public TimeSpan? ExpiresAfter { get; private set; } public string Name => StateName; + public SucceededState() + { + ExpiresAfter = TimeSpan.FromHours(1); + } + + public SucceededState(int ExpireAfterSeconds) + { + ExpiresAfter = TimeSpan.FromSeconds(ExpireAfterSeconds); + } + public void Apply(CapPublishedMessage message, IStorageTransaction transaction) { } From fe2dc1ac857df9400218edd10034e43c057cc1ef Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Wed, 16 Aug 2017 18:23:25 +0800 Subject: [PATCH 32/68] add ICallbackPublisher to DI --- src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs | 1 + .../CAP.PostgreSqlCapOptionsExtension.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs b/src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs index ce6fd34..dd18861 100644 --- a/src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs @@ -21,6 +21,7 @@ namespace DotNetCore.CAP services.AddSingleton(); services.AddScoped(); services.AddScoped(); + services.AddTransient(); services.AddTransient(); var mysqlOptions = new MySqlOptions(); diff --git a/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs b/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs index 9038dff..52de4b7 100644 --- a/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs @@ -21,6 +21,7 @@ namespace DotNetCore.CAP services.AddSingleton(); services.AddScoped(); services.AddScoped(); + services.AddTransient(); services.AddTransient(); var postgreSqlOptions = new PostgreSqlOptions(); From 78f837aab6458e1401fa759be3595685fe714fc8 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Wed, 16 Aug 2017 18:24:34 +0800 Subject: [PATCH 33/68] modify delete successed message processor wating interval to 5 min. --- src/DotNetCore.CAP.MySql/IAdditionalProcessor.Default.cs | 2 +- src/DotNetCore.CAP.PostgreSql/IAdditionalProcessor.Default.cs | 2 +- src/DotNetCore.CAP.SqlServer/IAdditionalProcessor.Default.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DotNetCore.CAP.MySql/IAdditionalProcessor.Default.cs b/src/DotNetCore.CAP.MySql/IAdditionalProcessor.Default.cs index b8e5922..3398dc0 100644 --- a/src/DotNetCore.CAP.MySql/IAdditionalProcessor.Default.cs +++ b/src/DotNetCore.CAP.MySql/IAdditionalProcessor.Default.cs @@ -15,7 +15,7 @@ namespace DotNetCore.CAP.MySql private const int MaxBatch = 1000; private readonly TimeSpan _delay = TimeSpan.FromSeconds(1); - private readonly TimeSpan _waitingInterval = TimeSpan.FromHours(2); + private readonly TimeSpan _waitingInterval = TimeSpan.FromMinutes(5); public DefaultAdditionalProcessor( IServiceProvider provider, diff --git a/src/DotNetCore.CAP.PostgreSql/IAdditionalProcessor.Default.cs b/src/DotNetCore.CAP.PostgreSql/IAdditionalProcessor.Default.cs index d832d0e..f544d2b 100644 --- a/src/DotNetCore.CAP.PostgreSql/IAdditionalProcessor.Default.cs +++ b/src/DotNetCore.CAP.PostgreSql/IAdditionalProcessor.Default.cs @@ -15,7 +15,7 @@ namespace DotNetCore.CAP.PostgreSql private const int MaxBatch = 1000; private readonly TimeSpan _delay = TimeSpan.FromSeconds(1); - private readonly TimeSpan _waitingInterval = TimeSpan.FromHours(2); + private readonly TimeSpan _waitingInterval = TimeSpan.FromMinutes(5); private static readonly string[] Tables = { diff --git a/src/DotNetCore.CAP.SqlServer/IAdditionalProcessor.Default.cs b/src/DotNetCore.CAP.SqlServer/IAdditionalProcessor.Default.cs index 65490ab..7c1bac1 100644 --- a/src/DotNetCore.CAP.SqlServer/IAdditionalProcessor.Default.cs +++ b/src/DotNetCore.CAP.SqlServer/IAdditionalProcessor.Default.cs @@ -15,7 +15,7 @@ namespace DotNetCore.CAP.SqlServer private const int MaxBatch = 1000; private readonly TimeSpan _delay = TimeSpan.FromSeconds(1); - private readonly TimeSpan _waitingInterval = TimeSpan.FromHours(2); + private readonly TimeSpan _waitingInterval = TimeSpan.FromMinutes(5); private static readonly string[] Tables = { From 75a38336f35951f5fd0993a65f64d27e0bbacf80 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Wed, 16 Aug 2017 18:27:37 +0800 Subject: [PATCH 34/68] modfiy os version to Visual Studio 2017 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 4073398..6a372fb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ version: '{build}' -os: Visual Studio 2017 Preview +os: Visual Studio 2017 environment: BUILDING_ON_PLATFORM: win BuildEnvironment: appveyor From 8c5da12d83346b9619864d3ded99abb8c285d8a6 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Thu, 17 Aug 2017 15:57:19 +0800 Subject: [PATCH 35/68] fixed consumer method injection context bug. (#34) --- .../Internal/ConsumerInvokerFactory.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/DotNetCore.CAP/Internal/ConsumerInvokerFactory.cs b/src/DotNetCore.CAP/Internal/ConsumerInvokerFactory.cs index 0921e1b..ea200d7 100644 --- a/src/DotNetCore.CAP/Internal/ConsumerInvokerFactory.cs +++ b/src/DotNetCore.CAP/Internal/ConsumerInvokerFactory.cs @@ -1,5 +1,6 @@ using System; using DotNetCore.CAP.Abstractions; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace DotNetCore.CAP.Internal @@ -22,12 +23,15 @@ namespace DotNetCore.CAP.Internal public IConsumerInvoker CreateInvoker(ConsumerContext consumerContext) { - var context = new ConsumerInvokerContext(consumerContext) + using(var scope = _serviceProvider.CreateScope()) { - Result = new DefaultConsumerInvoker(_logger, _serviceProvider, _modelBinderFactory, consumerContext) - }; + var context = new ConsumerInvokerContext(consumerContext) + { + Result = new DefaultConsumerInvoker(_logger, scope.ServiceProvider, _modelBinderFactory, consumerContext) + }; - return context.Result; + return context.Result; + } } } } \ No newline at end of file From 3237737d8240085914e0c64cd86724cd5fd72aee Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Thu, 17 Aug 2017 15:58:11 +0800 Subject: [PATCH 36/68] modify complex type parameter bind error. --- src/DotNetCore.CAP/Internal/IModelBinder.ComplexType.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/DotNetCore.CAP/Internal/IModelBinder.ComplexType.cs b/src/DotNetCore.CAP/Internal/IModelBinder.ComplexType.cs index f4362f2..6dbcb74 100644 --- a/src/DotNetCore.CAP/Internal/IModelBinder.ComplexType.cs +++ b/src/DotNetCore.CAP/Internal/IModelBinder.ComplexType.cs @@ -22,9 +22,7 @@ namespace DotNetCore.CAP.Internal { var type = _parameterInfo.ParameterType; - var message = Helper.FromJson(content); - - var value = Helper.FromJson(message.Content.ToString(), type); + var value = Helper.FromJson(content, type); return Task.FromResult(ModelBindingResult.Success(value)); } From 0e52aa1fb27fe09dcb3afb062e216b47e2d77bba Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Thu, 17 Aug 2017 15:59:38 +0800 Subject: [PATCH 37/68] modify cap subscribe method register to DI with transient. --- src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs b/src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs index 085205e..f4ca93f 100644 --- a/src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs +++ b/src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs @@ -78,7 +78,7 @@ namespace Microsoft.Extensions.DependencyInjection foreach (var service in consumerListenerServices) { - services.AddSingleton(service.Key, service.Value); + services.AddTransient(service.Key, service.Value); } var types = Assembly.GetEntryAssembly().ExportedTypes; @@ -86,7 +86,7 @@ namespace Microsoft.Extensions.DependencyInjection { if (Helper.IsController(type.GetTypeInfo())) { - services.AddSingleton(typeof(object), type); + services.AddTransient(typeof(object), type); } } } From 21bf869b1aee724eae4f02aab1eacb275fae678d Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Thu, 17 Aug 2017 16:20:42 +0800 Subject: [PATCH 38/68] no warn of NU1701 --- src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj index 7287e2d..96f0191 100644 --- a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj +++ b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj @@ -8,11 +8,11 @@ $(PackageTags);Kafka - - - - - + + NU1605 + NU1701 + + From e76a4299d193f160cd35f77dd67d0207b291a13b Mon Sep 17 00:00:00 2001 From: Savorboard Date: Thu, 17 Aug 2017 22:43:22 +0800 Subject: [PATCH 39/68] cleanup --- src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs | 2 +- .../KafkaConsumerClientFactory.cs | 5 +---- .../CAP.MySqlCapOptionsExtension.cs | 2 +- src/DotNetCore.CAP.MySql/CapPublisher.cs | 7 ++++--- .../CAP.PostgreSqlCapOptionsExtension.cs | 2 +- src/DotNetCore.CAP.PostgreSql/CapPublisher.cs | 10 +++++----- .../IAdditionalProcessor.Default.cs | 2 +- src/DotNetCore.CAP.SqlServer/CapPublisher.cs | 6 +++--- .../Abstractions/CapPublisherBase.cs | 2 +- src/DotNetCore.CAP/Infrastructure/ObjectId.cs | 8 +++++++- .../Internal/ConsumerInvokerFactory.cs | 2 +- .../Internal/IModelBinder.ComplexType.cs | 1 - .../Internal/MethodBindException.cs | 16 ++++++++++++---- src/DotNetCore.CAP/Models/Message.cs | 2 +- 14 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs b/src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs index e61ccee..0790458 100644 --- a/src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs +++ b/src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs @@ -42,7 +42,7 @@ namespace DotNetCore.CAP { throw new ArgumentNullException(nameof(Servers)); } - + MainConfig.Add("bootstrap.servers", Servers); MainConfig["queue.buffering.max.ms"] = "10"; diff --git a/src/DotNetCore.CAP.Kafka/KafkaConsumerClientFactory.cs b/src/DotNetCore.CAP.Kafka/KafkaConsumerClientFactory.cs index ea2ee67..8bda50a 100644 --- a/src/DotNetCore.CAP.Kafka/KafkaConsumerClientFactory.cs +++ b/src/DotNetCore.CAP.Kafka/KafkaConsumerClientFactory.cs @@ -1,7 +1,4 @@ -using System; -using Microsoft.Extensions.Options; - -namespace DotNetCore.CAP.Kafka +namespace DotNetCore.CAP.Kafka { internal sealed class KafkaConsumerClientFactory : IConsumerClientFactory { diff --git a/src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs b/src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs index dd18861..b9c1b0d 100644 --- a/src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs @@ -34,7 +34,7 @@ namespace DotNetCore.CAP var dbContext = (DbContext)x.GetService(mysqlOptions.DbContextType); mysqlOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString; return mysqlOptions; - }); + }); } else { diff --git a/src/DotNetCore.CAP.MySql/CapPublisher.cs b/src/DotNetCore.CAP.MySql/CapPublisher.cs index aac528f..0d6a1b3 100644 --- a/src/DotNetCore.CAP.MySql/CapPublisher.cs +++ b/src/DotNetCore.CAP.MySql/CapPublisher.cs @@ -37,7 +37,7 @@ namespace DotNetCore.CAP.MySql DbConnection = _dbContext.Database.GetDbConnection(); var dbContextTransaction = _dbContext.Database.CurrentTransaction; var dbTrans = dbContextTransaction?.GetDbTransaction(); - //DbTransaction is dispose in original + //DbTransaction is dispose in original if (dbTrans?.Connection == null) { IsCapOpenedTrans = true; @@ -58,7 +58,7 @@ namespace DotNetCore.CAP.MySql protected override async Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message) { await dbConnection.ExecuteAsync(PrepareSql(), message, dbTransaction); - + _logger.LogInformation("Published Message has been persisted in the database. name:" + message.ToString()); } @@ -69,7 +69,8 @@ namespace DotNetCore.CAP.MySql await conn.ExecuteAsync(PrepareSql(), message); } } - #region private methods + + #region private methods private string PrepareSql() { diff --git a/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs b/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs index 52de4b7..e4381e0 100644 --- a/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs @@ -1,6 +1,6 @@ using System; -using DotNetCore.CAP.Processor; using DotNetCore.CAP.PostgreSql; +using DotNetCore.CAP.Processor; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; diff --git a/src/DotNetCore.CAP.PostgreSql/CapPublisher.cs b/src/DotNetCore.CAP.PostgreSql/CapPublisher.cs index 5a81931..0f4e6c9 100644 --- a/src/DotNetCore.CAP.PostgreSql/CapPublisher.cs +++ b/src/DotNetCore.CAP.PostgreSql/CapPublisher.cs @@ -2,8 +2,8 @@ using System.Data; using System.Threading.Tasks; using Dapper; -using DotNetCore.CAP.Models; using DotNetCore.CAP.Abstractions; +using DotNetCore.CAP.Models; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Logging; @@ -28,7 +28,7 @@ namespace DotNetCore.CAP.PostgreSql if (_options.DbContextType != null) { IsUsingEF = true; - _dbContext = (DbContext)ServiceProvider.GetService(_options.DbContextType); + _dbContext = (DbContext)ServiceProvider.GetService(_options.DbContextType); } } @@ -37,7 +37,7 @@ namespace DotNetCore.CAP.PostgreSql DbConnection = _dbContext.Database.GetDbConnection(); var dbContextTransaction = _dbContext.Database.CurrentTransaction; var dbTrans = dbContextTransaction?.GetDbTransaction(); - //DbTransaction is dispose in original + //DbTransaction is dispose in original if (dbTrans?.Connection == null) { IsCapOpenedTrans = true; @@ -75,8 +75,8 @@ namespace DotNetCore.CAP.PostgreSql private string PrepareSql() { return $"INSERT INTO \"{_options.Schema}\".\"published\" (\"Name\",\"Content\",\"Retries\",\"Added\",\"ExpiresAt\",\"StatusName\")VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName)"; - } + } - #endregion + #endregion private methods } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.PostgreSql/IAdditionalProcessor.Default.cs b/src/DotNetCore.CAP.PostgreSql/IAdditionalProcessor.Default.cs index f544d2b..1c79f60 100644 --- a/src/DotNetCore.CAP.PostgreSql/IAdditionalProcessor.Default.cs +++ b/src/DotNetCore.CAP.PostgreSql/IAdditionalProcessor.Default.cs @@ -44,7 +44,7 @@ namespace DotNetCore.CAP.PostgreSql using (var connection = new NpgsqlConnection(_options.ConnectionString)) { removedCount = await connection.ExecuteAsync($"DELETE FROM \"{_options.Schema}\".\"{table}\" WHERE \"ExpiresAt\" < @now AND \"Id\" IN (SELECT \"Id\" FROM \"{_options.Schema}\".\"{table}\" LIMIT @count);", - new { now = DateTime.Now, count = MaxBatch }); + new { now = DateTime.Now, count = MaxBatch }); } if (removedCount != 0) diff --git a/src/DotNetCore.CAP.SqlServer/CapPublisher.cs b/src/DotNetCore.CAP.SqlServer/CapPublisher.cs index 60bafa4..18c17c2 100644 --- a/src/DotNetCore.CAP.SqlServer/CapPublisher.cs +++ b/src/DotNetCore.CAP.SqlServer/CapPublisher.cs @@ -37,7 +37,7 @@ namespace DotNetCore.CAP.SqlServer DbConnection = _dbContext.Database.GetDbConnection(); var dbContextTransaction = _dbContext.Database.CurrentTransaction; var dbTrans = dbContextTransaction?.GetDbTransaction(); - //DbTransaction is dispose in original + //DbTransaction is dispose in original if (dbTrans?.Connection == null) { IsCapOpenedTrans = true; @@ -66,8 +66,8 @@ namespace DotNetCore.CAP.SqlServer { using (var conn = new SqlConnection(_options.ConnectionString)) { - await conn.ExecuteAsync(PrepareSql(), message); - } + await conn.ExecuteAsync(PrepareSql(), message); + } } #region private methods diff --git a/src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs b/src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs index 34aba5b..2eeddc1 100644 --- a/src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs +++ b/src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs @@ -160,4 +160,4 @@ namespace DotNetCore.CAP.Abstractions #endregion private methods } -} +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Infrastructure/ObjectId.cs b/src/DotNetCore.CAP/Infrastructure/ObjectId.cs index d148126..23784fd 100644 --- a/src/DotNetCore.CAP/Infrastructure/ObjectId.cs +++ b/src/DotNetCore.CAP/Infrastructure/ObjectId.cs @@ -16,12 +16,14 @@ namespace DotNetCore.CAP { // private static fields private static readonly DateTime __unixEpoch; + private static readonly long __dateTimeMaxValueMillisecondsSinceEpoch; private static readonly long __dateTimeMinValueMillisecondsSinceEpoch; private static ObjectId __emptyInstance = default(ObjectId); private static int __staticMachine; private static short __staticPid; private static int __staticIncrement; // high byte will be masked out when generating new ObjectId + private static uint[] _lookup32 = Enumerable.Range(0, 256).Select(i => { string s = i.ToString("x2"); @@ -32,6 +34,7 @@ namespace DotNetCore.CAP // the extra two bytes are not visible to anyone outside of this class and they buy us considerable simplification // an additional advantage of this representation is that it will serialize to JSON without any 64 bit overflow problems private int _timestamp; + private int _machine; private short _pid; private int _increment; @@ -475,6 +478,7 @@ namespace DotNetCore.CAP return arr; } + /// /// Converts a byte array to a hex string. /// @@ -495,6 +499,7 @@ namespace DotNetCore.CAP } return new string(result); } + /// /// Converts a DateTime to number of milliseconds since Unix epoch. /// @@ -505,6 +510,7 @@ namespace DotNetCore.CAP var utcDateTime = ToUniversalTime(dateTime); return (utcDateTime - __unixEpoch).Ticks / 10000; } + /// /// Converts a DateTime to UTC (with special handling for MinValue and MaxValue). /// @@ -537,4 +543,4 @@ namespace DotNetCore.CAP return val - (val < 58 ? 48 : (val < 97 ? 55 : 87)); } } -} +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Internal/ConsumerInvokerFactory.cs b/src/DotNetCore.CAP/Internal/ConsumerInvokerFactory.cs index ea200d7..cdcb6d6 100644 --- a/src/DotNetCore.CAP/Internal/ConsumerInvokerFactory.cs +++ b/src/DotNetCore.CAP/Internal/ConsumerInvokerFactory.cs @@ -23,7 +23,7 @@ namespace DotNetCore.CAP.Internal public IConsumerInvoker CreateInvoker(ConsumerContext consumerContext) { - using(var scope = _serviceProvider.CreateScope()) + using (var scope = _serviceProvider.CreateScope()) { var context = new ConsumerInvokerContext(consumerContext) { diff --git a/src/DotNetCore.CAP/Internal/IModelBinder.ComplexType.cs b/src/DotNetCore.CAP/Internal/IModelBinder.ComplexType.cs index 6dbcb74..369e01e 100644 --- a/src/DotNetCore.CAP/Internal/IModelBinder.ComplexType.cs +++ b/src/DotNetCore.CAP/Internal/IModelBinder.ComplexType.cs @@ -3,7 +3,6 @@ using System.Reflection; using System.Threading.Tasks; using DotNetCore.CAP.Abstractions.ModelBinding; using DotNetCore.CAP.Infrastructure; -using DotNetCore.CAP.Models; namespace DotNetCore.CAP.Internal { diff --git a/src/DotNetCore.CAP/Internal/MethodBindException.cs b/src/DotNetCore.CAP/Internal/MethodBindException.cs index de9fe86..63eb454 100644 --- a/src/DotNetCore.CAP/Internal/MethodBindException.cs +++ b/src/DotNetCore.CAP/Internal/MethodBindException.cs @@ -5,8 +5,16 @@ namespace DotNetCore.CAP.Internal [Serializable] public class MethodBindException : Exception { - public MethodBindException() { } - public MethodBindException(string message) : base(message) { } - public MethodBindException(string message, Exception inner) : base(message, inner) { } + public MethodBindException() + { + } + + public MethodBindException(string message) : base(message) + { + } + + public MethodBindException(string message, Exception inner) : base(message, inner) + { + } } -} +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Models/Message.cs b/src/DotNetCore.CAP/Models/Message.cs index d739886..f55262a 100644 --- a/src/DotNetCore.CAP/Models/Message.cs +++ b/src/DotNetCore.CAP/Models/Message.cs @@ -23,4 +23,4 @@ namespace DotNetCore.CAP.Models Content = content; } } -} +} \ No newline at end of file From 83f221546a2204576baae84b880165c55dc46922 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Fri, 18 Aug 2017 00:11:14 +0800 Subject: [PATCH 40/68] update configuration add supported new database store provider description --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 72fe766..31c754c 100644 --- a/README.md +++ b/README.md @@ -44,10 +44,13 @@ If your Message Queue is using RabbitMQ, you can: PM> Install-Package DotNetCore.CAP.RabbitMQ ``` -CAP provides EntityFramework as default database store extension (The MySQL version is under development): +CAP supported SqlServer, MySql, PostgreSql as message store extension: ``` +//Select a database provider you are using PM> Install-Package DotNetCore.CAP.SqlServer +PM> Install-Package DotNetCore.CAP.MySql +PM> Install-Package DotNetCore.CAP.PostgreSql ``` ### Configuration @@ -69,6 +72,8 @@ public void ConfigureServices(IServiceCollection services) // If you are using Dapper,you need to add the config: x.UseSqlServer("Your ConnectionStrings"); + //x.UseMySql("Your ConnectionStrings"); + //x.UsePostgreSql("Your ConnectionStrings"); // If your Message Queue is using RabbitMQ you need to add the config: x.UseRabbitMQ("localhost"); From acdb17b198cbd88245b138c8de23e9b39fc8b392 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Fri, 18 Aug 2017 00:13:44 +0800 Subject: [PATCH 41/68] tab spaces to 4 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 31c754c..d45594c 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ public void ConfigureServices(IServiceCollection services) services.AddDbContext(); - services.AddCap(x => + services.AddCap(x => { // If your SqlServer is using EF for data operations, you need to add the following configuration: // Notice: You don't need to config x.UseSqlServer(""") again! From 69d22e7f9c21a6a3287d943e7b5886330a72f15c Mon Sep 17 00:00:00 2001 From: Savorboard Date: Fri, 18 Aug 2017 00:16:16 +0800 Subject: [PATCH 42/68] Update README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d45594c..5e6ce5f 100644 --- a/README.md +++ b/README.md @@ -64,12 +64,12 @@ public void ConfigureServices(IServiceCollection services) services.AddDbContext(); - services.AddCap(x => + services.AddCap(x => { // If your SqlServer is using EF for data operations, you need to add the following configuration: // Notice: You don't need to config x.UseSqlServer(""") again! x.UseEntityFramework(); - + // If you are using Dapper,you need to add the config: x.UseSqlServer("Your ConnectionStrings"); //x.UseMySql("Your ConnectionStrings"); @@ -87,7 +87,7 @@ public void Configure(IApplicationBuilder app) { ..... - app.UseCap(); + app.UseCap(); } ``` @@ -119,12 +119,12 @@ public class PublishController : Controller [Route("~/checkAccountWithTrans")] public async Task PublishMessageWithTransaction([FromServices]AppDbContext dbContext) { - using (var trans = dbContext.Database.BeginTransaction()) - { + using (var trans = dbContext.Database.BeginTransaction()) + { await _publisher.PublishAsync("xxx.services.account.check", new Person { Name = "Foo", Age = 11 }); trans.Commit(); - } + } return Ok(); } } @@ -179,7 +179,7 @@ namespace xxx.Service [CapSubscribe("xxx.services.account.check")] public void CheckReceivedMessage(Person person) { - + } } } From a12e3f95b2484cb4714a1ac807518b40015b287a Mon Sep 17 00:00:00 2001 From: yuleyule66 Date: Sun, 20 Aug 2017 00:15:18 +0800 Subject: [PATCH 43/68] delete change log file --- CHANGELOG.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index e69de29..0000000 From 04572d3810b739969c2f0f8d49c309b5bd5a9963 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Mon, 21 Aug 2017 10:54:43 +0800 Subject: [PATCH 44/68] rename --- src/DotNetCore.CAP/CAP.Options.cs | 8 ++++---- src/DotNetCore.CAP/IQueueExecutor.Publish.Base.cs | 2 +- src/DotNetCore.CAP/IQueueExecutor.Subscibe.cs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/DotNetCore.CAP/CAP.Options.cs b/src/DotNetCore.CAP/CAP.Options.cs index 3196c1e..d6cd441 100644 --- a/src/DotNetCore.CAP/CAP.Options.cs +++ b/src/DotNetCore.CAP/CAP.Options.cs @@ -23,7 +23,7 @@ namespace DotNetCore.CAP /// /// Default successed message expriation timespan, in seconds. /// - public const int DefaultSuccessMessageExpirationTimeSpan = 3600; + public const int DefaultSuccessMessageExpirationAfter = 3600; /// /// Failed message retry waiting interval. @@ -34,7 +34,7 @@ namespace DotNetCore.CAP { PollingDelay = DefaultPollingDelay; QueueProcessorCount = DefaultQueueProcessorCount; - SuccessedMessageExpiredTimeSpan = DefaultSuccessMessageExpirationTimeSpan; + SuccessedMessageExpiredAfter = DefaultSuccessMessageExpirationAfter; FailedMessageWaitingInterval = DefaultFailedMessageWaitingInterval; Extensions = new List(); } @@ -52,10 +52,10 @@ namespace DotNetCore.CAP public int QueueProcessorCount { get; set; } /// - /// Sent or received successed message due timespan, then the message will be deleted at due time. + /// Sent or received successed message after timespan of due, then the message will be deleted at due time. /// Dafault is 3600 seconds. /// - public int SuccessedMessageExpiredTimeSpan { get; set; } + public int SuccessedMessageExpiredAfter { get; set; } /// /// Failed messages polling delay time. diff --git a/src/DotNetCore.CAP/IQueueExecutor.Publish.Base.cs b/src/DotNetCore.CAP/IQueueExecutor.Publish.Base.cs index 9d5baab..e7274a8 100644 --- a/src/DotNetCore.CAP/IQueueExecutor.Publish.Base.cs +++ b/src/DotNetCore.CAP/IQueueExecutor.Publish.Base.cs @@ -58,7 +58,7 @@ namespace DotNetCore.CAP } else { - newState = new SucceededState(_options.SuccessedMessageExpiredTimeSpan); + newState = new SucceededState(_options.SuccessedMessageExpiredAfter); } await _stateChanger.ChangeStateAsync(message, newState, connection); diff --git a/src/DotNetCore.CAP/IQueueExecutor.Subscibe.cs b/src/DotNetCore.CAP/IQueueExecutor.Subscibe.cs index adcfd7a..a1f6956 100644 --- a/src/DotNetCore.CAP/IQueueExecutor.Subscibe.cs +++ b/src/DotNetCore.CAP/IQueueExecutor.Subscibe.cs @@ -64,7 +64,7 @@ namespace DotNetCore.CAP } else { - newState = new SucceededState(_options.SuccessedMessageExpiredTimeSpan); + newState = new SucceededState(_options.SuccessedMessageExpiredAfter); } await _stateChanger.ChangeStateAsync(message, newState, connection); From 13274160c65ab1b92cf8661cfa83c56bda33649b Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Sat, 19 Aug 2017 10:56:31 +0800 Subject: [PATCH 45/68] update samples. --- samples/Sample.RabbitMQ.MySql/AppDbContext.cs | 3 +- .../Controllers/ValuesController.cs | 9 ++-- .../Sample.RabbitMQ.MySql.csproj | 4 +- samples/Sample.RabbitMQ.MySql/Startup.cs | 6 ++- samples/Sample.RabbitMQ.PostgreSql/Startup.cs | 1 + .../Sample.RabbitMQ.SqlServer/AppDbContext.cs | 2 +- .../Controllers/ValuesController.cs | 48 +++++++++++++++++-- samples/Sample.RabbitMQ.SqlServer/Startup.cs | 6 ++- 8 files changed, 65 insertions(+), 14 deletions(-) diff --git a/samples/Sample.RabbitMQ.MySql/AppDbContext.cs b/samples/Sample.RabbitMQ.MySql/AppDbContext.cs index 20e01eb..cf3c96d 100644 --- a/samples/Sample.RabbitMQ.MySql/AppDbContext.cs +++ b/samples/Sample.RabbitMQ.MySql/AppDbContext.cs @@ -10,7 +10,8 @@ namespace Sample.RabbitMQ.MySql { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - optionsBuilder.UseMySql("Server=localhost;Database=Sample.RabbitMQ.MySql;UserId=root;Password=123123;"); + //optionsBuilder.UseMySql("Server=localhost;Database=Sample.RabbitMQ.MySql;UserId=root;Password=123123;"); + optionsBuilder.UseMySql("Server=192.168.2.206;Database=Sample.RabbitMQ.MySql;UserId=root;Password=123123;"); } } } diff --git a/samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs b/samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs index ed4ac06..0138520 100644 --- a/samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs +++ b/samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs @@ -43,18 +43,17 @@ namespace Sample.RabbitMQ.MySql.Controllers 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() + public void ReceiveMessage(DateTime time) { - Console.WriteLine("[sample.rabbitmq.mysql] message received"); - Debug.WriteLine("[sample.rabbitmq.mysql] message received"); + Console.WriteLine("[sample.rabbitmq.mysql] message received: "+ DateTime.Now.ToString() +" , sent time: " + time.ToString()); } } } diff --git a/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj b/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj index 2ae522d..1c67eb0 100644 --- a/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj +++ b/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj @@ -17,13 +17,13 @@ - + - + diff --git a/samples/Sample.RabbitMQ.MySql/Startup.cs b/samples/Sample.RabbitMQ.MySql/Startup.cs index 6a9951f..19b1eac 100644 --- a/samples/Sample.RabbitMQ.MySql/Startup.cs +++ b/samples/Sample.RabbitMQ.MySql/Startup.cs @@ -18,7 +18,11 @@ namespace Sample.RabbitMQ.MySql services.AddCap(x => { x.UseEntityFramework(); - x.UseRabbitMQ("localhost"); + x.UseRabbitMQ(y => { + y.HostName = "192.168.2.206"; + y.UserName = "admin"; + y.Password = "123123"; + }); }); services.AddMvc(); diff --git a/samples/Sample.RabbitMQ.PostgreSql/Startup.cs b/samples/Sample.RabbitMQ.PostgreSql/Startup.cs index fa963b1..ac7ead0 100644 --- a/samples/Sample.RabbitMQ.PostgreSql/Startup.cs +++ b/samples/Sample.RabbitMQ.PostgreSql/Startup.cs @@ -16,6 +16,7 @@ namespace Sample.RabbitMQ.PostgreSql // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + services.AddMvc(); } diff --git a/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs b/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs index 6e3ea9c..0607bb0 100644 --- a/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs +++ b/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs @@ -6,7 +6,7 @@ namespace Sample.RabbitMQ.SqlServer { 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=TestCap;User Id=cmswuliu;Password=h7xY81agBn*Veiu3;MultipleActiveResultSets=True"); //optionsBuilder.UseSqlServer("Server=DESKTOP-M9R8T31;Initial Catalog=Sample.Kafka.SqlServer;User Id=sa;Password=P@ssw0rd;MultipleActiveResultSets=True"); } } diff --git a/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs b/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs index 59a9b17..09b1df3 100644 --- a/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs +++ b/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs @@ -6,6 +6,18 @@ using Microsoft.AspNetCore.Mvc; namespace Sample.RabbitMQ.SqlServer.Controllers { + public class Person + { + public string Name { get; set; } + public int Age { get; set; } + + public override string ToString() + { + return "Name:" + Name + ";Age:" + Age; + } + } + + [Route("api/[controller]")] public class ValuesController : Controller, ICapSubscribe { @@ -21,8 +33,12 @@ namespace Sample.RabbitMQ.SqlServer.Controllers [Route("~/publish")] public IActionResult PublishMessage() { - _capBus.Publish("sample.rabbitmq.mysql", ""); - + using(var trans = _dbContext.Database.BeginTransaction()) + { + //_capBus.Publish("sample.rabbitmq.mysql22222", DateTime.Now); + _capBus.Publish("sample.rabbitmq.mysql33333", new Person { Name = "宜兴", Age = 11 }); + trans.Commit(); + } return Ok(); } @@ -32,12 +48,38 @@ namespace Sample.RabbitMQ.SqlServer.Controllers using (var trans = await _dbContext.Database.BeginTransactionAsync()) { await _capBus.PublishAsync("sample.rabbitmq.mysql", ""); - + trans.Commit(); } return Ok(); } + [CapSubscribe("sample.rabbitmq.mysql33333")] + public void KafkaTest22(Person person) + { + var aa = _dbContext.Database; + + _dbContext.Dispose(); + + Console.WriteLine("[sample.kafka.sqlserver] message received " + person.ToString()); + Debug.WriteLine("[sample.kafka.sqlserver] message received " + person.ToString()); + } + + //[CapSubscribe("sample.rabbitmq.mysql22222")] + //public void KafkaTest22(DateTime time) + //{ + // Console.WriteLine("[sample.kafka.sqlserver] message received " + time.ToString()); + // Debug.WriteLine("[sample.kafka.sqlserver] message received " + time.ToString()); + //} + + [CapSubscribe("sample.rabbitmq.mysql22222")] + public async Task KafkaTest33(DateTime time) + { + Console.WriteLine("[sample.kafka.sqlserver] message received " + time.ToString()); + Debug.WriteLine("[sample.kafka.sqlserver] message received " + time.ToString()); + return await Task.FromResult(time); + } + [NonAction] [CapSubscribe("sample.kafka.sqlserver3")] [CapSubscribe("sample.kafka.sqlserver4")] diff --git a/samples/Sample.RabbitMQ.SqlServer/Startup.cs b/samples/Sample.RabbitMQ.SqlServer/Startup.cs index 523e2d9..e6819ea 100644 --- a/samples/Sample.RabbitMQ.SqlServer/Startup.cs +++ b/samples/Sample.RabbitMQ.SqlServer/Startup.cs @@ -14,7 +14,11 @@ namespace Sample.RabbitMQ.SqlServer services.AddCap(x => { x.UseEntityFramework(); - x.UseRabbitMQ("localhost"); + x.UseRabbitMQ(y=> { + y.HostName = "192.168.2.206"; + y.UserName = "admin"; + y.Password = "123123"; + }); }); services.AddMvc(); From af9d22edaf9251bcb7f771143b1f217480776cba Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Mon, 21 Aug 2017 11:46:49 +0800 Subject: [PATCH 46/68] add model binder tests. --- .../ModelBinderFactoryTest.cs | 47 +++++++++++++++ test/DotNetCore.CAP.Test/Sample.cs | 58 +++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 test/DotNetCore.CAP.Test/ModelBinderFactoryTest.cs create mode 100644 test/DotNetCore.CAP.Test/Sample.cs diff --git a/test/DotNetCore.CAP.Test/ModelBinderFactoryTest.cs b/test/DotNetCore.CAP.Test/ModelBinderFactoryTest.cs new file mode 100644 index 0000000..accb17c --- /dev/null +++ b/test/DotNetCore.CAP.Test/ModelBinderFactoryTest.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Linq; +using System.Reflection; +using DotNetCore.CAP.Internal; +using Xunit; + +namespace DotNetCore.CAP.Test +{ + public class ModelBinderFactoryTest + { + IModelBinderFactory _factory; + + public ModelBinderFactoryTest() + { + _factory = new ModelBinderFactory(); + } + + [Theory] + [InlineData(nameof(Sample.DateTimeParam))] + [InlineData(nameof(Sample.StringParam))] + [InlineData(nameof(Sample.IntegerParam))] + [InlineData(nameof(Sample.GuidParam))] + [InlineData(nameof(Sample.UriParam))] + public void CreateSimpleTypeBinderTest(string methodName) + { + var datetimeMethod = typeof(Sample).GetRuntimeMethods().Single(x => x.Name == methodName); + var binder = _factory.CreateBinder(datetimeMethod.GetParameters()[0]); + Assert.NotNull(binder); + Assert.True(binder is SimpleTypeModelBinder); + Assert.False(binder is ComplexTypeModelBinder); + } + + [Theory] + [InlineData(nameof(Sample.ComplexTypeParam))] + public void CreateComplexTypeBinderTest(string methodName) + { + var datetimeMethod = typeof(Sample).GetRuntimeMethods().Single(x => x.Name == methodName); + var binder = _factory.CreateBinder(datetimeMethod.GetParameters()[0]); + Assert.NotNull(binder); + Assert.False(binder is SimpleTypeModelBinder); + Assert.True(binder is ComplexTypeModelBinder); + } + + } +} diff --git a/test/DotNetCore.CAP.Test/Sample.cs b/test/DotNetCore.CAP.Test/Sample.cs new file mode 100644 index 0000000..a383a1a --- /dev/null +++ b/test/DotNetCore.CAP.Test/Sample.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace DotNetCore.CAP.Test +{ + public class Sample + { + + public void DateTimeParam(DateTime dateTime) + { + + } + + public void StringParam(string @string) + { + + } + + public void GuidParam(Guid guid) + { + + } + + public void UriParam(Uri uri) + { + + } + + public void IntegerParam(int @int) + { + + } + + public void ComplexTypeParam(ComplexType complexType) + { + + } + } + + public class ComplexType + { + public DateTime Time { get; set; } + + public string String { get; set; } + + public Guid Guid { get; set; } + + public Person Person { get; set; } + } + + public class Person + { + public int Age { get; set; } + + public string Name { get; set; } + } +} From ab0ccc920aebdc7bc26d194e5e78250738e5a1f6 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Mon, 21 Aug 2017 18:33:29 +0800 Subject: [PATCH 47/68] add ObjectMethodExecutor --- .../ObjectMethodExecutor/AwaitableInfo.cs | 127 +++++++ .../CoercedAwaitableInfo.cs | 55 +++ .../ObjectMethodExecutor.cs | 340 ++++++++++++++++++ .../ObjectMethodExecutorAwaitable.cs | 114 ++++++ .../ObjectMethodExecutorFSharpSupport.cs | 151 ++++++++ 5 files changed, 787 insertions(+) create mode 100644 src/DotNetCore.CAP/Internal/ObjectMethodExecutor/AwaitableInfo.cs create mode 100644 src/DotNetCore.CAP/Internal/ObjectMethodExecutor/CoercedAwaitableInfo.cs create mode 100644 src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutor.cs create mode 100644 src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs create mode 100644 src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs diff --git a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/AwaitableInfo.cs b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/AwaitableInfo.cs new file mode 100644 index 0000000..431b83a --- /dev/null +++ b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/AwaitableInfo.cs @@ -0,0 +1,127 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Microsoft.Extensions.Internal +{ + internal struct AwaitableInfo + { + public Type AwaiterType { get; } + public PropertyInfo AwaiterIsCompletedProperty { get; } + public MethodInfo AwaiterGetResultMethod { get; } + public MethodInfo AwaiterOnCompletedMethod { get; } + public MethodInfo AwaiterUnsafeOnCompletedMethod { get; } + public Type ResultType { get; } + public MethodInfo GetAwaiterMethod { get; } + + public AwaitableInfo( + Type awaiterType, + PropertyInfo awaiterIsCompletedProperty, + MethodInfo awaiterGetResultMethod, + MethodInfo awaiterOnCompletedMethod, + MethodInfo awaiterUnsafeOnCompletedMethod, + Type resultType, + MethodInfo getAwaiterMethod) + { + AwaiterType = awaiterType; + AwaiterIsCompletedProperty = awaiterIsCompletedProperty; + AwaiterGetResultMethod = awaiterGetResultMethod; + AwaiterOnCompletedMethod = awaiterOnCompletedMethod; + AwaiterUnsafeOnCompletedMethod = awaiterUnsafeOnCompletedMethod; + ResultType = resultType; + GetAwaiterMethod = getAwaiterMethod; + } + + public static bool IsTypeAwaitable(Type type, out AwaitableInfo awaitableInfo) + { + // Based on Roslyn code: http://source.roslyn.io/#Microsoft.CodeAnalysis.Workspaces/Shared/Extensions/ISymbolExtensions.cs,db4d48ba694b9347 + + // Awaitable must have method matching "object GetAwaiter()" + var getAwaiterMethod = type.GetRuntimeMethods().FirstOrDefault(m => + m.Name.Equals("GetAwaiter", StringComparison.OrdinalIgnoreCase) + && m.GetParameters().Length == 0 + && m.ReturnType != null); + if (getAwaiterMethod == null) + { + awaitableInfo = default(AwaitableInfo); + return false; + } + + var awaiterType = getAwaiterMethod.ReturnType; + + // Awaiter must have property matching "bool IsCompleted { get; }" + var isCompletedProperty = awaiterType.GetRuntimeProperties().FirstOrDefault(p => + p.Name.Equals("IsCompleted", StringComparison.OrdinalIgnoreCase) + && p.PropertyType == typeof(bool) + && p.GetMethod != null); + if (isCompletedProperty == null) + { + awaitableInfo = default(AwaitableInfo); + return false; + } + + // Awaiter must implement INotifyCompletion + var awaiterInterfaces = awaiterType.GetInterfaces(); + var implementsINotifyCompletion = awaiterInterfaces.Any(t => t == typeof(INotifyCompletion)); + if (!implementsINotifyCompletion) + { + awaitableInfo = default(AwaitableInfo); + return false; + } + + // INotifyCompletion supplies a method matching "void OnCompleted(Action action)" + var iNotifyCompletionMap = awaiterType + .GetTypeInfo() + .GetRuntimeInterfaceMap(typeof(INotifyCompletion)); + var onCompletedMethod = iNotifyCompletionMap.InterfaceMethods.Single(m => + m.Name.Equals("OnCompleted", StringComparison.OrdinalIgnoreCase) + && m.ReturnType == typeof(void) + && m.GetParameters().Length == 1 + && m.GetParameters()[0].ParameterType == typeof(Action)); + + // Awaiter optionally implements ICriticalNotifyCompletion + var implementsICriticalNotifyCompletion = awaiterInterfaces.Any(t => t == typeof(ICriticalNotifyCompletion)); + MethodInfo unsafeOnCompletedMethod; + if (implementsICriticalNotifyCompletion) + { + // ICriticalNotifyCompletion supplies a method matching "void UnsafeOnCompleted(Action action)" + var iCriticalNotifyCompletionMap = awaiterType + .GetTypeInfo() + .GetRuntimeInterfaceMap(typeof(ICriticalNotifyCompletion)); + unsafeOnCompletedMethod = iCriticalNotifyCompletionMap.InterfaceMethods.Single(m => + m.Name.Equals("UnsafeOnCompleted", StringComparison.OrdinalIgnoreCase) + && m.ReturnType == typeof(void) + && m.GetParameters().Length == 1 + && m.GetParameters()[0].ParameterType == typeof(Action)); + } + else + { + unsafeOnCompletedMethod = null; + } + + // Awaiter must have method matching "void GetResult" or "T GetResult()" + var getResultMethod = awaiterType.GetRuntimeMethods().FirstOrDefault(m => + m.Name.Equals("GetResult") + && m.GetParameters().Length == 0); + if (getResultMethod == null) + { + awaitableInfo = default(AwaitableInfo); + return false; + } + + awaitableInfo = new AwaitableInfo( + awaiterType, + isCompletedProperty, + getResultMethod, + onCompletedMethod, + unsafeOnCompletedMethod, + getResultMethod.ReturnType, + getAwaiterMethod); + return true; + } + } +} diff --git a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/CoercedAwaitableInfo.cs b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/CoercedAwaitableInfo.cs new file mode 100644 index 0000000..4e48ef0 --- /dev/null +++ b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/CoercedAwaitableInfo.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq.Expressions; + +namespace Microsoft.Extensions.Internal +{ + internal struct CoercedAwaitableInfo + { + public AwaitableInfo AwaitableInfo { get; } + public Expression CoercerExpression { get; } + public Type CoercerResultType { get; } + public bool RequiresCoercion => CoercerExpression != null; + + public CoercedAwaitableInfo(AwaitableInfo awaitableInfo) + { + AwaitableInfo = awaitableInfo; + CoercerExpression = null; + CoercerResultType = null; + } + + public CoercedAwaitableInfo(Expression coercerExpression, Type coercerResultType, AwaitableInfo coercedAwaitableInfo) + { + CoercerExpression = coercerExpression; + CoercerResultType = coercerResultType; + AwaitableInfo = coercedAwaitableInfo; + } + + public static bool IsTypeAwaitable(Type type, out CoercedAwaitableInfo info) + { + if (AwaitableInfo.IsTypeAwaitable(type, out var directlyAwaitableInfo)) + { + info = new CoercedAwaitableInfo(directlyAwaitableInfo); + return true; + } + + // It's not directly awaitable, but maybe we can coerce it. + // Currently we support coercing FSharpAsync. + if (ObjectMethodExecutorFSharpSupport.TryBuildCoercerFromFSharpAsyncToAwaitable(type, + out var coercerExpression, + out var coercerResultType)) + { + if (AwaitableInfo.IsTypeAwaitable(coercerResultType, out var coercedAwaitableInfo)) + { + info = new CoercedAwaitableInfo(coercerExpression, coercerResultType, coercedAwaitableInfo); + return true; + } + } + + info = default(CoercedAwaitableInfo); + return false; + } + } +} diff --git a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutor.cs b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutor.cs new file mode 100644 index 0000000..b2025d6 --- /dev/null +++ b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutor.cs @@ -0,0 +1,340 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; + +namespace Microsoft.Extensions.Internal +{ + internal class ObjectMethodExecutor + { + private readonly object[] _parameterDefaultValues; + private readonly MethodExecutorAsync _executorAsync; + private readonly MethodExecutor _executor; + + private static readonly ConstructorInfo _objectMethodExecutorAwaitableConstructor = + typeof(ObjectMethodExecutorAwaitable).GetConstructor(new[] { + typeof(object), // customAwaitable + typeof(Func), // getAwaiterMethod + typeof(Func), // isCompletedMethod + typeof(Func), // getResultMethod + typeof(Action), // onCompletedMethod + typeof(Action) // unsafeOnCompletedMethod + }); + + private ObjectMethodExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo, object[] parameterDefaultValues) + { + if (methodInfo == null) + { + throw new ArgumentNullException(nameof(methodInfo)); + } + + MethodInfo = methodInfo; + MethodParameters = methodInfo.GetParameters(); + TargetTypeInfo = targetTypeInfo; + MethodReturnType = methodInfo.ReturnType; + + var isAwaitable = CoercedAwaitableInfo.IsTypeAwaitable(MethodReturnType, out var coercedAwaitableInfo); + + IsMethodAsync = isAwaitable; + AsyncResultType = isAwaitable ? coercedAwaitableInfo.AwaitableInfo.ResultType : null; + + // Upstream code may prefer to use the sync-executor even for async methods, because if it knows + // that the result is a specific Task where T is known, then it can directly cast to that type + // and await it without the extra heap allocations involved in the _executorAsync code path. + _executor = GetExecutor(methodInfo, targetTypeInfo); + + if (IsMethodAsync) + { + _executorAsync = GetExecutorAsync(methodInfo, targetTypeInfo, coercedAwaitableInfo); + } + + _parameterDefaultValues = parameterDefaultValues; + } + + private delegate ObjectMethodExecutorAwaitable MethodExecutorAsync(object target, params object[] parameters); + + private delegate object MethodExecutor(object target, params object[] parameters); + + private delegate void VoidMethodExecutor(object target, object[] parameters); + + public MethodInfo MethodInfo { get; } + + public ParameterInfo[] MethodParameters { get; } + + public TypeInfo TargetTypeInfo { get; } + + public Type AsyncResultType { get; } + + // This field is made internal set because it is set in unit tests. + public Type MethodReturnType { get; internal set; } + + public bool IsMethodAsync { get; } + + public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo) + { + return new ObjectMethodExecutor(methodInfo, targetTypeInfo, null); + } + + public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo, object[] parameterDefaultValues) + { + if (parameterDefaultValues == null) + { + throw new ArgumentNullException(nameof(parameterDefaultValues)); + } + + return new ObjectMethodExecutor(methodInfo, targetTypeInfo, parameterDefaultValues); + } + + /// + /// Executes the configured method on . This can be used whether or not + /// the configured method is asynchronous. + /// + /// + /// Even if the target method is asynchronous, it's desirable to invoke it using Execute rather than + /// ExecuteAsync if you know at compile time what the return type is, because then you can directly + /// "await" that value (via a cast), and then the generated code will be able to reference the + /// resulting awaitable as a value-typed variable. If you use ExecuteAsync instead, the generated + /// code will have to treat the resulting awaitable as a boxed object, because it doesn't know at + /// compile time what type it would be. + /// + /// The object whose method is to be executed. + /// Parameters to pass to the method. + /// The method return value. + public object Execute(object target, params object[] parameters) + { + return _executor(target, parameters); + } + + /// + /// Executes the configured method on . This can only be used if the configured + /// method is asynchronous. + /// + /// + /// If you don't know at compile time the type of the method's returned awaitable, you can use ExecuteAsync, + /// which supplies an awaitable-of-object. This always works, but can incur several extra heap allocations + /// as compared with using Execute and then using "await" on the result value typecasted to the known + /// awaitable type. The possible extra heap allocations are for: + /// + /// 1. The custom awaitable (though usually there's a heap allocation for this anyway, since normally + /// it's a reference type, and you normally create a new instance per call). + /// 2. The custom awaiter (whether or not it's a value type, since if it's not, you need a new instance + /// of it, and if it is, it will have to be boxed so the calling code can reference it as an object). + /// 3. The async result value, if it's a value type (it has to be boxed as an object, since the calling + /// code doesn't know what type it's going to be). + /// + /// The object whose method is to be executed. + /// Parameters to pass to the method. + /// An object that you can "await" to get the method return value. + public ObjectMethodExecutorAwaitable ExecuteAsync(object target, params object[] parameters) + { + return _executorAsync(target, parameters); + } + + public object GetDefaultValueForParameter(int index) + { + if (_parameterDefaultValues == null) + { + throw new InvalidOperationException($"Cannot call {nameof(GetDefaultValueForParameter)}, because no parameter default values were supplied."); + } + + if (index < 0 || index > MethodParameters.Length - 1) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return _parameterDefaultValues[index]; + } + + private static MethodExecutor GetExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo) + { + // Parameters to executor + var targetParameter = Expression.Parameter(typeof(object), "target"); + var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); + + // Build parameter list + var parameters = new List(); + var paramInfos = methodInfo.GetParameters(); + for (int i = 0; i < paramInfos.Length; i++) + { + var paramInfo = paramInfos[i]; + var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i)); + var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType); + + // valueCast is "(Ti) parameters[i]" + parameters.Add(valueCast); + } + + // Call method + var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType()); + var methodCall = Expression.Call(instanceCast, methodInfo, parameters); + + // methodCall is "((Ttarget) target) method((T0) parameters[0], (T1) parameters[1], ...)" + // Create function + if (methodCall.Type == typeof(void)) + { + var lambda = Expression.Lambda(methodCall, targetParameter, parametersParameter); + var voidExecutor = lambda.Compile(); + return WrapVoidMethod(voidExecutor); + } + else + { + // must coerce methodCall to match ActionExecutor signature + var castMethodCall = Expression.Convert(methodCall, typeof(object)); + var lambda = Expression.Lambda(castMethodCall, targetParameter, parametersParameter); + return lambda.Compile(); + } + } + + private static MethodExecutor WrapVoidMethod(VoidMethodExecutor executor) + { + return delegate (object target, object[] parameters) + { + executor(target, parameters); + return null; + }; + } + + private static MethodExecutorAsync GetExecutorAsync( + MethodInfo methodInfo, + TypeInfo targetTypeInfo, + CoercedAwaitableInfo coercedAwaitableInfo) + { + // Parameters to executor + var targetParameter = Expression.Parameter(typeof(object), "target"); + var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); + + // Build parameter list + var parameters = new List(); + var paramInfos = methodInfo.GetParameters(); + for (int i = 0; i < paramInfos.Length; i++) + { + var paramInfo = paramInfos[i]; + var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i)); + var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType); + + // valueCast is "(Ti) parameters[i]" + parameters.Add(valueCast); + } + + // Call method + var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType()); + var methodCall = Expression.Call(instanceCast, methodInfo, parameters); + + // Using the method return value, construct an ObjectMethodExecutorAwaitable based on + // the info we have about its implementation of the awaitable pattern. Note that all + // the funcs/actions we construct here are precompiled, so that only one instance of + // each is preserved throughout the lifetime of the ObjectMethodExecutor. + + // var getAwaiterFunc = (object awaitable) => + // (object)((CustomAwaitableType)awaitable).GetAwaiter(); + var customAwaitableParam = Expression.Parameter(typeof(object), "awaitable"); + var awaitableInfo = coercedAwaitableInfo.AwaitableInfo; + var postCoercionMethodReturnType = coercedAwaitableInfo.CoercerResultType ?? methodInfo.ReturnType; + var getAwaiterFunc = Expression.Lambda>( + Expression.Convert( + Expression.Call( + Expression.Convert(customAwaitableParam, postCoercionMethodReturnType), + awaitableInfo.GetAwaiterMethod), + typeof(object)), + customAwaitableParam).Compile(); + + // var isCompletedFunc = (object awaiter) => + // ((CustomAwaiterType)awaiter).IsCompleted; + var isCompletedParam = Expression.Parameter(typeof(object), "awaiter"); + var isCompletedFunc = Expression.Lambda>( + Expression.MakeMemberAccess( + Expression.Convert(isCompletedParam, awaitableInfo.AwaiterType), + awaitableInfo.AwaiterIsCompletedProperty), + isCompletedParam).Compile(); + + var getResultParam = Expression.Parameter(typeof(object), "awaiter"); + Func getResultFunc; + if (awaitableInfo.ResultType == typeof(void)) + { + // var getResultFunc = (object awaiter) => + // { + // ((CustomAwaiterType)awaiter).GetResult(); // We need to invoke this to surface any exceptions + // return (object)null; + // }; + getResultFunc = Expression.Lambda>( + Expression.Block( + Expression.Call( + Expression.Convert(getResultParam, awaitableInfo.AwaiterType), + awaitableInfo.AwaiterGetResultMethod), + Expression.Constant(null) + ), + getResultParam).Compile(); + } + else + { + // var getResultFunc = (object awaiter) => + // (object)((CustomAwaiterType)awaiter).GetResult(); + getResultFunc = Expression.Lambda>( + Expression.Convert( + Expression.Call( + Expression.Convert(getResultParam, awaitableInfo.AwaiterType), + awaitableInfo.AwaiterGetResultMethod), + typeof(object)), + getResultParam).Compile(); + } + + // var onCompletedFunc = (object awaiter, Action continuation) => { + // ((CustomAwaiterType)awaiter).OnCompleted(continuation); + // }; + var onCompletedParam1 = Expression.Parameter(typeof(object), "awaiter"); + var onCompletedParam2 = Expression.Parameter(typeof(Action), "continuation"); + var onCompletedFunc = Expression.Lambda>( + Expression.Call( + Expression.Convert(onCompletedParam1, awaitableInfo.AwaiterType), + awaitableInfo.AwaiterOnCompletedMethod, + onCompletedParam2), + onCompletedParam1, + onCompletedParam2).Compile(); + + Action unsafeOnCompletedFunc = null; + if (awaitableInfo.AwaiterUnsafeOnCompletedMethod != null) + { + // var unsafeOnCompletedFunc = (object awaiter, Action continuation) => { + // ((CustomAwaiterType)awaiter).UnsafeOnCompleted(continuation); + // }; + var unsafeOnCompletedParam1 = Expression.Parameter(typeof(object), "awaiter"); + var unsafeOnCompletedParam2 = Expression.Parameter(typeof(Action), "continuation"); + unsafeOnCompletedFunc = Expression.Lambda>( + Expression.Call( + Expression.Convert(unsafeOnCompletedParam1, awaitableInfo.AwaiterType), + awaitableInfo.AwaiterUnsafeOnCompletedMethod, + unsafeOnCompletedParam2), + unsafeOnCompletedParam1, + unsafeOnCompletedParam2).Compile(); + } + + // If we need to pass the method call result through a coercer function to get an + // awaitable, then do so. + var coercedMethodCall = coercedAwaitableInfo.RequiresCoercion + ? Expression.Invoke(coercedAwaitableInfo.CoercerExpression, methodCall) + : (Expression)methodCall; + + // return new ObjectMethodExecutorAwaitable( + // (object)coercedMethodCall, + // getAwaiterFunc, + // isCompletedFunc, + // getResultFunc, + // onCompletedFunc, + // unsafeOnCompletedFunc); + var returnValueExpression = Expression.New( + _objectMethodExecutorAwaitableConstructor, + Expression.Convert(coercedMethodCall, typeof(object)), + Expression.Constant(getAwaiterFunc), + Expression.Constant(isCompletedFunc), + Expression.Constant(getResultFunc), + Expression.Constant(onCompletedFunc), + Expression.Constant(unsafeOnCompletedFunc, typeof(Action))); + + var lambda = Expression.Lambda(returnValueExpression, targetParameter, parametersParameter); + return lambda.Compile(); + } + } +} diff --git a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs new file mode 100644 index 0000000..7509b86 --- /dev/null +++ b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs @@ -0,0 +1,114 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.CompilerServices; + +namespace Microsoft.Extensions.Internal +{ + /// + /// Provides a common awaitable structure that can + /// return, regardless of whether the underlying value is a System.Task, an FSharpAsync, or an + /// application-defined custom awaitable. + /// + internal struct ObjectMethodExecutorAwaitable + { + private readonly object _customAwaitable; + private readonly Func _getAwaiterMethod; + private readonly Func _isCompletedMethod; + private readonly Func _getResultMethod; + private readonly Action _onCompletedMethod; + private readonly Action _unsafeOnCompletedMethod; + + // Perf note: since we're requiring the customAwaitable to be supplied here as an object, + // this will trigger a further allocation if it was a value type (i.e., to box it). We can't + // fix this by making the customAwaitable type generic, because the calling code typically + // does not know the type of the awaitable/awaiter at compile-time anyway. + // + // However, we could fix it by not passing the customAwaitable here at all, and instead + // passing a func that maps directly from the target object (e.g., controller instance), + // target method (e.g., action method info), and params array to the custom awaiter in the + // GetAwaiter() method below. In effect, by delaying the actual method call until the + // upstream code calls GetAwaiter on this ObjectMethodExecutorAwaitable instance. + // This optimization is not currently implemented because: + // [1] It would make no difference when the awaitable was an object type, which is + // by far the most common scenario (e.g., System.Task). + // [2] It would be complex - we'd need some kind of object pool to track all the parameter + // arrays until we needed to use them in GetAwaiter(). + // We can reconsider this in the future if there's a need to optimize for ValueTask + // or other value-typed awaitables. + + public ObjectMethodExecutorAwaitable( + object customAwaitable, + Func getAwaiterMethod, + Func isCompletedMethod, + Func getResultMethod, + Action onCompletedMethod, + Action unsafeOnCompletedMethod) + { + _customAwaitable = customAwaitable; + _getAwaiterMethod = getAwaiterMethod; + _isCompletedMethod = isCompletedMethod; + _getResultMethod = getResultMethod; + _onCompletedMethod = onCompletedMethod; + _unsafeOnCompletedMethod = unsafeOnCompletedMethod; + } + + public Awaiter GetAwaiter() + { + var customAwaiter = _getAwaiterMethod(_customAwaitable); + return new Awaiter(customAwaiter, _isCompletedMethod, _getResultMethod, _onCompletedMethod, _unsafeOnCompletedMethod); + } + + public struct Awaiter : ICriticalNotifyCompletion + { + private readonly object _customAwaiter; + private readonly Func _isCompletedMethod; + private readonly Func _getResultMethod; + private readonly Action _onCompletedMethod; + private readonly Action _unsafeOnCompletedMethod; + + public Awaiter( + object customAwaiter, + Func isCompletedMethod, + Func getResultMethod, + Action onCompletedMethod, + Action unsafeOnCompletedMethod) + { + _customAwaiter = customAwaiter; + _isCompletedMethod = isCompletedMethod; + _getResultMethod = getResultMethod; + _onCompletedMethod = onCompletedMethod; + _unsafeOnCompletedMethod = unsafeOnCompletedMethod; + } + + public bool IsCompleted => _isCompletedMethod(_customAwaiter); + + public object GetResult() => _getResultMethod(_customAwaiter); + + public void OnCompleted(Action continuation) + { + _onCompletedMethod(_customAwaiter, continuation); + } + + public void UnsafeOnCompleted(Action continuation) + { + // If the underlying awaitable implements ICriticalNotifyCompletion, use its UnsafeOnCompleted. + // If not, fall back on using its OnCompleted. + // + // Why this is safe: + // - Implementing ICriticalNotifyCompletion is a way of saying the caller can choose whether it + // needs the execution context to be preserved (which it signals by calling OnCompleted), or + // that it doesn't (which it signals by calling UnsafeOnCompleted). Obviously it's faster *not* + // to preserve and restore the context, so we prefer that where possible. + // - If a caller doesn't need the execution context to be preserved and hence calls UnsafeOnCompleted, + // there's no harm in preserving it anyway - it's just a bit of wasted cost. That's what will happen + // if a caller sees that the proxy implements ICriticalNotifyCompletion but the proxy chooses to + // pass the call on to the underlying awaitable's OnCompleted method. + + var underlyingMethodToUse = _unsafeOnCompletedMethod ?? _onCompletedMethod; + underlyingMethodToUse(_customAwaiter, continuation); + } + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs new file mode 100644 index 0000000..2198c0c --- /dev/null +++ b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs @@ -0,0 +1,151 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.Internal +{ + /// + /// Helper for detecting whether a given type is FSharpAsync`1, and if so, supplying + /// an for mapping instances of that type to a C# awaitable. + /// + /// + /// The main design goal here is to avoid taking a compile-time dependency on + /// FSharp.Core.dll, because non-F# applications wouldn't use it. So all the references + /// to FSharp types have to be constructed dynamically at runtime. + /// + internal static class ObjectMethodExecutorFSharpSupport + { + private static object _fsharpValuesCacheLock = new object(); + private static Assembly _fsharpCoreAssembly; + private static MethodInfo _fsharpAsyncStartAsTaskGenericMethod; + private static PropertyInfo _fsharpOptionOfTaskCreationOptionsNoneProperty; + private static PropertyInfo _fsharpOptionOfCancellationTokenNoneProperty; + + public static bool TryBuildCoercerFromFSharpAsyncToAwaitable( + Type possibleFSharpAsyncType, + out Expression coerceToAwaitableExpression, + out Type awaitableType) + { + var methodReturnGenericType = possibleFSharpAsyncType.IsGenericType + ? possibleFSharpAsyncType.GetGenericTypeDefinition() + : null; + + if (!IsFSharpAsyncOpenGenericType(methodReturnGenericType)) + { + coerceToAwaitableExpression = null; + awaitableType = null; + return false; + } + + var awaiterResultType = possibleFSharpAsyncType.GetGenericArguments().Single(); + awaitableType = typeof(Task<>).MakeGenericType(awaiterResultType); + + // coerceToAwaitableExpression = (object fsharpAsync) => + // { + // return (object)FSharpAsync.StartAsTask( + // (Microsoft.FSharp.Control.FSharpAsync)fsharpAsync, + // FSharpOption.None, + // FSharpOption.None); + // }; + var startAsTaskClosedMethod = _fsharpAsyncStartAsTaskGenericMethod + .MakeGenericMethod(awaiterResultType); + var coerceToAwaitableParam = Expression.Parameter(typeof(object)); + coerceToAwaitableExpression = Expression.Lambda( + Expression.Convert( + Expression.Call( + startAsTaskClosedMethod, + Expression.Convert(coerceToAwaitableParam, possibleFSharpAsyncType), + Expression.MakeMemberAccess(null, _fsharpOptionOfTaskCreationOptionsNoneProperty), + Expression.MakeMemberAccess(null, _fsharpOptionOfCancellationTokenNoneProperty)), + typeof(object)), + coerceToAwaitableParam); + + return true; + } + + private static bool IsFSharpAsyncOpenGenericType(Type possibleFSharpAsyncGenericType) + { + var typeFullName = possibleFSharpAsyncGenericType?.FullName; + if (!string.Equals(typeFullName, "Microsoft.FSharp.Control.FSharpAsync`1", StringComparison.Ordinal)) + { + return false; + } + + lock (_fsharpValuesCacheLock) + { + if (_fsharpCoreAssembly != null) + { + // Since we've already found the real FSharpAsync.Core assembly, we just have + // to check that the supplied FSharpAsync`1 type is the one from that assembly. + return possibleFSharpAsyncGenericType.Assembly == _fsharpCoreAssembly; + } + else + { + // We'll keep trying to find the FSharp types/values each time any type called + // FSharpAsync`1 is supplied. + return TryPopulateFSharpValueCaches(possibleFSharpAsyncGenericType); + } + } + } + + private static bool TryPopulateFSharpValueCaches(Type possibleFSharpAsyncGenericType) + { + var assembly = possibleFSharpAsyncGenericType.Assembly; + var fsharpOptionType = assembly.GetType("Microsoft.FSharp.Core.FSharpOption`1"); + var fsharpAsyncType = assembly.GetType("Microsoft.FSharp.Control.FSharpAsync"); + + if (fsharpOptionType == null || fsharpAsyncType == null) + { + return false; + } + + // Get a reference to FSharpOption.None + var fsharpOptionOfTaskCreationOptionsType = fsharpOptionType + .MakeGenericType(typeof(TaskCreationOptions)); + _fsharpOptionOfTaskCreationOptionsNoneProperty = fsharpOptionOfTaskCreationOptionsType + .GetTypeInfo() + .GetRuntimeProperty("None"); + + // Get a reference to FSharpOption.None + var fsharpOptionOfCancellationTokenType = fsharpOptionType + .MakeGenericType(typeof(CancellationToken)); + _fsharpOptionOfCancellationTokenNoneProperty = fsharpOptionOfCancellationTokenType + .GetTypeInfo() + .GetRuntimeProperty("None"); + + // Get a reference to FSharpAsync.StartAsTask<> + var fsharpAsyncMethods = fsharpAsyncType + .GetRuntimeMethods() + .Where(m => m.Name.Equals("StartAsTask", StringComparison.Ordinal)); + foreach (var candidateMethodInfo in fsharpAsyncMethods) + { + var parameters = candidateMethodInfo.GetParameters(); + if (parameters.Length == 3 + && TypesHaveSameIdentity(parameters[0].ParameterType, possibleFSharpAsyncGenericType) + && parameters[1].ParameterType == fsharpOptionOfTaskCreationOptionsType + && parameters[2].ParameterType == fsharpOptionOfCancellationTokenType) + { + // This really does look like the correct method (and hence assembly). + _fsharpAsyncStartAsTaskGenericMethod = candidateMethodInfo; + _fsharpCoreAssembly = assembly; + break; + } + } + + return _fsharpCoreAssembly != null; + } + + private static bool TypesHaveSameIdentity(Type type1, Type type2) + { + return type1.Assembly == type2.Assembly + && string.Equals(type1.Namespace, type2.Namespace, StringComparison.Ordinal) + && string.Equals(type1.Name, type2.Name, StringComparison.Ordinal); + } + } +} From a162d4884b99a0a5e1c9952829a52ea423f03894 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Mon, 21 Aug 2017 18:34:24 +0800 Subject: [PATCH 48/68] add ConsumerInvokerFactory unit test. --- .../ConsumerInvokerFactoryTest.cs | 72 +++++++++++++++++++ test/DotNetCore.CAP.Test/Sample.cs | 12 ++++ 2 files changed, 84 insertions(+) create mode 100644 test/DotNetCore.CAP.Test/ConsumerInvokerFactoryTest.cs diff --git a/test/DotNetCore.CAP.Test/ConsumerInvokerFactoryTest.cs b/test/DotNetCore.CAP.Test/ConsumerInvokerFactoryTest.cs new file mode 100644 index 0000000..22388fd --- /dev/null +++ b/test/DotNetCore.CAP.Test/ConsumerInvokerFactoryTest.cs @@ -0,0 +1,72 @@ +using System; +using System.Linq; +using System.Reflection; +using DotNetCore.CAP.Abstractions; +using DotNetCore.CAP.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace DotNetCore.CAP.Test +{ + public class ConsumerInvokerFactoryTest + { + IConsumerInvokerFactory consumerInvokerFactory; + + public ConsumerInvokerFactoryTest() + { + var services = new ServiceCollection(); + services.AddLogging(); + var provider = services.BuildServiceProvider(); + var logFactory = provider.GetRequiredService(); + var binder = new ModelBinderFactory(); + + consumerInvokerFactory = new ConsumerInvokerFactory(logFactory, binder, provider); + } + + [Fact] + public void CreateInvokerTest() + { + var methodInfo = typeof(Sample).GetRuntimeMethods() + .Single(x => x.Name == nameof(Sample.ThrowException)); + + var description = new ConsumerExecutorDescriptor + { + MethodInfo = methodInfo, + ImplTypeInfo = typeof(Sample).GetTypeInfo() + }; + var messageContext = new MessageContext(); + + var context = new ConsumerContext(description, messageContext); + + var invoker = consumerInvokerFactory.CreateInvoker(context); + + Assert.NotNull(invoker); + } + + [Theory] + [InlineData(nameof(Sample.ThrowException))] + [InlineData(nameof(Sample.AsyncMethod))] + public async void InvokeMethodTest(string methodName) + { + var methodInfo = typeof(Sample).GetRuntimeMethods() + .Single(x => x.Name == methodName); + + var description = new ConsumerExecutorDescriptor + { + MethodInfo = methodInfo, + ImplTypeInfo = typeof(Sample).GetTypeInfo() + }; + var messageContext = new MessageContext(); + + var context = new ConsumerContext(description, messageContext); + + var invoker = consumerInvokerFactory.CreateInvoker(context); + + await Assert.ThrowsAsync(typeof(Exception), async () => + { + await invoker.InvokeAsync(); + }); + } + } +} diff --git a/test/DotNetCore.CAP.Test/Sample.cs b/test/DotNetCore.CAP.Test/Sample.cs index a383a1a..ff997fb 100644 --- a/test/DotNetCore.CAP.Test/Sample.cs +++ b/test/DotNetCore.CAP.Test/Sample.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading.Tasks; namespace DotNetCore.CAP.Test { @@ -36,6 +37,17 @@ namespace DotNetCore.CAP.Test { } + + public void ThrowException() + { + throw new Exception(); + } + + public async Task AsyncMethod() + { + await Task.FromResult(3); + throw new Exception(); + } } public class ComplexType From d143affac99e1581d8ce1d6bf67e8abae9ec2d1c Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Mon, 21 Aug 2017 18:35:06 +0800 Subject: [PATCH 49/68] delete, it not need to tests. --- .../ObjectMethodExecutorTest.cs | 90 ------------------- 1 file changed, 90 deletions(-) delete mode 100644 test/DotNetCore.CAP.Test/ObjectMethodExecutorTest.cs diff --git a/test/DotNetCore.CAP.Test/ObjectMethodExecutorTest.cs b/test/DotNetCore.CAP.Test/ObjectMethodExecutorTest.cs deleted file mode 100644 index 69fb54d..0000000 --- a/test/DotNetCore.CAP.Test/ObjectMethodExecutorTest.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Reflection; -using DotNetCore.CAP.Internal; -using Xunit; - -namespace DotNetCore.CAP.Test -{ - public class ObjectMethodExecutorTest - { - [Fact] - public void CanCreateInstance() - { - var testClass = new MethodExecutorClass(); - var methodInfo = testClass.GetType().GetMethod("Foo"); - - var executor = ObjectMethodExecutor.Create(methodInfo, typeof(MethodExecutorClass).GetTypeInfo()); - - Assert.NotNull(executor); - } - - [Fact] - public void CanExcuteMethodWithNoParameters() - { - var testClass = new MethodExecutorClass(); - var methodInfo = testClass.GetType().GetMethod("GetThree"); - - var executor = ObjectMethodExecutor.Create(methodInfo, typeof(MethodExecutorClass).GetTypeInfo()); - - Assert.NotNull(executor); - - var objResult = executor.Execute(testClass, null); - - Assert.Equal(3, objResult); - } - - [Fact] - public void CanExcuteMethodWithParameters() - { - var testClass = new MethodExecutorClass(); - var methodInfo = testClass.GetType().GetMethod("Add"); - - var executor = ObjectMethodExecutor.Create(methodInfo, typeof(MethodExecutorClass).GetTypeInfo()); - - Assert.NotNull(executor); - - var objResult = executor.Execute(testClass, 1, 2); - - Assert.Equal(3, objResult); - } - - - [Fact] - public void CanGetExcuteMethodDefaultValue() - { - var testClass = new MethodExecutorClass(); - var methodInfo = testClass.GetType().GetMethod("WithDefaultValue"); - - var executor = ObjectMethodExecutor.Create(methodInfo, typeof(MethodExecutorClass).GetTypeInfo()); - - var objResult = executor.GetDefaultValueForParameter(0); - Assert.Equal("aaa", objResult); - - var objResult2 = executor.GetDefaultValueForParameter(1); - Assert.Equal("bbb", objResult2); - } - } - - public class MethodExecutorClass - { - public void Foo() - { - } - - public int GetThree() - { - return 3; - } - - public int Add(int a, int b) - { - return a + b; - } - - public void WithDefaultValue(string aaa = "aaa", string bbb = "bbb") - { - } - } -} \ No newline at end of file From c33f4786b611f60160245acb608c2215b8c830e8 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Mon, 21 Aug 2017 18:35:27 +0800 Subject: [PATCH 50/68] add binder tests. --- test/DotNetCore.CAP.Test/ModelBinderFactoryTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/DotNetCore.CAP.Test/ModelBinderFactoryTest.cs b/test/DotNetCore.CAP.Test/ModelBinderFactoryTest.cs index accb17c..6f2a59d 100644 --- a/test/DotNetCore.CAP.Test/ModelBinderFactoryTest.cs +++ b/test/DotNetCore.CAP.Test/ModelBinderFactoryTest.cs @@ -25,8 +25,8 @@ namespace DotNetCore.CAP.Test [InlineData(nameof(Sample.UriParam))] public void CreateSimpleTypeBinderTest(string methodName) { - var datetimeMethod = typeof(Sample).GetRuntimeMethods().Single(x => x.Name == methodName); - var binder = _factory.CreateBinder(datetimeMethod.GetParameters()[0]); + var methodInfo = typeof(Sample).GetRuntimeMethods().Single(x => x.Name == methodName); + var binder = _factory.CreateBinder(methodInfo.GetParameters()[0]); Assert.NotNull(binder); Assert.True(binder is SimpleTypeModelBinder); Assert.False(binder is ComplexTypeModelBinder); @@ -36,8 +36,8 @@ namespace DotNetCore.CAP.Test [InlineData(nameof(Sample.ComplexTypeParam))] public void CreateComplexTypeBinderTest(string methodName) { - var datetimeMethod = typeof(Sample).GetRuntimeMethods().Single(x => x.Name == methodName); - var binder = _factory.CreateBinder(datetimeMethod.GetParameters()[0]); + var methodInfo = typeof(Sample).GetRuntimeMethods().Single(x => x.Name == methodName); + var binder = _factory.CreateBinder(methodInfo.GetParameters()[0]); Assert.NotNull(binder); Assert.False(binder is SimpleTypeModelBinder); Assert.True(binder is ComplexTypeModelBinder); From 750a0b1b4205367141f32d9f7cdf0948369b147e Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Mon, 21 Aug 2017 18:35:59 +0800 Subject: [PATCH 51/68] modify to used new ObjectMethodExecutor. --- .../Internal/ConsumerMethodExecutor.cs | 3 +- .../Internal/IConsumerInvoker.Default.cs | 1 + .../Internal/ObjectMethodExecutor.cs | 322 ------------------ 3 files changed, 3 insertions(+), 323 deletions(-) delete mode 100644 src/DotNetCore.CAP/Internal/ObjectMethodExecutor.cs diff --git a/src/DotNetCore.CAP/Internal/ConsumerMethodExecutor.cs b/src/DotNetCore.CAP/Internal/ConsumerMethodExecutor.cs index b33cc71..adef725 100644 --- a/src/DotNetCore.CAP/Internal/ConsumerMethodExecutor.cs +++ b/src/DotNetCore.CAP/Internal/ConsumerMethodExecutor.cs @@ -1,10 +1,11 @@ using System.Collections.Generic; +using Microsoft.Extensions.Internal; namespace DotNetCore.CAP.Internal { public class ConsumerMethodExecutor { - public static object[] PrepareArguments( + internal static object[] PrepareArguments( IDictionary actionParameters, ObjectMethodExecutor actionMethodExecutor) { diff --git a/src/DotNetCore.CAP/Internal/IConsumerInvoker.Default.cs b/src/DotNetCore.CAP/Internal/IConsumerInvoker.Default.cs index 9acca77..4118626 100644 --- a/src/DotNetCore.CAP/Internal/IConsumerInvoker.Default.cs +++ b/src/DotNetCore.CAP/Internal/IConsumerInvoker.Default.cs @@ -4,6 +4,7 @@ using DotNetCore.CAP.Abstractions; using DotNetCore.CAP.Infrastructure; using DotNetCore.CAP.Models; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Internal; using Microsoft.Extensions.Logging; namespace DotNetCore.CAP.Internal diff --git a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor.cs b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor.cs deleted file mode 100644 index cb0f99d..0000000 --- a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor.cs +++ /dev/null @@ -1,322 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Threading.Tasks; - -namespace DotNetCore.CAP.Internal -{ - public class ObjectMethodExecutor - { - private readonly object[] _parameterDefaultValues; - private readonly ConsumerMethodExecutorAsync _executorAsync; - private readonly ConsumerMethodExecutor _executor; - - private static readonly MethodInfo _convertOfTMethod = - typeof(ObjectMethodExecutor).GetRuntimeMethods() - .Single(methodInfo => methodInfo.Name == nameof(Convert)); - - private ObjectMethodExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo) - { - MethodInfo = methodInfo ?? throw new ArgumentNullException(nameof(methodInfo)); - TargetTypeInfo = targetTypeInfo; - MethodParameters = methodInfo.GetParameters(); - MethodReturnType = methodInfo.ReturnType; - IsMethodAsync = typeof(Task).IsAssignableFrom(MethodReturnType); - TaskGenericType = IsMethodAsync ? GetTaskInnerTypeOrNull(MethodReturnType) : null; - - if (IsMethodAsync && TaskGenericType != null) - { - _executor = GetExecutor(methodInfo, targetTypeInfo); - _executorAsync = GetExecutorAsync(TaskGenericType, methodInfo, targetTypeInfo); - } - else - { - _executor = GetExecutor(methodInfo, targetTypeInfo); - } - - _parameterDefaultValues = GetParameterDefaultValues(MethodParameters); - } - - private delegate Task ConsumerMethodExecutorAsync(object target, object[] parameters); - - private delegate object ConsumerMethodExecutor(object target, object[] parameters); - - private delegate void VoidActionExecutor(object target, object[] parameters); - - public MethodInfo MethodInfo { get; } - - public ParameterInfo[] MethodParameters { get; } - - public TypeInfo TargetTypeInfo { get; } - - public Type TaskGenericType { get; } - - // This field is made internal set because it is set in unit tests. - public Type MethodReturnType { get; internal set; } - - public bool IsMethodAsync { get; } - - //public bool IsTypeAssignableFromIActionResult { get; } - - public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo) - { - var executor = new ObjectMethodExecutor(methodInfo, targetTypeInfo); - return executor; - } - - public Task ExecuteAsync(object target, params object[] parameters) - { - return _executorAsync(target, parameters); - } - - public object Execute(object target, params object[] parameters) - { - return _executor(target, parameters); - } - - public object GetDefaultValueForParameter(int index) - { - if (index < 0 || index > MethodParameters.Length - 1) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - return _parameterDefaultValues[index]; - } - - private static ConsumerMethodExecutor GetExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo) - { - // Parameters to executor - var targetParameter = Expression.Parameter(typeof(object), "target"); - var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); - - // Build parameter list - var parameters = new List(); - var paramInfos = methodInfo.GetParameters(); - for (int i = 0; i < paramInfos.Length; i++) - { - var paramInfo = paramInfos[i]; - var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i)); - var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType); - - // valueCast is "(Ti) parameters[i]" - parameters.Add(valueCast); - } - - // Call method - var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType()); - var methodCall = Expression.Call(instanceCast, methodInfo, parameters); - - // methodCall is "((Ttarget) target) method((T0) parameters[0], (T1) parameters[1], ...)" - // Create function - if (methodCall.Type == typeof(void)) - { - var lambda = Expression.Lambda(methodCall, targetParameter, parametersParameter); - var voidExecutor = lambda.Compile(); - return WrapVoidAction(voidExecutor); - } - else - { - // must coerce methodCall to match ActionExecutor signature - var castMethodCall = Expression.Convert(methodCall, typeof(object)); - var lambda = - Expression.Lambda(castMethodCall, targetParameter, parametersParameter); - return lambda.Compile(); - } - } - - private static ConsumerMethodExecutor WrapVoidAction(VoidActionExecutor executor) - { - return delegate (object target, object[] parameters) - { - executor(target, parameters); - return null; - }; - } - - private static ConsumerMethodExecutorAsync GetExecutorAsync( - Type taskInnerType, - MethodInfo methodInfo, - TypeInfo targetTypeInfo) - { - // Parameters to executor - var targetParameter = Expression.Parameter(typeof(object), "target"); - var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); - - // Build parameter list - var parameters = new List(); - var paramInfos = methodInfo.GetParameters(); - for (int i = 0; i < paramInfos.Length; i++) - { - var paramInfo = paramInfos[i]; - var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i)); - var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType); - - // valueCast is "(Ti) parameters[i]" - parameters.Add(valueCast); - } - - // Call method - var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType()); - var methodCall = Expression.Call(instanceCast, methodInfo, parameters); - - var coerceMethodCall = GetCoerceMethodCallExpression(taskInnerType, methodCall, methodInfo); - - var lambda = Expression.Lambda(coerceMethodCall, - targetParameter, parametersParameter); - - return lambda.Compile(); - } - - // We need to CoerceResult as the object value returned from methodInfo.Invoke has to be cast to a Task. - // This is necessary to enable calling await on the returned task. - // i.e we need to write the following var result = await (Task)mInfo.Invoke. - // Returning Task enables us to await on the result. - private static Expression GetCoerceMethodCallExpression( - Type taskValueType, - MethodCallExpression methodCall, - MethodInfo methodInfo) - { - var castMethodCall = Expression.Convert(methodCall, typeof(object)); - var genericMethodInfo = _convertOfTMethod.MakeGenericMethod(taskValueType); - var genericMethodCall = Expression.Call(null, genericMethodInfo, castMethodCall); - var convertedResult = Expression.Convert(genericMethodCall, typeof(Task)); - return convertedResult; - } - - /// - /// Cast Task of T to Task of object - /// - private static async Task CastToObject(Task task) - { - return (object)await task; - } - - private static Type GetTaskInnerTypeOrNull(Type type) - { - var genericType = ExtractGenericInterface(type, typeof(Task<>)); - - return genericType?.GenericTypeArguments[0]; - } - - public static Type ExtractGenericInterface(Type queryType, Type interfaceType) - { - if (queryType == null) - { - throw new ArgumentNullException(nameof(queryType)); - } - - if (interfaceType == null) - { - throw new ArgumentNullException(nameof(interfaceType)); - } - - if (IsGenericInstantiation(queryType, interfaceType)) - { - // queryType matches (i.e. is a closed generic type created from) the open generic type. - return queryType; - } - - // Otherwise check all interfaces the type implements for a match. - // - If multiple different generic instantiations exists, we want the most derived one. - // - If that doesn't break the tie, then we sort alphabetically so that it's deterministic. - // - // We do this by looking at interfaces on the type, and recursing to the base type - // if we don't find any matches. - return GetGenericInstantiation(queryType, interfaceType); - } - - private static bool IsGenericInstantiation(Type candidate, Type interfaceType) - { - return - candidate.GetTypeInfo().IsGenericType && - candidate.GetGenericTypeDefinition() == interfaceType; - } - - private static Type GetGenericInstantiation(Type queryType, Type interfaceType) - { - Type bestMatch = null; - var interfaces = queryType.GetInterfaces(); - foreach (var @interface in interfaces) - { - if (IsGenericInstantiation(@interface, interfaceType)) - { - if (bestMatch == null) - { - bestMatch = @interface; - } - else if (StringComparer.Ordinal.Compare(@interface.FullName, bestMatch.FullName) < 0) - { - bestMatch = @interface; - } - else - { - // There are two matches at this level of the class hierarchy, but @interface is after - // bestMatch in the sort order. - } - } - } - - if (bestMatch != null) - { - return bestMatch; - } - - // BaseType will be null for object and interfaces, which means we've reached 'bottom'. - var baseType = queryType?.GetTypeInfo().BaseType; - if (baseType == null) - { - return null; - } - else - { - return GetGenericInstantiation(baseType, interfaceType); - } - } - - private static Task Convert(object taskAsObject) - { - var task = (Task)taskAsObject; - return CastToObject(task); - } - - private static object[] GetParameterDefaultValues(ParameterInfo[] parameters) - { - var values = new object[parameters.Length]; - - for (var i = 0; i < parameters.Length; i++) - { - var parameterInfo = parameters[i]; - object defaultValue; - - if (parameterInfo.HasDefaultValue) - { - defaultValue = parameterInfo.DefaultValue; - } - else - { - var defaultValueAttribute = parameterInfo - .GetCustomAttribute(inherit: false); - - if (defaultValueAttribute?.Value == null) - { - defaultValue = parameterInfo.ParameterType.GetTypeInfo().IsValueType - ? Activator.CreateInstance(parameterInfo.ParameterType) - : null; - } - else - { - defaultValue = defaultValueAttribute.Value; - } - } - - values[i] = defaultValue; - } - - return values; - } - } -} \ No newline at end of file From 9473d245a0ebde584c555513011ab1bce343d622 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Mon, 21 Aug 2017 21:05:13 +0800 Subject: [PATCH 52/68] fixed consumer method add to DI exception. (#38) --- src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs b/src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs index f4ca93f..06b8249 100644 --- a/src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs +++ b/src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs @@ -67,13 +67,15 @@ namespace Microsoft.Extensions.DependencyInjection private static void AddSubscribeServices(IServiceCollection services) { - var consumerListenerServices = new Dictionary(); + var consumerListenerServices = new List>(); foreach (var rejectedServices in services) { if (rejectedServices.ImplementationType != null && typeof(ICapSubscribe).IsAssignableFrom(rejectedServices.ImplementationType)) - - consumerListenerServices.Add(typeof(ICapSubscribe), rejectedServices.ImplementationType); + { + consumerListenerServices.Add(new KeyValuePair(typeof(ICapSubscribe), + rejectedServices.ImplementationType)); + } } foreach (var service in consumerListenerServices) From 978ef61d9460a5820d8cf358c412436e8192d494 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Tue, 22 Aug 2017 11:32:52 +0800 Subject: [PATCH 53/68] upgrade dependency version --- .../Sample.RabbitMQ.PostgreSql.csproj | 2 +- src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj | 2 +- test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/Sample.RabbitMQ.PostgreSql/Sample.RabbitMQ.PostgreSql.csproj b/samples/Sample.RabbitMQ.PostgreSql/Sample.RabbitMQ.PostgreSql.csproj index 44a8a52..7dba0be 100644 --- a/samples/Sample.RabbitMQ.PostgreSql/Sample.RabbitMQ.PostgreSql.csproj +++ b/samples/Sample.RabbitMQ.PostgreSql/Sample.RabbitMQ.PostgreSql.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj index 26a6926..00316dd 100644 --- a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj +++ b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj @@ -12,7 +12,7 @@ - + diff --git a/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj b/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj index dee994f..be4dae5 100644 --- a/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj +++ b/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj @@ -16,7 +16,7 @@ - + From cf3490ac415c31f7672187e80cab4a9aa9f23719 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Tue, 22 Aug 2017 16:57:13 +0800 Subject: [PATCH 54/68] delete unused fiels. --- .../Internal/ConsumerMethodExecutor.cs | 36 ------ .../Internal/ModelAttributes.cs | 104 ------------------ 2 files changed, 140 deletions(-) delete mode 100644 src/DotNetCore.CAP/Internal/ConsumerMethodExecutor.cs delete mode 100644 src/DotNetCore.CAP/Internal/ModelAttributes.cs diff --git a/src/DotNetCore.CAP/Internal/ConsumerMethodExecutor.cs b/src/DotNetCore.CAP/Internal/ConsumerMethodExecutor.cs deleted file mode 100644 index adef725..0000000 --- a/src/DotNetCore.CAP/Internal/ConsumerMethodExecutor.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections.Generic; -using Microsoft.Extensions.Internal; - -namespace DotNetCore.CAP.Internal -{ - public class ConsumerMethodExecutor - { - internal static object[] PrepareArguments( - IDictionary actionParameters, - ObjectMethodExecutor actionMethodExecutor) - { - var declaredParameterInfos = actionMethodExecutor.MethodParameters; - var count = declaredParameterInfos.Length; - if (count == 0) - { - return null; - } - - var arguments = new object[count]; - for (var index = 0; index < count; index++) - { - var parameterInfo = declaredParameterInfos[index]; - object value; - - if (!actionParameters.TryGetValue(parameterInfo.Name, out value)) - { - value = actionMethodExecutor.GetDefaultValueForParameter(index); - } - - arguments[index] = value; - } - - return arguments; - } - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Internal/ModelAttributes.cs b/src/DotNetCore.CAP/Internal/ModelAttributes.cs deleted file mode 100644 index 756f654..0000000 --- a/src/DotNetCore.CAP/Internal/ModelAttributes.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace DotNetCore.CAP.Internal -{ - /// - /// Provides access to the combined list of attributes associated a or property. - /// - public class ModelAttributes - { - /// - /// Creates a new for a . - /// - /// The set of attributes for the . - public ModelAttributes(IEnumerable typeAttributes) - { - Attributes = typeAttributes?.ToArray() ?? throw new ArgumentNullException(nameof(typeAttributes)); - TypeAttributes = Attributes; - } - - /// - /// Creates a new for a property. - /// - /// The set of attributes for the property. - /// - /// The set of attributes for the property's . See . - /// - public ModelAttributes(IEnumerable propertyAttributes, IEnumerable typeAttributes) - { - PropertyAttributes = propertyAttributes?.ToArray() - ?? throw new ArgumentNullException(nameof(propertyAttributes)); - - TypeAttributes = typeAttributes?.ToArray() - ?? throw new ArgumentNullException(nameof(typeAttributes)); - - Attributes = PropertyAttributes.Concat(TypeAttributes).ToArray(); - } - - /// - /// Gets the set of all attributes. If this instance represents the attributes for a property, the attributes - /// on the property definition are before those on the property's . - /// - public IReadOnlyList Attributes { get; } - - /// - /// Gets the set of attributes on the property, or null if this instance represents the attributes - /// for a . - /// - public IReadOnlyList PropertyAttributes { get; } - - /// - /// Gets the set of attributes on the . If this instance represents a property, - /// then contains attributes retrieved from - /// . - /// - public IReadOnlyList TypeAttributes { get; } - - /// - /// Gets the attributes for the given . - /// - /// The in which caller found . - /// - /// A for which attributes need to be resolved. - /// - /// A instance with the attributes of the property. - public static ModelAttributes GetAttributesForProperty(Type type, PropertyInfo property) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - if (property == null) - { - throw new ArgumentNullException(nameof(property)); - } - - var propertyAttributes = property.GetCustomAttributes(); - var typeAttributes = property.PropertyType.GetTypeInfo().GetCustomAttributes(); - - return new ModelAttributes(propertyAttributes, typeAttributes); - } - - /// - /// Gets the attributes for the given . - /// - /// The for which attributes need to be resolved. - /// - /// A instance with the attributes of the . - public static ModelAttributes GetAttributesForType(Type type) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - var attributes = type.GetTypeInfo().GetCustomAttributes(); - - return new ModelAttributes(attributes); - } - } -} \ No newline at end of file From d9157da124b6175454e3407411399219be53dcaf Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Tue, 22 Aug 2017 18:27:08 +0800 Subject: [PATCH 55/68] add postgresql project unit tests. --- .../ConnectionUtil.cs | 47 ++++++ .../DatabaseTestHost.cs | 79 +++++++++++ .../DotNetCore.CAP.PostgreSql.Test.csproj | 22 +++ .../PostgreSqlStorageConnectionTest.cs | 134 ++++++++++++++++++ .../PostgreSqlStorageTest.cs | 70 +++++++++ .../TestHost.cs | 97 +++++++++++++ 6 files changed, 449 insertions(+) create mode 100644 test/DotNetCore.CAP.PostgreSql.Test/ConnectionUtil.cs create mode 100644 test/DotNetCore.CAP.PostgreSql.Test/DatabaseTestHost.cs create mode 100644 test/DotNetCore.CAP.PostgreSql.Test/DotNetCore.CAP.PostgreSql.Test.csproj create mode 100644 test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageConnectionTest.cs create mode 100644 test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageTest.cs create mode 100644 test/DotNetCore.CAP.PostgreSql.Test/TestHost.cs diff --git a/test/DotNetCore.CAP.PostgreSql.Test/ConnectionUtil.cs b/test/DotNetCore.CAP.PostgreSql.Test/ConnectionUtil.cs new file mode 100644 index 0000000..21bcfa5 --- /dev/null +++ b/test/DotNetCore.CAP.PostgreSql.Test/ConnectionUtil.cs @@ -0,0 +1,47 @@ +using System; +using Npgsql; + +namespace DotNetCore.CAP.PostgreSql.Test +{ + public static class ConnectionUtil + { + private const string DatabaseVariable = "Cap_PostgreSql_DatabaseName"; + private const string ConnectionStringTemplateVariable = "Cap_PostgreSql_ConnectionStringTemplate"; + + private const string MasterDatabaseName = "master"; + private const string DefaultDatabaseName = @"DotNetCore.CAP.PostgreSql.Test"; + + private const string DefaultConnectionStringTemplate = + @"Server=192.168.2.206;Initial Catalog={0};User Id=sa;Password=123123;MultipleActiveResultSets=True"; + + 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 NpgsqlConnection CreateConnection(string connectionString = null) + { + connectionString = connectionString ?? GetConnectionString(); + var connection = new NpgsqlConnection(connectionString); + connection.Open(); + return connection; + } + } +} \ No newline at end of file diff --git a/test/DotNetCore.CAP.PostgreSql.Test/DatabaseTestHost.cs b/test/DotNetCore.CAP.PostgreSql.Test/DatabaseTestHost.cs new file mode 100644 index 0000000..3530f96 --- /dev/null +++ b/test/DotNetCore.CAP.PostgreSql.Test/DatabaseTestHost.cs @@ -0,0 +1,79 @@ +using System.Data; +using System.Data.SqlClient; +using System.Threading; +using Dapper; +using Microsoft.EntityFrameworkCore; + +namespace DotNetCore.CAP.PostgreSql.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(); + 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($@" +IF NOT EXISTS (SELECT * FROM sysdatabases WHERE name = N'{databaseName}') +CREATE DATABASE [{databaseName}];"); + } + } + + private void DeleteAllData() + { + var conn = ConnectionUtil.GetConnectionString(); + using (var connection = new SqlConnection(conn)) + { + var commands = new[] { + "DISABLE TRIGGER ALL ON ?", + "ALTER TABLE ? NOCHECK CONSTRAINT ALL", + "DELETE FROM ?", + "ALTER TABLE ? CHECK CONSTRAINT ALL", + "ENABLE TRIGGER ALL ON ?" + }; + + foreach (var command in commands) + { + connection.Execute( + "sp_MSforeachtable", + new { command1 = command }, + commandType: CommandType.StoredProcedure); + } + } + } + } +} \ No newline at end of file diff --git a/test/DotNetCore.CAP.PostgreSql.Test/DotNetCore.CAP.PostgreSql.Test.csproj b/test/DotNetCore.CAP.PostgreSql.Test/DotNetCore.CAP.PostgreSql.Test.csproj new file mode 100644 index 0000000..0f116c8 --- /dev/null +++ b/test/DotNetCore.CAP.PostgreSql.Test/DotNetCore.CAP.PostgreSql.Test.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp2.0 + + false + + + + + + + + + + + + + + + + diff --git a/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageConnectionTest.cs b/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageConnectionTest.cs new file mode 100644 index 0000000..71075ca --- /dev/null +++ b/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageConnectionTest.cs @@ -0,0 +1,134 @@ +using System; +using System.Threading.Tasks; +using Dapper; +using DotNetCore.CAP.Infrastructure; +using DotNetCore.CAP.Models; +using Xunit; + +namespace DotNetCore.CAP.PostgreSql.Test +{ + [Collection("postgresql")] + public class PostgreSqlStorageConnectionTest : DatabaseTestHost + { + private PostgreSqlStorageConnection _storage; + + public PostgreSqlStorageConnectionTest() + { + var options = GetService(); + _storage = new PostgreSqlStorageConnection(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 = "PostgreSqlStorageConnectionTest", + Content = "", + StatusName = StatusName.Scheduled + }; + var insertedId = default(int); + using (var connection = ConnectionUtil.CreateConnection()) + { + insertedId = connection.QueryFirst(sql, publishMessage); + } + var message = await _storage.GetPublishedMessageAsync(insertedId); + Assert.NotNull(message); + Assert.Equal("PostgreSqlStorageConnectionTest", 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 = "PostgreSqlStorageConnectionTest", + 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"") OUTPUT INSERTED.Id + VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; + var receivedMessage = new CapReceivedMessage + { + Name = "PostgreSqlStorageConnectionTest", + Content = "", + Group = "mygroup", + StatusName = StatusName.Scheduled + }; + var insertedId = default(int); + using (var connection = ConnectionUtil.CreateConnection()) + { + insertedId = connection.QueryFirst(sql, receivedMessage); + } + + var message = await _storage.GetReceivedMessageAsync(insertedId); + + Assert.NotNull(message); + Assert.Equal(StatusName.Scheduled, message.StatusName); + Assert.Equal("PostgreSqlStorageConnectionTest", message.Name); + Assert.Equal("mygroup", message.Group); + } + + [Fact] + public async Task GetNextReceviedMessageToBeEnqueuedAsync_Test() + { + var receivedMessage = new CapReceivedMessage + { + Name = "PostgreSqlStorageConnectionTest", + 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("PostgreSqlStorageConnectionTest", message.Name); + Assert.Equal("mygroup", message.Group); + } + + } +} diff --git a/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageTest.cs b/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageTest.cs new file mode 100644 index 0000000..b29f8ec --- /dev/null +++ b/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageTest.cs @@ -0,0 +1,70 @@ +using Xunit; +using Dapper; + +namespace DotNetCore.CAP.PostgreSql.Test +{ + //[Collection("postgresql")] + public class SqlServerStorageTest : DatabaseTestHost + { + private readonly string _dbName; + private readonly string _masterDbConnectionString; + + public SqlServerStorageTest() + { + _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(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(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(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(sql); + Assert.NotNull(result); + Assert.Equal(tableName, result); + } + } + } +} diff --git a/test/DotNetCore.CAP.PostgreSql.Test/TestHost.cs b/test/DotNetCore.CAP.PostgreSql.Test/TestHost.cs new file mode 100644 index 0000000..1da694b --- /dev/null +++ b/test/DotNetCore.CAP.PostgreSql.Test/TestHost.cs @@ -0,0 +1,97 @@ +using System; +using Microsoft.Extensions.DependencyInjection; + +namespace DotNetCore.CAP.PostgreSql.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 PostgreSqlOptions { ConnectionString = _connectionString }); + services.AddSingleton(); + + _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().CreateScope(); + return scope; + } + + public T GetService() => Provider.GetService(); + + public T Ensure(ref T service) + where T : class + => service ?? (service = GetService()); + + 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 From 817a646ee87678295988cd9452d044d0038af6bc Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Tue, 22 Aug 2017 18:27:44 +0800 Subject: [PATCH 56/68] add postgresql unit test project --- CAP.sln | 11 +++++++++-- appveyor.yml | 2 ++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CAP.sln b/CAP.sln index 45bd3bc..c81828b 100644 --- a/CAP.sln +++ b/CAP.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26730.0 +VisualStudioVersion = 15.0.26730.3 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9B2AE124-6636-4DE9-83A3-70360DABD0C4}" EndProject @@ -58,7 +58,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.RabbitMQ.SqlServer", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.PostgreSql", "src\DotNetCore.CAP.PostgreSql\DotNetCore.CAP.PostgreSql.csproj", "{82C403AB-ED68-4084-9A1D-11334F9F08F9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.RabbitMQ.PostgreSql", "samples\Sample.RabbitMQ.PostgreSql\Sample.RabbitMQ.PostgreSql.csproj", "{A17E8E72-DFFC-4822-BB38-73D59A8B264E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.RabbitMQ.PostgreSql", "samples\Sample.RabbitMQ.PostgreSql\Sample.RabbitMQ.PostgreSql.csproj", "{A17E8E72-DFFC-4822-BB38-73D59A8B264E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetCore.CAP.PostgreSql.Test", "test\DotNetCore.CAP.PostgreSql.Test\DotNetCore.CAP.PostgreSql.Test.csproj", "{7CA3625D-1817-4695-881D-7E79A1E1DED2}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -113,6 +115,10 @@ Global {A17E8E72-DFFC-4822-BB38-73D59A8B264E}.Debug|Any CPU.Build.0 = Debug|Any CPU {A17E8E72-DFFC-4822-BB38-73D59A8B264E}.Release|Any CPU.ActiveCfg = Release|Any CPU {A17E8E72-DFFC-4822-BB38-73D59A8B264E}.Release|Any CPU.Build.0 = Release|Any CPU + {7CA3625D-1817-4695-881D-7E79A1E1DED2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CA3625D-1817-4695-881D-7E79A1E1DED2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CA3625D-1817-4695-881D-7E79A1E1DED2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CA3625D-1817-4695-881D-7E79A1E1DED2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -130,6 +136,7 @@ Global {AF17B956-B79E-48B7-9B5B-EB15A386B112} = {3A6B6931-A123-477A-9469-8B468B5385AF} {82C403AB-ED68-4084-9A1D-11334F9F08F9} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} {A17E8E72-DFFC-4822-BB38-73D59A8B264E} = {3A6B6931-A123-477A-9469-8B468B5385AF} + {7CA3625D-1817-4695-881D-7E79A1E1DED2} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2E70565D-94CF-40B4-BFE1-AC18D5F736AB} diff --git a/appveyor.yml b/appveyor.yml index 6a372fb..4185499 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,9 +5,11 @@ environment: BuildEnvironment: appveyor Cap_SqlServer_ConnectionStringTemplate: Server=(local)\SQL2014;Database={0};User ID=sa;Password=Password12! Cap_MySql_ConnectionStringTemplate: Server=localhost;Database={0};Uid=root;Pwd=Password12! + Cap_PostgreSql_ConnectionStringTemplate: Server=localhost;Database={0};UserId=postgres;Password=Password12! services: - mssql2014 - mysql + - postgresql build_script: - ps: ./ConfigureMSDTC.ps1 - ps: ./build.ps1 From cb3c19b73167c025a62e32b0c05969ab49c0f6d4 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 22 Aug 2017 22:34:25 +0800 Subject: [PATCH 57/68] refactor unit tests. --- .../MySqlStorageTest.cs | 34 +++------------ .../SqlServerStorageTest.cs | 43 ++++--------------- 2 files changed, 13 insertions(+), 64 deletions(-) diff --git a/test/DotNetCore.CAP.MySql.Test/MySqlStorageTest.cs b/test/DotNetCore.CAP.MySql.Test/MySqlStorageTest.cs index 9286929..085246a 100644 --- a/test/DotNetCore.CAP.MySql.Test/MySqlStorageTest.cs +++ b/test/DotNetCore.CAP.MySql.Test/MySqlStorageTest.cs @@ -29,36 +29,12 @@ namespace DotNetCore.CAP.MySql.Test } } - [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(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(sql); - Assert.NotNull(result); - Assert.Equal(tableName, result); - } - } - - [Fact] - public void DatabaseTable_Received_IsExists() + [Theory] + [InlineData("cap.published")] + [InlineData("cap.queue")] + [InlineData("cap.received")] + public void DatabaseTable_IsExists(string tableName) { - 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}'"; diff --git a/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageTest.cs b/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageTest.cs index af1fdac..01e9aeb 100644 --- a/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageTest.cs +++ b/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageTest.cs @@ -23,49 +23,22 @@ SELECT 'False'"; } } - [Fact] - public void DatabaseTable_Published_IsExists() - { - using (var connection = ConnectionUtil.CreateConnection()) - { - var sql = @" -IF OBJECT_ID(N'[Cap].[Published]',N'U') IS NOT NULL -SELECT 'True' -ELSE -SELECT 'False'"; - var result = connection.QueryFirst(sql); - Assert.True(result); - } - } - - [Fact] - public void DatabaseTable_Queue_IsExists() + [Theory] + [InlineData("[Cap].[Published]")] + [InlineData("[Cap].[Queue]")] + [InlineData("[Cap].[Received]")] + public void DatabaseTable_IsExists(string tableName) { using (var connection = ConnectionUtil.CreateConnection()) { - var sql = @" -IF OBJECT_ID(N'[Cap].[Queue]',N'U') IS NOT NULL -SELECT 'True' -ELSE -SELECT 'False'"; - var result = connection.QueryFirst(sql); - Assert.True(result); - } - } - - [Fact] - public void DatabaseTable_Received_IsExists() - { - using (var connection = ConnectionUtil.CreateConnection()) - { - var sql = @" -IF OBJECT_ID(N'[Cap].[Received]',N'U') IS NOT NULL + var sql = $@" +IF OBJECT_ID(N'{tableName}',N'U') IS NOT NULL SELECT 'True' ELSE SELECT 'False'"; var result = connection.QueryFirst(sql); Assert.True(result); } - } + } } } From 08b3758b464547145bf4f47b38d4a9ab35407089 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 22 Aug 2017 22:34:40 +0800 Subject: [PATCH 58/68] add unit tests. --- .../ConnectionUtil.cs | 4 +- .../DatabaseTestHost.cs | 26 +++------- .../PostgreSqlStorageConnectionTest.cs | 8 +-- .../PostgreSqlStorageTest.cs | 51 +++++-------------- 4 files changed, 28 insertions(+), 61 deletions(-) diff --git a/test/DotNetCore.CAP.PostgreSql.Test/ConnectionUtil.cs b/test/DotNetCore.CAP.PostgreSql.Test/ConnectionUtil.cs index 21bcfa5..b293529 100644 --- a/test/DotNetCore.CAP.PostgreSql.Test/ConnectionUtil.cs +++ b/test/DotNetCore.CAP.PostgreSql.Test/ConnectionUtil.cs @@ -8,11 +8,11 @@ namespace DotNetCore.CAP.PostgreSql.Test private const string DatabaseVariable = "Cap_PostgreSql_DatabaseName"; private const string ConnectionStringTemplateVariable = "Cap_PostgreSql_ConnectionStringTemplate"; - private const string MasterDatabaseName = "master"; + private const string MasterDatabaseName = "postgres"; private const string DefaultDatabaseName = @"DotNetCore.CAP.PostgreSql.Test"; private const string DefaultConnectionStringTemplate = - @"Server=192.168.2.206;Initial Catalog={0};User Id=sa;Password=123123;MultipleActiveResultSets=True"; + @"Server=localhost;Database={0};UserId=postgres;Password=123123;"; public static string GetDatabaseName() { diff --git a/test/DotNetCore.CAP.PostgreSql.Test/DatabaseTestHost.cs b/test/DotNetCore.CAP.PostgreSql.Test/DatabaseTestHost.cs index 3530f96..1d99d84 100644 --- a/test/DotNetCore.CAP.PostgreSql.Test/DatabaseTestHost.cs +++ b/test/DotNetCore.CAP.PostgreSql.Test/DatabaseTestHost.cs @@ -48,31 +48,21 @@ namespace DotNetCore.CAP.PostgreSql.Test using (var connection = ConnectionUtil.CreateConnection(masterConn)) { connection.Execute($@" -IF NOT EXISTS (SELECT * FROM sysdatabases WHERE name = N'{databaseName}') -CREATE DATABASE [{databaseName}];"); +DROP DATABASE IF EXISTS ""{databaseName}""; +CREATE DATABASE ""{databaseName}"";"); } } private void DeleteAllData() { var conn = ConnectionUtil.GetConnectionString(); - using (var connection = new SqlConnection(conn)) - { - var commands = new[] { - "DISABLE TRIGGER ALL ON ?", - "ALTER TABLE ? NOCHECK CONSTRAINT ALL", - "DELETE FROM ?", - "ALTER TABLE ? CHECK CONSTRAINT ALL", - "ENABLE TRIGGER ALL ON ?" - }; - foreach (var command in commands) - { - connection.Execute( - "sp_MSforeachtable", - new { command1 = command }, - commandType: CommandType.StoredProcedure); - } + using (var connection = ConnectionUtil.CreateConnection(conn)) + { + connection.Execute($@" +TRUNCATE TABLE ""cap"".""published""; +TRUNCATE TABLE ""cap"".""received""; +TRUNCATE TABLE ""cap"".""queue"";"); } } } diff --git a/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageConnectionTest.cs b/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageConnectionTest.cs index 71075ca..89c8a37 100644 --- a/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageConnectionTest.cs +++ b/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageConnectionTest.cs @@ -21,7 +21,7 @@ namespace DotNetCore.CAP.PostgreSql.Test [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 sql = @"INSERT INTO ""cap"".""published""(""Name"",""Content"",""Retries"",""Added"",""ExpiresAt"",""StatusName"") VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName) RETURNING ""Id"";"; var publishMessage = new CapPublishedMessage { Name = "PostgreSqlStorageConnectionTest", @@ -42,7 +42,7 @@ namespace DotNetCore.CAP.PostgreSql.Test [Fact] public async Task FetchNextMessageAsync_Test() { - var sql = @"INSERT INTO ""Cap"".""Queue""(""MessageId"",""MessageType"") VALUES(@MessageId,@MessageType);"; + var sql = @"INSERT INTO ""cap"".""queue""(""MessageId"",""MessageType"") VALUES(@MessageId,@MessageType);"; var queue = new CapQueue { MessageId = 3333, @@ -87,8 +87,8 @@ namespace DotNetCore.CAP.PostgreSql.Test { var sql = $@" - INSERT INTO ""Cap"".""Received""(""Name"",""Group"",""Content"",""Retries"",""Added"",""ExpiresAt"",""StatusName"") OUTPUT INSERTED.Id - VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; + INSERT INTO ""cap"".""received""(""Name"",""Group"",""Content"",""Retries"",""Added"",""ExpiresAt"",""StatusName"") + VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName) RETURNING ""Id"";"; var receivedMessage = new CapReceivedMessage { Name = "PostgreSqlStorageConnectionTest", diff --git a/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageTest.cs b/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageTest.cs index b29f8ec..bb8a309 100644 --- a/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageTest.cs +++ b/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageTest.cs @@ -3,67 +3,44 @@ using Dapper; namespace DotNetCore.CAP.PostgreSql.Test { - //[Collection("postgresql")] + [Collection("postgresql")] public class SqlServerStorageTest : DatabaseTestHost { private readonly string _dbName; private readonly string _masterDbConnectionString; + private readonly string _dbConnectionString; public SqlServerStorageTest() { _dbName = ConnectionUtil.GetDatabaseName(); _masterDbConnectionString = ConnectionUtil.GetMasterConnectionString(); + _dbConnectionString = ConnectionUtil.GetConnectionString(); } - //[Fact] + [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 sql = $@"select * from pg_database where datname = '{databaseName}'"; var result = connection.QueryFirstOrDefault(sql); Assert.NotNull(result); Assert.True(databaseName.Equals(result, System.StringComparison.CurrentCultureIgnoreCase)); } } - //[Fact] - public void DatabaseTable_Published_IsExists() + [Theory] + [InlineData("cap.published")] + [InlineData("cap.queue")] + [InlineData("cap.received")] + public void DatabaseTable_IsExists(string tableName) { - 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(sql); - Assert.NotNull(result); - Assert.Equal(tableName, result); - } - } - - //[Fact] - public void DatabaseTable_Queue_IsExists() - { - var tableName = "cap.queue"; - using (var connection = ConnectionUtil.CreateConnection(_masterDbConnectionString)) + using (var connection = ConnectionUtil.CreateConnection(_dbConnectionString)) { - var sql = $"SELECT TABLE_NAME FROM `TABLES` WHERE TABLE_SCHEMA='{_dbName}' AND TABLE_NAME = '{tableName}'"; - var result = connection.QueryFirstOrDefault(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(sql); - Assert.NotNull(result); - Assert.Equal(tableName, result); + var sql = $"SELECT to_regclass('{tableName}') is not null;"; + var result = connection.QueryFirstOrDefault(sql); + Assert.True(result); } } } From 2b6c54c445795782b8b0c8ee491fcc9a0abd6176 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Wed, 23 Aug 2017 18:11:06 +0800 Subject: [PATCH 59/68] clearup --- test/DotNetCore.CAP.MySql.Test/DatabaseTestHost.cs | 1 - .../MySqlStorageConnectionTest.cs | 4 +--- test/DotNetCore.CAP.MySql.Test/MySqlStorageTest.cs | 7 +++---- test/DotNetCore.CAP.MySql.Test/TestHost.cs | 1 - .../DatabaseTestHost.cs | 2 -- .../PostgreSqlStorageConnectionTest.cs | 4 +--- .../PostgreSqlStorageTest.cs | 8 ++++---- test/DotNetCore.CAP.PostgreSql.Test/TestHost.cs | 2 +- .../SqlServerStorageConnectionTest.cs | 4 +--- .../SqlServerStorageTest.cs | 10 +++++----- test/DotNetCore.CAP.SqlServer.Test/TestHost.cs | 2 +- test/DotNetCore.CAP.Test/CAP.BuilderTest.cs | 3 +-- .../ConsumerInvokerFactoryTest.cs | 6 +++--- .../ConsumerServiceSelectorTest.cs | 5 +---- test/DotNetCore.CAP.Test/ModelBinderFactoryTest.cs | 10 +++------- .../Processor/DefaultDispatcherTest.cs | 14 +++++--------- .../Processor/StateChangerTest.cs | 2 +- .../QueueExecutorFactoryTest.cs | 7 +------ test/DotNetCore.CAP.Test/Sample.cs | 11 +---------- test/DotNetCore.CAP.Test/SubscribeFinderTest.cs | 7 +------ 20 files changed, 34 insertions(+), 76 deletions(-) diff --git a/test/DotNetCore.CAP.MySql.Test/DatabaseTestHost.cs b/test/DotNetCore.CAP.MySql.Test/DatabaseTestHost.cs index bd858e5..1fac1b6 100644 --- a/test/DotNetCore.CAP.MySql.Test/DatabaseTestHost.cs +++ b/test/DotNetCore.CAP.MySql.Test/DatabaseTestHost.cs @@ -1,4 +1,3 @@ -using System.Data; using System.Threading; using Dapper; using Microsoft.EntityFrameworkCore; diff --git a/test/DotNetCore.CAP.MySql.Test/MySqlStorageConnectionTest.cs b/test/DotNetCore.CAP.MySql.Test/MySqlStorageConnectionTest.cs index b83588e..b3df31f 100644 --- a/test/DotNetCore.CAP.MySql.Test/MySqlStorageConnectionTest.cs +++ b/test/DotNetCore.CAP.MySql.Test/MySqlStorageConnectionTest.cs @@ -85,7 +85,6 @@ namespace DotNetCore.CAP.MySql.Test [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;"; @@ -129,6 +128,5 @@ namespace DotNetCore.CAP.MySql.Test Assert.Equal("MySqlStorageConnectionTest", message.Name); Assert.Equal("mygroup", message.Group); } - } -} +} \ No newline at end of file diff --git a/test/DotNetCore.CAP.MySql.Test/MySqlStorageTest.cs b/test/DotNetCore.CAP.MySql.Test/MySqlStorageTest.cs index 085246a..715cb5c 100644 --- a/test/DotNetCore.CAP.MySql.Test/MySqlStorageTest.cs +++ b/test/DotNetCore.CAP.MySql.Test/MySqlStorageTest.cs @@ -1,5 +1,5 @@ -using Xunit; -using Dapper; +using Dapper; +using Xunit; namespace DotNetCore.CAP.MySql.Test { @@ -9,7 +9,6 @@ namespace DotNetCore.CAP.MySql.Test private readonly string _dbName; private readonly string _masterDbConnectionString; - public MySqlStorageTest() { _dbName = ConnectionUtil.GetDatabaseName(); @@ -44,4 +43,4 @@ namespace DotNetCore.CAP.MySql.Test } } } -} +} \ No newline at end of file diff --git a/test/DotNetCore.CAP.MySql.Test/TestHost.cs b/test/DotNetCore.CAP.MySql.Test/TestHost.cs index c8290ad..06dcba9 100644 --- a/test/DotNetCore.CAP.MySql.Test/TestHost.cs +++ b/test/DotNetCore.CAP.MySql.Test/TestHost.cs @@ -1,5 +1,4 @@ using System; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; namespace DotNetCore.CAP.MySql.Test diff --git a/test/DotNetCore.CAP.PostgreSql.Test/DatabaseTestHost.cs b/test/DotNetCore.CAP.PostgreSql.Test/DatabaseTestHost.cs index 1d99d84..66ae8e1 100644 --- a/test/DotNetCore.CAP.PostgreSql.Test/DatabaseTestHost.cs +++ b/test/DotNetCore.CAP.PostgreSql.Test/DatabaseTestHost.cs @@ -1,5 +1,3 @@ -using System.Data; -using System.Data.SqlClient; using System.Threading; using Dapper; using Microsoft.EntityFrameworkCore; diff --git a/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageConnectionTest.cs b/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageConnectionTest.cs index 89c8a37..df68d0b 100644 --- a/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageConnectionTest.cs +++ b/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageConnectionTest.cs @@ -85,7 +85,6 @@ namespace DotNetCore.CAP.PostgreSql.Test [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) RETURNING ""Id"";"; @@ -129,6 +128,5 @@ namespace DotNetCore.CAP.PostgreSql.Test Assert.Equal("PostgreSqlStorageConnectionTest", message.Name); Assert.Equal("mygroup", message.Group); } - } -} +} \ No newline at end of file diff --git a/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageTest.cs b/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageTest.cs index bb8a309..c4f5748 100644 --- a/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageTest.cs +++ b/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageTest.cs @@ -1,5 +1,5 @@ -using Xunit; -using Dapper; +using Dapper; +using Xunit; namespace DotNetCore.CAP.PostgreSql.Test { @@ -39,9 +39,9 @@ namespace DotNetCore.CAP.PostgreSql.Test using (var connection = ConnectionUtil.CreateConnection(_dbConnectionString)) { var sql = $"SELECT to_regclass('{tableName}') is not null;"; - var result = connection.QueryFirstOrDefault(sql); + var result = connection.QueryFirstOrDefault(sql); Assert.True(result); } } } -} +} \ No newline at end of file diff --git a/test/DotNetCore.CAP.PostgreSql.Test/TestHost.cs b/test/DotNetCore.CAP.PostgreSql.Test/TestHost.cs index 1da694b..4bff8cb 100644 --- a/test/DotNetCore.CAP.PostgreSql.Test/TestHost.cs +++ b/test/DotNetCore.CAP.PostgreSql.Test/TestHost.cs @@ -29,7 +29,7 @@ namespace DotNetCore.CAP.PostgreSql.Test _connectionString = ConnectionUtil.GetConnectionString(); services.AddSingleton(new PostgreSqlOptions { ConnectionString = _connectionString }); - services.AddSingleton(); + services.AddSingleton(); _services = services; } diff --git a/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageConnectionTest.cs b/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageConnectionTest.cs index bd0bab4..b081d1a 100644 --- a/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageConnectionTest.cs +++ b/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageConnectionTest.cs @@ -85,7 +85,6 @@ namespace DotNetCore.CAP.SqlServer.Test [Fact] public async Task GetReceivedMessageAsync_Test() { - var sql = $@" INSERT INTO [Cap].[Received]([Name],[Group],[Content],[Retries],[Added],[ExpiresAt],[StatusName]) OUTPUT INSERTED.Id VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; @@ -129,6 +128,5 @@ namespace DotNetCore.CAP.SqlServer.Test Assert.Equal("SqlServerStorageConnectionTest", message.Name); Assert.Equal("mygroup", message.Group); } - } -} +} \ No newline at end of file diff --git a/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageTest.cs b/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageTest.cs index 01e9aeb..af5fc41 100644 --- a/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageTest.cs +++ b/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageTest.cs @@ -1,5 +1,5 @@ -using Xunit; -using Dapper; +using Dapper; +using Xunit; namespace DotNetCore.CAP.SqlServer.Test { @@ -14,7 +14,7 @@ namespace DotNetCore.CAP.SqlServer.Test { var databaseName = ConnectionUtil.GetDatabaseName(); var sql = $@" -IF EXISTS (SELECT * FROM sysdatabases WHERE name = N'{databaseName}') +IF EXISTS (SELECT * FROM sysdatabases WHERE name = N'{databaseName}') SELECT 'True' ELSE SELECT 'False'"; @@ -39,6 +39,6 @@ SELECT 'False'"; var result = connection.QueryFirst(sql); Assert.True(result); } - } + } } -} +} \ No newline at end of file diff --git a/test/DotNetCore.CAP.SqlServer.Test/TestHost.cs b/test/DotNetCore.CAP.SqlServer.Test/TestHost.cs index 5922c17..d8618af 100644 --- a/test/DotNetCore.CAP.SqlServer.Test/TestHost.cs +++ b/test/DotNetCore.CAP.SqlServer.Test/TestHost.cs @@ -29,7 +29,7 @@ namespace DotNetCore.CAP.SqlServer.Test _connectionString = ConnectionUtil.GetConnectionString(); services.AddSingleton(new SqlServerOptions { ConnectionString = _connectionString }); - services.AddSingleton(); + services.AddSingleton(); _services = services; } diff --git a/test/DotNetCore.CAP.Test/CAP.BuilderTest.cs b/test/DotNetCore.CAP.Test/CAP.BuilderTest.cs index 69968de..a151d72 100644 --- a/test/DotNetCore.CAP.Test/CAP.BuilderTest.cs +++ b/test/DotNetCore.CAP.Test/CAP.BuilderTest.cs @@ -1,8 +1,8 @@ using System; +using System.Data; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Xunit; -using System.Data; namespace DotNetCore.CAP.Test { @@ -36,7 +36,6 @@ namespace DotNetCore.CAP.Test Assert.NotNull(markService); } - [Fact] public void CanOverridePublishService() { diff --git a/test/DotNetCore.CAP.Test/ConsumerInvokerFactoryTest.cs b/test/DotNetCore.CAP.Test/ConsumerInvokerFactoryTest.cs index 22388fd..bf070fe 100644 --- a/test/DotNetCore.CAP.Test/ConsumerInvokerFactoryTest.cs +++ b/test/DotNetCore.CAP.Test/ConsumerInvokerFactoryTest.cs @@ -11,7 +11,7 @@ namespace DotNetCore.CAP.Test { public class ConsumerInvokerFactoryTest { - IConsumerInvokerFactory consumerInvokerFactory; + private IConsumerInvokerFactory consumerInvokerFactory; public ConsumerInvokerFactoryTest() { @@ -67,6 +67,6 @@ namespace DotNetCore.CAP.Test { await invoker.InvokeAsync(); }); - } + } } -} +} \ No newline at end of file diff --git a/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs b/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs index 97dd29b..5ed56e1 100644 --- a/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs +++ b/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; using DotNetCore.CAP.Abstractions; -using DotNetCore.CAP.Internal; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -20,7 +17,7 @@ namespace DotNetCore.CAP.Test services.AddScoped(); services.AddScoped(); services.AddLogging(); - services.AddCap(x=> { }); + services.AddCap(x => { }); _provider = services.BuildServiceProvider(); } diff --git a/test/DotNetCore.CAP.Test/ModelBinderFactoryTest.cs b/test/DotNetCore.CAP.Test/ModelBinderFactoryTest.cs index 6f2a59d..946db42 100644 --- a/test/DotNetCore.CAP.Test/ModelBinderFactoryTest.cs +++ b/test/DotNetCore.CAP.Test/ModelBinderFactoryTest.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Linq; +using System.Linq; using System.Reflection; using DotNetCore.CAP.Internal; using Xunit; @@ -10,7 +7,7 @@ namespace DotNetCore.CAP.Test { public class ModelBinderFactoryTest { - IModelBinderFactory _factory; + private IModelBinderFactory _factory; public ModelBinderFactoryTest() { @@ -42,6 +39,5 @@ namespace DotNetCore.CAP.Test Assert.False(binder is SimpleTypeModelBinder); Assert.True(binder is ComplexTypeModelBinder); } - } -} +} \ No newline at end of file diff --git a/test/DotNetCore.CAP.Test/Processor/DefaultDispatcherTest.cs b/test/DotNetCore.CAP.Test/Processor/DefaultDispatcherTest.cs index 7ff1d19..8ccdbf0 100644 --- a/test/DotNetCore.CAP.Test/Processor/DefaultDispatcherTest.cs +++ b/test/DotNetCore.CAP.Test/Processor/DefaultDispatcherTest.cs @@ -1,12 +1,8 @@ using System; -using System.Collections.Generic; -using System.Text; using System.Threading; using System.Threading.Tasks; -using DotNetCore.CAP.Infrastructure; using DotNetCore.CAP.Models; using DotNetCore.CAP.Processor; -using DotNetCore.CAP.Processor.States; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Moq; @@ -64,12 +60,12 @@ namespace DotNetCore.CAP.Test public async Task ProcessAsync() { // Arrange - var job = new CapPublishedMessage { - + var job = new CapPublishedMessage + { }; - var mockFetchedJob = Mock.Get(Mock.Of(fj => fj.MessageId == 42 && fj.MessageType == MessageType.Publish )); - + var mockFetchedJob = Mock.Get(Mock.Of(fj => fj.MessageId == 42 && fj.MessageType == MessageType.Publish)); + _mockStorageConnection .Setup(m => m.FetchNextMessageAsync()) .ReturnsAsync(mockFetchedJob.Object).Verifiable(); @@ -84,7 +80,7 @@ namespace DotNetCore.CAP.Test await fixture.ProcessAsync(_context); // Assert - _mockStorageConnection.VerifyAll(); + _mockStorageConnection.VerifyAll(); } private DefaultDispatcher Create() diff --git a/test/DotNetCore.CAP.Test/Processor/StateChangerTest.cs b/test/DotNetCore.CAP.Test/Processor/StateChangerTest.cs index b57692b..9ff5208 100644 --- a/test/DotNetCore.CAP.Test/Processor/StateChangerTest.cs +++ b/test/DotNetCore.CAP.Test/Processor/StateChangerTest.cs @@ -56,4 +56,4 @@ namespace DotNetCore.CAP.Test private StateChanger Create() => new StateChanger(); } -} +} \ No newline at end of file diff --git a/test/DotNetCore.CAP.Test/QueueExecutorFactoryTest.cs b/test/DotNetCore.CAP.Test/QueueExecutorFactoryTest.cs index a5fef69..7292832 100644 --- a/test/DotNetCore.CAP.Test/QueueExecutorFactoryTest.cs +++ b/test/DotNetCore.CAP.Test/QueueExecutorFactoryTest.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -41,8 +39,5 @@ namespace DotNetCore.CAP.Test var publishExecutor = queueExecutorFactory.GetInstance(Models.MessageType.Publish); Assert.Null(publishExecutor); } - - - } -} +} \ No newline at end of file diff --git a/test/DotNetCore.CAP.Test/Sample.cs b/test/DotNetCore.CAP.Test/Sample.cs index ff997fb..f043298 100644 --- a/test/DotNetCore.CAP.Test/Sample.cs +++ b/test/DotNetCore.CAP.Test/Sample.cs @@ -1,41 +1,32 @@ using System; -using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; namespace DotNetCore.CAP.Test { public class Sample { - public void DateTimeParam(DateTime dateTime) { - } public void StringParam(string @string) { - } public void GuidParam(Guid guid) { - } public void UriParam(Uri uri) { - } public void IntegerParam(int @int) { - } public void ComplexTypeParam(ComplexType complexType) { - } public void ThrowException() @@ -67,4 +58,4 @@ namespace DotNetCore.CAP.Test public string Name { get; set; } } -} +} \ No newline at end of file diff --git a/test/DotNetCore.CAP.Test/SubscribeFinderTest.cs b/test/DotNetCore.CAP.Test/SubscribeFinderTest.cs index 54e2052..684101a 100644 --- a/test/DotNetCore.CAP.Test/SubscribeFinderTest.cs +++ b/test/DotNetCore.CAP.Test/SubscribeFinderTest.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; using DotNetCore.CAP.Abstractions; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -22,7 +20,6 @@ namespace DotNetCore.CAP.Test [Fact] public void CanFindControllers() { - } [Fact] @@ -36,7 +33,6 @@ namespace DotNetCore.CAP.Test public class HomeController { - } public interface ITestService { } @@ -46,7 +42,6 @@ namespace DotNetCore.CAP.Test [CapSubscribe("test")] public void Index() { - } } @@ -56,4 +51,4 @@ namespace DotNetCore.CAP.Test { } } -} +} \ No newline at end of file From 583f6c5484eb0828ebe28b0885adaa96e6516003 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Tue, 5 Sep 2017 18:48:52 +0800 Subject: [PATCH 60/68] fix bug #44 --- .gitignore | 4 +++ samples/Sample.RabbitMQ.SqlServer/Program.cs | 31 ++++++++++++------- .../CAP.SqlServerCapOptionsExtension.cs | 14 +++++++-- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index d7fe0b1..af4f8ef 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,7 @@ bin/ /.idea Properties /pack.bat +/src/DotNetCore.CAP/project.json +/src/DotNetCore.CAP/packages.config +/src/DotNetCore.CAP/DotNetCore.CAP.Net47.csproj +/NuGet.config diff --git a/samples/Sample.RabbitMQ.SqlServer/Program.cs b/samples/Sample.RabbitMQ.SqlServer/Program.cs index 2393f73..ed4ff09 100644 --- a/samples/Sample.RabbitMQ.SqlServer/Program.cs +++ b/samples/Sample.RabbitMQ.SqlServer/Program.cs @@ -1,4 +1,5 @@ using System.IO; +using Microsoft.AspNetCore; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -7,22 +8,30 @@ namespace Sample.RabbitMQ.SqlServer { public class Program { + + //var config = new ConfigurationBuilder() + // .AddCommandLine(args) + // .AddEnvironmentVariables("ASPNETCORE_") + // .Build(); + + //var host = new WebHostBuilder() + // .UseConfiguration(config) + // .UseKestrel() + // .UseContentRoot(Directory.GetCurrentDirectory()) + // .UseIISIntegration() + // .UseStartup() + // .Build(); + + //host.Run(); public static void Main(string[] args) { - var config = new ConfigurationBuilder() - .AddCommandLine(args) - .AddEnvironmentVariables("ASPNETCORE_") - .Build(); + BuildWebHost(args).Run(); + } - var host = new WebHostBuilder() - .UseConfiguration(config) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() + public static IWebHost BuildWebHost(string[] args) => + WebHost.CreateDefaultBuilder(args) .UseStartup() .Build(); - host.Run(); - } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs b/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs index 3053d76..7aac2e1 100644 --- a/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs @@ -23,7 +23,11 @@ namespace DotNetCore.CAP services.AddScoped(); services.AddTransient(); services.AddTransient(); + AddSqlServerOptions(services); + } + private void AddSqlServerOptions(IServiceCollection services) + { var sqlServerOptions = new SqlServerOptions(); _configure(sqlServerOptions); @@ -32,9 +36,13 @@ namespace DotNetCore.CAP { services.AddSingleton(x => { - var dbContext = (DbContext)x.GetService(sqlServerOptions.DbContextType); - sqlServerOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString; - return sqlServerOptions; + using (var scope = x.CreateScope()) + { + var provider = scope.ServiceProvider; + var dbContext = (DbContext)provider.GetService(sqlServerOptions.DbContextType); + sqlServerOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString; + return sqlServerOptions; + } }); } else From a8ba769a2f7455e88763466f34b65518133012bf Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 5 Sep 2017 22:11:42 +0800 Subject: [PATCH 61/68] move controller services finder to DefaultConsumerServiceSelector --- .../CAP.ServiceCollectionExtensions.cs | 9 --------- .../IConsumerServiceSelector.Default.cs | 19 +++++++++---------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs b/src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs index 06b8249..51f645b 100644 --- a/src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs +++ b/src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs @@ -82,15 +82,6 @@ namespace Microsoft.Extensions.DependencyInjection { services.AddTransient(service.Key, service.Value); } - - var types = Assembly.GetEntryAssembly().ExportedTypes; - foreach (var type in types) - { - if (Helper.IsController(type.GetTypeInfo())) - { - services.AddTransient(typeof(object), type); - } - } } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs b/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs index 4746b5c..6bf8795 100644 --- a/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs +++ b/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs @@ -67,17 +67,16 @@ namespace DotNetCore.CAP.Internal IServiceProvider provider) { var executorDescriptorList = new List(); - // at cap startup time, find all Controller into the DI container,the type is object. - var controllers = provider.GetServices(); - foreach (var controller in controllers) - { - var typeInfo = controller.GetType().GetTypeInfo(); - - //double check - if (!Helper.IsController(typeInfo)) continue; - executorDescriptorList.AddRange(GetTopicAttributesDescription(typeInfo)); - } + var types = Assembly.GetEntryAssembly().ExportedTypes; + foreach (var type in types) + { + var typeInfo = type.GetTypeInfo(); + if (Helper.IsController(typeInfo)) + { + executorDescriptorList.AddRange(GetTopicAttributesDescription(typeInfo)); + } + } return executorDescriptorList; } From b9c3a536f0548c23b5fe56aa42523c5673894ac9 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 5 Sep 2017 22:13:11 +0800 Subject: [PATCH 62/68] fixed dependency injection bugs. --- src/DotNetCore.CAP.Kafka/CAP.KafkaCapOptionsExtension.cs | 2 +- .../CAP.RabbitMQCapOptionsExtension.cs | 3 +-- src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs | 9 +++++---- .../RabbitMQConsumerClientFactory.cs | 4 ++-- .../CAP.SqlServerCapOptionsExtension.cs | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/DotNetCore.CAP.Kafka/CAP.KafkaCapOptionsExtension.cs b/src/DotNetCore.CAP.Kafka/CAP.KafkaCapOptionsExtension.cs index 8d9bf98..5c9b6ba 100644 --- a/src/DotNetCore.CAP.Kafka/CAP.KafkaCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.Kafka/CAP.KafkaCapOptionsExtension.cs @@ -21,7 +21,7 @@ namespace DotNetCore.CAP services.AddSingleton(kafkaOptions); services.AddSingleton(); - services.AddTransient(); + services.AddSingleton(); } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.RabbitMQ/CAP.RabbitMQCapOptionsExtension.cs b/src/DotNetCore.CAP.RabbitMQ/CAP.RabbitMQCapOptionsExtension.cs index 22e2af2..abc6d2b 100644 --- a/src/DotNetCore.CAP.RabbitMQ/CAP.RabbitMQCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.RabbitMQ/CAP.RabbitMQCapOptionsExtension.cs @@ -23,9 +23,8 @@ namespace DotNetCore.CAP services.AddSingleton(); services.AddSingleton(); - services.AddScoped(x => x.GetService().Rent()); - services.AddTransient(); + services.AddSingleton(); } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs b/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs index 791985d..ee49ce9 100644 --- a/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs +++ b/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs @@ -10,19 +10,19 @@ namespace DotNetCore.CAP.RabbitMQ internal sealed class PublishQueueExecutor : BasePublishQueueExecutor { private readonly ILogger _logger; - private readonly IConnection _connection; + private readonly ConnectionPool _connectionPool; private readonly RabbitMQOptions _rabbitMQOptions; public PublishQueueExecutor( CapOptions options, IStateChanger stateChanger, - IConnection connection, + ConnectionPool connectionPool, RabbitMQOptions rabbitMQOptions, ILogger logger) : base(options, stateChanger, logger) { _logger = logger; - _connection = connection; + _connectionPool = connectionPool; _rabbitMQOptions = rabbitMQOptions; } @@ -30,7 +30,8 @@ namespace DotNetCore.CAP.RabbitMQ { try { - using (var channel = _connection.CreateModel()) + var connection = _connectionPool.Rent(); + using (var channel = connection.CreateModel()) { var body = Encoding.UTF8.GetBytes(content); diff --git a/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs b/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs index 5fc9d8f..252b865 100644 --- a/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs +++ b/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs @@ -9,10 +9,10 @@ namespace DotNetCore.CAP.RabbitMQ private readonly IConnection _connection; - public RabbitMQConsumerClientFactory(RabbitMQOptions rabbitMQOptions, IConnection connection) + public RabbitMQConsumerClientFactory(RabbitMQOptions rabbitMQOptions, ConnectionPool pool) { _rabbitMQOptions = rabbitMQOptions; - _connection = connection; + _connection = pool.Rent(); } public IConsumerClient Create(string groupId) diff --git a/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs b/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs index 7aac2e1..f928031 100644 --- a/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs @@ -19,8 +19,8 @@ namespace DotNetCore.CAP public void AddServices(IServiceCollection services) { services.AddSingleton(); - services.AddScoped(); - services.AddScoped(); + services.AddSingleton(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); AddSqlServerOptions(services); From 52085b2973f6f5476c9e3ee43ee858b7e7384b21 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 5 Sep 2017 22:13:19 +0800 Subject: [PATCH 63/68] refactor. --- src/DotNetCore.CAP/IBootstrapper.Default.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/DotNetCore.CAP/IBootstrapper.Default.cs b/src/DotNetCore.CAP/IBootstrapper.Default.cs index 195916b..e4a75d5 100644 --- a/src/DotNetCore.CAP/IBootstrapper.Default.cs +++ b/src/DotNetCore.CAP/IBootstrapper.Default.cs @@ -25,14 +25,13 @@ namespace DotNetCore.CAP IOptions options, IStorage storage, IApplicationLifetime appLifetime, - IServiceProvider provider) + IEnumerable servers) { _logger = logger; _appLifetime = appLifetime; Options = options.Value; Storage = storage; - Provider = provider; - Servers = Provider.GetServices(); + Servers = servers; _cts = new CancellationTokenSource(); _ctsRegistration = appLifetime.ApplicationStopping.Register(() => @@ -55,8 +54,6 @@ namespace DotNetCore.CAP protected IEnumerable Servers { get; } - public IServiceProvider Provider { get; private set; } - public Task BootstrapAsync() { return (_bootstrappingTask = BootstrapTaskAsync()); From d646c5180e9c28bddb7a631979c4921fef0074ba Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 5 Sep 2017 22:16:35 +0800 Subject: [PATCH 64/68] set version number to 2.0.1 --- build/version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/version.props b/build/version.props index 604d215..6c03b29 100644 --- a/build/version.props +++ b/build/version.props @@ -2,7 +2,7 @@ 2 0 - 0 + 1 $(VersionMajor).$(VersionMinor).$(VersionPatch) From 975fcb7de54122cb263addbf93d6c59e29b24166 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Thu, 7 Sep 2017 20:54:22 +0800 Subject: [PATCH 65/68] fixed not return connection to pool bug. --- src/DotNetCore.CAP.RabbitMQ/ConnectionPool.cs | 2 +- src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs | 7 ++++++- .../RabbitMQConsumerClient.cs | 13 ++++++++----- .../RabbitMQConsumerClientFactory.cs | 6 +++--- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/DotNetCore.CAP.RabbitMQ/ConnectionPool.cs b/src/DotNetCore.CAP.RabbitMQ/ConnectionPool.cs index 83116bf..5db81e7 100644 --- a/src/DotNetCore.CAP.RabbitMQ/ConnectionPool.cs +++ b/src/DotNetCore.CAP.RabbitMQ/ConnectionPool.cs @@ -8,7 +8,7 @@ namespace DotNetCore.CAP.RabbitMQ { public class ConnectionPool : IConnectionPool, IDisposable { - private const int DefaultPoolSize = 32; + private const int DefaultPoolSize = 15; private readonly ConcurrentQueue _pool = new ConcurrentQueue(); diff --git a/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs b/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs index ee49ce9..0a47501 100644 --- a/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs +++ b/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs @@ -28,9 +28,10 @@ namespace DotNetCore.CAP.RabbitMQ public override Task PublishAsync(string keyName, string content) { + var connection = _connectionPool.Rent(); + try { - var connection = _connectionPool.Rent(); using (var channel = connection.CreateModel()) { var body = Encoding.UTF8.GetBytes(content); @@ -56,6 +57,10 @@ namespace DotNetCore.CAP.RabbitMQ Description = ex.Message })); } + finally + { + _connectionPool.Return(connection); + } } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClient.cs b/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClient.cs index a6720fd..0172c2b 100644 --- a/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClient.cs +++ b/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClient.cs @@ -14,7 +14,7 @@ namespace DotNetCore.CAP.RabbitMQ private readonly string _queueName; private readonly RabbitMQOptions _rabbitMQOptions; - private IConnection _connection; + private ConnectionPool _connectionPool; private IModel _channel; private ulong _deliveryTag; @@ -23,11 +23,11 @@ namespace DotNetCore.CAP.RabbitMQ public event EventHandler OnError; public RabbitMQConsumerClient(string queueName, - IConnection connection, + ConnectionPool connectionPool, RabbitMQOptions options) { _queueName = queueName; - _connection = connection; + _connectionPool = connectionPool; _rabbitMQOptions = options; _exchageName = options.TopicExchangeName; @@ -36,7 +36,9 @@ namespace DotNetCore.CAP.RabbitMQ private void InitClient() { - _channel = _connection.CreateModel(); + var connection = _connectionPool.Rent(); + + _channel = connection.CreateModel(); _channel.ExchangeDeclare( exchange: _exchageName, @@ -49,6 +51,8 @@ namespace DotNetCore.CAP.RabbitMQ exclusive: false, autoDelete: false, arguments: arguments); + + _connectionPool.Return(connection); } public void Subscribe(IEnumerable topics) @@ -81,7 +85,6 @@ namespace DotNetCore.CAP.RabbitMQ public void Dispose() { _channel.Dispose(); - _connection.Dispose(); } private void OnConsumerReceived(object sender, BasicDeliverEventArgs e) diff --git a/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs b/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs index 252b865..753fc05 100644 --- a/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs +++ b/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs @@ -6,18 +6,18 @@ namespace DotNetCore.CAP.RabbitMQ internal sealed class RabbitMQConsumerClientFactory : IConsumerClientFactory { private readonly RabbitMQOptions _rabbitMQOptions; - private readonly IConnection _connection; + private readonly ConnectionPool _connectionPool; public RabbitMQConsumerClientFactory(RabbitMQOptions rabbitMQOptions, ConnectionPool pool) { _rabbitMQOptions = rabbitMQOptions; - _connection = pool.Rent(); + _connectionPool = pool; } public IConsumerClient Create(string groupId) { - return new RabbitMQConsumerClient(groupId, _connection, _rabbitMQOptions); + return new RabbitMQConsumerClient(groupId, _connectionPool, _rabbitMQOptions); } } } \ No newline at end of file From 725a3a45f233b55cf7fb470aed4f354039f324e8 Mon Sep 17 00:00:00 2001 From: yangxiaodong Date: Mon, 11 Sep 2017 15:41:36 +0800 Subject: [PATCH 66/68] refactor and remove reference of IServiceProvider --- src/DotNetCore.CAP/Abstractions/IConsumerServiceSelector.cs | 5 ++--- src/DotNetCore.CAP/IConsumerHandler.Default.cs | 2 +- .../Internal/IConsumerServiceSelector.Default.cs | 6 +++--- src/DotNetCore.CAP/Internal/MethodMatcherCache.cs | 5 ++--- test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs | 4 ++-- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/DotNetCore.CAP/Abstractions/IConsumerServiceSelector.cs b/src/DotNetCore.CAP/Abstractions/IConsumerServiceSelector.cs index b9e1e03..e7985d7 100644 --- a/src/DotNetCore.CAP/Abstractions/IConsumerServiceSelector.cs +++ b/src/DotNetCore.CAP/Abstractions/IConsumerServiceSelector.cs @@ -11,10 +11,9 @@ namespace DotNetCore.CAP.Abstractions /// /// Selects a set of candidates for the current message associated with /// . - /// - /// . + /// /// A set of candidates or null. - IReadOnlyList SelectCandidates(IServiceProvider provider); + IReadOnlyList SelectCandidates(); /// /// Selects the best candidate from for the diff --git a/src/DotNetCore.CAP/IConsumerHandler.Default.cs b/src/DotNetCore.CAP/IConsumerHandler.Default.cs index 2990e8b..4e69e12 100644 --- a/src/DotNetCore.CAP/IConsumerHandler.Default.cs +++ b/src/DotNetCore.CAP/IConsumerHandler.Default.cs @@ -47,7 +47,7 @@ namespace DotNetCore.CAP public void Start() { - var groupingMatchs = _selector.GetCandidatesMethodsOfGroupNameGrouped(_serviceProvider); + var groupingMatchs = _selector.GetCandidatesMethodsOfGroupNameGrouped(); foreach (var matchGroup in groupingMatchs) { diff --git a/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs b/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs index 6bf8795..2f42690 100644 --- a/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs +++ b/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs @@ -33,13 +33,13 @@ namespace DotNetCore.CAP.Internal return executeDescriptor.FirstOrDefault(x => x.Attribute.Name == key); } - public IReadOnlyList SelectCandidates(IServiceProvider provider) + public IReadOnlyList SelectCandidates() { var executorDescriptorList = new List(); - executorDescriptorList.AddRange(FindConsumersFromInterfaceTypes(provider)); + executorDescriptorList.AddRange(FindConsumersFromInterfaceTypes(_serviceProvider)); - executorDescriptorList.AddRange(FindConsumersFromControllerTypes(provider)); + executorDescriptorList.AddRange(FindConsumersFromControllerTypes(_serviceProvider)); return executorDescriptorList; } diff --git a/src/DotNetCore.CAP/Internal/MethodMatcherCache.cs b/src/DotNetCore.CAP/Internal/MethodMatcherCache.cs index 332158b..db59fcc 100644 --- a/src/DotNetCore.CAP/Internal/MethodMatcherCache.cs +++ b/src/DotNetCore.CAP/Internal/MethodMatcherCache.cs @@ -22,12 +22,11 @@ namespace DotNetCore.CAP.Internal /// Get a dictionary of candidates.In the dictionary, /// the Key is the CAPSubscribeAttribute Group, the Value for the current Group of candidates /// - /// - public ConcurrentDictionary> GetCandidatesMethodsOfGroupNameGrouped(IServiceProvider provider) + public ConcurrentDictionary> GetCandidatesMethodsOfGroupNameGrouped() { if (Entries.Count != 0) return Entries; - var executorCollection = _selector.SelectCandidates(provider); + var executorCollection = _selector.SelectCandidates(); var groupedCandidates = executorCollection.GroupBy(x => x.Attribute.Group); diff --git a/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs b/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs index 5ed56e1..7704e88 100644 --- a/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs +++ b/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs @@ -25,7 +25,7 @@ namespace DotNetCore.CAP.Test public void CanFindAllConsumerService() { var selector = _provider.GetRequiredService(); - var candidates = selector.SelectCandidates(_provider); + var candidates = selector.SelectCandidates(); Assert.Equal(2, candidates.Count); } @@ -34,7 +34,7 @@ namespace DotNetCore.CAP.Test public void CanFindSpecifiedTopic() { var selector = _provider.GetRequiredService(); - var candidates = selector.SelectCandidates(_provider); + var candidates = selector.SelectCandidates(); var bestCandidates = selector.SelectBestCandidate("Candidates.Foo", candidates); Assert.NotNull(bestCandidates); From 756935abfbbee66bd72403ca207e6225b8566168 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 12 Sep 2017 20:52:17 +0800 Subject: [PATCH 67/68] fixed .net core 2.0 services resolve bug --- .../IConsumerServiceSelector.Default.cs | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs b/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs index 6bf8795..24c04db 100644 --- a/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs +++ b/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs @@ -48,19 +48,23 @@ namespace DotNetCore.CAP.Internal IServiceProvider provider) { var executorDescriptorList = new List(); - - var consumerServices = provider.GetServices(); - foreach (var service in consumerServices) + + using (var scoped = provider.CreateScope()) { - var typeInfo = service.GetType().GetTypeInfo(); - if (!typeof(ICapSubscribe).GetTypeInfo().IsAssignableFrom(typeInfo)) + var scopedProvider = scoped.ServiceProvider; + var consumerServices = scopedProvider.GetServices(); + foreach (var service in consumerServices) { - continue; - } + var typeInfo = service.GetType().GetTypeInfo(); + if (!typeof(ICapSubscribe).GetTypeInfo().IsAssignableFrom(typeInfo)) + { + continue; + } - executorDescriptorList.AddRange(GetTopicAttributesDescription(typeInfo)); + executorDescriptorList.AddRange(GetTopicAttributesDescription(typeInfo)); + } + return executorDescriptorList; } - return executorDescriptorList; } private static IEnumerable FindConsumersFromControllerTypes( @@ -76,7 +80,7 @@ namespace DotNetCore.CAP.Internal { executorDescriptorList.AddRange(GetTopicAttributesDescription(typeInfo)); } - } + } return executorDescriptorList; } From 53f72d8ec4242d7302bd59a5f8a8d0cc9ad6d209 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 12 Sep 2017 21:08:50 +0800 Subject: [PATCH 68/68] fixed issue #45 --- .../Internal/ConsumerInvokerFactory.cs | 13 +++---- .../Internal/IConsumerInvoker.Default.cs | 36 ++++++++++--------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/DotNetCore.CAP/Internal/ConsumerInvokerFactory.cs b/src/DotNetCore.CAP/Internal/ConsumerInvokerFactory.cs index cdcb6d6..1d2f5df 100644 --- a/src/DotNetCore.CAP/Internal/ConsumerInvokerFactory.cs +++ b/src/DotNetCore.CAP/Internal/ConsumerInvokerFactory.cs @@ -1,6 +1,5 @@ using System; using DotNetCore.CAP.Abstractions; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace DotNetCore.CAP.Internal @@ -23,15 +22,13 @@ namespace DotNetCore.CAP.Internal public IConsumerInvoker CreateInvoker(ConsumerContext consumerContext) { - using (var scope = _serviceProvider.CreateScope()) + var context = new ConsumerInvokerContext(consumerContext) { - var context = new ConsumerInvokerContext(consumerContext) - { - Result = new DefaultConsumerInvoker(_logger, scope.ServiceProvider, _modelBinderFactory, consumerContext) - }; + Result = new DefaultConsumerInvoker(_logger, _serviceProvider, + _modelBinderFactory, consumerContext) + }; - return context.Result; - } + return context.Result; } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/Internal/IConsumerInvoker.Default.cs b/src/DotNetCore.CAP/Internal/IConsumerInvoker.Default.cs index 4118626..0f408be 100644 --- a/src/DotNetCore.CAP/Internal/IConsumerInvoker.Default.cs +++ b/src/DotNetCore.CAP/Internal/IConsumerInvoker.Default.cs @@ -35,25 +35,29 @@ namespace DotNetCore.CAP.Internal { _logger.LogDebug("Executing consumer Topic: {0}", _consumerContext.ConsumerDescriptor.MethodInfo.Name); - var obj = ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, - _consumerContext.ConsumerDescriptor.ImplTypeInfo.AsType()); + using (var scope = _serviceProvider.CreateScope()) + { + var provider = scope.ServiceProvider; + var serviceType = _consumerContext.ConsumerDescriptor.ImplTypeInfo.AsType(); + var obj = ActivatorUtilities.GetServiceOrCreateInstance(provider, serviceType); - var jsonConent = _consumerContext.DeliverMessage.Content; - var message = Helper.FromJson(jsonConent); + var jsonConent = _consumerContext.DeliverMessage.Content; + var message = Helper.FromJson(jsonConent); - object result = null; - if (_executor.MethodParameters.Length > 0) - { - result = await ExecuteWithParameterAsync(obj, message.Content.ToString()); - } - else - { - result = await ExecuteAsync(obj); - } + object result = null; + if (_executor.MethodParameters.Length > 0) + { + result = await ExecuteWithParameterAsync(obj, message.Content.ToString()); + } + else + { + result = await ExecuteAsync(obj); + } - if (!string.IsNullOrEmpty(message.CallbackName)) - { - await SentCallbackMessage(message.Id, message.CallbackName, result); + if (!string.IsNullOrEmpty(message.CallbackName)) + { + await SentCallbackMessage(message.Id, message.CallbackName, result); + } } }