Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Damian Green 452 posts 1433 karma points
    Jul 16, 2014 @ 13:31
    Damian Green
    0

    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 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

    Umbraco.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.

    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?

    Thanks Damian

  • Stephen 767 posts 2273 karma points c-trib
    Jul 16, 2014 @ 15:14
    Stephen
    0

    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:

    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...

    Making sense?

    Stephan

  • Damian Green 452 posts 1433 karma points
    Jul 16, 2014 @ 15:31
    Damian Green
    0

    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

  • Damian Green 452 posts 1433 karma points
    Jul 16, 2014 @ 16:02
    Damian Green
    100

    Boom! :)

    Slight tweak and it works. Nice one.

            public class PublishedContentFactoryInit : ApplicationEventHandler
    {
        protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
        {
            var types = PluginManager.Current.ResolveTypes<PublishedContentModel>()
                .Except(new []
                {
                    typeof(SummaryDocumentCollection<,>)
                });
    
            PublishedContentModelFactoryResolver.Current.SetFactory(new PublishedContentModelFactory(types));            
        }
    } 
    

    I thought i was going to have to tell it to ignore the collection classes:

     .Except(new[]
                {
                    typeof (KnowledgeBaseCollection),
                    typeof (CareerCollection)
                });
    

    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:

    typeof(SummaryDocumentCollection<KnowledgeBaseSection, KnowledgeBaseArticle>) 
    

    In the list of the returned types the above was still inluded but called

    Project.UmbracoWeb.Models.Custom.SummaryDocumentCollection`2
    

    It still got through and kills the factory init.

  • Stephen 767 posts 2273 karma points c-trib
    Jul 16, 2014 @ 16:54
    Stephen
    0

    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<,>

  • Damian Green 452 posts 1433 karma points
    Jul 16, 2014 @ 17:31
    Damian Green
    0

    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

  • Damian Green 452 posts 1433 karma points
    Jul 16, 2014 @ 17:37
    Damian Green
    0

    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);
        }
    }
    
Please Sign in or register to post replies

Write your reply to:

Draft