Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the API content response builder extendable #16056

Merged
merged 2 commits into from
Apr 16, 2024

Conversation

kjac
Copy link
Contributor

@kjac kjac commented Apr 15, 2024

Prerequisites

  • I have added steps to test this contribution in the description below

Description

This PR makes the default API content response builder extendable, thus making it easier to customize the output of the Delivery API.

Due to how System.Text.Json handles polymorphism, the default JSON type resolver also needs to be extendable, in order to add custom response models to the allow-list for API responses.

Testing this PR

Build a custom IApiContentResponseBuilder by extending the default ApiContentResponseBuilder. The custom API response builder should be able to:

  1. Return its own content model (an extension of ApiContentResponse).
  2. Manipulate the properties collection created by the ApiContentResponseBuilder base before they are returned by the API.

The following test code can be used, or at least serve as inspiration for testing 😉

using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using Umbraco.Cms.Api.Common.DependencyInjection;
using Umbraco.Cms.Api.Delivery.Json;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.Models.DeliveryApi;
using Umbraco.Cms.Core.Models.PublishedContent;

namespace Umbraco.Cms.Web.UI.Custom;

public class MyApiContentResponseBuilder : ApiContentResponseBuilder
{
    public MyApiContentResponseBuilder(
        IApiContentNameProvider apiContentNameProvider,
        IApiContentRouteBuilder apiContentRouteBuilder,
        IOutputExpansionStrategyAccessor outputExpansionStrategyAccessor)
        : base(apiContentNameProvider, apiContentRouteBuilder, outputExpansionStrategyAccessor)
    {
    }

    protected override IApiContentResponse Create(
        IPublishedContent content,
        string name,
        IApiContentRoute route,
        IDictionary<string, object?> properties)
    {
        properties["title"] = properties.TryGetValue("title", out var value) && value is string titleValue
            ? $"I modified the title value: {titleValue}"
            : $"I added the title value";

        IDictionary<string, IApiContentRoute> cultures = GetCultures(content);
        return new MyApiContentResponse(
            content.Key,
            name,
            content.ContentType.Alias,
            content.CreateDate,
            content.UpdateDate,
            route,
            properties,
            cultures,
            DateTime.UtcNow.ToString("s"));
    }
}

public class MyApiContentResponse : ApiContentResponse
{
    public string MyExtraResponseData { get; }

    public MyApiContentResponse(
        Guid id,
        string name,
        string contentType,
        DateTime createDate,
        DateTime updateDate,
        IApiContentRoute route,
        IDictionary<string, object?> properties,
        IDictionary<string, IApiContentRoute> cultures,
        string myExtraResponseData)
        : base(id, name, contentType, createDate, updateDate, route, properties, cultures)
        => MyExtraResponseData = myExtraResponseData;
}

public class MyDeliveryApiJsonTypeResolver : DeliveryApiJsonTypeResolver
{
    protected override Type[] GetDerivedTypes(JsonTypeInfo jsonTypeInfo)
        => jsonTypeInfo.Type == typeof(IApiContentResponse)
            ? new[] { typeof(MyApiContentResponse), typeof(ApiContentResponse) }
            : base.GetDerivedTypes(jsonTypeInfo);
}

public class MyApiContentResponseComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.Services.AddSingleton<IApiContentResponseBuilder, MyApiContentResponseBuilder>();
        builder
            .Services
            .AddControllers()
            .AddJsonOptions(
                Constants.JsonOptionsNames.DeliveryApi,
                options => options.JsonSerializerOptions.TypeInfoResolver = new MyDeliveryApiJsonTypeResolver());
    }
}

The custom API response builder can be tested using swagger:

image

@Migaroez
Copy link
Contributor

After the DeliveryApiJsonTypeResolver changes, all endpoints work as expected.
Behavior is extendable as described.
Tests pass and code looks good 💪

