Browse Source

Etag improvements.

pull/320/head
Sebastian Stehle 8 years ago
parent
commit
8bbf4c9b0e
  1. 3
      src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs
  2. 3
      src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs
  3. 18
      src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs
  4. 5
      src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs
  5. 8
      src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs
  6. 16
      src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs
  7. 5
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs
  8. 11
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs
  9. 5
      src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs
  10. 2
      src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs
  11. 12
      src/Squidex/Pipeline/ETagFilter.cs
  12. 31
      src/Squidex/Pipeline/EtagExtensions.cs
  13. 6
      src/Squidex/Pipeline/IGenerateEtag.cs

3
src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs

@ -14,7 +14,6 @@ using Squidex.Areas.Api.Controllers.Apps.Models;
using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
using Squidex.Pipeline; using Squidex.Pipeline;
@ -63,7 +62,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
var response = entities.Select(a => AppDto.FromApp(a, subject, appPlansProvider)).ToList(); var response = entities.Select(a => AppDto.FromApp(a, subject, appPlansProvider)).ToList();
Response.Headers["Etag"] = string.Join(";", response.Select(x => $"{x.Id}{x.Version}")).Sha256Base64(); Response.Headers["ETag"] = response.ToManyEtag();
return Ok(response); return Ok(response);
} }

3
src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs

@ -14,10 +14,11 @@ using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Pipeline;
namespace Squidex.Areas.Api.Controllers.Apps.Models namespace Squidex.Areas.Api.Controllers.Apps.Models
{ {
public sealed class AppDto public sealed class AppDto : IGenerateEtag
{ {
/// <summary> /// <summary>
/// The name of the app. /// The name of the app.

18
src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs

@ -7,13 +7,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using NSwag.Annotations; using NSwag.Annotations;
using Squidex.Areas.Api.Controllers.Assets.Models; using Squidex.Areas.Api.Controllers.Assets.Models;
using Squidex.Areas.Api.Controllers.Contents;
using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Domain.Apps.Entities.Apps.Services;
@ -39,6 +39,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
private readonly IAssetQueryService assetQuery; private readonly IAssetQueryService assetQuery;
private readonly IAssetStatsRepository assetStatsRepository; private readonly IAssetStatsRepository assetStatsRepository;
private readonly IAppPlansProvider appPlanProvider; private readonly IAppPlansProvider appPlanProvider;
private readonly IOptions<MyContentsControllerOptions> controllerOptions;
private readonly ITagService tagService; private readonly ITagService tagService;
private readonly AssetConfig assetsConfig; private readonly AssetConfig assetsConfig;
@ -48,6 +49,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
IAssetStatsRepository assetStatsRepository, IAssetStatsRepository assetStatsRepository,
IAppPlansProvider appPlanProvider, IAppPlansProvider appPlanProvider,
IOptions<AssetConfig> assetsConfig, IOptions<AssetConfig> assetsConfig,
IOptions<MyContentsControllerOptions> controllerOptions,
ITagService tagService) ITagService tagService)
: base(commandBus) : base(commandBus)
{ {
@ -55,6 +57,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
this.assetQuery = assetQuery; this.assetQuery = assetQuery;
this.assetStatsRepository = assetStatsRepository; this.assetStatsRepository = assetStatsRepository;
this.appPlanProvider = appPlanProvider; this.appPlanProvider = appPlanProvider;
this.controllerOptions = controllerOptions;
this.tagService = tagService; this.tagService = tagService;
} }
@ -106,7 +109,12 @@ namespace Squidex.Areas.Api.Controllers.Assets
var response = AssetsDto.FromAssets(assets); var response = AssetsDto.FromAssets(assets);
Response.Headers["Surrogate-Key"] = string.Join(" ", response.Items.Select(x => x.Id)); if (controllerOptions.Value.EnableSurrogateKeys && response.Items.Length <= controllerOptions.Value.MaxItemsForSurrogateKeys)
{
Response.Headers["Surrogate-Key"] = response.Items.ToSurrogateKeys();
}
Response.Headers["ETag"] = response.Items.ToManyEtag(response.Total);
return Ok(response); return Ok(response);
} }
@ -138,8 +146,12 @@ namespace Squidex.Areas.Api.Controllers.Assets
var response = AssetDto.FromAsset(entity); var response = AssetDto.FromAsset(entity);
if (controllerOptions.Value.EnableSurrogateKeys)
{
Response.Headers["Surrogate-Key"] = entity.Id.ToString();
}
Response.Headers["ETag"] = entity.Version.ToString(); Response.Headers["ETag"] = entity.Version.ToString();
Response.Headers["Surrogate-Key"] = entity.Id.ToString();
return Ok(response); return Ok(response);
} }

