Browse Source

cleanup code and fix spelling

master
Savorboard 7 years ago
parent
commit
5e432a94bb
100 changed files with 1420 additions and 1551 deletions
  1. +1
    -3
      samples/Sample.Kafka.SqlServer/Startup.cs
  2. +1
    -2
      samples/Sample.RabbitMQ.MySql/Startup.cs
  3. +1
    -1
      samples/Sample.RabbitMQ.PostgreSql/Startup.cs
  4. +1
    -1
      samples/Sample.RabbitMQ.SqlServer/Startup.cs
  5. +10
    -12
      src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs
  6. +3
    -4
      src/DotNetCore.CAP.Kafka/CAP.Options.Extensions.cs
  7. +8
    -10
      src/DotNetCore.CAP.Kafka/KafkaConsumerClient.cs
  8. +8
    -10
      src/DotNetCore.CAP.Kafka/PublishQueueExecutor.cs
  9. +1
    -1
      src/DotNetCore.CAP.MySql/CAP.EFOptions.cs
  10. +1
    -5
      src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs
  11. +1
    -0
      src/DotNetCore.CAP.MySql/CAP.MySqlOptions.cs
  12. +3
    -9
      src/DotNetCore.CAP.MySql/CAP.Options.Extensions.cs
  13. +17
    -14
      src/DotNetCore.CAP.MySql/CapPublisher.cs
  14. +7
    -6
      src/DotNetCore.CAP.MySql/IAdditionalProcessor.Default.cs
  15. +3
    -3
      src/DotNetCore.CAP.MySql/MySqlFetchedMessage.cs
  16. +3
    -7
      src/DotNetCore.CAP.MySql/MySqlStorage.cs
  17. +37
    -28
      src/DotNetCore.CAP.MySql/MySqlStorageConnection.cs
  18. +10
    -6
      src/DotNetCore.CAP.MySql/MySqlStorageTransaction.cs
  19. +1
    -1
      src/DotNetCore.CAP.PostgreSql/CAP.EFOptions.cs
  20. +3
    -9
      src/DotNetCore.CAP.PostgreSql/CAP.Options.Extensions.cs
  21. +1
    -5
      src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs
  22. +1
    -0
      src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlOptions.cs
  23. +17
    -14
      src/DotNetCore.CAP.PostgreSql/CapPublisher.cs
  24. +10
    -13
      src/DotNetCore.CAP.PostgreSql/IAdditionalProcessor.Default.cs
  25. +3
    -3
      src/DotNetCore.CAP.PostgreSql/PostgreSqlFetchedMessage.cs
  26. +15
    -26
      src/DotNetCore.CAP.PostgreSql/PostgreSqlMonitoringApi.cs
  27. +2
    -6
      src/DotNetCore.CAP.PostgreSql/PostgreSqlStorage.cs
  28. +3
    -5
      src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageConnection.cs
  29. +14
    -6
      src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageTransaction.cs
  30. +1
    -4
      src/DotNetCore.CAP.RabbitMQ/CAP.Options.Extensions.cs
  31. +1
    -0
      src/DotNetCore.CAP.RabbitMQ/CAP.RabbiMQOptions.cs
  32. +25
    -21
      src/DotNetCore.CAP.RabbitMQ/ConnectionPool.cs
  33. +1
    -1
      src/DotNetCore.CAP.RabbitMQ/IConnectionPool.cs
  34. +9
    -8
      src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs
  35. +27
    -31
      src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClient.cs
  36. +1
    -1
      src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs
  37. +1
    -1
      src/DotNetCore.CAP.SqlServer/CAP.EFOptions.cs
  38. +3
    -9
      src/DotNetCore.CAP.SqlServer/CAP.Options.Extensions.cs
  39. +1
    -5
      src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs
  40. +1
    -0
      src/DotNetCore.CAP.SqlServer/CAP.SqlServerOptions.cs
  41. +17
    -14
      src/DotNetCore.CAP.SqlServer/CapPublisher.cs
  42. +7
    -7
      src/DotNetCore.CAP.SqlServer/IAdditionalProcessor.Default.cs
  43. +3
    -3
      src/DotNetCore.CAP.SqlServer/SqlServerFetchedMessage.cs
  44. +21
    -32
      src/DotNetCore.CAP.SqlServer/SqlServerMonitoringApi.cs
  45. +3
    -8
      src/DotNetCore.CAP.SqlServer/SqlServerStorage.cs
  46. +2
    -2
      src/DotNetCore.CAP.SqlServer/SqlServerStorageConnection.cs
  47. +10
    -6
      src/DotNetCore.CAP.SqlServer/SqlServerStorageTransaction.cs
  48. +19
    -17
      src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs
  49. +4
    -4
      src/DotNetCore.CAP/Abstractions/ConsumerContext.cs
  50. +6
    -6
      src/DotNetCore.CAP/Abstractions/IConsumerServiceSelector.cs
  51. +1
    -1
      src/DotNetCore.CAP/Abstractions/IContentSerializer.cs
  52. +14
    -24
      src/DotNetCore.CAP/Abstractions/ModelBinding/ModelBindingResult.cs
  53. +3
    -3
      src/DotNetCore.CAP/Abstractions/TopicAttribute.cs
  54. +12
    -19
      src/DotNetCore.CAP/CAP.AppBuilderExtensions.cs
  55. +2
    -4
      src/DotNetCore.CAP/CAP.Builder.cs
  56. +8
    -7
      src/DotNetCore.CAP/CAP.Options.cs
  57. +6
    -14
      src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs
  58. +96
    -48
      src/DotNetCore.CAP/CapCache.cs
  59. +1
    -1
      src/DotNetCore.CAP/Dashboard/BatchCommandDispatcher.cs
  60. +7
    -8
      src/DotNetCore.CAP/Dashboard/CAP.DashboardMiddleware.cs
  61. +2
    -3
      src/DotNetCore.CAP/Dashboard/CAP.DashboardOptions.cs
  62. +8
    -10
      src/DotNetCore.CAP/Dashboard/CAP.DashboardOptionsExtensions.cs
  63. +0
    -2
      src/DotNetCore.CAP/Dashboard/CombinedResourceDispatcher.cs
  64. +2
    -6
      src/DotNetCore.CAP/Dashboard/CommandDispatcher.cs
  65. +178
    -272
      src/DotNetCore.CAP/Dashboard/Content/css/cap.css
  66. +314
    -271
      src/DotNetCore.CAP/Dashboard/Content/js/cap.js
  67. +65
    -65
      src/DotNetCore.CAP/Dashboard/DashboardMetrics.cs
  68. +5
    -2
      src/DotNetCore.CAP/Dashboard/DashboardRequest.cs
  69. +5
    -6
      src/DotNetCore.CAP/Dashboard/EmbeddedResourceDispatcher.cs
  70. +12
    -19
      src/DotNetCore.CAP/Dashboard/GatewayProxy/GatewayProxyMiddleware.cs
  71. +5
    -13
      src/DotNetCore.CAP/Dashboard/GatewayProxy/IRequestMapper.Default.cs
  72. +5
    -5
      src/DotNetCore.CAP/Dashboard/GatewayProxy/IRequestMapper.cs
  73. +4
    -3
      src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/HttpClientBuilder.cs
  74. +1
    -3
      src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/HttpClientHttpRequester.cs
  75. +1
    -1
      src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpClientBuilder.cs
  76. +2
    -3
      src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/MemoryHttpClientCache.cs
  77. +33
    -59
      src/DotNetCore.CAP/Dashboard/HtmlHelper.cs
  78. +2
    -4
      src/DotNetCore.CAP/Dashboard/JsonDispatcher.cs
  79. +1
    -1
      src/DotNetCore.CAP/Dashboard/JsonStats.cs
  80. +2
    -4
      src/DotNetCore.CAP/Dashboard/LocalRequestsOnlyAuthorizationFilter.cs
  81. +1
    -3
      src/DotNetCore.CAP/Dashboard/MenuItem.cs
  82. +7
    -16
      src/DotNetCore.CAP/Dashboard/MessageHistoryRenderer.cs
  83. +6
    -5
      src/DotNetCore.CAP/Dashboard/MessagesSidebarMenu.cs
  84. +1
    -1
      src/DotNetCore.CAP/Dashboard/Metric.cs
  85. +3
    -3
      src/DotNetCore.CAP/Dashboard/Pager.cs
  86. +9
    -11
      src/DotNetCore.CAP/Dashboard/Pages/HomePage.cshtml
  87. +57
    -56
      src/DotNetCore.CAP/Dashboard/Pages/LayoutPage.cshtml
  88. +3
    -4
      src/DotNetCore.CAP/Dashboard/Pages/NodePage.cs
  89. +22
    -23
      src/DotNetCore.CAP/Dashboard/Pages/NodePage.cshtml
  90. +4
    -6
      src/DotNetCore.CAP/Dashboard/Pages/PublishedPage.cs
  91. +49
    -47
      src/DotNetCore.CAP/Dashboard/Pages/PublishedPage.cshtml
  92. +4
    -6
      src/DotNetCore.CAP/Dashboard/Pages/ReceivedPage.cs
  93. +55
    -53
      src/DotNetCore.CAP/Dashboard/Pages/ReceivedPage.cshtml
  94. +31
    -32
      src/DotNetCore.CAP/Dashboard/Pages/SubscriberPage.cshtml
  95. +1
    -1
      src/DotNetCore.CAP/Dashboard/Pages/_BlockMetric.cshtml
  96. +9
    -4
      src/DotNetCore.CAP/Dashboard/Pages/_Breadcrumbs.cshtml
  97. +5
    -2
      src/DotNetCore.CAP/Dashboard/Pages/_Navigation.cshtml
  98. +1
    -1
      src/DotNetCore.CAP/Dashboard/Pages/_Paginator.cs
  99. +5
    -5
      src/DotNetCore.CAP/Dashboard/Pages/_Paginator.cshtml
  100. +1
    -1
      src/DotNetCore.CAP/Dashboard/Pages/_PerPageSelector.cs

