Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Corné Hoskam 80 posts 587 karma points MVP 2x c-trib
    Jan 18, 2022 @ 12:01
    Corné Hoskam
    0

    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

  • Daniël Knippers 153 posts 1116 karma points MVP 2x c-trib
    Jan 18, 2022 @ 13:00
    Daniël Knippers
    100

    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 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 after MarketingSuiteContentBlocksComposer 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.

    Regards, Daniël

  • Stefan Besteman 6 posts 26 karma points
    Feb 14, 2022 @ 07:56
    Stefan Besteman
    0

    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
    }
    
  • Corné Hoskam 80 posts 587 karma points MVP 2x c-trib
    Jan 18, 2022 @ 13:50
    Corné Hoskam
    0

    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:

            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 😄

  • Daniël Knippers 153 posts 1116 karma points MVP 2x c-trib
    Jan 18, 2022 @ 15:02
    Daniël Knippers
    0

    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 ;-)

    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);
    
  • Corné Hoskam 80 posts 587 karma points MVP 2x c-trib
    Jan 19, 2022 @ 15:15
    Corné Hoskam
    0

    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é

  • Daniël Knippers 153 posts 1116 karma points MVP 2x c-trib
    Jan 19, 2022 @ 15:21
    Daniël Knippers
    0

    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.

  • Daniël Knippers 153 posts 1116 karma points MVP 2x c-trib
    Feb 04, 2022 @ 12:57
    Daniël Knippers
    0

    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.

  • Daniël Knippers 153 posts 1116 karma points MVP 2x c-trib
    Mar 17, 2022 @ 09:20
    Daniël Knippers
    0

    Update: 1.17.0 has just been released and should resolve the persona scoring bug.

Please Sign in or register to post replies

Write your reply to:

Draft