diff --git a/src/MediatR.CommandQuery.Audit/MediatR.CommandQuery.Audit.csproj b/src/MediatR.CommandQuery.Audit/MediatR.CommandQuery.Audit.csproj index 6bb45995..3d523285 100644 --- a/src/MediatR.CommandQuery.Audit/MediatR.CommandQuery.Audit.csproj +++ b/src/MediatR.CommandQuery.Audit/MediatR.CommandQuery.Audit.csproj @@ -1,7 +1,7 @@ - net6.0;net7.0;net8.0 + net6.0;net8.0 1591 enable diff --git a/src/MediatR.CommandQuery.Cosmos/MediatR.CommandQuery.Cosmos.csproj b/src/MediatR.CommandQuery.Cosmos/MediatR.CommandQuery.Cosmos.csproj index 2ef4ebd5..c38c88e4 100644 --- a/src/MediatR.CommandQuery.Cosmos/MediatR.CommandQuery.Cosmos.csproj +++ b/src/MediatR.CommandQuery.Cosmos/MediatR.CommandQuery.Cosmos.csproj @@ -1,7 +1,7 @@ - net6.0;net7.0;net8.0 + net6.0;net8.0 1591 enable diff --git a/src/MediatR.CommandQuery.Hangfire/MediatR.CommandQuery.Hangfire.csproj b/src/MediatR.CommandQuery.Hangfire/MediatR.CommandQuery.Hangfire.csproj index 95116ccf..f3cebe4e 100644 --- a/src/MediatR.CommandQuery.Hangfire/MediatR.CommandQuery.Hangfire.csproj +++ b/src/MediatR.CommandQuery.Hangfire/MediatR.CommandQuery.Hangfire.csproj @@ -1,7 +1,7 @@ - net6.0;net7.0;net8.0 + net6.0;net8.0 1591 enable diff --git a/src/MediatR.CommandQuery.MongoDB/MediatR.CommandQuery.MongoDB.csproj b/src/MediatR.CommandQuery.MongoDB/MediatR.CommandQuery.MongoDB.csproj index 3d91388f..85cae408 100644 --- a/src/MediatR.CommandQuery.MongoDB/MediatR.CommandQuery.MongoDB.csproj +++ b/src/MediatR.CommandQuery.MongoDB/MediatR.CommandQuery.MongoDB.csproj @@ -1,7 +1,7 @@ - net6.0;net7.0;net8.0 + net6.0;net8.0 1591 enable diff --git a/src/MediatR.CommandQuery.Mvc/MediatR.CommandQuery.Mvc.csproj b/src/MediatR.CommandQuery.Mvc/MediatR.CommandQuery.Mvc.csproj index f4366c91..9f3db13d 100644 --- a/src/MediatR.CommandQuery.Mvc/MediatR.CommandQuery.Mvc.csproj +++ b/src/MediatR.CommandQuery.Mvc/MediatR.CommandQuery.Mvc.csproj @@ -1,7 +1,7 @@ - net6.0;net7.0;net8.0 + net6.0;net8.0 Library 1591 enable diff --git a/src/MediatR.CommandQuery/Behaviors/DeletedFilterBehaviorBase.cs b/src/MediatR.CommandQuery/Behaviors/DeletedFilterBehaviorBase.cs index 9d83e5d4..6ea834da 100644 --- a/src/MediatR.CommandQuery/Behaviors/DeletedFilterBehaviorBase.cs +++ b/src/MediatR.CommandQuery/Behaviors/DeletedFilterBehaviorBase.cs @@ -1,4 +1,4 @@ -using System.Security.Principal; +using System.Security.Claims; using MediatR.CommandQuery.Definitions; using MediatR.CommandQuery.Queries; @@ -18,7 +18,7 @@ protected DeletedFilterBehaviorBase(ILoggerFactory loggerFactory) : base(loggerF { } - protected virtual EntityFilter? RewriteFilter(EntityFilter? originalFilter, IPrincipal? principal) + protected virtual EntityFilter? RewriteFilter(EntityFilter? originalFilter, ClaimsPrincipal? principal) { if (!_supportsDelete.Value) return originalFilter; diff --git a/src/MediatR.CommandQuery/Commands/EntityCreateCommand.cs b/src/MediatR.CommandQuery/Commands/EntityCreateCommand.cs index ede36455..d0f6c1d8 100644 --- a/src/MediatR.CommandQuery/Commands/EntityCreateCommand.cs +++ b/src/MediatR.CommandQuery/Commands/EntityCreateCommand.cs @@ -1,12 +1,12 @@ using System.Diagnostics.CodeAnalysis; -using System.Security.Principal; +using System.Security.Claims; namespace MediatR.CommandQuery.Commands; -public class EntityCreateCommand +public record EntityCreateCommand : EntityModelCommand { - public EntityCreateCommand(IPrincipal? principal, [NotNull] TCreateModel model) : base(principal, model) + public EntityCreateCommand(ClaimsPrincipal? principal, [NotNull] TCreateModel model) : base(principal, model) { } diff --git a/src/MediatR.CommandQuery/Commands/EntityDeleteCommand.cs b/src/MediatR.CommandQuery/Commands/EntityDeleteCommand.cs index 6af0f03f..37a23070 100644 --- a/src/MediatR.CommandQuery/Commands/EntityDeleteCommand.cs +++ b/src/MediatR.CommandQuery/Commands/EntityDeleteCommand.cs @@ -1,12 +1,12 @@ using System.Diagnostics.CodeAnalysis; -using System.Security.Principal; +using System.Security.Claims; namespace MediatR.CommandQuery.Commands; -public class EntityDeleteCommand +public record EntityDeleteCommand : EntityIdentifierCommand { - public EntityDeleteCommand(IPrincipal? principal, [NotNull] TKey id) : base(principal, id) + public EntityDeleteCommand(ClaimsPrincipal? principal, [NotNull] TKey id) : base(principal, id) { } diff --git a/src/MediatR.CommandQuery/Commands/EntityIdentifierCommand.cs b/src/MediatR.CommandQuery/Commands/EntityIdentifierCommand.cs index 21476f99..79639cd6 100644 --- a/src/MediatR.CommandQuery/Commands/EntityIdentifierCommand.cs +++ b/src/MediatR.CommandQuery/Commands/EntityIdentifierCommand.cs @@ -1,12 +1,12 @@ using System.Diagnostics.CodeAnalysis; -using System.Security.Principal; +using System.Security.Claims; namespace MediatR.CommandQuery.Commands; -public abstract class EntityIdentifierCommand +public abstract record EntityIdentifierCommand : PrincipalCommandBase { - protected EntityIdentifierCommand(IPrincipal? principal, [NotNull] TKey id) + protected EntityIdentifierCommand(ClaimsPrincipal? principal, [NotNull] TKey id) : base(principal) { if (id == null) diff --git a/src/MediatR.CommandQuery/Commands/EntityIdentifiersCommand.cs b/src/MediatR.CommandQuery/Commands/EntityIdentifiersCommand.cs index a0bffef6..bc28419e 100644 --- a/src/MediatR.CommandQuery/Commands/EntityIdentifiersCommand.cs +++ b/src/MediatR.CommandQuery/Commands/EntityIdentifiersCommand.cs @@ -1,12 +1,12 @@ using System.Diagnostics.CodeAnalysis; -using System.Security.Principal; +using System.Security.Claims; namespace MediatR.CommandQuery.Commands; -public abstract class EntityIdentifiersCommand +public abstract record EntityIdentifiersCommand : PrincipalCommandBase { - protected EntityIdentifiersCommand(IPrincipal? principal, [NotNull] IEnumerable ids) + protected EntityIdentifiersCommand(ClaimsPrincipal? principal, [NotNull] IEnumerable ids) : base(principal) { if (ids is null) diff --git a/src/MediatR.CommandQuery/Commands/EntityModelCommand.cs b/src/MediatR.CommandQuery/Commands/EntityModelCommand.cs index 09de54cf..7729c0e8 100644 --- a/src/MediatR.CommandQuery/Commands/EntityModelCommand.cs +++ b/src/MediatR.CommandQuery/Commands/EntityModelCommand.cs @@ -1,12 +1,12 @@ using System.Diagnostics.CodeAnalysis; -using System.Security.Principal; +using System.Security.Claims; namespace MediatR.CommandQuery.Commands; -public abstract class EntityModelCommand +public abstract record EntityModelCommand : PrincipalCommandBase { - protected EntityModelCommand(IPrincipal? principal, [NotNull] TEntityModel model) + protected EntityModelCommand(ClaimsPrincipal? principal, [NotNull] TEntityModel model) : base(principal) { if (model == null) diff --git a/src/MediatR.CommandQuery/Commands/EntityPatchCommand.cs b/src/MediatR.CommandQuery/Commands/EntityPatchCommand.cs index fed8d181..9e7c6dc4 100644 --- a/src/MediatR.CommandQuery/Commands/EntityPatchCommand.cs +++ b/src/MediatR.CommandQuery/Commands/EntityPatchCommand.cs @@ -1,24 +1,17 @@ using System.Diagnostics.CodeAnalysis; -using System.Security.Principal; +using System.Security.Claims; using SystemTextJsonPatch; namespace MediatR.CommandQuery.Commands; -public class EntityPatchCommand +public record EntityPatchCommand : EntityIdentifierCommand { - public EntityPatchCommand(IPrincipal? principal, [NotNull] TKey id, [NotNull] JsonPatchDocument patch) : base(principal, id) + public EntityPatchCommand(ClaimsPrincipal? principal, [NotNull] TKey id, [NotNull] JsonPatchDocument patch) : base(principal, id) { Patch = patch ?? throw new ArgumentNullException(nameof(patch)); } public JsonPatchDocument Patch { get; } - - - public override string ToString() - { - return $"Entity Patch Command; Model: {typeof(TReadModel).Name}; {base.ToString()}"; - } - } diff --git a/src/MediatR.CommandQuery/Commands/EntityUpdateCommand.cs b/src/MediatR.CommandQuery/Commands/EntityUpdateCommand.cs index 37fa8f7a..7be816d9 100644 --- a/src/MediatR.CommandQuery/Commands/EntityUpdateCommand.cs +++ b/src/MediatR.CommandQuery/Commands/EntityUpdateCommand.cs @@ -1,12 +1,12 @@ using System.Diagnostics.CodeAnalysis; -using System.Security.Principal; +using System.Security.Claims; namespace MediatR.CommandQuery.Commands; -public class EntityUpdateCommand +public record EntityUpdateCommand : EntityModelCommand { - public EntityUpdateCommand(IPrincipal? principal, [NotNull] TKey id, TUpdateModel model) : base(principal, model) + public EntityUpdateCommand(ClaimsPrincipal? principal, [NotNull] TKey id, TUpdateModel model) : base(principal, model) { if (id == null) throw new ArgumentNullException(nameof(id)); diff --git a/src/MediatR.CommandQuery/Commands/EntityUpsertCommand.cs b/src/MediatR.CommandQuery/Commands/EntityUpsertCommand.cs index 57887e23..2df99f13 100644 --- a/src/MediatR.CommandQuery/Commands/EntityUpsertCommand.cs +++ b/src/MediatR.CommandQuery/Commands/EntityUpsertCommand.cs @@ -1,12 +1,12 @@ using System.Diagnostics.CodeAnalysis; -using System.Security.Principal; +using System.Security.Claims; namespace MediatR.CommandQuery.Commands; -public class EntityUpsertCommand +public record EntityUpsertCommand : EntityModelCommand { - public EntityUpsertCommand(IPrincipal? principal, [NotNull] TKey id, TUpdateModel model) : base(principal, model) + public EntityUpsertCommand(ClaimsPrincipal? principal, [NotNull] TKey id, TUpdateModel model) : base(principal, model) { if (id == null) throw new ArgumentNullException(nameof(id)); @@ -16,10 +16,4 @@ public EntityUpsertCommand(IPrincipal? principal, [NotNull] TKey id, TUpdateMode [NotNull] public TKey Id { get; } - - public override string ToString() - { - return $"Entity Upsert Command; Model: {typeof(TUpdateModel).Name}; Id: {Id}; {base.ToString()}"; - } - } diff --git a/src/MediatR.CommandQuery/Commands/PrincipalCommandBase.cs b/src/MediatR.CommandQuery/Commands/PrincipalCommandBase.cs index 090c3b87..78096689 100644 --- a/src/MediatR.CommandQuery/Commands/PrincipalCommandBase.cs +++ b/src/MediatR.CommandQuery/Commands/PrincipalCommandBase.cs @@ -1,36 +1,24 @@ -using System.Runtime.Serialization; -using System.Security.Principal; +using System.Security.Claims; using System.Text.Json.Serialization; +using MediatR.CommandQuery.Converters; + namespace MediatR.CommandQuery.Commands; -public abstract class PrincipalCommandBase : IRequest +public abstract record PrincipalCommandBase : IRequest { - protected PrincipalCommandBase(IPrincipal? principal) - : this(DateTimeOffset.UtcNow, principal?.Identity?.Name) + protected PrincipalCommandBase(ClaimsPrincipal? principal) { Principal = principal; - } - protected PrincipalCommandBase(DateTimeOffset activated, string? activatedBy) - { - Activated = activated; - ActivatedBy = activatedBy; + Activated = DateTimeOffset.UtcNow; + ActivatedBy = principal?.Identity?.Name ?? "system"; } - [JsonIgnore] - [IgnoreDataMember] - public IPrincipal? Principal { get; } + [JsonConverter(typeof(ClaimsPrincipalConverter))] + public ClaimsPrincipal? Principal { get; } public DateTimeOffset Activated { get; } public string? ActivatedBy { get; } - - public override string ToString() - { - return $"Activated: {Activated}; ActivatedBy: {ActivatedBy}"; - } - - // ignore Principal property without attribute for JSON.NET - public bool ShouldSerializePrincipal() => false; } diff --git a/src/MediatR.CommandQuery/Converters/ClaimsPrincipalConverter.cs b/src/MediatR.CommandQuery/Converters/ClaimsPrincipalConverter.cs new file mode 100644 index 00000000..0259afaf --- /dev/null +++ b/src/MediatR.CommandQuery/Converters/ClaimsPrincipalConverter.cs @@ -0,0 +1,61 @@ +using System.Security.Claims; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MediatR.CommandQuery.Converters; + +public class ClaimsPrincipalConverter : JsonConverter +{ + public override ClaimsPrincipal? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + var claimsPrincipalProxy = JsonSerializer.Deserialize( + ref reader, + options + ); + + if (claimsPrincipalProxy is null) + { + return null; + } + + return new( + new ClaimsIdentity( + claimsPrincipalProxy.Claims.Select(c => new Claim(c.Type, c.Value)), + claimsPrincipalProxy.AuthenticationType, + claimsPrincipalProxy.NameType, + claimsPrincipalProxy.RoleType + ) + ); + } + + public override void Write( + Utf8JsonWriter writer, + ClaimsPrincipal value, + JsonSerializerOptions options + ) + { + var identity = value.Identity as ClaimsIdentity; + + var claimsPrincipalProxy = new ClaimsPrincipalProxy( + value.Claims.Select(c => new ClaimProxy(c.Type, c.Value)).ToList(), + identity?.AuthenticationType, + identity?.NameClaimType, + identity?.RoleClaimType + ); + + JsonSerializer.Serialize(writer, claimsPrincipalProxy, options); + } + + private sealed record ClaimsPrincipalProxy( + List Claims, + string? AuthenticationType, + string? NameType, + string? RoleType + ); + + private sealed record ClaimProxy(string Type, string Value); +} diff --git a/src/MediatR.CommandQuery/Queries/EntityFilterConverter.cs b/src/MediatR.CommandQuery/Converters/EntityFilterConverter.cs similarity index 98% rename from src/MediatR.CommandQuery/Queries/EntityFilterConverter.cs rename to src/MediatR.CommandQuery/Converters/EntityFilterConverter.cs index 8969ffb3..83b20e8b 100644 --- a/src/MediatR.CommandQuery/Queries/EntityFilterConverter.cs +++ b/src/MediatR.CommandQuery/Converters/EntityFilterConverter.cs @@ -1,7 +1,9 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace MediatR.CommandQuery.Queries; +using MediatR.CommandQuery.Queries; + +namespace MediatR.CommandQuery.Converters; public sealed class EntityFilterConverter : JsonConverter { diff --git a/src/MediatR.CommandQuery/Converters/PolymorphicConverter.cs b/src/MediatR.CommandQuery/Converters/PolymorphicConverter.cs new file mode 100644 index 00000000..440ecab9 --- /dev/null +++ b/src/MediatR.CommandQuery/Converters/PolymorphicConverter.cs @@ -0,0 +1,75 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MediatR.CommandQuery.Converters; + +public class PolymorphicConverter : JsonConverter + where T : class +{ + //Inspired by https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/converters-how-to?pivots=dotnet-6-0#support-polymorphic-deserialization + + private static readonly JsonEncodedText TypeDiscriminator = JsonEncodedText.Encode("$type"); + private static readonly JsonEncodedText TypeInstance = JsonEncodedText.Encode("$instance"); + + public override bool CanConvert(Type typeToConvert) + { + return typeof(T) == typeToConvert; + } + + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + throw new JsonException(); + + reader.Read(); + if (reader.TokenType != JsonTokenType.PropertyName) + throw new JsonException(); + + + if (!reader.ValueTextEquals(TypeDiscriminator.EncodedUtf8Bytes)) + throw new JsonException(); + + reader.Read(); + if (reader.TokenType != JsonTokenType.String) + throw new JsonException(); + + var typeDiscriminator = reader.GetString(); + if (typeDiscriminator == null) + throw new JsonException(); + + var type = Type.GetType(typeDiscriminator); + if (type == null) + throw new JsonException(); + + reader.Read(); + if (reader.TokenType != JsonTokenType.PropertyName) + throw new JsonException(); + + if (!reader.ValueTextEquals(TypeInstance.EncodedUtf8Bytes)) + throw new JsonException(); + + var instance = JsonSerializer.Deserialize(ref reader, type, options); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + return (instance as T)!; + } + + return (instance as T)!; + } + + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + var type = value.GetType(); + + writer.WriteString(TypeDiscriminator, type.AssemblyQualifiedName); + writer.WritePropertyName(TypeInstance); + + JsonSerializer.Serialize(writer, value, type); + + writer.WriteEndObject(); + } +} diff --git a/src/MediatR.CommandQuery/MediatR.CommandQuery.csproj b/src/MediatR.CommandQuery/MediatR.CommandQuery.csproj index 5565e763..873dc7b3 100644 --- a/src/MediatR.CommandQuery/MediatR.CommandQuery.csproj +++ b/src/MediatR.CommandQuery/MediatR.CommandQuery.csproj @@ -1,7 +1,7 @@ - net6.0;net7.0;net8.0 + net6.0;net8.0 1591 enable diff --git a/src/MediatR.CommandQuery/Queries/CacheableQueryBase.cs b/src/MediatR.CommandQuery/Queries/CacheableQueryBase.cs index b0774fad..dbae019a 100644 --- a/src/MediatR.CommandQuery/Queries/CacheableQueryBase.cs +++ b/src/MediatR.CommandQuery/Queries/CacheableQueryBase.cs @@ -1,17 +1,17 @@ // Ignore Spelling: Cacheable -using System.Security.Principal; +using System.Security.Claims; using MediatR.CommandQuery.Definitions; namespace MediatR.CommandQuery.Queries; -public abstract class CacheableQueryBase : PrincipalQueryBase, ICacheQueryResult +public abstract record CacheableQueryBase : PrincipalQueryBase, ICacheQueryResult { private DateTimeOffset? _absoluteExpiration; private TimeSpan? _slidingExpiration; - protected CacheableQueryBase(IPrincipal? principal) : base(principal) + protected CacheableQueryBase(ClaimsPrincipal? principal) : base(principal) { } diff --git a/src/MediatR.CommandQuery/Queries/EntityFilter.cs b/src/MediatR.CommandQuery/Queries/EntityFilter.cs index 0879416a..04a5f328 100644 --- a/src/MediatR.CommandQuery/Queries/EntityFilter.cs +++ b/src/MediatR.CommandQuery/Queries/EntityFilter.cs @@ -1,5 +1,7 @@ using System.Text.Json.Serialization; +using MediatR.CommandQuery.Converters; + namespace MediatR.CommandQuery.Queries; [JsonConverter(typeof(EntityFilterConverter))] diff --git a/src/MediatR.CommandQuery/Queries/EntityIdentifierQuery.cs b/src/MediatR.CommandQuery/Queries/EntityIdentifierQuery.cs index a8c3a2ca..67ebe0ce 100644 --- a/src/MediatR.CommandQuery/Queries/EntityIdentifierQuery.cs +++ b/src/MediatR.CommandQuery/Queries/EntityIdentifierQuery.cs @@ -1,12 +1,12 @@ using System.Diagnostics.CodeAnalysis; -using System.Security.Principal; +using System.Security.Claims; namespace MediatR.CommandQuery.Queries; -public class EntityIdentifierQuery : CacheableQueryBase +public record EntityIdentifierQuery : CacheableQueryBase { - public EntityIdentifierQuery(IPrincipal? principal, [NotNull] TKey id) + public EntityIdentifierQuery(ClaimsPrincipal? principal, [NotNull] TKey id) : base(principal) { if (id == null) diff --git a/src/MediatR.CommandQuery/Queries/EntityIdentifiersQuery.cs b/src/MediatR.CommandQuery/Queries/EntityIdentifiersQuery.cs index 15f8fd85..c7b95ffe 100644 --- a/src/MediatR.CommandQuery/Queries/EntityIdentifiersQuery.cs +++ b/src/MediatR.CommandQuery/Queries/EntityIdentifiersQuery.cs @@ -1,11 +1,11 @@ using System.Diagnostics.CodeAnalysis; -using System.Security.Principal; +using System.Security.Claims; namespace MediatR.CommandQuery.Queries; -public class EntityIdentifiersQuery : CacheableQueryBase> +public record EntityIdentifiersQuery : CacheableQueryBase> { - public EntityIdentifiersQuery(IPrincipal? principal, [NotNull] IEnumerable ids) + public EntityIdentifiersQuery(ClaimsPrincipal? principal, [NotNull] IEnumerable ids) : base(principal) { if (ids is null) diff --git a/src/MediatR.CommandQuery/Queries/EntityPagedQuery.cs b/src/MediatR.CommandQuery/Queries/EntityPagedQuery.cs index 8445a178..817b13f1 100644 --- a/src/MediatR.CommandQuery/Queries/EntityPagedQuery.cs +++ b/src/MediatR.CommandQuery/Queries/EntityPagedQuery.cs @@ -1,10 +1,10 @@ -using System.Security.Principal; +using System.Security.Claims; namespace MediatR.CommandQuery.Queries; -public class EntityPagedQuery : CacheableQueryBase> +public record EntityPagedQuery : CacheableQueryBase> { - public EntityPagedQuery(IPrincipal? principal, EntityQuery? query) + public EntityPagedQuery(ClaimsPrincipal? principal, EntityQuery? query) : base(principal) { Query = query ?? new EntityQuery(); diff --git a/src/MediatR.CommandQuery/Queries/EntitySelectQuery.cs b/src/MediatR.CommandQuery/Queries/EntitySelectQuery.cs index c6be1e62..1d94ca6a 100644 --- a/src/MediatR.CommandQuery/Queries/EntitySelectQuery.cs +++ b/src/MediatR.CommandQuery/Queries/EntitySelectQuery.cs @@ -1,31 +1,31 @@ -using System.Security.Principal; +using System.Security.Claims; namespace MediatR.CommandQuery.Queries; -public class EntitySelectQuery : CacheableQueryBase> +public record EntitySelectQuery : CacheableQueryBase> { - public EntitySelectQuery(IPrincipal? principal) + public EntitySelectQuery(ClaimsPrincipal? principal) : this(principal, new EntitySelect()) { } - public EntitySelectQuery(IPrincipal? principal, EntityFilter filter) + public EntitySelectQuery(ClaimsPrincipal? principal, EntityFilter filter) : this(principal, new EntitySelect(filter)) { } - public EntitySelectQuery(IPrincipal? principal, EntityFilter filter, EntitySort sort) + public EntitySelectQuery(ClaimsPrincipal? principal, EntityFilter filter, EntitySort sort) : this(principal, filter, new[] { sort }) { } - public EntitySelectQuery(IPrincipal? principal, EntityFilter filter, IEnumerable sort) + public EntitySelectQuery(ClaimsPrincipal? principal, EntityFilter filter, IEnumerable sort) : this(principal, new EntitySelect(filter, sort)) { } - public EntitySelectQuery(IPrincipal? principal, EntitySelect select) + public EntitySelectQuery(ClaimsPrincipal? principal, EntitySelect select) : base(principal) { Select = select ?? new EntitySelect(); diff --git a/src/MediatR.CommandQuery/Queries/PrincipalQueryBase.cs b/src/MediatR.CommandQuery/Queries/PrincipalQueryBase.cs index 54ada281..97e74a02 100644 --- a/src/MediatR.CommandQuery/Queries/PrincipalQueryBase.cs +++ b/src/MediatR.CommandQuery/Queries/PrincipalQueryBase.cs @@ -1,37 +1,12 @@ -using System.Runtime.Serialization; -using System.Security.Principal; -using System.Text.Json.Serialization; +using System.Security.Claims; + +using MediatR.CommandQuery.Commands; namespace MediatR.CommandQuery.Queries; -public abstract class PrincipalQueryBase : IRequest +public abstract record PrincipalQueryBase : PrincipalCommandBase { - protected PrincipalQueryBase(IPrincipal? principal) - : this(DateTimeOffset.UtcNow, principal?.Identity?.Name) - { - Principal = principal; - } - - protected PrincipalQueryBase(DateTimeOffset activated, string? activatedBy) - { - Activated = activated; - ActivatedBy = activatedBy; - } - - [JsonIgnore] - [IgnoreDataMember] - public IPrincipal? Principal { get; } - - public DateTimeOffset Activated { get; } - - public string? ActivatedBy { get; } - - - public override string ToString() + protected PrincipalQueryBase(ClaimsPrincipal? principal) : base(principal) { - return $"Activated: {Activated}; ActivatedBy: {ActivatedBy}"; } - - // ignore Principal property without attribute for JSON.NET - public bool ShouldSerializePrincipal() => false; } diff --git a/test/MediatR.CommandQuery.Cosmos.Tests/MediatR.CommandQuery.Cosmos.Tests.csproj b/test/MediatR.CommandQuery.Cosmos.Tests/MediatR.CommandQuery.Cosmos.Tests.csproj index 4aa143be..8120eb5c 100644 --- a/test/MediatR.CommandQuery.Cosmos.Tests/MediatR.CommandQuery.Cosmos.Tests.csproj +++ b/test/MediatR.CommandQuery.Cosmos.Tests/MediatR.CommandQuery.Cosmos.Tests.csproj @@ -1,7 +1,7 @@ - net6.0;net7.0;net8.0 + net6.0;net8.0 false diff --git a/test/MediatR.CommandQuery.Cosmos.Tests/MockPrincipal.cs b/test/MediatR.CommandQuery.Cosmos.Tests/MockPrincipal.cs index 24db83de..85a3bdd9 100644 --- a/test/MediatR.CommandQuery.Cosmos.Tests/MockPrincipal.cs +++ b/test/MediatR.CommandQuery.Cosmos.Tests/MockPrincipal.cs @@ -13,7 +13,7 @@ static MockPrincipal() } - public static IPrincipal Default { get; } + public static ClaimsPrincipal Default { get; } diff --git a/test/MediatR.CommandQuery.EntityFrameworkCore.SqlServer.Tests/MockPrincipal.cs b/test/MediatR.CommandQuery.EntityFrameworkCore.SqlServer.Tests/MockPrincipal.cs index 619ce1b8..87049526 100644 --- a/test/MediatR.CommandQuery.EntityFrameworkCore.SqlServer.Tests/MockPrincipal.cs +++ b/test/MediatR.CommandQuery.EntityFrameworkCore.SqlServer.Tests/MockPrincipal.cs @@ -13,7 +13,7 @@ static MockPrincipal() } - public static IPrincipal Default { get; } + public static ClaimsPrincipal Default { get; } diff --git a/test/MediatR.CommandQuery.EntityFrameworkCore.SqlServer.Tests/SerializationTests.cs b/test/MediatR.CommandQuery.EntityFrameworkCore.SqlServer.Tests/SerializationTests.cs new file mode 100644 index 00000000..0616c22e --- /dev/null +++ b/test/MediatR.CommandQuery.EntityFrameworkCore.SqlServer.Tests/SerializationTests.cs @@ -0,0 +1,89 @@ +using System.Text.Json; + +using MediatR.CommandQuery.Commands; +using MediatR.CommandQuery.Converters; +using MediatR.CommandQuery.EntityFrameworkCore.SqlServer.Tests.Domain.Audit.Models; + +namespace MediatR.CommandQuery.EntityFrameworkCore.SqlServer.Tests; + +public class SerializationTests +{ + [Fact] + public void EntityCreateCommandSerialize() + { + // Create Entity + var generator = new Faker() + .RuleFor(p => p.Id, (faker, model) => Guid.NewGuid()) + .RuleFor(p => p.Created, (faker, model) => faker.Date.PastOffset()) + .RuleFor(p => p.CreatedBy, (faker, model) => faker.Internet.Email()) + .RuleFor(p => p.Updated, (faker, model) => faker.Date.SoonOffset()) + .RuleFor(p => p.UpdatedBy, (faker, model) => faker.Internet.Email()) + .RuleFor(p => p.Date, (faker, model) => faker.Date.Soon()); + + var createModel = generator.Generate(); + createModel.Username = "TEST"; + createModel.Content = "Test " + DateTime.Now.Ticks; + + var createCommand = new EntityCreateCommand(MockPrincipal.Default, createModel); + + var options = SerializerOptions(); + + var json = JsonSerializer.Serialize(createCommand, options); + json.Should().NotBeNullOrEmpty(); + + + var deserializeCommand = JsonSerializer.Deserialize>(json); + deserializeCommand.Should().NotBeNull(); + } + + + [Fact] + public void PolymorphicConverterTest() + { + // Create Entity + var generator = new Faker() + .RuleFor(p => p.Id, (faker, model) => Guid.NewGuid()) + .RuleFor(p => p.Created, (faker, model) => faker.Date.PastOffset()) + .RuleFor(p => p.CreatedBy, (faker, model) => faker.Internet.Email()) + .RuleFor(p => p.Updated, (faker, model) => faker.Date.SoonOffset()) + .RuleFor(p => p.UpdatedBy, (faker, model) => faker.Internet.Email()) + .RuleFor(p => p.Date, (faker, model) => faker.Date.Soon()); + + var createModel = generator.Generate(); + createModel.Username = "TEST"; + createModel.Content = "Test " + DateTime.Now.Ticks; + + var createCommand = new EntityCreateCommand(MockPrincipal.Default, createModel); + + var options = SerializerOptions(); + + var json = JsonSerializer.Serialize(createCommand, options); + json.Should().NotBeNullOrEmpty(); + + var deserializeCommand = JsonSerializer.Deserialize(json, typeof(IBaseRequest), options); + deserializeCommand.Should().NotBeNull(); + deserializeCommand.Should().BeAssignableTo(); + } + + [Fact] + public void PolymorphicConverterNullTest() + { + EntityCreateCommand createCommand = null; + + var options = SerializerOptions(); + + var json = JsonSerializer.Serialize(createCommand, options); + json.Should().NotBeNullOrEmpty(); + + var deserializeCommand = JsonSerializer.Deserialize(json, typeof(IBaseRequest), options); + deserializeCommand.Should().BeNull(); + } + + private static JsonSerializerOptions SerializerOptions() + { + var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) { WriteIndented = true }; + options.Converters.Add(new PolymorphicConverter()); + + return options; + } +} diff --git a/test/MediatR.CommandQuery.EntityFrameworkCore.Tests/Samples/MockPrincipal.cs b/test/MediatR.CommandQuery.EntityFrameworkCore.Tests/Samples/MockPrincipal.cs index 23d30448..42e83853 100644 --- a/test/MediatR.CommandQuery.EntityFrameworkCore.Tests/Samples/MockPrincipal.cs +++ b/test/MediatR.CommandQuery.EntityFrameworkCore.Tests/Samples/MockPrincipal.cs @@ -10,7 +10,7 @@ static MockPrincipal() Default = CreatePrincipal("test@mailinator.com", "Test User"); } - public static IPrincipal CreatePrincipal(string email, string name) + public static ClaimsPrincipal CreatePrincipal(string email, string name) { var claimsIdentity = new ClaimsIdentity("JWT", "sub", "role"); claimsIdentity.AddClaim(new Claim("sub", email)); @@ -22,6 +22,6 @@ public static IPrincipal CreatePrincipal(string email, string name) } - public static IPrincipal Default { get; } + public static ClaimsPrincipal Default { get; } } diff --git a/test/MediatR.CommandQuery.MongoDB.Tests/MediatR.CommandQuery.MongoDB.Tests.csproj b/test/MediatR.CommandQuery.MongoDB.Tests/MediatR.CommandQuery.MongoDB.Tests.csproj index 4791bc98..6128ba02 100644 --- a/test/MediatR.CommandQuery.MongoDB.Tests/MediatR.CommandQuery.MongoDB.Tests.csproj +++ b/test/MediatR.CommandQuery.MongoDB.Tests/MediatR.CommandQuery.MongoDB.Tests.csproj @@ -1,7 +1,7 @@ - net6.0;net7.0;net8.0 + net6.0;net8.0 false diff --git a/test/MediatR.CommandQuery.MongoDB.Tests/MockPrincipal.cs b/test/MediatR.CommandQuery.MongoDB.Tests/MockPrincipal.cs index a9f3e741..4236c11e 100644 --- a/test/MediatR.CommandQuery.MongoDB.Tests/MockPrincipal.cs +++ b/test/MediatR.CommandQuery.MongoDB.Tests/MockPrincipal.cs @@ -13,7 +13,7 @@ static MockPrincipal() } - public static IPrincipal Default { get; } + public static ClaimsPrincipal Default { get; } diff --git a/test/MediatR.CommandQuery.Tests/MediatR.CommandQuery.Tests.csproj b/test/MediatR.CommandQuery.Tests/MediatR.CommandQuery.Tests.csproj index 72f7c648..9916efd5 100644 --- a/test/MediatR.CommandQuery.Tests/MediatR.CommandQuery.Tests.csproj +++ b/test/MediatR.CommandQuery.Tests/MediatR.CommandQuery.Tests.csproj @@ -1,7 +1,7 @@ - net6.0;net7.0;net8.0 + net6.0;net8.0 false diff --git a/test/MediatR.CommandQuery.Tests/Samples/MockPrincipal.cs b/test/MediatR.CommandQuery.Tests/Samples/MockPrincipal.cs index a4291c6c..eb0439d8 100644 --- a/test/MediatR.CommandQuery.Tests/Samples/MockPrincipal.cs +++ b/test/MediatR.CommandQuery.Tests/Samples/MockPrincipal.cs @@ -1,5 +1,4 @@ using System.Security.Claims; -using System.Security.Principal; namespace MediatR.CommandQuery.Tests.Samples; @@ -10,7 +9,7 @@ static MockPrincipal() Default = CreatePrincipal("test@mailinator.com", "Test User"); } - public static IPrincipal CreatePrincipal(string email, string name) + public static ClaimsPrincipal CreatePrincipal(string email, string name) { var claimsIdentity = new ClaimsIdentity("JWT", "sub", "role"); claimsIdentity.AddClaim(new Claim("sub", email)); @@ -22,6 +21,6 @@ public static IPrincipal CreatePrincipal(string email, string name) } - public static IPrincipal Default { get; } + public static ClaimsPrincipal Default { get; } }