+ 1
- 3
samples/Sample.Kafka.SqlServer/Startup.cs View File

@@ -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();
}
}
}

+ 1
- 2
samples/Sample.RabbitMQ.MySql/Startup.cs View 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();
}
}
}

+ 1
- 1
samples/Sample.RabbitMQ.PostgreSql/Startup.cs View File

@@ -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一号节点";


+ 1
- 1
samples/Sample.RabbitMQ.SqlServer/Startup.cs View File

@@ -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
- 12
src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs View File

@@ -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";


+ 3
- 4
src/DotNetCore.CAP.Kafka/CAP.Options.Extensions.cs View File

@@ -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)


+ 8
- 10
src/DotNetCore.CAP.Kafka/KafkaConsumerClient.cs View File

@@ -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)


+ 8
- 10
src/DotNetCore.CAP.Kafka/PublishQueueExecutor.cs View File

@@ -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));
}


+ 1
- 1
src/DotNetCore.CAP.MySql/CAP.EFOptions.cs View File

@@ -6,7 +6,7 @@ namespace DotNetCore.CAP
public class EFOptions
{
/// <summary>
/// EF dbcontext type.
/// EF db context type.
/// </summary>
internal Type DbContextType { get; set; }
}

+ 1
- 5
src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs View File

@@ -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);
}
}
}
}

