From 8fbc5f4f119dfdaf3766780c42c6e03e7d7a4085 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 3 Jul 2019 21:38:25 +0200 Subject: [PATCH] Deferred conversion. --- .../Contents/ContentQueryService.cs | 3 - src/Squidex.Web/ApiPermissionAttribute.cs | 1 - src/Squidex.Web/Deferred.cs | 42 +++++++++++++ src/Squidex.Web/ETagExtensions.cs | 61 +++++++++++++------ src/Squidex.Web/IGenerateEtag.cs | 18 ------ .../TypedJsonInheritanceConverter.cs} | 29 +++++---- .../Pipeline/DeferredActionFilter.cs | 26 ++++++++ src/Squidex.Web/{ => Pipeline}/ETagFilter.cs | 2 +- src/Squidex.Web/{ => Pipeline}/ETagOptions.cs | 2 +- .../Controllers/Apps/AppClientsController.cs | 7 ++- .../Apps/AppContributorsController.cs | 31 +++++----- .../Apps/AppLanguagesController.cs | 7 ++- .../Controllers/Apps/AppPatternsController.cs | 7 ++- .../Controllers/Apps/AppRolesController.cs | 16 +++-- .../Apps/AppWorkflowsController.cs | 7 ++- .../Api/Controllers/Apps/AppsController.cs | 7 ++- .../Api/Controllers/Apps/Models/AppDto.cs | 2 +- .../Controllers/Apps/Models/WorkflowsDto.cs | 1 - .../Controllers/Assets/AssetsController.cs | 37 ++++++----- .../Api/Controllers/Assets/Models/AssetDto.cs | 2 +- .../Controllers/Assets/Models/AssetsDto.cs | 10 --- .../Comments/CommentsController.cs | 8 ++- .../Contents/ContentsController.cs | 37 ++++++----- .../Controllers/Contents/Models/ContentDto.cs | 2 +- .../Contents/Models/ContentsDto.cs | 10 --- .../Languages/LanguagesController.cs | 5 +- .../Controllers/Plans/AppPlansController.cs | 7 ++- .../Rules/Models/RuleActionConverter.cs | 4 +- .../Api/Controllers/Rules/Models/RuleDto.cs | 2 +- .../Rules/Models/RuleTriggerDto.cs | 4 +- .../Api/Controllers/Rules/Models/RulesDto.cs | 5 -- .../Api/Controllers/Rules/RulesController.cs | 14 +++-- .../Schemas/Models/FieldPropertiesDto.cs | 4 +- .../Controllers/Schemas/Models/SchemaDto.cs | 2 +- .../Controllers/Schemas/Models/SchemasDto.cs | 5 -- .../Controllers/Schemas/SchemasController.cs | 14 +++-- .../Config/Domain/SerializationInitializer.cs | 1 + src/Squidex/Config/Web/WebServices.cs | 1 + 38 files changed, 271 insertions(+), 172 deletions(-) create mode 100644 src/Squidex.Web/Deferred.cs delete mode 100644 src/Squidex.Web/IGenerateEtag.cs rename src/Squidex.Web/{MyJsonInheritanceConverter.cs => Json/TypedJsonInheritanceConverter.cs} (79%) create mode 100644 src/Squidex.Web/Pipeline/DeferredActionFilter.cs rename src/Squidex.Web/{ => Pipeline}/ETagFilter.cs (98%) rename src/Squidex.Web/{ => Pipeline}/ETagOptions.cs (93%) diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs index 306b49309..e88a08f67 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs @@ -8,7 +8,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Security.Claims; using System.Threading.Tasks; using Microsoft.Extensions.Options; using Microsoft.OData; @@ -23,9 +22,7 @@ using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries.OData; using Squidex.Infrastructure.Reflection; -using Squidex.Infrastructure.Security; using Squidex.Shared; -using Squidex.Shared.Identity; #pragma warning disable RECS0147 diff --git a/src/Squidex.Web/ApiPermissionAttribute.cs b/src/Squidex.Web/ApiPermissionAttribute.cs index e93e1fed2..f655b2c6f 100644 --- a/src/Squidex.Web/ApiPermissionAttribute.cs +++ b/src/Squidex.Web/ApiPermissionAttribute.cs @@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Tasks; -using Squidex.Shared.Identity; namespace Squidex.Web { diff --git a/src/Squidex.Web/Deferred.cs b/src/Squidex.Web/Deferred.cs new file mode 100644 index 000000000..717182f49 --- /dev/null +++ b/src/Squidex.Web/Deferred.cs @@ -0,0 +1,42 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using Squidex.Infrastructure; + +namespace Squidex.Web +{ + public struct Deferred + { + private readonly Lazy> value; + + public Task Value + { + get { return value.Value; } + } + + private Deferred(Func> value) + { + this.value = new Lazy>(value); + } + + public static Deferred Response(Func factory) + { + Guard.NotNull(factory, nameof(factory)); + + return new Deferred(() => Task.FromResult(factory())); + } + + public static Deferred AsyncResponse(Func> factory) + { + Guard.NotNull(factory, nameof(factory)); + + return new Deferred(async () => await factory()); + } + } +} diff --git a/src/Squidex.Web/ETagExtensions.cs b/src/Squidex.Web/ETagExtensions.cs index 5ee961a9d..034a9b958 100644 --- a/src/Squidex.Web/ETagExtensions.cs +++ b/src/Squidex.Web/ETagExtensions.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Text; +using Squidex.Domain.Apps.Entities; using Squidex.Infrastructure; using Squidex.Infrastructure.Log; @@ -17,40 +18,54 @@ namespace Squidex.Web { private static readonly int GuidLength = Guid.Empty.ToString().Length; - public static string ToManyEtag(this IReadOnlyList items, long total = 0) where T : IGenerateETag + public static string ToEtag(this IReadOnlyList items, IEntityWithVersion app = null) where T : IEntity, IEntityWithVersion { using (Profiler.Trace("CalculateEtag")) { - var unhashed = Unhashed(items, total); + var unhashed = Unhashed(items, 0, app); return unhashed.Sha256Base64(); } } - private static string Unhashed(IReadOnlyList items, long total) where T : IGenerateETag + public static string ToEtag(this IResultList items, IEntityWithVersion app = null) where T : IEntity, IEntityWithVersion { - var sb = new StringBuilder((items.Count * (GuidLength + 4)) + 10); + using (Profiler.Trace("CalculateEtag")) + { + var unhashed = Unhashed(items, items.Total, app); + + return unhashed.Sha256Base64(); + } + } + + private static string Unhashed(IReadOnlyList items, long total, IEntityWithVersion app) where T : IEntity, IEntityWithVersion + { + var sb = new StringBuilder((items.Count * (GuidLength + 8)) + 10); + + for (var i = 0; i < items.Count; i++) + { + sb.Append(";"); + sb.Append(items[i].ToEtag()); + } - sb.Append(total); sb.Append("_"); + sb.Append(total); - if (items.Count > 0) + if (app != null) { - sb.Append(items[0].Id.ToString()); - sb.Append(items[0].Version); - - for (var i = 1; i < items.Count; i++) - { - sb.Append(";"); - sb.Append(items[i].Id.ToString()); - sb.Append(items[i].Version); - } + sb.Append("_"); + sb.Append(app.Version); } - return sb.ToString().Sha256Base64(); + return sb.ToString(); + } + + public static string ToSurrogateKey(this T item) where T : IEntity + { + return item.Id.ToString(); } - public static string ToSurrogateKeys(this IReadOnlyList items) where T : IGenerateETag + public static string ToSurrogateKeys(this IReadOnlyList items) where T : IEntity { if (items.Count == 0) { @@ -70,9 +85,17 @@ namespace Squidex.Web return sb.ToString(); } - public static string ToEtag(this T item) where T : IGenerateETag + public static string ToEtag(this T item, IEntityWithVersion app = null) where T : IEntity, IEntityWithVersion { - return item.Version.ToString(); + var result = $"{item.Id};{item.Version}"; + + if (app != null) + { + result += ";"; + result += app.Version; + } + + return result; } } } diff --git a/src/Squidex.Web/IGenerateEtag.cs b/src/Squidex.Web/IGenerateEtag.cs deleted file mode 100644 index 6986f1acc..000000000 --- a/src/Squidex.Web/IGenerateEtag.cs +++ /dev/null @@ -1,18 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; - -namespace Squidex.Web -{ - public interface IGenerateETag - { - Guid Id { get; } - - long Version { get; } - } -} diff --git a/src/Squidex.Web/MyJsonInheritanceConverter.cs b/src/Squidex.Web/Json/TypedJsonInheritanceConverter.cs similarity index 79% rename from src/Squidex.Web/MyJsonInheritanceConverter.cs rename to src/Squidex.Web/Json/TypedJsonInheritanceConverter.cs index ff3a1854e..21505bf6e 100644 --- a/src/Squidex.Web/MyJsonInheritanceConverter.cs +++ b/src/Squidex.Web/Json/TypedJsonInheritanceConverter.cs @@ -16,17 +16,16 @@ using Squidex.Infrastructure; #pragma warning disable RECS0108 // Warns about static fields in generic types -namespace Squidex.Web +namespace Squidex.Web.Json { - public class MyJsonInheritanceConverter : JsonInheritanceConverter + public class TypedJsonInheritanceConverter : JsonInheritanceConverter { - private static readonly Dictionary DefaultMapping = new Dictionary(); - private readonly IReadOnlyDictionary maping; - - static MyJsonInheritanceConverter() + private static readonly Lazy> DefaultMapping = new Lazy>(() => { var baseName = typeof(T).Name; + var result = new Dictionary(); + void AddType(Type type) { var discriminator = type.Name; @@ -36,7 +35,7 @@ namespace Squidex.Web discriminator = discriminator.Substring(0, discriminator.Length - baseName.Length); } - DefaultMapping[discriminator] = type; + result[discriminator] = type; } foreach (var attribute in typeof(T).GetCustomAttributes()) @@ -66,17 +65,23 @@ namespace Squidex.Web } } } - } - public MyJsonInheritanceConverter(string discriminator) - : this(discriminator, DefaultMapping) + return result; + }); + + private readonly IReadOnlyDictionary maping; + + public TypedJsonInheritanceConverter(string discriminator) + : this(discriminator, DefaultMapping.Value) { } - public MyJsonInheritanceConverter(string discriminator, IReadOnlyDictionary mapping) + public TypedJsonInheritanceConverter(string discriminator, IReadOnlyDictionary mapping) : base(typeof(T), discriminator) { - maping = mapping ?? DefaultMapping; + Guard.NotNull(maping, nameof(maping)); + + maping = mapping; } protected override Type GetDiscriminatorType(JObject jObject, Type objectType, string discriminatorValue) diff --git a/src/Squidex.Web/Pipeline/DeferredActionFilter.cs b/src/Squidex.Web/Pipeline/DeferredActionFilter.cs new file mode 100644 index 000000000..9e5b3b4f2 --- /dev/null +++ b/src/Squidex.Web/Pipeline/DeferredActionFilter.cs @@ -0,0 +1,26 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Squidex.Web.Pipeline +{ + public sealed class DeferredActionFilter : IAsyncActionFilter + { + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + await next(); + + if (context.Result is ObjectResult objectResult && objectResult.Value is Deferred deferred) + { + objectResult.Value = await deferred.Value; + } + } + } +} diff --git a/src/Squidex.Web/ETagFilter.cs b/src/Squidex.Web/Pipeline/ETagFilter.cs similarity index 98% rename from src/Squidex.Web/ETagFilter.cs rename to src/Squidex.Web/Pipeline/ETagFilter.cs index b76772ad3..4dd680374 100644 --- a/src/Squidex.Web/ETagFilter.cs +++ b/src/Squidex.Web/Pipeline/ETagFilter.cs @@ -12,7 +12,7 @@ using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; -namespace Squidex.Web +namespace Squidex.Web.Pipeline { public sealed class ETagFilter : IAsyncActionFilter { diff --git a/src/Squidex.Web/ETagOptions.cs b/src/Squidex.Web/Pipeline/ETagOptions.cs similarity index 93% rename from src/Squidex.Web/ETagOptions.cs rename to src/Squidex.Web/Pipeline/ETagOptions.cs index 8e832dbca..d6715b233 100644 --- a/src/Squidex.Web/ETagOptions.cs +++ b/src/Squidex.Web/Pipeline/ETagOptions.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -namespace Squidex.Web +namespace Squidex.Web.Pipeline { public sealed class ETagOptions { diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs index b89697d26..92e749028 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs @@ -46,9 +46,12 @@ namespace Squidex.Areas.Api.Controllers.Apps [ApiCosts(0)] public IActionResult GetClients(string app) { - var response = ClientsDto.FromApp(App, this); + var response = Deferred.Response(() => + { + return ClientsDto.FromApp(App, this); + }); - Response.Headers[HeaderNames.ETag] = App.Version.ToString(); + Response.Headers[HeaderNames.ETag] = App.ToEtag(); return Ok(response); } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs index 021afa123..8d1534b74 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs @@ -48,9 +48,12 @@ namespace Squidex.Areas.Api.Controllers.Apps [ApiCosts(0)] public IActionResult GetContributors(string app) { - var response = ContributorsDto.FromApp(App, appPlansProvider, this, false); + var response = Deferred.Response(() => + { + return ContributorsDto.FromApp(App, appPlansProvider, this, false); + }); - Response.Headers[HeaderNames.ETag] = App.Version.ToString(); + Response.Headers[HeaderNames.ETag] = App.ToEtag(); return Ok(response); } @@ -73,18 +76,8 @@ namespace Squidex.Areas.Api.Controllers.Apps public async Task PostContributor(string app, [FromBody] AssignContributorDto request) { var command = request.ToCommand(); - var context = await CommandBus.PublishAsync(command); - var response = (ContributorsDto)null; - - if (context.PlainResult is IAppEntity newApp) - { - response = ContributorsDto.FromApp(newApp, appPlansProvider, this, false); - } - else if (context.PlainResult is InvitedResult invited) - { - response = ContributorsDto.FromApp(invited.App, appPlansProvider, this, true); - } + var response = await InvokeCommandAsync(command); return CreatedAtAction(nameof(GetContributors), new { app }, response); } @@ -117,10 +110,14 @@ namespace Squidex.Areas.Api.Controllers.Apps { var context = await CommandBus.PublishAsync(command); - var result = context.Result(); - var response = ContributorsDto.FromApp(result, appPlansProvider, this, false); - - return response; + if (context.PlainResult is InvitedResult invited) + { + return ContributorsDto.FromApp(invited.App, appPlansProvider, this, true); + } + else + { + return ContributorsDto.FromApp(context.Result(), appPlansProvider, this, false); + } } } } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs index 03064da7b..43498aa82 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs @@ -45,9 +45,12 @@ namespace Squidex.Areas.Api.Controllers.Apps [ApiCosts(0)] public IActionResult GetLanguages(string app) { - var response = AppLanguagesDto.FromApp(App, this); + var response = Deferred.Response(() => + { + return AppLanguagesDto.FromApp(App, this); + }); - Response.Headers[HeaderNames.ETag] = App.Version.ToString(); + Response.Headers[HeaderNames.ETag] = App.ToEtag(); return Ok(response); } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs index 022a20cab..74f9fc136 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs @@ -47,9 +47,12 @@ namespace Squidex.Areas.Api.Controllers.Apps [ApiCosts(0)] public IActionResult GetPatterns(string app) { - var response = PatternsDto.FromApp(App, this); + var response = Deferred.Response(() => + { + return PatternsDto.FromApp(App, this); + }); - Response.Headers[HeaderNames.ETag] = App.Version.ToString(); + Response.Headers[HeaderNames.ETag] = App.ToEtag(); return Ok(response); } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs index d51daaf2d..ac427a567 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs @@ -47,9 +47,12 @@ namespace Squidex.Areas.Api.Controllers.Apps [ApiCosts(0)] public IActionResult GetRoles(string app) { - var response = RolesDto.FromApp(App, this); + var response = Deferred.Response(() => + { + return RolesDto.FromApp(App, this); + }); - Response.Headers[HeaderNames.ETag] = App.Version.ToString(); + Response.Headers[HeaderNames.ETag] = App.ToEtag(); return Ok(response); } @@ -67,11 +70,14 @@ namespace Squidex.Areas.Api.Controllers.Apps [ProducesResponseType(typeof(string[]), 200)] [ApiPermission(Permissions.AppRolesRead)] [ApiCosts(0)] - public async Task GetPermissions(string app) + public IActionResult GetPermissions(string app) { - var response = await permissionsProvider.GetPermissionsAsync(App); + var response = Deferred.AsyncResponse(() => + { + return permissionsProvider.GetPermissionsAsync(App); + }); - Response.Headers[HeaderNames.ETag] = string.Join(";", response).Sha256Base64(); + Response.Headers[HeaderNames.ETag] = string.Concat(response).Sha256Base64(); return Ok(response); } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppWorkflowsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppWorkflowsController.cs index 22e003e75..5377ea7d7 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppWorkflowsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppWorkflowsController.cs @@ -44,9 +44,12 @@ namespace Squidex.Areas.Api.Controllers.Apps [ApiCosts(0)] public IActionResult GetWorkflows(string app) { - var response = WorkflowsDto.FromApp(App, this); + var response = Deferred.Response(() => + { + return WorkflowsDto.FromApp(App, this); + }); - Response.Headers[HeaderNames.ETag] = App.Version.ToString(); + Response.Headers[HeaderNames.ETag] = App.ToEtag(); return Ok(response); } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs index 1d2b9c26a..6529dd4e5 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs @@ -62,9 +62,12 @@ namespace Squidex.Areas.Api.Controllers.Apps var apps = await appProvider.GetUserApps(userOrClientId, userPermissions); - var response = apps.ToArray(a => AppDto.FromApp(a, userOrClientId, userPermissions, appPlansProvider, this)); + var response = Deferred.Response(() => + { + return apps.ToArray(a => AppDto.FromApp(a, userOrClientId, userPermissions, appPlansProvider, this)); + }); - Response.Headers[HeaderNames.ETag] = response.ToManyEtag(); + Response.Headers[HeaderNames.ETag] = apps.ToEtag(); return Ok(response); } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs index ffa7b22fa..1eee4cdd5 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs @@ -25,7 +25,7 @@ using AllPermissions = Squidex.Shared.Permissions; namespace Squidex.Areas.Api.Controllers.Apps.Models { - public sealed class AppDto : Resource, IGenerateETag + public sealed class AppDto : Resource { /// /// The name of the app. diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowsDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowsDto.cs index b58be115c..3f1e1ecd7 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowsDto.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using System.ComponentModel.DataAnnotations; using System.Linq; using Squidex.Domain.Apps.Entities.Apps; diff --git a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs index 38faed8f3..84f423dda 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs @@ -103,14 +103,17 @@ namespace Squidex.Areas.Api.Controllers.Assets { var assets = await assetQuery.QueryAsync(Context, Q.Empty.WithODataQuery(Request.QueryString.ToString()).WithIds(ids)); - var response = AssetsDto.FromAssets(assets, this, app); + var response = Deferred.Response(() => + { + return AssetsDto.FromAssets(assets, this, app); + }); - if (controllerOptions.Value.EnableSurrogateKeys && response.Items.Length <= controllerOptions.Value.MaxItemsForSurrogateKeys) + if (controllerOptions.Value.EnableSurrogateKeys && assets.Count <= controllerOptions.Value.MaxItemsForSurrogateKeys) { - Response.Headers["Surrogate-Key"] = response.ToSurrogateKeys(); + Response.Headers["Surrogate-Key"] = assets.ToSurrogateKeys(); } - Response.Headers[HeaderNames.ETag] = response.ToEtag(); + Response.Headers[HeaderNames.ETag] = assets.ToEtag(); return Ok(response); } @@ -138,14 +141,17 @@ namespace Squidex.Areas.Api.Controllers.Assets return NotFound(); } - var response = AssetDto.FromAsset(asset, this, app); + var response = Deferred.Response(() => + { + return AssetDto.FromAsset(asset, this, app); + }); if (controllerOptions.Value.EnableSurrogateKeys) { - Response.Headers["Surrogate-Key"] = asset.Id.ToString(); + Response.Headers["Surrogate-Key"] = asset.ToSurrogateKey(); } - Response.Headers[HeaderNames.ETag] = asset.Version.ToString(); + Response.Headers[HeaderNames.ETag] = asset.ToEtag(); return Ok(response); } @@ -175,10 +181,7 @@ namespace Squidex.Areas.Api.Controllers.Assets var command = new CreateAsset { File = assetFile }; - var context = await CommandBus.PublishAsync(command); - - var result = context.Result(); - var response = AssetDto.FromAsset(result.Asset, this, app, result.IsDuplicate); + var response = await InvokeCommandAsync(app, command); return CreatedAtAction(nameof(GetAsset), new { app, id = response.Id }, response); } @@ -263,10 +266,14 @@ namespace Squidex.Areas.Api.Controllers.Assets { var context = await CommandBus.PublishAsync(command); - var result = context.Result(); - var response = AssetDto.FromAsset(result, this, app); - - return response; + if (context.PlainResult is AssetCreatedResult created) + { + return AssetDto.FromAsset(created.Asset, this, app, created.IsDuplicate); + } + else + { + return AssetDto.FromAsset(context.Result(), this, app); + } } private async Task CheckAssetFileAsync(IReadOnlyList file) diff --git a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs index 5c996cf0d..4ba2cb68f 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs @@ -18,7 +18,7 @@ using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Assets.Models { - public sealed class AssetDto : Resource, IGenerateETag + public sealed class AssetDto : Resource { /// /// The id of the asset. diff --git a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs index efd81147b..fbaa6dd46 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs @@ -27,16 +27,6 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models [Required] public AssetDto[] Items { get; set; } - public string ToEtag() - { - return Items.ToManyEtag(Total); - } - - public string ToSurrogateKeys() - { - return Items.ToSurrogateKeys(); - } - public static AssetsDto FromAssets(IResultList assets, ApiController controller, string app) { var response = new AssetsDto diff --git a/src/Squidex/Areas/Api/Controllers/Comments/CommentsController.cs b/src/Squidex/Areas/Api/Controllers/Comments/CommentsController.cs index 6125003f5..735bd640a 100644 --- a/src/Squidex/Areas/Api/Controllers/Comments/CommentsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Comments/CommentsController.cs @@ -55,9 +55,13 @@ namespace Squidex.Areas.Api.Controllers.Comments public async Task GetComments(string app, Guid commentsId, [FromQuery] long version = EtagVersion.Any) { var result = await grainFactory.GetGrain(commentsId).GetCommentsAsync(version); - var response = CommentsDto.FromResult(result); - Response.Headers[HeaderNames.ETag] = response.Version.ToString(); + var response = Deferred.Response(() => + { + return CommentsDto.FromResult(result); + }); + + Response.Headers[HeaderNames.ETag] = result.Version.ToString(); return Ok(response); } diff --git a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs b/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs index 095be8d07..8eb70829d 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs @@ -6,6 +6,7 @@ // ========================================================================== using System; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; @@ -126,14 +127,17 @@ namespace Squidex.Areas.Api.Controllers.Contents { var contents = await contentQuery.QueryAsync(Context, Q.Empty.WithIds(ids).Ids); - var response = await ContentsDto.FromContentsAsync(contents, Context, this, null, contentWorkflow); + var response = Deferred.AsyncResponse(() => + { + return ContentsDto.FromContentsAsync(contents, Context, this, null, contentWorkflow); + }); - if (controllerOptions.Value.EnableSurrogateKeys && response.Items.Length <= controllerOptions.Value.MaxItemsForSurrogateKeys) + if (ShouldProvideSurrogateKeys(contents)) { - Response.Headers["Surrogate-Key"] = response.ToSurrogateKeys(); + Response.Headers["Surrogate-Key"] = contents.ToSurrogateKeys(); } - Response.Headers[HeaderNames.ETag] = $"{response.ToEtag()}_{App.Version}"; + Response.Headers[HeaderNames.ETag] = contents.ToEtag(App); return Ok(response); } @@ -160,16 +164,19 @@ namespace Squidex.Areas.Api.Controllers.Contents { var contents = await contentQuery.QueryAsync(Context, name, Q.Empty.WithIds(ids).WithODataQuery(Request.QueryString.ToString())); - var schema = await contentQuery.GetSchemaOrThrowAsync(Context, name); + var response = Deferred.AsyncResponse(async () => + { + var schema = await contentQuery.GetSchemaOrThrowAsync(Context, name); - var response = await ContentsDto.FromContentsAsync(contents, Context, this, schema, contentWorkflow); + return await ContentsDto.FromContentsAsync(contents, Context, this, schema, contentWorkflow); + }); - if (ShouldProvideSurrogateKeys(response)) + if (ShouldProvideSurrogateKeys(contents)) { - Response.Headers["Surrogate-Key"] = response.ToSurrogateKeys(); + Response.Headers["Surrogate-Key"] = contents.ToSurrogateKeys(); } - Response.Headers[HeaderNames.ETag] = $"{response.ToEtag()}_{App.Version}"; + Response.Headers[HeaderNames.ETag] = contents.ToEtag(App); return Ok(response); } @@ -200,10 +207,10 @@ namespace Squidex.Areas.Api.Controllers.Contents if (controllerOptions.Value.EnableSurrogateKeys) { - Response.Headers["Surrogate-Key"] = content.Id.ToString(); + Response.Headers["Surrogate-Key"] = content.ToSurrogateKey(); } - Response.Headers[HeaderNames.ETag] = $"{response.ToEtag()}_{App.Version}"; + Response.Headers[HeaderNames.ETag] = content.ToEtag(App); return Ok(response); } @@ -235,10 +242,10 @@ namespace Squidex.Areas.Api.Controllers.Contents if (controllerOptions.Value.EnableSurrogateKeys) { - Response.Headers["Surrogate-Key"] = content.Id.ToString(); + Response.Headers["Surrogate-Key"] = content.ToSurrogateKey(); } - Response.Headers[HeaderNames.ETag] = $"{response.ToEtag()}_{App.Version}"; + Response.Headers[HeaderNames.ETag] = content.ToEtag(App); return Ok(response.Data); } @@ -447,9 +454,9 @@ namespace Squidex.Areas.Api.Controllers.Contents return response; } - private bool ShouldProvideSurrogateKeys(ContentsDto response) + private bool ShouldProvideSurrogateKeys(IReadOnlyList response) { - return controllerOptions.Value.EnableSurrogateKeys && response.Items.Length <= controllerOptions.Value.MaxItemsForSurrogateKeys; + return controllerOptions.Value.EnableSurrogateKeys && response.Count <= controllerOptions.Value.MaxItemsForSurrogateKeys; } } } diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs index 0725239e4..513abdbea 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs @@ -19,7 +19,7 @@ using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Contents.Models { - public sealed class ContentDto : Resource, IGenerateETag + public sealed class ContentDto : Resource { /// /// The if of the content item. diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs index 749e662d1..7e0d14ccd 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs @@ -37,16 +37,6 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models [Required] public StatusInfoDto[] Statuses { get; set; } - public string ToEtag() - { - return Items.ToManyEtag(Total); - } - - public string ToSurrogateKeys() - { - return Items.ToSurrogateKeys(); - } - public static async Task FromContentsAsync(IResultList contents, Context context, ApiController controller, ISchemaEntity schema, IContentWorkflow contentWorkflow) { diff --git a/src/Squidex/Areas/Api/Controllers/Languages/LanguagesController.cs b/src/Squidex/Areas/Api/Controllers/Languages/LanguagesController.cs index e87b8c68c..62726a9bb 100644 --- a/src/Squidex/Areas/Api/Controllers/Languages/LanguagesController.cs +++ b/src/Squidex/Areas/Api/Controllers/Languages/LanguagesController.cs @@ -40,7 +40,10 @@ namespace Squidex.Areas.Api.Controllers.Languages [ApiPermission] public IActionResult GetLanguages() { - var response = Language.AllLanguages.Select(LanguageDto.FromLanguage).ToArray(); + var response = Deferred.Response(() => + { + return Language.AllLanguages.Select(LanguageDto.FromLanguage).ToArray(); + }); Response.Headers[HeaderNames.ETag] = "1"; diff --git a/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs b/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs index a14f220a3..99de9745b 100644 --- a/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs +++ b/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs @@ -51,9 +51,12 @@ namespace Squidex.Areas.Api.Controllers.Plans { var hasPortal = appPlansBillingManager.HasPortal; - var response = AppPlansDto.FromApp(App, appPlansProvider, hasPortal); + var response = Deferred.Response(() => + { + return AppPlansDto.FromApp(App, appPlansProvider, hasPortal); + }); - Response.Headers[HeaderNames.ETag] = App.Version.ToString(); + Response.Headers[HeaderNames.ETag] = App.ToEtag(); return Ok(response); } diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionConverter.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionConverter.cs index 8f1da7b9e..b108b7be4 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionConverter.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionConverter.cs @@ -8,11 +8,11 @@ using System; using System.Collections.Generic; using Squidex.Domain.Apps.Core.Rules; -using Squidex.Web; +using Squidex.Web.Json; namespace Squidex.Areas.Api.Controllers.Rules.Models { - public sealed class RuleActionConverter : MyJsonInheritanceConverter + public sealed class RuleActionConverter : TypedJsonInheritanceConverter { public static IReadOnlyDictionary Mapping { get; set; } diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs index 39ec2ff60..e625f2f35 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs @@ -19,7 +19,7 @@ using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Rules.Models { - public sealed class RuleDto : Resource, IGenerateETag + public sealed class RuleDto : Resource { /// /// The id of the rule. diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleTriggerDto.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleTriggerDto.cs index 9ac6cd699..4392bdfba 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleTriggerDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleTriggerDto.cs @@ -10,11 +10,11 @@ using System.Linq; using System.Runtime.Serialization; using Newtonsoft.Json; using Squidex.Domain.Apps.Core.Rules; -using Squidex.Web; +using Squidex.Web.Json; namespace Squidex.Areas.Api.Controllers.Rules.Models { - [JsonConverter(typeof(MyJsonInheritanceConverter), "triggerType")] + [JsonConverter(typeof(TypedJsonInheritanceConverter), "triggerType")] [KnownType(nameof(Subtypes))] public abstract class RuleTriggerDto { diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RulesDto.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RulesDto.cs index c13c163fb..7379e019a 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/RulesDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RulesDto.cs @@ -22,11 +22,6 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models [Required] public RuleDto[] Items { get; set; } - public string GenerateEtag() - { - return Items.ToManyEtag(0); - } - public static RulesDto FromRules(IEnumerable items, ApiController controller, string app) { var result = new RulesDto diff --git a/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs b/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs index c6213c93b..bd5ed4fbd 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs @@ -58,9 +58,12 @@ namespace Squidex.Areas.Api.Controllers.Rules [ApiCosts(0)] public IActionResult GetActions() { - var etag = string.Join(";", ruleRegistry.Actions.Select(x => x.Key)).Sha256Base64(); + var etag = string.Concat(ruleRegistry.Actions.Select(x => x.Key)).Sha256Base64(); - var response = ruleRegistry.Actions.ToDictionary(x => x.Key, x => RuleElementDto.FromDefinition(x.Value)); + var response = Deferred.Response(() => + { + return ruleRegistry.Actions.ToDictionary(x => x.Key, x => RuleElementDto.FromDefinition(x.Value)); + }); Response.Headers[HeaderNames.ETag] = etag; @@ -84,9 +87,12 @@ namespace Squidex.Areas.Api.Controllers.Rules { var rules = await appProvider.GetRulesAsync(AppId); - var response = RulesDto.FromRules(rules, this, app); + var response = Deferred.Response(() => + { + return RulesDto.FromRules(rules, this, app); + }); - Response.Headers[HeaderNames.ETag] = response.GenerateEtag(); + Response.Headers[HeaderNames.ETag] = rules.ToEtag(); return Ok(response); } diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldPropertiesDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldPropertiesDto.cs index 02376143b..b09c7d002 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldPropertiesDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldPropertiesDto.cs @@ -11,11 +11,11 @@ using System.Linq; using System.Runtime.Serialization; using Newtonsoft.Json; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Web; +using Squidex.Web.Json; namespace Squidex.Areas.Api.Controllers.Schemas.Models { - [JsonConverter(typeof(MyJsonInheritanceConverter), "fieldType")] + [JsonConverter(typeof(TypedJsonInheritanceConverter), "fieldType")] [KnownType(nameof(Subtypes))] public abstract class FieldPropertiesDto { diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs index 35fafd420..4b349216c 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs @@ -17,7 +17,7 @@ using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Schemas.Models { - public class SchemaDto : Resource, IGenerateETag + public class SchemaDto : Resource { /// /// The id of the schema. diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs index 596c80d07..ebdaa95ab 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs @@ -21,11 +21,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models /// public SchemaDto[] Items { get; set; } - public string ToEtag() - { - return Items.ToManyEtag(); - } - public static SchemasDto FromSchemas(IList schemas, ApiController controller, string app) { var result = new SchemasDto diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs b/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs index 67e807a45..192c8fc87 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs @@ -50,9 +50,12 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var schemas = await appProvider.GetSchemasAsync(AppId); - var response = SchemasDto.FromSchemas(schemas, this, app); + var response = Deferred.Response(() => + { + return SchemasDto.FromSchemas(schemas, this, app); + }); - Response.Headers[HeaderNames.ETag] = response.ToEtag(); + Response.Headers[HeaderNames.ETag] = schemas.ToEtag(); return Ok(response); } @@ -89,9 +92,12 @@ namespace Squidex.Areas.Api.Controllers.Schemas return NotFound(); } - var response = SchemaDetailsDto.FromSchemaWithDetails(schema, this, app); + var response = Deferred.Response(() => + { + return SchemaDetailsDto.FromSchemaWithDetails(schema, this, app); + }); - Response.Headers[HeaderNames.ETag] = schema.Version.ToString(); + Response.Headers[HeaderNames.ETag] = schema.ToEtag(); return Ok(response); } diff --git a/src/Squidex/Config/Domain/SerializationInitializer.cs b/src/Squidex/Config/Domain/SerializationInitializer.cs index 0254b318b..9d1bb868e 100644 --- a/src/Squidex/Config/Domain/SerializationInitializer.cs +++ b/src/Squidex/Config/Domain/SerializationInitializer.cs @@ -29,6 +29,7 @@ namespace Squidex.Config.Domain { this.jsonNetSerializer = jsonNetSerializer; this.jsonSerializer = jsonSerializer; + this.ruleRegistry = ruleRegistry; } diff --git a/src/Squidex/Config/Web/WebServices.cs b/src/Squidex/Config/Web/WebServices.cs index 580b214ad..e4e867d85 100644 --- a/src/Squidex/Config/Web/WebServices.cs +++ b/src/Squidex/Config/Web/WebServices.cs @@ -51,6 +51,7 @@ namespace Squidex.Config.Web services.AddMvc(options => { options.Filters.Add(); + options.Filters.Add(); options.Filters.Add(); options.Filters.Add(); })