Copied to clipboard

Flag this post as spam?

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


  • shinsuke nakayama 109 posts 250 karma points
    Aug 07, 2014 @ 10:04
    shinsuke nakayama
    0

    UmbracoMapper and ArcheType

    Hi guys,

    Firstable I love what you've done with the Umbraco Mapper. This is exactly I was looking for.
    However does this work with the Archetype?

    What I’m doing is I'm hijacking the routes (MVC Request) and inserting the CMS values into my own ViewModel.
    It is all working good till when I get to the ArcheType object. (I got it working but I have to convert the ArchetypeModel object to the Dictionary<string, object>, then call mapper.MapCollection)

    Is there any easier ways of doing this?

    The web site I'm building is 1 pagers app and it is 1 long page. So I have about 15 set of ArcheType objects. I prefer not to convert ArcheType object to Dictionary<string, object> for all 15 of them.
    Preferably I can just convert everything with Mapper.map method.

    Thank you

  • Jeroen Breuer 4908 posts 12265 karma points MVP 4x admin c-trib
    Aug 07, 2014 @ 10:30
    Jeroen Breuer
    0

    I haven't used UmbracoMapper yet, but when you use the Models Builder it will return a strongly typed archetype model because it goes through the property editor value converter.

    Jeroen

  • Andy Butland 422 posts 2333 karma points MVP 4x hq c-trib
    Aug 07, 2014 @ 10:56
    Andy Butland
    0

    Hi Shinsuke

    Haven't yet had chance to use Archetype, but we're starting a project with it as we speak so it's likely we'll have to solve this issue ourselves shortly!  So I'll look into it in a bit more detail when I get a little bit of time and post back here.

    However I suspect the best way will be look into the custom mappings you can add: https://github.com/AndyButland/UmbracoMapper#custom-mappings - the intention of this is to handle the mapping too more complex types on your view model other than just primitives like strings and ints.  So in the example linked, there's a custom mapping for a GeoCoordinate complex type that's associated with the view model, that is mapped using a given function.

    Would you mind sharing your view model class though so I can see what you are looking to map too?

    Cheers

    Andy

     

  • shinsuke nakayama 109 posts 250 karma points
    Aug 07, 2014 @ 13:28
    shinsuke nakayama
    0

    Hi guys,

    Thank you for your reply.

     

    //Jeroen,

    thank you, I'll look into this "Models Builder", it sounds interesting. I'm quite new in Umbraco so i'm still trying out alot of things.

     

    //Andy

    Thank you for the link, I was reading this but i wasn't sure this was the best way, but let me have a go.

    Mean while, this is how my ViewModel and Controller code look like. It's still under the prototype stage so It's not too complex yet

     

    public class HomepageViewModel

    {

    public string FeatureImageUrl { get; set; }

    public string HomepageTitle { get; set; }

    public string HomepageDescription { get; set; }

    public IList<CtaType1> CtaModule { get; set; }

    public IList<CtaFooter> FooterCta1 { get; set; }

    public IList<CtaFooter> FooterCta2 { get; set; }

    }

     

    public class CtaFooter

    {

    public string Title { get; set; }

    public string ButtonText { get; set; }

    public string Link { get; set; }

    public string Image { get; set; }

    }

     

    public class CtaType1

    {

    public string ButtonText { get; set; }

    public string Link { get; set; }

    }

     

    //Controller code

    var footer1 = ArcheTypeToDictionary(model.Content, "footerCta1");

    var footer2 = ArcheTypeToDictionary(model.Content, "footerCta2");

    var ctaModule = ArcheTypeToDictionary(model.Content, "ctaModule");

     

    var mapper = new UmbracoMapper();

    var model1 = new HomepageViewModel()

    {

    FooterCta1 = new List<CtaFooter>(),

    FooterCta2 = new List<CtaFooter>(),

    CtaModule = new List<CtaType1>()

    };

    mapper.Map(CurrentPage, model1);

     

    mapper.MapCollection(footer1, model1.FooterCta1);

    mapper.MapCollection(footer2, model1.FooterCta2);

    mapper.MapCollection(ctaModule, model1.CtaModule);

     

    //Base Controller class

                    protected IList<Dictionary<string, object>> ArcheTypeToDictionary(IPublishedContent content, string alias)

    {

    var archetypeObject = content.GetPropertyValue<ArchetypeModel>(alias);

    return archetypeObject.Select(item => item.Properties.ToDictionary(m => StringHelper.FirstCharToUpper(m.Alias), m => m.Value)).ToList();

    }

  • Raffaele 3 posts 47 karma points
    Aug 07, 2014 @ 16:00
    Raffaele
    101

    Hi Shinsuke,

     as a follow up to Andy's answer (the project we're working on combines umbracoMapper and archetypes), this is what I would do:

         0.  Define a new class

    public class CtaFooterList
    {
    public IListItems { get; set; }
    }

    and change FooterCta1 and FooterCta2 in HomepageViewModel to

    public CtaFooterList FooterCta1 { get; set; }
    public CtaFooterList FooterCta2 { get; set; }

    1. After the mapper initialisation

    var mapper = new UmbracoMapper();

    add the "AddCustomMapping" method

    mapper.AddCustomMapping(typeof(CtaFooterList).FullName, ArchetypeMapper.GetCtaFooterListArchetype);

    with

    public class ArchetypeMapper
    {
    public static object GetCtaFooterListArchetype(IUmbracoMapper mapper, IPublishedContent contentToMapFrom, string propertyName, bool recursive)
    {
    var archetypeObject = content.GetPropertyValue(propertyName, recursive);
    var dictionaryList = archetypeObject.Select(item => item.Properties.ToDictionary(m => StringHelper.FirstCharToUpper(m.Alias), m => m.Value));
    var ctaFooter = new CtaFooterList();
    mapper.MapCollection(dictionaryList , ctaFooter.Items);
    return ctaFooter;
    }
    }

    (Do the same for CtaType1)

    2.(I haven't tested the code, but now) When you type

    mapper.Map(CurrentPage, model1);

    model1.FooterCta1, model1.FooterCta2, model1.CtaModule should get automatically mapped

     

    In terms of code lentgh it's probably the same as your solution. but if you use custom mapping your controllers get much tidier!

     

    Raffaele

  • Andy Butland 422 posts 2333 karma points MVP 4x hq c-trib
    Aug 07, 2014 @ 16:27
    Andy Butland
    0

    Thanks Raffaele - should just add we tend to put the instantantion and the set up of custom mappings of Umbraco Mapper in the base controller, and expose it as a property.  That way as Raffaele says much of this plumbing code can be tucked away and the setup will be the same wherever it's used around the application.

    Andy

  • Andy Butland 422 posts 2333 karma points MVP 4x hq c-trib
    Aug 07, 2014 @ 23:57
    Andy Butland
    1

    As mentioned have given this a little more thought, and basically I think what's suggested above is probably the way to go - however I think we can tidy it up a bit and avoid creating that additional class to hold the items (using instead an IEnumerable).  Rather than posting more code into this thread, I've written it up here.

    Andy

     

     

  • shinsuke nakayama 109 posts 250 karma points
    Aug 08, 2014 @ 01:45
    shinsuke nakayama
    0

    Thank you guys for the feedbacks and solutions. I will definitely try out the CustomMapping today.

    //Andy Very nice article :)

  • shinsuke nakayama 109 posts 250 karma points
    Aug 08, 2014 @ 07:11
    shinsuke nakayama
    0

    Hi Guys,

    Just a quick update on my mapper class. I made the mapper class more generic so different archetype object can use the same mapping class, but also allows you to extend it.

    //Base Controller

    Mapper = new UmbracoMapper();
    Mapper.AddCustomMapping(typeof(IEnumerable<CtaFooter>).FullName, ArchetypeMapper.GenericArchetypeMapper<CtaFooter>);
    

    //ArchetypeMapper

    public class ArchetypeMapper
        {
            public static IEnumerable<T> GenericArchetypeMapper<T>(IUmbracoMapper mapper, IPublishedContent contentToMapFrom, string propertyName, bool recursive) where T : class, new()
            {
                var result = ConvertToDto<T>(mapper, contentToMapFrom, propertyName, recursive);
                return result;
            }
    
            public static IEnumerable<T> SomeOtherArchetypeMapper<T>(IUmbracoMapper mapper, IPublishedContent contentToMapFrom, string propertyName, bool recursive) where T : class, new()
            {
                var result = ConvertToDto<T>(mapper, contentToMapFrom, propertyName, recursive);
    
                //can also do extra mapping / logic here, or can pass in PropertyMapping dictionary to extend it
                return result;
            }
    
            private static IEnumerable<T> ConvertToDto<T>(IUmbracoMapper mapper, IPublishedContent contentToMapFrom, string propertyName, bool recursive) where T : class, new()
            {
                var result = new List<T>();
                var archetypeObject = contentToMapFrom.GetPropertyValue<ArchetypeModel>(propertyName, recursive);
                if (archetypeObject != null)
                {
                    var dictionary = archetypeObject.Select(item => item.Properties.ToDictionary(m => StringHelper.FirstCharToUpper(m.Alias), m => m.Value)).ToList();
                    mapper.MapCollection(dictionary, result);
                }
                return result;
            }
        }
    

    Cheers

    Shinsuke

  • Andy Butland 422 posts 2333 karma points MVP 4x hq c-trib
    Aug 08, 2014 @ 11:19
    Andy Butland
    0

    Thanks for sharing that, it's a better approach to make it generic as you say.

    Just wanted to add that I found a way to support picked content in the Archetypes too now, which the just updated version of the mapper package will support.  Details here: http://web-matters.blogspot.it/2014/08/using-umbraco-mapper-with-archetype_8.html

    Cheers

    Andy

  • shinsuke nakayama 109 posts 250 karma points
    Aug 08, 2014 @ 16:05
    shinsuke nakayama
    0

    Thank you Andy for the update,

    This was actual my next step. But dealing with "Media Picker".

    I was going to use "Umbraco.Media()" then map the values.

    var media = Umbraco.Media(model.Content.GetPropertyValue("featureImage"));
    

    Have you guys done this already? i saw something similar in the Damp mapper class.

  • Andy Butland 422 posts 2333 karma points MVP 4x hq c-trib
    Aug 09, 2014 @ 11:35
    Andy Butland
    0

    Yes, we set up a similar custom mapping to a complex type on our view models called something like MediaFile - with properties for URL, width, height etc.

    There's an example for the DAMP model as you say in the related project on GitHub.  That's good for V6 but for V7 you'll probably not be using that.  It's the same principle though - set up a custom mapping for the type on your view model, and pass a function you implement that maps the media properties you need from Umbraco.TypedMedia.

    Andy

  • shinsuke nakayama 109 posts 250 karma points
    Aug 10, 2014 @ 14:01
    shinsuke nakayama
    0

    Thank you Andy for the advice, I created a simple mapper that maps Umbraco.Media(id) object to the MediaFile Object.

    It seems like its all working.

    Cheers

    Shinsuke

  • shinsuke nakayama 109 posts 250 karma points
    Aug 11, 2014 @ 04:42
    shinsuke nakayama
    0

    Hi Andy,

    I've just updated the Umbraco Mapper to the latest and trying to map the media Picker inside the Archetype and I'm getting an exception.

    Line 77:                     throw new ArgumentNullException("model", "Object to map to cannot be null");
    Source File: c:\Development\Reference\Github\UmbracoMapper\Zone.UmbracoMapper\UmbracoMapper.cs    Line: 77 
    

    So I debugged the code and the property.GetValue(model) was returning null @ UmbracoMapper.cs (line 248 ish)

    if (dictionary[propName] is IPublishedContent)
    {
        Map((IPublishedContent)dictionary[propName], property.GetValue(model), propertyMappings);
    }
    

    It works if i remove the

    property.GetValue()
    

    I'm mapping the property into "Zone.UmbracoMapper.MediaFile" , not a string

    Also "propertyMappings" property is null.

    My code looks the same from your Blog, but i added "Umbraco.MediaPicker" and getting mapped into MediaFile

            switch (archetypeProperty.PropertyEditorAlias)
            {
                case "Umbraco.ContentPickerAlias":
                case "Umbraco.MediaPicker":
                    return archetypeProperty.GetValue<IPublishedContent>();
                    break;
    

    Thank you

    Shinsuke

    //EDIT

    Sorry i just looked at your BLOG again and I found one more difference.

    I have MediaFile property object in the sub class. so in your example, it will look like this

    public class MatchReportTeaser
    {
        public string Name { get; set; }
        public string Url { get; set; }
        public MediaFile Image {get; set;}
    }
    
  • Andy Butland 422 posts 2333 karma points MVP 4x hq c-trib
    Aug 11, 2014 @ 07:04
    Andy Butland
    0

    Hi Shinsuke

    The exception you are seeing is a custom one being thrown by the mapper if the object you are looking to map to is null - it can't handle that.  We found issues with trying to to new it up in the mapper if it was null.  

    I found we couldn't do this:

        // Ensure model is not null
        if (model == null)
        {
            model = new T();
        }

    Gives compiler error as we can't know for sure that T has a parameterless constructor.

    So instead we just do this:

        // Ensure model is not null
        if (model == null)
        {
            throw new ArgumentNullException("model", "Object to map to cannot be null");
        }

    ---

    The way we work around it is to ensure the object or collection you are mapping to is not null, by doing something like this in our view models:

        public class MatchReportTeaser
        {
            public MatchReportTeaser()
            {
                Image = new MediaFile();
            }
            public string Name { get; set; }
            public string Url { get; set; }
            public MediaFile Image { get; set; }
        }

    The only issue with that then is of course you cannot test for null say in your view, if you want to determine whether or not you have an image file to render.  Instead you need to check say if the Url property of the media file is populated.

    Andy

  • shinsuke nakayama 109 posts 250 karma points
    Aug 11, 2014 @ 07:53
    shinsuke nakayama
    0

    Ooooh Thank you very much,

    I don't know how i missed that (Implementing constructor on the model object).

    But that worked :)

    I don't mind implementing like this, purpose of the controller in this instance is to convert the Umbraco object to my custom View Model. Which is exactly what this is doing :)

    I just need to implement something like this in the Model

    public bool HasImage
    {
        get { return !(String.IsNullOrEmpty(this.Url)); }
    }
    

    To make the View if Condition easier.

    Thank you very much for your help.

    Your Umbraco Mapper is awesome.

  • Daniel Bardi 927 posts 2562 karma points
    Sep 11, 2014 @ 20:45
    Daniel Bardi
    0

    We use a custom Image model that has a crops list attached. Here are the mappers we use.

    Second overload can be called directly when mapping media items used in an Archetype

    public static object ImageMapper(IUmbracoMapper mapper, IPublishedContent content, string name, bool recursive)
    {
        return ImageMapper(mapper, content.GetProperty(name).Value);
    }
    public static object ImageMapper(IUmbracoMapper mapper, object value)
    {
        var mediaIdStr = value as string;
        if (mediaIdStr == null) return new Image();
    
        var mediaId = int.Parse(mediaIdStr);
        var image = ModelCache.GetCachedModel(mediaId);
        if (image != null) return image;
    
        // Map an Image from an IPublishedContent in MediaCache
    
        IPublishedContent mediaContent = UmbracoContext.Current.MediaCache.GetById(mediaId);
    if (mediaContent == null || mediaContent.DocumentTypeAlias != "Image") { return new Image(); } image = new Image(); image.Crops.AddRange(new[] { new ImageCrop("thumb", mediaContent.GetCropUrl("thumb")), new ImageCrop("pod", mediaContent.GetCropUrl("pod")), new ImageCrop("hero", mediaContent.GetCropUrl("hero")) }); mapper.Map(mediaContent, image); if (String.IsNullOrEmpty(image.AltText)) { image.AltText = image.Name.ToLower().Replace(" ", "-"); } image.Cache(mediaContent.Id); return image; }
Please Sign in or register to post replies

Write your reply to:

Draft