+ 1
- 0
src/DotNetCore.CAP.MySql/CAP.MySqlOptions.cs View File

@@ -1,4 +1,5 @@
// ReSharper disable once CheckNamespace

namespace DotNetCore.CAP
{
public class MySqlOptions : EFOptions


+ 3
- 9
src/DotNetCore.CAP.MySql/CAP.Options.Extensions.cs View File

@@ -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));


+ 17
- 14
src/DotNetCore.CAP.MySql/CapPublisher.cs View File

@@ -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


+ 7
- 6
src/DotNetCore.CAP.MySql/IAdditionalProcessor.Default.cs View File

@@ -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)


+ 3
- 3
src/DotNetCore.CAP.MySql/MySqlFetchedMessage.cs View File

@@ -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,


+ 3
- 7
src/DotNetCore.CAP.MySql/MySqlStorage.cs View File

@@ -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();
}
}
}
}

+ 37
- 28
src/DotNetCore.CAP.MySql/MySqlStorageConnection.cs View 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);
}
}
}

+ 10
- 6
src/DotNetCore.CAP.MySql/MySqlStorageTransaction.cs View 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()


+ 1
- 1
src/DotNetCore.CAP.PostgreSql/CAP.EFOptions.cs View File

@@ -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;



+ 3
- 9
src/DotNetCore.CAP.PostgreSql/CAP.Options.Extensions.cs View File

