Commit 5e432a94 authored by Savorboard's avatar Savorboard

cleanup code and fix spelling

parent 49f4eb6b
......@@ -20,7 +20,7 @@ namespace Sample.Kafka.SqlServer
x.UseDiscovery(d =>
{
d.DiscoveryServerHostName = "localhost";
d.DiscoveryServerProt = 8500;
d.DiscoveryServerPort = 8500;
d.CurrentNodeHostName = "localhost";
d.CurrentNodePort = 5820;
d.NodeName = "CAP 2号节点";
......@@ -40,8 +40,6 @@ namespace Sample.Kafka.SqlServer
app.UseMvc();
app.UseCap();
app.UseCapDashboard();
}
}
}
\ No newline at end of file
......@@ -27,7 +27,7 @@ namespace Sample.RabbitMQ.MySql
x.UseDiscovery(d =>
{
d.DiscoveryServerHostName = "localhost";
d.DiscoveryServerProt = 8500;
d.DiscoveryServerPort = 8500;
d.CurrentNodeHostName = "localhost";
d.CurrentNodePort = 5800;
d.NodeName = "CAP 1号节点";
......@@ -45,7 +45,6 @@ namespace Sample.RabbitMQ.MySql
app.UseMvc();
app.UseCap();
app.UseCapDashboard();
}
}
}
......@@ -26,7 +26,7 @@ namespace Sample.RabbitMQ.PostgreSql
x.UseDiscovery(d =>
{
d.DiscoveryServerHostName = "localhost";
d.DiscoveryServerProt = 8500;
d.DiscoveryServerPort = 8500;
d.CurrentNodeHostName = "localhost";
d.CurrentNodePort = 5800;
d.NodeName = "CAP一号节点";
......
......@@ -29,7 +29,7 @@ namespace Sample.RabbitMQ.SqlServer
x.UseDiscovery(d =>
{
d.DiscoveryServerHostName = "localhost";
d.DiscoveryServerProt = 8500;
d.DiscoveryServerPort = 8500;
d.CurrentNodeHostName = "localhost";
d.CurrentNodePort = 5800;
d.NodeName = "CAP一号节点";
......
......@@ -10,14 +10,6 @@ namespace DotNetCore.CAP
/// </summary>
public class KafkaOptions
{
private IEnumerable<KeyValuePair<string, object>> _kafkaConfig;
public KafkaOptions()
{
MainConfig = new Dictionary<string, object>();
}
/// <summary>
/// librdkafka configuration parameters (refer to https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md).
/// <para>
......@@ -26,22 +18,28 @@ namespace DotNetCore.CAP
/// </summary>
public readonly IDictionary<string, object> MainConfig;
private IEnumerable<KeyValuePair<string, object>> _kafkaConfig;
public KafkaOptions()
{
MainConfig = new Dictionary<string, object>();
}
/// <summary>
/// The `bootstrap.servers` item config of <see cref="MainConfig"/>.
/// The `bootstrap.servers` item config of <see cref="MainConfig" />.
/// <para>
/// Initial list of brokers as a CSV list of broker host or host:port.
/// </para>
/// </summary>
public string Servers { get; set; }
internal IEnumerable<KeyValuePair<string, object>> AskafkaConfig()
internal IEnumerable<KeyValuePair<string, object>> AsKafkaConfig()
{
if (_kafkaConfig == null)
{
if (string.IsNullOrWhiteSpace(Servers))
{
throw new ArgumentNullException(nameof(Servers));
}
MainConfig["bootstrap.servers"] = Servers;
MainConfig["queue.buffering.max.ms"] = "10";
......
......@@ -9,18 +9,17 @@ namespace Microsoft.Extensions.DependencyInjection
/// <summary>
/// Configuration to use kafka in CAP.
/// </summary>
/// <param name="options">CAP configuration options</param>
/// <param name="bootstrapServers">Kafka bootstrap server urls.</param>
public static CapOptions UseKafka(this CapOptions options, string bootstrapServers)
{
return options.UseKafka(opt =>
{
opt.Servers = bootstrapServers;
});
return options.UseKafka(opt => { opt.Servers = bootstrapServers; });
}
/// <summary>
/// Configuration to use kafka in CAP.
/// </summary>
/// <param name="options">CAP configuration options</param>
/// <param name="configure">Provides programmatic configuration for the kafka .</param>
/// <returns></returns>
public static CapOptions UseKafka(this CapOptions options, Action<KafkaOptions> configure)
......
......@@ -13,12 +13,6 @@ namespace DotNetCore.CAP.Kafka
private readonly KafkaOptions _kafkaOptions;
private Consumer<Null, string> _consumerClient;
public event EventHandler<MessageContext> OnMessageReceieved;
public event EventHandler<string> OnError;
public IDeserializer<string> StringDeserializer { get; set; }
public KafkaConsumerClient(string groupId, KafkaOptions options)
{
_groupId = groupId;
......@@ -26,15 +20,19 @@ namespace DotNetCore.CAP.Kafka
StringDeserializer = new StringDeserializer(Encoding.UTF8);
}
public IDeserializer<string> StringDeserializer { get; set; }
public event EventHandler<MessageContext> OnMessageReceived;
public event EventHandler<string> OnError;
public void Subscribe(IEnumerable<string> topics)
{
if (topics == null)
throw new ArgumentNullException(nameof(topics));
if (_consumerClient == null)
{
InitKafkaClient();
}
//_consumerClient.Assign(topics.Select(x=> new TopicPartition(x, 0)));
_consumerClient.Subscribe(topics);
......@@ -65,7 +63,7 @@ namespace DotNetCore.CAP.Kafka
{
_kafkaOptions.MainConfig["group.id"] = _groupId;
var config = _kafkaOptions.AskafkaConfig();
var config = _kafkaOptions.AsKafkaConfig();
_consumerClient = new Consumer<Null, string>(config, null, StringDeserializer);
_consumerClient.OnMessage += ConsumerClient_OnMessage;
......@@ -81,7 +79,7 @@ namespace DotNetCore.CAP.Kafka
Content = e.Value
};
OnMessageReceieved?.Invoke(sender, message);
OnMessageReceived?.Invoke(sender, message);
}
private void ConsumerClient_OnError(object sender, Error e)
......
......@@ -9,8 +9,8 @@ namespace DotNetCore.CAP.Kafka
{
internal class PublishQueueExecutor : BasePublishQueueExecutor
{
private readonly ILogger _logger;
private readonly KafkaOptions _kafkaOptions;
private readonly ILogger _logger;
public PublishQueueExecutor(
CapOptions options,
......@@ -27,7 +27,7 @@ namespace DotNetCore.CAP.Kafka
{
try
{
var config = _kafkaOptions.AskafkaConfig();
var config = _kafkaOptions.AsKafkaConfig();
var contentBytes = Encoding.UTF8.GetBytes(content);
using (var producer = new Producer(config))
{
......@@ -39,19 +39,17 @@ namespace DotNetCore.CAP.Kafka
return Task.FromResult(OperateResult.Success);
}
else
return Task.FromResult(OperateResult.Failed(new OperateError
{
return Task.FromResult(OperateResult.Failed(new OperateError
{
Code = message.Error.Code.ToString(),
Description = message.Error.Reason
}));
}
Code = message.Error.Code.ToString(),
Description = message.Error.Reason
}));
}
}
catch (Exception ex)
{
_logger.LogError($"kafka topic message [{keyName}] has benn raised an exception of sending. the exception is: {ex.Message}");
_logger.LogError(
$"kafka topic message [{keyName}] has benn raised an exception of sending. the exception is: {ex.Message}");
return Task.FromResult(OperateResult.Failed(ex));
}
......
......@@ -6,7 +6,7 @@ namespace DotNetCore.CAP
public class EFOptions
{
/// <summary>
/// EF dbcontext type.
/// EF db context type.
/// </summary>
internal Type DbContextType { get; set; }
}
......
......@@ -29,22 +29,18 @@ namespace DotNetCore.CAP
_configure(mysqlOptions);
if (mysqlOptions.DbContextType != null)
{
services.AddSingleton(x =>
{
using (var scope = x.CreateScope())
{
var provider = scope.ServiceProvider;
var dbContext = (DbContext)provider.GetService(mysqlOptions.DbContextType);
var dbContext = (DbContext) provider.GetService(mysqlOptions.DbContextType);
mysqlOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString;
return mysqlOptions;
}
});
}
else
{
services.AddSingleton(mysqlOptions);
}
}
}
}
\ No newline at end of file
// ReSharper disable once CheckNamespace
namespace DotNetCore.CAP
{
public class MySqlOptions : EFOptions
......
......@@ -9,10 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection
{
public static CapOptions UseMySql(this CapOptions options, string connectionString)
{
return options.UseMySql(opt =>
{
opt.ConnectionString = connectionString;
});
return options.UseMySql(opt => { opt.ConnectionString = connectionString; });
}
public static CapOptions UseMySql(this CapOptions options, Action<MySqlOptions> configure)
......@@ -27,10 +24,7 @@ namespace Microsoft.Extensions.DependencyInjection
public static CapOptions UseEntityFramework<TContext>(this CapOptions options)
where TContext : DbContext
{
return options.UseEntityFramework<TContext>(opt =>
{
opt.DbContextType = typeof(TContext);
});
return options.UseEntityFramework<TContext>(opt => { opt.DbContextType = typeof(TContext); });
}
public static CapOptions UseEntityFramework<TContext>(this CapOptions options, Action<EFOptions> configure)
......@@ -38,7 +32,7 @@ namespace Microsoft.Extensions.DependencyInjection
{
if (configure == null) throw new ArgumentNullException(nameof(configure));
var efOptions = new EFOptions { DbContextType = typeof(TContext) };
var efOptions = new EFOptions {DbContextType = typeof(TContext)};
configure(efOptions);
options.RegisterExtension(new MySqlCapOptionsExtension(configure));
......
......@@ -13,9 +13,9 @@ namespace DotNetCore.CAP.MySql
{
public class CapPublisher : CapPublisherBase, ICallbackPublisher
{
private readonly DbContext _dbContext;
private readonly ILogger _logger;
private readonly MySqlOptions _options;
private readonly DbContext _dbContext;
public CapPublisher(IServiceProvider provider,
ILogger<CapPublisher> logger,
......@@ -28,7 +28,15 @@ namespace DotNetCore.CAP.MySql
if (_options.DbContextType != null)
{
IsUsingEF = true;
_dbContext = (DbContext)ServiceProvider.GetService(_options.DbContextType);
_dbContext = (DbContext) ServiceProvider.GetService(_options.DbContextType);
}
}
public async Task PublishAsync(CapPublishedMessage message)
{
using (var conn = new MySqlConnection(_options.ConnectionString))
{
await conn.ExecuteAsync(PrepareSql(), message);
}
}
......@@ -45,36 +53,31 @@ namespace DotNetCore.CAP.MySql
dbContextTransaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted);
dbTrans = dbContextTransaction.GetDbTransaction();
}
DbTranasaction = dbTrans;
DbTransaction = dbTrans;
}
protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message)
protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction,
CapPublishedMessage message)
{
dbConnection.Execute(PrepareSql(), message, dbTransaction);
_logger.LogInformation("Published Message has been persisted in the database. name:" + message);
}
protected override async Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message)
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);
}
public async Task PublishAsync(CapPublishedMessage message)
{
using (var conn = new MySqlConnection(_options.ConnectionString))
{
await conn.ExecuteAsync(PrepareSql(), message);
}
}
#region private methods
private string PrepareSql()
{
return $"INSERT INTO `{_options.TableNamePrefix}.published` (`Name`,`Content`,`Retries`,`Added`,`ExpiresAt`,`StatusName`)VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName)";
return
$"INSERT INTO `{_options.TableNamePrefix}.published` (`Name`,`Content`,`Retries`,`Added`,`ExpiresAt`,`StatusName`)VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName)";
}
#endregion private methods
......
......@@ -9,11 +9,10 @@ namespace DotNetCore.CAP.MySql
{
internal class DefaultAdditionalProcessor : IAdditionalProcessor
{
private readonly ILogger _logger;
private readonly MySqlOptions _options;
private const int MaxBatch = 1000;
private readonly TimeSpan _delay = TimeSpan.FromSeconds(1);
private readonly ILogger _logger;
private readonly MySqlOptions _options;
private readonly TimeSpan _waitingInterval = TimeSpan.FromMinutes(5);
public DefaultAdditionalProcessor(ILogger<DefaultAdditionalProcessor> logger,
......@@ -27,7 +26,8 @@ namespace DotNetCore.CAP.MySql
{
_logger.LogDebug("Collecting expired entities.");
var tables = new[]{
var tables = new[]
{
$"{_options.TableNamePrefix}.published",
$"{_options.TableNamePrefix}.received"
};
......@@ -39,8 +39,9 @@ namespace DotNetCore.CAP.MySql
{
using (var connection = new MySqlConnection(_options.ConnectionString))
{
removedCount = await connection.ExecuteAsync($@"DELETE FROM `{table}` WHERE ExpiresAt < @now limit @count;",
new { now = DateTime.Now, count = MaxBatch });
removedCount = await connection.ExecuteAsync(
$@"DELETE FROM `{table}` WHERE ExpiresAt < @now limit @count;",
new {now = DateTime.Now, count = MaxBatch});
}
if (removedCount != 0)
......
......@@ -8,11 +8,11 @@ namespace DotNetCore.CAP.MySql
{
public class MySqlFetchedMessage : IFetchedMessage
{
private readonly IDbConnection _connection;
private readonly IDbTransaction _transaction;
private readonly Timer _timer;
private static readonly TimeSpan KeepAliveInterval = TimeSpan.FromMinutes(1);
private readonly IDbConnection _connection;
private readonly object _lockObject = new object();
private readonly Timer _timer;
private readonly IDbTransaction _transaction;
public MySqlFetchedMessage(int messageId,
MessageType type,
......
......@@ -11,9 +11,9 @@ namespace DotNetCore.CAP.MySql
{
public class MySqlStorage : IStorage
{
private readonly MySqlOptions _options;
private readonly ILogger _logger;
private readonly IDbConnection _existingConnection = null;
private readonly ILogger _logger;
private readonly MySqlOptions _options;
public MySqlStorage(ILogger<MySqlStorage> logger, MySqlOptions options)
{
......@@ -46,7 +46,7 @@ namespace DotNetCore.CAP.MySql
protected virtual string CreateDbTablesScript(string prefix)
{
var batchSql =
$@"
$@"
CREATE TABLE IF NOT EXISTS `{prefix}.queue` (
`MessageId` int(11) NOT NULL,
`MessageType` tinyint(4) NOT NULL
......@@ -97,9 +97,7 @@ CREATE TABLE IF NOT EXISTS `{prefix}.published` (
var connection = _existingConnection ?? new MySqlConnection(_options.ConnectionString);
if (connection.State == ConnectionState.Closed)
{
connection.Open();
}
return connection;
}
......@@ -112,9 +110,7 @@ CREATE TABLE IF NOT EXISTS `{prefix}.published` (
internal void ReleaseConnection(IDbConnection connection)
{
if (connection != null && !IsExistingConnection(connection))
{
connection.Dispose();
}
}
}
}
\ No newline at end of file
......@@ -5,23 +5,21 @@ using System.Threading.Tasks;
using Dapper;
using DotNetCore.CAP.Infrastructure;
using DotNetCore.CAP.Models;
using DotNetCore.CAP.Processor.States;
using MySql.Data.MySqlClient;
namespace DotNetCore.CAP.MySql
{
public class MySqlStorageConnection : IStorageConnection
{
private readonly MySqlOptions _options;
private readonly string _prefix;
public MySqlStorageConnection(MySqlOptions options)
{
_options = options;
_prefix = _options.TableNamePrefix;
Options = options;
_prefix = Options.TableNamePrefix;
}
public MySqlOptions Options => _options;
public MySqlOptions Options { get; }
public IStorageTransaction CreateTransaction()
{
......@@ -32,7 +30,7 @@ namespace DotNetCore.CAP.MySql
{
var sql = $@"SELECT * FROM `{_prefix}.published` WHERE `Id`={id};";
using (var connection = new MySqlConnection(_options.ConnectionString))
using (var connection = new MySqlConnection(Options.ConnectionString))
{
return await connection.QueryFirstOrDefaultAsync<CapPublishedMessage>(sql);
}
......@@ -59,7 +57,7 @@ DELETE FROM `{_prefix}.queue` LIMIT 1;";
{
var sql = $"SELECT * FROM `{_prefix}.published` WHERE `StatusName` = '{StatusName.Scheduled}' LIMIT 1;";
using (var connection = new MySqlConnection(_options.ConnectionString))
using (var connection = new MySqlConnection(Options.ConnectionString))
{
return await connection.QueryFirstOrDefaultAsync<CapPublishedMessage>(sql);
}
......@@ -69,14 +67,12 @@ DELETE FROM `{_prefix}.queue` LIMIT 1;";
{
var sql = $"SELECT * FROM `{_prefix}.published` WHERE `StatusName` = '{StatusName.Failed}';";
using (var connection = new MySqlConnection(_options.ConnectionString))
using (var connection = new MySqlConnection(Options.ConnectionString))
{
return await connection.QueryAsync<CapPublishedMessage>(sql);
}
}
// CapReceviedMessage
public async Task StoreReceivedMessageAsync(CapReceivedMessage message)
{
if (message == null) throw new ArgumentNullException(nameof(message));
......@@ -85,7 +81,7 @@ DELETE FROM `{_prefix}.queue` LIMIT 1;";
INSERT INTO `{_prefix}.received`(`Name`,`Group`,`Content`,`Retries`,`Added`,`ExpiresAt`,`StatusName`)
VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);";
using (var connection = new MySqlConnection(_options.ConnectionString))
using (var connection = new MySqlConnection(Options.ConnectionString))
{
await connection.ExecuteAsync(sql, message);
}
......@@ -94,25 +90,25 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);";
public async Task<CapReceivedMessage> GetReceivedMessageAsync(int id)
{
var sql = $@"SELECT * FROM `{_prefix}.received` WHERE Id={id};";
using (var connection = new MySqlConnection(_options.ConnectionString))
using (var connection = new MySqlConnection(Options.ConnectionString))
{
return await connection.QueryFirstOrDefaultAsync<CapReceivedMessage>(sql);
}
}
public async Task<CapReceivedMessage> GetNextReceviedMessageToBeEnqueuedAsync()
public async Task<CapReceivedMessage> GetNextReceivedMessageToBeEnqueuedAsync()
{
var sql = $"SELECT * FROM `{_prefix}.received` WHERE `StatusName` = '{StatusName.Scheduled}' LIMIT 1;";
using (var connection = new MySqlConnection(_options.ConnectionString))
using (var connection = new MySqlConnection(Options.ConnectionString))
{
return await connection.QueryFirstOrDefaultAsync<CapReceivedMessage>(sql);
}
}
public async Task<IEnumerable<CapReceivedMessage>> GetFailedReceviedMessages()
public async Task<IEnumerable<CapReceivedMessage>> GetFailedReceivedMessages()
{
var sql = $"SELECT * FROM `{_prefix}.received` WHERE `StatusName` = '{StatusName.Failed}';";
using (var connection = new MySqlConnection(_options.ConnectionString))
using (var connection = new MySqlConnection(Options.ConnectionString))
{
return await connection.QueryAsync<CapReceivedMessage>(sql);
}
......@@ -123,10 +119,32 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);";
{
}
public bool ChangePublishedState(int messageId, string state)
{
var sql =
$"UPDATE `{_prefix}.published` SET `Retries`=`Retries`+1,`StatusName` = '{state}' WHERE `Id`={messageId}";
using (var connection = new MySqlConnection(Options.ConnectionString))
{
return connection.Execute(sql) > 0;
}
}
public bool ChangeReceivedState(int messageId, string state)
{
var sql =
$"UPDATE `{_prefix}.received` SET `Retries`=`Retries`+1,`StatusName` = '{state}' WHERE `Id`={messageId}";
using (var connection = new MySqlConnection(Options.ConnectionString))
{
return connection.Execute(sql) > 0;
}
}
private async Task<IFetchedMessage> FetchNextMessageCoreAsync(string sql, object args = null)
{
//here don't use `using` to dispose
var connection = new MySqlConnection(_options.ConnectionString);
var connection = new MySqlConnection(Options.ConnectionString);
await connection.OpenAsync();
var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted);
FetchedMessage fetchedMessage;
......@@ -148,17 +166,8 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);";
return null;
}
return new MySqlFetchedMessage(fetchedMessage.MessageId, fetchedMessage.MessageType, connection, transaction);
}
public bool ChangePublishedState(int messageId, string state)
{
throw new NotImplementedException();
}
public bool ChangeReceivedState(int messageId, string state)
{
throw new NotImplementedException();
return new MySqlFetchedMessage(fetchedMessage.MessageId, fetchedMessage.MessageType, connection,
transaction);
}
}
}
\ No newline at end of file
......@@ -9,10 +9,10 @@ namespace DotNetCore.CAP.MySql
{
public class MySqlStorageTransaction : IStorageTransaction
{
private readonly string _prefix;
private readonly IDbConnection _dbConnection;
private readonly IDbTransaction _dbTransaction;
private readonly IDbConnection _dbConnection;
private readonly string _prefix;
public MySqlStorageTransaction(MySqlStorageConnection connection)
{
......@@ -28,7 +28,8 @@ namespace DotNetCore.CAP.MySql
{
if (message == null) throw new ArgumentNullException(nameof(message));
var sql = $"UPDATE `{_prefix}.published` SET `Retries` = @Retries,`ExpiresAt` = @ExpiresAt,`StatusName`=@StatusName WHERE `Id`=@Id;";
var sql =
$"UPDATE `{_prefix}.published` SET `Retries` = @Retries,`ExpiresAt` = @ExpiresAt,`StatusName`=@StatusName WHERE `Id`=@Id;";
_dbConnection.Execute(sql, message, _dbTransaction);
}
......@@ -36,7 +37,8 @@ namespace DotNetCore.CAP.MySql
{
if (message == null) throw new ArgumentNullException(nameof(message));
var sql = $"UPDATE `{_prefix}.received` SET `Retries` = @Retries,`Content`= @Content,`ExpiresAt` = @ExpiresAt,`StatusName`=@StatusName WHERE `Id`=@Id;";
var sql =
$"UPDATE `{_prefix}.received` SET `Retries` = @Retries,`Content`= @Content,`ExpiresAt` = @ExpiresAt,`StatusName`=@StatusName WHERE `Id`=@Id;";
_dbConnection.Execute(sql, message, _dbTransaction);
}
......@@ -45,7 +47,8 @@ namespace DotNetCore.CAP.MySql
if (message == null) throw new ArgumentNullException(nameof(message));
var sql = $"INSERT INTO `{_prefix}.queue` values(@MessageId,@MessageType);";
_dbConnection.Execute(sql, new CapQueue { MessageId = message.Id, MessageType = MessageType.Publish }, _dbTransaction);
_dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Publish},
_dbTransaction);
}
public void EnqueueMessage(CapReceivedMessage message)
......@@ -53,7 +56,8 @@ namespace DotNetCore.CAP.MySql
if (message == null) throw new ArgumentNullException(nameof(message));
var sql = $"INSERT INTO `{_prefix}.queue` values(@MessageId,@MessageType);";
_dbConnection.Execute(sql, new CapQueue { MessageId = message.Id, MessageType = MessageType.Subscribe }, _dbTransaction);
_dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Subscribe},
_dbTransaction);
}
public Task CommitAsync()
......
......@@ -9,7 +9,7 @@ namespace DotNetCore.CAP
/// <summary>
/// Gets or sets the schema to use when creating database objects.
/// Default is <see cref="DefaultSchema"/>.
/// Default is <see cref="DefaultSchema" />.
/// </summary>
public string Schema { get; set; } = DefaultSchema;
......
......@@ -9,10 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection
{
public static CapOptions UsePostgreSql(this CapOptions options, string connectionString)
{
return options.UsePostgreSql(opt =>
{
opt.ConnectionString = connectionString;
});
return options.UsePostgreSql(opt => { opt.ConnectionString = connectionString; });
}
public static CapOptions UsePostgreSql(this CapOptions options, Action<PostgreSqlOptions> configure)
......@@ -27,10 +24,7 @@ namespace Microsoft.Extensions.DependencyInjection
public static CapOptions UseEntityFramework<TContext>(this CapOptions options)
where TContext : DbContext
{
return options.UseEntityFramework<TContext>(opt =>
{
opt.DbContextType = typeof(TContext);
});
return options.UseEntityFramework<TContext>(opt => { opt.DbContextType = typeof(TContext); });
}
public static CapOptions UseEntityFramework<TContext>(this CapOptions options, Action<EFOptions> configure)
......@@ -38,7 +32,7 @@ namespace Microsoft.Extensions.DependencyInjection
{
if (configure == null) throw new ArgumentNullException(nameof(configure));
var efOptions = new EFOptions { DbContextType = typeof(TContext) };
var efOptions = new EFOptions {DbContextType = typeof(TContext)};
configure(efOptions);
options.RegisterExtension(new PostgreSqlCapOptionsExtension(configure));
......
......@@ -29,22 +29,18 @@ namespace DotNetCore.CAP
_configure(postgreSqlOptions);
if (postgreSqlOptions.DbContextType != null)
{
services.AddSingleton(x =>
{
using (var scope = x.CreateScope())
{
var provider = scope.ServiceProvider;
var dbContext = (DbContext)provider.GetService(postgreSqlOptions.DbContextType);
var dbContext = (DbContext) provider.GetService(postgreSqlOptions.DbContextType);
postgreSqlOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString;
return postgreSqlOptions;
}
});
}
else
{
services.AddSingleton(postgreSqlOptions);
}
}
}
}
\ No newline at end of file
// ReSharper disable once CheckNamespace
namespace DotNetCore.CAP
{
public class PostgreSqlOptions : EFOptions
......
......@@ -13,9 +13,9 @@ namespace DotNetCore.CAP.PostgreSql
{
public class CapPublisher : CapPublisherBase, ICallbackPublisher
{
private readonly DbContext _dbContext;
private readonly ILogger _logger;
private readonly PostgreSqlOptions _options;
private readonly DbContext _dbContext;
public CapPublisher(IServiceProvider provider,
ILogger<CapPublisher> logger,
......@@ -28,7 +28,15 @@ namespace DotNetCore.CAP.PostgreSql
if (_options.DbContextType != null)
{
IsUsingEF = true;
_dbContext = (DbContext)ServiceProvider.GetService(_options.DbContextType);
_dbContext = (DbContext) ServiceProvider.GetService(_options.DbContextType);
}
}
public async Task PublishAsync(CapPublishedMessage message)
{
using (var conn = new NpgsqlConnection(_options.ConnectionString))
{
await conn.ExecuteAsync(PrepareSql(), message);
}
}
......@@ -45,36 +53,31 @@ namespace DotNetCore.CAP.PostgreSql
dbContextTransaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted);
dbTrans = dbContextTransaction.GetDbTransaction();
}
DbTranasaction = dbTrans;
DbTransaction = dbTrans;
}
protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message)
protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction,
CapPublishedMessage message)
{
dbConnection.Execute(PrepareSql(), message, dbTransaction);
_logger.LogInformation("Published Message has been persisted in the database. name:" + message);
}
protected override async Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message)
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);
}
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)";
return
$"INSERT INTO \"{_options.Schema}\".\"published\" (\"Name\",\"Content\",\"Retries\",\"Added\",\"ExpiresAt\",\"StatusName\")VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName)";
}
#endregion private methods
......
......@@ -9,26 +9,22 @@ namespace DotNetCore.CAP.PostgreSql
{
internal class DefaultAdditionalProcessor : IAdditionalProcessor
{
private readonly IServiceProvider _provider;
private readonly ILogger _logger;
private readonly PostgreSqlOptions _options;
private const int MaxBatch = 1000;
private readonly TimeSpan _delay = TimeSpan.FromSeconds(1);
private readonly TimeSpan _waitingInterval = TimeSpan.FromMinutes(5);
private static readonly string[] Tables =
{
"published","received"
"published", "received"
};
public DefaultAdditionalProcessor(
IServiceProvider provider,
ILogger<DefaultAdditionalProcessor> logger,
private readonly TimeSpan _delay = TimeSpan.FromSeconds(1);
private readonly ILogger _logger;
private readonly PostgreSqlOptions _options;
private readonly TimeSpan _waitingInterval = TimeSpan.FromMinutes(5);
public DefaultAdditionalProcessor(ILogger<DefaultAdditionalProcessor> logger,
PostgreSqlOptions sqlServerOptions)
{
_logger = logger;
_provider = provider;
_options = sqlServerOptions;
}
......@@ -43,8 +39,9 @@ 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 });
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});
}
if (removedCount != 0)
......
......@@ -8,11 +8,11 @@ namespace DotNetCore.CAP.PostgreSql
{
public class PostgreSqlFetchedMessage : IFetchedMessage
{
private readonly IDbConnection _connection;
private readonly IDbTransaction _transaction;
private readonly Timer _timer;
private static readonly TimeSpan KeepAliveInterval = TimeSpan.FromMinutes(1);
private readonly IDbConnection _connection;
private readonly object _lockObject = new object();
private readonly Timer _timer;
private readonly IDbTransaction _transaction;
public PostgreSqlFetchedMessage(int messageId,
MessageType type,
......
......@@ -10,10 +10,10 @@ using DotNetCore.CAP.Models;
namespace DotNetCore.CAP.PostgreSql
{
public class PostgreSqlMonitoringApi: IMonitoringApi
public class PostgreSqlMonitoringApi : IMonitoringApi
{
private readonly PostgreSqlStorage _storage;
private readonly PostgreSqlOptions _options;
private readonly PostgreSqlStorage _storage;
public PostgreSqlMonitoringApi(IStorage storage, PostgreSqlOptions options)
{
......@@ -23,7 +23,7 @@ namespace DotNetCore.CAP.PostgreSql
public StatisticsDto GetStatistics()
{
string sql = String.Format(@"
var sql = string.Format(@"
select count(Id) from ""{0}"".""published"" where ""StatusName"" = N'Succeeded';
select count(Id) from ""{0}"".""received"" where ""StatusName"" = N'Succeeded';
select count(Id) from ""{0}"".""published"" where ""StatusName"" = N'Failed';
......@@ -55,32 +55,22 @@ select count(Id) from ""{0}"".""received"" where ""StatusName"" in (N'Processin
{
var tableName = queryDto.MessageType == MessageType.Publish ? "published" : "received";
var where = string.Empty;
if (!string.IsNullOrEmpty(queryDto.StatusName))
{
if (string.Equals(queryDto.StatusName, StatusName.Processing, StringComparison.CurrentCultureIgnoreCase))
{
if (string.Equals(queryDto.StatusName, StatusName.Processing,
StringComparison.CurrentCultureIgnoreCase))
where += " and \"StatusName\" in (N'Processing',N'Scheduled',N'Enqueued')";
}
else
{
where += " and \"StatusName\" = @StatusName";
}
}
if (!string.IsNullOrEmpty(queryDto.Name))
{
where += " and \"Name\" = @Name";
}
if (!string.IsNullOrEmpty(queryDto.Group))
{
where += " and \"Group\" = @Group";
}
if (!string.IsNullOrEmpty(queryDto.Content))
{
where += " and \"Content\" like '%@Content%'";
}
var sqlQuery = $"select * from \"{_options.Schema}\".\"{tableName}\" where 1=1 {where} order by \"Added\" desc offset @Offset limit @Limit";
var sqlQuery =
$"select * from \"{_options.Schema}\".\"{tableName}\" where 1=1 {where} order by \"Added\" desc offset @Offset limit @Limit";
return UseConnection(conn => conn.Query<MessageDto>(sqlQuery, new
{
......@@ -89,7 +79,7 @@ select count(Id) from ""{0}"".""received"" where ""StatusName"" in (N'Processin
queryDto.Name,
queryDto.Content,
Offset = queryDto.CurrentPage * queryDto.PageSize,
Limit = queryDto.PageSize,
Limit = queryDto.PageSize
}).ToList());
}
......@@ -143,7 +133,7 @@ select count(Id) from ""{0}"".""received"" where ""StatusName"" in (N'Processin
? $"select count(Id) from \"{_options.Schema}\".\"{tableName}\" where \"StatusName\" in (N'Processing',N'Scheduled',N'Enqueued')"
: $"select count(Id) from \"{_options.Schema}\".\"{tableName}\" where \"StatusName\" = @state";
var count = connection.ExecuteScalar<int>(sqlQuery, new { state = statusName });
var count = connection.ExecuteScalar<int>(sqlQuery, new {state = statusName});
return count;
}
......@@ -152,7 +142,8 @@ select count(Id) from ""{0}"".""received"" where ""StatusName"" in (N'Processin
return _storage.UseConnection(action);
}
private Dictionary<DateTime, int> GetHourlyTimelineStats(IDbConnection connection, string tableName, string statusName)
private Dictionary<DateTime, int> GetHourlyTimelineStats(IDbConnection connection, string tableName,
string statusName)
{
var endDate = DateTime.Now;
var dates = new List<DateTime>();
......@@ -174,7 +165,7 @@ select count(Id) from ""{0}"".""received"" where ""StatusName"" in (N'Processin
IDictionary<string, DateTime> keyMaps)
{
//SQL Server 2012+
string sqlQuery =
var sqlQuery =
$@"
with aggr as (
select to_char(""Added"",'yyyy-MM-dd-HH') as ""Key"",
......@@ -187,13 +178,11 @@ select ""Key"",""Count"" from aggr where ""Key"" in @keys;";
var valuesMap = connection.Query(
sqlQuery,
new { keys = keyMaps.Keys, statusName })
.ToDictionary(x => (string)x.Key, x => (int)x.Count);
new {keys = keyMaps.Keys, statusName})
.ToDictionary(x => (string) x.Key, x => (int) x.Count);
foreach (var key in keyMaps.Keys)
{
if (!valuesMap.ContainsKey(key)) valuesMap.Add(key, 0);
}
var result = new Dictionary<DateTime, int>();
for (var i = 0; i < keyMaps.Count; i++)
......
......@@ -11,9 +11,9 @@ namespace DotNetCore.CAP.PostgreSql
{
public class PostgreSqlStorage : IStorage
{
private readonly PostgreSqlOptions _options;
private readonly ILogger _logger;
private readonly IDbConnection _existingConnection = null;
private readonly ILogger _logger;
private readonly PostgreSqlOptions _options;
public PostgreSqlStorage(ILogger<PostgreSqlStorage> logger, PostgreSqlOptions options)
{
......@@ -64,9 +64,7 @@ namespace DotNetCore.CAP.PostgreSql
var connection = _existingConnection ?? new NpgsqlConnection(_options.ConnectionString);
if (connection.State == ConnectionState.Closed)
{
connection.Open();
}
return connection;
}
......@@ -79,9 +77,7 @@ namespace DotNetCore.CAP.PostgreSql
internal void ReleaseConnection(IDbConnection connection)
{
if (connection != null && !IsExistingConnection(connection))
{
connection.Dispose();
}
}
protected virtual string CreateDbTablesScript(string schema)
......
......@@ -63,8 +63,6 @@ namespace DotNetCore.CAP.PostgreSql
}
}
// CapReceviedMessage
public async Task StoreReceivedMessageAsync(CapReceivedMessage message)
{
if (message == null) throw new ArgumentNullException(nameof(message));
......@@ -87,7 +85,7 @@ namespace DotNetCore.CAP.PostgreSql
}
}
public async Task<CapReceivedMessage> GetNextReceviedMessageToBeEnqueuedAsync()
public async Task<CapReceivedMessage> GetNextReceivedMessageToBeEnqueuedAsync()
{
var sql =
$"SELECT * FROM \"{Options.Schema}\".\"received\" WHERE \"StatusName\" = '{StatusName.Scheduled}' FOR UPDATE SKIP LOCKED LIMIT 1;";
......@@ -97,7 +95,7 @@ namespace DotNetCore.CAP.PostgreSql
}
}
public async Task<IEnumerable<CapReceivedMessage>> GetFailedReceviedMessages()
public async Task<IEnumerable<CapReceivedMessage>> GetFailedReceivedMessages()
{
var sql =
$"SELECT * FROM \"{Options.Schema}\".\"received\" WHERE \"StatusName\"='{StatusName.Failed}' LIMIT 1000;";
......@@ -125,7 +123,7 @@ namespace DotNetCore.CAP.PostgreSql
public bool ChangeReceivedState(int messageId, string state)
{
var sql =
$"UPDATE \"{Options.Schema}\".\"received\" SET \"Retries\"=\"Retries\"+1,\"StatusName\" = '{state}' WHERE \"Id\"={messageId}";
$"UPDATE \"{Options.Schema}\".\"received\" SET \"Retries\"=\"Retries\"+1,\"StatusName\" = '{state}' WHERE \"Id\"={messageId}";
using (var connection = new NpgsqlConnection(Options.ConnectionString))
{
......
......@@ -9,10 +9,10 @@ namespace DotNetCore.CAP.PostgreSql
{
public class PostgreSqlStorageTransaction : IStorageTransaction
{
private readonly string _schema;
private readonly IDbConnection _dbConnection;
private readonly IDbTransaction _dbTransaction;
private readonly IDbConnection _dbConnection;
private readonly string _schema;
public PostgreSqlStorageTransaction(PostgreSqlStorageConnection connection)
{
......@@ -28,7 +28,10 @@ namespace DotNetCore.CAP.PostgreSql
{
if (message == null) throw new ArgumentNullException(nameof(message));
var sql = $@"UPDATE ""{_schema}"".""published"" SET ""Retries""=@Retries,""ExpiresAt""=@ExpiresAt,""StatusName""=@StatusName WHERE ""Id""=@Id;";
var sql =
$@"UPDATE ""{
_schema
}"".""published"" SET ""Retries""=@Retries,""ExpiresAt""=@ExpiresAt,""StatusName""=@StatusName WHERE ""Id""=@Id;";
_dbConnection.Execute(sql, message, _dbTransaction);
}
......@@ -36,7 +39,10 @@ namespace DotNetCore.CAP.PostgreSql
{
if (message == null) throw new ArgumentNullException(nameof(message));
var sql = $@"UPDATE ""{_schema}"".""received"" SET ""Retries""=@Retries,""Content""= @Content,""ExpiresAt""=@ExpiresAt,""StatusName""=@StatusName WHERE ""Id""=@Id;";
var sql =
$@"UPDATE ""{
_schema
}"".""received"" SET ""Retries""=@Retries,""Content""= @Content,""ExpiresAt""=@ExpiresAt,""StatusName""=@StatusName WHERE ""Id""=@Id;";
_dbConnection.Execute(sql, message, _dbTransaction);
}
......@@ -45,7 +51,8 @@ namespace DotNetCore.CAP.PostgreSql
if (message == null) throw new ArgumentNullException(nameof(message));
var sql = $@"INSERT INTO ""{_schema}"".""queue"" values(@MessageId,@MessageType);";
_dbConnection.Execute(sql, new CapQueue { MessageId = message.Id, MessageType = MessageType.Publish }, _dbTransaction);
_dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Publish},
_dbTransaction);
}
public void EnqueueMessage(CapReceivedMessage message)
......@@ -53,7 +60,8 @@ namespace DotNetCore.CAP.PostgreSql
if (message == null) throw new ArgumentNullException(nameof(message));
var sql = $@"INSERT INTO ""{_schema}"".""queue"" values(@MessageId,@MessageType);";
_dbConnection.Execute(sql, new CapQueue { MessageId = message.Id, MessageType = MessageType.Subscribe }, _dbTransaction);
_dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Subscribe},
_dbTransaction);
}
public Task CommitAsync()
......
......@@ -8,10 +8,7 @@ namespace Microsoft.Extensions.DependencyInjection
{
public static CapOptions UseRabbitMQ(this CapOptions options, string hostName)
{
return options.UseRabbitMQ(opt =>
{
opt.HostName = hostName;
});
return options.UseRabbitMQ(opt => { opt.HostName = hostName; });
}
public static CapOptions UseRabbitMQ(this CapOptions options, Action<RabbitMQOptions> configure)
......
// ReSharper disable once CheckNamespace
namespace DotNetCore.CAP
{
public class RabbitMQOptions
......
......@@ -10,13 +10,13 @@ namespace DotNetCore.CAP.RabbitMQ
{
private const int DefaultPoolSize = 15;
private readonly ConcurrentQueue<IConnection> _pool = new ConcurrentQueue<IConnection>();
private readonly Func<IConnection> _activator;
private int _maxSize;
private readonly ConcurrentQueue<IConnection> _pool = new ConcurrentQueue<IConnection>();
private int _count;
private int _maxSize;
public ConnectionPool(RabbitMQOptions options)
{
_maxSize = DefaultPoolSize;
......@@ -24,9 +24,28 @@ namespace DotNetCore.CAP.RabbitMQ
_activator = CreateActivator(options);
}
IConnection IConnectionPool.Rent()
{
return Rent();
}
bool IConnectionPool.Return(IConnection connection)
{
return Return(connection);
}
public void Dispose()
{
_maxSize = 0;
IConnection context;
while (_pool.TryDequeue(out context))
context.Dispose();
}
private static Func<IConnection> CreateActivator(RabbitMQOptions options)
{
var factory = new ConnectionFactory()
var factory = new ConnectionFactory
{
HostName = options.HostName,
UserName = options.UserName,
......@@ -43,7 +62,7 @@ namespace DotNetCore.CAP.RabbitMQ
public virtual IConnection Rent()
{
if (_pool.TryDequeue(out IConnection connection))
if (_pool.TryDequeue(out var connection))
{
Interlocked.Decrement(ref _count);
......@@ -72,20 +91,5 @@ namespace DotNetCore.CAP.RabbitMQ
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();
}
}
}
}
}
\ No newline at end of file
......@@ -8,4 +8,4 @@ namespace DotNetCore.CAP.RabbitMQ
bool Return(IConnection context);
}
}
}
\ No newline at end of file
......@@ -9,8 +9,8 @@ namespace DotNetCore.CAP.RabbitMQ
{
internal sealed class PublishQueueExecutor : BasePublishQueueExecutor
{
private readonly ILogger _logger;
private readonly ConnectionPool _connectionPool;
private readonly ILogger _logger;
private readonly RabbitMQOptions _rabbitMQOptions;
public PublishQueueExecutor(
......@@ -36,11 +36,11 @@ namespace DotNetCore.CAP.RabbitMQ
{
var body = Encoding.UTF8.GetBytes(content);
channel.ExchangeDeclare(_rabbitMQOptions.TopicExchangeName, RabbitMQOptions.ExchangeType, durable: true);
channel.BasicPublish(exchange: _rabbitMQOptions.TopicExchangeName,
routingKey: keyName,
basicProperties: null,
body: body);
channel.ExchangeDeclare(_rabbitMQOptions.TopicExchangeName, RabbitMQOptions.ExchangeType, true);
channel.BasicPublish(_rabbitMQOptions.TopicExchangeName,
keyName,
null,
body);
_logger.LogDebug($"rabbitmq topic message [{keyName}] has been published.");
}
......@@ -48,10 +48,11 @@ namespace DotNetCore.CAP.RabbitMQ
}
catch (Exception ex)
{
_logger.LogError($"rabbitmq topic message [{keyName}] has benn raised an exception of sending. the exception is: {ex.Message}");
_logger.LogError(
$"rabbitmq topic message [{keyName}] has benn raised an exception of sending. the exception is: {ex.Message}");
return Task.FromResult(OperateResult.Failed(ex,
new OperateError()
new OperateError
{
Code = ex.HResult.ToString(),
Description = ex.Message
......
......@@ -10,21 +10,17 @@ namespace DotNetCore.CAP.RabbitMQ
{
internal sealed class RabbitMQConsumerClient : IConsumerClient
{
private readonly ConnectionPool _connectionPool;
private readonly string _exchageName;
private readonly string _queueName;
private readonly RabbitMQOptions _rabbitMQOptions;
private readonly ConnectionPool _connectionPool;
private IModel _channel;
private ulong _deliveryTag;
public event EventHandler<MessageContext> OnMessageReceieved;
public event EventHandler<string> OnError;
public RabbitMQConsumerClient(string queueName,
ConnectionPool connectionPool,
RabbitMQOptions options)
ConnectionPool connectionPool,
RabbitMQOptions options)
{
_queueName = queueName;
_connectionPool = connectionPool;
......@@ -34,35 +30,16 @@ namespace DotNetCore.CAP.RabbitMQ
InitClient();
}
private void InitClient()
{
var connection = _connectionPool.Rent();
_channel = connection.CreateModel();
_channel.ExchangeDeclare(
exchange: _exchageName,
type: RabbitMQOptions.ExchangeType,
durable: true);
var arguments = new Dictionary<string, object> { { "x-message-ttl", _rabbitMQOptions.QueueMessageExpires } };
_channel.QueueDeclare(_queueName,
durable: true,
exclusive: false,
autoDelete: false,
arguments: arguments);
public event EventHandler<MessageContext> OnMessageReceived;
_connectionPool.Return(connection);
}
public event EventHandler<string> OnError;
public void Subscribe(IEnumerable<string> topics)
{
if (topics == null) throw new ArgumentNullException(nameof(topics));
foreach (var topic in topics)
{
_channel.QueueBind(_queueName, _exchageName, topic);
}
}
public void Listening(TimeSpan timeout, CancellationToken cancellationToken)
......@@ -72,9 +49,7 @@ namespace DotNetCore.CAP.RabbitMQ
consumer.Shutdown += OnConsumerShutdown;
_channel.BasicConsume(_queueName, false, consumer);
while (true)
{
Task.Delay(timeout, cancellationToken).GetAwaiter().GetResult();
}
}
public void Commit()
......@@ -87,6 +62,27 @@ namespace DotNetCore.CAP.RabbitMQ
_channel.Dispose();
}
private void InitClient()
{
var connection = _connectionPool.Rent();
_channel = connection.CreateModel();
_channel.ExchangeDeclare(
_exchageName,
RabbitMQOptions.ExchangeType,
true);
var arguments = new Dictionary<string, object> {{"x-message-ttl", _rabbitMQOptions.QueueMessageExpires}};
_channel.QueueDeclare(_queueName,
true,
false,
false,
arguments);
_connectionPool.Return(connection);
}
private void OnConsumerReceived(object sender, BasicDeliverEventArgs e)
{
_deliveryTag = e.DeliveryTag;
......@@ -96,7 +92,7 @@ namespace DotNetCore.CAP.RabbitMQ
Name = e.RoutingKey,
Content = Encoding.UTF8.GetString(e.Body)
};
OnMessageReceieved?.Invoke(sender, message);
OnMessageReceived?.Invoke(sender, message);
}
private void OnConsumerShutdown(object sender, ShutdownEventArgs e)
......
......@@ -2,8 +2,8 @@
{
internal sealed class RabbitMQConsumerClientFactory : IConsumerClientFactory
{
private readonly RabbitMQOptions _rabbitMQOptions;
private readonly ConnectionPool _connectionPool;
private readonly RabbitMQOptions _rabbitMQOptions;
public RabbitMQConsumerClientFactory(RabbitMQOptions rabbitMQOptions, ConnectionPool pool)
......
......@@ -9,7 +9,7 @@ namespace DotNetCore.CAP
/// <summary>
/// Gets or sets the schema to use when creating database objects.
/// Default is <see cref="DefaultSchema"/>.
/// Default is <see cref="DefaultSchema" />.
/// </summary>
public string Schema { get; set; } = DefaultSchema;
......
......@@ -9,10 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection
{
public static CapOptions UseSqlServer(this CapOptions options, string connectionString)
{
return options.UseSqlServer(opt =>
{
opt.ConnectionString = connectionString;
});
return options.UseSqlServer(opt => { opt.ConnectionString = connectionString; });
}
public static CapOptions UseSqlServer(this CapOptions options, Action<SqlServerOptions> configure)
......@@ -27,10 +24,7 @@ namespace Microsoft.Extensions.DependencyInjection
public static CapOptions UseEntityFramework<TContext>(this CapOptions options)
where TContext : DbContext
{
return options.UseEntityFramework<TContext>(opt =>
{
opt.DbContextType = typeof(TContext);
});
return options.UseEntityFramework<TContext>(opt => { opt.DbContextType = typeof(TContext); });
}
public static CapOptions UseEntityFramework<TContext>(this CapOptions options, Action<EFOptions> configure)
......@@ -38,7 +32,7 @@ namespace Microsoft.Extensions.DependencyInjection
{
if (configure == null) throw new ArgumentNullException(nameof(configure));
var efOptions = new EFOptions { DbContextType = typeof(TContext) };
var efOptions = new EFOptions {DbContextType = typeof(TContext)};
configure(efOptions);
options.RegisterExtension(new SqlServerCapOptionsExtension(configure));
......
......@@ -34,22 +34,18 @@ namespace DotNetCore.CAP
_configure(sqlServerOptions);
if (sqlServerOptions.DbContextType != null)
{
services.AddSingleton(x =>
{
using (var scope = x.CreateScope())
{
var provider = scope.ServiceProvider;
var dbContext = (DbContext)provider.GetService(sqlServerOptions.DbContextType);
var dbContext = (DbContext) provider.GetService(sqlServerOptions.DbContextType);
sqlServerOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString;
return sqlServerOptions;
}
});
}
else
{
services.AddSingleton(sqlServerOptions);
}
}
}
}
\ No newline at end of file
// ReSharper disable once CheckNamespace
namespace DotNetCore.CAP
{
public class SqlServerOptions : EFOptions
......
......@@ -13,9 +13,9 @@ namespace DotNetCore.CAP.SqlServer
{
public class CapPublisher : CapPublisherBase, ICallbackPublisher
{
private readonly DbContext _dbContext;
private readonly ILogger _logger;
private readonly SqlServerOptions _options;
private readonly DbContext _dbContext;
public CapPublisher(IServiceProvider provider,
ILogger<CapPublisher> logger,
......@@ -28,7 +28,15 @@ namespace DotNetCore.CAP.SqlServer
if (_options.DbContextType != null)
{
IsUsingEF = true;
_dbContext = (DbContext)ServiceProvider.GetService(_options.DbContextType);
_dbContext = (DbContext) ServiceProvider.GetService(_options.DbContextType);
}
}
public async Task PublishAsync(CapPublishedMessage message)
{
using (var conn = new SqlConnection(_options.ConnectionString))
{
await conn.ExecuteAsync(PrepareSql(), message);
}
}
......@@ -45,36 +53,31 @@ namespace DotNetCore.CAP.SqlServer
dbContextTransaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted);
dbTrans = dbContextTransaction.GetDbTransaction();
}
DbTranasaction = dbTrans;
DbTransaction = dbTrans;
}
protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message)
protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction,
CapPublishedMessage message)
{
dbConnection.Execute(PrepareSql(), message, dbTransaction);
_logger.LogInformation("Published Message has been persisted in the database. name:" + message);
}
protected override async Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message)
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);
}
public async Task PublishAsync(CapPublishedMessage message)
{
using (var conn = new SqlConnection(_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)";
return
$"INSERT INTO {_options.Schema}.[Published] ([Name],[Content],[Retries],[Added],[ExpiresAt],[StatusName])VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName)";
}
#endregion private methods
......
......@@ -9,18 +9,18 @@ namespace DotNetCore.CAP.SqlServer
{
public class DefaultAdditionalProcessor : IAdditionalProcessor
{
private readonly ILogger _logger;
private readonly SqlServerOptions _options;
private const int MaxBatch = 1000;
private readonly TimeSpan _delay = TimeSpan.FromSeconds(1);
private readonly TimeSpan _waitingInterval = TimeSpan.FromMinutes(5);
private static readonly string[] Tables =
{
"Published","Received"
"Published", "Received"
};
private readonly TimeSpan _delay = TimeSpan.FromSeconds(1);
private readonly ILogger _logger;
private readonly SqlServerOptions _options;
private readonly TimeSpan _waitingInterval = TimeSpan.FromMinutes(5);
public DefaultAdditionalProcessor(ILogger<DefaultAdditionalProcessor> logger,
SqlServerOptions sqlServerOptions)
{
......@@ -42,7 +42,7 @@ namespace DotNetCore.CAP.SqlServer
removedCount = await connection.ExecuteAsync($@"
DELETE TOP (@count)
FROM [{_options.Schema}].[{table}] WITH (readpast)
WHERE ExpiresAt < @now;", new { now = DateTime.Now, count = MaxBatch });
WHERE ExpiresAt < @now;", new {now = DateTime.Now, count = MaxBatch});
}
if (removedCount != 0)
......
......@@ -8,11 +8,11 @@ namespace DotNetCore.CAP.SqlServer
{
public class SqlServerFetchedMessage : IFetchedMessage
{
private readonly IDbConnection _connection;
private readonly IDbTransaction _transaction;
private readonly Timer _timer;
private static readonly TimeSpan KeepAliveInterval = TimeSpan.FromMinutes(1);
private readonly IDbConnection _connection;
private readonly object _lockObject = new object();
private readonly Timer _timer;
private readonly IDbTransaction _transaction;
public SqlServerFetchedMessage(int messageId,
MessageType type,
......
......@@ -12,8 +12,8 @@ namespace DotNetCore.CAP.SqlServer
{
internal class SqlServerMonitoringApi : IMonitoringApi
{
private readonly SqlServerStorage _storage;
private readonly SqlServerOptions _options;
private readonly SqlServerStorage _storage;
public SqlServerMonitoringApi(IStorage storage, SqlServerOptions options)
{
......@@ -23,7 +23,7 @@ namespace DotNetCore.CAP.SqlServer
public StatisticsDto GetStatistics()
{
string sql = String.Format(@"
var sql = string.Format(@"
set transaction isolation level read committed;
select count(Id) from [{0}].Published with (nolock) where StatusName = N'Succeeded';
select count(Id) from [{0}].Received with (nolock) where StatusName = N'Succeeded';
......@@ -31,7 +31,7 @@ select count(Id) from [{0}].Published with (nolock) where StatusName = N'Failed'
select count(Id) from [{0}].Received with (nolock) where StatusName = N'Failed';
select count(Id) from [{0}].Published with (nolock) where StatusName in (N'Processing',N'Scheduled',N'Enqueued');
select count(Id) from [{0}].Received with (nolock) where StatusName in (N'Processing',N'Scheduled',N'Enqueued');",
_options.Schema);
_options.Schema);
var statistics = UseConnection(connection =>
{
......@@ -63,7 +63,7 @@ _options.Schema);
{
var tableName = type == MessageType.Publish ? "Published" : "Received";
return UseConnection(connection =>
GetHourlyTimelineStats(connection, tableName, StatusName.Succeeded));
GetHourlyTimelineStats(connection, tableName, StatusName.Succeeded));
}
public IList<MessageDto> Messages(MessageQueryDto queryDto)
......@@ -71,30 +71,20 @@ _options.Schema);
var tableName = queryDto.MessageType == MessageType.Publish ? "Published" : "Received";
var where = string.Empty;
if (!string.IsNullOrEmpty(queryDto.StatusName))
{
if (string.Equals(queryDto.StatusName, StatusName.Processing, StringComparison.CurrentCultureIgnoreCase))
{
if (string.Equals(queryDto.StatusName, StatusName.Processing,
StringComparison.CurrentCultureIgnoreCase))
where += " and statusname in (N'Processing',N'Scheduled',N'Enqueued')";
}
else
{
where += " and statusname=@StatusName";
}
}
if (!string.IsNullOrEmpty(queryDto.Name))
{
where += " and name=@Name";
}
if (!string.IsNullOrEmpty(queryDto.Group))
{
where += " and group=@Group";
}
if (!string.IsNullOrEmpty(queryDto.Content))
{
where += " and content like '%@Content%'";
}
var sqlQuery = $"select * from [{_options.Schema}].{tableName} where 1=1 {where} order by Added desc offset @Offset rows fetch next @Limit rows only";
var sqlQuery =
$"select * from [{_options.Schema}].{tableName} where 1=1 {where} order by Added desc offset @Offset rows fetch next @Limit rows only";
return UseConnection(conn => conn.Query<MessageDto>(sqlQuery, new
{
......@@ -103,7 +93,7 @@ _options.Schema);
queryDto.Name,
queryDto.Content,
Offset = queryDto.CurrentPage * queryDto.PageSize,
Limit = queryDto.PageSize,
Limit = queryDto.PageSize
}).ToList());
}
......@@ -143,7 +133,7 @@ _options.Schema);
? $"select count(Id) from [{_options.Schema}].{tableName} with (nolock) where StatusName in (N'Processing',N'Scheduled',N'Enqueued')"
: $"select count(Id) from [{_options.Schema}].{tableName} with (nolock) where StatusName = @state";
var count = connection.ExecuteScalar<int>(sqlQuery, new { state = statusName });
var count = connection.ExecuteScalar<int>(sqlQuery, new {state = statusName});
return count;
}
......@@ -152,7 +142,8 @@ _options.Schema);
return _storage.UseConnection(action);
}
private Dictionary<DateTime, int> GetHourlyTimelineStats(IDbConnection connection, string tableName, string statusName)
private Dictionary<DateTime, int> GetHourlyTimelineStats(IDbConnection connection, string tableName,
string statusName)
{
var endDate = DateTime.Now;
var dates = new List<DateTime>();
......@@ -168,14 +159,14 @@ _options.Schema);
}
private Dictionary<DateTime, int> GetTimelineStats(
IDbConnection connection,
string tableName,
string statusName,
IDictionary<string, DateTime> keyMaps)
IDbConnection connection,
string tableName,
string statusName,
IDictionary<string, DateTime> keyMaps)
{
//SQL Server 2012+
string sqlQuery =
$@"
var sqlQuery =
$@"
with aggr as (
select FORMAT(Added,'yyyy-MM-dd-HH') as [Key],
count(id) [Count]
......@@ -186,14 +177,12 @@ with aggr as (
select [Key], [Count] from aggr with (nolock) where [Key] in @keys;";
var valuesMap = connection.Query(
sqlQuery,
new { keys = keyMaps.Keys, statusName })
.ToDictionary(x => (string)x.Key, x => (int)x.Count);
sqlQuery,
new {keys = keyMaps.Keys, statusName})
.ToDictionary(x => (string) x.Key, x => (int) x.Count);
foreach (var key in keyMaps.Keys)
{
if (!valuesMap.ContainsKey(key)) valuesMap.Add(key, 0);
}
var result = new Dictionary<DateTime, int>();
for (var i = 0; i < keyMaps.Count; i++)
......
......@@ -11,9 +11,9 @@ namespace DotNetCore.CAP.SqlServer
{
public class SqlServerStorage : IStorage
{
private readonly SqlServerOptions _options;
private readonly ILogger _logger;
private readonly IDbConnection _existingConnection = null;
private readonly ILogger _logger;
private readonly SqlServerOptions _options;
public SqlServerStorage(ILogger<SqlServerStorage> logger, SqlServerOptions options)
{
......@@ -47,7 +47,7 @@ namespace DotNetCore.CAP.SqlServer
protected virtual string CreateDbTablesScript(string schema)
{
var batchSql =
$@"
$@"
IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = '{schema}')
BEGIN
EXEC('CREATE SCHEMA {schema}')
......@@ -118,9 +118,7 @@ END;";
var connection = _existingConnection ?? new SqlConnection(_options.ConnectionString);
if (connection.State == ConnectionState.Closed)
{
connection.Open();
}
return connection;
}
......@@ -133,10 +131,7 @@ END;";
internal void ReleaseConnection(IDbConnection connection)
{
if (connection != null && !IsExistingConnection(connection))
{
connection.Dispose();
}
}
}
}
\ No newline at end of file
......@@ -101,7 +101,7 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);";
}
}
public async Task<CapReceivedMessage> GetNextReceviedMessageToBeEnqueuedAsync()
public async Task<CapReceivedMessage> GetNextReceivedMessageToBeEnqueuedAsync()
{
var sql =
$"SELECT TOP (1) * FROM [{Options.Schema}].[Received] WITH (readpast) WHERE StatusName = '{StatusName.Scheduled}'";
......@@ -111,7 +111,7 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);";
}
}
public async Task<IEnumerable<CapReceivedMessage>> GetFailedReceviedMessages()
public async Task<IEnumerable<CapReceivedMessage>> GetFailedReceivedMessages()
{
var sql =
$"SELECT * FROM [{Options.Schema}].[Received] WITH (readpast) WHERE StatusName = '{StatusName.Failed}'";
......
......@@ -9,10 +9,10 @@ namespace DotNetCore.CAP.SqlServer
{
public class SqlServerStorageTransaction : IStorageTransaction
{
private readonly string _schema;
private readonly IDbConnection _dbConnection;
private readonly IDbTransaction _dbTransaction;
private readonly IDbConnection _dbConnection;
private readonly string _schema;
public SqlServerStorageTransaction(SqlServerStorageConnection connection)
{
......@@ -28,7 +28,8 @@ namespace DotNetCore.CAP.SqlServer
{
if (message == null) throw new ArgumentNullException(nameof(message));
var sql = $"UPDATE [{_schema}].[Published] SET [Retries] = @Retries,[ExpiresAt] = @ExpiresAt,[StatusName]=@StatusName WHERE Id=@Id;";
var sql =
$"UPDATE [{_schema}].[Published] SET [Retries] = @Retries,[ExpiresAt] = @ExpiresAt,[StatusName]=@StatusName WHERE Id=@Id;";
_dbConnection.Execute(sql, message, _dbTransaction);
}
......@@ -36,7 +37,8 @@ namespace DotNetCore.CAP.SqlServer
{
if (message == null) throw new ArgumentNullException(nameof(message));
var sql = $"UPDATE [{_schema}].[Received] SET [Retries] = @Retries,[Content] = @Content,[ExpiresAt] = @ExpiresAt,[StatusName]=@StatusName WHERE Id=@Id;";
var sql =
$"UPDATE [{_schema}].[Received] SET [Retries] = @Retries,[Content] = @Content,[ExpiresAt] = @ExpiresAt,[StatusName]=@StatusName WHERE Id=@Id;";
_dbConnection.Execute(sql, message, _dbTransaction);
}
......@@ -45,7 +47,8 @@ namespace DotNetCore.CAP.SqlServer
if (message == null) throw new ArgumentNullException(nameof(message));
var sql = $"INSERT INTO [{_schema}].[Queue] values(@MessageId,@MessageType);";
_dbConnection.Execute(sql, new CapQueue { MessageId = message.Id, MessageType = MessageType.Publish }, _dbTransaction);
_dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Publish},
_dbTransaction);
}
public void EnqueueMessage(CapReceivedMessage message)
......@@ -53,7 +56,8 @@ namespace DotNetCore.CAP.SqlServer
if (message == null) throw new ArgumentNullException(nameof(message));
var sql = $"INSERT INTO [{_schema}].[Queue] values(@MessageId,@MessageType);";
_dbConnection.Execute(sql, new CapQueue { MessageId = message.Id, MessageType = MessageType.Subscribe }, _dbTransaction);
_dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Subscribe},
_dbTransaction);
}
public Task CommitAsync()
......
......@@ -10,7 +10,7 @@ namespace DotNetCore.CAP.Abstractions
public abstract class CapPublisherBase : ICapPublisher, IDisposable
{
protected IDbConnection DbConnection { get; set; }
protected IDbTransaction DbTranasaction { get; set; }
protected IDbTransaction DbTransaction { get; set; }
protected bool IsCapOpenedTrans { get; set; }
protected bool IsCapOpenedConn { get; set; }
protected bool IsUsingEF { get; set; }
......@@ -60,13 +60,15 @@ namespace DotNetCore.CAP.Abstractions
protected abstract void PrepareConnectionForEF();
protected abstract void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message);
protected abstract void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction,
CapPublishedMessage message);
protected abstract Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message);
protected abstract Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction,
CapPublishedMessage message);
protected virtual string Serialize<T>(T obj, string callbackName = null)
{
var serializer = (IContentSerializer)ServiceProvider.GetService(typeof(IContentSerializer));
var serializer = (IContentSerializer) ServiceProvider.GetService(typeof(IContentSerializer));
var message = new CapMessageDto(obj)
{
......@@ -85,11 +87,11 @@ namespace DotNetCore.CAP.Abstractions
IsCapOpenedConn = true;
DbConnection.Open();
}
DbTranasaction = dbTransaction;
if (DbTranasaction == null)
DbTransaction = dbTransaction;
if (DbTransaction == null)
{
IsCapOpenedTrans = true;
DbTranasaction = dbConnection.BeginTransaction(IsolationLevel.ReadCommitted);
DbTransaction = dbConnection.BeginTransaction(IsolationLevel.ReadCommitted);
}
}
......@@ -97,15 +99,17 @@ namespace DotNetCore.CAP.Abstractions
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (!IsUsingEF)
throw new InvalidOperationException("If you are using the EntityFramework, you need to configure the DbContextType first." +
" otherwise you need to use overloaded method with IDbConnection and IDbTransaction.");
throw new InvalidOperationException(
"If you are using the EntityFramework, you need to configure the DbContextType first." +
" otherwise you need to use overloaded method with IDbConnection and IDbTransaction.");
}
private void CheckIsAdoNet(string name)
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (IsUsingEF)
throw new InvalidOperationException("If you are using the EntityFramework, you do not need to use this overloaded.");
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)
......@@ -117,7 +121,7 @@ namespace DotNetCore.CAP.Abstractions
StatusName = StatusName.Scheduled
};
await ExecuteAsync(DbConnection, DbTranasaction, message);
await ExecuteAsync(DbConnection, DbTransaction, message);
ClosedCap();
......@@ -133,7 +137,7 @@ namespace DotNetCore.CAP.Abstractions
StatusName = StatusName.Scheduled
};
Execute(DbConnection, DbTranasaction, message);
Execute(DbConnection, DbTransaction, message);
ClosedCap();
......@@ -144,18 +148,16 @@ namespace DotNetCore.CAP.Abstractions
{
if (IsCapOpenedTrans)
{
DbTranasaction.Commit();
DbTranasaction.Dispose();
DbTransaction.Commit();
DbTransaction.Dispose();
}
if (IsCapOpenedConn)
{
DbConnection.Dispose();
}
}
public void Dispose()
{
DbTranasaction?.Dispose();
DbTransaction?.Dispose();
DbConnection?.Dispose();
}
......
......@@ -3,15 +3,15 @@
namespace DotNetCore.CAP.Abstractions
{
/// <summary>
/// A context for consumers, it used to be provider wapper of method description and received message.
/// A context for consumers, it used to be provider wrapper of method description and received message.
/// </summary>
public class ConsumerContext
{
/// <summary>
/// create a new instance of <see cref="ConsumerContext"/> .
/// create a new instance of <see cref="ConsumerContext" /> .
/// </summary>
/// <param name="descriptor">consumer method descriptor. </param>
/// <param name="message"> reveied message.</param>
/// <param name="message"> received message.</param>
public ConsumerContext(ConsumerExecutorDescriptor descriptor, MessageContext message)
{
ConsumerDescriptor = descriptor ?? throw new ArgumentNullException(nameof(descriptor));
......@@ -24,7 +24,7 @@ namespace DotNetCore.CAP.Abstractions
public ConsumerExecutorDescriptor ConsumerDescriptor { get; set; }
/// <summary>
/// consumer reveived message.
/// consumer received message.
/// </summary>
public MessageContext DeliverMessage { get; set; }
}
......
......@@ -3,22 +3,22 @@
namespace DotNetCore.CAP.Abstractions
{
/// <summary>
/// Defines an interface for selecting an cosumer service method to invoke for the current message.
/// Defines an interface for selecting an consumer service method to invoke for the current message.
/// </summary>
public interface IConsumerServiceSelector
{
/// <summary>
/// Selects a set of <see cref="ConsumerExecutorDescriptor"/> candidates for the current message associated with
/// </summary>
/// <returns>A set of <see cref="ConsumerExecutorDescriptor"/> candidates or <c>null</c>.</returns>
/// Selects a set of <see cref="ConsumerExecutorDescriptor" /> candidates for the current message associated with
/// </summary>
/// <returns>A set of <see cref="ConsumerExecutorDescriptor" /> candidates or <c>null</c>.</returns>
IReadOnlyList<ConsumerExecutorDescriptor> SelectCandidates();
/// <summary>
/// Selects the best <see cref="ConsumerExecutorDescriptor"/> candidate from <paramref name="candidates"/> for the
/// Selects the best <see cref="ConsumerExecutorDescriptor" /> candidate from <paramref name="candidates" /> for the
/// current message associated.
/// </summary>
/// <param name="key">topic or exchange router key.</param>
/// <param name="candidates">the set of <see cref="ConsumerExecutorDescriptor"/> candidates.</param>
/// <param name="candidates">the set of <see cref="ConsumerExecutorDescriptor" /> candidates.</param>
/// <returns></returns>
ConsumerExecutorDescriptor
SelectBestCandidate(string key, IReadOnlyList<ConsumerExecutorDescriptor> candidates);
......
......@@ -8,4 +8,4 @@ namespace DotNetCore.CAP.Abstractions
T DeSerialize<T>(string content) where T : CapMessageDto, new();
}
}
}
\ No newline at end of file
......@@ -8,22 +8,22 @@ namespace DotNetCore.CAP.Abstractions.ModelBinding
public struct ModelBindingResult
{
/// <summary>
/// Creates a <see cref="ModelBindingResult"/> representing a failed model binding operation.
/// Creates a <see cref="ModelBindingResult" /> representing a failed model binding operation.
/// </summary>
/// <returns>A <see cref="ModelBindingResult"/> representing a failed model binding operation.</returns>
/// <returns>A <see cref="ModelBindingResult" /> representing a failed model binding operation.</returns>
public static ModelBindingResult Failed()
{
return new ModelBindingResult(model: null, isSuccess: false);
return new ModelBindingResult(null, false);
}
/// <summary>
/// Creates a <see cref="ModelBindingResult"/> representing a successful model binding operation.
/// Creates a <see cref="ModelBindingResult" /> representing a successful model binding operation.
/// </summary>
/// <param name="model">The model value. May be <c>null.</c></param>
/// <returns>A <see cref="ModelBindingResult"/> representing a successful model bind.</returns>
/// <returns>A <see cref="ModelBindingResult" /> representing a successful model bind.</returns>
public static ModelBindingResult Success(object model)
{
return new ModelBindingResult(model, isSuccess: true);
return new ModelBindingResult(model, true);
}
private ModelBindingResult(object model, bool isSuccess)
......@@ -42,26 +42,16 @@ namespace DotNetCore.CAP.Abstractions.ModelBinding
public override string ToString()
{
if (IsSuccess)
{
return $"Success '{Model}'";
}
else
{
return $"Failed";
}
return $"Failed";
}
public override bool Equals(object obj)
{
var other = obj as ModelBindingResult?;
if (other == null)
{
return false;
}
else
{
return Equals(other.Value);
}
return Equals(other.Value);
}
public override int GetHashCode()
......@@ -81,10 +71,10 @@ namespace DotNetCore.CAP.Abstractions.ModelBinding
}
/// <summary>
/// Compares <see cref="ModelBindingResult"/> objects for equality.
/// Compares <see cref="ModelBindingResult" /> objects for equality.
/// </summary>
/// <param name="x">A <see cref="ModelBindingResult"/>.</param>
/// <param name="y">A <see cref="ModelBindingResult"/>.</param>
/// <param name="x">A <see cref="ModelBindingResult" />.</param>
/// <param name="y">A <see cref="ModelBindingResult" />.</param>
/// <returns><c>true</c> if the objects are equal, otherwise <c>false</c>.</returns>
public static bool operator ==(ModelBindingResult x, ModelBindingResult y)
{
......@@ -92,10 +82,10 @@ namespace DotNetCore.CAP.Abstractions.ModelBinding
}
/// <summary>
/// Compares <see cref="ModelBindingResult"/> objects for inequality.
/// Compares <see cref="ModelBindingResult" /> objects for inequality.
/// </summary>
/// <param name="x">A <see cref="ModelBindingResult"/>.</param>
/// <param name="y">A <see cref="ModelBindingResult"/>.</param>
/// <param name="x">A <see cref="ModelBindingResult" />.</param>
/// <param name="y">A <see cref="ModelBindingResult" />.</param>
/// <returns><c>true</c> if the objects are not equal, otherwise <c>false</c>.</returns>
public static bool operator !=(ModelBindingResult x, ModelBindingResult y)
{
......
......@@ -4,7 +4,7 @@ namespace DotNetCore.CAP.Abstractions
{
/// <inheritdoc />
/// <summary>
/// An abstract attribute that for kafka attribute or rabbitmq attribute
/// An abstract attribute that for kafka attribute or rabbit mq attribute
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
public abstract class TopicAttribute : Attribute
......@@ -20,8 +20,8 @@ namespace DotNetCore.CAP.Abstractions
public string Name { get; }
/// <summary>
/// kafak --> groups.id
/// rabbitmq --> queue.name
/// kafka --> groups.id
/// rabbit MQ --> queue.name
/// </summary>
public string Group { get; set; } = "cap.default.group";
}
......
......@@ -7,21 +7,19 @@ using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Builder
{
/// <summary>
/// app extensions for <see cref="IApplicationBuilder"/>
/// app extensions for <see cref="IApplicationBuilder" />
/// </summary>
public static class AppBuilderExtensions
{
///<summary>
/// <summary>
/// Enables cap for the current application
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/> instance this method extends.</param>
/// <returns>The <see cref="IApplicationBuilder"/> instance this method extends.</returns>
/// <param name="app">The <see cref="IApplicationBuilder" /> instance this method extends.</param>
/// <returns>The <see cref="IApplicationBuilder" /> instance this method extends.</returns>
public static IApplicationBuilder UseCap(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
CheckRequirement(app);
......@@ -33,9 +31,7 @@ namespace Microsoft.AspNetCore.Builder
if (provider.GetService<DashboardOptions>() != null)
{
if (provider.GetService<DiscoveryOptions>() != null)
{
app.UseMiddleware<GatewayProxyMiddleware>();
}
app.UseMiddleware<DashboardMiddleware>();
}
......@@ -46,21 +42,18 @@ namespace Microsoft.AspNetCore.Builder
{
var marker = app.ApplicationServices.GetService<CapMarkerService>();
if (marker == null)
{
throw new InvalidOperationException("AddCap() must be called on the service collection. eg: services.AddCap(...)");
}
throw new InvalidOperationException(
"AddCap() must be called on the service collection. eg: services.AddCap(...)");
var messageQueuemarker = app.ApplicationServices.GetService<CapMessageQueueMakerService>();
if (messageQueuemarker == null)
{
throw new InvalidOperationException("You must be config used message queue provider at AddCap() options! eg: services.AddCap(options=>{ options.UseKafka(...) })");
}
var messageQueueMarker = app.ApplicationServices.GetService<CapMessageQueueMakerService>();
if (messageQueueMarker == null)
throw new InvalidOperationException(
"You must be config used message queue provider at AddCap() options! eg: services.AddCap(options=>{ options.UseKafka(...) })");
var databaseMarker = app.ApplicationServices.GetService<CapDatabaseStorageMarkerService>();
if (databaseMarker == null)
{
throw new InvalidOperationException("You must be config used database provider at AddCap() options! eg: services.AddCap(options=>{ options.UseSqlServer(...) })");
}
throw new InvalidOperationException(
"You must be config used database provider at AddCap() options! eg: services.AddCap(options=>{ options.UseSqlServer(...) })");
}
}
}
\ No newline at end of file
......@@ -15,7 +15,6 @@ namespace DotNetCore.CAP
/// </summary>
public class CapDatabaseStorageMarkerService
{
}
/// <summary>
......@@ -23,7 +22,6 @@ namespace DotNetCore.CAP
/// </summary>
public class CapMessageQueueMakerService
{
}
/// <summary>
......@@ -37,7 +35,7 @@ namespace DotNetCore.CAP
}
/// <summary>
/// Gets the <see cref="IServiceCollection"/> where MVC services are configured.
/// Gets the <see cref="IServiceCollection" /> where MVC services are configured.
/// </summary>
public IServiceCollection Services { get; }
......@@ -51,7 +49,7 @@ namespace DotNetCore.CAP
}
/// <summary>
/// Add an <see cref="ICapPublisher"/>.
/// Add an <see cref="ICapPublisher" />.
/// </summary>
/// <typeparam name="T">The type of the service.</typeparam>
public virtual CapBuilder AddProducerService<T>()
......
using System;
using System.Collections.Generic;
using DotNetCore.CAP.Models;
namespace DotNetCore.CAP
{
......@@ -8,8 +9,6 @@ namespace DotNetCore.CAP
/// </summary>
public class CapOptions
{
internal IList<ICapOptionsExtension> Extensions { get; }
/// <summary>
/// Default value for polling delay timeout, in seconds.
/// </summary>
......@@ -21,7 +20,7 @@ namespace DotNetCore.CAP
public const int DefaultQueueProcessorCount = 2;
/// <summary>
/// Default succeeded message expriation timespan, in seconds.
/// Default succeeded message expiration time span, in seconds.
/// </summary>
public const int DefaultSucceedMessageExpirationAfter = 24 * 3600;
......@@ -39,8 +38,10 @@ namespace DotNetCore.CAP
Extensions = new List<ICapOptionsExtension>();
}
internal IList<ICapOptionsExtension> Extensions { get; }
/// <summary>
/// Productor job polling delay time.
/// Producer job polling delay time.
/// Default is 15 sec.
/// </summary>
public int PollingDelay { get; set; }
......@@ -52,8 +53,8 @@ namespace DotNetCore.CAP
public int QueueProcessorCount { get; set; }
/// <summary>
/// Sent or received succeed message after timespan of due, then the message will be deleted at due time.
/// Dafault is 24*3600 seconds.
/// Sent or received succeed message after timespan of due, then the message will be deleted at due time.
/// Default is 24*3600 seconds.
/// </summary>
public int SucceedMessageExpiredAfter { get; set; }
......@@ -66,7 +67,7 @@ namespace DotNetCore.CAP
/// <summary>
/// We’ll invoke this call-back with message type,name,content when requeue failed message.
/// </summary>
public Action<Models.MessageType, string, string> FailedCallback { get; set; }
public Action<MessageType, string, string> FailedCallback { get; set; }
/// <summary>
/// Registers an extension that will be executed when building services.
......
......@@ -11,16 +11,16 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Contains extension methods to <see cref="IServiceCollection"/> for configuring consistence services.
/// Contains extension methods to <see cref="IServiceCollection" /> for configuring consistence services.
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds and configures the consistence services for the consitence.
/// Adds and configures the consistence services for the consistency.
/// </summary>
/// <param name="services">The services available in the application.</param>
/// <param name="setupAction">An action to configure the <see cref="CapOptions"/>.</param>
/// <returns>An <see cref="CapBuilder"/> for application services.</returns>
/// <param name="setupAction">An action to configure the <see cref="CapOptions" />.</param>
/// <returns>An <see cref="CapBuilder" /> for application services.</returns>
public static CapBuilder AddCap(
this IServiceCollection services,
Action<CapOptions> setupAction)
......@@ -42,7 +42,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddSingleton<IProcessingServer, CapProcessingServer>();
services.AddSingleton<IBootstrapper, DefaultBootstrapper>();
services.AddSingleton<IStateChanger, StateChanger>();
//Processors
services.AddTransient<PublishQueuer>();
services.AddTransient<SubscribeQueuer>();
......@@ -51,15 +51,13 @@ namespace Microsoft.Extensions.DependencyInjection
//Executors
services.AddSingleton<IQueueExecutorFactory, QueueExecutorFactory>();
services.AddSingleton<IQueueExecutor, SubscibeQueueExecutor>();
services.AddSingleton<IQueueExecutor, SubscribeQueueExecutor>();
//Options and extension service
var options = new CapOptions();
setupAction(options);
foreach (var serviceExtension in options.Extensions)
{
serviceExtension.AddServices(services);
}
services.AddSingleton(options);
return new CapBuilder(services);
......@@ -69,19 +67,13 @@ namespace Microsoft.Extensions.DependencyInjection
{
var consumerListenerServices = new List<KeyValuePair<Type, Type>>();
foreach (var rejectedServices in services)
{
if (rejectedServices.ImplementationType != null
&& typeof(ICapSubscribe).IsAssignableFrom(rejectedServices.ImplementationType))
{
consumerListenerServices.Add(new KeyValuePair<Type, Type>(typeof(ICapSubscribe),
rejectedServices.ImplementationType));
}
}
foreach (var service in consumerListenerServices)
{
services.AddTransient(service.Key, service.Value);
}
}
}
}
\ No newline at end of file
......@@ -6,26 +6,26 @@ using System.Threading;
namespace DotNetCore.CAP
{
#region Cache<T> class
/// <summary>
/// This is a generic cache subsystem based on key/value pairs, where key is generic, too. Key must be unique.
/// Every cache entry has its own timeout.
/// Cache is thread safe and will delete expired entries on its own using System.Threading.Timers (which run on <see cref="ThreadPool"/> threads).
/// Cache is thread safe and will delete expired entries on its own using System.Threading.Timers (which run on
/// <see cref="ThreadPool" /> threads).
/// </summary>
public class Cache<K, T> : IDisposable
{
#region Constructor and class members
/// <summary>
/// Initializes a new instance of the <see cref="Cache{K,T}"/> class.
/// </summary>
public Cache() { }
private Dictionary<K, T> _cache = new Dictionary<K, T>();
private Dictionary<K, Timer> _timers = new Dictionary<K, Timer>();
private ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
private readonly Dictionary<K, T> _cache = new Dictionary<K, T>();
private readonly Dictionary<K, Timer> _timers = new Dictionary<K, Timer>();
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
#endregion
#region IDisposable implementation & Clear
private bool disposed = false;
private bool disposed;
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
......@@ -40,7 +40,8 @@ namespace DotNetCore.CAP
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing">
/// <c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
/// <c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (!disposed)
......@@ -67,20 +68,26 @@ namespace DotNetCore.CAP
{
try
{
foreach (Timer t in _timers.Values)
foreach (var t in _timers.Values)
t.Dispose();
}
catch
{ }
{
}
_timers.Clear();
_cache.Clear();
}
finally { _locker.ExitWriteLock(); }
finally
{
_locker.ExitWriteLock();
}
}
#endregion
#region CheckTimer
// Checks whether a specific timer already exists and adds a new one, if not
private void CheckTimer(K key, TimeSpan? cacheTimeout, bool restartTimerIfExists)
{
......@@ -89,41 +96,48 @@ namespace DotNetCore.CAP
if (_timers.TryGetValue(key, out timer))
{
if (restartTimerIfExists)
{
timer.Change(
cacheTimeout ?? Timeout.InfiniteTimeSpan,
Timeout.InfiniteTimeSpan);
}
}
else
{
_timers.Add(
key,
new Timer(
new TimerCallback(RemoveByTimer),
RemoveByTimer,
key,
cacheTimeout ?? Timeout.InfiniteTimeSpan,
Timeout.InfiniteTimeSpan));
}
}
private void RemoveByTimer(object state)
{
Remove((K)state);
Remove((K) state);
}
#endregion
#region AddOrUpdate, Get, Remove, Exists, Clear
/// <summary>
/// Adds or updates the specified cache-key with the specified cacheObject and applies a specified timeout (in seconds) to this key.
/// Adds or updates the specified cache-key with the specified cacheObject and applies a specified timeout (in seconds)
/// to this key.
/// </summary>
/// <param name="key">The cache-key to add or update.</param>
/// <param name="cacheObject">The cache object to store.</param>
/// <param name="cacheTimeout">The cache timeout (lifespan) of this object. Must be 1 or greater.
/// Specify Timeout.Infinite to keep the entry forever.</param>
/// <param name="restartTimerIfExists">(Optional). If set to <c>true</c>, the timer for this cacheObject will be reset if the object already
/// exists in the cache. (Default = false).</param>
/// <param name="cacheTimeout">
/// The cache timeout (lifespan) of this object. Must be 1 or greater.
/// Specify Timeout.Infinite to keep the entry forever.
/// </param>
/// <param name="restartTimerIfExists">
/// (Optional). If set to <c>true</c>, the timer for this cacheObject will be reset if the object already
/// exists in the cache. (Default = false).
/// </param>
public void AddOrUpdate(K key, T cacheObject, TimeSpan? cacheTimeout, bool restartTimerIfExists = false)
{
if (disposed) return;
if (disposed) return;
_locker.EnterWriteLock();
try
......@@ -135,11 +149,15 @@ namespace DotNetCore.CAP
else
_cache[key] = cacheObject;
}
finally { _locker.ExitWriteLock(); }
finally
{
_locker.ExitWriteLock();
}
}
/// <summary>
/// Adds or updates the specified cache-key with the specified cacheObject and applies <c>Timeout.Infinite</c> to this key.
/// Adds or updates the specified cache-key with the specified cacheObject and applies <c>Timeout.Infinite</c> to this
/// key.
/// </summary>
/// <param name="key">The cache-key to add or update.</param>
/// <param name="cacheObject">The cache object to store.</param>
......@@ -168,9 +186,12 @@ namespace DotNetCore.CAP
try
{
T rv;
return (_cache.TryGetValue(key, out rv) ? rv : default(T));
return _cache.TryGetValue(key, out rv) ? rv : default(T);
}
finally
{
_locker.ExitReadLock();
}
finally { _locker.ExitReadLock(); }
}
/// <summary>
......@@ -192,7 +213,10 @@ namespace DotNetCore.CAP
{
return _cache.TryGetValue(key, out value);
}
finally { _locker.ExitReadLock(); }
finally
{
_locker.ExitReadLock();
}
}
/// <summary>
......@@ -207,18 +231,26 @@ namespace DotNetCore.CAP
try
{
var removers = (from k in _cache.Keys.Cast<K>()
where keyPattern(k)
select k).ToList();
where keyPattern(k)
select k).ToList();
foreach (K workKey in removers)
foreach (var workKey in removers)
{
try { _timers[workKey].Dispose(); }
catch { }
try
{
_timers[workKey].Dispose();
}
catch
{
}
_timers.Remove(workKey);
_cache.Remove(workKey);
}
}
finally { _locker.ExitWriteLock(); }
finally
{
_locker.ExitWriteLock();
}
}
/// <summary>
......@@ -235,13 +267,21 @@ namespace DotNetCore.CAP
{
if (_cache.ContainsKey(key))
{
try { _timers[key].Dispose(); }
catch { }
try
{
_timers[key].Dispose();
}
catch
{
}
_timers.Remove(key);
_cache.Remove(key);
}
}
finally { _locker.ExitWriteLock(); }
finally
{
_locker.ExitWriteLock();
}
}
/// <summary>
......@@ -258,33 +298,40 @@ namespace DotNetCore.CAP
{
return _cache.ContainsKey(key);
}
finally { _locker.ExitReadLock(); }
finally
{
_locker.ExitReadLock();
}
}
#endregion
}
#endregion
#region Other Cache classes (derived)
/// <summary>
/// This is a generic cache subsystem based on key/value pairs, where key is a string.
/// You can add any item to this cache as long as the key is unique, so treat keys as something like namespaces and build them with a
/// You can add any item to this cache as long as the key is unique, so treat keys as something like namespaces and
/// build them with a
/// specific system/syntax in your application.
/// Every cache entry has its own timeout.
/// Cache is thread safe and will delete expired entries on its own using System.Threading.Timers (which run on <see cref="ThreadPool"/> threads).
/// Cache is thread safe and will delete expired entries on its own using System.Threading.Timers (which run on
/// <see cref="ThreadPool" /> threads).
/// </summary>
//public class Cache<T> : Cache<string, T>
//{
//}
/// <summary>
/// The non-generic Cache class instanciates a Cache{object} that can be used with any type of (mixed) contents.
/// It also publishes a static <c>.Global</c> member, so a cache can be used even without creating a dedicated instance.
/// It also publishes a static <c>.Global</c> member, so a cache can be used even without creating a dedicated
/// instance.
/// The <c>.Global</c> member is lazy instanciated.
/// </summary>
public class CapCache : Cache<string, object>
{
#region Static Global Cache instance
private static Lazy<CapCache> global = new Lazy<CapCache>();
private static readonly Lazy<CapCache> global = new Lazy<CapCache>();
/// <summary>
/// Gets the global shared cache instance valid for the entire process.
/// </summary>
......@@ -292,8 +339,9 @@ namespace DotNetCore.CAP
/// The global shared cache instance.
/// </value>
public static CapCache Global => global.Value;
#endregion
}
#endregion
}
#endregion
}
\ No newline at end of file
......@@ -28,7 +28,7 @@ namespace DotNetCore.CAP.Dashboard
_command(context, id);
}
context.Response.StatusCode = (int)HttpStatusCode.NoContent;
context.Response.StatusCode = (int) HttpStatusCode.NoContent;
}
}
}
\ No newline at end of file
......@@ -10,12 +10,13 @@ namespace DotNetCore.CAP
{
public class DashboardMiddleware
{
private readonly DashboardOptions _options;
private readonly RequestDelegate _next;
private readonly IStorage _storage;
private readonly DashboardOptions _options;
private readonly RouteCollection _routes;
private readonly IStorage _storage;
public DashboardMiddleware(RequestDelegate next, DashboardOptions options, IStorage storage, RouteCollection routes)
public DashboardMiddleware(RequestDelegate next, DashboardOptions options, IStorage storage,
RouteCollection routes)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
_options = options ?? throw new ArgumentNullException(nameof(options));
......@@ -40,17 +41,15 @@ namespace DotNetCore.CAP
var findResult = _routes.FindDispatcher(context.Request.Path.Value);
if (findResult == null)
{
return _next.Invoke(context);
}
if (_options.Authorization.Any(filter => !filter.Authorize(dashboardContext)))
{
var isAuthenticated = context.User?.Identity?.IsAuthenticated;
context.Response.StatusCode = isAuthenticated == true
? (int)HttpStatusCode.Forbidden
: (int)HttpStatusCode.Unauthorized;
? (int) HttpStatusCode.Forbidden
: (int) HttpStatusCode.Unauthorized;
return Task.CompletedTask;
}
......@@ -66,4 +65,4 @@ namespace DotNetCore.CAP
}
}
}
}
}
\ No newline at end of file
......@@ -10,7 +10,7 @@ namespace DotNetCore.CAP
{
AppPath = "/";
PathMatch = "/cap";
Authorization = new[] { new LocalRequestsOnlyAuthorizationFilter() };
Authorization = new[] {new LocalRequestsOnlyAuthorizationFilter()};
StatsPollingInterval = 2000;
}
......@@ -28,5 +28,4 @@ namespace DotNetCore.CAP
/// </summary>
public int StatsPollingInterval { get; set; }
}
}
}
\ No newline at end of file
using System;
using DotNetCore.CAP;
using DotNetCore.CAP.Dashboard;
using DotNetCore.CAP.Dashboard.GatewayProxy;
using DotNetCore.CAP.Dashboard.GatewayProxy.Requester;
using Microsoft.Extensions.DependencyInjection;
namespace DotNetCore.CAP
{
using Dashboard;
using Dashboard.GatewayProxy;
using Dashboard.GatewayProxy.Requester;
using Microsoft.Extensions.DependencyInjection;
internal sealed class DashboardOptionsExtension : ICapOptionsExtension
{
private readonly Action<DashboardOptions> _options;
......@@ -20,8 +20,8 @@ namespace DotNetCore.CAP
{
var dashboardOptions = new DashboardOptions();
_options?.Invoke(dashboardOptions);
services.AddSingleton(dashboardOptions);
services.AddSingleton(DashboardRoutes.Routes);
services.AddSingleton(dashboardOptions);
services.AddSingleton(DashboardRoutes.Routes);
services.AddSingleton<IHttpRequester, HttpClientHttpRequester>();
services.AddSingleton<IHttpClientCache, MemoryHttpClientCache>();
services.AddSingleton<IRequestMapper, RequestMapper>();
......@@ -31,13 +31,11 @@ namespace DotNetCore.CAP
namespace Microsoft.Extensions.DependencyInjection
{
using DotNetCore.CAP;
public static class CapOptionsExtensions
{
public static CapOptions UseDashboard(this CapOptions capOptions)
{
return capOptions.UseDashboard(opt => {});
return capOptions.UseDashboard(opt => { });
}
public static CapOptions UseDashboard(this CapOptions capOptions, Action<DashboardOptions> options)
......
......@@ -22,12 +22,10 @@ namespace DotNetCore.CAP.Dashboard
protected override void WriteResponse(DashboardResponse response)
{
foreach (var resourceName in _resourceNames)
{
WriteResource(
response,
_assembly,
$"{_baseNamespace}.{resourceName}");
}
}
}
}
\ No newline at end of file
......@@ -20,18 +20,14 @@ namespace DotNetCore.CAP.Dashboard
if (!"POST".Equals(request.Method, StringComparison.OrdinalIgnoreCase))
{
response.StatusCode = (int)HttpStatusCode.MethodNotAllowed;
response.StatusCode = (int) HttpStatusCode.MethodNotAllowed;
return Task.FromResult(false);
}
if (_command(context))
{
response.StatusCode = (int)HttpStatusCode.NoContent;
}
response.StatusCode = (int) HttpStatusCode.NoContent;
else
{
response.StatusCode = 422;
}
return Task.FromResult(true);
}
......
......@@ -12,34 +12,32 @@ body {
}
/* Wrapper for page content to push down footer */
#wrap {
min-height: 100%;
height: auto !important;
height: 100%;
/* Negative indent footer by its height */
margin: 0 auto -60px;
min-height: 100%;
/* Pad bottom by footer height */
padding: 0 0 60px;
}
/* Set the fixed height of the footer here */
#footer {
background-color: #f5f5f5;
}
#footer { background-color: #f5f5f5; }
/* Custom page CSS
-------------------------------------------------- */
.container .credit {
margin: 20px 0;
}
.container .credit { margin: 20px 0; }
.page-header {
margin-top: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
white-space: nowrap;
}
.btn-death {
......@@ -48,37 +46,33 @@ body {
color: #fff;
}
.btn-death:hover {
background-color: #666;
border-color: #555;
color: #fff;
}
.list-group .list-group-item .glyphicon {
margin-right: 3px;
.btn-death:hover {
background-color: #666;
border-color: #555;
color: #fff;
}
.list-group .list-group-item .glyphicon { margin-right: 3px; }
.breadcrumb {
margin-bottom: 10px;
background-color: inherit;
margin-bottom: 10px;
padding: 0;
}
.btn-toolbar-label {
padding: 7px 0;
vertical-align: middle;
display: inline-block;
margin-left: 5px;
padding: 7px 0;
vertical-align: middle;
}
.btn-toolbar-label-sm {
padding: 5px 0;
}
.btn-toolbar-label-sm { padding: 5px 0; }
.btn-toolbar-spacer {
width: 5px;
display: inline-block;
height: 1px;
width: 5px;
}
a:hover .label-hover {
......@@ -86,31 +80,23 @@ a:hover .label-hover {
color: #fff !important;
}
.expander {
cursor: pointer;
}
.expander { cursor: pointer; }
.expandable {
display: none;
}
.expandable { display: none; }
.table-inner {
margin-bottom: 7px;
font-size: 90%;
margin-bottom: 7px;
}
.min-width {
width: 1%;
white-space: nowrap;
width: 1%;
}
.align-right {
text-align: right;
}
.align-right { text-align: right; }
.table > tbody > tr.hover:hover > td, .table > tbody > tr.hover:hover > th {
background-color: #f9f9f9;
}
.table > tbody > tr.hover:hover > td, .table > tbody > tr.hover:hover > th { background-color: #f9f9f9; }
.table > tbody > tr.highlight > td, .table > tbody > tr.highlight > th {
background-color: #fcf8e3;
......@@ -122,246 +108,196 @@ a:hover .label-hover {
border-color: #f5e8ce;
}
.word-break {
word-break: break-all;
}
.word-break { word-break: break-all; }
/* Statistics widget
-------------------------------------------------- */
#stats .list-group-item {
border-color: #e7e7e7;
background-color: #f8f8f8;
border-color: #e7e7e7;
}
#stats a.list-group-item {
color: #777;
}
#stats a.list-group-item { color: #777; }
#stats a.list-group-item:hover,
#stats a.list-group-item:focus {
color: #333;
}
#stats a.list-group-item:hover,
#stats a.list-group-item:focus { color: #333; }
#stats .list-group-item.active,
#stats .list-group-item.active:hover,
#stats .list-group-item.active:focus {
color: #555;
background-color: #e7e7e7;
border-color: #e7e7e7;
color: #555;
}
.table td.failed-job-details {
padding-top: 0;
padding-bottom: 0;
border-top: none;
background-color: #f5f5f5;
border-top: none;
padding-bottom: 0;
padding-top: 0;
}
.obsolete-data, .obsolete-data a, .obsolete-data pre, .obsolete-data .label {
color: #999;
}
.obsolete-data, .obsolete-data a, .obsolete-data pre, .obsolete-data .label { color: #999; }
.obsolete-data pre, .obsolete-data .label {
background-color: #f5f5f5;
}
.obsolete-data pre, .obsolete-data .label { background-color: #f5f5f5; }
.obsolete-data .glyphicon-question-sign {
font-size: 80%;
color: #999;
}
.obsolete-data .glyphicon-question-sign {
color: #999;
font-size: 80%;
}
.stack-trace {
padding: 10px;
border: none;
padding: 10px;
}
.st-type {
font-weight: bold;
}
.st-type { font-weight: bold; }
.st-param-name {
color: #666;
}
.st-param-name { color: #666; }
.st-file {
color: #999;
}
.st-file { color: #999; }
.st-method {
color: #00008B;
font-weight: bold;
}
.st-line {
color: #8B008B;
}
.st-line { color: #8B008B; }
.width-200 {
width: 200px;
}
.width-200 { width: 200px; }
.btn-toolbar-top {
margin-bottom: 10px;
}
.btn-toolbar-top { margin-bottom: 10px; }
.paginator .btn {
color: #428bca;
}
.paginator .btn { color: #428bca; }
.paginator .btn.active {
color: #333;
}
.paginator .btn.active { color: #333; }
/* Job Snippet styles */
.job-snippet {
-ms-border-radius: 4px;
background-color: #f5f5f5;
border-radius: 4px;
display: table;
margin-bottom: 20px;
padding: 15px;
display: table;
width: 100%;
-ms-border-radius: 4px;
border-radius: 4px;
background-color: #f5f5f5;
}
.job-snippet > * {
display: table-cell;
vertical-align: top;
}
.job-snippet-code {
.job-snippet > * {
display: table-cell;
vertical-align: top;
}
.job-snippet-code pre {
border: none;
margin: 0;
background: inherit;
padding: 0;
-ms-border-radius: 0;
border-radius: 0;
font-size: 14px;
}
.job-snippet-code { vertical-align: top; }
.job-snippet-code code {
display: block;
color: black;
background-color: #f5f5f5;
}
.job-snippet-code pre {
-ms-border-radius: 0;
background: inherit;
border: none;
border-radius: 0;
font-size: 14px;
margin: 0;
padding: 0;
}
.job-snippet-code pre .comment {
color: rgb(0, 128, 0);
}
.job-snippet-code code {
background-color: #f5f5f5;
color: black;
display: block;
}
.job-snippet-code pre .keyword {
color: rgb(0, 0, 255);
}
.job-snippet-code pre .comment { color: rgb(0, 128, 0); }
.job-snippet-code pre .string {
color: rgb(163, 21, 21);
}
.job-snippet-code pre .keyword { color: rgb(0, 0, 255); }
.job-snippet-code pre .type {
color: rgb(43, 145, 175);
}
.job-snippet-code pre .string { color: rgb(163, 21, 21); }
.job-snippet-code pre .xmldoc {
color: rgb(128, 128, 128);
}
.job-snippet-code pre .type { color: rgb(43, 145, 175); }
.job-snippet-code pre .xmldoc { color: rgb(128, 128, 128); }
.job-snippet-properties {
max-width: 200px;
padding-left: 5px;
}
.job-snippet-properties dl {
margin: 0;
}
.job-snippet-properties dl { margin: 0; }
.job-snippet-properties dl dt {
color: #999;
text-shadow: 0 1px white;
font-weight: normal;
}
.job-snippet-properties dl dd {
margin-left: 0;
margin-bottom: 5px;
}
.job-snippet-properties pre {
background-color: white;
-webkit-box-shadow: none;
-ms-box-shadow: none;
padding: 2px 4px;
border: none;
margin: 0;
}
.job-snippet-properties dl dt {
color: #999;
font-weight: normal;
text-shadow: 0 1px white;
}
.job-snippet-properties code {
color: black;
}
.job-snippet-properties dl dd {
margin-bottom: 5px;
margin-left: 0;
}
.job-snippet-properties pre {
-ms-box-shadow: none;
-webkit-box-shadow: none;
background-color: white;
border: none;
margin: 0;
padding: 2px 4px;
}
.job-snippet-properties code { color: black; }
.state-card {
position: relative;
display: block;
margin-bottom: 7px;
padding: 12px;
-ms-border-radius: 3px;
background-color: #fff;
border: 1px solid #e5e5e5;
-ms-border-radius: 3px;
border-radius: 3px;
display: block;
margin-bottom: 7px;
padding: 12px;
position: relative;
}
.state-card-title {
margin-bottom: 0;
}
.state-card-title { margin-bottom: 0; }
.state-card-title .pull-right {
margin-top: 3px;
}
.state-card-title .pull-right { margin-top: 3px; }
.state-card-text {
margin-top: 5px;
margin-bottom: 0;
margin-top: 5px;
}
.state-card h4 {
margin-top: 0;
}
.state-card h4 { margin-top: 0; }
.state-card-body {
padding: 10px;
margin: 10px -12px -12px -12px;
-ms-border-bottom-left-radius: 3px;
border-bottom-left-radius: 3px;
-ms-border-bottom-right-radius: 3px;
border-bottom-right-radius: 3px;
background-color: #f5f5f5;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
margin: 10px -12px -12px -12px;
padding: 10px;
}
.state-card-body dl {
margin-top: 5px;
margin-bottom: 0;
}
margin-bottom: 0;
margin-top: 5px;
}
.state-card-body pre {
white-space: pre-wrap; /* CSS 3 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
background: transparent;
padding: 0;
}
.state-card-body pre {
background: transparent;
padding: 0;
white-space: pre-wrap; /* CSS 3 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
.state-card-body .stack-trace {
background-color: transparent;
padding: 0 20px;
margin-bottom: 0;
}
.state-card-body .stack-trace {
background-color: transparent;
margin-bottom: 0;
padding: 0 20px;
}
.state-card-body .exception-type {
margin-top: 0;
}
.state-card-body .exception-type { margin-top: 0; }
/* Job History styles */
......@@ -370,17 +306,15 @@ a:hover .label-hover {
opacity: 0.8;
}
.job-history.job-history-current {
opacity: 1.0;
}
.job-history.job-history-current { opacity: 1.0; }
.job-history-heading {
padding: 5px 10px;
color: #666;
-ms-border-top-left-radius: 4px;
border-top-left-radius: 4px;
-ms-border-top-right-radius: 4px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
color: #666;
padding: 5px 10px;
}
.job-history-body {
......@@ -389,140 +323,118 @@ a:hover .label-hover {
}
.job-history-title {
margin-top: 0;
margin-bottom: 2px;
margin-top: 0;
}
.job-history dl {
margin-top: 5px;
margin-bottom: 5px;
margin-top: 5px;
}
.job-history .stack-trace {
background-color: transparent;
padding: 0 20px;
margin-bottom: 5px;
padding: 0 20px;
}
.job-history .exception-type {
margin-top: 0;
}
.job-history .exception-type { margin-top: 0; }
.job-history-current .job-history-heading,
.job-history-current small {
color: white;
}
.job-history-current small { color: white; }
a.job-method {
color: inherit;
}
a.job-method { color: inherit; }
.list-group .glyphicon {
top: 2px;
}
.list-group .glyphicon { top: 2px; }
span.metric {
-moz-transition: color .1s ease-out, background .1s ease-out, border .1s ease-out;
-ms-transition: color .1s ease-out, background .1s ease-out, border .1s ease-out;
-o-transition: color .1s ease-out, background .1s ease-out, border .1s ease-out;
-webkit-transition: color .1s ease-out, background .1s ease-out, border .1s ease-out;
background-color: transparent;
border: solid 1px;
border-radius: 10px;
display: inline-block;
min-width: 10px;
padding: 2px 6px;
font-size: 12px;
line-height: 1;
min-width: 10px;
padding: 2px 6px;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
background-color: transparent;
border-radius: 10px;
border: solid 1px;
-webkit-transition: color .1s ease-out, background .1s ease-out, border .1s ease-out;
-moz-transition: color .1s ease-out, background .1s ease-out, border .1s ease-out;
-ms-transition: color .1s ease-out, background .1s ease-out, border .1s ease-out;
-o-transition: color .1s ease-out, background .1s ease-out, border .1s ease-out;
transition: color .1s ease-out, background .1s ease-out, border .1s ease-out;
vertical-align: baseline;
white-space: nowrap;
}
span.metric.highlighted {
font-weight: bold;
color: #fff !important;
}
span.metric.highlighted {
color: #fff !important;
font-weight: bold;
}
span.metric-default {
color: #777;
border-color: #777;
color: #777;
}
span.metric-default.highlighted {
background-color: #777;
}
span.metric-default.highlighted { background-color: #777; }
div.metric {
border: solid 1px transparent;
-ms-border-radius: 4px;
-webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
border: solid 1px transparent;
border-radius: 4px;
-webkit-box-shadow: 0 1px 1px rgba(0,0,0,.05);
box-shadow: 0 1px 1px rgba(0,0,0,.05);
box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
margin-bottom: 20px;
transition: color .1s ease-out, background .1s ease-out, border .1s ease-out;
}
div.metric .metric-body {
padding: 15px 15px 0;
font-size: 26px;
text-align: center;
}
font-size: 26px;
padding: 15px 15px 0;
text-align: center;
}
div.metric .metric-description {
padding: 0 15px 15px;
text-align: center;
}
div.metric .metric-description {
padding: 0 15px 15px;
text-align: center;
}
div.metric.metric-default {
border-color: #ddd;
}
div.metric.metric-default { border-color: #ddd; }
div.metric-info,
span.metric-info {
color: #5bc0de;
border-color: #5bc0de;
color: #5bc0de;
}
span.metric-info.highlighted {
background-color: #5bc0de;
}
span.metric-info.highlighted { background-color: #5bc0de; }
div.metric-warning,
span.metric-warning {
color: #f0ad4e;
border-color: #f0ad4e;
color: #f0ad4e;
}
span.metric-warning.highlighted {
background-color: #f0ad4e;
}
span.metric-warning.highlighted { background-color: #f0ad4e; }
div.metric-success,
span.metric-success {
color: #5cb85c;
border-color: #5cb85c;
color: #5cb85c;
}
span.metric-success.highlighted {
background-color: #5cb85c;
}
span.metric-success.highlighted { background-color: #5cb85c; }
div.metric-danger,
span.metric-danger {
color: #d9534f;
border-color: #d9534f;
color: #d9534f;
}
span.metric-danger.highlighted {
background-color: #d9534f;
}
span.metric-danger.highlighted { background-color: #d9534f; }
span.metric-null,
div.metric-null {
display: none;
}
div.metric-null { display: none; }
@media (min-width: 992px) {
#stats {
......@@ -532,23 +444,17 @@ div.metric-null {
}
@media (min-width: 1200px) {
#stats {
width: 262.5px;
}
#stats { width: 262.5px; }
}
.subscribe-table td {
vertical-align: middle !important;
}
.subscribe-table td { vertical-align: middle !important; }
.subscribe-table td[rowspan] {
font-weight: bold;
}
.subscribe-table td[rowspan] { font-weight: bold; }
#legend {
background: rgba(173, 169, 169, 0.13);
color: #000;
position: absolute;
top: 110px;
right: 20px;
color: #000;
}
top: 110px;
}
\ No newline at end of file
(function (cap) {
(function(cap) {
cap.config = {
pollInterval: $("#capConfig").data("pollinterval"),
pollUrl: $("#capConfig").data("pollurl"),
locale: document.documentElement.lang
};
cap.Metrics = (function () {
cap.Metrics = (function() {
function Metrics() {
this._metrics = {};
}
Metrics.prototype.addElement = function (name, element) {
Metrics.prototype.addElement = function(name, element) {
if (!(name in this._metrics)) {
this._metrics[name] = [];
}
......@@ -18,7 +18,7 @@
this._metrics[name].push(element);
};
Metrics.prototype.getElements = function (name) {
Metrics.prototype.getElements = function(name) {
if (!(name in this._metrics)) {
return [];
}
......@@ -26,11 +26,11 @@
return this._metrics[name];
};
Metrics.prototype.getNames = function () {
var result = [];
var metrics = this._metrics;
Metrics.prototype.getNames = function() {
const result = [];
const metrics = this._metrics;
for (var name in metrics) {
for (let name in metrics) {
if (metrics.hasOwnProperty(name)) {
result.push(name);
}
......@@ -42,14 +42,14 @@
return Metrics;
})();
var BaseGraph = function () {
var BaseGraph = function() {
this.height = 200;
};
BaseGraph.prototype.update = function () {
var graph = this._graph;
BaseGraph.prototype.update = function() {
const graph = this._graph;
var width = $(graph.element).innerWidth();
const width = $(graph.element).innerWidth();
if (width !== graph.width) {
graph.configure({
width: width,
......@@ -60,48 +60,57 @@
graph.update();
};
BaseGraph.prototype._initGraph = function (element, settings, xSettings, ySettings) {
BaseGraph.prototype._initGraph = function(element, settings, xSettings, ySettings) {
var graph = this._graph = new Rickshaw.Graph($.extend({
element: element,
width: $(element).innerWidth(),
height: this.height,
interpolation: 'linear',
stroke: true
}, settings));
const graph = this._graph = new Rickshaw.Graph($.extend({
element: element,
width: $(element).innerWidth(),
height: this.height,
interpolation: "linear",
stroke: true
},
settings));
this._hoverDetail = new Rickshaw.Graph.HoverDetail({
graph: graph,
yFormatter: function (y) { return Math.floor(y); },
xFormatter: function (x) { return moment(new Date(x * 1000)).format("LLLL"); }
yFormatter: function(y) { return Math.floor(y); },
xFormatter: function(x) { return moment(new Date(x * 1000)).format("LLLL"); }
});
if (xSettings) {
this._xAxis = new Rickshaw.Graph.Axis.Time($.extend({
graph: graph,
timeFixture: new Rickshaw.Fixtures.Time.Local()
}, xSettings));
graph: graph,
timeFixture: new Rickshaw.Fixtures.Time.Local()
},
xSettings));
var legend = new Rickshaw.Graph.Legend({
element: document.querySelector('#legend'),
const legend = new Rickshaw.Graph.Legend({
element: document.querySelector("#legend"),
graph: graph
});
}
if (ySettings) {
this._yAxis = new Rickshaw.Graph.Axis.Y($.extend({
graph: graph,
tickFormat: Rickshaw.Fixtures.Number.formatKMBT
}, ySettings));
graph: graph,
tickFormat: Rickshaw.Fixtures.Number.formatKMBT
},
ySettings));
}
graph.render();
}
};
cap.RealtimeGraph = (function () {
cap.RealtimeGraph = (function() {
function RealtimeGraph(element,
pubSucceeded, pubFailed, pubSucceededStr, pubFailedStr,
recSucceeded, recFailed, recSucceededStr, recFailedStr
pubSucceeded,
pubFailed,
pubSucceededStr,
pubFailedStr,
recSucceeded,
recFailed,
recSucceededStr,
recFailedStr
) {
this._pubSucceeded = pubSucceeded;
this._pubSucceededStr = pubSucceededStr;
......@@ -115,48 +124,53 @@
this._recFailed = recFailed;
this._recFailedStr = recFailedStr;
this._initGraph(element, {
renderer: 'bar',
series: new Rickshaw.Series.FixedDuration([
{
name: pubSucceededStr,
color: '#33cc33'
},{
name: recSucceededStr,
color: '#3333cc'
},{
name: pubFailedStr,
color: '#ff3300'
},{
name: recFailedStr,
color: '#ff3399'
}
],
undefined,
{ timeInterval: 2000, maxDataPoints: 100 }
)
}, null, {});
this._initGraph(element,
{
renderer: "bar",
series: new Rickshaw.Series.FixedDuration([
{
name: pubSucceededStr,
color: "#33cc33"
}, {
name: recSucceededStr,
color: "#3333cc"
}, {
name: pubFailedStr,
color: "#ff3300"
}, {
name: recFailedStr,
color: "#ff3399"
}
],
undefined,
{ timeInterval: 2000, maxDataPoints: 100 }
)
},
null,
{});
}
RealtimeGraph.prototype = Object.create(BaseGraph.prototype);
RealtimeGraph.prototype.appendHistory = function (statistics) {
var newPubSucceeded = parseInt(statistics["published_succeeded:count"].intValue);
var newPubFailed = parseInt(statistics["published_failed:count"].intValue);
RealtimeGraph.prototype.appendHistory = function(statistics) {
const newPubSucceeded = parseInt(statistics["published_succeeded:count"].intValue);
const newPubFailed = parseInt(statistics["published_failed:count"].intValue);
var newRecSucceeded = parseInt(statistics["received_succeeded:count"].intValue);
var newRecFailed = parseInt(statistics["received_failed:count"].intValue);
const newRecSucceeded = parseInt(statistics["received_succeeded:count"].intValue);
const newRecFailed = parseInt(statistics["received_failed:count"].intValue);
if (this._pubSucceeded !== null && this._pubFailed !== null &&
this._recSucceeded !== null && this._recFailed !== null
if (this._pubSucceeded !== null &&
this._pubFailed !== null &&
this._recSucceeded !== null &&
this._recFailed !== null
) {
var pubSucceeded = newPubSucceeded - this._pubSucceeded;
var pubFailed = newPubFailed - this._pubFailed;
const pubSucceeded = newPubSucceeded - this._pubSucceeded;
const pubFailed = newPubFailed - this._pubFailed;
var recSucceeded = newRecSucceeded - this._recSucceeded;
var recFailed = newRecFailed - this._recFailed;
const recSucceeded = newRecSucceeded - this._recSucceeded;
const recFailed = newRecFailed - this._recFailed;
var dataObj = {};
const dataObj = {};
dataObj[this._pubFailedStr] = pubFailed;
dataObj[this._pubSucceededStr] = pubSucceeded;
dataObj[this._recFailedStr] = recFailed;
......@@ -176,31 +190,41 @@
return RealtimeGraph;
})();
cap.HistoryGraph = (function () {
function HistoryGraph(element, pubSucceeded, pubFailed, pubSucceededStr, pubFailedStr,
recSucceeded, recFailed, recSucceededStr, recFailedStr) {
this._initGraph(element, {
renderer: 'area',
series: [
{
color: '#33cc33',
data: pubSucceeded,
name: pubSucceededStr
},{
color: '#3333cc',
data: recSucceeded,
name: recSucceededStr
},{
color: '#ff3300',
data: pubFailed,
name: pubFailedStr
}, {
color: '#ff3399',
data: recFailed,
name: recFailedStr
}
]
}, {}, { ticksTreatment: 'glow' });
cap.HistoryGraph = (function() {
function HistoryGraph(element,
pubSucceeded,
pubFailed,
pubSucceededStr,
pubFailedStr,
recSucceeded,
recFailed,
recSucceededStr,
recFailedStr) {
this._initGraph(element,
{
renderer: "area",
series: [
{
color: "#33cc33",
data: pubSucceeded,
name: pubSucceededStr
}, {
color: "#3333cc",
data: recSucceeded,
name: recSucceededStr
}, {
color: "#ff3300",
data: pubFailed,
name: pubFailedStr
}, {
color: "#ff3399",
data: recFailed,
name: recFailedStr
}
]
},
{},
{ ticksTreatment: "glow" });
}
HistoryGraph.prototype = Object.create(BaseGraph.prototype);
......@@ -208,7 +232,7 @@
return HistoryGraph;
})();
cap.StatisticsPoller = (function () {
cap.StatisticsPoller = (function() {
function StatisticsPoller(metricsCallback, statisticsUrl, pollInterval) {
this._metricsCallback = metricsCallback;
this._listeners = [];
......@@ -217,14 +241,16 @@
this._intervalId = null;
}
StatisticsPoller.prototype.start = function () {
StatisticsPoller.prototype.start = function() {
var self = this;
var intervalFunc = function () {
const intervalFunc = function() {
try {
$.post(self._statisticsUrl, { metrics: self._metricsCallback() }, function (data) {
self._notifyListeners(data);
});
$.post(self._statisticsUrl,
{ metrics: self._metricsCallback() },
function(data) {
self._notifyListeners(data);
});
} catch (e) {
console.log(e);
}
......@@ -233,19 +259,19 @@
this._intervalId = setInterval(intervalFunc, this._pollInterval);
};
StatisticsPoller.prototype.stop = function () {
StatisticsPoller.prototype.stop = function() {
if (this._intervalId !== null) {
clearInterval(this._intervalId);
this._intervalId = null;
}
};
StatisticsPoller.prototype.addListener = function (listener) {
StatisticsPoller.prototype.addListener = function(listener) {
this._listeners.push(listener);
};
StatisticsPoller.prototype._notifyListeners = function (statistics) {
var length = this._listeners.length;
StatisticsPoller.prototype._notifyListeners = function(statistics) {
const length = this._listeners.length;
var i;
for (i = 0; i < length; i++) {
......@@ -256,36 +282,36 @@
return StatisticsPoller;
})();
cap.Page = (function () {
cap.Page = (function() {
function Page(config) {
this._metrics = new cap.Metrics();
var self = this;
this._poller = new cap.StatisticsPoller(
function () { return self._metrics.getNames(); },
function() { return self._metrics.getNames(); },
config.pollUrl,
config.pollInterval);
this._initialize(config.locale);
this.realtimeGraph = this._createRealtimeGraph('realtimeGraph');
this.historyGraph = this._createHistoryGraph('historyGraph');
this.realtimeGraph = this._createRealtimeGraph("realtimeGraph");
this.historyGraph = this._createHistoryGraph("historyGraph");
this._poller.start();
};
Page.prototype._createRealtimeGraph = function (elementId) {
var realtimeElement = document.getElementById(elementId);
Page.prototype._createRealtimeGraph = function(elementId) {
const realtimeElement = document.getElementById(elementId);
if (realtimeElement) {
var pubSucceeded = parseInt($(realtimeElement).data('published-succeeded'));
var pubFailed = parseInt($(realtimeElement).data('published-failed'));
var pubSucceededStr = $(realtimeElement).data('published-succeeded-string');
var pubFailedStr = $(realtimeElement).data('published-failed-string');
const pubSucceeded = parseInt($(realtimeElement).data("published-succeeded"));
const pubFailed = parseInt($(realtimeElement).data("published-failed"));
const pubSucceededStr = $(realtimeElement).data("published-succeeded-string");
const pubFailedStr = $(realtimeElement).data("published-failed-string");
var recSucceeded = parseInt($(realtimeElement).data('received-succeeded'));
var recFailed = parseInt($(realtimeElement).data('received-failed'));
var recSucceededStr = $(realtimeElement).data('received-succeeded-string');
var recFailedStr = $(realtimeElement).data('received-failed-string');
const recSucceeded = parseInt($(realtimeElement).data("received-succeeded"));
const recFailed = parseInt($(realtimeElement).data("received-failed"));
const recSucceededStr = $(realtimeElement).data("received-succeeded-string");
const recFailedStr = $(realtimeElement).data("received-failed-string");
var realtimeGraph = new Cap.RealtimeGraph(realtimeElement,
pubSucceeded,
......@@ -298,11 +324,11 @@
recFailedStr
);
this._poller.addListener(function (data) {
this._poller.addListener(function(data) {
realtimeGraph.appendHistory(data);
});
$(window).resize(function () {
$(window).resize(function() {
realtimeGraph.update();
});
......@@ -312,30 +338,30 @@
return null;
};
Page.prototype._createHistoryGraph = function (elementId) {
var historyElement = document.getElementById(elementId);
Page.prototype._createHistoryGraph = function(elementId) {
const historyElement = document.getElementById(elementId);
if (historyElement) {
var createSeries = function (obj) {
var series = [];
for (var date in obj) {
const createSeries = function(obj) {
const series = [];
for (let date in obj) {
if (obj.hasOwnProperty(date)) {
var value = obj[date];
var point = { x: Date.parse(date) / 1000, y: value };
const value = obj[date];
const point = { x: Date.parse(date) / 1000, y: value };
series.unshift(point);
}
}
return series;
};
var publishedSucceeded = createSeries($(historyElement).data("published-succeeded"));
var publishedFailed = createSeries($(historyElement).data("published-failed"));
var publishedSucceededStr = $(historyElement).data('published-succeeded-string');
var publishedFailedStr = $(historyElement).data('published-failed-string');
const publishedSucceeded = createSeries($(historyElement).data("published-succeeded"));
const publishedFailed = createSeries($(historyElement).data("published-failed"));
const publishedSucceededStr = $(historyElement).data("published-succeeded-string");
const publishedFailedStr = $(historyElement).data("published-failed-string");
var receivedSucceeded = createSeries($(historyElement).data("received-succeeded"));
var receivedFailed = createSeries($(historyElement).data("received-failed"));
var receivedSucceededStr = $(historyElement).data('received-succeeded-string');
var receivedFailedStr = $(historyElement).data('received-failed-string');
const receivedSucceeded = createSeries($(historyElement).data("received-succeeded"));
const receivedFailed = createSeries($(historyElement).data("received-failed"));
const receivedSucceededStr = $(historyElement).data("received-succeeded-string");
const receivedFailedStr = $(historyElement).data("received-failed-string");
var historyGraph = new Cap.HistoryGraph(historyElement,
publishedSucceeded,
......@@ -348,7 +374,7 @@
receivedFailedStr,
);
$(window).resize(function () {
$(window).resize(function() {
historyGraph.update();
});
......@@ -358,39 +384,39 @@
return null;
};
Page.prototype._initialize = function (locale) {
Page.prototype._initialize = function(locale) {
moment.locale(locale);
var updateRelativeDates = function () {
$('*[data-moment]').each(function () {
var $this = $(this);
var timestamp = $this.data('moment');
const updateRelativeDates = function() {
$("*[data-moment]").each(function() {
const $this = $(this);
const timestamp = $this.data("moment");
if (timestamp) {
var time = moment(timestamp, 'X');
const time = moment(timestamp, "X");
$this.html(time.fromNow())
.attr('title', time.format('llll'))
.attr('data-container', 'body');
.attr("title", time.format("llll"))
.attr("data-container", "body");
}
});
$('*[data-moment-title]').each(function () {
var $this = $(this);
var timestamp = $this.data('moment-title');
$("*[data-moment-title]").each(function() {
const $this = $(this);
const timestamp = $this.data("moment-title");
if (timestamp) {
var time = moment(timestamp, 'X');
$this.prop('title', time.format('llll'))
.attr('data-container', 'body');
const time = moment(timestamp, "X");
$this.prop("title", time.format("llll"))
.attr("data-container", "body");
}
});
$('*[data-moment-local]').each(function () {
var $this = $(this);
var timestamp = $this.data('moment-local');
$("*[data-moment-local]").each(function() {
const $this = $(this);
const timestamp = $this.data("moment-local");
if (timestamp) {
var time = moment(timestamp, 'X');
$this.html(time.format('l LTS'));
const time = moment(timestamp, "X");
$this.html(time.format("l LTS"));
}
});
};
......@@ -401,163 +427,180 @@
$("*[title]").tooltip();
var self = this;
$("*[data-metric]").each(function () {
var name = $(this).data('metric');
$("*[data-metric]").each(function() {
const name = $(this).data("metric");
self._metrics.addElement(name, this);
});
this._poller.addListener(function (metrics) {
for (var name in metrics) {
var elements = self._metrics.getElements(name);
for (var i = 0; i < elements.length; i++) {
var metric = metrics[name];
var metricClass = metric ? "metric-" + metric.style : "metric-null";
var highlighted = metric && metric.highlighted ? "highlighted" : null;
var value = metric ? metric.value : null;
this._poller.addListener(function(metrics) {
for (let name in metrics) {
const elements = self._metrics.getElements(name);
for (let i = 0; i < elements.length; i++) {
const metric = metrics[name];
const metricClass = metric ? `metric-${metric.style}` : "metric-null";
const highlighted = metric && metric.highlighted ? "highlighted" : null;
const value = metric ? metric.value : null;
$(elements[i])
.text(value)
.closest('.metric')
.closest(".metric")
.removeClass()
.addClass(["metric", metricClass, highlighted].join(' '));
.addClass(["metric", metricClass, highlighted].join(" "));
}
}
});
$(document).on('click', '*[data-ajax]', function (e) {
var $this = $(this);
var confirmText = $this.data('confirm');
if (!confirmText || confirm(confirmText)) {
$this.prop('disabled');
var loadingDelay = setTimeout(function () {
$this.button('loading');
}, 100);
$(document).on("click",
"*[data-ajax]",
function(e) {
var $this = $(this);
const confirmText = $this.data("confirm");
$.post($this.data('ajax'), function () {
clearTimeout(loadingDelay);
window.location.reload();
});
}
if (!confirmText || confirm(confirmText)) {
$this.prop("disabled");
var loadingDelay = setTimeout(function() {
$this.button("loading");
},
100);
$.post($this.data("ajax"),
function() {
clearTimeout(loadingDelay);
window.location.reload();
});
}
e.preventDefault();
});
e.preventDefault();
});
$(document).on('click', '.expander', function (e) {
var $expander = $(this),
$expandable = $expander.closest('tr').next().find('.expandable');
$(document).on("click",
".expander",
function(e) {
var $expander = $(this),
$expandable = $expander.closest("tr").next().find(".expandable");
if (!$expandable.is(':visible')) {
$expander.text('Less details...');
}
if (!$expandable.is(":visible")) {
$expander.text("Less details...");
}
$expandable.slideToggle(
150,
function () {
if (!$expandable.is(':visible')) {
$expander.text('More details...');
}
});
e.preventDefault();
});
$expandable.slideToggle(
150,
function() {
if (!$expandable.is(":visible")) {
$expander.text("More details...");
}
});
e.preventDefault();
});
$('.js-jobs-list').each(function () {
$(".js-jobs-list").each(function() {
var container = this;
var selectRow = function (row, isSelected) {
var $checkbox = $('.js-jobs-list-checkbox', row);
var selectRow = function(row, isSelected) {
const $checkbox = $(".js-jobs-list-checkbox", row);
if ($checkbox.length > 0) {
$checkbox.prop('checked', isSelected);
$(row).toggleClass('highlight', isSelected);
$checkbox.prop("checked", isSelected);
$(row).toggleClass("highlight", isSelected);
}
};
var toggleRowSelection = function (row) {
var $checkbox = $('.js-jobs-list-checkbox', row);
var toggleRowSelection = function(row) {
const $checkbox = $(".js-jobs-list-checkbox", row);
if ($checkbox.length > 0) {
var isSelected = $checkbox.is(':checked');
const isSelected = $checkbox.is(":checked");
selectRow(row, !isSelected);
}
};
var setListState = function (state) {
$('.js-jobs-list-select-all', container)
.prop('checked', state === 'all-selected')
.prop('indeterminate', state === 'some-selected');
var setListState = function(state) {
$(".js-jobs-list-select-all", container)
.prop("checked", state === "all-selected")
.prop("indeterminate", state === "some-selected");
$('.js-jobs-list-command', container)
.prop('disabled', state === 'none-selected');
$(".js-jobs-list-command", container)
.prop("disabled", state === "none-selected");
};
var updateListState = function () {
var selectedRows = $('.js-jobs-list-checkbox', container).map(function () {
return $(this).prop('checked');
var updateListState = function() {
const selectedRows = $(".js-jobs-list-checkbox", container).map(function() {
return $(this).prop("checked");
}).get();
var state = 'none-selected';
var state = "none-selected";
if (selectedRows.length > 0) {
state = 'some-selected';
state = "some-selected";
if ($.inArray(false, selectedRows) === -1) {
state = 'all-selected';
state = "all-selected";
} else if ($.inArray(true, selectedRows) === -1) {
state = 'none-selected';
state = "none-selected";
}
}
setListState(state);
};
$(this).on('click', '.js-jobs-list-checkbox', function (e) {
selectRow(
$(this).closest('.js-jobs-list-row').first(),
$(this).is(':checked'));
updateListState();
$(this).on("click",
".js-jobs-list-checkbox",
function(e) {
selectRow(
$(this).closest(".js-jobs-list-row").first(),
$(this).is(":checked"));
e.stopPropagation();
});
updateListState();
$(this).on('click', '.js-jobs-list-row', function (e) {
if ($(e.target).is('a')) return;
toggleRowSelection(this);
updateListState();
});
e.stopPropagation();
});
$(this).on('click', '.js-jobs-list-select-all', function () {
var selectRows = $(this).is(':checked');
$(this).on("click",
".js-jobs-list-row",
function(e) {
if ($(e.target).is("a")) return;
$('.js-jobs-list-row', container).each(function () {
selectRow(this, selectRows);
toggleRowSelection(this);
updateListState();
});
updateListState();
});
$(this).on("click",
".js-jobs-list-select-all",
function() {
var selectRows = $(this).is(":checked");
$(this).on('click', '.js-jobs-list-command', function (e) {
var $this = $(this);
var confirmText = $this.data('confirm');
$(".js-jobs-list-row", container).each(function() {
selectRow(this, selectRows);
});
var jobs = $("input[name='messages[]']:checked", container).map(function () {
return $(this).val();
}).get();
updateListState();
});
if (!confirmText || confirm(confirmText)) {
$this.prop('disabled');
var loadingDelay = setTimeout(function () {
$this.button('loading');
}, 100);
$.post($this.data('url'), { 'messages[]': jobs }, function () {
clearTimeout(loadingDelay);
window.location.reload();
});
}
$(this).on("click",
".js-jobs-list-command",
function(e) {
var $this = $(this);
const confirmText = $this.data("confirm");
const jobs = $("input[name='messages[]']:checked", container).map(function() {
return $(this).val();
}).get();
if (!confirmText || confirm(confirmText)) {
$this.prop("disabled");
var loadingDelay = setTimeout(function() {
$this.button("loading");
},
100);
$.post($this.data("url"),
{ 'messages[]': jobs },
function() {
clearTimeout(loadingDelay);
window.location.reload();
});
}
e.preventDefault();
});
e.preventDefault();
});
updateListState();
});
......@@ -567,20 +610,20 @@
})();
})(window.Cap = window.Cap || {});
$(function () {
$(function() {
Cap.page = new Cap.Page(Cap.config);
});
(function () {
(function() {
var json = null;
$(".openModal").click(function () {
var url = $(this).data("url");
$(".openModal").click(function() {
const url = $(this).data("url");
$.ajax({
url: url,
dataType: "json",
success: function (data) {
success: function(data) {
json = data;
$("#formatBtn").click();
$(".modal").modal("show");
......@@ -588,25 +631,25 @@ $(function () {
});
});
$("#formatBtn").click(function () {
$('#jsonContent').JSONView(json);
$("#formatBtn").click(function() {
$("#jsonContent").JSONView(json);
});
$("#rawBtn").click(function () {
$('#jsonContent').text(JSON.stringify(json));
$("#rawBtn").click(function() {
$("#jsonContent").text(JSON.stringify(json));
});
$("#expandBtn").click(function () {
$('#jsonContent').JSONView('expand');
$("#expandBtn").click(function() {
$("#jsonContent").JSONView("expand");
});
$("#collapseBtn").click(function () {
$('#jsonContent').JSONView('collapse');
$("#collapseBtn").click(function() {
$("#jsonContent").JSONView("collapse");
});
})();
function nodeSwitch(id) {
console.log("id:" + id);
document.cookie = "cap.node=" + escape(id) + ";";
console.log(`id:${id}`);
document.cookie = `cap.node=${escape(id)};`;
}
\ No newline at end of file
......@@ -11,42 +11,6 @@ namespace DotNetCore.CAP.Dashboard
{
private static readonly Dictionary<string, DashboardMetric> Metrics = new Dictionary<string, DashboardMetric>();
static DashboardMetrics()
{
AddMetric(ServerCount);
AddMetric(SubscriberCount);
AddMetric(PublishedFailedCountOrNull);
AddMetric(ReceivedFailedCountOrNull);
AddMetric(PublishedProcessingCount);
AddMetric(ReceivedProcessingCount);
AddMetric(PublishedSucceededCount);
AddMetric(ReceivedSucceededCount);
AddMetric(PublishedFailedCount);
AddMetric(ReceivedFailedCount);
}
public static void AddMetric(DashboardMetric metric)
{
if (metric == null) throw new ArgumentNullException(nameof(metric));
lock (Metrics)
{
Metrics[metric.Name] = metric;
}
}
public static IEnumerable<DashboardMetric> GetMetrics()
{
lock (Metrics)
{
return Metrics.Values.ToList();
}
}
public static readonly DashboardMetric ServerCount = new DashboardMetric(
"servers:count",
"Metrics_Servers",
......@@ -61,7 +25,7 @@ namespace DotNetCore.CAP.Dashboard
public static readonly DashboardMetric SubscriberCount = new DashboardMetric(
"retries:count",
"Metrics_Retries",
"Metrics_Retries",
page =>
{
long retryCount;
......@@ -91,14 +55,14 @@ namespace DotNetCore.CAP.Dashboard
public static readonly DashboardMetric ReceivedFailedCountOrNull = new DashboardMetric(
"received_failed:count-or-null",
"Metrics_FailedJobs",
page => page.Statistics.ReceivedFailed > 0
? new Metric(page.Statistics.ReceivedFailed.ToString("N0"))
{
Style = MetricStyle.Danger,
Highlighted = true,
Title = string.Format(Strings.Metrics_FailedCountOrNull, page.Statistics.ReceivedFailed)
}
: null);
page => page.Statistics.ReceivedFailed > 0
? new Metric(page.Statistics.ReceivedFailed.ToString("N0"))
{
Style = MetricStyle.Danger,
Highlighted = true,
Title = string.Format(Strings.Metrics_FailedCountOrNull, page.Statistics.ReceivedFailed)
}
: null);
//----------------------------------------------------
......@@ -111,12 +75,12 @@ namespace DotNetCore.CAP.Dashboard
});
public static readonly DashboardMetric ReceivedProcessingCount = new DashboardMetric(
"received_processing:count",
"Metrics_ProcessingJobs",
page => new Metric(page.Statistics.ReceivedProcessing.ToString("N0"))
{
Style = page.Statistics.ReceivedProcessing > 0 ? MetricStyle.Warning : MetricStyle.Default
});
"received_processing:count",
"Metrics_ProcessingJobs",
page => new Metric(page.Statistics.ReceivedProcessing.ToString("N0"))
{
Style = page.Statistics.ReceivedProcessing > 0 ? MetricStyle.Warning : MetricStyle.Default
});
//----------------------------------------------------
public static readonly DashboardMetric PublishedSucceededCount = new DashboardMetric(
......@@ -128,12 +92,12 @@ namespace DotNetCore.CAP.Dashboard
});
public static readonly DashboardMetric ReceivedSucceededCount = new DashboardMetric(
"received_succeeded:count",
"Metrics_SucceededJobs",
page => new Metric(page.Statistics.ReceivedSucceeded.ToString("N0"))
{
IntValue = page.Statistics.ReceivedSucceeded
});
"received_succeeded:count",
"Metrics_SucceededJobs",
page => new Metric(page.Statistics.ReceivedSucceeded.ToString("N0"))
{
IntValue = page.Statistics.ReceivedSucceeded
});
//----------------------------------------------------
......@@ -148,13 +112,49 @@ namespace DotNetCore.CAP.Dashboard
});
public static readonly DashboardMetric ReceivedFailedCount = new DashboardMetric(
"received_failed:count",
"Metrics_FailedJobs",
page => new Metric(page.Statistics.ReceivedFailed.ToString("N0"))
{
IntValue = page.Statistics.ReceivedFailed,
Style = page.Statistics.ReceivedFailed > 0 ? MetricStyle.Danger : MetricStyle.Default,
Highlighted = page.Statistics.ReceivedFailed > 0
});
"received_failed:count",
"Metrics_FailedJobs",
page => new Metric(page.Statistics.ReceivedFailed.ToString("N0"))
{
IntValue = page.Statistics.ReceivedFailed,
Style = page.Statistics.ReceivedFailed > 0 ? MetricStyle.Danger : MetricStyle.Default,
Highlighted = page.Statistics.ReceivedFailed > 0
});
static DashboardMetrics()
{
AddMetric(ServerCount);
AddMetric(SubscriberCount);
AddMetric(PublishedFailedCountOrNull);
AddMetric(ReceivedFailedCountOrNull);
AddMetric(PublishedProcessingCount);
AddMetric(ReceivedProcessingCount);
AddMetric(PublishedSucceededCount);
AddMetric(ReceivedSucceededCount);
AddMetric(PublishedFailedCount);
AddMetric(ReceivedFailedCount);
}
public static void AddMetric(DashboardMetric metric)
{
if (metric == null) throw new ArgumentNullException(nameof(metric));
lock (Metrics)
{
Metrics[metric.Name] = metric;
}
}
public static IEnumerable<DashboardMetric> GetMetrics()
{
lock (Metrics)
{
return Metrics.Values.ToList();
}
}
}
}
\ No newline at end of file
......@@ -34,8 +34,11 @@ namespace DotNetCore.CAP.Dashboard
public override string PathBase => _context.Request.PathBase.Value;
public override string LocalIpAddress => _context.Connection.LocalIpAddress.ToString();
public override string RemoteIpAddress => _context.Connection.RemoteIpAddress.ToString();
public override string GetQuery(string key) => _context.Request.Query[key];
public override string GetQuery(string key)
{
return _context.Request.Query[key];
}
public override async Task<IList<string>> GetFormValuesAsync(string key)
{
......
......@@ -7,12 +7,12 @@ namespace DotNetCore.CAP.Dashboard
internal class EmbeddedResourceDispatcher : IDashboardDispatcher
{
private readonly Assembly _assembly;
private readonly string _resourceName;
private readonly string _contentType;
private readonly string _resourceName;
public EmbeddedResourceDispatcher(
string contentType,
Assembly assembly,
string contentType,
Assembly assembly,
string resourceName)
{
if (assembly != null)
......@@ -47,9 +47,8 @@ namespace DotNetCore.CAP.Dashboard
using (var inputStream = assembly.GetManifestResourceStream(resourceName))
{
if (inputStream == null)
{
throw new ArgumentException($@"Resource with name {resourceName} not found in assembly {assembly}.");
}
throw new ArgumentException(
$@"Resource with name {resourceName} not found in assembly {assembly}.");
inputStream.CopyTo(response.Body);
}
......
......@@ -16,20 +16,18 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy
public class GatewayProxyMiddleware
{
public const string NodeCookieName = "cap.node";
private readonly ILogger _logger;
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly IRequestMapper _requestMapper;
private readonly IHttpRequester _requester;
private readonly IRequestMapper _requestMapper;
private INodeDiscoveryProvider _discoveryProvider;
protected HttpRequestMessage DownstreamRequest { get; set; }
public GatewayProxyMiddleware(RequestDelegate next,
ILoggerFactory loggerFactory,
IRequestMapper requestMapper,
IHttpRequester requester)
ILoggerFactory loggerFactory,
IRequestMapper requestMapper,
IHttpRequester requester)
{
_next = next;
_logger = loggerFactory.CreateLogger<GatewayProxyMiddleware>();
......@@ -37,6 +35,8 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy
_requester = requester;
}
protected HttpRequestMessage DownstreamRequest { get; set; }
public async Task Invoke(HttpContext context,
DiscoveryOptions discoveryOptions,
INodeDiscoveryProvider discoveryProvider)
......@@ -53,7 +53,7 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy
else
{
//For performance reasons, we need to put this functionality in the else
var isSwitchNode = request.Cookies.TryGetValue(NodeCookieName, out string requestNodeId);
var isSwitchNode = request.Cookies.TryGetValue(NodeCookieName, out var requestNodeId);
var isCurrentNode = discoveryOptions.NodeId.ToString() == requestNodeId;
var isNodesPage = request.Path.StartsWithSegments(new PathString(pathMatch + "/nodes"));
......@@ -65,7 +65,7 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy
{
_logger.LogDebug("started calling gateway proxy middleware");
if (TryGetRemoteNode(requestNodeId, out Node node))
if (TryGetRemoteNode(requestNodeId, out var node))
{
try
{
......@@ -94,31 +94,26 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy
public async Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response)
{
foreach (var httpResponseHeader in response.Content.Headers)
{
AddHeaderIfDoesntExist(context, httpResponseHeader);
}
var content = await response.Content.ReadAsByteArrayAsync();
AddHeaderIfDoesntExist(context,
new KeyValuePair<string, IEnumerable<string>>("Content-Length", new[] { content.Length.ToString() }));
new KeyValuePair<string, IEnumerable<string>>("Content-Length", new[] {content.Length.ToString()}));
context.Response.OnStarting(state =>
{
var httpContext = (HttpContext)state;
var httpContext = (HttpContext) state;
httpContext.Response.StatusCode = (int)response.StatusCode;
httpContext.Response.StatusCode = (int) response.StatusCode;
return Task.CompletedTask;
}, context);
using (Stream stream = new MemoryStream(content))
{
if (response.StatusCode != HttpStatusCode.NotModified)
{
await stream.CopyToAsync(context.Response.Body);
}
}
}
......@@ -139,10 +134,8 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy
KeyValuePair<string, IEnumerable<string>> httpResponseHeader)
{
if (!context.Response.Headers.ContainsKey(httpResponseHeader.Key))
{
context.Response.Headers.Add(httpResponseHeader.Key,
new StringValues(httpResponseHeader.Value.ToArray()));
}
}
}
}
\ No newline at end of file
......@@ -12,14 +12,14 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy
{
public class RequestMapper : IRequestMapper
{
private readonly string[] _unsupportedHeaders = { "host", "cookie" };
private const string SchemeDelimiter = "://";
private readonly string[] _unsupportedHeaders = {"host", "cookie"};
public async Task<HttpRequestMessage> Map(HttpRequest request)
{
try
{
var requestMessage = new HttpRequestMessage()
var requestMessage = new HttpRequestMessage
{
Content = await MapContent(request),
Method = MapMethod(request),
......@@ -45,11 +45,9 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy
FragmentString fragment = new FragmentString())
{
if (scheme == null)
{
throw new ArgumentNullException(nameof(scheme));
}
var combinedPath = (pathBase.HasValue || path.HasValue) ? (pathBase + path).ToString() : "/";
var combinedPath = pathBase.HasValue || path.HasValue ? (pathBase + path).ToString() : "/";
var encodedHost = host.ToString();
var encodedQuery = query.ToString();
......@@ -57,7 +55,7 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy
// PERF: Calculate string length to allocate correct buffer size for StringBuilder.
var length = scheme.Length + SchemeDelimiter.Length + encodedHost.Length
+ combinedPath.Length + encodedQuery.Length + encodedFragment.Length;
+ combinedPath.Length + encodedQuery.Length + encodedFragment.Length;
return new StringBuilder(length)
.Append(scheme)
......@@ -77,13 +75,11 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy
private async Task<HttpContent> MapContent(HttpRequest request)
{
if (request.Body == null)
{
return null;
}
var content = new ByteArrayContent(await ToByteArray(request.Body));
content.Headers.TryAddWithoutValidation("Content-Type", new[] { request.ContentType });
content.Headers.TryAddWithoutValidation("Content-Type", new[] {request.ContentType});
return content;
}
......@@ -101,12 +97,8 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy
private void MapHeaders(HttpRequest request, HttpRequestMessage requestMessage)
{
foreach (var header in request.Headers)
{
if (IsSupportedHeader(header))
{
requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
}
}
}
private async Task<byte[]> ToByteArray(Stream stream)
......
namespace DotNetCore.CAP.Dashboard.GatewayProxy
{
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace DotNetCore.CAP.Dashboard.GatewayProxy
{
public interface IRequestMapper
{
Task<HttpRequestMessage> Map(HttpRequest request);
......
......@@ -8,7 +8,8 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester
{
internal class HttpClientBuilder : IHttpClientBuilder
{
private readonly Dictionary<int, Func<DelegatingHandler>> _handlers = new Dictionary<int, Func<DelegatingHandler>>();
private readonly Dictionary<int, Func<DelegatingHandler>> _handlers =
new Dictionary<int, Func<DelegatingHandler>>();
public IHttpClient Create()
{
......@@ -41,13 +42,13 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester
/// </summary>
internal class HttpClientWrapper : IHttpClient
{
public HttpClient Client { get; }
public HttpClientWrapper(HttpClient client)
{
Client = client;
}
public HttpClient Client { get; }
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
{
return Client.SendAsync(request);
......
......@@ -44,15 +44,13 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester
var httpClient = _cacheHandlers.Get(cacheKey);
if (httpClient == null)
{
httpClient = builder.Create();
}
return httpClient;
}
private string GetCacheKey(HttpRequestMessage request, IHttpClientBuilder builder)
{
string baseUrl = $"{request.RequestUri.Scheme}://{request.RequestUri.Authority}";
var baseUrl = $"{request.RequestUri.Scheme}://{request.RequestUri.Authority}";
return baseUrl;
}
......
......@@ -5,7 +5,7 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester
public interface IHttpClientBuilder
{
/// <summary>
/// Creates the <see cref="HttpClient"/>
/// Creates the <see cref="HttpClient" />
/// </summary>
IHttpClient Create();
}
......
......@@ -5,7 +5,8 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester
{
public class MemoryHttpClientCache : IHttpClientCache
{
private readonly ConcurrentDictionary<string, ConcurrentQueue<IHttpClient>> _httpClientsCache = new ConcurrentDictionary<string, ConcurrentQueue<IHttpClient>>();
private readonly ConcurrentDictionary<string, ConcurrentQueue<IHttpClient>> _httpClientsCache =
new ConcurrentDictionary<string, ConcurrentQueue<IHttpClient>>();
public void Set(string id, IHttpClient client, TimeSpan expirationTime)
{
......@@ -30,9 +31,7 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester
{
IHttpClient client = null;
if (_httpClientsCache.TryGetValue(id, out var connectionQueue))
{
connectionQueue.TryDequeue(out client);
}
return client;
}
......
......@@ -31,9 +31,7 @@ namespace DotNetCore.CAP.Dashboard
public NonEscapedString MessagesSidebar(MessageType type)
{
if (type == MessageType.Publish)
{
return SidebarMenu(MessagesSidebarMenu.PublishedItems);
}
return SidebarMenu(MessagesSidebarMenu.ReceivedItems);
}
......@@ -80,12 +78,11 @@ namespace DotNetCore.CAP.Dashboard
public NonEscapedString StateLabel(string stateName)
{
if (String.IsNullOrWhiteSpace(stateName))
{
if (string.IsNullOrWhiteSpace(stateName))
return Raw($"<em>{Strings.Common_NoState}</em>");
}
return Raw($"<span class=\"label label-default\" style=\"background-color: {MessageHistoryRenderer.GetForegroundStateColor(stateName)};\">{stateName}</span>");
return Raw(
$"<span class=\"label label-default\" style=\"background-color: {MessageHistoryRenderer.GetForegroundStateColor(stateName)};\">{stateName}</span>");
}
public NonEscapedString RelativeTime(DateTime value)
......@@ -109,52 +106,36 @@ namespace DotNetCore.CAP.Dashboard
var builder = new StringBuilder();
if (displaySign)
{
builder.Append(duration.Value.TotalMilliseconds < 0 ? "-" : "+");
}
duration = duration.Value.Duration();
if (duration.Value.Days > 0)
{
builder.Append($"{duration.Value.Days}d ");
}
if (duration.Value.Hours > 0)
{
builder.Append($"{duration.Value.Hours}h ");
}
if (duration.Value.Minutes > 0)
{
builder.Append($"{duration.Value.Minutes}m ");
}
if (duration.Value.TotalHours < 1)
{
if (duration.Value.Seconds > 0)
{
builder.Append(duration.Value.Seconds);
if (duration.Value.Milliseconds > 0)
{
builder.Append($".{duration.Value.Milliseconds.ToString().PadLeft(3, '0')}");
}
builder.Append("s ");
}
else
{
if (duration.Value.Milliseconds > 0)
{
builder.Append($"{duration.Value.Milliseconds}ms ");
}
}
}
if (builder.Length <= 1)
{
builder.Append(" <1ms ");
}
builder.Remove(builder.Length - 1, 1);
......@@ -163,7 +144,7 @@ namespace DotNetCore.CAP.Dashboard
public string FormatProperties(IDictionary<string, string> properties)
{
return String.Join(", ", properties.Select(x => $"{x.Key}: \"{x.Value}\""));
return string.Join(", ", properties.Select(x => $"{x.Key}: \"{x.Value}\""));
}
public NonEscapedString QueueLabel(string queue)
......@@ -179,7 +160,7 @@ namespace DotNetCore.CAP.Dashboard
{
var parts = serverId.Split(':');
var shortenedId = parts.Length > 1
? String.Join(":", parts.Take(parts.Length - 1))
? string.Join(":", parts.Take(parts.Length - 1))
: serverId;
return new NonEscapedString(
......@@ -188,20 +169,40 @@ namespace DotNetCore.CAP.Dashboard
public NonEscapedString NodeSwitchLink(string id)
{
return Raw($"<a class=\"job-method\" onclick=\"nodeSwitch({id});\" href=\"javascript:;\">{Strings.NodePage_Switch}</a>");
return Raw(
$"<a class=\"job-method\" onclick=\"nodeSwitch({id});\" href=\"javascript:;\">{Strings.NodePage_Switch}</a>");
}
public NonEscapedString StackTrace(string stackTrace)
{
try
{
//return new NonEscapedString(StackTraceFormatter.FormatHtml(stackTrace, StackTraceHtmlFragments));
return new NonEscapedString(stackTrace);
}
catch (RegexMatchTimeoutException)
{
return new NonEscapedString(HtmlEncode(stackTrace));
}
}
public string HtmlEncode(string text)
{
return WebUtility.HtmlEncode(text);
}
#region MethodEscaped
public NonEscapedString MethodEscaped(MethodInfo method)
{
var @public = WrapKeyword("public");
var @async = string.Empty;
var async = string.Empty;
string @return;
var isAwaitable = CoercedAwaitableInfo.IsTypeAwaitable(method.ReturnType, out var coercedAwaitableInfo);
if (isAwaitable)
{
@async = WrapKeyword("async");
async = WrapKeyword("async");
var asyncResultType = coercedAwaitableInfo.AwaitableInfo.ResultType;
@return = WrapType("Task") + WrapIdentifier("<") + WrapType(asyncResultType) + WrapIdentifier(">");
......@@ -211,7 +212,7 @@ namespace DotNetCore.CAP.Dashboard
@return = WrapType(method.ReturnType);
}
var @name = method.Name;
var name = method.Name;
string paramType = null;
string paramName = null;
......@@ -227,7 +228,8 @@ namespace DotNetCore.CAP.Dashboard
var paramString = paramType == null ? "();" : $"({paramType} {paramName});";
var outputString = @public + " " + (string.IsNullOrEmpty(@async) ? "" : @async + " ") + @return + " " + @name + paramString;
var outputString = @public + " " + (string.IsNullOrEmpty(async) ? "" : async + " ") + @return + " " + name +
paramString;
return new NonEscapedString(outputString);
}
......@@ -235,26 +237,15 @@ namespace DotNetCore.CAP.Dashboard
private string WrapType(Type type)
{
if (type == null)
{
return string.Empty;
}
if (type.Name == "Void")
{
return WrapKeyword(type.Name.ToLower());
}
if (Helper.IsComplexType(type))
{
return WrapType(type.Name);
}
if (type.IsPrimitive || type == typeof(string) || type == typeof(decimal))
{
return WrapKeyword(type.Name.ToLower());
}
else
{
return WrapType(type.Name);
}
return WrapType(type.Name);
}
private string WrapIdentifier(string value)
......@@ -275,25 +266,8 @@ namespace DotNetCore.CAP.Dashboard
private string Span(string @class, string value)
{
return $"<span class=\"{@class}\">{value}</span>";
}
#endregion
public NonEscapedString StackTrace(string stackTrace)
{
try
{
//return new NonEscapedString(StackTraceFormatter.FormatHtml(stackTrace, StackTraceHtmlFragments));
return new NonEscapedString(stackTrace);
}
catch (RegexMatchTimeoutException)
{
return new NonEscapedString(HtmlEncode(stackTrace));
}
}
public string HtmlEncode(string text)
{
return WebUtility.HtmlEncode(text);
}
#endregion
}
}
\ No newline at end of file
......@@ -26,20 +26,18 @@ namespace DotNetCore.CAP.Dashboard
string serialized = null;
if (_command != null)
{
object result = _command(context);
var result = _command(context);
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Converters = new JsonConverter[] { new StringEnumConverter { CamelCaseText = true } }
Converters = new JsonConverter[] {new StringEnumConverter {CamelCaseText = true}}
};
serialized = JsonConvert.SerializeObject(result, settings);
}
if (_jsonCommand != null)
{
serialized = _jsonCommand(context);
}
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(serialized ?? string.Empty);
......
......@@ -27,7 +27,7 @@ namespace DotNetCore.CAP.Dashboard
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Converters = new JsonConverter[] { new StringEnumConverter { CamelCaseText = true } }
Converters = new JsonConverter[] {new StringEnumConverter {CamelCaseText = true}}
};
var serialized = JsonConvert.SerializeObject(result, settings);
......
using System;
namespace DotNetCore.CAP.Dashboard
namespace DotNetCore.CAP.Dashboard
{
public class LocalRequestsOnlyAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
// if unknown, assume not local
if (String.IsNullOrEmpty(context.Request.RemoteIpAddress))
if (string.IsNullOrEmpty(context.Request.RemoteIpAddress))
return false;
// check if localhost
......
......@@ -20,12 +20,10 @@ namespace DotNetCore.CAP.Dashboard
public IEnumerable<DashboardMetric> GetAllMetrics()
{
var metrics = new List<DashboardMetric> { Metric };
var metrics = new List<DashboardMetric> {Metric};
if (Metrics != null)
{
metrics.AddRange(Metrics);
}
return metrics.Where(x => x != null).ToList();
}
......
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Text;
using DotNetCore.CAP.Infrastructure;
......@@ -16,7 +18,7 @@ namespace DotNetCore.CAP.Dashboard
private static readonly IDictionary<string, string> ForegroundStateColors
= new Dictionary<string, string>();
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
[SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
static MessageHistoryRenderer()
{
Register(StatusName.Succeeded, SucceededRenderer);
......@@ -44,9 +46,7 @@ namespace DotNetCore.CAP.Dashboard
public static string GetBackgroundStateColor(string stateName)
{
if (stateName == null || !BackgroundStateColors.ContainsKey(stateName))
{
return "inherit";
}
return BackgroundStateColors[stateName];
}
......@@ -59,23 +59,18 @@ namespace DotNetCore.CAP.Dashboard
public static string GetForegroundStateColor(string stateName)
{
if (stateName == null || !ForegroundStateColors.ContainsKey(stateName))
{
return "inherit";
}
return ForegroundStateColors[stateName];
}
public static void Register(string state, Func<HtmlHelper, IDictionary<string, string>, NonEscapedString> renderer)
public static void Register(string state,
Func<HtmlHelper, IDictionary<string, string>, NonEscapedString> renderer)
{
if (!Renderers.ContainsKey(state))
{
Renderers.Add(state, renderer);
}
else
{
Renderers[state] = renderer;
}
}
public static bool Exists(string state)
......@@ -141,10 +136,10 @@ namespace DotNetCore.CAP.Dashboard
itemsAdded = true;
}
if (stateData.ContainsKey("Result") && !String.IsNullOrWhiteSpace(stateData["Result"]))
if (stateData.ContainsKey("Result") && !string.IsNullOrWhiteSpace(stateData["Result"]))
{
var result = stateData["Result"];
builder.Append($"<dt>Result:</dt><dd>{System.Net.WebUtility.HtmlEncode(result)}</dd>");
builder.Append($"<dt>Result:</dt><dd>{WebUtility.HtmlEncode(result)}</dd>");
itemsAdded = true;
}
......@@ -171,13 +166,9 @@ namespace DotNetCore.CAP.Dashboard
string serverId = null;
if (stateData.ContainsKey("ServerId"))
{
serverId = stateData["ServerId"];
}
else if (stateData.ContainsKey("ServerName"))
{
serverId = stateData["ServerName"];
}
if (serverId != null)
{
......
......@@ -20,11 +20,12 @@ namespace DotNetCore.CAP.Dashboard
Metric = DashboardMetrics.PublishedSucceededCount
});
PublishedItems.Add(page => new MenuItem(Strings.SidebarMenu_Processing, page.Url.To("/published/processing"))
{
Active = page.RequestPath.StartsWith("/published/processing"),
Metric = DashboardMetrics.PublishedProcessingCount
});
PublishedItems.Add(page =>
new MenuItem(Strings.SidebarMenu_Processing, page.Url.To("/published/processing"))
{
Active = page.RequestPath.StartsWith("/published/processing"),
Metric = DashboardMetrics.PublishedProcessingCount
});
PublishedItems.Add(page => new MenuItem(Strings.SidebarMenu_Failed, page.Url.To("/published/failed"))
{
......
......@@ -20,7 +20,7 @@
Info,
Success,
Warning,
Danger,
Danger
}
internal static class MetricStyleExtensions
......
......@@ -7,9 +7,9 @@ namespace DotNetCore.CAP.Dashboard
{
private const int PageItemsCount = 7;
private const int DefaultRecordsPerPage = 10;
private int _endPageIndex = 1;
private int _startPageIndex = 1;
private int _endPageIndex = 1;
public Pager(int from, int perPage, long total)
{
......@@ -17,7 +17,7 @@ namespace DotNetCore.CAP.Dashboard
RecordsPerPage = perPage > 0 ? perPage : DefaultRecordsPerPage;
TotalRecordCount = total;
CurrentPage = FromRecord / RecordsPerPage + 1;
TotalPageCount = (int)Math.Ceiling((double)TotalRecordCount / RecordsPerPage);
TotalPageCount = (int) Math.Ceiling((double) TotalRecordCount / RecordsPerPage);
PagerItems = GenerateItems();
}
......@@ -110,7 +110,7 @@ namespace DotNetCore.CAP.Dashboard
if (_endPageIndex < TotalPageCount - 1)
{
var index = _startPageIndex + PageItemsCount;
if (index > TotalPageCount) { index = TotalPageCount; }
if (index > TotalPageCount) index = TotalPageCount;
var item = new Item(index, false, ItemType.MorePage);
results.Add(item);
}
......
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@
@using System
@using System.Collections.Generic
@using DotNetCore.CAP.Models;
@using DotNetCore.CAP.Dashboard
@using DotNetCore.CAP.Dashboard.Pages
@using DotNetCore.CAP.Dashboard.Resources
@using DotNetCore.CAP.Models
@using Newtonsoft.Json
@inherits RazorPage
@inherits DotNetCore.CAP.Dashboard.RazorPage
@{
Layout = new LayoutPage(Strings.HomePage_Title);
var monitor = Storage.GetMonitoringApi();
IDictionary<DateTime, int> publishedSucceeded = monitor.HourlySucceededJobs(MessageType.Publish);
IDictionary<DateTime, int> publishedFailed = monitor.HourlyFailedJobs(MessageType.Publish);
var publishedSucceeded = monitor.HourlySucceededJobs(MessageType.Publish);
var publishedFailed = monitor.HourlyFailedJobs(MessageType.Publish);
IDictionary<DateTime, int> receivedSucceeded = monitor.HourlySucceededJobs(MessageType.Subscribe);
IDictionary<DateTime, int> receivedFailed = monitor.HourlyFailedJobs(MessageType.Subscribe);
var receivedSucceeded = monitor.HourlySucceededJobs(MessageType.Subscribe);
var receivedFailed = monitor.HourlyFailedJobs(MessageType.Subscribe);
}
<div class="row">
......@@ -41,7 +38,8 @@
data-received-succeeded="@Statistics.ReceivedSucceeded"
data-received-failed="@Statistics.ReceivedFailed"
data-received-succeeded-string="@Strings.HomePage_GraphHover_RSucceeded"
data-received-failed-string="@Strings.HomePage_GraphHover_RFailed"></div>
data-received-failed-string="@Strings.HomePage_GraphHover_RFailed">
</div>
<div style="display: none;">
<span data-metric="published_succeeded:count"></span>
<span data-metric="published_failed:count"></span>
......@@ -61,7 +59,7 @@
data-received-succeeded="@JsonConvert.SerializeObject(receivedSucceeded)"
data-received-failed="@JsonConvert.SerializeObject(receivedFailed)"
data-received-succeeded-string="@Strings.HomePage_GraphHover_RSucceeded"
data-received-failed-string="@Strings.HomePage_GraphHover_RFailed">
data-received-failed-string="@Strings.HomePage_GraphHover_RFailed">
</div>
</div>
</div>
\ No newline at end of file
......@@ -2,10 +2,9 @@
@using System
@using System.Globalization
@using System.Reflection
@using DotNetCore.CAP.Dashboard
@using DotNetCore.CAP.Dashboard.Pages
@using DotNetCore.CAP.Dashboard.Resources
@inherits RazorPage
@inherits DotNetCore.CAP.Dashboard.RazorPage
<!DOCTYPE html>
<html lang="@CultureInfo.CurrentUICulture.TwoLetterISOLanguageName">
<head>
......@@ -16,63 +15,65 @@
@{ var version = GetType().GetTypeInfo().Assembly.GetName().Version; }
<link rel="stylesheet" href="@Url.To($"/css{version.Major}{version.Minor}{version.Build}")">
</head>
<body>
<!-- Wrap all page content here -->
<div id="wrap">
<body>
<!-- Wrap all page content here -->
<div id="wrap">
<!-- Fixed navbar -->
<div class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="@Url.Home()">CAP Dashboard</a>
</div>
<div class="collapse navbar-collapse">
@Html.RenderPartial(new Navigation())
@if(@AppPath != null) {
<ul class="nav navbar-nav navbar-right">
<li>
<a href="@AppPath">
<span class="glyphicon glyphicon-log-out"></span>
@Strings.LayoutPage_Back
</a>
</li>
</ul>
}
</div>
<!--/.nav-collapse -->
</div>
<!-- Fixed navbar -->
<div class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="@Url.Home()">CAP Dashboard</a>
</div>
<!-- Begin page content -->
<div class="container" style="margin-bottom: 20px;">
@RenderBody()
<div class="collapse navbar-collapse">
@Html.RenderPartial(new Navigation())
@if (AppPath != null)
{
<ul class="nav navbar-nav navbar-right">
<li>
<a href="@AppPath">
<span class="glyphicon glyphicon-log-out"></span>
@Strings.LayoutPage_Back
</a>
</li>
</ul>
}
</div>
<!--/.nav-collapse -->
</div>
</div>
<div id="footer">
<div class="container">
<ul class="list-inline credit">
<li>
<a href="https://github.com/dotnetcore/cap/" target="_blank">CAP @($"{version.Major}.{version.Minor}.{version.Build}")
</a>
</li>
<li>@Storage</li>
<li>@Strings.LayoutPage_Footer_Time @Html.LocalTime(DateTime.UtcNow)</li>
<li>@String.Format(Strings.LayoutPage_Footer_Generatedms, GenerationTime.Elapsed.TotalMilliseconds.ToString("N"))</li>
</ul>
</div>
</div>
<div id="capConfig"
data-pollinterval="@StatsPollingInterval"
data-pollurl="@(Url.To("/stats"))">
</div>
<!-- Begin page content -->
<div class="container" style="margin-bottom: 20px;">
@RenderBody()
</div>
</div>
<div id="footer">
<div class="container">
<ul class="list-inline credit">
<li>
<a href="https://github.com/dotnetcore/cap/" target="_blank">
CAP @($"{version.Major}.{version.Minor}.{version.Build}")
</a>
</li>
<li>@Storage</li>
<li>@Strings.LayoutPage_Footer_Time @Html.LocalTime(DateTime.UtcNow)</li>
<li>@string.Format(Strings.LayoutPage_Footer_Generatedms, GenerationTime.Elapsed.TotalMilliseconds.ToString("N"))</li>
</ul>
</div>
</div>
<div id="capConfig"
data-pollinterval="@StatsPollingInterval"
data-pollurl="@(Url.To("/stats"))">
</div>
<script src="@Url.To($"/js{version.Major}{version.Minor}{version.Build}")"></script>
</body>
</html>
<script src="@Url.To($"/js{version.Major}{version.Minor}{version.Build}")"></script>
</body>
</html>
\ No newline at end of file
......@@ -6,12 +6,11 @@ namespace DotNetCore.CAP.Dashboard.Pages
{
internal partial class NodePage
{
private IList<Node> _nodes;
private INodeDiscoveryProvider _discoveryProvider;
private IList<Node> _nodes;
public NodePage()
{
}
public NodePage(string id)
......@@ -28,10 +27,10 @@ namespace DotNetCore.CAP.Dashboard.Pages
if (_nodes == null)
{
_discoveryProvider = RequestServices.GetService<INodeDiscoveryProvider>();
_nodes = _discoveryProvider.GetNodes().GetAwaiter().GetResult();
_nodes = _discoveryProvider.GetNodes().GetAwaiter().GetResult();
}
return _nodes;
}
}
}
}
}
\ No newline at end of file
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@
@using DotNetCore.CAP.Dashboard
@using DotNetCore.CAP.Dashboard.Pages
@using DotNetCore.CAP.Dashboard.Resources
@inherits RazorPage
@inherits DotNetCore.CAP.Dashboard.RazorPage
@{
Layout = new LayoutPage(Strings.NodePage_Title);
}
......@@ -21,29 +20,29 @@
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th width="10%">编号</th>
<th width="20%">节点名称</th>
<th width="20%">IP地址</th>
<th width="7%">端口号</th>
<th>Tags</th>
<th width="20%">操作</th>
</tr>
<tr>
<th width="10%">编号</th>
<th width="20%">节点名称</th>
<th width="20%">IP地址</th>
<th width="7%">端口号</th>
<th>Tags</th>
<th width="20%">操作</th>
</tr>
</thead>
<tbody>
@foreach (var node in Nodes)
{
<tr class="@(CurrentNodeId == node.Id? "active":null )">
<td>@node.Id</td>
<td>@node.Name</td>
<td>@node.Address</td>
<td>@node.Port</td>
<td>@node.Tags</td>
<td>
@Html.NodeSwitchLink(node.Id)
</td>
</tr>
}
@foreach (var node in Nodes)
{
<tr class="@(CurrentNodeId == node.Id ? "active" : null)">
<td>@node.Id</td>
<td>@node.Name</td>
<td>@node.Address</td>
<td>@node.Port</td>
<td>@node.Tags</td>
<td>
@Html.NodeSwitchLink(node.Id)
</td>
</tr>
}
</tbody>
</table>
</div>
......
......@@ -13,14 +13,12 @@ namespace DotNetCore.CAP.Dashboard.Pages
public int GetTotal(IMonitoringApi api)
{
if (string.Equals(StatusName, Infrastructure.StatusName.Succeeded, StringComparison.CurrentCultureIgnoreCase))
{
if (string.Equals(StatusName, Infrastructure.StatusName.Succeeded,
StringComparison.CurrentCultureIgnoreCase))
return api.PublishedSucceededCount();
}
if (string.Equals(StatusName, Infrastructure.StatusName.Processing, StringComparison.CurrentCultureIgnoreCase))
{
if (string.Equals(StatusName, Infrastructure.StatusName.Processing,
StringComparison.CurrentCultureIgnoreCase))
return api.PublishedProcessingCount();
}
return api.PublishedFailedCount();
}
}
......
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@
@using System
@using DotNetCore.CAP.Models;
@using DotNetCore.CAP.Dashboard
@using DotNetCore.CAP.Dashboard.Pages
@using DotNetCore.CAP.Dashboard.Monitoring
@using DotNetCore.CAP.Dashboard.Pages
@using DotNetCore.CAP.Dashboard.Resources
@inherits RazorPage
@using DotNetCore.CAP.Models
@inherits DotNetCore.CAP.Dashboard.RazorPage
@{
Layout = new LayoutPage(Strings.PublishedMessagesPage_Title);
......@@ -13,8 +13,8 @@
int.TryParse(Query("from"), out from);
int.TryParse(Query("count"), out perPage);
string name = Query("name");
string content = Query("content");
var name = Query("name");
var content = Query("content");
var monitor = Storage.GetMonitoringApi();
var pager = new Pager(from, perPage, GetTotal(monitor));
......@@ -49,11 +49,11 @@
<div class="btn-toolbar btn-toolbar-top">
<form class="row">
<span class="col-md-3">
<input type="text" class="form-control" name="name" value="@Query("name")" placeholder="@Strings.MessagesPage_Query_MessageName" />
<input type="text" class="form-control" name="name" value="@Query("name")" placeholder="@Strings.MessagesPage_Query_MessageName"/>
</span>
<div class="col-md-5">
<div class="input-group">
<input type="text" class="form-control" name="content" value="@Query("content")" placeholder="@Strings.MessagesPage_Query_MessageBody" />
<input type="text" class="form-control" name="content" value="@Query("content")" placeholder="@Strings.MessagesPage_Query_MessageBody"/>
<span class="input-group-btn">
<button class="btn btn-info">@Strings.MessagesPage_Query_Button</button>
</span>
......@@ -76,51 +76,51 @@
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th style="width:60px;">
<input type="checkbox" class="js-jobs-list-select-all" />
</th>
<th>@Strings.MessagesPage_Table_Code</th>
<th>@Strings.MessagesPage_Table_Name</th>
<th class="min-width">@Strings.MessagesPage_Table_Retries</th>
@if (string.Equals(StatusName, "Processing", StringComparison.CurrentCultureIgnoreCase))
{
<th>@Strings.MessagesPage_Table_State</th>
}
<th class="align-right">@Strings.MessagesPage_Table_ExpiresAt</th>
</tr>
<tr>
<th style="width: 60px;">
<input type="checkbox" class="js-jobs-list-select-all"/>
</th>
<th>@Strings.MessagesPage_Table_Code</th>
<th>@Strings.MessagesPage_Table_Name</th>
<th class="min-width">@Strings.MessagesPage_Table_Retries</th>
@if (string.Equals(StatusName, "Processing", StringComparison.CurrentCultureIgnoreCase))
{
<th>@Strings.MessagesPage_Table_State</th>
}
<th class="align-right">@Strings.MessagesPage_Table_ExpiresAt</th>
</tr>
</thead>
<tbody>
@foreach (var message in succeededMessages)
{
<tr class="js-jobs-list-row hover">
<td>
<input type="checkbox" class="js-jobs-list-checkbox" name="messages[]" value="@message.Id" />
</td>
<td class="word-break">
<a href="javascript:;" data-url='@(Url.To("/published/message/")+message.Id)' class="openModal">#@message.Id</a>
</td>
<td>
@message.Name
</td>
@foreach (var message in succeededMessages)
{
<tr class="js-jobs-list-row hover">
<td>
<input type="checkbox" class="js-jobs-list-checkbox" name="messages[]" value="@message.Id"/>
</td>
<td class="word-break">
<a href="javascript:;" data-url='@(Url.To("/published/message/") + message.Id)' class="openModal">#@message.Id</a>
</td>
<td>
@message.Name
</td>
<td>
@message.Retries
</td>
@if (string.Equals(StatusName, "Processing", StringComparison.CurrentCultureIgnoreCase))
{
<td>
@message.Retries
@message.StatusName
</td>
@if (string.Equals(StatusName, "Processing", StringComparison.CurrentCultureIgnoreCase))
}
<td class="align-right">
@if (message.ExpiresAt.HasValue)
{
<td>
@message.StatusName
</td>
@Html.RelativeTime(message.ExpiresAt.Value)
}
<td class="align-right">
@if (message.ExpiresAt.HasValue)
{
@Html.RelativeTime(message.ExpiresAt.Value)
}
</td>
</td>
</tr>
}
</tr>
}
</tbody>
</table>
</div>
......@@ -132,10 +132,12 @@
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Message Content</h4>
</div>
<div id="jsonContent" style="max-height:500px;overflow-y:auto;" class="modal-body">
<div id="jsonContent" style="max-height: 500px; overflow-y: auto;" class="modal-body">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-primary" id="formatBtn" onclick="">@Strings.MessagesPage_Modal_Format</button>
......
......@@ -13,14 +13,12 @@ namespace DotNetCore.CAP.Dashboard.Pages
public int GetTotal(IMonitoringApi api)
{
if (string.Equals(StatusName, Infrastructure.StatusName.Succeeded, StringComparison.CurrentCultureIgnoreCase))
{
if (string.Equals(StatusName, Infrastructure.StatusName.Succeeded,
StringComparison.CurrentCultureIgnoreCase))
return api.ReceivedSucceededCount();
}
if (string.Equals(StatusName, Infrastructure.StatusName.Processing, StringComparison.CurrentCultureIgnoreCase))
{
if (string.Equals(StatusName, Infrastructure.StatusName.Processing,
StringComparison.CurrentCultureIgnoreCase))
return api.ReceivedProcessingCount();
}
return api.ReceivedFailedCount();
}
}
......
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@
@using System
@using DotNetCore.CAP.Models;
@using DotNetCore.CAP.Dashboard
@using DotNetCore.CAP.Dashboard.Pages
@using DotNetCore.CAP.Dashboard.Monitoring
@using DotNetCore.CAP.Dashboard.Pages
@using DotNetCore.CAP.Dashboard.Resources
@inherits RazorPage
@using DotNetCore.CAP.Models
@inherits DotNetCore.CAP.Dashboard.RazorPage
@{
Layout = new LayoutPage(Strings.ReceivedMessagesPage_Title);
......@@ -13,9 +13,9 @@
int.TryParse(Query("from"), out from);
int.TryParse(Query("count"), out perPage);
string group = Query("group");
string name = Query("name");
string content = Query("content");
var group = Query("group");
var name = Query("name");
var content = Query("content");
var monitor = Storage.GetMonitoringApi();
var pager = new Pager(from, perPage, GetTotal(monitor));
......@@ -51,14 +51,14 @@
<div class="btn-toolbar btn-toolbar-top">
<form class="row">
<span class="col-md-2">
<input type="text" class="form-control" name="group" value="@Query("group")" placeholder="@Strings.MessagesPage_Query_MessageGroup" />
<input type="text" class="form-control" name="group" value="@Query("group")" placeholder="@Strings.MessagesPage_Query_MessageGroup"/>
</span>
<span class="col-md-3">
<input type="text" class="form-control" name="name" value="@Query("name")" placeholder="@Strings.MessagesPage_Query_MessageName" />
<input type="text" class="form-control" name="name" value="@Query("name")" placeholder="@Strings.MessagesPage_Query_MessageName"/>
</span>
<div class="col-md-5">
<div class="input-group">
<input type="text" class="form-control" name="content" value="@Query("content")" placeholder="@Strings.MessagesPage_Query_MessageBody" />
<input type="text" class="form-control" name="content" value="@Query("content")" placeholder="@Strings.MessagesPage_Query_MessageBody"/>
<span class="input-group-btn">
<button class="btn btn-info">@Strings.MessagesPage_Query_Button</button>
</span>
......@@ -81,55 +81,55 @@
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th style="width:60px;">
<input type="checkbox" class="js-jobs-list-select-all" />
</th>
<th>@Strings.MessagesPage_Table_Code</th>
<th>@Strings.MessagesPage_Table_Group</th>
<th>@Strings.MessagesPage_Table_Name</th>
<th class="min-width">@Strings.MessagesPage_Table_Retries</th>
@if (string.Equals(StatusName, "Processing", StringComparison.CurrentCultureIgnoreCase))
{
<th>@Strings.MessagesPage_Table_State</th>
}
<th class="align-right">@Strings.MessagesPage_Table_ExpiresAt</th>
</tr>
<tr>
<th style="width: 60px;">
<input type="checkbox" class="js-jobs-list-select-all"/>
</th>
<th>@Strings.MessagesPage_Table_Code</th>
<th>@Strings.MessagesPage_Table_Group</th>
<th>@Strings.MessagesPage_Table_Name</th>
<th class="min-width">@Strings.MessagesPage_Table_Retries</th>
@if (string.Equals(StatusName, "Processing", StringComparison.CurrentCultureIgnoreCase))
{
<th>@Strings.MessagesPage_Table_State</th>
}
<th class="align-right">@Strings.MessagesPage_Table_ExpiresAt</th>
</tr>
</thead>
<tbody>
@foreach (var message in succeededMessages)
{
<tr class="js-jobs-list-row hover">
<td>
<input type="checkbox" class="js-jobs-list-checkbox" name="messages[]" value="@message.Id" />
</td>
<td class="word-break">
<a href="javascript:;" data-url='@(Url.To("/received/message/")+message.Id)' class="openModal">#@message.Id</a>
</td>
<td>
@message.Group
</td>
<td>
@message.Name
</td>
@foreach (var message in succeededMessages)
{
<tr class="js-jobs-list-row hover">
<td>
<input type="checkbox" class="js-jobs-list-checkbox" name="messages[]" value="@message.Id"/>
</td>
<td class="word-break">
<a href="javascript:;" data-url='@(Url.To("/received/message/") + message.Id)' class="openModal">#@message.Id</a>
</td>
<td>
@message.Group
</td>
<td>
@message.Name
</td>
<td>
@message.Retries
</td>
@if (string.Equals(StatusName, "Processing", StringComparison.CurrentCultureIgnoreCase))
{
<td>
@message.Retries
@message.StatusName
</td>
@if (string.Equals(StatusName, "Processing", StringComparison.CurrentCultureIgnoreCase))
}
<td class="align-right">
@if (message.ExpiresAt.HasValue)
{
<td>
@message.StatusName
</td>
@Html.RelativeTime(message.ExpiresAt.Value)
}
<td class="align-right">
@if (message.ExpiresAt.HasValue)
{
@Html.RelativeTime(message.ExpiresAt.Value)
}
</td>
</td>
</tr>
}
</tr>
}
</tbody>
</table>
</div>
......@@ -141,10 +141,12 @@
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Message Content</h4>
</div>
<div id="jsonContent" style="max-height:500px;overflow-y:auto;" class="modal-body">
<div id="jsonContent" style="max-height: 500px; overflow-y: auto;" class="modal-body">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-primary" id="formatBtn" onclick="">@Strings.MessagesPage_Modal_Format</button>
......
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@
@using DotNetCore.CAP.Internal
@using DotNetCore.CAP.Dashboard
@using DotNetCore.CAP.Dashboard.Pages
@using DotNetCore.CAP.Dashboard.Resources
@inherits RazorPage
@using DotNetCore.CAP.Internal
@inherits DotNetCore.CAP.Dashboard.RazorPage
@{
Layout = new LayoutPage(Strings.SubscribersPage_Title);
......@@ -26,39 +25,39 @@
<div class="table-responsive">
<table class="table subscribe-table">
<thead>
<tr>
<th width="20%">@Strings.Common_Group</th>
<th width="40%">
@Strings.Common_Name
</th>
<th>@Strings.Common_Method</th>
</tr>
<tr>
<th width="20%">@Strings.Common_Group</th>
<th width="40%">
@Strings.Common_Name
</th>
<th>@Strings.Common_Method</th>
</tr>
</thead>
<tbody>
@foreach (var subscriber in subscribers)
@foreach (var subscriber in subscribers)
{
var i = 0;
var rowCount = subscriber.Value.Count;
foreach (var column in subscriber.Value)
{
var i = 0;
var rowCount = subscriber.Value.Count;
foreach (var column in subscriber.Value)
{
<tr>
@if (i == 0)
{
<td rowspan="@rowCount">@subscriber.Key</td>
}
<td>@column.Attribute.Name</td>
<td>
<span style="color:#00bcd4">@column.ImplTypeInfo.Name</span>:
<div class="job-snippet-code">
<code>
<pre>@Html.MethodEscaped(column.MethodInfo)</pre>
</code>
</div>
</td>
</tr>
i++;
}
<tr>
@if (i == 0)
{
<td rowspan="@rowCount">@subscriber.Key</td>
}
<td>@column.Attribute.Name</td>
<td>
<span style="color: #00bcd4">@column.ImplTypeInfo.Name</span>:
<div class="job-snippet-code">
<code>
<pre>@Html.MethodEscaped(column.MethodInfo)</pre>
</code>
</div>
</td>
</tr>
i++;
}
}
</tbody>
</table>
</div>
......
......@@ -7,7 +7,7 @@
var className = metric == null ? "metric-null" : metric.Style.ToClassName();
var highlighted = metric != null && metric.Highlighted ? "highlighted" : null;
}
<div class="metric @className @highlighted">
<div class="metric @className @highlighted">
<div class="metric-body" data-metric="@DashboardMetric.Name">
@(metric?.Value)
</div>
......
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@
@using DotNetCore.CAP.Dashboard
@inherits RazorPage
@inherits DotNetCore.CAP.Dashboard.RazorPage
<ol class="breadcrumb">
<li><a href="@Url.Home()"><span class="glyphicon glyphicon-home"></span></a></li>
<li>
<a href="@Url.Home()">
<span class="glyphicon glyphicon-home"></span>
</a>
</li>
@foreach (var item in Items)
{
<li><a href="@item.Value">@item.Key</a></li>
<li>
<a href="@item.Value">@item.Key</a>
</li>
}
<li class="active">@Title</li>
</ol>
\ No newline at end of file
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@
@using DotNetCore.CAP.Dashboard
@inherits RazorPage
@inherits DotNetCore.CAP.Dashboard.RazorPage
@if (NavigationMenu.Items.Count > 0)
{
<ul class="nav navbar-nav">
......@@ -8,7 +8,10 @@
{
var itemValue = item(this);
if (itemValue == null) { continue; }
if (itemValue == null)
{
continue;
}
<li class="@(itemValue.Active ? "active" : null)">
<a href="@itemValue.Url">
......
namespace DotNetCore.CAP.Dashboard.Pages
{
partial class Paginator
internal partial class Paginator
{
private readonly Pager _pager;
......
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@
@using DotNetCore.CAP.Dashboard
@using DotNetCore.CAP.Dashboard.Resources;
@inherits RazorPage
@using DotNetCore.CAP.Dashboard
@using DotNetCore.CAP.Dashboard.Resources
@inherits DotNetCore.CAP.Dashboard.RazorPage
<div class="btn-toolbar">
@if (_pager.TotalPageCount > 1)
{
......@@ -13,7 +13,7 @@
{
case Pager.ItemType.Page:
<a href="@_pager.PageUrl(page.PageIndex)" class="btn btn-default @(_pager.CurrentPage == page.PageIndex ? "active" : null)">
@page.PageIndex
@page.PageIndex
</a>
break;
case Pager.ItemType.NextPage:
......@@ -40,4 +40,4 @@
<div class="btn-toolbar-label">
@Strings.Paginator_TotalItems: @_pager.TotalRecordCount
</div>
</div>
</div>
\ No newline at end of file
namespace DotNetCore.CAP.Dashboard.Pages
{
partial class PerPageSelector
internal partial class PerPageSelector
{
private readonly Pager _pager;
......
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@
@using DotNetCore.CAP.Dashboard.Resources;
@using DotNetCore.CAP.Dashboard.Resources
@inherits DotNetCore.CAP.Dashboard.RazorPage
<div class="btn-group pull-right paginator">
@foreach (var count in new[] { 10, 20, 50, 100, 500 })
{
<a class="btn btn-sm btn-default @(count == _pager.RecordsPerPage ? "active" : null)"
href="@_pager.RecordsPerPageUrl(count)">@count</a>
}
</div>
<div class="btn-toolbar-spacer pull-right"></div>
<div class="btn-toolbar-label btn-toolbar-label-sm pull-right">
@Strings.PerPageSelector_ItemsPerPage:
</div>
<div class="btn-group pull-right paginator">
@foreach (var count in new[] {10, 20, 50, 100, 500})
{
<a class="btn btn-sm btn-default @(count == _pager.RecordsPerPage ? "active" : null)"
href="@_pager.RecordsPerPageUrl(count)">
@count</a>
}
</div>
<div class="btn-toolbar-spacer pull-right"></div>
<div class="btn-toolbar-label btn-toolbar-label-sm pull-right">
@Strings.PerPageSelector_ItemsPerPage:
</div>
\ No newline at end of file
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@
@using DotNetCore.CAP.Dashboard
@inherits RazorPage
@inherits DotNetCore.CAP.Dashboard.RazorPage
@if (Items.Any())
{
<div id="stats" class="list-group">
......@@ -18,4 +17,4 @@
</a>
}
</div>
}
}
\ No newline at end of file
......@@ -8,10 +8,9 @@ namespace DotNetCore.CAP.Dashboard
{
public abstract class RazorPage
{
private Lazy<StatisticsDto> _statisticsLazy;
private readonly StringBuilder _content = new StringBuilder();
private string _body;
private Lazy<StatisticsDto> _statisticsLazy;
protected RazorPage()
{
......@@ -20,7 +19,7 @@ namespace DotNetCore.CAP.Dashboard
}
public RazorPage Layout { get; protected set; }
public HtmlHelper Html { get; private set; }
public HtmlHelper Html { get; }
public UrlHelper Url { get; private set; }
public IStorage Storage { get; internal set; }
......@@ -86,10 +85,8 @@ namespace DotNetCore.CAP.Dashboard
var monitoring = Storage.GetMonitoringApi();
var dto = monitoring.GetStatistics();
if (CapCache.Global.TryGet("cap.nodes.count", out object count))
{
dto.Servers = (int)count;
}
if (CapCache.Global.TryGet("cap.nodes.count", out var count))
dto.Servers = (int) count;
return dto;
});
......@@ -135,8 +132,8 @@ namespace DotNetCore.CAP.Dashboard
private static string Encode(string text)
{
return string.IsNullOrEmpty(text)
? string.Empty
: WebUtility.HtmlEncode(text);
? string.Empty
: WebUtility.HtmlEncode(text);
}
}
}
\ No newline at end of file
......@@ -36,9 +36,7 @@ namespace DotNetCore.CAP.Dashboard
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline);
if (match.Success)
{
return new Tuple<IDashboardDispatcher, Match>(dispatcher.Item2, match);
}
}
return null;
......
......@@ -7,8 +7,8 @@ namespace DotNetCore.CAP.Dashboard
{
public static void AddRazorPage(
this RouteCollection routes,
string pathTemplate,
Func<Match, RazorPage> pageFunc)
string pathTemplate,
Func<Match, RazorPage> pageFunc)
{
if (routes == null) throw new ArgumentNullException(nameof(routes));
if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate));
......@@ -18,9 +18,9 @@ namespace DotNetCore.CAP.Dashboard
}
public static void AddCommand(
this RouteCollection routes,
string pathTemplate,
Func<DashboardContext, bool> command)
this RouteCollection routes,
string pathTemplate,
Func<DashboardContext, bool> command)
{
if (routes == null) throw new ArgumentNullException(nameof(routes));
if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate));
......@@ -42,9 +42,9 @@ namespace DotNetCore.CAP.Dashboard
}
public static void AddJsonResult(
this RouteCollection routes,
string pathTemplate,
Func<DashboardContext, string> jsonfunc)
this RouteCollection routes,
string pathTemplate,
Func<DashboardContext, string> jsonfunc)
{
if (routes == null) throw new ArgumentNullException(nameof(routes));
if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate));
......@@ -55,8 +55,8 @@ namespace DotNetCore.CAP.Dashboard
public static void AddPublishBatchCommand(
this RouteCollection routes,
string pathTemplate,
Action<DashboardContext, int> command)
string pathTemplate,
Action<DashboardContext, int> command)
{
if (routes == null) throw new ArgumentNullException(nameof(routes));
if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate));
......
......@@ -9,14 +9,14 @@ using Microsoft.Extensions.Options;
namespace DotNetCore.CAP
{
/// <summary>
/// Default implement of <see cref="IBootstrapper"/>.
/// Default implement of <see cref="IBootstrapper" />.
/// </summary>
internal class DefaultBootstrapper : IBootstrapper
{
private readonly ILogger<DefaultBootstrapper> _logger;
private readonly IApplicationLifetime _appLifetime;
private readonly CancellationTokenSource _cts;
private readonly CancellationTokenRegistration _ctsRegistration;
private readonly ILogger<DefaultBootstrapper> _logger;
private Task _bootstrappingTask;
public DefaultBootstrapper(
......@@ -55,7 +55,7 @@ namespace DotNetCore.CAP
public Task BootstrapAsync()
{
return (_bootstrappingTask = BootstrapTaskAsync());
return _bootstrappingTask = BootstrapTaskAsync();
}
private async Task BootstrapTaskAsync()
......@@ -69,7 +69,6 @@ namespace DotNetCore.CAP
if (_cts.IsCancellationRequested) return;
foreach (var item in Servers)
{
try
{
item.Start();
......@@ -78,7 +77,6 @@ namespace DotNetCore.CAP
{
_logger.ServerStartedError(ex);
}
}
_ctsRegistration.Dispose();
_cts.Dispose();
......@@ -89,9 +87,7 @@ namespace DotNetCore.CAP
_appLifetime.ApplicationStopping.Register(() =>
{
foreach (var item in Servers)
{
item.Dispose();
}
});
return Task.CompletedTask;
}
......
......@@ -7,4 +7,4 @@ namespace DotNetCore.CAP
{
Task PublishAsync(CapPublishedMessage obj);
}
}
}
\ No newline at end of file
......@@ -11,8 +11,8 @@ namespace DotNetCore.CAP
/// <summary>
/// (EntityFramework) Asynchronous publish a object message.
/// <para>
/// If you are using the EntityFramework, you need to configure the DbContextType first.
/// otherwise you need to use overloaded method with IDbConnection and IDbTransaction.
/// If you are using the EntityFramework, you need to configure the DbContextType first.
/// otherwise you need to use overloaded method with IDbConnection and IDbTransaction.
/// </para>
/// </summary>
/// <typeparam name="T">The type of conetent object.</typeparam>
......@@ -24,8 +24,8 @@ namespace DotNetCore.CAP
/// <summary>
/// (EntityFramework) Publish a object message.
/// <para>
/// If you are using the EntityFramework, you need to configure the DbContextType first.
/// otherwise you need to use overloaded method with IDbConnection and IDbTransaction.
/// If you are using the EntityFramework, you need to configure the DbContextType first.
/// otherwise you need to use overloaded method with IDbConnection and IDbTransaction.
/// </para>
/// </summary>
/// <typeparam name="T">The type of conetent object.</typeparam>
......@@ -40,9 +40,10 @@ namespace DotNetCore.CAP
/// <param name="name">the topic name or exchange router key.</param>
/// <param name="contentObj">message body content, that will be serialized of json.</param>
/// <param name="callbackName">callback subscriber name</param>
/// <param name="dbConnection">the connection of <see cref="IDbConnection"/></param>
/// <param name="dbTransaction">the transaction of <see cref="IDbTransaction"/></param>
Task PublishAsync<T>(string name, T contentObj, IDbConnection dbConnection, string callbackName = null, IDbTransaction dbTransaction = null);
/// <param name="dbConnection">the connection of <see cref="IDbConnection" /></param>
/// <param name="dbTransaction">the transaction of <see cref="IDbTransaction" /></param>
Task PublishAsync<T>(string name, T contentObj, IDbConnection dbConnection, string callbackName = null,
IDbTransaction dbTransaction = null);
/// <summary>
/// (ado.net) Publish a object message.
......@@ -50,8 +51,9 @@ namespace DotNetCore.CAP
/// <param name="name">the topic name or exchange router key.</param>
/// <param name="contentObj">message body content, that will be serialized of json.</param>
/// <param name="callbackName">callback subscriber name</param>
/// <param name="dbConnection">the connection of <see cref="IDbConnection"/></param>
/// <param name="dbTransaction">the transaction of <see cref="IDbTransaction"/></param>
void Publish<T>(string name, T contentObj, IDbConnection dbConnection, string callbackName = null, IDbTransaction dbTransaction = null);
/// <param name="dbConnection">the connection of <see cref="IDbConnection" /></param>
/// <param name="dbTransaction">the transaction of <see cref="IDbTransaction" /></param>
void Publish<T>(string name, T contentObj, IDbConnection dbConnection, string callbackName = null,
IDbTransaction dbTransaction = null);
}
}
\ No newline at end of file
......@@ -15,7 +15,7 @@ namespace DotNetCore.CAP
void Commit();
event EventHandler<MessageContext> OnMessageReceieved;
event EventHandler<MessageContext> OnMessageReceived;
event EventHandler<string> OnError;
}
......
......@@ -6,7 +6,7 @@
public interface IConsumerClientFactory
{
/// <summary>
/// Create a new instance of <see cref="IConsumerClient"/>.
/// Create a new instance of <see cref="IConsumerClient" />.
/// </summary>
/// <param name="groupId"></param>
IConsumerClient Create(string groupId);
......
......@@ -13,14 +13,14 @@ namespace DotNetCore.CAP
{
internal class ConsumerHandler : IConsumerHandler
{
private readonly IServiceProvider _serviceProvider;
private readonly IConsumerClientFactory _consumerClientFactory;
private readonly ILogger _logger;
private readonly CancellationTokenSource _cts;
private readonly MethodMatcherCache _selector;
private readonly ILogger _logger;
private readonly TimeSpan _pollingDelay = TimeSpan.FromSeconds(1);
private readonly MethodMatcherCache _selector;
private readonly IServiceProvider _serviceProvider;
private Task _compositeTask;
private bool _disposed;
......@@ -43,7 +43,6 @@ namespace DotNetCore.CAP
var groupingMatchs = _selector.GetCandidatesMethodsOfGroupNameGrouped();
foreach (var matchGroup in groupingMatchs)
{
Task.Factory.StartNew(() =>
{
using (var client = _consumerClientFactory.Create(matchGroup.Key))
......@@ -55,16 +54,13 @@ namespace DotNetCore.CAP
client.Listening(_pollingDelay, _cts.Token);
}
}, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
_compositeTask = Task.CompletedTask;
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
_logger.ServerShuttingDown();
......@@ -78,15 +74,18 @@ namespace DotNetCore.CAP
{
var innerEx = ex.InnerExceptions[0];
if (!(innerEx is OperationCanceledException))
{
_logger.ExpectedOperationCanceledException(innerEx);
}
}
}
public void Pulse()
{
SubscribeQueuer.PulseEvent.Set();
}
private void RegisterMessageProcessor(IConsumerClient client)
{
client.OnMessageReceieved += (sender, message) =>
client.OnMessageReceived += (sender, message) =>
{
_logger.EnqueuingReceivedMessage(message.Name, message.Content);
......@@ -99,10 +98,7 @@ namespace DotNetCore.CAP
Pulse();
};
client.OnError += (sender, reason) =>
{
_logger.LogError(reason);
};
client.OnError += (sender, reason) => { _logger.LogError(reason); };
}
private static void StoreMessage(IServiceScope serviceScope, MessageContext messageContext)
......@@ -111,14 +107,9 @@ namespace DotNetCore.CAP
var messageStore = provider.GetRequiredService<IStorageConnection>();
var receivedMessage = new CapReceivedMessage(messageContext)
{
StatusName = StatusName.Scheduled,
StatusName = StatusName.Scheduled
};
messageStore.StoreReceivedMessageAsync(receivedMessage).GetAwaiter().GetResult();
}
public void Pulse()
{
SubscribeQueuer.PulseEvent.Set();
}
}
}
\ No newline at end of file
......@@ -10,9 +10,9 @@ namespace DotNetCore.CAP
{
public abstract class BasePublishQueueExecutor : IQueueExecutor
{
private readonly ILogger _logger;
private readonly CapOptions _options;
private readonly IStateChanger _stateChanger;
private readonly ILogger _logger;
protected BasePublishQueueExecutor(
CapOptions options,
......@@ -24,8 +24,6 @@ namespace DotNetCore.CAP
_logger = logger;
}
public abstract Task<OperateResult> PublishAsync(string keyName, string content);
public async Task<OperateResult> ExecuteAsync(IStorageConnection connection, IFetchedMessage fetched)
{
var message = await connection.GetPublishedMessageAsync(fetched.MessageId);
......@@ -35,9 +33,7 @@ namespace DotNetCore.CAP
await _stateChanger.ChangeStateAsync(message, new ProcessingState(), connection);
if (message.Retries > 0)
{
_logger.JobRetrying(message.Retries);
}
var result = await PublishAsync(message.Name, message.Content);
sp.Stop();
......@@ -65,9 +61,7 @@ namespace DotNetCore.CAP
fetched.RemoveFromQueue();
if (result.Succeeded)
{
_logger.JobExecuted(sp.Elapsed.TotalSeconds);
}
return OperateResult.Success;
}
......@@ -78,15 +72,16 @@ namespace DotNetCore.CAP
}
}
private static async Task<bool> UpdateMessageForRetryAsync(CapPublishedMessage message, IStorageConnection connection)
public abstract Task<OperateResult> PublishAsync(string keyName, string content);
private static async Task<bool> UpdateMessageForRetryAsync(CapPublishedMessage message,
IStorageConnection connection)
{
var retryBehavior = RetryBehavior.DefaultRetry;
var retries = ++message.Retries;
if (retries >= retryBehavior.RetryCount)
{
return false;
}
var due = message.Added.AddSeconds(retryBehavior.RetryIn(retries));
message.ExpiresAt = due;
......
......@@ -12,7 +12,7 @@ using Microsoft.Extensions.Logging;
namespace DotNetCore.CAP
{
public class SubscibeQueueExecutor : IQueueExecutor
public class SubscribeQueueExecutor : IQueueExecutor
{
private readonly IConsumerInvokerFactory _consumerInvokerFactory;
private readonly ILogger _logger;
......@@ -20,7 +20,7 @@ namespace DotNetCore.CAP
private readonly MethodMatcherCache _selector;
private readonly IStateChanger _stateChanger;
public SubscibeQueueExecutor(
public SubscribeQueueExecutor(
IStateChanger stateChanger,
MethodMatcherCache selector,
CapOptions options,
......
......@@ -50,15 +50,15 @@ namespace DotNetCore.CAP
/// <summary>
/// Returns the next message to be enqueued.
/// </summary>
Task<CapReceivedMessage> GetNextReceviedMessageToBeEnqueuedAsync();
Task<CapReceivedMessage> GetNextReceivedMessageToBeEnqueuedAsync();
/// <summary>
/// Returns executed failed message.
/// </summary>
Task<IEnumerable<CapReceivedMessage>> GetFailedReceviedMessages();
Task<IEnumerable<CapReceivedMessage>> GetFailedReceivedMessages();
/// <summary>
/// Creates and returns an <see cref="IStorageTransaction"/>.
/// Creates and returns an <see cref="IStorageTransaction" />.
/// </summary>
IStorageTransaction CreateTransaction();
......
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
......@@ -44,45 +43,25 @@ namespace DotNetCore.CAP.Infrastructure
public static long ToTimestamp(DateTime value)
{
var elapsedTime = value - Epoch;
return (long)elapsedTime.TotalSeconds;
return (long) elapsedTime.TotalSeconds;
}
public static DateTime FromTimestamp(long value)
{
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
return Epoch.AddSeconds(value);
}
public static string SerializeDateTime(DateTime value)
{
return value.ToString("o", CultureInfo.InvariantCulture);
}
public static DateTime DeserializeDateTime(string value)
{
if (long.TryParse(value, out var timestamp))
{
return FromTimestamp(timestamp);
}
return DateTime.Parse(value, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind);
}
public static bool IsController(TypeInfo typeInfo)
{
if (!typeInfo.IsClass)
{
return false;
}
if (typeInfo.IsAbstract)
{
return false;
}
if (!typeInfo.IsPublic)
{
return false;
}
return !typeInfo.ContainsGenericParameters
&& typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase);
......@@ -97,9 +76,7 @@ namespace DotNetCore.CAP.Infrastructure
{
var jObj = JObject.Parse(json);
foreach (var property in properties)
{
jObj.Add(new JProperty(property.Key, property.Value));
}
return jObj.ToString();
}
......@@ -113,13 +90,13 @@ namespace DotNetCore.CAP.Infrastructure
private static bool IsSimpleType(Type type)
{
return type.GetTypeInfo().IsPrimitive ||
type == typeof(decimal) ||
type == typeof(string) ||
type == typeof(DateTime) ||
type == typeof(Guid) ||
type == typeof(DateTimeOffset) ||
type == typeof(TimeSpan) ||
type == typeof(Uri);
type == typeof(decimal) ||
type == typeof(string) ||
type == typeof(DateTime) ||
type == typeof(Guid) ||
type == typeof(DateTimeOffset) ||
type == typeof(TimeSpan) ||
type == typeof(Uri);
}
}
}
\ No newline at end of file
......@@ -19,25 +19,24 @@ namespace DotNetCore.CAP
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 readonly int __staticMachine;
private static readonly 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 =>
private static readonly uint[] _lookup32 = Enumerable.Range(0, 256).Select(i =>
{
string s = i.ToString("x2");
return ((uint)s[0]) + ((uint)s[1] << 16);
var 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 readonly int _timestamp;
private int _machine;
private short _pid;
private int _increment;
private readonly int _machine;
private readonly short _pid;
private readonly int _increment;
// static constructor
static ObjectId()
......@@ -46,8 +45,8 @@ namespace DotNetCore.CAP
__dateTimeMaxValueMillisecondsSinceEpoch = (DateTime.MaxValue - __unixEpoch).Ticks / 10000;
__dateTimeMinValueMillisecondsSinceEpoch = (DateTime.MinValue - __unixEpoch).Ticks / 10000;
__staticMachine = GetMachineHash();
__staticIncrement = (new Random()).Next();
__staticPid = (short)GetCurrentProcessId();
__staticIncrement = new Random().Next();
__staticPid = (short) GetCurrentProcessId();
}
// constructors
......@@ -58,9 +57,7 @@ namespace DotNetCore.CAP
public ObjectId(byte[] bytes)
{
if (bytes == null)
{
throw new ArgumentNullException("bytes");
}
Unpack(bytes, out _timestamp, out _machine, out _pid, out _increment);
}
......@@ -86,13 +83,11 @@ namespace DotNetCore.CAP
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).");
}
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).");
}
throw new ArgumentOutOfRangeException("increment",
"The increment value must be between 0 and 16777215 (it must fit in 3 bytes).");
_timestamp = timestamp;
_machine = machine;
......@@ -107,9 +102,7 @@ namespace DotNetCore.CAP
public ObjectId(string value)
{
if (value == null)
{
throw new ArgumentNullException("value");
}
Unpack(ParseHexString(value), out _timestamp, out _machine, out _pid, out _increment);
}
......@@ -117,51 +110,33 @@ namespace DotNetCore.CAP
/// <summary>
/// Gets an instance of ObjectId where the value is empty.
/// </summary>
public static ObjectId Empty
{
get { return __emptyInstance; }
}
public static ObjectId Empty { get; } = default(ObjectId);
// public properties
/// <summary>
/// Gets the timestamp.
/// </summary>
public int Timestamp
{
get { return _timestamp; }
}
public int Timestamp => _timestamp;
/// <summary>
/// Gets the machine.
/// </summary>
public int Machine
{
get { return _machine; }
}
public int Machine => _machine;
/// <summary>
/// Gets the PID.
/// </summary>
public short Pid
{
get { return _pid; }
}
public short Pid => _pid;
/// <summary>
/// Gets the increment.
/// </summary>
public int Increment
{
get { return _increment; }
}
public int Increment => _increment;
/// <summary>
/// Gets the creation time (derived from the timestamp).
/// </summary>
public DateTime CreationTime
{
get { return __unixEpoch.AddSeconds(_timestamp); }
}
public DateTime CreationTime => __unixEpoch.AddSeconds(_timestamp);
// public operators
/// <summary>
......@@ -257,7 +232,7 @@ namespace DotNetCore.CAP
/// <returns>An ObjectId.</returns>
public static ObjectId GenerateNewId(int timestamp)
{
int increment = Interlocked.Increment(ref __staticIncrement) & 0x00ffffff; // only use low order 3 bytes
var increment = Interlocked.Increment(ref __staticIncrement) & 0x00ffffff; // only use low order 3 bytes
return new ObjectId(timestamp, __staticMachine, __staticPid, increment);
}
......@@ -281,27 +256,25 @@ namespace DotNetCore.CAP
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).");
}
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);
throw new ArgumentOutOfRangeException("increment",
"The increment value must be between 0 and 16777215 (it must fit in 3 bytes).");
var 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;
}
......@@ -313,13 +286,9 @@ namespace DotNetCore.CAP
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));
}
......@@ -334,16 +303,12 @@ namespace DotNetCore.CAP
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]);
pid = (short) ((bytes[7] << 8) + bytes[8]);
increment = (bytes[9] << 16) + (bytes[10] << 8) + bytes[11];
}
......@@ -369,7 +334,7 @@ namespace DotNetCore.CAP
private static int GetTimestampFromDateTime(DateTime timestamp)
{
return (int)Math.Floor((ToUniversalTime(timestamp) - __unixEpoch).TotalSeconds);
return (int) Math.Floor((ToUniversalTime(timestamp) - __unixEpoch).TotalSeconds);
}
// public methods
......@@ -377,15 +342,18 @@ namespace DotNetCore.CAP
/// Compares this ObjectId to another ObjectId.
/// </summary>
/// <param name="other">The other ObjectId.</param>
/// <returns>A 32-bit signed integer that indicates whether this ObjectId is less than, equal to, or greather than the other.</returns>
/// <returns>
/// A 32-bit signed integer that indicates whether this ObjectId is less than, equal to, or greather than the
/// other.
/// </returns>
public int CompareTo(ObjectId other)
{
int r = _timestamp.CompareTo(other._timestamp);
if (r != 0) { return r; }
var r = _timestamp.CompareTo(other._timestamp);
if (r != 0) return r;
r = _machine.CompareTo(other._machine);
if (r != 0) { return r; }
if (r != 0) return r;
r = _pid.CompareTo(other._pid);
if (r != 0) { return r; }
if (r != 0) return r;
return _increment.CompareTo(other._increment);
}
......@@ -411,13 +379,8 @@ namespace DotNetCore.CAP
public override bool Equals(object obj)
{
if (obj is ObjectId)
{
return Equals((ObjectId)obj);
}
else
{
return false;
}
return Equals((ObjectId) obj);
return false;
}
/// <summary>
......@@ -426,7 +389,7 @@ namespace DotNetCore.CAP
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
int hash = 17;
var hash = 17;
hash = 37 * hash + _timestamp.GetHashCode();
hash = 37 * hash + _machine.GetHashCode();
hash = 37 * hash + _pid.GetHashCode();
......@@ -460,21 +423,15 @@ namespace DotNetCore.CAP
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];
var 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])));
}
for (var i = 0; i < s.Length >> 1; ++i)
arr[i] = (byte) ((GetHexVal(s[i << 1]) << 4) + GetHexVal(s[(i << 1) + 1]));
return arr;
}
......@@ -487,15 +444,13 @@ namespace DotNetCore.CAP
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++)
for (var i = 0; i < bytes.Length; i++)
{
var val = _lookup32[bytes[i]];
result[2 * i] = (char)val;
result[2 * i + 1] = (char)(val >> 16);
result[2 * i] = (char) val;
result[2 * i + 1] = (char) (val >> 16);
}
return new string(result);
}
......@@ -519,22 +474,15 @@ namespace DotNetCore.CAP
public static DateTime ToUniversalTime(DateTime dateTime)
{
if (dateTime == DateTime.MinValue)
{
return DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
}
else if (dateTime == DateTime.MaxValue)
{
if (dateTime == DateTime.MaxValue)
return DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc);
}
else
{
return dateTime.ToUniversalTime();
}
return dateTime.ToUniversalTime();
}
private static int GetHexVal(char hex)
{
int val = (int)hex;
int val = hex;
//For uppercase A-F letters:
//return val - (val < 58 ? 48 : 55);
//For lowercase a-f letters:
......
......@@ -21,7 +21,7 @@ namespace DotNetCore.CAP.Infrastructure
var tcs = new TaskCompletionSource<bool>();
registeredHandle = ThreadPool.RegisterWaitForSingleObject(
handle,
(state, timedOut) => ((TaskCompletionSource<bool>)state).TrySetResult(!timedOut),
(state, timedOut) => ((TaskCompletionSource<bool>) state).TrySetResult(!timedOut),
tcs,
timeout,
true);
......
using System;
namespace DotNetCore.CAP.Infrastructure
{
public class WebHookProvider
{
public WebHookProvider()
{
throw new NotImplementedException();
}
}
}
\ No newline at end of file
......@@ -7,8 +7,8 @@ namespace DotNetCore.CAP.Internal
public class ConsumerInvokerFactory : IConsumerInvokerFactory
{
private readonly ILogger _logger;
private readonly IServiceProvider _serviceProvider;
private readonly IModelBinderFactory _modelBinderFactory;
private readonly IServiceProvider _serviceProvider;
public ConsumerInvokerFactory(
ILoggerFactory loggerFactory,
......@@ -25,7 +25,7 @@ namespace DotNetCore.CAP.Internal
var context = new ConsumerInvokerContext(consumerContext)
{
Result = new DefaultConsumerInvoker(_logger, _serviceProvider,
_modelBinderFactory, consumerContext)
_modelBinderFactory, consumerContext)
};
return context.Result;
......
......@@ -10,8 +10,7 @@ namespace DotNetCore.CAP.Internal
public int CombinedHash
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get { return _combinedHash64.GetHashCode(); }
[MethodImpl(MethodImplOptions.AggressiveInlining)] get { return _combinedHash64.GetHashCode(); }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
......@@ -30,7 +29,7 @@ namespace DotNetCore.CAP.Internal
else
{
var count = 0;
foreach (object o in e)
foreach (var o in e)
{
Add(o);
count++;
......@@ -54,14 +53,14 @@ namespace DotNetCore.CAP.Internal
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(string s)
{
var hashCode = (s != null) ? s.GetHashCode() : 0;
var hashCode = s != null ? s.GetHashCode() : 0;
Add(hashCode);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(object o)
{
var hashCode = (o != null) ? o.GetHashCode() : 0;
var hashCode = o != null ? o.GetHashCode() : 0;
Add(hashCode);
}
......
......@@ -11,11 +11,11 @@ namespace DotNetCore.CAP.Internal
{
public class DefaultConsumerInvoker : IConsumerInvoker
{
private readonly ILogger _logger;
private readonly IServiceProvider _serviceProvider;
private readonly IModelBinderFactory _modelBinderFactory;
private readonly ConsumerContext _consumerContext;
private readonly ObjectMethodExecutor _executor;
private readonly ILogger _logger;
private readonly IModelBinderFactory _modelBinderFactory;
private readonly IServiceProvider _serviceProvider;
public DefaultConsumerInvoker(ILogger logger,
IServiceProvider serviceProvider,
......@@ -42,32 +42,24 @@ namespace DotNetCore.CAP.Internal
var serviceType = _consumerContext.ConsumerDescriptor.ImplTypeInfo.AsType();
var obj = ActivatorUtilities.GetServiceOrCreateInstance(provider, serviceType);
var jsonConent = _consumerContext.DeliverMessage.Content;
var message = serializer.DeSerialize<CapMessageDto>(jsonConent);
var jsonContent = _consumerContext.DeliverMessage.Content;
var message = serializer.DeSerialize<CapMessageDto>(jsonContent);
object result;
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);
}
}
}
private async Task<object> ExecuteAsync(object @class)
{
if (_executor.IsMethodAsync)
{
return await _executor.ExecuteAsync(@class);
}
return _executor.Execute(@class);
}
......@@ -81,16 +73,16 @@ namespace DotNetCore.CAP.Internal
if (bindResult.IsSuccess)
{
if (_executor.IsMethodAsync)
{
return await _executor.ExecuteAsync(@class, bindResult.Model);
}
return _executor.Execute(@class, bindResult.Model);
}
throw new MethodBindException($"Parameters:{firstParameter.Name} bind failed! ParameterString is: {parameterString} ");
throw new MethodBindException(
$"Parameters:{firstParameter.Name} bind failed! ParameterString is: {parameterString} ");
}
catch (FormatException ex)
{
_logger.ModelBinderFormattingException(_executor.MethodInfo?.Name, firstParameter.Name, parameterString, ex);
_logger.ModelBinderFormattingException(_executor.MethodInfo?.Name, firstParameter.Name, parameterString,
ex);
return null;
}
}
......
......@@ -10,14 +10,14 @@ namespace DotNetCore.CAP.Internal
{
/// <inheritdoc />
/// <summary>
/// A default <see cref="T:DotNetCore.CAP.Abstractions.IConsumerServiceSelector" /> implementation.
/// A default <see cref="T:DotNetCore.CAP.Abstractions.IConsumerServiceSelector" /> implementation.
/// </summary>
public class DefaultConsumerServiceSelector : IConsumerServiceSelector
{
private readonly IServiceProvider _serviceProvider;
/// <summary>
/// Creates a new <see cref="DefaultConsumerServiceSelector"/>.
/// Creates a new <see cref="DefaultConsumerServiceSelector" />.
/// </summary>
public DefaultConsumerServiceSelector(IServiceProvider serviceProvider)
{
......@@ -25,7 +25,7 @@ namespace DotNetCore.CAP.Internal
}
/// <summary>
/// Selects the best <see cref="ConsumerExecutorDescriptor"/> candidate from <paramref name="executeDescriptor"/> for the
/// Selects the best <see cref="ConsumerExecutorDescriptor" /> candidate from <paramref name="executeDescriptor" /> for the
/// current message associated.
/// </summary>
public ConsumerExecutorDescriptor SelectBestCandidate(string key,
......@@ -49,7 +49,7 @@ namespace DotNetCore.CAP.Internal
IServiceProvider provider)
{
var executorDescriptorList = new List<ConsumerExecutorDescriptor>();
using (var scoped = provider.CreateScope())
{
var scopedProvider = scoped.ServiceProvider;
......@@ -58,9 +58,7 @@ namespace DotNetCore.CAP.Internal
{
var typeInfo = service.GetType().GetTypeInfo();
if (!typeof(ICapSubscribe).GetTypeInfo().IsAssignableFrom(typeInfo))
{
continue;
}
executorDescriptorList.AddRange(GetTopicAttributesDescription(typeInfo));
}
......@@ -77,9 +75,7 @@ namespace DotNetCore.CAP.Internal
{
var typeInfo = type.GetTypeInfo();
if (Helper.IsController(typeInfo))
{
executorDescriptorList.AddRange(GetTopicAttributesDescription(typeInfo));
}
}
return executorDescriptorList;
......@@ -89,14 +85,14 @@ namespace DotNetCore.CAP.Internal
{
foreach (var method in typeInfo.DeclaredMethods)
{
var topicAttrs = method.GetCustomAttributes<TopicAttribute>(true);
var topicAttr = method.GetCustomAttributes<TopicAttribute>(true);
if (!topicAttrs.Any()) continue;
var topicAttributes = topicAttr as IList<TopicAttribute> ?? topicAttr.ToList();
foreach (var attr in topicAttrs)
{
if (!topicAttributes.Any()) continue;
foreach (var attr in topicAttributes)
yield return InitDescriptor(attr, method, typeInfo);
}
}
}
......@@ -105,7 +101,7 @@ namespace DotNetCore.CAP.Internal
MethodInfo methodInfo,
TypeInfo implType)
{
var descriptor = new ConsumerExecutorDescriptor()
var descriptor = new ConsumerExecutorDescriptor
{
Attribute = attr,
MethodInfo = methodInfo,
......
......@@ -6,14 +6,14 @@ namespace DotNetCore.CAP.Internal
{
public class JsonContentSerializer : IContentSerializer
{
public T DeSerialize<T>(string messageObjStr) where T : CapMessageDto, new() {
public T DeSerialize<T>(string messageObjStr) where T : CapMessageDto, new()
{
return Helper.FromJson<T>(messageObjStr);
}
public string Serialize<T>(T messageObj) where T : CapMessageDto, new() {
public string Serialize<T>(T messageObj) where T : CapMessageDto, new()
{
return Helper.ToJson(messageObj);
}
}
}
}
\ No newline at end of file
......@@ -22,9 +22,7 @@ namespace DotNetCore.CAP.Internal
public Task<ModelBindingResult> BindModelAsync(string content)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
var parameterType = _parameterInfo.ParameterType;
......@@ -32,47 +30,27 @@ namespace DotNetCore.CAP.Internal
{
object model;
if (parameterType == typeof(string))
{
if (string.IsNullOrWhiteSpace(content))
{
model = null;
}
else
{
model = content;
}
}
else if (string.IsNullOrWhiteSpace(content))
{
// Other than the StringConverter, converters Trim() the value then throw if the result is empty.
model = null;
}
else
{
model = _typeConverter.ConvertFrom(
context: null,
culture: CultureInfo.CurrentCulture,
value: content);
}
null,
CultureInfo.CurrentCulture,
content);
if (model == null && !IsReferenceOrNullableType(parameterType))
{
return Task.FromResult(ModelBindingResult.Failed());
}
else
{
return Task.FromResult(ModelBindingResult.Success(model));
}
return Task.FromResult(ModelBindingResult.Success(model));
}
catch (Exception exception)
{
var isFormatException = exception is FormatException;
if (!isFormatException && exception.InnerException != null)
{
// TypeConverter throws System.Exception wrapping the FormatException,
// so we capture the inner exception.
exception = ExceptionDispatchInfo.Capture(exception.InnerException).SourceException;
}
throw;
}
}
......
......@@ -10,14 +10,14 @@ namespace DotNetCore.CAP.Internal
{
private readonly IConsumerServiceSelector _selector;
private ConcurrentDictionary<string, IList<ConsumerExecutorDescriptor>> Entries { get; }
public MethodMatcherCache(IConsumerServiceSelector selector)
{
_selector = selector;
Entries = new ConcurrentDictionary<string, IList<ConsumerExecutorDescriptor>>();
}
private ConcurrentDictionary<string, IList<ConsumerExecutorDescriptor>> Entries { get; }
/// <summary>
/// Get a dictionary of candidates.In the dictionary,
/// the Key is the CAPSubscribeAttribute Group, the Value for the current Group of candidates
......@@ -31,24 +31,20 @@ namespace DotNetCore.CAP.Internal
var groupedCandidates = executorCollection.GroupBy(x => x.Attribute.Group);
foreach (var item in groupedCandidates)
{
Entries.TryAdd(item.Key, item.ToList());
}
return Entries;
}
/// <summary>
/// Get a dictionary of specify topic candidates.
/// The Key is Group name, the value is specify topic candidates.
/// Get a dictionary of specify topic candidates.
/// The Key is Group name, the value is specify topic candidates.
/// </summary>
/// <param name="topicName">message topic name</param>
public IDictionary<string, IList<ConsumerExecutorDescriptor>> GetTopicExector(string topicName)
{
if (Entries == null)
{
throw new ArgumentNullException(nameof(Entries));
}
var dic = new Dictionary<string, IList<ConsumerExecutorDescriptor>>();
foreach (var item in Entries)
......
......@@ -8,7 +8,7 @@ using DotNetCore.CAP.Infrastructure;
namespace DotNetCore.CAP.Internal
{
/// <summary>
/// A factory for <see cref="IModelBinder"/> instances.
/// A factory for <see cref="IModelBinder" /> instances.
/// </summary>
public class ModelBinderFactory : IModelBinderFactory
{
......@@ -18,36 +18,25 @@ namespace DotNetCore.CAP.Internal
public IModelBinder CreateBinder(ParameterInfo parameter)
{
if (parameter == null)
{
throw new ArgumentNullException(nameof(parameter));
}
object token = parameter;
var binder = CreateBinderCoreCached(parameter, token);
if (binder == null)
{
throw new InvalidOperationException("Format Could Not Create IModelBinder");
}
return binder;
}
private IModelBinder CreateBinderCoreCached(ParameterInfo parameterInfo, object token)
{
IModelBinder binder;
if (TryGetCachedBinder(parameterInfo, token, out binder))
{
if (TryGetCachedBinder(parameterInfo, token, out var binder))
return binder;
}
if (!Helper.IsComplexType(parameterInfo.ParameterType))
{
binder = new SimpleTypeModelBinder(parameterInfo);
}
else
{
binder = new ComplexTypeModelBinder(parameterInfo);
}
AddToCache(parameterInfo, token, binder);
......@@ -57,9 +46,7 @@ namespace DotNetCore.CAP.Internal
private void AddToCache(ParameterInfo info, object cacheToken, IModelBinder binder)
{
if (cacheToken == null)
{
return;
}
_cache.TryAdd(new Key(info, cacheToken), binder);
}
......
......@@ -84,7 +84,8 @@ namespace Microsoft.Extensions.Internal
&& m.GetParameters()[0].ParameterType == typeof(Action));
// Awaiter optionally implements ICriticalNotifyCompletion
var implementsICriticalNotifyCompletion = awaiterInterfaces.Any(t => t == typeof(ICriticalNotifyCompletion));
var implementsICriticalNotifyCompletion =
awaiterInterfaces.Any(t => t == typeof(ICriticalNotifyCompletion));
MethodInfo unsafeOnCompletedMethod;
if (implementsICriticalNotifyCompletion)
{
......@@ -124,4 +125,4 @@ namespace Microsoft.Extensions.Internal
return true;
}
}
}
}
\ No newline at end of file
......@@ -20,7 +20,8 @@ namespace Microsoft.Extensions.Internal
CoercerResultType = null;
}
public CoercedAwaitableInfo(Expression coercerExpression, Type coercerResultType, AwaitableInfo coercedAwaitableInfo)
public CoercedAwaitableInfo(Expression coercerExpression, Type coercerResultType,
AwaitableInfo coercedAwaitableInfo)
{
CoercerExpression = coercerExpression;
CoercerResultType = coercerResultType;
......@@ -40,16 +41,14 @@ namespace Microsoft.Extensions.Internal
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;
}
}
}
}
\ No newline at end of file
......@@ -10,26 +10,25 @@ 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<object, object>), // getAwaiterMethod
typeof(Func<object, bool>), // isCompletedMethod
typeof(Func<object, object>), // getResultMethod
typeof(ObjectMethodExecutorAwaitable).GetConstructor(new[]
{
typeof(object), // customAwaitable
typeof(Func<object, object>), // getAwaiterMethod
typeof(Func<object, bool>), // isCompletedMethod
typeof(Func<object, object>), // getResultMethod
typeof(Action<object, Action>), // onCompletedMethod
typeof(Action<object, Action>) // unsafeOnCompletedMethod
typeof(Action<object, Action>) // unsafeOnCompletedMethod
});
private readonly MethodExecutor _executor;
private readonly MethodExecutorAsync _executorAsync;
private readonly object[] _parameterDefaultValues;
private ObjectMethodExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo, object[] parameterDefaultValues)
{
if (methodInfo == null)
{
throw new ArgumentNullException(nameof(methodInfo));
}
MethodInfo = methodInfo;
MethodParameters = methodInfo.GetParameters();
......@@ -47,19 +46,11 @@ namespace Microsoft.Extensions.Internal
_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; }
......@@ -78,18 +69,17 @@ namespace Microsoft.Extensions.Internal
return new ObjectMethodExecutor(methodInfo, targetTypeInfo, null);
}
public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo, object[] parameterDefaultValues)
public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo,
object[] parameterDefaultValues)
{
if (parameterDefaultValues == null)
{
throw new ArgumentNullException(nameof(parameterDefaultValues));
}
return new ObjectMethodExecutor(methodInfo, targetTypeInfo, parameterDefaultValues);
}
/// <summary>
/// Executes the configured method on <paramref name="target"/>. This can be used whether or not
/// Executes the configured method on <paramref name="target" />. This can be used whether or not
/// the configured method is asynchronous.
/// </summary>
/// <remarks>
......@@ -109,7 +99,7 @@ namespace Microsoft.Extensions.Internal
}
/// <summary>
/// Executes the configured method on <paramref name="target"/>. This can only be used if the configured
/// Executes the configured method on <paramref name="target" />. This can only be used if the configured
/// method is asynchronous.
/// </summary>
/// <remarks>
......@@ -117,13 +107,12 @@ namespace Microsoft.Extensions.Internal
/// 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).
/// 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).
/// 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).
/// code doesn't know what type it's going to be).
/// </remarks>
/// <param name="target">The object whose method is to be executed.</param>
/// <param name="parameters">Parameters to pass to the method.</param>
......@@ -136,14 +125,11 @@ namespace Microsoft.Extensions.Internal
public object GetDefaultValueForParameter(int index)
{
if (_parameterDefaultValues == null)
{
throw new InvalidOperationException($"Cannot call {nameof(GetDefaultValueForParameter)}, because no parameter default values were supplied.");
}
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];
}
......@@ -157,7 +143,7 @@ namespace Microsoft.Extensions.Internal
// Build parameter list
var parameters = new List<Expression>();
var paramInfos = methodInfo.GetParameters();
for (int i = 0; i < paramInfos.Length; i++)
for (var i = 0; i < paramInfos.Length; i++)
{
var paramInfo = paramInfos[i];
var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
......@@ -190,7 +176,7 @@ namespace Microsoft.Extensions.Internal
private static MethodExecutor WrapVoidMethod(VoidMethodExecutor executor)
{
return delegate (object target, object[] parameters)
return delegate(object target, object[] parameters)
{
executor(target, parameters);
return null;
......@@ -209,7 +195,7 @@ namespace Microsoft.Extensions.Internal
// Build parameter list
var parameters = new List<Expression>();
var paramInfos = methodInfo.GetParameters();
for (int i = 0; i < paramInfos.Length; i++)
for (var i = 0; i < paramInfos.Length; i++)
{
var paramInfo = paramInfos[i];
var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
......@@ -253,12 +239,6 @@ namespace Microsoft.Extensions.Internal
var getResultParam = Expression.Parameter(typeof(object), "awaiter");
Func<object, object> 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<Func<object, object>>(
Expression.Block(
Expression.Call(
......@@ -267,11 +247,7 @@ namespace Microsoft.Extensions.Internal
Expression.Constant(null)
),
getResultParam).Compile();
}
else
{
// var getResultFunc = (object awaiter) =>
// (object)((CustomAwaiterType)awaiter).GetResult();
getResultFunc = Expression.Lambda<Func<object, object>>(
Expression.Convert(
Expression.Call(
......@@ -279,7 +255,6 @@ namespace Microsoft.Extensions.Internal
awaitableInfo.AwaiterGetResultMethod),
typeof(object)),
getResultParam).Compile();
}
// var onCompletedFunc = (object awaiter, Action continuation) => {
// ((CustomAwaiterType)awaiter).OnCompleted(continuation);
......@@ -315,7 +290,7 @@ namespace Microsoft.Extensions.Internal
// awaitable, then do so.
var coercedMethodCall = coercedAwaitableInfo.RequiresCoercion
? Expression.Invoke(coercedAwaitableInfo.CoercerExpression, methodCall)
: (Expression)methodCall;
: (Expression) methodCall;
// return new ObjectMethodExecutorAwaitable(
// (object)coercedMethodCall,
......@@ -333,8 +308,15 @@ namespace Microsoft.Extensions.Internal
Expression.Constant(onCompletedFunc),
Expression.Constant(unsafeOnCompletedFunc, typeof(Action<object, Action>)));
var lambda = Expression.Lambda<MethodExecutorAsync>(returnValueExpression, targetParameter, parametersParameter);
var lambda =
Expression.Lambda<MethodExecutorAsync>(returnValueExpression, targetParameter, parametersParameter);
return lambda.Compile();
}
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);
}
}
}
\ No newline at end of file
......@@ -7,7 +7,7 @@ using System.Runtime.CompilerServices;
namespace Microsoft.Extensions.Internal
{
/// <summary>
/// Provides a common awaitable structure that <see cref="ObjectMethodExecutor.ExecuteAsync"/> can
/// Provides a common awaitable structure that <see cref="ObjectMethodExecutor.ExecuteAsync" /> can
/// return, regardless of whether the underlying value is a System.Task, an FSharpAsync, or an
/// application-defined custom awaitable.
/// </summary>
......@@ -57,7 +57,8 @@ namespace Microsoft.Extensions.Internal
public Awaiter GetAwaiter()
{
var customAwaiter = _getAwaiterMethod(_customAwaitable);
return new Awaiter(customAwaiter, _isCompletedMethod, _getResultMethod, _onCompletedMethod, _unsafeOnCompletedMethod);
return new Awaiter(customAwaiter, _isCompletedMethod, _getResultMethod, _onCompletedMethod,
_unsafeOnCompletedMethod);
}
public struct Awaiter : ICriticalNotifyCompletion
......@@ -84,7 +85,10 @@ namespace Microsoft.Extensions.Internal
public bool IsCompleted => _isCompletedMethod(_customAwaiter);
public object GetResult() => _getResultMethod(_customAwaiter);
public object GetResult()
{
return _getResultMethod(_customAwaiter);
}
public void OnCompleted(Action continuation)
{
......
......@@ -12,7 +12,7 @@ namespace Microsoft.Extensions.Internal
{
/// <summary>
/// Helper for detecting whether a given type is FSharpAsync`1, and if so, supplying
/// an <see cref="Expression"/> for mapping instances of that type to a C# awaitable.
/// an <see cref="Expression" /> for mapping instances of that type to a C# awaitable.
/// </summary>
/// <remarks>
/// The main design goal here is to avoid taking a compile-time dependency on
......@@ -21,7 +21,7 @@ namespace Microsoft.Extensions.Internal
/// </remarks>
internal static class ObjectMethodExecutorFSharpSupport
{
private static object _fsharpValuesCacheLock = new object();
private static readonly object _fsharpValuesCacheLock = new object();
private static Assembly _fsharpCoreAssembly;
private static MethodInfo _fsharpAsyncStartAsTaskGenericMethod;
private static PropertyInfo _fsharpOptionOfTaskCreationOptionsNoneProperty;
......@@ -73,24 +73,13 @@ namespace Microsoft.Extensions.Internal
{
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);
}
return TryPopulateFSharpValueCaches(possibleFSharpAsyncGenericType);
}
}
......@@ -101,9 +90,7 @@ namespace Microsoft.Extensions.Internal
var fsharpAsyncType = assembly.GetType("Microsoft.FSharp.Control.FSharpAsync");
if (fsharpOptionType == null || fsharpAsyncType == null)
{
return false;
}
// Get a reference to FSharpOption<TaskCreationOptions>.None
var fsharpOptionOfTaskCreationOptionsType = fsharpOptionType
......@@ -144,8 +131,8 @@ namespace Microsoft.Extensions.Internal
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);
&& string.Equals(type1.Namespace, type2.Namespace, StringComparison.Ordinal)
&& string.Equals(type1.Name, type2.Name, StringComparison.Ordinal);
}
}
}
}
\ No newline at end of file
......@@ -14,6 +14,7 @@ namespace DotNetCore.CAP.Internal
public SubscriberNotFoundException(string message, Exception inner) :
base(message, inner)
{ }
{
}
}
}
\ No newline at end of file
......@@ -10,8 +10,8 @@ namespace DotNetCore.CAP
private static readonly Action<ILogger, Exception> _serverShuttingDown;
private static readonly Action<ILogger, string, Exception> _expectedOperationCanceledException;
private static readonly Action<ILogger, string, string, Exception> _enqueuingSentMessage;
private static readonly Action<ILogger, string, string, Exception> _enqueuingReceivdeMessage;
private static readonly Action<ILogger, string, string, Exception> _enqueueingSentMessage;
private static readonly Action<ILogger, string, string, Exception> _enqueueingReceivdeMessage;
private static readonly Action<ILogger, string, Exception> _executingConsumerMethod;
private static readonly Action<ILogger, string, Exception> _receivedMessageRetryExecuting;
private static readonly Action<ILogger, string, string, string, Exception> _modelBinderFormattingException;
......@@ -44,12 +44,12 @@ namespace DotNetCore.CAP
3,
"Expected an OperationCanceledException, but found '{ExceptionMessage}'.");
_enqueuingSentMessage = LoggerMessage.Define<string, string>(
_enqueueingSentMessage = LoggerMessage.Define<string, string>(
LogLevel.Debug,
2,
"Enqueuing a topic to the sent message store. NameKey: '{NameKey}' Content: '{Content}'.");
_enqueuingReceivdeMessage = LoggerMessage.Define<string, string>(
_enqueueingReceivdeMessage = LoggerMessage.Define<string, string>(
LogLevel.Debug,
2,
"Enqueuing a topic to the received message store. NameKey: '{NameKey}. Content: '{Content}'.");
......@@ -68,7 +68,7 @@ namespace DotNetCore.CAP
LogLevel.Error,
5,
"When call subscribe method, a parameter format conversion exception occurs. MethodName:'{MethodName}' ParameterName:'{ParameterName}' Content:'{Content}'."
);
);
_jobRetrying = LoggerMessage.Define<int>(
LogLevel.Debug,
......@@ -81,9 +81,9 @@ namespace DotNetCore.CAP
"Job executed. Took: {Seconds} secs.");
_jobFailed = LoggerMessage.Define(
LogLevel.Warning,
1,
"Job failed to execute.");
LogLevel.Warning,
1,
"Job failed to execute.");
_jobFailedWillRetry = LoggerMessage.Define(
LogLevel.Warning,
......@@ -91,10 +91,10 @@ namespace DotNetCore.CAP
"Job failed to execute. Will retry.");
_exceptionOccuredWhileExecutingJob = LoggerMessage.Define<string>(
LogLevel.Error,
6,
"An exception occured while trying to execute a job: '{JobId}'. " +
"Requeuing for another retry.");
LogLevel.Error,
6,
"An exception occured while trying to execute a job: '{JobId}'. " +
"Requeuing for another retry.");
}
public static void JobFailed(this ILogger logger, Exception ex)
......@@ -129,12 +129,12 @@ namespace DotNetCore.CAP
public static void EnqueuingReceivedMessage(this ILogger logger, string nameKey, string content)
{
_enqueuingReceivdeMessage(logger, nameKey, content, null);
_enqueueingReceivdeMessage(logger, nameKey, content, null);
}
public static void EnqueuingSentMessage(this ILogger logger, string nameKey, string content)
{
_enqueuingSentMessage(logger, nameKey, content, null);
_enqueueingSentMessage(logger, nameKey, content, null);
}
public static void ServerStarting(this ILogger logger, int machineProcessorCount, int processorCount)
......@@ -162,7 +162,8 @@ namespace DotNetCore.CAP
_exceptionOccuredWhileExecutingJob(logger, jobId, ex);
}
public static void ModelBinderFormattingException(this ILogger logger, string methodName, string parameterName, string content, Exception ex)
public static void ModelBinderFormattingException(this ILogger logger, string methodName, string parameterName,
string content, Exception ex)
{
_modelBinderFormattingException(logger, methodName, parameterName, content, ex);
}
......
......@@ -4,14 +4,6 @@ namespace DotNetCore.CAP.Models
{
public class CapMessageDto
{
public virtual string Id { get; set; }
public virtual DateTime Timestamp { get; set; }
public virtual object Content { get; set; }
public virtual string CallbackName { get; set; }
public CapMessageDto()
{
Id = ObjectId.GenerateNewStringId();
......@@ -22,5 +14,13 @@ namespace DotNetCore.CAP.Models
{
Content = content;
}
public virtual string Id { get; set; }
public virtual DateTime Timestamp { get; set; }
public virtual object Content { get; set; }
public virtual string CallbackName { get; set; }
}
}
\ No newline at end of file
......@@ -5,11 +5,8 @@ namespace DotNetCore.CAP.Models
public class CapPublishedMessage
{
/// <summary>
/// Initializes a new instance of <see cref="CapPublishedMessage"/>.
/// Initializes a new instance of <see cref="CapPublishedMessage" />.
/// </summary>
/// <remarks>
/// The Id property is initialized to from a new GUID string value.
/// </remarks>
public CapPublishedMessage()
{
Added = DateTime.Now;
......
......@@ -5,7 +5,7 @@
public int MessageId { get; set; }
/// <summary>
/// 0 is CapSentMessage, 1 is CapReceviedMessage
/// 0 is CapSentMessage, 1 is CapReceivedMessage
/// </summary>
public MessageType MessageType { get; set; }
}
......
......@@ -5,11 +5,8 @@ namespace DotNetCore.CAP.Models
public class CapReceivedMessage
{
/// <summary>
/// Initializes a new instance of <see cref="CapReceivedMessage"/>.
/// Initializes a new instance of <see cref="CapReceivedMessage" />.
/// </summary>
/// <remarks>
/// The Id property is initialized to from a new GUID string value.
/// </remarks>
public CapReceivedMessage()
{
Added = DateTime.Now;
......
// ReSharper disable once CheckNamespace
namespace DotNetCore.CAP
{
public class DiscoveryOptions
{
public const string DefaultDiscoveryServerHost = "localhost";
public const int DefaultDiscoveryServerProt = 8500;
public const int DefaultDiscoveryServerPort = 8500;
public const string DefaultCurrentNodeHostName = "localhost";
public const int DefaultCurrentNodePort = 5000;
......@@ -14,7 +15,7 @@ namespace DotNetCore.CAP
public DiscoveryOptions()
{
DiscoveryServerHostName = DefaultDiscoveryServerHost;
DiscoveryServerProt = DefaultDiscoveryServerProt;
DiscoveryServerPort = DefaultDiscoveryServerPort;
CurrentNodeHostName = DefaultCurrentNodeHostName;
CurrentNodePort = DefaultCurrentNodePort;
......@@ -23,7 +24,7 @@ namespace DotNetCore.CAP
}
public string DiscoveryServerHostName { get; set; }
public int DiscoveryServerProt { get; set; }
public int DiscoveryServerPort { get; set; }
public string CurrentNodeHostName { get; set; }
public int CurrentNodePort { get; set; }
......@@ -32,5 +33,4 @@ namespace DotNetCore.CAP
public string NodeName { get; set; }
public string MatchPath { get; set; }
}
}
\ No newline at end of file
using System;
using DotNetCore.CAP;
using DotNetCore.CAP.NodeDiscovery;
using Microsoft.Extensions.DependencyInjection;
namespace DotNetCore.CAP
{
using NodeDiscovery;
using Microsoft.Extensions.DependencyInjection;
internal sealed class DiscoveryOptionsExtension : ICapOptionsExtension
{
private readonly Action<DiscoveryOptions> _options;
......@@ -23,7 +23,7 @@ namespace DotNetCore.CAP
services.AddSingleton<IDiscoveryProviderFactory, DiscoveryProviderFactory>();
services.AddSingleton<IProcessingServer, ConsulProcessingNodeServer>();
services.AddSingleton<INodeDiscoveryProvider>(x =>
services.AddSingleton(x =>
{
var configOptions = x.GetService<DiscoveryOptions>();
var factory = x.GetService<IDiscoveryProviderFactory>();
......@@ -35,8 +35,6 @@ namespace DotNetCore.CAP
namespace Microsoft.Extensions.DependencyInjection
{
using DotNetCore.CAP;
public static class CapDiscoveryOptionsExtensions
{
public static CapOptions UseDiscovery(this CapOptions capOptions)
......
......@@ -7,11 +7,9 @@ namespace DotNetCore.CAP.NodeDiscovery
public INodeDiscoveryProvider Create(DiscoveryOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
return new ConsulNodeDiscoveryProvider(options);
}
}
}
}
\ No newline at end of file
......@@ -4,4 +4,4 @@
{
INodeDiscoveryProvider Create(DiscoveryOptions options);
}
}
}
\ No newline at end of file
using System;
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Consul;
......@@ -8,8 +8,8 @@ namespace DotNetCore.CAP.NodeDiscovery
{
public class ConsulNodeDiscoveryProvider : INodeDiscoveryProvider, IDisposable
{
private ConsulClient _consul;
private readonly DiscoveryOptions _options;
private ConsulClient _consul;
public ConsulNodeDiscoveryProvider(DiscoveryOptions options)
{
......@@ -18,21 +18,19 @@ namespace DotNetCore.CAP.NodeDiscovery
InitClient();
}
public void InitClient()
public void Dispose()
{
_consul = new ConsulClient(config =>
{
config.WaitTime = TimeSpan.FromSeconds(5);
config.Address = new Uri($"http://{_options.DiscoveryServerHostName}:{_options.DiscoveryServerProt}");
});
_consul.Dispose();
}
public async Task<IList<Node>> GetNodes()
{
try {
try
{
var services = await _consul.Agent.Services();
var nodes = services.Response.Select(x => new Node {
var nodes = services.Response.Select(x => new Node
{
Id = x.Key,
Name = x.Value.Service,
Address = x.Value.Address,
......@@ -41,11 +39,12 @@ namespace DotNetCore.CAP.NodeDiscovery
});
var nodeList = nodes.ToList();
CapCache.Global.AddOrUpdate("cap.nodes.count", nodeList.Count, TimeSpan.FromSeconds(30),true);
CapCache.Global.AddOrUpdate("cap.nodes.count", nodeList.Count, TimeSpan.FromSeconds(30), true);
return nodeList;
}
catch (Exception) {
catch (Exception)
{
return null;
}
}
......@@ -58,20 +57,25 @@ namespace DotNetCore.CAP.NodeDiscovery
Name = _options.NodeName,
Address = _options.CurrentNodeHostName,
Port = _options.CurrentNodePort,
Tags = new[] { "CAP", "Client", "Dashboard" },
Tags = new[] {"CAP", "Client", "Dashboard"},
Check = new AgentServiceCheck
{
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(30),
Interval = TimeSpan.FromSeconds(10),
Status = HealthStatus.Passing,
HTTP = $"http://{_options.CurrentNodeHostName}:{_options.CurrentNodePort}{_options.MatchPath}/health"
HTTP =
$"http://{_options.CurrentNodeHostName}:{_options.CurrentNodePort}{_options.MatchPath}/health"
}
});
}
public void Dispose()
public void InitClient()
{
_consul.Dispose();
_consul = new ConsulClient(config =>
{
config.WaitTime = TimeSpan.FromSeconds(5);
config.Address = new Uri($"http://{_options.DiscoveryServerHostName}:{_options.DiscoveryServerPort}");
});
}
}
}
}
\ No newline at end of file
......@@ -9,4 +9,4 @@ namespace DotNetCore.CAP.NodeDiscovery
Task RegisterNode();
}
}
}
\ No newline at end of file
......@@ -27,7 +27,6 @@
public void Dispose()
{
}
}
}
}
\ No newline at end of file
......@@ -12,4 +12,4 @@
public string Tags { get; set; }
}
}
}
\ No newline at end of file
......@@ -22,30 +22,32 @@ namespace DotNetCore.CAP
public Exception Exception { get; set; }
/// <summary>
/// An <see cref="IEnumerable{T}"/> of <see cref="OperateError"/>s containing an errors
/// An <see cref="IEnumerable{T}" /> of <see cref="OperateError" />s containing an errors
/// that occurred during the operation.
/// </summary>
/// <value>An <see cref="IEnumerable{T}"/> of <see cref="OperateError"/>s.</value>
/// <value>An <see cref="IEnumerable{T}" /> of <see cref="OperateError" />s.</value>
public IEnumerable<OperateError> Errors => _errors;
/// <summary>
/// Returns an <see cref="OperateResult"/> indicating a successful identity operation.
/// Returns an <see cref="OperateResult" /> indicating a successful identity operation.
/// </summary>
/// <returns>An <see cref="OperateResult"/> indicating a successful operation.</returns>
public static OperateResult Success { get; } = new OperateResult { Succeeded = true };
/// <returns>An <see cref="OperateResult" /> indicating a successful operation.</returns>
public static OperateResult Success { get; } = new OperateResult {Succeeded = true};
/// <summary>
/// Creates an <see cref="OperateResult"/> indicating a failed operation, with a list of <paramref name="errors"/> if applicable.
/// Creates an <see cref="OperateResult" /> indicating a failed operation, with a list of <paramref name="errors" /> if
/// applicable.
/// </summary>
/// <param name="errors">An optional array of <see cref="OperateError"/>s which caused the operation to fail.</param>
/// <returns>An <see cref="OperateResult"/> indicating a failed operation, with a list of <paramref name="errors"/> if applicable.</returns>
/// <param name="errors">An optional array of <see cref="OperateError" />s which caused the operation to fail.</param>
/// <returns>
/// An <see cref="OperateResult" /> indicating a failed operation, with a list of <paramref name="errors" /> if
/// applicable.
/// </returns>
public static OperateResult Failed(params OperateError[] errors)
{
var result = new OperateResult { Succeeded = false };
var result = new OperateResult {Succeeded = false};
if (errors != null)
{
result._errors.AddRange(errors);
}
return result;
}
......@@ -57,19 +59,17 @@ namespace DotNetCore.CAP
Exception = ex
};
if (errors != null)
{
result._errors.AddRange(errors);
}
return result;
}
/// <summary>
/// Converts the value of the current <see cref="OperateResult"/> object to its equivalent string representation.
/// Converts the value of the current <see cref="OperateResult" /> object to its equivalent string representation.
/// </summary>
/// <returns>A string representation of the current <see cref="OperateResult"/> object.</returns>
/// <returns>A string representation of the current <see cref="OperateResult" /> object.</returns>
/// <remarks>
/// If the operation was successful the ToString() will return "Succeeded" otherwise it returned
/// "Failed : " followed by a comma delimited list of error codes from its <see cref="Errors"/> collection, if any.
/// "Failed : " followed by a comma delimited list of error codes from its <see cref="Errors" /> collection, if any.
/// </remarks>
public override string ToString()
{
......
......@@ -9,14 +9,13 @@ namespace DotNetCore.CAP.Processor
{
public class DefaultDispatcher : IDispatcher
{
private readonly IQueueExecutorFactory _queueExecutorFactory;
internal static readonly AutoResetEvent PulseEvent = new AutoResetEvent(true);
private readonly TimeSpan _pollingDelay;
internal static readonly AutoResetEvent PulseEvent = new AutoResetEvent(true);
private readonly IQueueExecutorFactory _queueExecutorFactory;
public DefaultDispatcher(IQueueExecutorFactory queueExecutorFactory,
IOptions<CapOptions> capOptions)
IOptions<CapOptions> capOptions)
{
_queueExecutorFactory = queueExecutorFactory;
_pollingDelay = TimeSpan.FromSeconds(capOptions.Value.PollingDelay);
......@@ -39,7 +38,7 @@ namespace DotNetCore.CAP.Processor
try
{
var worked = await Step(context);
context.ThrowIfStopping();
Waiting = true;
......@@ -70,13 +69,11 @@ namespace DotNetCore.CAP.Processor
var connection = provider.GetRequiredService<IStorageConnection>();
if ((fetched = await connection.FetchNextMessageAsync()) != null)
{
using (fetched)
{
var queueExecutor = _queueExecutorFactory.GetInstance(fetched.MessageType);
await queueExecutor.ExecuteAsync(connection, fetched);
}
}
}
return fetched != null;
}
......
......@@ -11,18 +11,18 @@ namespace DotNetCore.CAP.Processor
{
public class CapProcessingServer : IProcessingServer
{
private readonly CancellationTokenSource _cts;
private readonly ILogger _logger;
private readonly ILoggerFactory _loggerFactory;
private readonly IServiceProvider _provider;
private readonly CancellationTokenSource _cts;
private readonly CapOptions _options;
private readonly IList<IDispatcher> _messageDispatchers;
private IProcessor[] _processors;
private ProcessingContext _context;
private readonly CapOptions _options;
private readonly IServiceProvider _provider;
private Task _compositeTask;
private ProcessingContext _context;
private bool _disposed;
private IProcessor[] _processors;
public CapProcessingServer(
ILogger<CapProcessingServer> logger,
ILoggerFactory loggerFactory,
......@@ -55,10 +55,7 @@ namespace DotNetCore.CAP.Processor
public void Pulse()
{
if (!AllProcessorsWaiting())
{
// Some processor is still executing jobs so no need to pulse.
return;
}
_logger.LogTrace("Pulsing the Queuer.");
......@@ -68,36 +65,28 @@ namespace DotNetCore.CAP.Processor
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
_logger.ServerShuttingDown();
_cts.Cancel();
try
{
_compositeTask.Wait((int)TimeSpan.FromSeconds(10).TotalMilliseconds);
_compositeTask.Wait((int) TimeSpan.FromSeconds(10).TotalMilliseconds);
}
catch (AggregateException ex)
{
var innerEx = ex.InnerExceptions[0];
if (!(innerEx is OperationCanceledException))
{
_logger.ExpectedOperationCanceledException(innerEx);
}
}
}
private bool AllProcessorsWaiting()
{
foreach (var processor in _messageDispatchers)
{
if (!processor.Waiting)
{
return false;
}
}
return true;
}
......
using System;
using System.Threading.Tasks;
using DotNetCore.CAP.Models;
using DotNetCore.CAP.Processor.States;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
......@@ -9,12 +10,11 @@ namespace DotNetCore.CAP.Processor
{
public class FailedJobProcessor : IProcessor
{
private readonly CapOptions _options;
private readonly TimeSpan _delay = TimeSpan.FromSeconds(1);
private readonly ILogger _logger;
private readonly CapOptions _options;
private readonly IServiceProvider _provider;
private readonly IStateChanger _stateChanger;
private readonly TimeSpan _delay = TimeSpan.FromSeconds(1);
private readonly TimeSpan _waitingInterval;
public FailedJobProcessor(
......@@ -42,7 +42,7 @@ namespace DotNetCore.CAP.Processor
await Task.WhenAll(
ProcessPublishedAsync(connection, context),
ProcessReceivededAsync(connection, context));
ProcessReceivedAsync(connection, context));
DefaultDispatcher.PulseEvent.Set();
......@@ -58,18 +58,15 @@ namespace DotNetCore.CAP.Processor
foreach (var message in messages)
{
if (!hasException)
{
try
{
_options.FailedCallback?.Invoke(Models.MessageType.Publish, message.Name, message.Content);
_options.FailedCallback?.Invoke(MessageType.Publish, message.Name, message.Content);
}
catch (Exception ex)
{
hasException = true;
_logger.LogWarning("Failed call-back method raised an exception:" + ex.Message);
}
}
using (var transaction = connection.CreateTransaction())
{
......@@ -83,26 +80,23 @@ namespace DotNetCore.CAP.Processor
}
}
private async Task ProcessReceivededAsync(IStorageConnection connection, ProcessingContext context)
private async Task ProcessReceivedAsync(IStorageConnection connection, ProcessingContext context)
{
var messages = await connection.GetFailedReceviedMessages();
var messages = await connection.GetFailedReceivedMessages();
var hasException = false;
foreach (var message in messages)
{
if (!hasException)
{
try
{
_options.FailedCallback?.Invoke(Models.MessageType.Subscribe, message.Name, message.Content);
_options.FailedCallback?.Invoke(MessageType.Subscribe, message.Name, message.Content);
}
catch (Exception ex)
{
hasException = true;
_logger.LogWarning("Failed call-back method raised an exception:" + ex.Message);
}
}
using (var transaction = connection.CreateTransaction())
{
......
......@@ -17,12 +17,9 @@ namespace DotNetCore.CAP.Processor
_logger = loggerFactory.CreateLogger<InfiniteRetryProcessor>();
}
public override string ToString() => _inner.ToString();
public async Task ProcessAsync(ProcessingContext context)
{
while (!context.IsStopping)
{
try
{
await _inner.ProcessAsync(context);
......@@ -33,9 +30,13 @@ namespace DotNetCore.CAP.Processor
}
catch (Exception ex)
{
_logger.LogWarning(1, ex, "Prcessor '{ProcessorName}' failed. Retrying...", _inner.ToString());
_logger.LogWarning(1, ex, "Processor '{ProcessorName}' failed. Retrying...", _inner.ToString());
}
}
}
public override string ToString()
{
return _inner.ToString();
}
}
}
\ No newline at end of file
......@@ -12,12 +12,11 @@ namespace DotNetCore.CAP.Processor
{
public class PublishQueuer : IProcessor
{
public static readonly AutoResetEvent PulseEvent = new AutoResetEvent(true);
private readonly ILogger _logger;
private readonly IStateChanger _stateChanger;
private readonly IServiceProvider _provider;
private readonly TimeSpan _pollingDelay;
public static readonly AutoResetEvent PulseEvent = new AutoResetEvent(true);
private readonly IServiceProvider _provider;
private readonly IStateChanger _stateChanger;
public PublishQueuer(
ILogger<PublishQueuer> logger,
......@@ -35,7 +34,7 @@ namespace DotNetCore.CAP.Processor
public async Task ProcessAsync(ProcessingContext context)
{
_logger.LogDebug("Publish Queuer start calling.");
_logger.LogDebug("Publish Queuer start calling.");
using (var scope = _provider.CreateScope())
{
CapPublishedMessage sentMessage;
......
......@@ -12,12 +12,11 @@ namespace DotNetCore.CAP.Processor
{
public class SubscribeQueuer : IProcessor
{
internal static readonly AutoResetEvent PulseEvent = new AutoResetEvent(true);
private readonly ILogger _logger;
private readonly IStateChanger _stateChanger;
private readonly IServiceProvider _provider;
private readonly TimeSpan _pollingDelay;
internal static readonly AutoResetEvent PulseEvent = new AutoResetEvent(true);
private readonly IServiceProvider _provider;
private readonly IStateChanger _stateChanger;
public SubscribeQueuer(
ILogger<SubscribeQueuer> logger,
......@@ -44,7 +43,7 @@ namespace DotNetCore.CAP.Processor
while (
!context.IsStopping &&
(message = await connection.GetNextReceviedMessageToBeEnqueuedAsync()) != null)
(message = await connection.GetNextReceivedMessageToBeEnqueuedAsync()) != null)
{
var state = new EnqueuedState();
......
......@@ -33,7 +33,15 @@ namespace DotNetCore.CAP.Processor
public bool IsStopping => CancellationToken.IsCancellationRequested;
public void ThrowIfStopping() => CancellationToken.ThrowIfCancellationRequested();
public void Dispose()
{
_scope?.Dispose();
}
public void ThrowIfStopping()
{
CancellationToken.ThrowIfCancellationRequested();
}
public ProcessingContext CreateScope()
{
......@@ -50,10 +58,5 @@ namespace DotNetCore.CAP.Processor
{
return Task.Delay(timeout, CancellationToken);
}
public void Dispose()
{
_scope?.Dispose();
}
}
}
\ No newline at end of file
......@@ -10,6 +10,7 @@ namespace DotNetCore.CAP.Processor
public static readonly RetryBehavior DefaultRetry;
public static readonly RetryBehavior NoRetry;
// ReSharper disable once InconsistentNaming
private static readonly Random _random = new Random();
private readonly Func<int, int> _retryInThunk;
......@@ -18,7 +19,7 @@ namespace DotNetCore.CAP.Processor
{
DefaultRetryCount = 15;
DefaultRetryInThunk = retries =>
(int)Math.Round(Math.Pow(retries - 1, 4) + 15 + (_random.Next(30) * (retries)));
(int) Math.Round(Math.Pow(retries - 1, 4) + 15 + _random.Next(30) * retries);
DefaultRetry = new RetryBehavior(true);
NoRetry = new RetryBehavior(false);
......@@ -30,7 +31,7 @@ namespace DotNetCore.CAP.Processor
}
/// <summary>
/// Initializes a new instance of the <see cref="RetryBehavior"/> class.
/// Initializes a new instance of the <see cref="RetryBehavior" /> class.
/// </summary>
/// <param name="retry">Whether to retry.</param>
/// <param name="retryCount">The maximum retry count.</param>
......@@ -38,9 +39,7 @@ namespace DotNetCore.CAP.Processor
public RetryBehavior(bool retry, int retryCount, Func<int, int> retryInThunk)
{
if (retry)
{
if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Can't be negative.");
}
Retry = retry;
RetryCount = retryCount;
......
......@@ -7,10 +7,6 @@ namespace DotNetCore.CAP.Processor.States
{
public const string StateName = "Succeeded";
public TimeSpan? ExpiresAfter { get; }
public string Name => StateName;
public SucceededState()
{
ExpiresAfter = TimeSpan.FromHours(1);
......@@ -21,6 +17,10 @@ namespace DotNetCore.CAP.Processor.States
ExpiresAfter = TimeSpan.FromSeconds(ExpireAfterSeconds);
}
public TimeSpan? ExpiresAfter { get; }
public string Name => StateName;
public void Apply(CapPublishedMessage message, IStorageTransaction transaction)
{
}
......
......@@ -9,13 +9,9 @@ namespace DotNetCore.CAP.Processor.States
{
var now = DateTime.Now;
if (state.ExpiresAfter != null)
{
message.ExpiresAt = now.Add(state.ExpiresAfter.Value);
}
else
{
message.ExpiresAt = null;
}
message.StatusName = state.Name;
state.Apply(message, transaction);
......@@ -26,13 +22,9 @@ namespace DotNetCore.CAP.Processor.States
{
var now = DateTime.Now;
if (state.ExpiresAfter != null)
{
message.ExpiresAt = now.Add(state.ExpiresAfter.Value);
}
else
{
message.ExpiresAt = null;
}
message.StatusName = state.Name;
state.Apply(message, transaction);
......
......@@ -16,11 +16,11 @@ namespace DotNetCore.CAP
public IQueueExecutor GetInstance(MessageType messageType)
{
var queueExectors = _serviceProvider.GetServices<IQueueExecutor>();
var queueExecutors = _serviceProvider.GetServices<IQueueExecutor>();
return messageType == MessageType.Publish
? queueExectors.FirstOrDefault(x => x is BasePublishQueueExecutor)
: queueExectors.FirstOrDefault(x => !(x is BasePublishQueueExecutor));
? queueExecutors.FirstOrDefault(x => x is BasePublishQueueExecutor)
: queueExecutors.FirstOrDefault(x => !(x is BasePublishQueueExecutor));
}
}
}
\ No newline at end of file
......@@ -121,7 +121,7 @@ namespace DotNetCore.CAP.MySql.Test
};
await _storage.StoreReceivedMessageAsync(receivedMessage);
var message = await _storage.GetNextReceviedMessageToBeEnqueuedAsync();
var message = await _storage.GetNextReceivedMessageToBeEnqueuedAsync();
Assert.NotNull(message);
Assert.Equal(StatusName.Scheduled, message.StatusName);
......
......@@ -121,7 +121,7 @@ namespace DotNetCore.CAP.PostgreSql.Test
};
await _storage.StoreReceivedMessageAsync(receivedMessage);
var message = await _storage.GetNextReceviedMessageToBeEnqueuedAsync();
var message = await _storage.GetNextReceivedMessageToBeEnqueuedAsync();
Assert.NotNull(message);
Assert.Equal(StatusName.Scheduled, message.StatusName);
......
......@@ -121,7 +121,7 @@ namespace DotNetCore.CAP.SqlServer.Test
};
await _storage.StoreReceivedMessageAsync(receivedMessage);
var message = await _storage.GetNextReceviedMessageToBeEnqueuedAsync();
var message = await _storage.GetNextReceivedMessageToBeEnqueuedAsync();
Assert.NotNull(message);
Assert.Equal(StatusName.Scheduled, message.StatusName);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment