renable weapon name in anticheat snapshot list

update migrations for unique index
fix missing total connection time
include total connection time in get client query
This commit is contained in:
RaidMax 2019-11-25 12:05:12 -06:00
parent 56008e80c7
commit b086190ab0
18 changed files with 221 additions and 114 deletions

View File

@ -84,18 +84,18 @@ namespace IW4MAdmin
if (client.ClientNumber >= 0) if (client.ClientNumber >= 0)
{ {
#endif #endif
Logger.WriteInfo($"Client {client} [{client.State.ToString().ToLower()}] disconnecting..."); Logger.WriteInfo($"Client {client} [{client.State.ToString().ToLower()}] disconnecting...");
Clients[client.ClientNumber] = null; Clients[client.ClientNumber] = null;
await client.OnDisconnect(); await client.OnDisconnect();
var e = new GameEvent() var e = new GameEvent()
{ {
Origin = client, Origin = client,
Owner = this, Owner = this,
Type = GameEvent.EventType.Disconnect Type = GameEvent.EventType.Disconnect
}; };
Manager.GetEventHandler().AddEvent(e); Manager.GetEventHandler().AddEvent(e);
#if DEBUG == true #if DEBUG == true
} }
#endif #endif
@ -176,10 +176,14 @@ namespace IW4MAdmin
finally finally
{ {
E.Origin?.Unlock(); if (E.IsBlocking)
{
E.Origin?.Unlock();
}
if (lastException != null) if (lastException != null)
{ {
Logger.WriteDebug("Last Exception is not null");
throw lastException; throw lastException;
} }
} }
@ -555,28 +559,25 @@ namespace IW4MAdmin
private async Task OnClientUpdate(EFClient origin) private async Task OnClientUpdate(EFClient origin)
{ {
var client = GetClientsAsList().FirstOrDefault(_client => _client.Equals(origin)); var client = GetClientsAsList().First(_client => _client.Equals(origin));
if (client != null) client.Ping = origin.Ping;
client.Score = origin.Score;
// update their IP if it hasn't been set yet
if (client.IPAddress == null &&
!client.IsBot &&
client.State == ClientState.Connected)
{ {
client.Ping = origin.Ping; try
client.Score = origin.Score;
// update their IP if it hasn't been set yet
if (client.IPAddress == null &&
!client.IsBot &&
client.State == ClientState.Connected)
{ {
try await client.OnJoin(origin.IPAddress);
{ }
await client.OnJoin(origin.IPAddress);
}
catch (Exception e) catch (Exception e)
{ {
origin.CurrentServer.Logger.WriteWarning($"Could not execute on join for {origin}"); origin.CurrentServer.Logger.WriteWarning($"Could not execute on join for {origin}");
origin.CurrentServer.Logger.WriteDebug(e.GetExceptionInfo()); origin.CurrentServer.Logger.WriteDebug(e.GetExceptionInfo());
}
} }
} }
} }

View File