5
src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs

@ -98,11 +98,6 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models
/// </summary> /// </summary>
public long Version { get; set; } public long Version { get; set; }
public string GenerateETag()
{
return $"{Id}{Version}";
}
public static AssetDto FromAsset(IAssetEntity asset) public static AssetDto FromAsset(IAssetEntity asset)
{ {
return SimpleMapper.Map(asset, new AssetDto { FileType = asset.FileName.FileType() }); return SimpleMapper.Map(asset, new AssetDto { FileType = asset.FileName.FileType() });

8
src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs

@ -9,11 +9,10 @@ using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Pipeline;
namespace Squidex.Areas.Api.Controllers.Assets.Models namespace Squidex.Areas.Api.Controllers.Assets.Models
{ {
public sealed class AssetsDto : IGenerateEtag public sealed class AssetsDto
{ {
/// <summary> /// <summary>
/// The assets. /// The assets.
@ -26,11 +25,6 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models
/// </summary> /// </summary>
public long Total { get; set; } public long Total { get; set; }
public string GenerateETag()
{
return string.Join(";", Items?.Select(x => x.GenerateETag()) ?? Enumerable.Empty<string>()).Sha256Base64();
}
public static AssetsDto FromAssets(IResultList<IAssetEntity> assets) public static AssetsDto FromAssets(IResultList<IAssetEntity> assets)
{ {
return new AssetsDto { Total = assets.Total, Items = assets.Select(AssetDto.FromAsset).ToArray() }; return new AssetsDto { Total = assets.Total, Items = assets.Select(AssetDto.FromAsset).ToArray() };

16
src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs

@ -138,13 +138,13 @@ namespace Squidex.Areas.Api.Controllers.Contents
Items = result.Take(200).Select(x => ContentDto.FromContent(x, context.Base)).ToArray() Items = result.Take(200).Select(x => ContentDto.FromContent(x, context.Base)).ToArray()
}; };
var options = controllerOptions.Value; if (controllerOptions.Value.EnableSurrogateKeys && response.Items.Length <= controllerOptions.Value.MaxItemsForSurrogateKeys)
if (options.EnableSurrogateKeys && response.Items.Length <= options.MaxItemsForSurrogateKeys)
{ {
Response.Headers["Surrogate-Key"] = string.Join(" ", response.Items.Select(x => x.Id)); Response.Headers["Surrogate-Key"] = response.Items.ToSurrogateKeys();
} }
Response.Headers["ETag"] = response.Items.ToManyEtag(response.Total);
return Ok(response); return Ok(response);
} }
@ -172,13 +172,13 @@ namespace Squidex.Areas.Api.Controllers.Contents
var response = ContentDto.FromContent(content, context.Base); var response = ContentDto.FromContent(content, context.Base);
Response.Headers["ETag"] = content.Version.ToString();
if (controllerOptions.Value.EnableSurrogateKeys) if (controllerOptions.Value.EnableSurrogateKeys)
{ {
Response.Headers["Surrogate-Key"] = content.Id.ToString(); Response.Headers["Surrogate-Key"] = content.Id.ToString();
} }
Response.Headers["ETag"] = content.Version.ToString();
return Ok(response); return Ok(response);
} }
@ -208,13 +208,13 @@ namespace Squidex.Areas.Api.Controllers.Contents
var response = ContentDto.FromContent(content, context.Base); var response = ContentDto.FromContent(content, context.Base);
Response.Headers["ETag"] = content.Version.ToString();
if (controllerOptions.Value.EnableSurrogateKeys) if (controllerOptions.Value.EnableSurrogateKeys)
{ {
Response.Headers["Surrogate-Key"] = content.Id.ToString(); Response.Headers["Surrogate-Key"] = content.Id.ToString();
} }
Response.Headers["ETag"] = content.Version.ToString();
return Ok(response.Data); return Ok(response.Data);
} }

5
src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs

