I found these two blog posts:
http://joelabrahamsson.com/convert-a-linkitemcollection-to-a-list-of-pagedata/
http://world.episerver.com/Blogs/Alexander-Haneng/Dates/2013/1/Limiting-a-Page-Property-to-a-specific-Page-Type-in-EPiServer-7/
So I modified their code a little bit to work with IContent instead of PageData.
After some dotPeek-ing, I implemented this:
namespace EPiServer.Templates.Alloy.Business
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class LinkCollectionAllowedTypeAttribute : ValidationAttribute
{
private readonly Type _contentType;
private string _linkName;
private readonly IContentLoader _contentLoader;
public LinkCollectionAllowedTypeAttribute(Type contentType)
{
_contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
_contentType = contentType;
}
public override bool IsValid(object value)
{
// deleted items
if (value == null)
{
return true;
}
var linkItemCollection = value as LinkItemCollection;
// we're validating link item collections only
if (linkItemCollection == null)
{
return false;
}
foreach (var linkItem in linkItemCollection)
{
string linkUrl;
if (!PermanentLinkMapStore.TryToMapped(linkItem.Href, out linkUrl))
{
if (string.IsNullOrEmpty(linkUrl))
{
_linkName = linkItem.Text;
return false;
}
}
if (string.IsNullOrEmpty(linkUrl))
{
_linkName = linkItem.Text;
return false;
}
var contentReference = ParseUrl(linkUrl);
if (ContentReference.IsNullOrEmpty(contentReference))
{
_linkName = linkItem.Text;
return false;
}
var page = _contentLoader.Get<IContent>(contentReference);
var isValid = _contentType.IsInstanceOfType(page);
if (!isValid)
{
_linkName = linkItem.Text;
return false;
}
}
return true;
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, _linkName, _contentType.Name);
}
private ContentReference ParseUrl(string url)
{
var result = ContentReference.EmptyReference;
if (string.IsNullOrEmpty(url) || url.StartsWith("//"))
{
return result;
}
string complexReference = new UrlBuilder(url).QueryCollection["id"];
if (!string.IsNullOrEmpty(complexReference))
{
return ContentReference.TryParse(complexReference, out result) ? result : PageReference.EmptyReference;
}
int startIndex = url.IndexOf("____", StringComparison.Ordinal);
if (startIndex > 0)
{
int length = url.IndexOf("#", StringComparison.Ordinal);
if (length > 0)
{
url = url.Substring(0, length);
}
int num = url.IndexOf(".", startIndex, StringComparison.Ordinal);
if (num > 0 &&
ContentReference.TryParse(url.Substring(startIndex + 4, num - startIndex - 4), out result))
{
return result;
}
}
return ContentReference.EmptyReference;
}
}
}
And usage:
[LinkCollectionAllowedType(typeof(ImageFile),
ErrorMessage = "Field '{0}' is invalid. Link item '{1}' is not an instance of type '{2}'")]
public virtual LinkItemCollection ImageCollection { get; set; }
And here's the screenshot: http://oi57.tinypic.com/29ohj07.jpg
Hope this helps!
That will not work.
Can't you use a ContentArea instead, since you're only allowing ImageFile?
The reason for this not working for a link collection is that link collections expect a different format for the saved data (basically an object with a href and a title). Normally, when dropping a content item to a link collection, we have a restriction that only types that can be converted to links can be dropped here. In this case, what happens behind the scenes when adding the AllowedTypes attribute is that we are telling the client that it's OK to drop content of the type "ImageFile" to the editor without converting it. Of course, one would expect that EPiServer would handle this behind the scenes but unfortunately that's not currently the case.
I've come up with a work around for now that can be used until we have a better solution in place. It's not pretty I must admit, but hopefully it remove the imidiate issue with this. First, you need to indicate that the allowed types need to be treated as links. This is pretty easy and can be done in the attribute:
[AllowedTypes(new Type[]{typeof(ImageFile)}, AllowedTypesFormatSuffix = "link")]
public virtual LinkItemCollection ProductPageLinks { get; set; }
This basically tells the UI that the editor accepts items of "[namespace].ImageFile.link". The next problem that you get is that you can't drop anything in the editor. The reason for this is that there is no direct conversion between a ImageFile and a ImageFile.link. Normally, when we check for available type converters on the client, we traverse the inherritance chain and have set up converters for the base types, like PageData, IContentImage etc. The work around for this is to register something called a ContentRepositoryDescriptor. This is not something that we have talked much about but something that I plan do blog about in the future. Let's add one class that tells the UI that the type ImageFile can be treated as a link:
[ServiceConfiguration(typeof(IContentRepositoryDescriptor))]
public class CustomRepositoryDescriptor : ContentRepositoryDescriptorBase
{
public override string Key
{
get { return "whatever"; }
}
public override string Name
{
get { return "whatever"; }
}
public override System.Collections.Generic.IEnumerable<ContentReference> Roots
{
get { return new ContentReference[0]; }
}
public override System.Collections.Generic.IEnumerable<Type> ContainedTypes
{
get { return new Type[0]; }
}
public override System.Collections.Generic.IEnumerable<Type> LinkableTypes
{
get
{
return new Type[]{typeof(ImageFile)};
}
}
}
Thanks for the solution! Even if it's a little ugly code wise it will be very clear for the editors how to use it. Way better then sorting only images from the LinkItemCollection. And personally, for my purpose, I don't believe ContentArea is a proper solution, so thanks very much!
And a blog post would be great to get a better understanding for the solution :)
Linus, as of CMS 8.0 [AllowedTypes(new Type[]{typeof(ImageFile)}, AllowedTypesFormatSuffix = "link")]
generates an exception that AllowedTypes only can be used on ContentAreas.
Any Ideas on how to solve the issue with a content sensetive LinkiItemCollection in CMS 8.0?
Is this supposed to work? It works fine for LinkItemCollection until you actually put a valid content type in the area. It puts the content type but the name becomes "undefined" and you can't publis the page cause it says "Offline, unable to save".
Thanks!