I recently started working on a new project using the DXA. One of the site types was a rebuild; using an existing content model rather than creating schemas from scratch. Part of the DXA philosophy is to enable MVC developers to use simple view models which hide the complexity of the underlying domain (CM) model. This article shows some tricks you can use to do help take this approach. Its based on the .NET implementation but the same concepts should apply to the Java version.
As we were creating the View Models, rather than trying to mirror 1-1 the rather convoluted existing CMS content model we took the approach to think just about what data should be rendered in that view and to build the view model to make that as simple as possible. Not only does this make for cleaner views, it will give a slight performance improvement, as only properties that you really need are mapped from the domain model. Here are some of the techniques we used:
1. Flattening
Every single Rich Text field, was wrapped up in a generic Rich Text embedded schema. While this makes sense in the domain (CM) model – we want to define common formatting options for all schemas which have fields which allow rich text content, it is a level with no purpose in the View Models. Rather than creating a single RichTextField
model, with a single Content
property, we can use the technique of flattening to ignore this level in the View models. So while our Promo Box schema might have a body field which uses the embedded Rich Text schema containing a content field, our PromoBox
model looks something like this:
public class PromoBox : EntityModel
{
public string Heading { get; set; }
[SemanticProperty("content")]
public RichText Description { get; set; }
}
DXA sees that Description maps to a field called content, and if it can’t find it in the first level of fields, searches for it in embedded fields.
2. Cherry picking
Our ArticleSummary
model has a Description
property. The business logic was to map this from the Article schema’s first embedded paragraph value’s body field (which by the way uses our embedded Rich Text schema containing a content field). There is no need to define a List Paragraphs property on the ArticleSummary model – we are only interested in the first value, and only the body field’s embedded content value in this. The following will enable us to cherry pick that item:
public class ArticleSummary : EntityModel
{
public string Heading { get; set; }
[SemanticProperty("content")]
public RichText Description { get; set; }
}
DXA actually processes this in the same way as flattening, and even though the field is multivalue, it sees that the property we are mapping is not a List, so just takes the first value. This means that we do not have put clumsy logic in the view to pull out the first paragraph field’s body content – the business logic is encapsulated in the View Model definition
By the way, if you are worried about XPM markup being lost in translation, fear not – DXA has you covered. Checking the markup generated for this field using the Html.DxaPropertyMarkup
helper, it reveals that content structure in full:
!-- Start Component Field: {"XPath":"tcm:Content/custom:Article/custom:paragraph[1]/custom:body[1]/custom:paragraph_text[1]/custom:content[1]"} --
3. Merging
Almost every implementation I have worked on has an embedded Link schema, which has the option to select either an internal (Component) or external link, with separate fields for each. In our views, however, we do not care if its an internal or external link, we just want the URL to render. With DXA this is easy – we can map both fields to the same property:
public class PromoBox : EntityModel
{
public string Heading { get; set; }
[SemanticProperty("internalLink")]
[SemanticProperty("externalLink")]
public String Url {get;set;}
}
This example is actually straight from the docs (link) but its good to mention it again, as its very useful that DXA will automatically resolve the internal link to a URL, and prevents the need for clumsy if/else logic in your model or view to test which field has a value.
4. Self-linking
Again – this is mentioned in the docs, but its worth re-iterating. If you like the idea of creating different view models for the same schema, to cater for a Summary / Details type views then you will need to use the self linking concept. Your Details model may not need a link to the item that you are rendering (as presumably you are already on that page), however your Summary model will need a resolved link to the details page. This is done as follows:
public class ArticleSummary : EntityModel
{
public string Heading { get; set; }
[SemanticProperty("content")]
public RichText Description { get; set; }
[SemanticProperty("_self")]
public String Url { get; set; }
}
The _self reserved property name will ensure that a link is resolved to the item itself.
5. Defaults
Last but not least, don’t forget that DXA does default mapping. There is no need to put any SemanticProperty
decorators on your View Model properties if the property name matches the schema field XML name (with the first letter small case, so Heading
property maps to heading schema XML field name).
I have also noticed that there is a tendency to add SemanticEntity
decorators to every EntityModel class definition – its not clear in the docs but you DO NOT NEED THIS. DXA will map whatever component it has to the View Model used by the view it is rendering, it only cares about SemanticEntity
decorators if you need to map multiple schemas with differing field XML names, to the same view model (this is the example you see in the docs with the Teaser model), or if you want to use public semantics in your rendered output (schema.org etc.)
6. Ignorance is performance
If you have additional properties in your view model which are not populated from component fields (but rather from an external system or result of some calcuation in the controller/model) then don’t forget to tell DXA to ignore them when mapping content with IgnoreMapping
option.
[SemanticProperty(IgnoreMapping = true)]
public int ResultCount { get; set; }
If you don’t do this, DXA will dig around in the source component trying to find some matching field (using the flattening technique described earlier) – this is all wasted effort so using this option will give you a small performance benefit. If you have a lot of ignored properties then you can set MapAllProperties
to false
on the SemanticEntity
decorator – in this way it will only try to map properties with a SemanticProperty
decorator
[SemanticDefaults(MapAllProperties = false)]
public class Product : EntityModel
{
//Product Identifier comes from stub component in CMS
[SemanticProperty("sku")]
public String ProductSku {get;set;}
//All other properties come from external PIM and eCom system
public String Title {get;set;}
public String Description {get;set;}
public MediaItem Image {get;set;}
public Price ProductPrice {get;set;}
}
Brilliant post Will! Every DXA developer should read this.
Really nice article that explains the power of DXA’s semantic mapping very clearly!
Note that in DXA 1.7 we made the semantic mappin even more powerful (suited for retrofitting on existing content models) and we have added a topic about it to the docs:
http://docs.sdl.com/LiveContent/content/en-US/SDL%20DXA-v7/GUID-C0AD6DCC-ED65-441F-B361-928A18B2896D
A nice undocumented model mapping features is the “_all” property. It maps all fields into a Dictionary property.
The Configuration model uses it:
https://github.com/sdl/dxa-web-application-dotnet/blob/9c8724cb638a11d6d444bc5526c6c95de0800d18/Sdl.Web.Common/Models/Entity/Configuration.cs