Hi Mark,
I would override the SetDefaultValues and in here create a block and then put it into your content area.
Something like
public override void SetDefaultValues(ContentType contentType)
{
base.SetDefaultValues(contentType);
var sharedBlock = ServiceLocator.Current.GetInstance<IContentRepository>().GetDefault<MyBlock>(ContentReference.GlobalBlockFolder);
.. add values to props on block ..
MainContentArea = new ContentArea();
MainContentArea.Add(sharedBlock);
}
Oh.. the Add method is obsolete as of EPiServer 7.5 so if your on 7.5 or higher you need to add a ContentAreaItem to the ContentArea's Items property instead.
public override void SetDefaultValues(ContentType contentType)
{
base.SetDefaultValues(contentType);
var repository =ServiceLocator.Current.GetInstance<IContentRepository>();
var sharedBlock = repository.GetDefault<MyBlock>(ContentReference.GlobalBlockFolder);
.. add props to block ..
var savedReference = repository.Save(sharedBlock, DataAccess.SaveAction.Publish);
MainContentArea = new ContentArea();
MainContentArea.Add(savedReference); or MainContentArea.Items.Add(savedReference);
}
More info:
http://world.episerver.com/Blogs/Johan-Bjornfot/Dates1/2012/11/Shared-blocks--IContent/
http://joelabrahamsson.com/working-programmatically-with-local-blocks-in-episerver-7/
public override void SetDefaultValues(ContentType contentType)
{
base.SetDefaultValues(contentType);
var repository =ServiceLocator.Current.GetInstance<IContentRepository>();
var sharedBlock = repository.GetDefault<MyBlock>(ContentReference.GlobalBlockFolder);
.. add props to block ..
var savedReference = repository.Save(sharedBlock, DataAccess.SaveAction.Publish);
MainContentArea = new ContentArea();
MainContentArea.Add(savedReference); or MainContentArea.Items.Add(savedReference);
}
Sorry for spamming but seems the editing button doesn't work with code blocks.
Tacking the block creation firstly - I am able to get the following to create a block:
public override void SetDefaultValues(ContentType contentType) { base.SetDefaultValues(contentType); var repository = ServiceLocator.Current.GetInstance<IContentRepository>(); IContent block = repository.GetDefault<AuthorBlock>(ContentReference.GlobalBlockFolder) as IContent; block.Name = "Author"; var reference = repository.Save(block as IContent, SaveAction.Publish); }
However, this seems to get called twice creating 2 blocks, also if possible I'd like to scope the blocks to the 'For this page' block folder. As such I'm wondering if default values is the right place or if there is another event I need look at.
I've moved this into an OnPageCreated method through an initializable module, so now I have:
[InitializableModule] public class PageInitialization : IInitializableModule { public void Initialize(EPiServer.Framework.Initialization.InitializationEngine context) { DataFactory.Instance.CreatedPage += CreatedPage; } public void Preload(string[] parameters) { throw new NotImplementedException(); } public void Uninitialize(EPiServer.Framework.Initialization.InitializationEngine context) { DataFactory.Instance.CreatedPage -= CreatedPage; } protected void CreatedPage(object sender, PageEventArgs e) { if (e.Page is BasePageData) { (e.Page as BasePageData).OnPageCreated(e); } } }
Then in my page type I override an OnPageCreated method inherited from my BasePageData:
public override void OnPageCreated(PageEventArgs e) { var repository = ServiceLocator.Current.GetInstance<IContentRepository>(); IContent block = repository.GetDefault<AuthorBlock>(e.PageLink) as IContent; block.Name = "Author"; var reference = repository.Save(block as IContent, SaveAction.Publish); }
But still haven't figured out how to get the block in the 'For this page' block folder, the page link doesn't seem to work.
From the database it looks like the 'For This Block' folder doesn't exist until a block is added to it for the first time, I've been trying to figure out how it works from the UI.
From the databse the page specific block folders appear to be created as fkContentTypeID = 4 with fkParentID = 4, what actually looks to tie it to the page is that the ContentOwnerID is the ContentGUID of the page.
Interestingly these page related block folder still exist in the database event if the owner item is trashed, so I don't know whether caution is advised due to the potential for inflating the database with the orphaned items or whether a clean-up does exist for these at some point.
All that said I've still got to figure out how I can use that to get the reference to it.
Interestingly I thought I could hack it, and although the code below looks like it creates the right structure in tblContent table of the database it doesn't actually get seen as the 'For This Page' block folder, and when you try add another block it creates another folder under ContentReference(4) which is then used. Shame as it is really making me wonder how I can create a page with blocks that the user can then rearrange to customise the display of the page data.
Anyway here's the code that runs fine, but doesn't work:
public override void OnPageCreated(PageEventArgs e) { var repository = ServiceLocator.Current.GetInstance<IContentRepository>(); var rootAssetFolder = new ContentReference(4); var folders = repository.GetChildren<ContentAssetFolder>(rootAssetFolder); ContentAssetFolder folder = null; foreach (ContentAssetFolder item in folders) { if (item.ContentOwnerID == e.Page.ContentGuid) { folder = item; } } if (folder == null) { folder = repository.GetDefault<ContentAssetFolder>(rootAssetFolder); folder.Name = e.Page.Name; folder.ContentOwnerID = e.Page.ContentGuid; repository.Save(folder as IContent, SaveAction.Publish); } IContent block = repository.GetDefault<AuthorBlock>(folder.ContentLink) as IContent; block.Name = "Author"; var reference = repository.Save(block as IContent, SaveAction.Publish); }
Stumbled across a couple of things. there is a ContentAssetFolder.Attach(IContent content) method, but whenever I try call it I get a read only error on PageContentAssetID.
I get the same if I try update that on the page directly, as below.
e.Page.ContentAssetsID = folder.ContentGuid; repository.Save(e.Page, SaveAction.Save);
I don't know if this just isn't really available or I'm somehow accessing a read-only version of the page.
Found a ResolveContentFolder with any luck perhaps that creates a folder if it doesn't find one?
EPiServer.DataFactory.Instance.ResolveContentFolder(e.Page.PageFolderID)
Success! Need some refactoring and testing though. My problem is that I needed a writable clone of the page to update the necessary bits.
Code to create a block on page creation and add it to the 'For This Page' asset folder is as follows:
public override void OnPageCreated(PageEventArgs e) { var repository = ServiceLocator.Current.GetInstance<IContentRepository>(); var rootAssetFolder = new ContentReference(4); var folders = repository.GetChildren<ContentAssetFolder>(rootAssetFolder); ContentAssetFolder folder = null; foreach (ContentAssetFolder item in folders) { if (item.ContentOwnerID == e.Page.ContentGuid) { folder = item; } } if (folder == null) { var currentPage = e.Page.CreateWritableClone(); folder = repository.GetDefault<ContentAssetFolder>(rootAssetFolder); folder.Name = e.Page.Name; folder.ContentOwnerID = e.Page.ContentGuid; folder.Attach(currentPage); repository.Save(folder, SaveAction.Publish); currentPage.ContentAssetsID = folder.ContentGuid; repository.Save(currentPage, SaveAction.Save); } IContent block = repository.GetDefault<AuthorBlock>(folder.ContentLink) as IContent; block.Name = "Author"; var reference = repository.Save(block as IContent, SaveAction.Publish); }
Next step, adding it to the content area.
Sweet!
Was just going to add that I found a SiteDefinition.Current.ContentAssetsRoot so you could have used that to check if a folder for the currentPage exists else create a new. But glad you got it working
Will take a look at what you found for resolving the folder as I'm never too happy with hardcoded IDs, even if they are unlikely to change. :)
The last bit to add to the content area turned out to be much easier once I'd got that down, just slightly to move the delcaration for currentPage outside the if statement the added this:
currentPage.RightContentArea = this.RightContentArea ?? new ContentArea(); currentPage.RightContentArea.Items.Add(new ContentAreaItem() { ContentLink = block.ContentLink }); repository.Save(currentPage, SaveAction.Save);
There's more options I could preconfigure on the content area item but I think for all my bits I'm happy to defer that to the user.
I switched some of my logic to tidy it up, and put the logic around content asset folders in an extension method. Thought I'd share it below.
Now I have the extension method:
...
public static ContentAssetFolder GetContentAssetFolder(this PageData page) { var repository = ServiceLocator.Current.GetInstance<IContentRepository>(); var rootAssetFolder = SiteDefinition.Current.ContentAssetsRoot; // should be id 4 var assetFolders = repository.GetChildren<ContentAssetFolder>(rootAssetFolder); ContentAssetFolder assetFolder = null; foreach (ContentAssetFolder folder in assetFolders) { if (folder.ContentOwnerID == page.ContentGuid) { assetFolder = folder; break; } } if (assetFolder == null) { assetFolder = repository.GetDefault<ContentAssetFolder>(rootAssetFolder); assetFolder.Name = page.Name; assetFolder.ContentOwnerID = page.ContentGuid; assetFolder.Attach(page); repository.Save(assetFolder, SaveAction.Publish); } return assetFolder; }
...
Then in my blog list page model I use the OnPageCreated method, which I add to my BasePageData and wired up through the intialization module earlier, to create blocks in the page:
...
public override void OnPageCreated(EPiServer.PageEventArgs e) { base.OnPageCreated(e); var page = e.Page.CreateWritableClone() as BlogListingPage; var assetFolder = page.GetContentAssetFolder(); var repository = ServiceLocator.Current.GetInstance<IContentRepository>(); var pageListArchiveBlock = repository.GetDefault<PageListArchiveBlock>(assetFolder.ContentLink); pageListArchiveBlock.Title = ((IContent)pageListArchiveBlock).Name = "Blog Archive"; var pageListArchiveReference = repository.Save(pageListArchiveBlock as IContent, SaveAction.Publish); var pageListTagBlock = repository.GetDefault<PageListTagBlock>(assetFolder.ContentLink); pageListTagBlock.Title = ((IContent)pageListTagBlock).Name = "Tags"; var pageListTagReference = repository.Save(pageListTagBlock as IContent, SaveAction.Publish); page.RightContentArea = this.RightContentArea ?? new ContentArea(); page.RightContentArea.Items.Add(new ContentAreaItem() { ContentLink = pageListArchiveReference }); page.RightContentArea.Items.Add(new ContentAreaItem() { ContentLink = pageListTagReference }); repository.Save(page, SaveAction.Save); }
...
Hi,
Fyi, there is a ContentAssetHelper service you can use:
var contentAssetHelper = ServiceLocator.Current.GetInstance<ContentAssetHelper>();
ContentAssetFolder folder = contentAssetHelper.GetOrCreateAssetFolder(page.ContentLink);
Thanks, that's good to know, I can throw my code away now. Shame I can't get those 2 hours back spent figuring it out. :/
I have used your approach to automatically create blocks and add them to a content area for specific page types in a couple of scenarios. I think it's a good approach.
So I've got some pages, and the model for the page type contains some data which is likely to be displayed in most instances but I'd like user to be able to lay that out.
My thought was if I could have a block in a content area that was created as the page way then the user could insert other blocks before or after it, or if they didn't want it they could remove the block post creation.
This also fits with my standard page from which these page types inherit, since the content areas are the same it's just for the more specific page type I have some more specific stuff in my main body content and, as I see it, some predefined blocks in my right column.
Unfortunately I don't have any eperience of programmatically creating pages or blocks, let alone then programmatically adding them to content areas so it would be good to know if firstly this is the right approach, and secondly how I might go about doing it.