@@ -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));


+ 1
- 5
src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs View File

@@ -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);
}
}
}
}

+ 1
- 0
src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlOptions.cs View File

@@ -1,4 +1,5 @@
// ReSharper disable once CheckNamespace

namespace DotNetCore.CAP
{
public class PostgreSqlOptions : EFOptions


+ 17
- 14
src/DotNetCore.CAP.PostgreSql/CapPublisher.cs View File

@@ -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


+ 10
- 13
src/DotNetCore.CAP.PostgreSql/IAdditionalProcessor.Default.cs View File

@@ -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)


+ 3
- 3
src/DotNetCore.CAP.PostgreSql/PostgreSqlFetchedMessage.cs View File

@@ -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,


+ 15
- 26
src/DotNetCore.CAP.PostgreSql/PostgreSqlMonitoringApi.cs View File

@@ -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++)


+ 2
- 6
src/DotNetCore.CAP.PostgreSql/PostgreSqlStorage.cs View File

@@ -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)


+ 3
- 5
src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageConnection.cs View File

@@ -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))
{


+ 14
- 6
src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageTransaction.cs View File

@@ -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()


+ 1
- 4
src/DotNetCore.CAP.RabbitMQ/CAP.Options.Extensions.cs View File

@@ -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)


+ 1
- 0
src/DotNetCore.CAP.RabbitMQ/CAP.RabbiMQOptions.cs View File

@@ -1,4 +1,5 @@
// ReSharper disable once CheckNamespace

namespace DotNetCore.CAP
{
public class RabbitMQOptions


+ 25
- 21
src/DotNetCore.CAP.RabbitMQ/ConnectionPool.cs View File

@@ -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();
}
}
}
}
}

+ 1
- 1
src/DotNetCore.CAP.RabbitMQ/IConnectionPool.cs View File

@@ -8,4 +8,4 @@ namespace DotNetCore.CAP.RabbitMQ

bool Return(IConnection context);
}
}
}

+ 9
- 8
src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs View 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


+ 27
- 31
src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClient.cs View File

@@ -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)


+ 1
- 1
src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs View File

@@ -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)


+ 1
- 1
src/DotNetCore.CAP.SqlServer/CAP.EFOptions.cs View File

@@ -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;



+ 3
- 9
src/DotNetCore.CAP.SqlServer/CAP.Options.Extensions.cs View File

@@ -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));


+ 1
- 5
src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs View File

@@ -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);
}
}
}
}

+ 1
- 0
src/DotNetCore.CAP.SqlServer/CAP.SqlServerOptions.cs View File

@@ -1,4 +1,5 @@
// ReSharper disable once CheckNamespace

namespace DotNetCore.CAP
{
public class SqlServerOptions : EFOptions


+ 17
- 14
src/DotNetCore.CAP.SqlServer/CapPublisher.cs View File

@@ -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


+ 7
- 7
src/DotNetCore.CAP.SqlServer/IAdditionalProcessor.Default.cs View File

@@ -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)


+ 3
- 3
src/DotNetCore.CAP.SqlServer/SqlServerFetchedMessage.cs View File

@@ -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,


+ 21
- 32
src/DotNetCore.CAP.SqlServer/SqlServerMonitoringApi.cs View File

@@ -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++)


+ 3
- 8
src/DotNetCore.CAP.SqlServer/SqlServerStorage.cs View File

@@ -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();
}
}

}
}

+ 2
- 2
src/DotNetCore.CAP.SqlServer/SqlServerStorageConnection.cs View 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}'";


+ 10
- 6
src/DotNetCore.CAP.SqlServer/SqlServerStorageTransaction.cs View File

@@ -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()


