# Umbraco Media Delivery API

In this guide, we'll use Umbraco as a *Digital Asset Management* system and integrate it with Ucommerce. We'll use the *Umbraco Media Delivery API* to retrieve the information we need.

Here's a look at the final result:

<figure><img src="https://3923258684-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FhumgDG3LdnIYeGE8nt71%2Fuploads%2FfCCu4FaCC62puV29hqw2%2FSk%C3%A6rmoptagelse%202024-11-20%20kl.%2009.59.09.gif?alt=media&#x26;token=5cec05b8-c36d-4a11-8b5b-957f54090a60" alt=""><figcaption><p>A look at how Umbraco can be leveraged as a DAM system for Ucommerce</p></figcaption></figure>

## Prerequisites

Before you get started, please make sure both [Umbraco templates](https://docs.umbraco.com/umbraco-cms/fundamentals/setup/install) and [Ucommerce templates](https://dev.ucommerce.net/readme/getting-started/ucommerce-templates) are installed.

## Setup Projects

### Umbraco

Create a new project using the [Umbraco template](https://docs.umbraco.com/umbraco-cms/fundamentals/setup/install/install-umbraco-with-templates). Make sure both delivery API and media is enabled in *appsettings.json*:

```
"DeliveryApi": {
  "Enabled": true,
  "PublicAccess": true,
  "Media": {
    "Enabled": true,
    "PublicAccess": true
  }
}
```

{% hint style="warning" %}
`PublicAccess` should not be `true` in a publicly available environment
{% endhint %}

When Umbraco is installed, take it for a spin and add some images to the Media library:

<figure><img src="https://3923258684-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FhumgDG3LdnIYeGE8nt71%2Fuploads%2FJmIMDjS4inAGLFKdCRo7%2Fimage.png?alt=media&#x26;token=c4d00a8a-36b9-4ff4-9dc5-536b5d609ff0" alt=""><figcaption><p>Media added to Umbraco</p></figcaption></figure>

### Ucommerce

Create a new project using the [Ucommerce Headless template](https://dev.ucommerce.net/readme/getting-started/standalone).

## Generate the Umbraco Client

To make Ucommerce communicate with Umbraco, we need a client to handle the REST calls to the Umbraco Delivery API. You can either code this yourself or use a tool to generate it. For this guide, we're going to use [Microsoft Kiota](https://learn.microsoft.com/en-us/openapi/kiota/overview) to generate it for us.

First, install Microsoft Kiota as a [.Net tool](https://learn.microsoft.com/en-us/openapi/kiota/install?tabs=bash#install-as-net-tool) or as a [VS Code extension](https://learn.microsoft.com/en-us/openapi/kiota/install?tabs=bash#install-the-visual-studio-code-extension).&#x20;

Second, use Kiota to generate a client from the Umbraco Delivery API Specification using either the command line (see below) or the VS Code plugin UI.

```bash
kiota generate 
    --openapi "https://localhost:{yourUmbracoPortNumber}/umbraco/swagger/delivery/swagger.json" \
    --language CSharp \
    --additional-data false \
    --class-name UmbracoClient \
    --namespace-name UcommerceTest.Integrations.Umbraco \
    --output ./Umbraco
```

{% hint style="info" %}
The Umbraco Delivery API Specification can be found on [https://localhost:{yourUmbracoPortNumber}/umbraco/swagger/delivery/swagger.json](https://dev.ucommerce.net/readme/integrations/https:/localhost:{yourUmbracoPortNumber}/umbraco/swagger/delivery/swagger.json)
{% endhint %}

{% hint style="info" %}
See [Optional parameters](https://learn.microsoft.com/en-us/openapi/kiota/using#optional-parameters-3) for more details on the command
{% endhint %}

Third, add the needed Kiota dependencies to the Ucommerce project.

{% hint style="info" %}
Run `kiota info --language CSharp` to get a list of dependencies needed.
{% endhint %}

The csproj file should look similar to this:

```html
<ItemGroup>
    <PackageReference Include="Microsoft.Kiota.Abstractions" Version="1.14.0" />
    <PackageReference Include="Microsoft.Kiota.Http.HttpClientLibrary" Version="1.14.0" />
    <PackageReference Include="Microsoft.Kiota.Serialization.Form" Version="1.14.0" />
    <PackageReference Include="Microsoft.Kiota.Serialization.Json" Version="1.14.0" />
    <PackageReference Include="Microsoft.Kiota.Serialization.Multipart" Version="1.14.0" />
    <PackageReference Include="Microsoft.Kiota.Serialization.Text" Version="1.14.0" />
    <PackageReference Include="Ucommerce.Web.BackOffice" Version="10.8.0" />
    <PackageReference Include="Ucommerce.Web.WebSite" Version="10.8.0" />
    <PackageReference Include="Ucommerce.Search.Elastic" Version="10.8.0" />
</ItemGroup>
```

## Implementing UmbracoImageService.cs

In the Ucommerce project, create a new class `UmbracoImageService` that inherits from `IImageService`. This class is responsible for mapping between the `UmbracoClient` created previously and Ucommerce. See [Images](https://dev.ucommerce.net/readme/extensions/change-service-behavior/images) for more details.

Below is a complete example:

{% code overflow="wrap" lineNumbers="true" fullWidth="false" %}

```csharp
using System.Collections.Immutable;
using Ucommerce.Web.Infrastructure;
using Ucommerce.Web.Infrastructure.Core;
using Ucommerce.Web.Infrastructure.Core.Models;
using UcommerceTest.Integrations.Umbraco;
using UcommerceTest.Integrations.Umbraco.Models;

namespace UcommerceTest;

public class UmbracoImageService(UmbracoClient kiotaClient, UmbracoSettings umbracoSettings) : IImageService
{
    public async Task<IImmutableList<Content>> Get(
        string? parentId = null, 
        int startAt = 0, 
        int limit = 30,
        CancellationToken? token = null)
    {
        parentId ??= "/";
        var items =
            await kiotaClient.Umbraco.Delivery.Api.V2.Media.GetAsync(
                request =>
                {
                    request.QueryParameters.Fetch = $"children:{parentId}";
                    request.QueryParameters.Skip = startAt;
                    request.QueryParameters.Take = limit;
                }, token.GetValueOrDefault());

        return items?.Items?
            .Select(item => MapMedia(parentId, item))
            .ToImmutableList() ?? ImmutableList<Content>.Empty;
    }

    public async Task<Content> GetById(string? id, CancellationToken token)
    {
        if (string.IsNullOrEmpty(id))
        {
            return ImageNotFound();
        }

        var item = await kiotaClient.Umbraco.Delivery.Api.V2.Media.Item[Guid.Parse(id)]
            .GetAsync(cancellationToken: token);

        return item is null ? ImageNotFound() : MapMedia(null, item);
    }
    
    public async Task<IImmutableList<Content>> GetByIds(
        IImmutableList<string> ids, 
        CancellationToken? token = null)
    {
        var guids = ids.Select(Guid.Parse).Cast<Guid?>().ToArray();
        var items = await kiotaClient.Umbraco.Delivery.Api.V2.Media.Items.GetAsync(
            request => request.QueryParameters.Id = guids, token.GetValueOrDefault()) ?? [];

        return items
            .Select(item => MapMedia(null, item))
            .ToImmutableList();
    }

    /// <summary>
    /// Maps the response model to a Content object
    /// </summary>
    private Content MapMedia(
        string? parentId, 
        ApiMediaWithCropsResponseModel responseModel) => new()
    {
        Name = responseModel.Name ?? "No name",
        NodeType = responseModel.MediaType!.Equals("Folder") ? Constants.ImagePicker.Folder : Constants.ImagePicker.Image,
        Url = string.IsNullOrWhiteSpace(responseModel.Url) ? "No URL" : CombineUris(umbracoSettings.BaseUrl, responseModel.Url),
        Id = responseModel.Id?.ToString() ?? "No ID",
        ParentId = parentId,
        Icon = responseModel.MediaType!.Equals("Folder") ? "icon-folder" : "icon-picture",
        ChildrenCount = null
    };

    /// <summary>
    /// Gives a default image when the request image could not be found
    /// </summary>
    private static Content ImageNotFound()
    {
        return new Content
        {
            Id = "image-not-found",
            Name = "image-not-found.png",
            Url = "ImageNotFoundImageURL",
            NodeType = Constants.ImagePicker.Image
        };
    }
    
    /// <summary>
    /// Combines two URIs into one making sure there is only one '/' between them
    /// </summary>
    private static string CombineUris(string uri1, string uri2)
    {
        uri1 = uri1.TrimEnd('/');
        uri2 = uri2.TrimStart('/');
        return $"{uri1}/{uri2}";
    }
}
```

{% endcode %}

Each method is straightforward: Get the necessary data from Umbraco using the `UmbracoClient`, map the data to fit Ucommerce, and return the result.

{% hint style="info" %}
Note that the `ChildrenCount` is set to `null`. This is because the Umbraco Delivery API does not contain any information on the number of children for a given node. See [Media](https://dev.ucommerce.net/readme/miscellaneous/media) for more details on what that means.
{% endhint %}

## Tying everything together

The last step needed is to set up `UmbracoClient` and `UmbracoImageService` in the Ucommerce project. Because the Umbraco Delivery API does not contain any information on the base URL, we'll have to set that up manually. As you might have noticed, we also need the base URL when mapping the URL as Umbraco returns only relative URLs.

### UmbracoSettings.cs

The settings object is a single line of code:&#x20;

```csharp
public record UmbracoSettings(string BaseUrl);
```

### UmbracoModuleExtensions.cs

To make the integration reusable, we suggest using an extension method for every module:

{% code overflow="wrap" lineNumbers="true" %}

```csharp
public static class UmbracoModuleExtensions
{
    public static IUcommerceBuilder AddUmbracoIntegration(
        this IUcommerceBuilder builder, 
        IConfiguration configuration)
    {
        
        var umbracoSettings = configuration.GetRequiredSection("Umbraco").Get<UmbracoSettings>();
        if(umbracoSettings is null)
        {
            throw new InvalidOperationException("Umbraco settings are missing");
        }
        builder.Services.AddSingleton(umbracoSettings);
        builder.Services.AddSingleton(new UmbracoClient(
            new HttpClientRequestAdapter(new AnonymousAuthenticationProvider())
            {
                BaseUrl = umbracoSettings.BaseUrl
            }));
        builder.Services.AddUnique<IImageService, UmbracoImageService>();
        return builder;
    }
}
```

{% endcode %}

{% hint style="warning" %}
Remember to use a different authentication provider when your Umbraco instance is not publicly accessible.
{% endhint %}

For the configuration to work, add the base URL to the following path in *appsettings.json*: `Umbraco:BaseUrl`:

```json
"Umbraco":
{
  "BaseUrl": "https://localhost:{yourUmbracoPortNumber}"
}
```

The final step is to call `AddUmbracoIntegration` in *Program.cs*:

{% code overflow="wrap" lineNumbers="true" %}

```csharp
// Set up services
builder.Services.AddUcommerce(builder.Configuration)
    ...
    .AddUmbracoIntegration(builder.Configuration)
    .Build();
```

{% endcode %}

**Congratulations! Ucommerce is now able to get images from Umbraco!**

## See it in action

Now, try taking the Ucommerce project for a spin and select an image for a product.