@ -26,7 +26,7 @@ var plugin = {
rconParser.Configuration.Dvar.AddMapping(107, 2); rconParser.Configuration.Dvar.AddMapping(107, 2);
rconParser.Configuration.WaitForResponse = false; rconParser.Configuration.WaitForResponse = false;
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +([0-9]+) +(.+) +((?:[A-Z]+|[0-9]+)) +((?:[A-Z]|[0-9]){1,16}) +(.{0,16}) +([0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+:-?\\d{1,5}|loopback) +(-?[0-9]+) +([0-9]+) *$' rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +([0-9]+) +(.+) +((?:[A-Z]+|[0-9]+)) +((?:[A-Z]|[0-9]){1,16}) +(.{0,16}) +([0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+:-?\\d{1,5}|loopback) +(-?[0-9]+) +([0-9]+) *$';
rconParser.Configuration.Status.AddMapping(100, 1); rconParser.Configuration.Status.AddMapping(100, 1);
rconParser.Configuration.Status.AddMapping(101, 2); rconParser.Configuration.Status.AddMapping(101, 2);
rconParser.Configuration.Status.AddMapping(102, 4); rconParser.Configuration.Status.AddMapping(102, 4);

View File

@ -110,6 +110,7 @@ namespace IW4MAdmin.Plugins.Stats.Web.Controllers
.Select(_penalty => new { _penalty.OffenderId, _penalty.PenaltyId, _penalty.When, _penalty.AutomatedOffense }) .Select(_penalty => new { _penalty.OffenderId, _penalty.PenaltyId, _penalty.When, _penalty.AutomatedOffense })
.FirstOrDefaultAsync(_penalty => _penalty.PenaltyId == penaltyId); .FirstOrDefaultAsync(_penalty => _penalty.PenaltyId == penaltyId);
// todo: this can be optimized
var iqSnapshotInfo = ctx.Set<Models.EFACSnapshot>() var iqSnapshotInfo = ctx.Set<Models.EFACSnapshot>()
.Where(s => s.ClientId == penalty.OffenderId) .Where(s => s.ClientId == penalty.OffenderId)
.Include(s => s.LastStrainAngle) .Include(s => s.LastStrainAngle)
@ -121,9 +122,6 @@ namespace IW4MAdmin.Plugins.Stats.Web.Controllers
.OrderBy(s => s.When) .OrderBy(s => s.When)
.ThenBy(s => s.Hits); .ThenBy(s => s.Hits);
#if DEBUG == true
var sql = iqSnapshotInfo.ToSql();
#endif
var penaltyInfo = await iqSnapshotInfo.ToListAsync(); var penaltyInfo = await iqSnapshotInfo.ToListAsync();
if (penaltyInfo.Count > 0) if (penaltyInfo.Count > 0)

View File

@ -548,6 +548,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
} }
catch (TaskCanceledException) { }
catch (Exception ex) catch (Exception ex)
{ {
_log.WriteError("Could not save hit or AC info"); _log.WriteError("Could not save hit or AC info");

View File

@ -1,14 +1,11 @@
using IW4MAdmin.Plugins.Stats.Models; using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Helpers; using SharedLibraryCore.Helpers;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Text;
namespace IW4MAdmin.Plugins.Stats.Models namespace IW4MAdmin.Plugins.Stats.Models
{ {
public class EFACSnapshotVector3 public class EFACSnapshotVector3 : SharedEntity
{ {
[Key] [Key]
public int ACSnapshotVector3Id { get; set; } public int ACSnapshotVector3Id { get; set; }

View File

@ -1,11 +1,7 @@
using SharedLibraryCore.Database.Models; using SharedLibraryCore.Database.Models;
using System; using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IW4MAdmin.Plugins.Stats.Models namespace IW4MAdmin.Plugins.Stats.Models
{ {

View File

@ -3,10 +3,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using SharedLibraryCore.Database.Models; using SharedLibraryCore.Database.Models;
namespace IW4MAdmin.Plugins.Stats.Models namespace IW4MAdmin.Plugins.Stats.Models

View File

@ -14,7 +14,7 @@ namespace Tests
public override string Version => "test"; public override string Version => "test";
public override async Task<List<EFClient>> GetStatusAsync(Connection connection) public override async Task<(List<EFClient>, string)> GetStatusAsync(Connection connection)
{ {
var clientList = new List<EFClient>(); var clientList = new List<EFClient>();
@ -32,7 +32,7 @@ namespace Tests
}); });
} }
return clientList.Count > 0 ? clientList : FakeClients; return clientList.Count > 0 ? (clientList, "mp_rust") : (FakeClients, "mp_rust");
} }
} }
} }

View File

@ -10,12 +10,12 @@
var snapProperties = Model.First().GetType().GetProperties(); var snapProperties = Model.First().GetType().GetProperties();
foreach (var prop in snapProperties) foreach (var prop in snapProperties)
{ {
@if (prop.Name.EndsWith("Id") || new[] { "Active", "Client", "PredictedViewAngles" }.Contains(prop.Name)) @if ((prop.Name.EndsWith("Id") && prop.Name != "WeaponId") || new[] { "Active", "Client", "PredictedViewAngles" }.Contains(prop.Name))
{ {
continue; continue;
} }
<span class="text-white">@prop.Name </span> <span>&mdash; @prop.GetValue(snapshot)</span><br /> <span class="text-white">@prop.Name </span> <span>&mdash; @prop.GetValue(snapshot).ToString()</span><br />
} }
<div class="w-100 mt-1 mb-1 border-bottom"></div> <div class="w-100 mt-1 mb-1 border-bottom"></div>
} }

View File

@ -10,6 +10,8 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace SharedLibraryCore.Database namespace SharedLibraryCore.Database
{ {
@ -69,8 +71,8 @@ namespace SharedLibraryCore.Database
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {
// optionsBuilder.UseLoggerFactory(_loggerFactory) // optionsBuilder.UseLoggerFactory(_loggerFactory)
// .EnableSensitiveDataLogging(); // .EnableSensitiveDataLogging();
if (string.IsNullOrEmpty(_ConnectionString)) if (string.IsNullOrEmpty(_ConnectionString))
{ {
@ -102,6 +104,41 @@ namespace SharedLibraryCore.Database
} }
} }
private void SetAuditColumns()
{
return;
var entries = ChangeTracker
.Entries()
.Where(e => e.Entity is SharedEntity && (
e.State == EntityState.Added
|| e.State == EntityState.Modified)).ToList();
foreach (var entityEntry in entries)
{
if (entityEntry.State == EntityState.Added)
{
//((SharedEntity)entityEntry.Entity).CreatedDateTime = DateTime.UtcNow;
}
else
{
//((SharedEntity)entityEntry.Entity).UpdatedDateTime = DateTime.UtcNow;
}
}
}
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
SetAuditColumns();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
public override int SaveChanges()
{
SetAuditColumns();
return base.SaveChanges();
}
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
// make network id unique // make network id unique

View File

@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace SharedLibraryCore.Database.Models namespace SharedLibraryCore.Database.Models
{ {
public partial class EFAlias public partial class EFAlias : SharedEntity
{ {
[Key] [Key]
public int AliasId { get; set; } public int AliasId { get; set; }

View File

@ -1,13 +1,25 @@
using System; using System;
using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Database.Models namespace SharedLibraryCore.Database.Models
{ {
public class SharedEntity public class SharedEntity
{ {
/// <summary>
/// indicates if the entity is active
/// </summary>
public bool Active { get; set; } = true; public bool Active { get; set; } = true;
///// <summary>
///// Specifies when the entity was created
///// </summary>
//[Column(TypeName="datetime")]
//public DateTime CreatedDateTime { get; set; }
///// <summary>
///// Specifies when the entity was updated
///// </summary>
//[Column(TypeName = "datetime")]
//public DateTime? UpdatedDateTime { get;set; }
} }
} }

View File

@ -1,7 +1,4 @@
using System; using System.Threading.Tasks;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Interfaces namespace SharedLibraryCore.Interfaces
{ {

View File

@ -8,11 +8,55 @@ namespace SharedLibraryCore.Migrations
{ {
if (migrationBuilder.ActiveProvider == "Microsoft.EntityFrameworkCore.Sqlite") if (migrationBuilder.ActiveProvider == "Microsoft.EntityFrameworkCore.Sqlite")
{ {
migrationBuilder.Sql(@"DELETE FROM EFAlias WHERE AliasId IN ( migrationBuilder.Sql(@"DROP TABLE IF EXISTS DUPLICATE_ALIASES;
SELECT AliasId from ( CREATE TABLE DUPLICATE_ALIASES AS
SELECT AliasId, Name, Min(DateAdded), IPAddress FROM EFAlias where (IPAddress, Name) in (SELECT DISTINCT IPAddress, Name FROM EFAlias GROUP BY EFAlias.IPAddress, Name HAVING count(IPAddress) > 1 AND count(Name) > 1) SELECT
GROUP BY IPAddress ORDER BY IPAddress))", true); MIN(AliasId) MIN,
migrationBuilder.Sql(@"CREATE UNIQUE INDEX IX_EFAlias_Name_IPAddress ON EFAlias ( IPAddress, Name );", true); MAX(AliasId) MAX,
LinkId
FROM
EFAlias
WHERE
(IPAddress, NAME) IN(
SELECT DISTINCT
IPAddress,
NAME
FROM
EFAlias
GROUP BY
EFAlias.IPAddress,
NAME
HAVING
COUNT(IPAddress) > 1 AND COUNT(NAME) > 1
)
GROUP BY
IPAddress
ORDER BY
IPAddress;
UPDATE
EFClients
SET CurrentAliasId = (SELECT MAX FROM DUPLICATE_ALIASES WHERE CurrentAliasId = MIN)
WHERE
CurrentAliasId IN(
SELECT
MIN
FROM
DUPLICATE_ALIASES
);
DELETE
FROM
EFAlias
WHERE
AliasId IN(
SELECT
MIN
FROM
DUPLICATE_ALIASES
);
DROP TABLE
DUPLICATE_ALIASES;");
return; return;
} }
@ -77,30 +121,57 @@ DROP TABLE
else else
{ {
migrationBuilder.Sql(@"DELETE migrationBuilder.Sql(@"CREATE TEMPORARY TABLE DUPLICATE_ALIASES AS
FROM ""EFAlias"" SELECT
WHERE ""AliasId"" MIN(""AliasId"") ""MIN"",
IN MAX(""AliasId"") ""MAX"",
( MIN(""LinkId"") ""LinkId""
SELECT MIN(""AliasId"") AliasId FROM
""EFAlias""
FROM ""EFAlias"" WHERE(""IPAddress"", ""Name"") WHERE
(""IPAddress"", ""Name"") IN(
IN SELECT DISTINCT
( ""IPAddress"",
SELECT DISTINCT ""IPAddress"", ""Name"" ""Name""
FROM
FROM ""EFAlias"" ""EFAlias""
GROUP BY
GROUP BY ""EFAlias"".""IPAddress"", ""Name"" ""EFAlias"".""IPAddress"",
""Name""
HAVING COUNT(""IPAddress"") > 1 AND COUNT(""Name"") > 1 HAVING
) COUNT(""IPAddress"") > 1 AND COUNT(""Name"") > 1
)
GROUP BY ""IPAddress"" GROUP BY
""IPAddress""
ORDER BY ""IPAddress"" ORDER BY
)", true); ""IPAddress"";
UPDATE
""EFClients"" AS ""Client""
SET
""CurrentAliasId"" = ""Duplicate"".""MAX""
FROM
DUPLICATE_ALIASES ""Duplicate""
WHERE
""Client"".""CurrentAliasId"" IN(
SELECT
""MIN""
FROM
DUPLICATE_ALIASES
)
AND
""Client"".""CurrentAliasId"" = ""Duplicate"".""MIN"";
DELETE
FROM
""EFAlias""
WHERE
""AliasId"" IN(
SELECT
""MIN""
FROM
DUPLICATE_ALIASES
);
DROP TABLE
DUPLICATE_ALIASES;");
} }
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
@ -112,9 +183,6 @@ IN
protected override void Down(MigrationBuilder migrationBuilder) protected override void Down(MigrationBuilder migrationBuilder)
{ {
migrationBuilder.DropIndex(
name: "IX_EFAlias_Name_IPAddress",
table: "EFAlias");
} }
} }
} }

View File

@ -540,6 +540,7 @@ namespace SharedLibraryCore.Database.Models
else else
{ {
CurrentServer.Logger.WriteDebug($"Creating join event for {this}");
var e = new GameEvent() var e = new GameEvent()
{ {
Type = GameEvent.EventType.Join, Type = GameEvent.EventType.Join,

View File

@ -103,8 +103,6 @@ namespace SharedLibraryCore.Services
private async Task UpdateAlias(string name, int? ip, EFClient entity, DatabaseContext context) private async Task UpdateAlias(string name, int? ip, EFClient entity, DatabaseContext context)
{ {
entity.CurrentServer.Manager.GetLogger(0).WriteDebug($"Begin update alias for {entity}");
// entity is the tracked db context item // entity is the tracked db context item
// get all aliases by IP address and LinkId // get all aliases by IP address and LinkId
var iqAliases = context.Aliases var iqAliases = context.Aliases
@ -216,8 +214,6 @@ namespace SharedLibraryCore.Services
entity.CurrentAliasId = 0; entity.CurrentAliasId = 0;
await context.SaveChangesAsync(); await context.SaveChangesAsync();
} }
entity.CurrentServer.Manager.GetLogger(0).WriteDebug($"End update alias for {entity}");
} }
/// <summary> /// <summary>
@ -308,7 +304,8 @@ namespace SharedLibraryCore.Services
{ {
Name = _client.CurrentAlias.Name, Name = _client.CurrentAlias.Name,
IPAddress = _client.CurrentAlias.IPAddress IPAddress = _client.CurrentAlias.IPAddress
} },
TotalConnectionTime = _client.TotalConnectionTime
}) })
.FirstOrDefault(_client => _client.ClientId == entityId); .FirstOrDefault(_client => _client.ClientId == entityId);
@ -362,8 +359,6 @@ namespace SharedLibraryCore.Services
EF.CompileAsyncQuery((DatabaseContext context, long networkId) => EF.CompileAsyncQuery((DatabaseContext context, long networkId) =>
context.Clients context.Clients
.Include(c => c.CurrentAlias) .Include(c => c.CurrentAlias)
//.Include(c => c.AliasLink.Children)
//.Include(c => c.ReceivedPenalties)
.Select(_client => new EFClient() .Select(_client => new EFClient()
{ {
ClientId = _client.ClientId, ClientId = _client.ClientId,
@ -373,7 +368,8 @@ namespace SharedLibraryCore.Services
FirstConnection = _client.FirstConnection, FirstConnection = _client.FirstConnection,
LastConnection = _client.LastConnection, LastConnection = _client.LastConnection,
Masked = _client.Masked, Masked = _client.Masked,
NetworkId = _client.NetworkId NetworkId = _client.NetworkId,
TotalConnectionTime = _client.TotalConnectionTime
}) })
.FirstOrDefault(c => c.NetworkId == networkId) .FirstOrDefault(c => c.NetworkId == networkId)
); );

View File

@ -34,26 +34,32 @@
<Compile Remove="Migrations\20190223012312_SetMaxLengthForMetaKey.cs" /> <Compile Remove="Migrations\20190223012312_SetMaxLengthForMetaKey.cs" />
<Compile Remove="Migrations\20190907222702_AddMomentViewAnglesToAcSnapshot.cs" /> <Compile Remove="Migrations\20190907222702_AddMomentViewAnglesToAcSnapshot.cs" />
<Compile Remove="Migrations\20190907222702_AddMomentViewAnglesToAcSnapshot.Designer.cs" /> <Compile Remove="Migrations\20190907222702_AddMomentViewAnglesToAcSnapshot.Designer.cs" />
<Compile Remove="Migrations\20191120170503_AddDateAuditColumnsToSharedEntity.cs" />
<Compile Remove="Migrations\20191120170503_AddDateAuditColumnsToSharedEntity.Designer.cs" />
<Compile Remove="Migrations\20191120204934_AddDateAuditColumnsToSharedEntity.cs" />
<Compile Remove="Migrations\20191120204934_AddDateAuditColumnsToSharedEntity.Designer.cs" />
<Compile Remove="Migrations\20191120205633_AddDateAuditColumnsToSharedEntity.cs" />
<Compile Remove="Migrations\20191120205633_AddDateAuditColumnsToSharedEntity.Designer.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Jint" Version="2.11.58" /> <PackageReference Include="Jint" Version="2.11.58" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.0.0"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.0.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="3.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.1" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="3.0.0" /> <PackageReference Include="Microsoft.Extensions.Localization" Version="3.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.0.1" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.0.0" /> <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Npgsql" Version="4.1.1" /> <PackageReference Include="Npgsql" Version="4.1.2" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.0.1" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.0.1" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.0.0-rc3.final" /> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.0.0" />
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" /> <PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
</ItemGroup> </ItemGroup>

View File

@ -45,7 +45,7 @@ namespace WebfrontCore.Middleware
} }
} }
public async Task<string> Invoke(string original) public Task<string> Invoke(string original)
{ {
foreach (var color in ColorReplacements) foreach (var color in ColorReplacements)
{ {
@ -57,7 +57,7 @@ namespace WebfrontCore.Middleware
} }
} }
return original; return Task.FromResult(original);
} }
/// <summary> /// <summary>