@Migaroez Migaroez merged commit a6a76d1 into release/13.3.0 Apr 16, 2024
14 checks passed
@Migaroez Migaroez deleted the v13/feature/extendable-api-response-builder branch April 16, 2024 10:03
Zeegaan added a commit that referenced this pull request May 22, 2024
* Updates JSON schema for Umbraco 10 with latest references for Forms and Deploy (#15918)

* Ported over #15928 changes for 13.3 RC (#16023)

* Ported over #15928 changes for 13.3 RC

* Use GetOrAdd()

* Lock dictionary initialization

---------

Co-authored-by: Jason Elkin <jasonelkin86@gmail.com>

* Make the API content response builder extendable (#16056)

* Make the API content response builder extendable

* DeliveryApiJsonTypeResolver needs to be extendable too

* bump rc to regular

* Bump to next minor

* Add blocks in RTE telemetry (#16104)

* Add blocks telemetry

* Use constants and update tests

* V13: Add property type information to telemetry (#16109)

* Add property type counts to telemetry

* Use constants and fix tests

* Update description

* V10: Fix for fallback file upload (#14892) (#15868)

* Fix for fallback file upload (#14892)

* Added check for file type

* Removed unneeded null checks and fixed tabs

* Cleaning

* Cleanups, cleanups, and removal of unneeded null checks

* Reverted removal of relationshipservice

* Revert null check removals (too risky)

---------

Co-authored-by: Ambert van Unen <AvanUnen@ilionx.com>
Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>

(cherry picked from commit 0b5d1f8)

* Fix up formatting

---------

Co-authored-by: Ambert van Unen <ambertvu@gmail.com>

* Implementors using Umbraco.Tests.Integration won't have to override GetLocalizedTextService

(cherry picked from commit b001668)
(cherry picked from commit 2bb56f1)

* Fix logic for retrieving lastKnownElement

(cherry picked from commit cae106b)

* bump version

* Bump version

* Bump version

* Since v13 properties can sometimes be of type IRichTextEditorIntermediateValue - this was unexpected in the XPath navigator code (#16121)

* Webhook log improvements (#16200)

* fix: include all headers in webhook log

* feat: return webhook log status from server

* feat: make webhook logs deep linkable

* feat: add webhook log pagination

* feat: improve webhook request/response body preview

* V13: Optimize custom MVC routing (#16218)

* Introduce EagerMatcherPolicy to conditionally bypass content routing

* Ensure that the candidate we disable dynamic routing for is valid

* Skip Umbraco endpoints

* Simplify logic a bit

* Move install logic to matcher

* Ensure that dynamic routing is still skipped when in upgrade state

* Fixup comments

* Reduce nesting a bit

* Don't show maintenance page when statically routed controllers are hít

* Remove excess check, since installer requests are statically routed

* V13: Optimize custom MVC routing (#16218)

* Introduce EagerMatcherPolicy to conditionally bypass content routing

* Ensure that the candidate we disable dynamic routing for is valid

* Skip Umbraco endpoints

* Simplify logic a bit

* Move install logic to matcher

* Ensure that dynamic routing is still skipped when in upgrade state

* Fixup comments

* Reduce nesting a bit

* Don't show maintenance page when statically routed controllers are hít

* Remove excess check, since installer requests are statically routed

(cherry picked from commit ba9ddd1)

* Property source level variation should only be applied when configured (#16270)

* Property source level variation should only be applied when configured (#16270)

(cherry picked from commit ab32bac)

* Merge pull request from GHSA-j74q-mv2c-rxmp

* Merge pull request from GHSA-j74q-mv2c-rxmp

* Merge pull request from GHSA-j74q-mv2c-rxmp

* Fix up after merge

* Remove obselete test

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>
Co-authored-by: Jason Elkin <jasonelkin86@gmail.com>
Co-authored-by: Sven Geusens <sge@umbraco.dk>
Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>
Co-authored-by: Ambert van Unen <ambertvu@gmail.com>
Co-authored-by: Lars-Erik <lars-erik@aabech.no>
Co-authored-by: Joshua Daniel Pratt Nielsen <jdpnielsen@gmail.com>
Co-authored-by: Bjarke Berg <mail@bergmania.dk>
Co-authored-by: Sebastiaan Janssen <sebastiaan@umbraco.com>
Co-authored-by: Rasmus John Pedersen <mail@rjp.dk>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants