using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Net.Http.Json;
using IdentityModel.Client;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<ApiAccessTokenService>();
builder.Services.AddTransient<ApiConsumerService>();
using IHost host = builder.Build();
var consumer = host.Services.GetRequiredService<ApiConsumerService>();
await consumer.ExecuteAsync();
public static class Constants
{
// the base URL of the Umbraco site - change this to fit your custom setup
public static string Host => "https://localhost:44391";
}
// This is the API consumer, which will be listing the first few available content items - including protected ones.
public class ApiConsumerService
{
private readonly ApiAccessTokenService _apiAccessTokenService;
public ApiConsumerService(ApiAccessTokenService apiAccessTokenService)
=> _apiAccessTokenService = apiAccessTokenService;
public async Task ExecuteAsync()
{
// get an access token from the access token service.
var accessToken = _apiAccessTokenService.GetAccessToken();
if (accessToken is null)
{
Console.WriteLine("Could not get an access token, aborting.");
return;
}
var client = new HttpClient();
client.SetBearerToken(accessToken);
// fetch [pageSize] content items from the "all content" Delivery API endpoint.
const int pageSize = 5;
var apiResponse = await client.GetAsync($"{Constants.Host}/umbraco/delivery/api/v2/content?take={pageSize}");
var apiContentResponse = await apiResponse
.EnsureSuccessStatusCode()
.Content
.ReadFromJsonAsync<ApiContentResponse>();
if (apiContentResponse is null)
{
Console.WriteLine("Could not parse content from the API response.");
return;
}
Console.WriteLine($"There are {apiContentResponse.Total} items in total - listing the first {pageSize} items.");
foreach (var item in apiContentResponse.Items)
{
Console.WriteLine($"- {item.Name} ({item.Id})");
}
}
}
// This service ensures the reuse of access tokens for the duration of their lifetime.
// It must be registered as a singleton service to work properly.
public class ApiAccessTokenService
{
private readonly Lock _lock = new();
private string? _accessToken;
private DateTime _accessTokenExpiry = DateTime.MinValue;
public string? GetAccessToken()
{
if (_accessTokenExpiry > DateTime.UtcNow)
{
// we already have a token, reuse it.
return _accessToken;
}
using (_lock.EnterScope())
{
if (_accessTokenExpiry > DateTime.UtcNow)
{
// another thread fetched a new token before this thread entered the lock, reuse it.
return _accessToken;
}
var client = new HttpClient();
var tokenResponse = client.RequestClientCredentialsTokenAsync(
new ClientCredentialsTokenRequest
{
Address = $"{Constants.Host}/umbraco/delivery/api/v1/security/member/token",
ClientId = "umbraco-member-my-client",
ClientSecret = "my-client-secret"
}
)
// cannot await inside a using.
.GetAwaiter().GetResult();
if (tokenResponse.IsError || tokenResponse.AccessToken is null)
{
Console.WriteLine($"Error obtaining a token: {tokenResponse.ErrorDescription}");
return null;
}
_accessToken = tokenResponse.AccessToken;
_accessTokenExpiry = DateTime.UtcNow.AddSeconds(tokenResponse.ExpiresIn - 20);
return tokenResponse.AccessToken;
}
}
}
public class ApiContentResponse
{
public required int Total { get; set; }
public required ApiContentItemResponse[] Items { get; set; }
}
public class ApiContentItemResponse
{
public required Guid Id { get; set; }
public required string Name { get; set; }
}