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
    Sep 05, 2014 @ 05:20
    shinsuke nakayama
    0

    Custom Mapping doesn't seem to work for the property types in Archetype

    Hi guys,

    I'm trying to custom map the property in the Archetype, but it doesn't seems to work.

    I've got similar scenario from Andy's blog here

    http://web-matters.blogspot.com.au/2014/08/using-umbraco-mapper-with-archetype_8.html

    But difference is instead of

    MatchReportTeaser.Url
    

    I have

    MatchReportTeaser.Link
    

    and I have this in the Base controller

    BaseViewModel.Mapper.AddCustomMapping(typeof(ContentPickerDataModel).FullName, ContentPickerMapper.Map<ContentPickerDataModel>);
    
    BaseViewModel.Mapper.AddCustomMapping(typeof(IEnumerable<ContentPickerDataModel>).FullName, ContentPickerMapper.MapMultiple<ContentPickerDataModel>);
    

    I tried to debug the code and for the properties in the Archetype, it only seems to map by _customObjectMappings or by property name mapping? and not the custom mapping

    Just wondering why is this?

    Shinsuke

  • shinsuke nakayama 109 posts 250 karma points
    Sep 05, 2014 @ 07:05
    shinsuke nakayama
    0

    I've just added following code in the UmbracoMapper.cs line 277ish and it seems it's working.

    if (_customObjectMappings.ContainsKey(namedCustomMappingKey))
    { ... }
    else if (_customObjectMappings.ContainsKey(unnamedCustomMappingKey))
    { ... }
    else if (dictionary[propName] is IPublishedContent && _customMappings.ContainsKey(namedCustomMappingKey))
    {
        var value = _customMappings[namedCustomMappingKey](this, (IPublishedContent)dictionary[propName], propName, false);
        if (value != null)
        {
            property.SetValue(model, value);
        }
    }
    else if (dictionary[propName] is IPublishedContent && _customMappings.ContainsKey(unnamedCustomMappingKey))
    {
        var value = _customMappings[unnamedCustomMappingKey](this, (IPublishedContent)dictionary[propName], propName, false);
        if (value != null)
        {
            property.SetValue(model, value);
        }
    }
    else if (dictionary[propName] is IPublishedContent)
    { ... }
    else ...
    

    I'll run more test to see this breaks anything but do you see any reason i can't do this here?

  • Andy Butland 422 posts 2333 karma points MVP 4x hq c-trib
    Sep 06, 2014 @ 15:29
    Andy Butland
    0

    Hi Shinsuke

    I don't see any reason why what you have wouldn't work, but I think it's actually duplicating something that's already there - so I'm not quite sure why what you were originally trying wasn't working.

    As you've seen for mappings from IPublishedContent you can supply a custom mapping.

    We also - leading from our previous conversations - support mapping from a dictionary of <string, object> which you can use for Archetype mappings.

    In the mapping from the dictionary - here - we have a check to see if the object you are mapping from is actually itself an IPublishedContent.  If it is, we then call the Map() override that takes an IPublishedContent - and that process should pick up any custom mappings you have defined.  So you shouldn't need these additional checks you've added in the code above, as they are already in the Map() method that gets called.

    I'll try to replicate the scenario and take a look.  Will be on holiday next week from Monday so hopefully before I go, but if not and no further response that'll be why.

    Cheers

    Andy

     

  • Andy Butland 422 posts 2333 karma points MVP 4x hq c-trib
    Sep 06, 2014 @ 15:50
    Andy Butland
    0

    OK - have put together a quick example and it does seem to pick the custom mappings up OK.  Here's what I've done... maybe you can spot the difference with your scenario and see either how you can amend or how yours is different and so illustraing a use case we may have missed.

    Taking the example I used on my blog post as a starting point, I've added a new complex type to the MatchReportTeaser view model class.  Not very real-world but something simple to illustrate, I've added a field that holds the Name and URL of the IPublishedContent concantenated together:

        
        public class MatchReportTeaser
        {
            public string Name { get; set; }
    
            public string Url { get; set; }
    
            public NameAndUrlCombined NameAndUrl { get; set; }
        }
    
        public class NameAndUrlCombined
        {
            public string Value { get; set; }
        }
    

     I've then registered a custom mapping for this new NameAndUrlCombined class in my base controller:

        Mapper.AddCustomMapping(typeof(NameAndUrlCombined).FullName, CustomMappings.MapNameAndUrlCombined);
    

    And implemented it like this:

        public static object MapNameAndUrlCombined(IUmbracoMapper mapper, IPublishedContent contentToMapFrom, string propName, bool isRecursive) 
        {
            var result = new NameAndUrlCombined();
            result.Value = contentToMapFrom.Name + "|" + contentToMapFrom.Url;
            return result;
        }   
    

    Sure enough in my view I see the value coming out as expected:

    @foreach (var report in match.ReportsFromPreviousGames)
        {
            <ul> <li><a href="http://mce_host/projects/developer-tools/umbraco-mapper/bugs,-questions,-suggestions/@report.Url">@report.Name</a> <div>Custom mapping test: @report.NameAndUrl.Value</div> </ul>
        }
    

    I.e.

    <div>Custom mapping test: Go Further|/go-further/</div>

    Hope that helps resolve it.

    Cheers

    Andy

  • shinsuke nakayama 109 posts 250 karma points
    Sep 06, 2014 @ 18:01
    shinsuke nakayama
    0

    Thank you for your reply Andy,

    i just commented out the code i've written aaaaand it seems to be working now.

    hmmm weird one, i spent most friday trying to get this to work.

    I'll look into details tomorrow morning.

    maybe something was cached >.<

    Shinsuke

  • shinsuke nakayama 109 posts 250 karma points
    Sep 08, 2014 @ 03:18
    shinsuke nakayama
    0

    Hi Andy,

    Sorry about the late reply, I didn't get to have chance to look at it till now.

    I looked into more details and it wasn't working. It looked like it was working because i have changed the property name to URL and it was doing the property name mapping.

    So i went in debugging and i got to this point

    UmbracoMapper.cs line 279 ish

     Map((IPublishedContent)dictionary[propName], property.GetValue(model), propertyMappings);
    

    Then it goes to UmbracoMapper.cs line 80, then line 107 and start doing property by property mapping.

    Okay, I think i just figured this out.

    The main difference between my code and your code was, my code look something like this

    public class MatchReportTeaser
        {
            public string Name { get; set; }
    
            public string Url { get; set; }
    
            public string Link { get; set; }
    
            Public int PageId { get; set; }
        }
    

    I think in your scenario when it was doing the property matching, it found a type of "NameAndURlCombined"

    public NameAndUrlCombined NameAndUrl { get; set; }
    

    Then it searched for the CustomMapping, where in my case, it's just a string and int property.

    so the Custom Mapping didn't take place.

        public string Link { get; set; }
    
        Public int PageId { get; set; }
    

    I'm not sure what's the best practice here, but having another children in the "MatchReportTeaser" feels like the hierarchy is getting too big?

    Really sorry about the late reply, I was hoping i can get back to you yesterday but took me a while to figure this out.

    This is not a show stopper so we can discuses this later, mean while i might uncomment the the code above.

    Cheers

    Shinsuke

    Edit

    Enjoy your holiday :)

  • Andy Butland 422 posts 2333 karma points MVP 4x hq c-trib
    Sep 12, 2014 @ 21:56
    Andy Butland
    100

    Thanks - am back now!... not sure if you've got any further with this but here's a few more thoughts.

    Firstly - yes, completely agree the example I've created above with an additional child class in the view model is taking it too far.  It was just that I wanted something simple to demonstrate.  In your case really you are looking to map to a primitive type - i.e. a string - so ideally you don't want to have to create a class for this.

    So there's a couple of things to note here first.

    Custom mappings as you've found require a type to map to - you basically say any time you find a particular type on your view model, map it this way, and hence it makes most sense for actual complex types you've created on your view model.  Like the geo-coordinate example here.  If actually you want a custom mapping for a built-in type - like a string - it's not going to work as well.  You only want to use this custom mapping for a particular string, not all strings of course.

    One option that is possible is to add a custom mapping for a particular type AND property name.  You can do that by passing the propertyName parameter to the AddCustomMapping method.  By doing that your custom mapping will only apply to view model properties that match both the type and the name.

    So that would work, but still isn't really the best way.  If I've understood correctly, you can just apply an override to the standard mapping that maps from IPublishedContent to your view model.  By convention, mapping is made using the name - so a field called "Url" will be mapped from the content's Url property.

    If in your case your property is called "Link", you can map it from the Url property by providing an override to this convention, either using attributes or by passing a dictionary detailing the overrides you need.

    So - to get to the point(!) - I think all you need to do is decorate your view model like this:

    public class MatchReportTeaser
    {
        public string Name { get; set; }
    
        [PropertyMapping(SourceProperty = "Url")]
        public string Link { get; set; }
    
        [PropertyMapping(SourceProperty = "Id")]
        public int PageId { get; set; }
    }
    

    If I've followed you correctly hopefully that helps. 

    Cheers

    Andy

     

  • shinsuke nakayama 109 posts 250 karma points
    Sep 14, 2014 @ 04:17
    shinsuke nakayama
    0

    Hi Andy

    thank you for your reply.

    yes that make sense and thank you.

    During the week, I hit another issue where i couldn't map archtype within the archtype (2 level archetype). Then I realised I think i was making things too complex by trying to be more custom. Well in my scenario, i didn't really need 2 level archetype. (I was grouping Data Types in in archtype so i can create the DocumentTypes easy. )

    Then i went back to the issue above. Initially i was worried about having a complex data type and trying to map that to the custom object. I think if i stick with the Umbraco Data Type, and its naming convention then i don't think i will ever have this issue.

    Currently i still have the code i written in my previous post, but I'll look into this again.

    Thank you

    Shinsuke

Please Sign in or register to post replies

Write your reply to:

Draft