Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 15 additions & 71 deletions 17/umbraco-engage/developers/ab-testing/csharp-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,75 +10,14 @@ description: >-

You can retrieve the active A/B test variants for a visitor in different ways depending on your specific scenario:

* `IAbTestingVisitorService.GetVisitorAbTestVariants(visitorExternalId, pageId, culture, contentTypeId)`
* `IAbTestingVisitorService.GetVisitorAbTestVariants(visitorExternalId, contentId, culture, contentTypeId)`
* Namespace: `Umbraco.Engage.Infrastructure.AbTesting.Services.Interfaces`
* Retrieves active A/B test variants on a specific page, without requiring a request context.
* Retrieves active A/B test variants on a specific page using numeric IDs, without requiring a request context.
* The visitor external id can be retrieved using `IAnalyticsVisitorExternalIdHandler.GetExternalId()`
* `IAbTestVisitorToVariantManager.GetActiveVisitorVariants(visitorExternalId)`
* Namespace: `Umbraco.Engage.Infrastructure.AbTesting`
* Retrieves _all_ active A/B test variants for the given visitor throughout the website.
* The visitor external id can be retrieved using `IAnalyticsVisitorExternalIdHandler.GetExternalId()`

### Example - Getting the A/B test variants for the current visitor

This example demonstrates how to create a service that retrieves the active A/B test variants for a specific visitor, content, and culture variation. The service wraps the `IAbTestingVisitorService.GetVisitorAbTestVariants()` method, using the current HttpContext and UmbracoContext to obtain the visitor ID and requested content.

```cs
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Web;
using Umbraco.Engage.Infrastructure.AbTesting.Models;
using Umbraco.Engage.Infrastructure.AbTesting.Services.Interfaces;
using Umbraco.Engage.Infrastructure.Analytics.Collection.Visitor;

namespace Umbraco.Example;

public class ExampleService
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly IAnalyticsVisitorExternalIdHandler _externalIdHandler;
private readonly IAbTestingVisitorService _abTestingVisitorService;

public ExampleService(
IHttpContextAccessor httpContextAccessor,
IUmbracoContextAccessor umbracoContextAccessor,
IAnalyticsVisitorExternalIdHandler externalIdHandler,
IAbTestingVisitorService abTestingVisitorService)
{
_httpContextAccessor = httpContextAccessor;
_umbracoContextAccessor = umbracoContextAccessor;
_externalIdHandler = externalIdHandler ;
_abTestingVisitorService = abTestingVisitorService;
}

/// <summary>
/// Gets the active A/B test variants for the current visitor.
/// </summary>
/// <returns>Active <see cref="AbTestVariant"/>s for the visitor, or an empty list if unavailable.</returns>
public IEnumerable<AbTestVariant> GetCurrentVisitorActiveAbTestVariants()
{
if (_httpContextAccessor?.HttpContext is not HttpContext httpCtx ||
_externalIdHandler.GetExternalId(httpCtx) is not Guid externalId)
return [];

if (!_umbracoContextAccessor.TryGetUmbracoContext(out var umbCtx) ||
umbCtx.PublishedRequest?.Culture is not string culture ||
umbCtx.PublishedRequest?.PublishedContent is not IPublishedContent content)
return [];

return _abTestingVisitorService.GetVisitorAbTestVariants(externalId, content.Id, culture, content.ContentType.Id);
}
}
```

## Retrieving Active A/B test variants

You can retrieve the active A/B test variants for a visitor in different ways depending on your specific scenario:

* `IAbTestingVisitorService.GetVisitorAbTestVariants(visitorExternalId, pageId, culture, contentTypeId)`
* `IAbTestingVisitorService.GetVisitorAbTestVariants(visitorExternalId, contentKey, contentTypeKey, culture)`
* Namespace: `Umbraco.Engage.Infrastructure.AbTesting.Services.Interfaces`
* Retrieves active A/B test variants on a specific page, without requiring a request context.
* The visitor external id can be retrieved using `IAnalyticsVisitorExternalIdHandler.GetExternalId()`
* Retrieves active A/B test variants on a specific page using GUID keys, without requiring a request context.
* Preferred for new implementations as it uses Umbraco's GUID-based identifiers.
* `IAbTestVisitorToVariantManager.GetActiveVisitorVariants(visitorExternalId)`
* Namespace: `Umbraco.Engage.Infrastructure.AbTesting`
* Retrieves _all_ active A/B test variants for the given visitor throughout the website.
Expand Down Expand Up @@ -117,21 +56,26 @@ public class ExampleService
}