+ 19
- 17
src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs View File

@@ -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();
}



+ 4
- 4
src/DotNetCore.CAP/Abstractions/ConsumerContext.cs View File

@@ -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; }
}

+ 6
- 6
src/DotNetCore.CAP/Abstractions/IConsumerServiceSelector.cs View File

@@ -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);


+ 1
- 1
src/DotNetCore.CAP/Abstractions/IContentSerializer.cs View File

@@ -8,4 +8,4 @@ namespace DotNetCore.CAP.Abstractions

T DeSerialize<T>(string content) where T : CapMessageDto, new();
}
}
}

+ 14
- 24
src/DotNetCore.CAP/Abstractions/ModelBinding/ModelBindingResult.cs View 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)
{


+ 3
- 3
src/DotNetCore.CAP/Abstractions/TopicAttribute.cs View File

@@ -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";
}

+ 12
- 19
src/DotNetCore.CAP/CAP.AppBuilderExtensions.cs View File

@@ -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(...) })");
}
}
}

+ 2
- 4
src/DotNetCore.CAP/CAP.Builder.cs View 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>()


+ 8
- 7
src/DotNetCore.CAP/CAP.Options.cs View File

@@ -1,5 +1,6 @@
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.


+ 6
- 14
src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs View File

@@ -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);
}
}
}
}

+ 96
- 48
src/DotNetCore.CAP/CapCache.cs View 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
}

+ 1
- 1
src/DotNetCore.CAP/Dashboard/BatchCommandDispatcher.cs View File

@@ -28,7 +28,7 @@ namespace DotNetCore.CAP.Dashboard
_command(context, id);
}

context.Response.StatusCode = (int)HttpStatusCode.NoContent;
context.Response.StatusCode = (int) HttpStatusCode.NoContent;
}
}
}

+ 7
- 8
src/DotNetCore.CAP/Dashboard/CAP.DashboardMiddleware.cs View 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
}
}
}
}
}

+ 2
- 3
src/DotNetCore.CAP/Dashboard/CAP.DashboardOptions.cs View 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; }
}

}
}

+ 8
- 10
src/DotNetCore.CAP/Dashboard/CAP.DashboardOptionsExtensions.cs View File

@@ -1,12 +1,12 @@
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)


+ 0
- 2
src/DotNetCore.CAP/Dashboard/CombinedResourceDispatcher.cs View File

@@ -22,12 +22,10 @@ namespace DotNetCore.CAP.Dashboard
protected override void WriteResponse(DashboardResponse response)
{
foreach (var resourceName in _resourceNames)
{
WriteResource(
response,
_assembly,
$"{_baseNamespace}.{resourceName}");
}
}
}
}

+ 2
- 6
src/DotNetCore.CAP/Dashboard/CommandDispatcher.cs View 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);
}


+ 178
- 272
src/DotNetCore.CAP/Dashboard/Content/css/cap.css View File

@@ -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;
}

+ 314
- 271
src/DotNetCore.CAP/Dashboard/Content/js/cap.js View File