@ -80,11 +80,6 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
/// </summary> /// </summary>
public long Version { get; set; } public long Version { get; set; }
public string GenerateETag()
{
return $"{Id}{Version}";
}
public static ContentDto FromCommand(CreateContent command, EntityCreatedResult<NamedContentData> result) public static ContentDto FromCommand(CreateContent command, EntityCreatedResult<NamedContentData> result)
{ {
var now = SystemClock.Instance.GetCurrentInstant(); var now = SystemClock.Instance.GetCurrentInstant();

11
src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs

@ -5,13 +5,9 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Linq;
using Squidex.Infrastructure;
using Squidex.Pipeline;
namespace Squidex.Areas.Api.Controllers.Contents.Models namespace Squidex.Areas.Api.Controllers.Contents.Models
{ {
public sealed class ContentsDto : IGenerateEtag public sealed class ContentsDto
{ {
/// <summary> /// <summary>
/// The total number of content items. /// The total number of content items.
@ -22,10 +18,5 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
/// The content items. /// The content items.
/// </summary> /// </summary>
public ContentDto[] Items { get; set; } public ContentDto[] Items { get; set; }
public string GenerateETag()
{
return string.Join(";", Items?.Select(x => x.GenerateETag()) ?? Enumerable.Empty<string>()).Sha256Base64();
}
} }
} }

5
src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs

@ -11,10 +11,11 @@ using NodaTime;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Pipeline;
namespace Squidex.Areas.Api.Controllers.Schemas.Models namespace Squidex.Areas.Api.Controllers.Schemas.Models
{ {
public sealed class SchemaDto public sealed class SchemaDto : IGenerateEtag
{ {
/// <summary> /// <summary>
/// The id of the schema. /// The id of the schema.
@ -74,7 +75,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
/// <summary> /// <summary>
/// The version of the schema. /// The version of the schema.
/// </summary> /// </summary>
public int Version { get; set; } public long Version { get; set; }
public static SchemaDto FromSchema(ISchemaEntity schema) public static SchemaDto FromSchema(ISchemaEntity schema)
{ {

2
src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs

@ -55,6 +55,8 @@ namespace Squidex.Areas.Api.Controllers.Schemas
var response = schemas.Select(SchemaDto.FromSchema).ToList(); var response = schemas.Select(SchemaDto.FromSchema).ToList();
Response.Headers["ETag"] = response.ToManyEtag();
return Ok(response); return Ok(response);
} }

12
src/Squidex/Pipeline/ETagFilter.cs

@ -19,20 +19,10 @@ namespace Squidex.Pipeline
var httpContext = context.HttpContext; var httpContext = context.HttpContext;
if (!httpContext.Response.Headers.TryGetValue("Etag", out _) && resultContext.Result is ObjectResult obj && obj.Value is IGenerateEtag g)
{
var calculatedEtag = g.GenerateETag();
if (!string.IsNullOrWhiteSpace(calculatedEtag))
{
httpContext.Response.Headers.Add("Etag", calculatedEtag);
}
}
if (httpContext.Request.Method == "GET" && if (httpContext.Request.Method == "GET" &&
httpContext.Request.Headers.TryGetValue("If-None-Match", out var noneMatch) && httpContext.Request.Headers.TryGetValue("If-None-Match", out var noneMatch) &&
httpContext.Response.StatusCode == 200 && httpContext.Response.StatusCode == 200 &&
httpContext.Response.Headers.TryGetValue("Etag", out var etag) && httpContext.Response.Headers.TryGetValue("ETag", out var etag) &&
!string.IsNullOrWhiteSpace(noneMatch) && !string.IsNullOrWhiteSpace(noneMatch) &&
!string.IsNullOrWhiteSpace(etag) && !string.IsNullOrWhiteSpace(etag) &&
string.Equals(etag, noneMatch, System.StringComparison.Ordinal)) string.Equals(etag, noneMatch, System.StringComparison.Ordinal))

31
src/Squidex/Pipeline/EtagExtensions.cs

@ -0,0 +1,31 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using Squidex.Infrastructure;
namespace Squidex.Pipeline
{
public static class EtagExtensions
{
public static string ToManyEtag<T>(this IEnumerable<T> items, long total = 0) where T : IGenerateEtag
{
return $"{total}_{string.Join(";", items.Select(x => $"{x.Id}{x.Version}"))}".Sha256Base64();
}
public static string ToSurrogateKeys<T>(this IEnumerable<T> items) where T : IGenerateEtag
{
return string.Join(" ", items.Select(x => x.Id));
}
public static string ToEtag<T>(this T item) where T : IGenerateEtag
{
return item.Version.ToString();
}
}
}

6
src/Squidex/Pipeline/IGenerateEtag.cs

@ -5,10 +5,14 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
namespace Squidex.Pipeline namespace Squidex.Pipeline
{ {
public interface IGenerateEtag public interface IGenerateEtag
{ {
string GenerateETag(); Guid Id { get; }
long Version { get; }
} }
} }

Loading…
Cancel
Save