Hi,
We have gone with your last suggestion, creating a custom content area renderer. And we're passing the RenderSettings dictionary, which Tag is part of, to it. We're also passing the item index to it.
I wouldn't call it common practice to add a css class to the last item though. Much easier to add one to the first item, or just use a css selector.
I can't give you any directions though, our solution is made in webforms and not MVC.
Hi Johan
Thank you for your response. That is a definitely a start. I have added my own ContentArea overrie as the below:
@if(Model != null)
{
Html.RenderContentAreaWithInfo(Model);
}
My RenderContentAreaWithInfo method so far looks like this:
public static void RenderContentAreaWithInfo(this HtmlHelper helper, ContentArea contentArea)
{
var viewData = helper.ViewContext.ViewData;
var renderingTag = viewData["tag"] as string;
var contentRequestContext = ServiceLocator.Current.GetInstance<ContentRequestContext>();
var contents = contentRequestContext.IsInEditMode(helper.ViewContext.HttpContext)
? contentArea.Contents
: contentArea.FilteredContents;
var renderingSettings = new RenderingSettings(helper.ViewContext.ViewData);
var templateResolver = ServiceLocator.Current.GetInstance<TemplateResolver>();
for (var i = 0; i < contents.Count(); i++)
{
var content = contents.ToList()[i];
var contentType = content.GetOriginalType();
var blockBaseType = typeof(SiteBlockData);
if (blockBaseType.IsAssignableFrom(contentType))
{
//Need to pass the Type into BlockViewModel as well as convert content to its base type, at the moment it is a proxy?
var model = new BlockViewModel<SiteBlockData>(content)
{
Tag = renderingTag,
Index = i,
RenderSettings = renderingSettings
};
}
var templateModel = templateResolver.Resolve(
helper.ViewContext.HttpContext,
content.GetOriginalType(),
content,
TemplateTypeCategories.MvcPartial,
renderingTag);
// Here I want to pass my view model instead of content
// Guessing I need to role my own version of the RenderContentData method somehow?
var contentRenderer = ServiceLocator.Current.GetInstance<IContentRenderer>();
helper.RenderContentData(
content,
true,
templateModel,
contentRenderer);
}
}
From here I have access to all of the data that I need, however I am stuck trying to work out how to pass that to the individual views for each block. My two main problems (as illustrated in the comments in the code are)
I need to be able to convert the Castle.Proxy object of the block to its raw item, I cannot find a way of doing this short of using the DataFactory to call it again, there must be a better way?
I need to then get my BlockViewModel into the corresponding PartialViews. However the RenderContentData method just accepts an object of type IContent so I am guessing I would need to role my own version of this render method but thats where Im stuck at the moment.
Could anyone else possibly help me point me in the direction of the next steps.
Many Many Thanks
Dave
Instead of turning your blocks into view models, you could add the extra properties to your SiteBlockData class, and decorate them with [Ignore]. By doing this, EPiServer will ignore those properties as editable properties.
Converting to view models should really happen in the controller I suppose.
Although a different approach, you may find some information to be useful here: http://world.episerver.com/Blogs/Per-Magne-Skuseth/Dates/2013/8/Letting-your-blocks-know-where-they-are/
Hi Per
Thank you so much for your response. I was hoping with my original solution to be able to remove the extra markup added with the ContentAreas but your solution does look really good, and much simpler to implement!!!
I have got it working in one of my existing block controllers but ideally I would not want to create a controller for every block so I would like a controller that implements this by default for all blocks. I am having trouble working this part out though as there is no BlockControllerBase, I cannot find any reference on line for creating one, I have also tried creating one in the same method that the PageControllerBase is built but my controller is not hit.
Any thoughts on a good way to do this? Or possily inject it into every block at some point during the execution?
Many thanks
Dave
I see what you mean. Since you want to do it for every block, doing it in a controller is not optional. I think the easiest solution in your case would be to use your RenderContentAreaWithInfo method above, but instead of converting to view models, expand SiteBlockData with Index, Tag and RenderSettings (make sure you use Ignore as I previously mentioned.
To set the values, you should be able to do something like this:
((SiteBlockData)content).Tag = renderingTag;
Hi Per
Thanks once again, unfortunately I am getting the following error:
System.InvalidCastException: Unable to cast object of type 'Castle.Proxies.BlockDataProxy' to type 'EPiServer.Templates.XXXXX.Models.Blocks.SiteBlockData'.
I am not sure how to convert from the proxy to the true object, ive done a lot of googling on this but nothing seems to work for me.
Many Thanks
Dave
Just guessing here, but could it be that you have blocks in the content area that do not inherit from SiteBlockData?
Make sure that you do the converting after calling IsAssignableFrom(..)
Hi Per
Thank you once again for your response, they are definately all inheriting from SiteBlockData and inside the IsAssignableFrom. My current method looks like this:
public static void RenderContentAreaWithInfo(this HtmlHelper helper, ContentArea contentArea)
{
var viewData = helper.ViewContext.ViewData;
var renderingTag = viewData["tag"] as string;
var contentRequestContext = ServiceLocator.Current.GetInstance<ContentRequestContext>();
var contents = contentRequestContext.IsInEditMode(helper.ViewContext.HttpContext)
? contentArea.Contents
: contentArea.FilteredContents;
var renderingSettings = new RenderingSettings(helper.ViewContext.ViewData);
var templateResolver = ServiceLocator.Current.GetInstance<TemplateResolver>();
for (var i = 0; i < contents.Count(); i++)
{
var content = contents.ToList()[i];
var contentType = content.GetOriginalType();
var blockBaseType = typeof(SiteBlockData);
if (contentType.IsAssignableFrom(typeof(SiteBlockData)) || blockBaseType.IsAssignableFrom(contentType))
{
((SiteBlockData)content).Tag = renderingTag;
((SiteBlockData)content).Index = i;
((SiteBlockData)content).RenderSettings = renderingSettings;
}
var templateModel = templateResolver.Resolve(
helper.ViewContext.HttpContext,
content.GetOriginalType(),
content,
TemplateTypeCategories.MvcPartial,
renderingTag);
var contentRenderer = ServiceLocator.Current.GetInstance<IContentRenderer>();
helper.RenderContentData(
content,
true,
templateModel,
contentRenderer);
}
}
Any thoughts?
Many Thanks
Dave
Try changing
var contentType = content.GetOriginalType();
var blockBaseType = typeof(SiteBlockData);
if (contentType.IsAssignableFrom(typeof(SiteBlockData)) || blockBaseType.IsAssignableFrom(contentType))
to
if (content as SiteBlockData != null)
Optinally, add a try catch, debug and step through every block. Or add it the Alloy template and see if it works better there.
I'm leaving for vacation. Wish you the best of luck! :-)
Hi All
Im not sure if this is going to be possible or not but is there any way of determining information about the ContentArea a block belongs to from inside the block's view. A couple of examples of how this could be used:
Determining a tag
There are a couple of places in my site where I want to tweak one CssClass on a div in a block based on the ContentArea that the block belongs to. In an ideal world I could just do a simple if statment to see if my block belongs to a ContentArea with a specific Tag, if it does add the class. This would save all the hassle of having to create a new block renderer and view every time for such a minor tweak.
Determining the position
It is fairly common practice these days to add a CssClass to the last element in a list to remove a margin or every Nth item to add a clear or something. This is only possible if we know the position of the current block from within block's view.
I have looked into looping over the contents of a ContentArea to manually output them which would be a big help but that seems a little flaky unless you can constrain the type of blocks that are put into that ContentArea.
Ideally I would like a generic BlockViewModel that contained the information about the CurrentBlock as well as the ContentArea it belongs to so you could simple make these checks.
Has anyone tried anythign similar or could possibly recommend a place to start looking to develop such a system? I am fairly new to EPiServer and don't really know where to start.
Many Thanks
Dave