Copied to clipboard

Flag this post as spam?

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


  • Mike Donahue 36 posts 158 karma points
    May 25, 2018 @ 17:44
    Mike Donahue
    0

    Upgrading to Ditto 9 and reworking Resolvers

    During a client upgrade to 7.10.4 we also Upgraded Ditto. Version 9 has some breaking changes and provides some documentation here: http://umbraco-ditto.readthedocs.io/en/latest/upgrade-090/

    Unfortunately, the site I inherited has a pretty complex usage of Ditto for pretty much eveything, and Im running into issues =- especially in regards to dependency injection and generics.

    Here is an example of what Im trying to convert:

    namespace Sample.Repository.Cms.ValueResolvers{
    public class SampleChildrenAttribute : DittoValueResolverAttribute
    {
        public SampleType SampleType { get; set; }
    
        public SampleChildrenAttribute(Type resolverType) : base(resolverType)
        {
            if (!(typeof(ISampleChildrenResolver).IsAssignableFrom(resolverType))) throw new Exception("Must use type which inherits SampleChildrenResolver");
        }   
    }
    
    public enum SampleType { Node, Top, Reduced } 
    
    public class SampleChildrenResolver<T> : DittoValueResolver, ISampleChildrenResolver
        where T : class
    {
        public override object ResolveValue()
        {
            SampleChildrenAttribute attr = this.Attribute as SampleChildrenAttribute;
            return this.Content.Children().Where(c => SampleNode(c, attr.SampleType)).As<T>().ToList();
        }
    
        private bool SampleNode(IPublishedContent content, SampleType type)
        {
            if (type == SampleType.Node) return content.HasProperty(UmbracoConstants.UmbracoAliases.SampleOptions.Alias);
            string val = content[UmbracoConstants.UmbracoAliases.SampleOptions.Alias] as string;
            bool hideFromMenu = (!content.HasProperty(UmbracoConstants.UmbracoAliases.HideFromMenu) || (content.HasProperty(UmbracoConstants.UmbracoAliases.HideFromMenu) && !(bool)content[UmbracoConstants.UmbracoAliases.HideFromMenu]));
            return content.HasProperty(UmbracoConstants.UmbracoAliases.SampleOptions.Alias) 
                && hideFromMenu
                && (val == null || !val.Contains(type == SampleType.Top ? UmbracoConstants.UmbracoAliases.SampleOptions.HideTop : 
                                                                                UmbracoConstants.UmbracoAliases.SampleOptions.HideReduced));
        }
    }
    
    public interface ISampleChildrenResolver
    {
        object ResolveValue();
    }}
    

    I gave it a shot, but it's more of a wild guess. Even though it compiles, the site is giving conversion errors. I figured it could benefit others if I bring my troubleshooting into the forum. I will continue to troubleshoot, and update this post, but I'd love some help with anyone who successfully navigated a Ditto upgrade if possible.

  • Mike Donahue 36 posts 158 karma points
    May 25, 2018 @ 19:52
    Mike Donahue
    0

    It seems the first issue is two fold:

    1) The previous implementation used a property annotation like [SampleChildren(typeof(SampleChildrenResolver), SampleType = SampleType.Node)] to also define the SampleType for reusability. I assume this will now change to [SampleChildrenResolver], and will attempt to pass the SampleType in the same manner.

    2) When converting a resolver from DittoValueResolver to DittoProcessorAttribute as documentation suggests, we lose the ability to use generic types.

    Here is a Model Example:

    public class SpecialItemLink : ItemLink
    {
        // pre upgrade tactic: [SampleChildren(typeof(SampleChildrenResolver), SampleType = SampleType.Node)]
    
        [SampleChildrenResolver]
        public IEnumerable<LinkBase> Children { get; set; }
        public string LinkDescription { get; set; }
    }
    

    Here is how I have updated it:

    public class SpecialItemLink : ItemLink
    {
        [SpecialChildrenResolver(typeof(SampleChildrenResolver), SampleType = SampleType.Node)]
        public IEnumerable<LinkBase> Children { get; set; }
        public string LinkDescription { get; set; }
    }
    

    And I converted the Resolver to look like:

    namespace client.Repository.Cms.ValueResolvers {
    public enum SampleType { Node, Top, Reduced } 
    
    public class SampleChildrenResolver : DittoProcessorAttribute, ISampleChildrenResolver
    
    {
    
        public SampleType SampleType { get; set; }
    
        public SampleChildrenResolver(Type resolverType)
        {
            if (!(typeof(ISampleChildrenResolver).IsAssignableFrom(resolverType))) throw new Exception("Must use type which inherits SampleChildrenResolver");
        }
    
        public override object ProcessValue()
        {
            var dataset = this.Context.Content.Children().Where(c => SampleNode(c, this.SampleType)).As<IPublishedContent>().ToList();
            return dataset;
        }
    
        private bool SampleNode(IPublishedContent content, SampleType type)
        {
            if (type == SampleType.Node) return content.HasProperty(UmbracoConstants.UmbracoAliases.SampleOptions.Alias);
            string val = content[UmbracoConstants.UmbracoAliases.SampleOptions.Alias] as string;
            bool hideFromMenu = (!content.HasProperty(UmbracoConstants.UmbracoAliases.HideFromMenu) || (content.HasProperty(UmbracoConstants.UmbracoAliases.HideFromMenu) && !(bool)content[UmbracoConstants.UmbracoAliases.HideFromMenu]));
            return content.HasProperty(UmbracoConstants.UmbracoAliases.SampleOptions.Alias)
                && hideFromMenu
                && (val == null || !val.Contains(type == SampleType.Top ? UmbracoConstants.UmbracoAliases.SampleOptions.HideTop :
                                                                                UmbracoConstants.UmbracoAliases.SampleOptions.HideReduced));
        }
    }
    
    public interface ISampleChildrenResolver
    {
        object ProcessValue();
    }}
    

    This allows compilation and debugging looks decent until I stumble across

    public IEnumerable<T> GetContentItems<T>(string ids, CultureInfo culture = null, IEnumerable<DittoProcessorContext> valueResolverContexts = null, Action<DittoConversionHandlerContext> onConverting = null, Action<DittoConversionHandlerContext> onConverted = null)
            where T : class
        {
            return _dittoService.ContentAs<T>(GetContentItems(ids));
        }
    

    Looks like the generic type isn't resolving.

    Prior to upgrading the code was:

    public IEnumerable<T> GetContentItems<T>(string ids, CultureInfo culture = null, IEnumerable<DittoValueResolverContext> valueResolverContexts = null, Action<DittoConversionHandlerContext> onConverting = null, Action<DittoConversionHandlerContext> onConverted = null)
            where T : class
        {
            return _dittoService.ContentAs<T>(GetContentItems(ids));
        }
    
  • Lee Kelleher 3850 posts 14420 karma points MVP 8x admin c-trib
    May 29, 2018 @ 09:58
    Lee Kelleher
    0

    Hi Mike,

    I'd been away at CodeGarden the past week, just got around to catching up with forum notifications.

    I'm not too sure what the issues, errors or breaking changes are - if you could post a specific error? (from the logs) ... then I might be able to help with that.

    I suspect that there are going to be several errors with upgrading Ditto for this inherited project/implementation. It looks slightly over-engineered, (no offence intended to the previous developers).

    I've tried to decipher the SampleChildrenResolver code ... but I'm not quite sure what it's meant to be doing - I assume that you've renamed it for this forum post?

    Thanks,
    - Lee

  • Mike Donahue 36 posts 158 karma points
    May 29, 2018 @ 13:28
    Mike Donahue
    0

    Thanks Lee, hope CodeGarden was awesome! Admittedly, my issue is being new to Ditto and also not acclimated with the project architecture fully - so while I agree; it appears over engineered, I'm sure they had their reasons (probably automated tests or something) right now the error is:

    Cannot convert IPublishedContent to System.String as it has no valid constructor. A valid constructor is either an empty one, or one accepting a single IPublishedContent parameter.

    This error happens when populating a model:

    ContentItems = _sampleService.GetContentItems<SpecialItemLink>(contentIds)
    

    the service is:

    public IEnumerable<T> GetContentItems<T>(string ids, CultureInfo culture = null, IEnumerable<DittoProcessorContext> valueResolverContexts = null, Action<DittoConversionHandlerContext> onConverting = null, Action<DittoConversionHandlerContext> onConverted = null)
                where T : class
            {
                var content = _dittoService.ContentAs<T>(GetContentItems(ids));
                return content;
            }
    

    That is where the error happens.

    For reference, as it stands the "SpecialItemLink" (yes things are renamed for some slight client and former agency obfuscation):

    public class LinkCardItemLink : ItemLink
        {
            [SpecialChildrenResolver(typeof(SampleChildrenResolver), SampleType = SampleType .Node)]
            public IEnumerable<LinkBase> Children { get; set; }
            public string LinkDescription { get; set; }
        }
    
  • Mike Donahue 36 posts 158 karma points
    May 29, 2018 @ 13:56
    Mike Donahue
    0

    Right now it would appear that replacing the DittoValueResolver with the v9+ DittoProcessorAttribute is preventing the generic type from being possible.

    Im not opposed to backing the complexity out of the project, but I'd like to avoid a complete rebuild of the core that utilizes Ditto.

  • Lee Kelleher 3850 posts 14420 karma points MVP 8x admin c-trib
    May 29, 2018 @ 14:12
    Lee Kelleher
    0

    re: Can't convert IPublishedContent to string - that's when Ditto finds that the property value is a node and the target type is a string. There's no direct conversion, so it throws an error. If it was another object-type, (with an empty-constructor) Ditto would try to convert it.


    re: generics - which part is causing you an issue? Is it the SampleChildrenResolver<T> code?

    If so, then that's being used to cast a bunch of child nodes in the ResolveValue method ... which it doesn't need to do, as child nodes can be returned (as IPublishedContent) and Ditto will then cast them to the target type.

    If it's the GetContentItems<T> method, I'm not sure where that lives - is it inside a processor, or in a helper class somewhere?

    Not sure if any of this helps?

    It's a bit tricky to offer advice, (v0.9.0 was released 2 years ago - after CodeGarden I'm struggling to remember what I had for breakfast today, haha)

    I'll try to help where I can.

    Cheers,
    - Lee

  • Mike Donahue 36 posts 158 karma points
    May 29, 2018 @ 14:48
    Mike Donahue
    0

    hehe, yeah 2 years of recollection is basically unheard of in tech.

    The error happens within a "DittoService" layer

    public IEnumerable<T> ContentAs<T>(IEnumerable<IPublishedContent> content, CultureInfo culture = null, IEnumerable<DittoProcessorContext> valueResolverContexts = null, Action<DittoConversionHandlerContext> onConverting = null, Action<DittoConversionHandlerContext> onConverted = null)
        where T : class
    {
        var data = content != null ? content.As<T>(culture, valueResolverContexts, onConverting, onConverted) : null;
        return data;
    }
    

    Most likely in the content.As To comply with the upgrade I changed the DittoValueResolverContext to DittoProcessorContext -> although it was kinda speculative since I couldn't find any documentation on either of those objects.

    The content variable is not null and does hold IPublishedContent items, but I think at some point it's expecting the SpecialItemLink type. When resolvers were used the SpecialItemLink was notated with (notice the LinkBase object allowing the generics to be fed):

    [SampleChildren(typeof(SampleChildrenResolver<LinkBase>), SampleType = SampleType.Node)]
    

    I really appreciate the help - and if there is an easier way to give you the full picture, I'm more than willing to accommodate.

  • Lee Kelleher 3850 posts 14420 karma points MVP 8x admin c-trib
    May 29, 2018 @ 15:19
    Lee Kelleher
    0

    With the ContentAs<T> helper method - do you know if all the optional params are being used in your codebase? If not, then try removing them.

    Passing in the processor-context object is quite advanced usage, my guess is that it isn't being used. But anyway...

    Side note, re: docs ... I think the processor-context stuff was added after v0.9.x, and didn't update the docs. We kind of made a lot of changes in a short space of time ... hence keeping the version number at zero-point.

    But that doesn't help you right now though :-(


    re: generics - I'm not sure what else to say, other than I don't think it's needed in this scenario.

    You could try this instead...

    public override object ProcessValue()
    {
        var dataset = this.Context.Content.Children().Where(c => SampleNode(c, this.SampleType));
        return dataset;
    }
    

    ... removing the .As<IPublishedContent>() part.

  • Mike Donahue 36 posts 158 karma points
    May 29, 2018 @ 16:07
    Mike Donahue
    0

    Bugger. It does appear to use that context. Here is an example:

    public BreadcrumbModel Breadcrumb()
            {
                var currentPage = _umbHelper.AssignedContentItem;
    
                return _umbDittoService.ContentAs(currentPage, **instance: new BreadcrumbModel()**
                {
                    BreadcrumbLinks = _umbDittoService.ContentAs<LinkBase>(currentPage.Ancestors().OrderBy(n => n.Level))
                });
            }
    
  • Lee Kelleher 3850 posts 14420 karma points MVP 8x admin c-trib
    May 29, 2018 @ 16:22
    Lee Kelleher
    0

    As far as I can tell, the ContentAs<T> helper method is purely checking for null references, I'd be tempted to scrap it.

    In fact, I'd be removing all the _umbDittoService references (but I say that not knowing what else it can do). Just waiting for someone to jump in and screen "Unit Testing" at me here. ;-)

    Replace it with something like this...

    public BreadcrumbModel Breadcrumb()
    {
        var currentPage = _umbHelper.AssignedContentItem;
        var ancestors = currentPage.Ancestors().OrderBy(n => n.Level);
        var model = new BreadcrumbModel
        {
            BreadcrumbLinks = ancestors.As<LinkBase>()
        };
    
        return currentPage.As(instance: model)
    }
    

    The BreadcrumbModel object-type should be inferred in the .As(instance) call.

  • Mike Donahue 36 posts 158 karma points
    May 29, 2018 @ 18:10
    Mike Donahue
    0

    Awesome! That makes sense, and wasn't too monumental. Is there anything I need to do to directly handle chained elements to resolve the following error:

    [InvalidOperationException: Cannot convert IPublishedContent to System.String as it has no valid constructor. A valid constructor is either an empty one, or one accepting a single IPublishedContent parameter.]
    Our.Umbraco.Ditto.PublishedContentExtensions.ConvertContent(IPublishedContent content, Type type, IDittoContextAccessor contextAccessor, CultureInfo culture, Object instance, Action1 onConverting, Action1 onConverted, DittoChainContext chainContext) +2622
    Our.Umbraco.Ditto.PublishedContentExtensions.As(IPublishedContent content, Type type, CultureInfo culture, Object instance, IEnumerable1 processorContexts, Action1 onConverting, Action`1 onConverted, DittoChainContext chainContext) +969
    Our.Umbraco.Ditto.RecursiveDittoAttribute.ProcessValue() +236

    For a method like:

    public FooterModel Footer()
            {
                var footer = _umbHelper.AssignedContentItem.AncestorsOrSelf()
                    .FirstOrDefault(k => k.HasProperty(UmbracoConstants.UmbracoAliases.FooterNavigationProperty));
                return footer != null ? footer.As<FooterModel>() : null;
            }
    

    And an object like so:

    public class FooterModel
        {
            public IEnumerable<LinkBase> FooterNavigation { get; set; }
            public IEnumerable<Link> Subsidiaries { get; set; }
            public IEnumerable<Link> TermsUrl { get; set; }
            public IEnumerable<Link> LegalUrl { get; set; }
            public IEnumerable<Link> GooglePlusUrl { get; set; }
            public IEnumerable<Link> FacebookUrl { get; set; }
            public IEnumerable<Link> LinkedinUrl { get; set; }
            public IEnumerable<Link> TwitterUrl { get; set; }
            public IEnumerable<Link> YoutubeUrl { get; set; }
            [UmbracoDictionary(UmbracoConstants.Dictionary.FooterCopyrightText)]
            public string Copyright { get; set; }
            [UmbracoDictionary(UmbracoConstants.Dictionary.FooterSubsidiaryText)]
            public string SubsidiaryText { get; set; }
    
            [CurrentContentAs]
            public ConnectWithClientModel ConnectWithClient { get; set; }
    
        }
    

    In this case, the correct home node is being captured by the footer variable. And there is a doc type called "Footer" used on that template.

  • Lee Kelleher 3850 posts 14420 karma points MVP 8x admin c-trib
    May 30, 2018 @ 09:45
    Lee Kelleher
    0

    Hmmm... the "Cannot convert IPublishedContent to System.String as it has no valid constructor" error means that a property value is an IPublishedContent and the target object-type (on the POCO) is a string, which Ditto can't map to, (due to a string not having an empty constructor).

    It'd be a case of trying to figure out which property (on the POCO) it is failing on and why that might be.

  • Mike Donahue 36 posts 158 karma points
    May 30, 2018 @ 16:08
    Mike Donahue
    0

    Ok, I think I need to backup a second. For the following class, I specify a resolver:

    public class LinkCardItemLink : ItemLink
        {
            [NavigationChildrenResolver(typeof(NavigationChildrenResolver), NavigationType = NavigationType.Node)]
            public IEnumerable<LinkBase> Children { get; set; }
            public string LinkDescription { get; set; }
        }
    

    Then to populate a model property for the view (This is a macro directly within the template actually), I call a function:

    GetContentItems<LinkCardItemLink>(contentIds)
    

    This function receives a generic type and calls another function to theoretically transform a list of IPublished content into the whatever generic is needed (in this case "LinkCardItemLink").

    Basically uses Ditto to process a return of:

    GetContentItems(ids).As<T>();
    

    The LinkCardItemLink class is decorated with the resolver, but I'm not entirely sure how to get the resolving process to start (I would think the "As" method would kick it off) - the break point in the custom resolver does not get hit during this process.

    The resolver does get hit later in the process but Im still tracking down where that working call is coming from.

  • Lee Kelleher 3850 posts 14420 karma points MVP 8x admin c-trib
    May 30, 2018 @ 16:16
    Lee Kelleher
    0

    OK, I'll try to explain what Ditto is doing.

    When the .As<T> call is made, Ditto will take the T object-type and loop over all the settable properties and attempt to map a value from the associated IPublishedContent to the target property ... it does this using processors (or in old versions, value-resolvers).

    In the case of LinkCardItemLink, Ditto will loop over the properties, it'll get to the Children property, check if it has a processor, if so, it uses that. (If not, there's a default processor for calling .GetPropertyValue()).

    Hope this is making sense so far?

    Cheers,
    - Lee

  • Mike Donahue 36 posts 158 karma points
    May 30, 2018 @ 16:32
    Mike Donahue
    0

    Makes sense. But in the case of LinkCardItemLink, wouldn't the DittoProcessorAttribute be hit because of the (poorly named - it is a processor attribute instead of former resolver) NavigationChildrenResolver decoration?

  • Lee Kelleher 3850 posts 14420 karma points MVP 8x admin c-trib
    May 30, 2018 @ 16:35
    Lee Kelleher
    0

    The NavigationChildrenResolver processor would be called when Ditto is processing the Children property, as that's what the attribute is associated to.

  • Mike Donahue 36 posts 158 karma points
    May 30, 2018 @ 16:41
    Mike Donahue
    0

    That's what I would expect as well, but the break point does not get hit.

    GetContentItems(ids).As<T>();
    

    returns with a conversion error and appears that Ditto is trying to process this without the custom processor.

    The process does eventually get called (hits breakpoint) when the view is processing:

    foreach (var item in Model.Items)
    
  • Lee Kelleher 3850 posts 14420 karma points MVP 8x admin c-trib
    May 30, 2018 @ 16:47
    Lee Kelleher
    0

    Ah that'd be IEnumerable<T>'s deferred execution, (some call it a feature) ;-) the code will only be called when the items are evaluated.

    If you wanted to force the evaluation, then you could try adding a ToList() on the end...

    GetContentItems(ids).As<T>().ToList();
    

    But still that'll only hit your break-point once all the items have been processed.

    Gah, I bet you're disliking Ditto at the moment. All I can say is that some implementations can push libraries to their extremes.

  • Mike Donahue 36 posts 158 karma points
    May 30, 2018 @ 16:52
    Mike Donahue
    1

    Lol, I see the value for sure! But I appreciate your help, and will definitely be supporting your contributions through https://www.patreon.com/umco.

  • Mike Donahue 36 posts 158 karma points
    May 30, 2018 @ 21:57
    Mike Donahue
    0

    I seem to be making progress. Another hurdle is prior to version upgrade we had:

    [TypeConverter(typeof(DittoPickerConverter))]
        public class SomeMedia : SMedia
        {
            [CropUrl(width: 263, height: 353, Mode = ImageCropMode.Crop)]
            public override string CropUrl { get; set; }
    
        }
    

    which we updated based on Ditto documentation to:

    [UmbracoPicker]
        public class SomeMedia : SMedia
        {      
            [CropUrl(width: 263, height: 353, Mode = ImageCropMode.Crop)]
            public override string CropUrl { get; set; }
    
        }
    

    This seems to be the source of numerous conversion issues. Any road tested advice on media and crop ?

  • Lee Kelleher 3850 posts 14420 karma points MVP 8x admin c-trib
    May 31, 2018 @ 07:58
    Lee Kelleher
    0

    Hmmm, it looks correct... but it could be that the POCO property that is of SomeMedia type might have a different processor - as opposed to the default (e.g. without attribute) aka [UmbracoProperty] - so then it wouldn't be getting the value.

    The idea with the processors was that they could be chained, and they'd each have a single purpose. (Previously the various TypeConverters/ValueResolvers were doing multiple things).

  • Mike Donahue 36 posts 158 karma points
    May 31, 2018 @ 13:27
    Mike Donahue
    0

    Oh alright. So the SMedia class that is inherited is actually the culprit.

    [UmbracoPicker]
        public class SMedia : PageBase
        {
            [UmbracoProperty(Constants.Conventions.Media.Bytes)]
            public int Bytes { get; set; }
            [CropUrl(width: 94, height: 94, Mode = ImageCropMode.Crop)]
            public virtual string CropUrl { get; set; }
            [CropUrl(width: 202, height: 202, Mode = ImageCropMode.Crop)]
            public virtual string MidCropUrl { get; set; }
            [UmbracoProperty(Constants.Conventions.Media.Extension)]
            public string Extension { get; set; }
        }
    

    When I [DittoIgnore] the two virtual strings (CropUrl and MidCropUrl) the rest of the properties can map.

    I cant imagine there would be a problem using virtual strings though is there?

  • Mike Donahue 36 posts 158 karma points
    May 31, 2018 @ 13:44
    Mike Donahue
    0
       [CropUrl(width: 202, height: 202, Mode = ImageCropMode.Crop)]
    

    Oh, I was unaware that this was a custom processor (formerly resolver) on our end. sheesh, this goes deeeep! I think we're close here!

    The Resolver used to be:

     public class CropUrlAttribute : DittoValueResolverAttribute
        {
            public int? Width { get; private set; }
            public int? Height { get; private set; }
            public ImageCropMode Mode { get; set; }
    
            public CropUrlAttribute(int limit, bool widthLimit = true) : base(typeof(CopyUrlResolver))
            {
                if (widthLimit)
                    this.Width = limit;
                else
                    this.Height = limit;
            }
    
            public CropUrlAttribute(int width, int height) : base(typeof(CopyUrlResolver))
            {
                this.Width = width;
                this.Height = height;
            }
        }
    
        public class CopyUrlResolver : DittoValueResolver
        {
            public override object ResolveValue()
            {
                CropUrlAttribute attr = this.Attribute as CropUrlAttribute;
                return this.Content.GetCropUrl(width: attr.Width, height: attr.Height, imageCropMode: attr.Mode);
            }
        }
    

    The we replaced it with the following - althoug I was guessing because I have no real idea what DittoMultiProcessorAttribute is but it at least let me compile. Although it looks like the "CopyUrlResolver" needs adjusting some how.

    public class CropUrlAttribute : DittoMultiProcessorAttribute
        {
            public int? Width { get; private set; }
            public int? Height { get; private set; }
            public ImageCropMode Mode { get; set; }
    
            public CropUrlAttribute(int limit, bool widthLimit = true) : base(Enumerable.Empty<DittoProcessorAttribute>())
            {
                if (widthLimit)
                    this.Width = limit;
                else
                    this.Height = limit;
            }
    
            public CropUrlAttribute(int width, int height) : base(Enumerable.Empty<DittoProcessorAttribute>())
            {
                this.Width = width;
                this.Height = height;
            }
        }
    
        public class CopyUrlResolver : DittoProcessorAttribute
        {
            public override object ProcessValue()
            {
                CropUrlAttribute attr = this.Value as CropUrlAttribute;
                return this.Context.Content.GetCropUrl(width: attr.Width, height: attr.Height, imageCropMode: attr.Mode);
            }
        }
    
  • Lee Kelleher 3850 posts 14420 karma points MVP 8x admin c-trib
    May 31, 2018 @ 14:10
    Lee Kelleher
    100

    Yup, it's looking pretty deep! Yikes, "DITTO ALL THE THINGS!" eek!

    With the CropUrl, it doesn't need the DittoMultiProcessorAttribute ... try something like this...

    public class CropUrlAttribute : DittoProcessorAttribute
    {
        public int Width { get; set; }
        public int Height { get; set; }
        public ImageCropMode Mode { get; set; }
    
        public CropUrlAttribute(int limit, bool widthLimit = true)
        {
            if (widthLimit)
                this.Width = limit;
            else
                this.Height = limit;
        }
    
        public CropUrlAttribute(int width, int height)
        {
            this.Width = width;
            this.Height = height;
        }
    
        public override object ProcessValue()
        {
            return this.Value.GetCropUrl(width: this.Width, height: this.Height, imageCropMode: this.Mode);
        }
    }
    
  • Mike Donahue 36 posts 158 karma points
    Jun 08, 2018 @ 12:35
    Mike Donahue
    0

    Thanks again for all your help Lee. This thread got me sorted out :)

  • Lee Kelleher 3850 posts 14420 karma points MVP 8x admin c-trib
    Jun 08, 2018 @ 12:36
    Lee Kelleher
    0

    Cool, good to hear you got there in the end! :-)

    Cheers,
    - Lee

Please Sign in or register to post replies

Write your reply to:

Draft