Copied to clipboard

Flag this post as spam?

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


  • Martin 81 posts 246 karma points
    Oct 06, 2014 @ 23:04
    Martin
    0

    BaseController / BaseViewModel with Hijacked controllers

    Hi Lee,

    I've got Ditto up and running using a hijacked controller and the IPublishedContentModelFactory. I'm now wondering how I go about mapping common elements throughout the site such as my navigation which is a multi-node treepicker on my homepage node. Would I require another type converter to map a list of nodes to my NavigationItem model which just has two properties of 'Name' and 'Url'.

    Would love some clarification on this as I'm a bit confused!

  • Lee Kelleher 4020 posts 15802 karma points MVP 13x admin c-trib
    Oct 07, 2014 @ 11:29
    Lee Kelleher
    0

    Hi Martin,

    Take a read of a gist I wrote up for someone about how to map the more complex property-editor types:

    https://gist.github.com/leekelleher/513c48c634fc44729900

    I'm assuming that your base POCO has a property using IEnumerable<NavigationItem> (or list, or array)?

    Unfortunately you wont be able to assign a TypeConverter to a generic collection, so you will need to add a TypeConverter attribute property using it.

    [TypeConverter(typeof(NavigationItemListTypeConverter))]
    public IEnumerable<NavigationItem> Navigation { get; set; }
    

    In this case NavigationItemListTypeConverter would be your custom TypeConverter. (Obviously I'm guessing your property name)

    In your custom TypeConverter, you would have the value of the property (I'm not sure how multi-node treepicker stores it's value these days... guessing CSV?). Then from there you'd take the CSV value and call UmbracoHelper.TypedContent to get a IPublishedContent collection ... and then construct your list of NavigationItem objects.

    I hope this makes sense so far?

    Cheers,
    - Lee

  • Jeroen Breuer 4908 posts 12265 karma points MVP 4x admin c-trib
    Oct 07, 2014 @ 12:00
    Jeroen Breuer
    0

    Hello,

    If you want an example on how to use a base controller / base model have a look at this blog post (for Umbraco 6): http://24days.in/umbraco/2013/hybrid-framework/

    There is a complete solution available as an example project.

    Hybrid Framework Umbraco 6

    Hybrid Framework Umbraco 7

     

    The Hybrid Framework for Umbraco 7 uses the Models Builder, but Ditto could also be used.

    Jeroen

  • Martin 81 posts 246 karma points
    Oct 07, 2014 @ 12:48
    Martin
    0

    Thanks for the help guys. I was looking at the hybrid framework last night and the way base viewmodels/controllers are done there and I couldn't seem to figure out how to combine bits from both to make something work.

    What I currently have is this:

    public class HomePageController : RenderMvcController
    {
        public override ActionResult Index(RenderModel model)
        {
            if(!(model.Content is HomePage))
            {
                var homePage = model.Content.As<HomePage>();
                model = new RenderModel(homePage, model.CurrentCulture)
            }
    
            return base.Index(model);
        }
    }
    

    but what I really want is for each of my controllers (HomePageController, ProductListingController, etc) to inherit from some kind of base controller which maps my primary nav / footer nav / widgets etc, then allows me to map extra things to my model before returning the model.

    So something like:

    public class HomePageController : BaseController
    {
        public override ActionResult Index(RenderModel model)
        {
            if(!(model.Content is HomePage))
            {
                var homePage = model.Content.As<HomePage>();
                model = new RenderModel(homePage, model.CurrentCulture)
            }
    
            // somehow get base model with mapped properties
    
            // maybe map something extra
    
            // return model with base mapped properties plus automapped properties from current RenderModel
        }
    }
    

    Hope this makes sense. The reason for the base controller mapping is that my navigation multi-node treepicker is on my HomePage node but I want the IEnumerable

  • Jeroen Breuer 4908 posts 12265 karma points MVP 4x admin c-trib
    Oct 07, 2014 @ 12:49
    Jeroen Breuer
    0

    Hello,

    What you're trying to do is exactly what the Hybrid Framework solves. Did you read the blog post?

    Jeroen

  • Lee Kelleher 4020 posts 15802 karma points MVP 13x admin c-trib
    Oct 07, 2014 @ 13:01
    Lee Kelleher
    0

    Hi Martin,

    Quick check, did you say that you were already using the DittoPublishedContentModelFactory?

    If so, then you can remove the if(!(model.Content is HomePage)) check from your controller :-)

    With respect to having common/base properties - Ditto will handle the mapping of those.
    Do your HomePage and ProductListing classes inherit from a shared base class?

    If so, in that base class, create the properties that you want. If they are of complex types, then they'll need custom TypeConverters, (as mentioned in my first reply), otherwise Ditto will automatically map them (using Umbraco core APIs).

    Cheers,
    - Lee

  • Lee Kelleher 4020 posts 15802 karma points MVP 13x admin c-trib
    Oct 07, 2014 @ 13:04
    Lee Kelleher
    0

    Thinking about it some more... if you're using the DittoPublishedContentModelFactory, and unless you're doing additional logic - you don't even need to use hijacked controllers. Get rid of all the ceremony.

  • Martin 81 posts 246 karma points
    Oct 08, 2014 @ 00:49
    Martin
    0

    Lee, I'm getting the following exception when using the typeconverter:

    ValueFactory attempted to access the Value property of this instance.
    

    Here's my code:

    public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType)
    {
        if (sourceType == typeof(string))
        {
            return true;
        }
    
        return base.CanConvertFrom(context, sourceType);
    }
    
    public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
    {
        if (value is string)
        {
            var navigationIds = (string)value;
            var helper = new UmbracoHelper(UmbracoContext.Current);
            var navigationNodes = helper.TypedContent(navigationIds.Split(',')).ToList(); // throws exception
            var navigationItems = new List<NavigationItem>();
    
            if (navigationNodes != null && navigationNodes.Any())
            {
                foreach (var node in navigationNodes)
                {
                    navigationItems.Add(new NavigationItem { Name = node.Name, Url = node.Url });
                }
    
                return navigationItems;
            }
        }
    
        return base.ConvertFrom(context, culture, value);
    }
    

    Anything I'm doing wrong?

  • Lee Kelleher 4020 posts 15802 karma points MVP 13x admin c-trib
    Oct 08, 2014 @ 11:10
    Lee Kelleher
    0

    Hi Martin,

    I had a google for the ValueFactory exception message... turns out there's an issue with .NET's Lazy<T>, which Umbraco core is probably using, I'm not sure of the finer details of the inner workings.

    At this point my advice would be to remove the ToList() and have:

    var navigationNodes = helper.TypedContent(navigationIds.Split(','));
    

    See if that works without error.

    Failing that, if you can post the full stack-trace, that might help pinpoint the underlying issue.

    Thanks,
    - Lee

  • Martin 81 posts 246 karma points
    Oct 08, 2014 @ 11:40
    Martin
    0

    Hi Lee,

    Removing the ToList() from that line causes the exception to be thrown further down in the execution on the following line:

    if (navigationNodes != null && navigationNodes.Any())
    

    as soon as it tries to access the navigationNodes object.

    Perhaps you could share how you've accomplished common site elements such as Navigations in ditto projects you've worked on?

  • Lee Kelleher 4020 posts 15802 karma points MVP 13x admin c-trib
    Oct 08, 2014 @ 12:10
    Lee Kelleher
    0

    Hi Martin,

    On my recent sites, I've used Archetype for the navigation (rather than a MNTP). I use the same approach that I put in the gist:
    https://gist.github.com/leekelleher/513c48c634fc44729900


    I'm wondering if there is something suspect with the value of navigationIds - are you able to debug it and check the value, to make sure that it actually is a CSV (as opposed to an XML or JSON string ... I'm guessing here).

    Alternatively you could look at using Jeavon's Core PropertyValueConverters - as this will convert the MNTP value to an IEnumerable<IPublishedContent> collection. Then in your custom TypeConverter you will have access to that object type, rather than dealing with string-parsing and UmbracoHelper.

    Cheers,
    - Lee

  • Martin 81 posts 246 karma points
    Oct 08, 2014 @ 15:21
    Martin
    0

    Hi Lee,

    invalid navigationIds value was my first thought so I made sure that was correctly populated, which it is, the value is something like 1085,1087 which correctly matches the nodes selected in my MNTP.

    I can give you the stack trace when I get home from work. Really hoping we can solve this one as Ditto works beautifully with the bread and butter automatic model mapping.

  • Stephen 767 posts 2273 karma points c-trib
    Oct 15, 2014 @ 18:45
    Stephen
    1

    Joining the party, a bit late. We do definitively use Lazy a lot to run the converters. Would be really intersted in a detailed stack trace for the "value factory" exception. I think that what you're doing with the type converters etc... are pushing the core to the limits, which is fine ;-)

    Note: that exception basically means that the code that generates the value of the Lazytried to use that value. It could happen if a converter triggered some sort of loop code that would cause it to be called again... so... stack trace would be great!

    See: http://stackoverflow.com/questions/6300398/invalidoperationexception-in-my-lazy-value-factory

    See also: http://www.davesquared.net/2012/12/threading-and-lazy-t.html (or, I might have to check the LazyThreadSafetyMode of some of those Lazy)

    Please... Stack Trace!

  • Aaron Mitchard 7 posts 47 karma points
    Jan 05, 2015 @ 18:09
    Aaron Mitchard
    0

    Hi all,

    Just thought I'd add to this as I've stumbled across the same issue, first off here's my stack trace:

    [InvalidOperationException: ValueFactory attempted to access the Value property of this instance.]
       System.Lazy`1.CreateValue() +14033772
       System.Lazy`1.LazyInitValue() +524
       Umbraco.Core.Cache.HttpRequestCacheProvider.GetCacheItem(String cacheKey, Func`1 getCacheItem) +355
       Our.Umbraco.Ditto.DittoPublishedContentModelFactory.CreateModel(IPublishedContent content) +334
       Umbraco.Core.Models.PublishedContent.PublishedContentExtensionsForModels.CreateModel(IPublishedContent content) +109
       Umbraco.Web.PublishedCache.XmlPublishedCache.XmlPublishedContent..ctor(XmlNode xmlNode, Boolean isPreviewing, Boolean lazyInitialize) +288
       Umbraco.Web.PublishedCache.XmlPublishedCache.XmlPublishedContent.InitializeChildren() +399
       Umbraco.Web.PublishedCache.XmlPublishedCache.PublishedContentCache.ConvertToDocument(XmlNode xmlNode, Boolean isPreviewing) +186
       Umbraco.Web.PublishedCache.XmlPublishedCache.PublishedContentCache.GetById(UmbracoContext umbracoContext, Boolean preview, Int32 nodeId) +107
       Umbraco.Web.PublishedCache.ContextualPublishedCache`1.GetById(Boolean preview, Int32 contentId) +41
       Umbraco.Web.PublishedContentQuery.TypedDocumentById(Int32 id, ContextualPublishedCache cache) +43
       System.Linq.WhereSelectListIterator`2.MoveNext() +217
       System.Collections.Generic.List`1..ctor(IEnumerable`1 collection) +536
       System.Linq.Enumerable.ToList(IEnumerable`1 source) +80
       Bids.Core.TypeConverters.NavigationItemListTypeConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value) in c:\inetpub\wwwroot\Bids\Source\Bids.Core\TypeConverters\NavigationItemListTypeConverter.cs:29
       Our.Umbraco.Ditto.PublishedContentExtensions.As(IPublishedContent content, Type type, Action`1 convertingType, Action`1 convertedType) +1624
       Our.Umbraco.Ditto.<>c__DisplayClass4.<.ctor>b__1(IPublishedContent x) +25
       Our.Umbraco.Ditto.<>c__DisplayClass7.<CreateModel>b__6() +19
       System.Lazy`1.CreateValue() +14033772
       System.Lazy`1.LazyInitValue() +524
       Umbraco.Core.Cache.HttpRequestCacheProvider.GetCacheItem(String cacheKey, Func`1 getCacheItem) +355
       Our.Umbraco.Ditto.DittoPublishedContentModelFactory.CreateModel(IPublishedContent content) +334
       Umbraco.Core.Models.PublishedContent.PublishedContentExtensionsForModels.CreateModel(IPublishedContent content) +109
       Umbraco.Web.PublishedCache.XmlPublishedCache.XmlPublishedContent.InitializeStructure() +293
       Umbraco.Web.PublishedCache.XmlPublishedCache.PublishedContentCache.ConvertToDocument(XmlNode xmlNode, Boolean isPreviewing) +170
       Umbraco.Web.PublishedCache.XmlPublishedCache.PublishedContentCache.DetermineIdByRoute(UmbracoContext umbracoContext, Boolean preview, String route, Boolean hideTopLevelNode) +272
       Umbraco.Web.PublishedCache.XmlPublishedCache.PublishedContentCache.GetByRoute(UmbracoContext umbracoContext, Boolean preview, String route, Nullable`1 hideTopLevelNode) +228
       Umbraco.Web.Routing.ContentFinderByNiceUrl.FindContent(PublishedContentRequest docreq, String route) +247
       Umbraco.Web.Routing.ContentFinderByNiceUrl.TryFindContent(PublishedContentRequest docRequest) +228
       System.Linq.Enumerable.Any(IEnumerable`1 source, Func`2 predicate) +149
       Umbraco.Web.Routing.PublishedContentRequestEngine.FindPublishedContent() +326
       Umbraco.Web.Routing.PublishedContentRequestEngine.FindPublishedContentAndTemplate() +226
       Umbraco.Web.Routing.PublishedContentRequestEngine.PrepareRequest() +119
       Umbraco.Web.UmbracoModule.ProcessRequest(HttpContextBase httpContext) +509
       System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +80
       System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +165

     

    Let me know if you need anything else,

    Aaron.

  • Lee Kelleher 4020 posts 15802 karma points MVP 13x admin c-trib
    Jan 06, 2015 @ 11:38
    Lee Kelleher
    0

    Thanks Aaron,

    Hmmm, I'm wondering if it is getting caught in a loop too. As in the model that is trying to be created (via Ditto/ModelFactory) is trying to create a nested instance of itself... if that makes sense?

    Meaning that whatever the NavigationItemListTypeConverter.ConvertFrom logic is trying to do, (assuming that it is creating a list of IPublishedContent objects for links?), then it has a nodeId reference to the containing node.

    If that's the case, then I'm a worried, as I'm not sure of a resolution :-(

    Let me know what the values of the nodeId and the 'NavigationItemList' property.

    Thanks,
    - Lee

  • Aaron Mitchard 7 posts 47 karma points
    Jan 06, 2015 @ 12:21
    Aaron Mitchard
    0

    Hi Lee, thanks for the quick response.

    My next post was going to explain that :) You've got it spot on though I think its getting caught up in a loop because my BaseViewModel has a recursive UmbracoProperty for 'primaryNavigation' which uses a NavigationItemListTypeConverter. The Converter uses UmbracoHelper (to load the MNTP ids) which is where the issue is caused I think.

    I've got small fix in place I'm not massively happy with using the Our.Umbraco.PropertyEditorConverters, tweaking the Converter AND then manually new'ing up my BaseViewModel(ipublishedcontentnode) instead of using As<>().

    I was thinking of just pulling out all the navigation from my baseviewmodel into a simple service as there's potentially a lot of overhead, for example when loading up a list of child pages, it would end up populating all the navigation fields for each one...

    Make sense... any thoughts?

    Been away from Umbraco for a bit just getting back in to v7 and trying to pick a ModelBuilder (lots of options!) but liked the look of Ditto. 

     

  • Stephen 767 posts 2273 karma points c-trib
    Jan 06, 2015 @ 12:38
    Stephen
    0

    @Lee, @Aaron: looking at Aaron's exception stack... it definitively is entering a loop. It tries to get a content from the cache, which triggers the creation of a model for that content, and it seems that the creation of the model does in turn tries to get content from the cache... and at some point it tries to get a content that it is already getting.

    I don't see an easy resolution, apart from making sure to not access the cache when creating the model (ie don't access the cache in the ctor, do it lazily when the content is actually needed).

    Point is... the cache is not really re-entrant at that point. And I'm not sure how to make the XML cache re-entrant at that point.

    Would like to figure out whether this (a) is a constraint that can be accepted or (b) is going to be a blocking issue at some point.

  • Lee Kelleher 4020 posts 15802 karma points MVP 13x admin c-trib
    Jan 07, 2015 @ 11:21
    Lee Kelleher
    0

    Unfortunately not good news, it's looking more and more that Ditto doesn't play well with the ModelFactory approach.

    Due to Ditto using reflection to populate the POCO models, there is a performance hit when mapping the properties - this isn't really noticeable when doing a single node/POCO, but becomes much more noticeable when using the ModelFactory. Which is why I adding caching (using the RequestCache) to Ditto's ModelFactory implementation.

    See code comments here: DittoPublishedContentModelFactory.cs#L49

    (Which goes against Stephan's WARNING about factories caching models - sorry!)

    By removing the caching from here, I think, would resolve the looping issue... however the performance would be much worse - worse to the point that you'd think "What's the point in using Ditto's ModelFactory?"

    Unless someone knows of a better way?


    @Aaron - have you looked at Stephan's Zbu.ModelsBuilder as an alternative?

    Cheers,
    - Lee

  • Stephen 767 posts 2273 karma points c-trib
    Jan 07, 2015 @ 11:46
    Stephen
    1

    @Lee: to get the factory to handle loops, we'd need to keep track of which content has been converted already - which is complicated by the fact that the current XML cache does create a new instance of IPublishedContent each time you GetById(). So the cache keeps generating new objects - and that probably is the reason why you have implemented some cache in the factory, so that all those objects are mapped to the same model.

    Caching per request should be OK to some extend - not sure.

    There's no quick and easy fix to that one. I'm currently deep into refactoring the XML cache (and introducing a new cache) and working on all sorts of similar consistency questions...

    The solution would probably be to make sure that the cache returns one unique instance of an IPublishedContent. The current XML cache would make sure that it creates one instance per request, and the new cache would store instances directly. Each IPublishedContent would then be mapped only once, therefore you wouldn't need to cache in the factory, and we could implement loop-detection in the factory.

    Making sense?

  • Lee Kelleher 4020 posts 15802 karma points MVP 13x admin c-trib
    Jan 07, 2015 @ 11:56
    Lee Kelleher
    0

    @Stephan - makes sense and sounds good to me! :-)

    Thanks,
    - Lee

Please Sign in or register to post replies

Write your reply to:

Draft