DTOs versus ViewModels
Business layer communicates with presentation layer through interface objects called DTOs to hide the DB classes and to keep separation between layers. All
ICRUDRepository methods accept DTOs and automatically map them to DB classes according to name conventions and specification expressed as LinQ expressions as described here.
ICRUDRepository implementation has its own way to declare the LinQ expressions that specifies how to map DTOs to/from db objects.
See DefaultCRUDRepository and DocumentDBCRUDRepository.
May we use DTOs in Views? Sometimes we can! We need just to insert them into some properties of the overall View ViewModel. However, if our classes need presentation layer specific attributes like for instance, DisplayAttribute, ColumnConnectionAttribute, ColumnLayoutAttribute, etc., then we can't place them on DTOs since DTOs can't contain any presentation layer specific stuff. Thus, we are forced to copy DTOs into ViewModels containing all needed attributes. Typically, ViewModels contain exactly the same properties of their corresponding DTOs but marked with the appropriate attributes, plus possible further properties needed for their rendering (for instance a value/display list needed by a select).
How to map DTOs to/from ViewModels
The way DTOs and ViewModels, are mapped into each others are specified by LinQ expressions that follow the general rules described here. If no LinQ specification is provided for a pair then the objects are mapped according to the default name conventions described here.
Mapping specifications are associated to
MappingContext is specified in a mapping operations the default
in the static property
MappingContext.Default is used.
All class mapping types are contained in the
Defining mappings in a MappingContext
The LinQ expression that specifies how to map an object of a class
S into an object of a class
is declared with the
expression parameter is omitted or null, just the standard naming conventions are used.
When a mapping is performed between objects whose class mappings have not been declared an
Add with a null expression
is automatically performed.
Add returns the
MappingContext instance it was called on, so several
method calls may be chained. Below a declaration is added to the default
Declarations may be added to the default
MappingContext in any static method. Custom
MappingContext, instead are better defined together with their declarations in subclasses of
MappingContext that are then added to the Asp.net Core Dependency Injection engine as singletons.
Important: for each DTO/ViewModel pair two declarations are needed, namely how to map the DTO into the ViewModel, and how to map the ViewModel into the DTO. Needless to say, mappings are not limited to DTO/ViewModel pairs but may be defined for all class pairs.
MvcControlsToolkit.Core.Business.Transformations namespace is added the
Map extension method
becomes available to all objects, while the
MapIEnumerable method becomes available to all
Thus, an object may be mapped into another class by simply writing something like:
context is the
MappingContext to be used for the transformation. If
context is omitted
or null the default
MappingContext is used.
Analogously all objects of an
IEnumerable<T> may be mapped with something like:
ICRUDRepository implementations that operates on ViewModels instead of DTOs
ICRUDRepository implementation may be wrapped with an
ICRUDRepository implementation that operates directly
on ViewModels and maps them to the right DTOs before invoking the wrapped
The first step to define the wrapper is the declaration of wicht DTO to associate to each ViewModel. This may be done with
TransformationRepositoryFarm class or with the
ODataTransformationRepositoryFarm class if we want to add
also IWebQueryable capabilities to the wrapped repository.
The overload with 3 generics is used when the ViewModel must be used in ODATA queries that allow grouping. In this case the third generic is the subclass of the secondo generic (DTO) to be used when the query contains a grouping operation (it may contain further fields for the aggregated properties). In case grouping operations don't need further fields for aggregations the third generic argument may be equal to the second. For more information on ODATA queries see the ODATA query documentation.
In ViewModels used for ODATA queries all properties that might be involved in queries must have exactly the same names of their corresponding DTO properties, since each ODATA query use the ViewModel properties (that is their names), but is "interpreted" on the DTO.
For a similar reason the
GetPageExtended methods are not pre-processed
by the wrapper that passes the ViewModel as it is to the wrapped repository without any DTO coversion. In fact,
all query related parameters of these methods (filters, sortings, and grouping) are expressed in terms of the ViewModel
and can't be applied to an
IQueryable that is based on a different class (the DTO).
For this reason ODATA queries may be processed by just the
ODataTransformationRepositoryFarm class that may create
ICRUDRepository implementation that is also an IWebQueryable implementation, that is
an implementation that contains the
ExecuteQuery methods that accepts directly an ODATA query,
instead of strongly typed LinQ stuffs.
ODataTransformationRepositoryFarm may be configured
in a static method since they need to be configured just once. A Good place to configure them is the static constructor of each controller
that uses the wrapped repository, so that they are available in a static private property of the controller and may be
used by any controller instance that needs to wrap a repository instance.
ODataTransformationRepositoryFarm may be used to create
the wrapper repository by invoking their
Create method whose parameters are the
ICRUDRepository to wrap, and the
MappingContext to use for all mappings.
MappingContext is omitted or null the default
MappingContext is used.
Since the repository to wrap is usually injected through Dependency Injection either in the Controller constructor or in a specific action method, the creation operation should be performed there.