In am using the PublishedContentModel factory and I was seeing some common patterns in the classes I was writing so decided to create a generic class for a Document/Child collection relationship.## Heading ##
All that seemed fine until I spun it up for the first time and the PCM factory doesnt seem to be able to handle Generic types and got the error when the factpry was being initialised.
**[ArgumentException: Type SummaryDocumentCollection2[T,TSubType] is a generic type definition]**
System.Dynamic.Utils.TypeUtils.ValidateType(Type type) +4096914
System.Linq.Expressions.Expression.New(ConstructorInfo constructor, IEnumerable1 arguments) +57
Umbraco.Core.Models.PublishedContent.PublishedContentModelFactory..ctor(IEnumerable`1 types) +334
Is there a way to allow this by maybe providing a converter or something to save me going down the copy/paste route?
I thought about using inheritance but i want to identify seperate types of document for a reason.
Here is my generic class:
ContentSections inherits from PublishedContentModel in the inheritance structure.
public class SummaryDocumentCollection<T, TSubType> : ContentSections, ISummary
where T : class, IPublishedContent, ISummary
where TSubType : class, IPublishedContent, ISummary
{
public SummaryDocumentCollection(IPublishedContent content) : base(content)
{
}
public IEnumerable<ISummary> Contents
{
get
{
var items = new List<ISummary>();
items.AddRange(GetDocuments());
items.AddRange(GetSections());
return items.OrderBy(o => o.DisplayName);
}
}
public string Summary
{
get { return Resolve<string>(Property()); }
}
public IEnumerable<TSubType> Documents
{
get { return GetDocuments().OrderBy(x => x.DisplayName); }
}
public IEnumerable<T> Sections
{
get { return GetSections().OrderBy(o => o.DisplayName); }
}
private IEnumerable<TSubType> GetDocuments()
{
return this.Descendants<TSubType>();
}
private IEnumerable<T> GetSections()
{
return this.Descendants<T>();
}
}
And then on top of this i have created custom collections to hide the generics:
public class KnowledgeBaseCollection : SummaryDocumentCollection<KnowledgeBaseSection, KnowledgeBaseArticle>
{
public KnowledgeBaseCollection(IPublishedContent content)
: base(content)
{
}
}
public class CareerCollection : SummaryDocumentCollection<CareerSection, CareerArticle>
{
public CareerCollection(IPublishedContent content)
: base(content)
{
}
}
And then to serve these up i just hijacked the route:
public class KnowledgeBaseSectionController : SurfaceController
{
public ActionResult KnowledgeBaseSection()
{
var kb = new KnowledgeBaseCollection(CurrentPage);
return View(kb);
}
}
So all looked great until i tried running and hit the problem with the factory.
Any way i can achieve this without resulting to copy and paste for the collections?
Trouble is, the real model types are non-generic (CareerCollection, etc) and it makes sense because a model type cannot be generic (how would the factory know about the generic type parameter?). But when you initialize the factory, you get all the types inheriting from PublishedContentModel... and that includes SummaryDocumentCollection<T, TSubType>, so the factory thinks it's a model type, and fails because models types cannot be generic.
var types = PluginManager.Current.ResolveTypes<PublishedContentModel>(); var factory = new PublishedContentModelFactory(types); PublishedContentModelFactoryResolver.Current.SetFactory(factory);
Then you probably want to do something along...
var types = PluginManager.Current.ResolveTypes<PublishedContentModel>() .Except(new[] { typeof(SummaryDocumentCollection<>) });
Might not be the exact correct syntax but do you see the point? Also there might be other types that you want to exclude...
Ah yea - I understand and i'll give it a try - didn't know you could Exclude types from the factory.
I've actually refactored to stop my Generic Type inheriting from PCM but rather it decorated it, and then exposed itas a property for my Views. However the problem I have now is my master template needs IPublished content and passing the CustomCollection class to the view wont cut it as it doesnt inherit from IPublishedContent anymore. Doh! Wonder if i am going to hit a similar thing here....
I'll give your suggestion a go anyway and let you know.
Do the collection classes match an actual content type? If not, why would they inherit from PublishedContentModel?
SummaryDocumentCollection<KnowledgeBaseSection, KnowledgeBaseArticle> is a specific generic type, whereas the one you want to exclude is the "generic generic" type ie SummaryDocumentCollection<,>
The types used in the Generic Types (KnowledgeBaseSection & KnowledgeBaseArticle) do match ContentTypes but not the customer generic classes.
I have to inherit from PCM because of the issue i posted above - my master page expects an IPublishedContent so if i pass a specific class down to the view it doesn't like it - obviously.
I didn't realise you could pass <,> for an untyped generic in this instance. This now works so used this instead of the sweeping IsGeneric Where clause.
Learnt a few great bits today in doing this. :) Not really built any custom generic classes before either so all good.
Oh and i have fixed the above code for the Controllers - i needed to inherit from RenderMvcController not SurfaceController (but i cant edit the post now)
public class KnowledgeBaseSectionController : RenderMvcController
{
public ActionResult KnowledgeBaseSection()
{
var kb = new KnowledgeBaseCollection(CurrentPage);
return View(kb);
}
}
PublishedContentModel and generic types
In am using the PublishedContentModel factory and I was seeing some common patterns in the classes I was writing so decided to create a generic class for a Document/Child collection relationship.## Heading ##
All that seemed fine until I spun it up for the first time and the PCM factory doesnt seem to be able to handle Generic types and got the error when the factpry was being initialised.
**[ArgumentException: Type SummaryDocumentCollection
2[T,TSubType] is a generic type definition]** System.Dynamic.Utils.TypeUtils.ValidateType(Type type) +4096914 System.Linq.Expressions.Expression.New(ConstructorInfo constructor, IEnumerable
1 arguments) +57 Umbraco.Core.Models.PublishedContent.PublishedContentModelFactory..ctor(IEnumerable`1 types) +334Umbraco.Core.ApplicationEventHandler.OnApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) +36 Umbraco.Core.CoreBootManager.
Is there a way to allow this by maybe providing a converter or something to save me going down the copy/paste route?
I thought about using inheritance but i want to identify seperate types of document for a reason.
Here is my generic class:
ContentSections inherits from PublishedContentModel in the inheritance structure.
And then on top of this i have created custom collections to hide the generics:
And then to serve these up i just hijacked the route:
So all looked great until i tried running and hit the problem with the factory.
Any way i can achieve this without resulting to copy and paste for the collections?
Thanks Damian
Trouble is, the real model types are non-generic (CareerCollection, etc) and it makes sense because a model type cannot be generic (how would the factory know about the generic type parameter?). But when you initialize the factory, you get all the types inheriting from PublishedContentModel... and that includes SummaryDocumentCollection<T, TSubType>, so the factory thinks it's a model type, and fails because models types cannot be generic.
What you want is to exclude that type from the list of types you pass to the factory when you initialize it. Assuming you're using the code from https://github.com/zpqrtbnk/Zbu.ModelsBuilder/wiki/IPublishedContentModelFactory ie:
Then you probably want to do something along...
Might not be the exact correct syntax but do you see the point? Also there might be other types that you want to exclude...
Making sense?
Stephan
Ah yea - I understand and i'll give it a try - didn't know you could Exclude types from the factory.
I've actually refactored to stop my Generic Type inheriting from PCM but rather it decorated it, and then exposed itas a property for my Views. However the problem I have now is my master template needs IPublished content and passing the CustomCollection class to the view wont cut it as it doesnt inherit from IPublishedContent anymore. Doh! Wonder if i am going to hit a similar thing here....
I'll give your suggestion a go anyway and let you know.
Thanks for you help Stephan. Much appreciated :)
Damian
Boom! :)
Slight tweak and it works. Nice one.
I thought i was going to have to tell it to ignore the collection classes:
but it seems ok with them in there.
Do you see any issue with the Where clause. I tried to add them independently but it didnt work:
In the list of the returned types the above was still inluded but called
It still got through and kills the factory init.
Do the collection classes match an actual content type? If not, why would they inherit from PublishedContentModel?
SummaryDocumentCollection<KnowledgeBaseSection, KnowledgeBaseArticle> is a specific generic type, whereas the one you want to exclude is the "generic generic" type ie SummaryDocumentCollection<,>
The types used in the Generic Types (KnowledgeBaseSection & KnowledgeBaseArticle) do match ContentTypes but not the customer generic classes.
I have to inherit from PCM because of the issue i posted above - my master page expects an IPublishedContent so if i pass a specific class down to the view it doesn't like it - obviously.
I didn't realise you could pass <,> for an untyped generic in this instance. This now works so used this instead of the sweeping IsGeneric Where clause.
Learnt a few great bits today in doing this. :) Not really built any custom generic classes before either so all good.
Thanks! Damian
Oh and i have fixed the above code for the Controllers - i needed to inherit from RenderMvcController not SurfaceController (but i cant edit the post now)
is working on a reply...