It seems like the CSS overwrite is the only available option for now.
Did you find any other solution for this?
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.
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?