November Happy Hour will be moved to Thursday December 5th.
November Happy Hour will be moved to Thursday December 5th.
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?