/// <summary>
/// Gets the active A/B test variants for the current visitor.
/// Gets the active A/B test variants for the current visitor using GUID keys (recommended).
/// </summary>
/// <returns>Active <see cref="AbTestVariant"/>s for the visitor, or an empty list if unavailable.</returns>
public IEnumerable<AbTestVariant> GetCurrentVisitorActiveAbTestVariants()
{
if (_httpContextAccessor?.HttpContext is not HttpContext httpCtx ||
_externalIdHandler.GetExternalId(httpCtx) is not Guid externalId)
return [];
if (!_umbracoContextAccessor.TryGetUmbracoContext(out var umbCtx) ||
umbCtx.PublishedRequest?.Culture is not string culture ||

if (!_umbracoContextAccessor.TryGetUmbracoContext(out var umbCtx) ||
umbCtx.PublishedRequest?.Culture is not string culture ||
umbCtx.PublishedRequest?.PublishedContent is not IPublishedContent content)
return [];

return _abTestingVisitorService.GetVisitorAbTestVariants(externalId, content.Id, culture, content.ContentType.Id);
// Using GUID-based overload (recommended)
return _abTestingVisitorService.GetVisitorAbTestVariants(
externalId,
content.Key,
content.ContentType.Key,
culture);
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -15,43 +15,43 @@ In this case, you may need to provide a custom implementation of the `IHttpConte
The default extractor looks like this:

```csharp
using System.Web;
using Umbraco.Engage.Business.Analytics.Collection.Extractors;
using Microsoft.AspNetCore.Http;
using Umbraco.Engage.Infrastructure.Analytics.Collection.Extractors;

public string ExtractIpAddress(HttpContextBase context)
public string? ExtractIpAddress(HttpContext context)
{
if (context?.Request?.ServerVariables["X-Forwarded-For"] is string ipAddresses)
if (context?.Request?.Headers["X-Forwarded-For"].FirstOrDefault() is string ipAddresses)
{
var ipAddress = ipAddresses.Split(',')[0].Trim();
if (System.Net.IPAddress.TryParse(ipAddress, out _)) return ipAddress;
}
return context?.Request?.UserHostAddress;
return context?.Connection?.RemoteIpAddress?.ToString();
}
```

To override this behavior, implement your own `IHttpContextIpAddressExtractor` and instruct Umbraco to use your extractor instead of the default extractor:

```cs
using Umbraco.Engage.Business.Analytics.Collection.Extractors;
using Umbraco.Core.Composing;
using Umbraco.Core;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Engage.Infrastructure.Analytics.Collection.Extractors;

[ComposeAfter(typeof(Umbraco.Engage.Business.Analytics.Collection.Extractors.AnalyticsExtractorsComposer))]
public class CustomIpExtractorUserComposer : IUserComposer
[ComposeAfter(typeof(Umbraco.Engage.Infrastructure.Analytics.Collection.Extractors.AnalyticsExtractorsComposer))]
public class CustomIpExtractorComposer : IComposer
{
public void Compose(Composition composition)
public void Compose(IUmbracoBuilder builder)
{
composition.RegisterUnique<IHttpContextIpAddressExtractor, MyIpAddressExtractor>();
builder.Services.AddUnique<IHttpContextIpAddressExtractor, MyIpAddressExtractor>();
}
}
```

{% hint style="info" %}
It is important that your `UserComposer` adjusts the service registration **after** Umbraco Engage has initialized.
It is important that your `Composer` adjusts the service registration **after** Umbraco Engage has initialized.
{% endhint %}

This can be enforced using the `ComposeAfterAttribute`. Failing to add this attribute may result in Umbraco running your IUserComposer before the Umbraco Engage composer, causing your changes to be overwritten.
This can be enforced using the `ComposeAfterAttribute`. Failing to add this attribute may result in Umbraco running your Composer before the Umbraco Engage composer, causing your changes to be overwritten.

Additionally, ensure you use `RegisterUnique<...>()` instead of `Register<...>()`. While you can use Register when multiple implementations of a single service exist, in this case, you want your own extractor to be resolved exclusively. Therefore, RegisterUnique will overwrite the Umbraco Engage extractor.
Additionally, ensure you use `AddUnique<...>()` instead of `AddSingleton<...>()`. While you can use AddSingleton when multiple implementations of a single service exist, in this case, you want your own extractor to be resolved exclusively. Therefore, AddUnique will overwrite the Umbraco Engage extractor.

After implementing both classes and running your project, your extractor should be called to resolve IP addresses. You can verify the output of your extractor by inspecting the `umbracoEngageAnalyticsIpAddress` database table. The last portion of the IP address may be anonymized (set to 0) if this option is enabled in the Umbraco Engage configuration file.
40 changes: 22 additions & 18 deletions 17/umbraco-engage/developers/analytics/location.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Once you have a service that can provide localization information, you must inte

Implement the following interface:

`Umbraco.Engage.Business.Analytics.Processing.Extractors.IRawPageviewLocationExtractor`
`Umbraco.Engage.Infrastructure.Analytics.Processing.Extractors.IRawPageviewLocationExtractor`

This interface allows information about localization for a pageview, defined as a single visitor's visit to a specific point in time. The page view contains the `IpAddress` property that can be used for Geo IP lookup.

Expand All @@ -25,8 +25,10 @@ This interface allows information about localization for a pageview, defined as

{% code overflow="wrap" %}
```cs
using Umbraco.Engage.Business.Analytics.Processed;
public class GeoIpLocation : ILocation {
using Umbraco.Engage.Infrastructure.Analytics.Processed;

public class GeoIpLocation : ILocation
{
public string Country { get; set; }
public string County { get; set; }
public string Province { get; set; }
Expand All @@ -40,27 +42,29 @@ public class GeoIpLocation : ILocation {

{% code overflow="wrap" %}
```cs
using Umbraco.Engage.Business.Analytics.Processing.Extractors;
using Umbraco.Engage.Infrastructure.Analytics.Processing.Extractors;

public class MyCustomLocationExtractor : IRawPageviewLocationExtractor
{
public ILocation Extract(IRawPageview rawPageview)
public ILocation? Extract(IRawPageview rawPageview)
{
if (!IPAddress.TryParse(rawPageview?.IpAddress, out var ipAddress) || IPAddress.IsLoopback(ipAddress)) return null;

if (!IPAddress.TryParse(rawPageview?.IpAddress, out var ipAddress) || IPAddress.IsLoopback(ipAddress))
return null;

string country, county, province, city;

//...
// Perform your own GEO IP lookup here
// ...
var location = new GeoIpLocation
{
Country = country,
County = county,
Province = province,
City = city
};

return location;
}
}
Expand All @@ -69,23 +73,23 @@ public class MyCustomLocationExtractor : IRawPageviewLocationExtractor

4. Let the IoC container know to use your implementation for the `IRawPageviewLocationExtractor`.

Umbraco Engage has a default implementation of this service, which only returns null. This default service is registered using Umbraco's `RegisterUnique` method.
Umbraco Engage has a default implementation of this service, which only returns null. This default service is registered using Umbraco's `AddUnique` method.

5. Override this service by calling `RegisterUnique` **after** the `UmbracoEngageApplicationComposer`.
5. Override this service by calling `AddUnique` **after** the `UmbracoEngageApplicationComposer`.

{% code overflow="wrap" %}
```cs
using Umbraco.Engage.Business.Analytics.Processing.Extractors;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Engage.Common.Composing;
using Umbraco.Core;
using Umbraco.Core.Composing;

using Umbraco.Engage.Infrastructure.Analytics.Processing.Extractors;

[ComposeAfter(typeof(UmbracoEngageApplicationComposer))]
public class UmbracoEngageComposer: IComposer
public class UmbracoEngageComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.services.AddUnique<IRawPageviewLocationExtractor, MyCustomLocationExtractor>();
builder.Services.AddUnique<IRawPageviewLocationExtractor, MyCustomLocationExtractor>();
}
}
```
Expand Down
6 changes: 3 additions & 3 deletions 17/umbraco-engage/developers/headless/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
---
description: >-
Discover how to integrate Umbraco.Engage.Headless package with Umbraco 12.0+
Discover how to integrate `Umbraco.Engage.Headless` package with Umbraco.
for a Content Delivery API.
---

# Headless

Umbraco Engage offers the **Umbraco.Engage.Headless** package for seamless integration with Umbraco 12.0 and later. This package enables access to the Headless Content Delivery API, enabling personalized content, A/B tests, and segmentation.
Umbraco Engage offers the **Umbraco.Engage.Headless** package for seamless integration with Umbraco. This package enables access to the Headless Content Delivery API, enabling personalized content, A/B tests, and segmentation.

{% hint style="info" %}
All Engage features are supported except:
Expand All @@ -28,7 +28,7 @@ All Engage features are supported except:

To install Umbraco.Engage.Headless, ensure the following prerequisites:

* Umbraco v13 is required to integrate with the [Content Delivery API](https://docs.umbraco.com/umbraco-cms/reference/content-delivery-api).
* The latest version of Umbraco is required to integrate with the [Content Delivery API](https://docs.umbraco.com/umbraco-cms/reference/content-delivery-api).
* Enable the [Umbraco Content Delivery API](https://docs.umbraco.com/umbraco-cms/reference/content-delivery-api#enable-the-content-delivery-api) by adding the following configuration setting in the `appsettings.json` file:

```json
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ To track a page view, send a POST request to:
* `/umbraco/engage/api/v1/analytics/pageview/trackpageview/client`

* Required: `url` property of the page that a user has visited in the site
* Optional: `reffererUrl` can be set to inform Umbraco Engage where the user came from.
* Optional: `referrerUrl` can be set to inform Umbraco Engage where the user came from.

* `/umbraco/engage/api/v1/analytics/pageview/trackpageview/server`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ description: >-

You could choose to give visitors control over these settings through a cookie bar on your site.

To do this you have to create an implementation of the `Umbraco.Engage.Business.Permissions.ModulePermissions.IModulePermissions` interface and override our default implementation.
To do this you have to create an implementation of the `Umbraco.Engage.Infrastructure.Permissions.ModulePermissions.IModulePermissions` interface and override our default implementation.

This interface defines 3 methods that you will have to implement:

Expand All @@ -21,7 +21,7 @@ This interface defines 3 methods that you will have to implement:
/// </summary>
/// <param name="context">Context of the request</param>
/// <returns>True if A/B testing is allowed, otherwise false.</returns>
bool AbTestingIsAllowed(HttpContextBase context);
bool AbTestingIsAllowed(HttpContext context);

/// <summary>
/// Indicates if Analytics is allowed for the given request context.
Expand All @@ -33,37 +33,37 @@ bool AbTestingIsAllowed(HttpContextBase context);
/// </summary>
/// <param name="context">Context of the request</param>
/// <returns>True if Analytics is allowed, otherwise false.</returns>
bool AnalyticsIsAllowed(HttpContextBase context);
bool AnalyticsIsAllowed(HttpContext context);

/// <summary>
/// Indicates if Personalization testing is allowed for the given request context.
/// If false, the visitor will not see any personalized content.
/// </summary>
/// <param name="context">Context of the request</param>
/// <returns>True if Personalization is allowed, otherwise false.</returns>
bool PersonalizationIsAllowed(HttpContextBase context);
bool PersonalizationIsAllowed(HttpContext context);
```
{% endcode %}

Using these methods you can control per visitor whether or not the modules are active. Your implementation will need to be registered with Umbraco using the `RegisterUnique()` method, overriding the default implementation which enables all modules all the time. Make sure your composer runs after the Umbraco Engage composer by using the `[ComposeAfter]` attribute.
Using these methods you can control per visitor whether or not the modules are active. Your implementation will need to be registered with Umbraco using the `AddUnique()` method, overriding the default implementation which enables all modules all the time. Make sure your composer runs after the Umbraco Engage composer by using the `[ComposeAfter]` attribute.

It could look something like this:

{% code overflow="wrap" %}
```csharp
using Umbraco.Engage.Infrastructure.Permissions.ModulePermissions;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Engage.Common.Composing;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Engage.Infrastructure.Permissions.ModulePermissions;

namespace YourNamespace
namespace YourNamespace
{
[ComposeAfter(typeof(UmbracoEngageApplicationComposer))]
public class YourComposer : IComposer
{
public void Compose(Composition composition)
public void Compose(IUmbracoBuilder builder)
{
composition.RegisterUnique<IModulePermissions, YourCustomModulePermissions>();
builder.Services.AddUnique<IModulePermissions, YourCustomModulePermissions>();
}
}
}
Expand Down
Loading