@@ -1,16 +1,16 @@
(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)};`;
}

+ 65
- 65
src/DotNetCore.CAP/Dashboard/DashboardMetrics.cs View 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();
}
}
}
}

+ 5
- 2
src/DotNetCore.CAP/Dashboard/DashboardRequest.cs View 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)
{


+ 5
- 6
src/DotNetCore.CAP/Dashboard/EmbeddedResourceDispatcher.cs View File

@@ -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);
}


+ 12
- 19
src/DotNetCore.CAP/Dashboard/GatewayProxy/GatewayProxyMiddleware.cs View File

@@ -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()));
}
}
}
}

+ 5
- 13
src/DotNetCore.CAP/Dashboard/GatewayProxy/IRequestMapper.Default.cs View 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)


+ 5
- 5
src/DotNetCore.CAP/Dashboard/GatewayProxy/IRequestMapper.cs View File

@@ -1,9 +1,9 @@
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);


+ 4
- 3
src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/HttpClientBuilder.cs View File

@@ -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);


+ 1
- 3
src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/HttpClientHttpRequester.cs View File

@@ -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;
}


+ 1
- 1
src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpClientBuilder.cs View File

@@ -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();
}

+ 2
- 3
src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/MemoryHttpClientCache.cs View File

@@ -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;
}



+ 33
- 59
src/DotNetCore.CAP/Dashboard/HtmlHelper.cs View File

@@ -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
}
}

+ 2
- 4
src/DotNetCore.CAP/Dashboard/JsonDispatcher.cs View 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);


+ 1
- 1
src/DotNetCore.CAP/Dashboard/JsonStats.cs View File

@@ -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);



+ 2
- 4
src/DotNetCore.CAP/Dashboard/LocalRequestsOnlyAuthorizationFilter.cs View File

@@ -1,13 +1,11 @@
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


+ 1
- 3
src/DotNetCore.CAP/Dashboard/MenuItem.cs View File

@@ -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();
}


+ 7
- 16
src/DotNetCore.CAP/Dashboard/MessageHistoryRenderer.cs View File

@@ -1,5 +1,7 @@
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)
{


+ 6
- 5
src/DotNetCore.CAP/Dashboard/MessagesSidebarMenu.cs View File

@@ -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"))
{


+ 1
- 1
src/DotNetCore.CAP/Dashboard/Metric.cs View File

@@ -20,7 +20,7 @@
Info,
Success,
Warning,
Danger,
Danger
}

internal static class MetricStyleExtensions


+ 3
- 3
src/DotNetCore.CAP/Dashboard/Pager.cs View File

@@ -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);
}


+ 9
- 11
src/DotNetCore.CAP/Dashboard/Pages/HomePage.cshtml View File

@@ -1,21 +1,18 @@
@* 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>

+ 57
- 56
src/DotNetCore.CAP/Dashboard/Pages/LayoutPage.cshtml View 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>

+ 3
- 4
src/DotNetCore.CAP/Dashboard/Pages/NodePage.cs View 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;
}
}
}
}
}

+ 22
- 23
src/DotNetCore.CAP/Dashboard/Pages/NodePage.cshtml View File

@@ -1,8 +1,7 @@
@* 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>


+ 4
- 6
src/DotNetCore.CAP/Dashboard/Pages/PublishedPage.cs View File

@@ -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();
}
}

+ 49
- 47
src/DotNetCore.CAP/Dashboard/Pages/PublishedPage.cshtml View File

@@ -1,11 +1,11 @@
@* 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>


+ 4
- 6
src/DotNetCore.CAP/Dashboard/Pages/ReceivedPage.cs View File

@@ -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();
}
}

+ 55
- 53
src/DotNetCore.CAP/Dashboard/Pages/ReceivedPage.cshtml View File

@@ -1,11 +1,11 @@
@* 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>


+ 31
- 32
src/DotNetCore.CAP/Dashboard/Pages/SubscriberPage.cshtml View File

@@ -1,9 +1,8 @@
@* 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>


+ 1
- 1
src/DotNetCore.CAP/Dashboard/Pages/_BlockMetric.cshtml View File

@@ -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>


+ 9
- 4
src/DotNetCore.CAP/Dashboard/Pages/_Breadcrumbs.cshtml View File

@@ -1,12 +1,17 @@
@* 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>

+ 5
- 2
src/DotNetCore.CAP/Dashboard/Pages/_Navigation.cshtml View File

@@ -1,6 +1,6 @@
@* 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">


+ 1
- 1
src/DotNetCore.CAP/Dashboard/Pages/_Paginator.cs View File

@@ -1,6 +1,6 @@
namespace DotNetCore.CAP.Dashboard.Pages
{
partial class Paginator
internal partial class Paginator
{
private readonly Pager _pager;



+ 5
- 5
src/DotNetCore.CAP/Dashboard/Pages/_Paginator.cshtml View File

@@ -1,8 +1,8 @@
@* 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>

+ 1
- 1
src/DotNetCore.CAP/Dashboard/Pages/_PerPageSelector.cs View File

@@ -1,6 +1,6 @@
namespace DotNetCore.CAP.Dashboard.Pages
{
partial class PerPageSelector
internal partial class PerPageSelector
{
private readonly Pager _pager;



Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save