November Happy Hour will be moved to Thursday December 5th.

Strategy Block Styling

Vote:
 

I am using the Strategy Block that comes with EPiServer.PErsonalization.CMS.UI. I want to override the styling/markup. With EPiServer Forms, I can just create new user controls in my solution with the same name/path as the EPiServer forms controls. This does not seem to work with the Strategy Block. I would prefer not to rebuild the entire thing from scratch as it just works from a content perspective. Is there a way to override the markup its using?

#208716
Oct 30, 2019 19:39
Vote:
 

It seems like the CSS overwrite is the only available option for now.

Did you find any other solution for this?

#208876
Nov 04, 2019 13:59
Ethan Schofer - Nov 04, 2019 14:26
No, I reverse engineered it and made custom one.
Praful Jangid - Nov 04, 2019 14:40
You should post the solution and mark as answer to better help other developers :)
Thanks
Vote:
 

Solution was to reverse engineer the strategy block. I used dotPeek to look at the Strategy Block code and built from scratch:

Create a RecommendationsBlock

[ContentType(GroupName = GroupDefinitions.Blocks.BasicContent, DisplayName = "Recommendations Block", GUID = "44977067-366F-465C-A03F-9DEC073231DB", Description = "Display a list of recommended content")]
public class RecommendationsBlock : ContentBlockData, IStrategyContainerBlock
{
	private Injected<StrategyService> _strategyService;

	[CultureSpecific]
	[Display(Name = "Recommendations Title", GroupName = GroupDefinitions.Content, Description = "Title for the list of recommended content", Order = 10)]
	public virtual string RecommendationsTitle { get; set; }

	[Display(Name = "Recommendations Default Strategy", GroupName = GroupDefinitions.Content, Description = "DefaultStrategy for type of recommendations", Order = 20)]
	[SelectOne(SelectionFactoryType = typeof(StrategySelectionFactory))]
	public virtual string DefaultStrategy { get; set; }

	[Display(Name = "Number of Recommendations", GroupName = GroupDefinitions.Content, Description = "Number of recommendations to display", Order = 30)]
	[Range(0,20)]
	public virtual int NumberOfRecommendations { get; set; }

	[Ignore]
	public IEnumerable<RecommendationItem> Recommendations { get; set; }

	public override void SetDefaultValues(ContentType contentType)
	{
		NumberOfRecommendations = 5;
		SetDefaultStrategyValue();
		base.SetDefaultValues(contentType);
	}

	private void SetDefaultStrategyValue()
	{
		var siteId = SiteDefinition.Current.Id.ToString();
		var results = System.Threading.Tasks.Task.Run(async () => await _strategyService.Service.GetStrategies(siteId)).Result;

		var strategies = results as Strategy[] ?? results.ToArray();

		if (!strategies.Any())
			return;

		var strategy = strategies.FirstOrDefault(s => s.StrategyId != null && s.StrategyId.Equals("Default", StringComparison.OrdinalIgnoreCase));

		if (strategy == null)
			return;

		DefaultStrategy = strategy.StrategyId;
	}
}

This grabs the default recommendation strategy using the StrategyService. The SelectionFactory for strategies gets them from the service:

public class StrategySelectionFactory : ISelectionFactory
{
	private Injected<IStrategyService> _strategyService;

	public IEnumerable<ISelectItem> GetSelections(ExtendedMetadata metadata)
	{
		var siteId = SiteDefinition.Current.Id.ToString();
		return System.Threading.Tasks.Task.Run(async () => await _strategyService.Service.GetStrategies(siteId)).Result.Select(item => new SelectItem()
		{
			Value = item.StrategyId,
			Text = item.Name
		});
	}
}

Also added a view model to display the actual recommendations on a page:

public class RecommendationsBlockViewModel
{
	public RecommendationsBlock Item { get; set; }

	public string RecommendationTitle { get; set; }

	public IEnumerable<RecommendationItem> Recommendations { get; set; }
}

Then a RecommendationsController:

[TemplateDescriptor(AvailableWithoutTag = true)]
public class RecommendationsBlockController : BlockController<RecommendationsBlock>
{
	private readonly IRecommendationService _recommendationService;
	private readonly UserService _userService;
	private readonly IStrategyService _strategyService;

	public RecommendationsBlockController(LocalizationService localizationService,IRecommendationService recommendationService,UserService userService,IStrategyService strategyService)
	{
		_recommendationService = recommendationService;
		_userService = userService;
		_strategyService = strategyService;
	}

