Rendering/Updating segmented content blocks using API Calls
Hi uMarketingSuite Team & Others,
For one of our clients we're currently making use of uMarketingSuite in combination with Content Blocks. Our first use-case is to show specific Content Blocks on a page, based on the segment the user fits in, on page load. This seems to work just fine out of the box with uMarketingSuite!
The second use-case we still have some questions about, and which I would love to hear what options are available for, would be to dynamically update the Content Blocks rendered on a page, depending on the buttons/actions that user does on that same place, without having to reload the page using a PostBack.
Think of an API endpoint that returns the rendered HTML of a single (or complete) Content Block on a page, but personalized depending on the segment the user is currently in (either via a cookie or a parameter of some sorts?). This HTML response could then be used to update the DOM itself. Is something like this implemented and/or possible to create using the existing API's?
There is no dedicated ContentBlocks API to return rendered HTML but there are plenty of ways to achieve this in MVC. You could simply return PartialView("path/to/partial.cshtml", CurrentPage.ContentBlocks) from a SurfaceController action for example, where the PartialView only contains a @Html.RenderContentBlocks(...). Please note we provide some convenience extension methods to render only a subset of blocks rather than everything, see here.
So as long as you make sure the user is part of the correct segment before you render the ContentBlocks, the HTML returned from the controller is already personalized and should work the same your working scenario on pageload.
I suppose it all depends on what happens when the user presses the button and if the user segment is immediately updated so it will be used in uMarketingSuite.ContentBlocks to render the correct block. But please first try this as it is the easiest for you.
If that somehow does not work, please explain here exactly how you guys are assigning the visitor to a new segment when the button is pressed, perhaps we can help to make that work better.
Another solution is to provide your own implementation of Perplex.ContentBlocks.Variants.IContentBlockVariantSelector which is in charge of selecting the variant for each ContentBlock for the current request. This is also the class that uMarketingSuite.ContentBlocks replaces to implement the connection with UMS.
So in your case you could probably inherit from our class (MarketingSuiteContentBlockVariantSelector) and implement the SelectVariant method based on some querystring value that you mentioned. This would look something like below (untested code). You probably need to run your composer where you .RegisterUnique() this instance afterMarketingSuiteContentBlocksComposer using the [ComposeAfter] attribute. FYI the uMarketingSuite block variants use the alias ums-segment-<UMS_SEGMENT_ID> so that's how you can find them in block.Variants.
public class CorneContentBlockVariantSelector : MarketingSuiteContentBlockVariantSelector
{
private readonly IHttpContextAccessor _httpContextAccessor;
public CorneContentBlockVariantSelector(
IAnalyticsStateProvider analyticsStateProvider,
IHttpContextAccessor httpContextAccessor)
: base(analyticsStateProvider)
{
_httpContextAccessor = httpContextAccessor;
}
public new ContentBlockVariantModelValue SelectVariant(ContentBlockModelValue block, IPublishedElement content, bool preview)
{
if(_httpContextAccessor.HttpContext is HttpContext httpCtx &&
httpCtx.Request?.QueryString["SOME_PARAMETER"] is string someValue)
{
// Select a variant based on whatever you sent in the querystring here.
var someVariant = block.Variants.FirstOrDefault(v => v.Alias == "...");
return someVariant;
}
else
{
// Default UMS behavior
return base.SelectVariant(block, content, preview);;
}
}
}
Hope this helps, if something does not work let us know here.
After a nice teams session with Daniël. We needed to change example code to the following. Now we have full control on the variant we want to send to the client application
public class CorneContentBlockVariantSelector : MarketingSuiteContentBlockVariantSelector, IContentBlockVariantSelector
{
#region Fields
private readonly IHttpContextAccessor _httpContextAccessor;
#endregion
#region Constructors
public CustomMarketingSuiteContentBlockVariantSelector(
IAnalyticsStateProvider analyticsStateProvider,
IHttpContextAccessor httpContextAccessor)
: base(analyticsStateProvider)
{
_httpContextAccessor = httpContextAccessor;
}
#endregion
#region Methods
ContentBlockVariantModelValue IContentBlockVariantSelector.SelectVariant(ContentBlockModelValue block, IPublishedElement content, bool preview)
{
if (preview)
return null;
if (_httpContextAccessor.HttpContext is HttpContext httpCtx &&
httpCtx.Request?.QueryString["segment"] is string segment)
{
// Select a variant based on whatever you sent in the querystring here.
var someVariant = block.Variants.FirstOrDefault(v => v.Alias == segment);
if (someVariant != null)
return someVariant;
}
return base.SelectVariant(block, content, preview);
}
#endregion
}
And for the composer
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
[ComposeAfter(typeof(MarketingSuiteContentBlocksComposer))]
public class CustomMarketingSuiteContentBlocksComposer : IUserComposer
{
#region Methods
public void Compose(Composition composition)
{
composition.RegisterUnique<IContentBlockVariantSelector, CustomMarketingSuiteContentBlockVariantSelector>();
}
#endregion
}
Thank you for your very detailed response! We haven't chosen what happens when the user presses the button just yet, as we're still in the process of learning the in's-and-out's of uMarketingSuite!
I will definitely try out your given examples, as they sound very promising!
An issue that I am currently running into, related to this, has to do with setting the Segments programmatically. Perhaps you have an idea what I am doing wrong;
I have tried to setup both a setup with a Customer Journey, and with Personas, but let's go with Persona's for now; Programmatically I am giving the current visitor a score for a specific persona, and in Umbraco I have configured a specific segment to correspond to this persona. However, no matter what I do, it doesn't actually seem to set the segment, nor the Persona to the current Visitor. Any idea's what I am missing? I am using the following snippet:
var exernalId = _visitorContext.GetVisitorExternalId();
var personaGroup = _personaGroupRepository.GetAll().First(x => x.Title == "Default");
var persona = personaGroup.Personas.FirstOrDefault(x => x.Title == "Buyer");
_personaService.ScorePersona(persona.Id, 10);
var visitorPersonas = _personaService.GetPersonasForVisitor(exernalId.Value);
In this case, visitorPersonas is always an empty list, and the segmented Content Blocks that I am using on the view that gets rendered always returns the Default non-segmented version. Any pointers would be much appreciated 😄
I'm sorry to say it seems you found a bug here, obviously what you do should work. I see the ScorePersona method does not properly update the in-memory cached profile we have of a visitor. I will add this to our internal bug tracking system, not exactly sure of the timeline for this to be fixed but should not be more than a few weeks.
The scores are being recorded now but since the cache is not updated the segment rule is not being activated either since it checks the cache only. Of course we also record this in the database but that's done behind the scenes at some interval (I believe every minute or so) so this is not real-time unlike the cache that is updated immediately. If you restart the site though it should show up properly, at least it did when I tested this locally.
Also take into account there is a certain threshold of points to be reached before a persona becomes active. So it is possible the threshold was simply not reached yet. You can configure this threshold in the Advanced Settings of a Persona group.
Note you can use the Cockpit to see the live scores of each persona, so make sure to enable it and check the Personalization tab. It will show the scores per persona and the threshold of points needed.
In the meantime there is a workaround available that worked for me, and should also work for you in this scenario. When calling ScorePersona, you can manually update the cache we keep for a visitor that stores among other things the points for every persona. Of course this is all unfortunate and this should have simply worked
However, personaService.GetPersonasForVisitor(externalId.Value) will not be updated immediately since it checks the DB, but the segment rule does not use this. It will look in the profile so should work fine. If you need the active personas you can also just check profile.ActivePersonas instead.
By the way the same is likely true for Customer Journey, same workaround should apply.
Sorry for the inconvenience!
I was testing this code in a view so excuse all the Current.Factory.GetInstance calls ;-)
var visitorContext = Current.Factory.GetInstance<uMarketingSuite.Business.Analytics.Common.IAnalyticsVisitorContext>();
var personaService = Current.Factory.GetInstance<uMarketingSuite.Business.Personalization.Services.IPersonaService>();
var externalId = visitorContext.GetVisitorExternalId();
var score = 100;
var personaId = 1;
// Inject these
var profileAccessor = Current.Factory.GetInstance<uMarketingSuite.Business.Personalization.PersonalizationProfile.IPersonalizationVisitorProfileAccessor>();
var profileService = Current.Factory.GetInstance<uMarketingSuite.Business.Personalization.PersonalizationProfile.IPersonalizationProfileService>();
var profile = profileAccessor.EnsureVisitorProfile(externalId.Value);
personaService.ScorePersona(personaId, score);
// After ScorePersona simply update profile by hand:
if (!profile.PersonaScores.ContainsKey(personaId))
{
profile.PersonaScores[personaId] = 0;
}
profile.PersonaScores[personaId] += score;
// And call recalculate method to update ActivePersonas too
profileService.RecalculateVisitorProfilePersonaScores(profile);
// This one will not be updated as it queries the database directly so it will be a bit delayed
var visitorPersonas = personaService.GetPersonasForVisitor(externalId.Value);
Glad I could help you find a bug haha! For now your workaround seems to work perfectly, so for the time being we will make use of that instead! Thank you very much!
Could you keep me posted for when the bug gets fixed? You can keep in touch if you'd like via [email protected], or just via this thread! 😄
I'll make sure to let you know, probably via an update here so anyone else reading this topic is also aware which version contains the fix. It is indeed important to remove the workaround when you update to that version otherwise the score will be added twice to the cached profile.
Update: this bug has been fixed in our development version and will be part of the next UMS release. The next release is not scheduled yet, but should be 1.17.0. If another patch release is done on 1.16 instead it will be part of 1.16.2 so keep an eye out for either of them.
When the version containing the fix is released I will post another update here.
And to be clear; 1.16.1 does not contain the fix yet.
Rendering/Updating segmented content blocks using API Calls
Hi uMarketingSuite Team & Others,
For one of our clients we're currently making use of uMarketingSuite in combination with Content Blocks. Our first use-case is to show specific Content Blocks on a page, based on the segment the user fits in, on page load. This seems to work just fine out of the box with uMarketingSuite!
The second use-case we still have some questions about, and which I would love to hear what options are available for, would be to dynamically update the Content Blocks rendered on a page, depending on the buttons/actions that user does on that same place, without having to reload the page using a PostBack.
Think of an API endpoint that returns the rendered HTML of a single (or complete) Content Block on a page, but personalized depending on the segment the user is currently in (either via a cookie or a parameter of some sorts?). This HTML response could then be used to update the DOM itself. Is something like this implemented and/or possible to create using the existing API's?
Kind regards,
Corné Hoskam
Hi Corné,
There is no dedicated ContentBlocks API to return rendered HTML but there are plenty of ways to achieve this in MVC. You could simply
return PartialView("path/to/partial.cshtml", CurrentPage.ContentBlocks)
from a SurfaceController action for example, where the PartialView only contains a@Html.RenderContentBlocks(...)
. Please note we provide some convenience extension methods to render only a subset of blocks rather than everything, see here.So as long as you make sure the user is part of the correct segment before you render the ContentBlocks, the HTML returned from the controller is already personalized and should work the same your working scenario on pageload.
I suppose it all depends on what happens when the user presses the button and if the user segment is immediately updated so it will be used in uMarketingSuite.ContentBlocks to render the correct block. But please first try this as it is the easiest for you.
If that somehow does not work, please explain here exactly how you guys are assigning the visitor to a new segment when the button is pressed, perhaps we can help to make that work better.
Another solution is to provide your own implementation of Perplex.ContentBlocks.Variants.IContentBlockVariantSelector which is in charge of selecting the variant for each ContentBlock for the current request. This is also the class that uMarketingSuite.ContentBlocks replaces to implement the connection with UMS.
So in your case you could probably inherit from our class (
MarketingSuiteContentBlockVariantSelector
) and implement theSelectVariant
method based on some querystring value that you mentioned. This would look something like below (untested code). You probably need to run your composer where you .RegisterUnique() this instance afterMarketingSuiteContentBlocksComposer
using the [ComposeAfter] attribute. FYI the uMarketingSuite block variants use the aliasums-segment-<UMS_SEGMENT_ID>
so that's how you can find them inblock.Variants
.Hope this helps, if something does not work let us know here.
Regards, Daniël
After a nice teams session with Daniël. We needed to change example code to the following. Now we have full control on the variant we want to send to the client application
And for the composer
Hi Daniël,
Thank you for your very detailed response! We haven't chosen what happens when the user presses the button just yet, as we're still in the process of learning the in's-and-out's of uMarketingSuite!
I will definitely try out your given examples, as they sound very promising!
An issue that I am currently running into, related to this, has to do with setting the Segments programmatically. Perhaps you have an idea what I am doing wrong;
I have tried to setup both a setup with a Customer Journey, and with Personas, but let's go with Persona's for now; Programmatically I am giving the current visitor a score for a specific persona, and in Umbraco I have configured a specific segment to correspond to this persona. However, no matter what I do, it doesn't actually seem to set the segment, nor the Persona to the current Visitor. Any idea's what I am missing? I am using the following snippet:
In this case, visitorPersonas is always an empty list, and the segmented Content Blocks that I am using on the view that gets rendered always returns the Default non-segmented version. Any pointers would be much appreciated 😄
Hi Corné,
I'm sorry to say it seems you found a bug here, obviously what you do should work. I see the ScorePersona method does not properly update the in-memory cached profile we have of a visitor. I will add this to our internal bug tracking system, not exactly sure of the timeline for this to be fixed but should not be more than a few weeks.
The scores are being recorded now but since the cache is not updated the segment rule is not being activated either since it checks the cache only. Of course we also record this in the database but that's done behind the scenes at some interval (I believe every minute or so) so this is not real-time unlike the cache that is updated immediately. If you restart the site though it should show up properly, at least it did when I tested this locally.
Also take into account there is a certain threshold of points to be reached before a persona becomes active. So it is possible the threshold was simply not reached yet. You can configure this threshold in the Advanced Settings of a Persona group.
Note you can use the Cockpit to see the live scores of each persona, so make sure to enable it and check the Personalization tab. It will show the scores per persona and the threshold of points needed.
In the meantime there is a workaround available that worked for me, and should also work for you in this scenario. When calling ScorePersona, you can manually update the cache we keep for a visitor that stores among other things the points for every persona. Of course this is all unfortunate and this should have simply worked
However, personaService.GetPersonasForVisitor(externalId.Value) will not be updated immediately since it checks the DB, but the segment rule does not use this. It will look in the profile so should work fine. If you need the active personas you can also just check profile.ActivePersonas instead.
By the way the same is likely true for Customer Journey, same workaround should apply.
Sorry for the inconvenience!
I was testing this code in a view so excuse all the Current.Factory.GetInstance calls ;-)
Hi Daniël,
Glad I could help you find a bug haha! For now your workaround seems to work perfectly, so for the time being we will make use of that instead! Thank you very much!
Could you keep me posted for when the bug gets fixed? You can keep in touch if you'd like via [email protected], or just via this thread! 😄
Thank you kindly for the excellent support!
Kind regards,
Corné
I'll make sure to let you know, probably via an update here so anyone else reading this topic is also aware which version contains the fix. It is indeed important to remove the workaround when you update to that version otherwise the score will be added twice to the cached profile.
Update: this bug has been fixed in our development version and will be part of the next UMS release. The next release is not scheduled yet, but should be 1.17.0. If another patch release is done on 1.16 instead it will be part of 1.16.2 so keep an eye out for either of them.
When the version containing the fix is released I will post another update here.
And to be clear; 1.16.1 does not contain the fix yet.
Update: 1.17.0 has just been released and should resolve the persona scoring bug.
is working on a reply...