Create Mappers

Writing your Mapper

A class mapper must implement the IMapper interface from Mobilize.WebMap.Common.Messaging. The first type parameter of this generic interface should be the model. The second parameter of the generic interface is the DTO class. For information on creating your mappers see the Creating a DTO.

This interface has three methods:

  • Map this method is called to create a DTO instance and populate from the model. If you do not want to send anything to the client you can return null

  • ApplyChanges this method is called to sync back data from the client. In this method you can take data sent from the client and apply those changes to the model on the backend.

  • Configure this method is called to setup which data elements from the model must be considered as references.

A possible mapper for the previous DTO can be like the following code:

    public class ButtonMapper : IMapper
    {
        public void ApplyChanges(IObservable observable, IDataTransfer dto)
        {
            var target = observable as Models.Button;
            var source = dto        as DataTransfer.Button;
            this.Map(source, target);
        }

        public void Configure(MapperInformation config)
        {
            // There is no references to configure
        }

        public object Map(IObservable observable)
        {
            var source = observable as Model.Button;
            var target = new           DataTransfer.Button();
            this.Map(source, target);
            return target;
        }

        private void Map(Models.Button observable, DataTransfer.Button dto)
        {
            dto.Text = observable.Text;
            dto.Name = observable.Name;
        }

        private void Map(DataTransfer.Button dto, Models.Button observable)
        {
            observable.Name = dto.Name;
            observable.Text = dto.Text;
        }
    }

Mapper registration

Mappers need to be registered in a Registration class in order to be able to be accessed during the request/response execution. A mapper is specifically associated to a type, so, when each button in an application will always trigger the ButtonMapper to synchronize data.

catalog.AddMapper(new DmColumn());

Using the ObservableExtensions

You can also use using Mobilize.WebMAP.Common.Core to include the ObservableExtensions. This will extensions add helper methods that can be used for several tasks.

NullableTypes

If you define properties like public int? Value then the null value can be used when we know that those properties do not need to be sent.

For example with this code:

dto.Value = model.GetChanged(x => x.Value);

The extension method GetChanged will return null. Null values on the current formatting settings will not be sent.

Getting Events or Delegates

To get information about model delegates you can use several extensions methods.

For example to get information about all model delegates (regardless of whether they have been modified on the current request or not):

 Dictionary events = model.GetDelegates();

This will return a dictionary where each key is the Event or Delegate name and the value is a boolean value indicating if those events or delegates have a handler.

You can use restrict which events or delegates you need. For example:

 Dictionary events = model.GetDelegates(x => x.Click, x => x.KeyPress);

You can also send only the events or delegates that changed during a request:

 Dictionary events = model.GetDelegatesChanged(x => x.Click, x => x.KeyPress);

Sending private properties

Mappers and DTO should be created on a different assembly that the one where the model is defined. However, that makes it impossible to access private properties.

A recommendation is to make these properties internal or expose them thru an internal property. And then add a InternalsVisible('MappersAndDTOAssemblyName') attribute to the models assembly. This will allow using this internals from the mappers.

Avoid setting properties with side effects

As a recommendation, if you have a property getter or setter that has some logic avoid using them directly and prefer to set other properties without any side effects.

Handling Calculated properties

Sometimes you cannot avoid using calculated properties. The problem with calculated properties is that the GetChanged extension only works on properties with the Intercepted attribute.

Let's see how to handle it on a hypothetical calculated property CalculatedProperty1. Let's say that this property depends on PropertyA and Reference.PropertyB then in your mapper you can create a method like:

private bool GetCalculatedProperty1Changed(SomeModel model) 
{
   if (model.GetChanged(x => x.PropertyA)!=null && model.Reference?.GetChanged(x => x.PropertyB))
   {
     return model.CalculatedProperty1;
   }
   return null;
}

Creating Mappers with Factories

There are situations where you have types that are not completely known beforehand for example for generic types. In this case, you can create a factory class that will be used whenever a mapper is not found.

The mapper registration works only for a specific type, which, means that only if a control is of the same type that is registered it will be take part of the mapping process, although, in some cases it may be necessary to register different types to a particular mapper, in these cases WebMap provides a MapperFactory which is used to register several types to a Mapper. For example, if there is a GenericButton class that inherits from Button, by registering directly a Mapper it would only connect the Button type to it, but, if a MapperFactory is registered, both the Button and GenericButton class would go through the same mapper.

Let's say that you have a generic component Button then the factory will be something like:

    public class GenericButtonMapper
    {

        bool CanGetMapper(Type type) { returns type.IsGeneric && typeof(Button<>).IsAssignableFrom(type.GetGenericTypeDefinition());}

        /// 
        /// Gets the mapper.
        /// 
        /// The type.
        /// The type for the mapper
        MapperInformation GetMapperInformation(Type type) 
        {
             var mapperType = typeof(ButtonMapper<>).MakeGenericType(type.GetGenericArguments()[0]);
             var dtoType = typeof(ButtonDataTransfer<>).MakeGenericType(type.GetGenericArguments()[0]);
             var mapper = Activator.CreateInstance(mapperType);
             var mapperId = "btn" + type.GetGenericArguments()[0].Name;
             new MapperInformation(mapperId, type, dtoType, mapper);           
        }
    }

This factory will return a new ButtonMapper instance using T as InType and ButtonDataTransfer as OutType

Last updated