	public override ActionResult Index(RecommendationsBlock currentBlock)
	{
		var renderingStrategy = !(ControllerContext.RouteData.Values["renderSettings"] is IDictionary<string, object> dictionary) || !dictionary.ContainsKey("data-strategy") ? string.Empty : dictionary["data-strategy"].ToString();

		var dataStrategy = string.IsNullOrWhiteSpace(renderingStrategy) || renderingStrategy.Equals("251679C7-088F-45EE-8B39-238F73C7C5C6", StringComparison.OrdinalIgnoreCase) ? currentBlock.DefaultStrategy : renderingStrategy;
		
		var httpContext = ControllerContext.HttpContext;
		var recommendationRequest = BuildRecommendationRequest(currentBlock, dataStrategy, httpContext);
		recommendationRequest.NumberOfRecommendations += 5;

		var result = System.Threading.Tasks.Task.Run(async () => await _recommendationService.Get(recommendationRequest)).Result;

		currentBlock.Recommendations = result.Take(currentBlock.NumberOfRecommendations);

		return PartialView(currentBlock);
	}

	protected PageData CurrentPage => ServiceLocator.Current.GetInstance<IPageRouteHelper>().Page;

	private RecommendationRequest BuildRecommendationRequest(RecommendationsBlock recommendationsBlock,string customStrategy,HttpContextBase httpContext)
	{
		var siteId = SiteDefinition.Current.Id;

		var context = new Context
		{
			ContentId = CurrentPage.ContentGuid.ToString(),
			LanguageId = CurrentPage.Language?.Name
		};

		var user = _userService.CreateUser(httpContext);
		return _recommendationService.BuildRecommendationRequest(recommendationsBlock, customStrategy, httpContext, siteId.ToString(), context, user);
	}
}

The requests recommendations using the RecommendationService and the StrategyService. It hard codes the id of the default strategy.

Also registered the RecommendationService with DI. In my dependency resolver initialization module I added the new IHttpClientFacotry and the RecommendationService, as well as setting up the Stragety Configuration.

public void ConfigureContainer(ServiceConfigurationContext context)
        {
            context.ConfigurationComplete += (o, e) =>
            {
                //Register custom implementations that should be used in favour of the default implementations
                context.Services.AddTransient<IContentRenderer, ErrorHandlingContentRenderer>()
                    .AddTransient<ContentAreaRenderer, CustomContentAreaRenderer>()
                    .AddTransient<ICustomCacheManager, CustomCacheManager>()
                    .AddSingleton<IHttpClientFactory, HmacHttpClientFactory>()
                    .AddSingleton<IRecommendationService, RecommendationService>();

                SetupStrategyConfiguration(context);
            };
        }

        private void SetupStrategyConfiguration(ServiceConfigurationContext context)
        {
            var applicationKey = ConfigurationManager.AppSettings.Get("episerver:RecommendationServiceKey");

            if (string.IsNullOrWhiteSpace(applicationKey))
                throw new ConfigurationErrorsException("AppSetting 'episerver:RecommendationServiceKey' is required for recommendation service");

            var secret = ConfigurationManager.AppSettings.Get("episerver:RecommendationServiceSecret");

            if (string.IsNullOrWhiteSpace(secret))
                throw new ConfigurationErrorsException("AppSetting 'episerver:RecommendationServiceSecret' is required for recommendation service");

            var baseUri = ConfigurationManager.AppSettings.Get("episerver:RecommendationServiceUri");

            if (string.IsNullOrWhiteSpace(baseUri) || !Uri.IsWellFormedUriString(baseUri, UriKind.Absolute))
                throw new ConfigurationErrorsException("AppSetting 'episerver:RecommendationServiceUri' is required for recommendation service");

            var cacheDurationConfig = ConfigurationManager.AppSettings.Get("episerver:RecommendationServiceCacheDuration");
            var cacheDurationMinutes = TimeSpan.FromMinutes(5.0);

            if (int.TryParse(cacheDurationConfig, out var num))
                cacheDurationMinutes = TimeSpan.FromMinutes(num);

            context.Services.Configure((Action<ApiConfiguration>)(o =>
            {
                o.ApplicationKey = applicationKey;
                o.Secret = secret;
                o.BaseUri = new Uri(baseUri);
            }));

            context.Services.Configure((Action<StrategyApiConfiguration>)(o =>
            {
                o.ApplicationKey = applicationKey;
                o.Secret = secret;
                o.BaseUri = new Uri(baseUri);
                o.CacheDuration = cacheDurationMinutes;
            }));
        }

Then I can add whatever Razor View I want for the controller..

Its not quite as slick as the Strategy Block from EPiServer, but it works perfectly fine.

#208913
Edited, Nov 04, 2019 20:31
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.