1

I am using AutoMapper 4.x.

I have a couple of classes as follows:

/// <summary>
///     All service outputs need to descend from this class.
/// </summary>
public class OzCpAppServiceOutputBase : IOzCpAppServiceOutputBase
{
    private readonly OzCpResultErrors _OzCpResultErrors;

    public OzCpAppServiceOutputBase()
    {
        _OzCpResultErrors = new OzCpResultErrors();
    }

    public OzCpResultErrors ResultErrors
    {
        get { return _OzCpResultErrors; }
    }

    public bool ResultSuccess
    {
        get { return _OzCpResultErrors.Messages.Count == 0; }
    }
}

/// <summary>
///     Return from the booking service when a simple booking is made.
/// </summary>
public class OzCpSimpleManualCruiseBookingOutput : OzCpAppServiceOutputBase
{
    public int OzBookingId { get; set; }
    }  
}


public class SimpleManualCruiseBookingOutput : OzCpSimpleManualCruiseBookingOutput
{
}

My issue comes in when I call AutoMapper to translate between OzCpSimpleManualCruiseBookingOutput and SimpleManualCruiseBookingOutput is that the ResultErrors is cleared.

public SimpleManualCruiseBookingOutput SimpleManualCruiseBooking(SimpleManualCruiseBookingInput aParams)
{
    OzCpSimpleManualCruiseBookingOutput result = _PlatformBookingService.SimpleManualBooking(Mapper.Map<OzCpSimpleManualCruiseBookingInput>(aParams));

    //**TESTING
    result.ResultErrors.AddFatalError(1, "Oh Dear!!!!");

    //**As soon as I perform the mapping the ResultErrros collection loses the item I have added above
    return Mapper.Map<SimpleManualCruiseBookingOutput>(result);
}

I am guessing it is because it is a read only property, but I cannot figure out how to make it transfer the collection.

Any help greatly appreciated.

EDIT

I have also tried adding the items in the collection myself so changing my mapping from:

Mapper.CreateMap<OzCpSimpleManualCruiseBookingOutput, SimpleManualCruiseBookingOutput>();

to using the after map function as follows:

Mapper.CreateMap<OzCpSimpleManualCruiseBookingOutput, SimpleManualCruiseBookingOutput>()
                .AfterMap((src, dst) => dst.ResultErrors.Messages.AddRange(src.ResultErrors.Messages));

but this then results in the destination having TWO items in the list instead of 1 viz:

enter image description here

which are both the same entry of "Oh Dear!!!!" SOLUTION

Using the private setter approach suggested by DavidL (and an upgrade to Automapper 4.x) meant I got the required behaviour. So this is what I ended up with:

  /// <summary>
    ///     Defines the contract for all output DTO's to platform
    ///     application services.
    /// </summary>
    /// <seealso cref="OzCpAppServiceOutputBase" />
    public interface IOzCpAppServiceOutputBase : IOutputDto
    {
        /// <summary>
        ///     Contains a list of errors should a call to an application service fail.
        /// </summary>
        OzCpResultErrors ResultErrors{ get; }

        /// <summary>
        ///     When TRUE the underlying call to the application service was successful, FALSE
        ///     otherwise. When FALSE see ResultErrors for more information on the error condition.
        /// </summary>
        bool ResultSuccess { get; }
    }

  public class OzCpAppServiceOutputBase : IOzCpAppServiceOutputBase
    {
        public OzCpAppServiceOutputBase()
        {
            ResultErrors = new OzCpResultErrors();
        }

        /// <remarks>The private setter is here so that AutoMapper works.</remarks>
        public OzCpResultErrors ResultErrors { get; private set; }

        public bool ResultSuccess
        {
            get { return ResultErrors.Messages.Count == 0; }
        }
    }

So while needing to add a private setter "just for" AutoMapper that is a small price to pay to have this work and not use complicated mappings to deal with the issue.

TheEdge
  • 9,291
  • 15
  • 67
  • 135
  • 1
    Please don't copy and paste your project code here. It is difficult to read such large class names and properties. Why can't you update with simple classes which will have same logic. – Prasad Kanaparthi Sep 10 '15 at 04:24
  • @PrasadKanaparthi Posting on SO is so much fun.... You end up getting so much "don't post like this" noise that it outweighs the answers about 300:1 – TheEdge Sep 10 '15 at 04:36

1 Answers1

3

With the current inheritance structure, AutoMapper will NOT be able to do what you want it to do. Since your destination structure has the same properties as your source structure, the properties are also readonly. AutoMapper will not map to readonly properties that do not have a setter declared.

You have a few options:

  • Make the property setter explicitly private. This answer suggests that later versions of AutoMapper support this functionality. In this case it works for 4.x.
  • Make the property setter internal, so that only members of this assembly can set it. Since latest versions of AutoMapper will map to private setters, they should also map to internal setters.
  • Make the property settable.
  • Downcast the object instead of mapping (you've mentioned you don't want to do this because your object structures will eventually diverge).
  • Shadow the property on the destination object with a public setter. Ugly and a good source of strange bugs.

    public class SimpleManualCruiseBookingOutput : OzCpSimpleManualCruiseBookingOutput
    {
        public new OzCpResultErrors ResultErrors { get; set; }
    }
    
  • Create a helper that maps your read-only properties via reflection. DO NOT DO THIS!

    PropertyInfo nameProperty = aParams.GetType().GetProperty ("ResultErrors");
    FieldInfo nameField = nameProperty.GetBackingField ();
    nameField.SetValue (person, aParams.ResultErrors);
    
Community
  • 1
  • 1
David L
  • 32,885
  • 8
  • 62
  • 93
  • I may control the data, but I don't control bonehead developers ;-). Your explicit private setter did the trick! I could not test this with 3.3.1 as I had upgraded to 4.x in the hopes that it would fix the original problem, but it did not. Only small thing (which I can live with) is that IOzCpAppServiceOutputBase declares the property as readonly and the base class OzCpAppServiceOutputBase implements a private setter purely for AutoMapper benefit. But that is a minor. – TheEdge Sep 10 '15 at 05:30
  • @TheEdge glad to hear that did it :) I'll mention it as the accepted answer and I'd greatly appreciated it if you could mark it as accepted. – David L Sep 10 '15 at 05:32
  • Done. Thanks again for the help. – TheEdge Sep 10 '15 at 05:40