Internal data structure for model mapping purposes.
        protected virtual IDictionary <string, T> GetAllFieldsAsDictionary <T>(MappingData mappingData)
        {
            IDictionary <string, T> result = new Dictionary <string, T>();

            if (mappingData.Fields != null)
            {
                foreach (KeyValuePair <string, object> field in mappingData.Fields)
                {
                    if ((field.Key == "settings"))
                    {
                        throw new NotImplementedException("'settings' field handling"); // TODO
                    }

                    if (typeof(T) == typeof(KeywordModel))
                    {
                        if (field.Value is KeywordModelData)
                        {
                            result[field.Key] = GetFieldValues <T>(field.Value, mappingData, resolveComponentLinks: false).FirstOrDefault();
                        }
                    }
                    else
                    {
                        result[field.Key] = GetFieldValues <T>(field.Value, mappingData, resolveComponentLinks: false).FirstOrDefault();
                    }
                }
            }
            if (mappingData.MetadataFields != null && typeof(T) == typeof(string))
            {
                foreach (KeyValuePair <string, object> field in mappingData.MetadataFields)
                {
                    result[field.Key] = GetFieldValues <T>(field.Value, mappingData, resolveComponentLinks: false).FirstOrDefault();
                }
            }
            return(result);
        }
        /// <summary>
        /// Builds a strongly typed Entity Model based on a given DXA R2 Data Model.
        /// </summary>
        /// <param name="entityModel">The strongly typed Entity Model to build. Is <c>null</c> for the first Entity Model Builder in the pipeline.</param>
        /// <param name="entityModelData">The DXA R2 Data Model.</param>
        /// <param name="baseModelType">The base type for the Entity Model to build.</param>
        /// <param name="localization">The context <see cref="ILocalization"/>.</param>
        public virtual void BuildEntityModel(ref EntityModel entityModel, EntityModelData entityModelData, Type baseModelType, ILocalization localization)
        {
            using (new Tracer(entityModel, entityModelData, baseModelType, localization))
            {
                MvcData        mvcData        = CreateMvcData(entityModelData.MvcData, "Entity");
                SemanticSchema semanticSchema = SemanticMapping.GetSchema(entityModelData.SchemaId, localization);

                Type modelType = (baseModelType == null) ?
                                 ModelTypeRegistry.GetViewModelType(mvcData) :
                                 semanticSchema.GetModelTypeFromSemanticMapping(baseModelType);

                MappingData mappingData = new MappingData
                {
                    SourceViewModel    = entityModelData,
                    ModelType          = modelType,
                    PropertyValidation = new Validation
                    {
                        MainSchema       = semanticSchema,
                        InheritedSchemas = GetInheritedSemanticSchemas(entityModelData, localization)
                    },
                    Fields         = entityModelData.Content,
                    MetadataFields = entityModelData.Metadata,
                    Localization   = localization
                };

                entityModel = (EntityModel)CreateViewModel(mappingData);

                entityModel.Id      = entityModelData.Id;
                entityModel.MvcData = mvcData ?? entityModel.GetDefaultView(localization);
            }
        }
        protected virtual ViewModel CreateViewModel(MappingData mappingData)
        {
            ViewModelData   viewModelData   = mappingData.SourceViewModel;
            EntityModelData entityModelData = viewModelData as EntityModelData;

            ViewModel result;

            if (string.IsNullOrEmpty(mappingData.ModelId))
            {
                // Use parameterless constructor
                result = (ViewModel)mappingData.ModelType.CreateInstance();
            }
            else
            {
                // Pass model Identifier in constructor.
                result = (ViewModel)mappingData.ModelType.CreateInstance(mappingData.ModelId);
            }

            result.ExtensionData = viewModelData.ExtensionData;
            result.HtmlClasses   = viewModelData.HtmlClasses;
            result.XpmMetadata   = mappingData.Localization.IsXpmEnabled ? viewModelData.XpmMetadata : null;

            MediaItem mediaItem = result as MediaItem;

            if (mediaItem != null)
            {
                BinaryContentData binaryContent = entityModelData?.BinaryContent;
                if (binaryContent == null)
                {
                    throw new DxaException(
                              $"Unable to create Media Item ('{mappingData.ModelType.Name}') because the Data Model '{entityModelData?.Id}' does not contain Binary Content Data."
                              );
                }
                mediaItem.Url      = binaryContent.Url;
                mediaItem.FileName = binaryContent.FileName;
                mediaItem.MimeType = binaryContent.MimeType;
                mediaItem.FileSize = binaryContent.FileSize;
            }

            EclItem eclItem = result as EclItem;

            if (eclItem != null)
            {
                ExternalContentData externalContent = entityModelData.ExternalContent;
                if (externalContent == null)
                {
                    throw new DxaException(
                              $"Unable to create ECL Item ('{mappingData.ModelType.Name}') because the Data Model '{entityModelData.Id}' does not contain External Content Data."
                              );
                }
                eclItem.EclDisplayTypeId    = externalContent.DisplayTypeId;
                eclItem.EclTemplateFragment = externalContent.TemplateFragment;
                eclItem.EclExternalMetadata = externalContent.Metadata;
                eclItem.EclUri = externalContent.Id;
            }

            MapSemanticProperties(result, mappingData);

            return(result);
        }
        protected virtual object MapKeyword(KeywordModelData keywordModelData, Type targetType, ILocalization localization)
        {
            if (typeof(KeywordModel).IsAssignableFrom(targetType))
            {
                KeywordModel result;
                if (keywordModelData.SchemaId == null)
                {
                    result = new KeywordModel
                    {
                        ExtensionData = keywordModelData.ExtensionData
                    };
                }
                else
                {
                    MappingData keywordMappingData = new MappingData
                    {
                        SourceViewModel    = keywordModelData,
                        ModelType          = targetType,
                        PropertyValidation = new Validation
                        {
                            MainSchema       = SemanticMapping.GetSchema(keywordModelData.SchemaId, localization),
                            InheritedSchemas = GetInheritedSemanticSchemas(keywordModelData as ViewModelData, localization)
                        },
                        MetadataFields = keywordModelData.Metadata,
                        Localization   = localization
                    };

                    result = (KeywordModel)CreateViewModel(keywordMappingData);
                }

                result.Id          = keywordModelData.Id;
                result.Title       = keywordModelData.Title;
                result.Description = keywordModelData.Description ?? string.Empty;
                result.Key         = keywordModelData.Key ?? string.Empty;
                result.TaxonomyId  = keywordModelData.TaxonomyId;

                return(result);
            }

            if (targetType == typeof(Tag))
            {
                return(new Tag
                {
                    DisplayText = GetKeywordDisplayText(keywordModelData),
                    Key = keywordModelData.Key,
                    TagCategory = localization.GetCmUri(keywordModelData.TaxonomyId, (int)ItemType.Category)
                });
            }

            if (targetType == typeof(bool))
            {
                string key = string.IsNullOrEmpty(keywordModelData.Key) ? keywordModelData.Title : keywordModelData.Key;
                return(Convert.ToBoolean(key));
            }

            return(GetKeywordDisplayText(keywordModelData));
        }
        /// <summary>
        /// Builds a strongly typed Page Model from a given DXA R2 Data Model.
        /// </summary>
        /// <param name="pageModel">The strongly typed Page Model to build. Is <c>null</c> for the first Page Model Builder in the pipeline.</param>
        /// <param name="pageModelData">The DXA R2 Data Model.</param>
        /// <param name="includePageRegions">Indicates whether Include Page Regions should be included.</param>
        /// <param name="localization">The context <see cref="ILocalization"/>.</param>
        public virtual void BuildPageModel(ref PageModel pageModel, PageModelData pageModelData, bool includePageRegions, ILocalization localization)
        {
            using (new Tracer(pageModel, pageModelData, includePageRegions, localization))
            {
                MvcData mvcData   = CreateMvcData(pageModelData.MvcData, "Page");
                Type    modelType = ModelTypeRegistry.GetViewModelType(mvcData);

                if (modelType == typeof(PageModel))
                {
                    // Standard Page Model.
                    pageModel = new PageModel(pageModelData.Id)
                    {
                        ExtensionData = pageModelData.ExtensionData,
                        HtmlClasses   = pageModelData.HtmlClasses,
                        XpmMetadata   = localization.IsXpmEnabled ? pageModelData.XpmMetadata : null,
                    };
                }
                else if (pageModelData.SchemaId == null)
                {
                    // Custom Page Model, but no custom metadata.
                    pageModel = (PageModel)modelType.CreateInstance(pageModelData.Id);
                    pageModel.ExtensionData = pageModelData.ExtensionData;
                    pageModel.HtmlClasses   = pageModelData.HtmlClasses;
                    pageModel.XpmMetadata   = localization.IsXpmEnabled ? pageModelData.XpmMetadata : null;
                }
                else
                {
                    // Custom Page Model with custom metadata; do full-blown model mapping.
                    MappingData mappingData = new MappingData
                    {
                        SourceViewModel    = pageModelData,
                        ModelId            = pageModelData.Id,
                        ModelType          = modelType,
                        PropertyValidation = new Validation
                        {
                            MainSchema       = SemanticMapping.GetSchema(pageModelData.SchemaId, localization),
                            InheritedSchemas = GetInheritedSemanticSchemas(pageModelData, localization)
                        },
                        MetadataFields = pageModelData.Metadata,
                        Localization   = localization
                    };
                    pageModel = (PageModel)CreateViewModel(mappingData);
                }

                pageModel.MvcData = mvcData;
                pageModel.Meta    = pageModelData.Meta ?? new Dictionary <string, string>();
                pageModel.Title   = PostProcessPageTitle(pageModelData, localization); // TODO TSI-2210: This should eventually be done in Model Service.
                pageModel.Url     = pageModelData.UrlPath;
                if (pageModelData.Regions != null)
                {
                    IEnumerable <RegionModelData> regions = includePageRegions ? pageModelData.Regions : pageModelData.Regions.Where(r => r.IncludePageId == null);
                    pageModel.Regions.UnionWith(regions.Select(data => CreateRegionModel(data, localization)));
                    pageModel.IsVolatile |= pageModel.Regions.Any(region => region.IsVolatile);
                }
            }
        }
 /// <summary>
 /// Initializes a new <see cref="MappingData"/> instance which is a (shallow) copy of another.
 /// </summary>
 /// <param name="other">The other <see cref="MappingData"/> instance to copy.</param>
 public MappingData(MappingData other)
 {
     TargetType = other.TargetType;
     Content = other.Content;
     Meta = other.Meta;
     TargetEntitiesByPrefix = other.TargetEntitiesByPrefix;
     SemanticSchema = other.SemanticSchema;
     EmbeddedSemanticSchemaField = other.EmbeddedSemanticSchemaField;
     EntityNames = other.EntityNames;
     ParentDefaultPrefix = other.ParentDefaultPrefix;
     EmbedLevel = other.EmbedLevel;
     SourceEntity = other.SourceEntity;
     ModelId = other.ModelId;
     Localization = other.Localization;
     ContextXPath = other.ContextXPath;
 }
Beispiel #7
0
 /// <summary>
 /// Initializes a new <see cref="MappingData"/> instance which is a (shallow) copy of another.
 /// </summary>
 /// <param name="other">The other <see cref="MappingData"/> instance to copy.</param>
 public MappingData(MappingData other)
 {
     TargetType                  = other.TargetType;
     Content                     = other.Content;
     Meta                        = other.Meta;
     TargetEntitiesByPrefix      = other.TargetEntitiesByPrefix;
     SemanticSchema              = other.SemanticSchema;
     EmbeddedSemanticSchemaField = other.EmbeddedSemanticSchemaField;
     EntityNames                 = other.EntityNames;
     ParentDefaultPrefix         = other.ParentDefaultPrefix;
     EmbedLevel                  = other.EmbedLevel;
     SourceEntity                = other.SourceEntity;
     ModelId                     = other.ModelId;
     Localization                = other.Localization;
     ContextXPath                = other.ContextXPath;
 }
        protected virtual object MapEmbeddedFields(ContentModelData embeddedFields, Type targetType, SemanticSchemaField semanticSchemaField, string contextXPath, MappingData mappingData)
        {
            MappingData embeddedMappingData = new MappingData
            {
                ModelType                   = targetType,
                PropertyValidation          = mappingData.PropertyValidation,
                EmbeddedSemanticSchemaField = semanticSchemaField,
                EmbedLevel                  = mappingData.EmbedLevel + 1,
                SourceViewModel             = mappingData.SourceViewModel,
                Fields         = embeddedFields,
                MetadataFields = embeddedFields,
                ContextXPath   = contextXPath,
                Localization   = mappingData.Localization
            };

            return(CreateViewModel(embeddedMappingData));
        }
        protected virtual IEnumerable <T> GetFieldValues <T>(object fieldValues, MappingData mappingData, bool resolveComponentLinks)
        {
            if (!resolveComponentLinks)
            {
                // Handle Component Links here, because standard model mapping will resolve them.
                ILocalization localization = mappingData.Localization;
                if (fieldValues is EntityModelData)
                {
                    return((new[] { localization.GetCmUri(((EntityModelData)fieldValues).Id) }) as IEnumerable <T>);
                }
                if (fieldValues is EntityModelData[])
                {
                    return(((EntityModelData[])fieldValues).Select(emd => localization.GetCmUri(emd.Id)) as IEnumerable <T>);
                }
            }

            // Use standard model mapping to map the field to a list of T.
            return(MapField(fieldValues, typeof(List <T>), null, mappingData) as IEnumerable <T>);
        }
        protected virtual IDictionary <string, string> GetAllFieldsAsDictionary(MappingData mappingData)
        {
            IDictionary <string, string> result = new Dictionary <string, string>();

            if (mappingData.Fields != null)
            {
                foreach (KeyValuePair <string, object> field in mappingData.Fields)
                {
                    if ((field.Key == "settings"))
                    {
                        throw new NotImplementedException("'settings' field handling"); // TODO
                    }
                    result[field.Key] = GetFieldValuesAsStrings(field.Value, mappingData, resolveComponentLinks: false).FirstOrDefault();
                }
            }
            if (mappingData.MetadataFields != null)
            {
                foreach (KeyValuePair <string, object> field in mappingData.MetadataFields)
                {
                    result[field.Key] = GetFieldValuesAsStrings(field.Value, mappingData, resolveComponentLinks: false).FirstOrDefault();
                }
            }
            return(result);
        }
 protected virtual Dictionary<string, List<SemanticProperty>> FilterPropertySemanticsByEntity(Dictionary<string, List<SemanticProperty>> propertySemantics, MappingData mapData)
 {
     Dictionary<string, List<SemanticProperty>> filtered = new Dictionary<string, List<SemanticProperty>>();
     foreach (KeyValuePair<string, List<SemanticProperty>> property in propertySemantics)
     {
         filtered.Add(property.Key, new List<SemanticProperty>());
         List<SemanticProperty> defaultProperties = new List<SemanticProperty>();
         foreach (SemanticProperty semanticProperty in property.Value)
         {
             //Default prefix is always OK, but should be added last
             if (string.IsNullOrEmpty(semanticProperty.Prefix))
             {
                 defaultProperties.Add(semanticProperty);
             }
             else
             {
                 //Filter out any properties belonging to other entities than the source entity
                 KeyValuePair<string, string>? entityData = GetEntityData(semanticProperty.Prefix, mapData.TargetEntitiesByPrefix, mapData.ParentDefaultPrefix);
                 if (entityData != null && mapData.EntityNames!=null && mapData.EntityNames.Contains(entityData.Value.Key))
                 {
                     if (mapData.EntityNames[entityData.Value.Key].First() == entityData.Value.Value)
                     {
                         filtered[property.Key].Add(semanticProperty);
                     }
                 }
             }
         }
         filtered[property.Key].AddRange(defaultProperties);
     }
     return filtered;
 }
        protected virtual RegionModel CreateRegionModel(RegionModelData regionModelData, ILocalization localization)
        {
            MvcData mvcData         = CreateMvcData(regionModelData.MvcData, "Region");
            Type    regionModelType = ModelTypeRegistry.GetViewModelType(mvcData);

            RegionModel result = (RegionModel)regionModelType.CreateInstance(regionModelData.Name);

            result.ExtensionData = regionModelData.ExtensionData;
            result.HtmlClasses   = regionModelData.HtmlClasses;
            result.MvcData       = mvcData;
            result.XpmMetadata   = localization.IsXpmEnabled ? regionModelData.XpmMetadata : null;
            result.SchemaId      = regionModelData.SchemaId;

            if (!string.IsNullOrEmpty(regionModelData.SchemaId))
            {
                SemanticSchema semanticSchema = SemanticMapping.GetSchema(regionModelData.SchemaId, localization);

                Type modelType = ModelTypeRegistry.GetViewModelType(mvcData);

                MappingData mappingData = new MappingData
                {
                    SourceViewModel    = regionModelData,
                    ModelType          = modelType,
                    PropertyValidation = new Validation
                    {
                        MainSchema       = semanticSchema,
                        InheritedSchemas = GetInheritedSemanticSchemas(regionModelData, localization)
                    },
                    Fields         = null,
                    MetadataFields = regionModelData.Metadata,
                    Localization   = localization
                };
                MapSemanticProperties(result, mappingData);
            }

            if (regionModelData.Regions != null)
            {
                IEnumerable <RegionModel> nestedRegionModels = regionModelData.Regions.Select(data => CreateRegionModel(data, localization));
                result.Regions.UnionWith(nestedRegionModels);
                result.IsVolatile |= result.Regions.Any(region => region.IsVolatile);
            }

            if (regionModelData.Entities != null)
            {
                foreach (EntityModelData entityModelData in regionModelData.Entities)
                {
                    EntityModel entityModel;
                    try
                    {
                        entityModel = ModelBuilderPipeline.CreateEntityModel(entityModelData, null, localization);
                        // indicate to region model that this region is potentially volatile if it contains a volatile entity
                        result.IsVolatile |= entityModel.IsVolatile;
                        entityModel.MvcData.RegionName = regionModelData.Name;
                    }
                    catch (Exception ex)
                    {
                        // If there is a problem mapping an Entity, we replace it with an ExceptionEntity which holds the error details and carry on.
                        Log.Error(ex);
                        entityModel = new ExceptionEntity(ex);
                    }
                    result.Entities.Add(entityModel);
                }
            }

            return(result);
        }
        protected virtual object MapField(object fieldValues, Type modelPropertyType, SemanticSchemaField semanticSchemaField, MappingData mappingData)
        {
            Type sourceType = fieldValues.GetType();
            bool isArray    = sourceType.IsArray;

            if (isArray)
            {
                sourceType = sourceType.GetElementType();
            }

            bool isListProperty = modelPropertyType.IsGenericList();
            Type targetType     = modelPropertyType.GetUnderlyingGenericListType() ?? modelPropertyType;

            // Convert.ChangeType cannot convert non-nullable types to nullable types, so don't try that.
            Type bareTargetType = modelPropertyType.GetUnderlyingNullableType() ?? targetType;

            IList mappedValues = targetType.CreateGenericList();

            if (typeof(EntityModel).IsAssignableFrom(targetType) && sourceType != typeof(EntityModelData) &&
                (sourceType == typeof(string) && string.IsNullOrEmpty((string)fieldValues)))
            {
                return(isListProperty ? mappedValues : null);
            }

            switch (sourceType.Name)
            {
            case "String":
                if (isArray)
                {
                    foreach (string fieldValue in (string[])fieldValues)
                    {
                        mappedValues.Add(MapString(fieldValue, bareTargetType));
                    }
                }
                else
                {
                    mappedValues.Add(MapString((string)fieldValues, bareTargetType));
                }
                break;

            case "DateTime":
            case "Single":
            case "Double":
                if (isArray)
                {
                    foreach (object fieldValue in (Array)fieldValues)
                    {
                        mappedValues.Add(Convert.ChangeType(fieldValue, bareTargetType));
                    }
                }
                else
                {
                    mappedValues.Add(Convert.ChangeType(fieldValues, bareTargetType));
                }
                break;

            case "RichTextData":
                if (isArray)
                {
                    foreach (RichTextData fieldValue in (RichTextData[])fieldValues)
                    {
                        mappedValues.Add(MapRichText(fieldValue, targetType, mappingData.Localization));
                    }
                }
                else
                {
                    mappedValues.Add(MapRichText((RichTextData)fieldValues, targetType, mappingData.Localization));
                }
                break;

            case "ContentModelData":
                string fieldXPath = semanticSchemaField.GetXPath(mappingData.ContextXPath);
                if (isArray)
                {
                    int index = 1;
                    foreach (ContentModelData embeddedFields in (ContentModelData[])fieldValues)
                    {
                        string indexedFieldXPath = $"{fieldXPath}[{index++}]";
                        mappedValues.Add(MapEmbeddedFields(embeddedFields, targetType, semanticSchemaField, indexedFieldXPath, mappingData));
                    }
                }
                else
                {
                    string indexedFieldXPath = $"{fieldXPath}[1]";
                    mappedValues.Add(MapEmbeddedFields((ContentModelData)fieldValues, targetType, semanticSchemaField, indexedFieldXPath, mappingData));
                }
                break;

            case "EntityModelData":
                if (isArray)
                {
                    foreach (EntityModelData entityModelData in (EntityModelData[])fieldValues)
                    {
                        mappedValues.Add(MapComponentLink(entityModelData, targetType, mappingData.Localization));
                    }
                }
                else
                {
                    mappedValues.Add(MapComponentLink((EntityModelData)fieldValues, targetType, mappingData.Localization));
                }
                break;

            case "KeywordModelData":
                if (isArray)
                {
                    foreach (KeywordModelData keywordModelData in (KeywordModelData[])fieldValues)
                    {
                        mappedValues.Add(MapKeyword(keywordModelData, targetType, mappingData.Localization));
                    }
                }
                else
                {
                    mappedValues.Add(MapKeyword((KeywordModelData)fieldValues, targetType, mappingData.Localization));
                }
                break;


            default:
                throw new DxaException($"Unexpected field type: '{sourceType.Name}'.");
            }

            return(isListProperty ? mappedValues : ((mappedValues.Count == 0) ? null : mappedValues[0]));
        }
 private static IField GetFieldFromSemantics(MappingData mapData, SemanticProperty info)
 {
     KeyValuePair<string, string>? entityData = GetEntityData(info.Prefix, mapData.TargetEntitiesByPrefix, mapData.ParentDefaultPrefix);
     if (entityData != null)
     {
         // determine field semantics
         string vocab = entityData.Value.Key;
         string prefix = SemanticMapping.GetPrefix(vocab, mapData.Localization);
         if (prefix != null && mapData.EntityNames!=null)
         {
             string property = info.PropertyName;
             string entity = mapData.EntityNames[vocab].FirstOrDefault();
             if (entity != null && mapData.SemanticSchema!=null)
             {
                 FieldSemantics fieldSemantics = new FieldSemantics(prefix, entity, property);
                 // locate semantic schema field
                 SemanticSchemaField matchingField = mapData.SemanticSchema.FindFieldBySemantics(fieldSemantics);
                 if (matchingField != null)
                 {
                     return ExtractMatchedField(matchingField, (matchingField.IsMetadata && mapData.Meta!=null) ? mapData.Meta : mapData.Content, mapData.EmbedLevel);
                 }
             }
         }
     }
     return null;
 }
        protected virtual void MapSemanticProperties(ViewModel viewModel, MappingData mappingData)
        {
            Type modelType = viewModel.GetType();
            IDictionary <string, List <SemanticProperty> > propertySemanticsMap = ModelTypeRegistry.GetPropertySemantics(modelType);
            IDictionary <string, string> xpmPropertyMetadata = new Dictionary <string, string>();
            Validation validation = mappingData.PropertyValidation;

            foreach (KeyValuePair <string, List <SemanticProperty> > propertySemantics in propertySemanticsMap)
            {
                PropertyInfo            modelProperty      = modelType.GetProperty(propertySemantics.Key);
                List <SemanticProperty> semanticProperties = propertySemantics.Value;

                bool   isFieldMapped = false;
                string fieldXPath    = null;
                foreach (SemanticProperty semanticProperty in semanticProperties)
                {
                    if (semanticProperty.PropertyName == SemanticProperty.AllFields)
                    {
                        modelProperty.SetValue(viewModel, GetAllFieldsAsDictionary(mappingData));
                        isFieldMapped = true;
                        break;
                    }


                    if ((semanticProperty.PropertyName == SemanticProperty.Self) && validation.MainSchema.HasSemanticType(semanticProperty.SemanticType))
                    {
                        try
                        {
                            object mappedSelf = MapComponentLink((EntityModelData)mappingData.SourceViewModel, modelProperty.PropertyType, mappingData.Localization);
                            modelProperty.SetValue(viewModel, mappedSelf);
                            isFieldMapped = true;
                            break;
                        }
                        catch (Exception ex)
                        {
                            Log.Debug($"Self mapping failed for {modelType.Name}.{modelProperty.Name}: {ex.Message}");
                            continue;
                        }
                    }

                    FieldSemantics fieldSemantics = new FieldSemantics(
                        semanticProperty.SemanticType.Vocab,
                        semanticProperty.SemanticType.EntityName,
                        semanticProperty.PropertyName,
                        null);
                    SemanticSchemaField semanticSchemaField = (mappingData.EmbeddedSemanticSchemaField == null) ?
                                                              ValidateField(validation, fieldSemantics) :
                                                              mappingData.EmbeddedSemanticSchemaField.FindFieldBySemantics(fieldSemantics);
                    if (semanticSchemaField == null)
                    {
                        // No matching Semantic Schema Field found for this Semantic Property; maybe another one will match.
                        continue;
                    }

                    // Matching Semantic Schema Field found
                    fieldXPath = IsFieldFromMainSchema(validation, fieldSemantics) ? semanticSchemaField.GetXPath(mappingData.ContextXPath) : null;

                    ContentModelData fields     = semanticSchemaField.IsMetadata ? mappingData.MetadataFields : mappingData.Fields;
                    object           fieldValue = FindFieldValue(semanticSchemaField, fields, mappingData.EmbedLevel);
                    if (fieldValue == null)
                    {
                        // No field value found; maybe we will find a value for another Semantic Property.
                        continue;
                    }

                    try
                    {
                        object mappedPropertyValue = MapField(fieldValue, modelProperty.PropertyType, semanticSchemaField, mappingData);
                        modelProperty.SetValue(viewModel, mappedPropertyValue);
                    }
                    catch (Exception ex)
                    {
                        throw new DxaException(
                                  $"Unable to map field '{semanticSchemaField.Name}' to property {modelType.Name}.{modelProperty.Name} of type '{modelProperty.PropertyType.FullName}'.",
                                  ex);
                    }
                    isFieldMapped = true;
                    break;
                }

                if (fieldXPath != null)
                {
                    xpmPropertyMetadata.Add(modelProperty.Name, fieldXPath);
                }
                else if (!isFieldMapped && Log.IsDebugEnabled)
                {
                    string formattedSemanticProperties = string.Join(", ", semanticProperties.Select(sp => sp.ToString()));
                    Log.Debug(
                        $"Property {modelType.Name}.{modelProperty.Name} cannot be mapped to a CM field of {validation.MainSchema}. Semantic properties: {formattedSemanticProperties}.");
                }
            }

            EntityModel entityModel = viewModel as EntityModel;

            if ((entityModel != null) && mappingData.Localization.IsXpmEnabled)
            {
                entityModel.XpmPropertyMetadata = xpmPropertyMetadata;
            }
        }
        public virtual void BuildEntityModel(ref EntityModel entityModel, IComponent component, Type baseModelType, Localization localization)
        {
            using (new Tracer(entityModel, component, baseModelType, localization))
            {
                string[] schemaTcmUriParts = component.Schema.Id.Split('-');
                SemanticSchema semanticSchema = SemanticMapping.GetSchema(schemaTcmUriParts[1], localization);

                // The semantic mapping may resolve to a more specific model type than specified by the View Model itself (e.g. Image instead of just MediaItem for Teaser.Media)
                Type modelType = semanticSchema.GetModelTypeFromSemanticMapping(baseModelType);

                MappingData mappingData = new MappingData
                {
                    SemanticSchema = semanticSchema,
                    EntityNames = semanticSchema.GetEntityNames(),
                    TargetEntitiesByPrefix = GetEntityDataFromType(modelType),
                    Content = component.Fields,
                    Meta = component.MetadataFields,
                    TargetType = modelType,
                    SourceEntity = component,
                    Localization = localization
                };

                entityModel = (EntityModel)CreateViewModel(mappingData);
                entityModel.Id = GetDxaIdentifierFromTcmUri(component.Id);
                entityModel.XpmMetadata = GetXpmMetadata(component);

                if (entityModel is MediaItem && component.Multimedia != null && component.Multimedia.Url != null)
                {
                    MediaItem mediaItem = (MediaItem)entityModel;
                    mediaItem.Url = component.Multimedia.Url;
                    mediaItem.FileName = component.Multimedia.FileName;
                    mediaItem.FileSize = component.Multimedia.Size;
                    mediaItem.MimeType = component.Multimedia.MimeType;
                }

                if (entityModel is Link)
                {
                    Link link = (Link)entityModel;
                    if (String.IsNullOrEmpty(link.Url))
                    {
                        link.Url = SiteConfiguration.LinkResolver.ResolveLink(component.Id);
                    }
                }
            }
        }
        private PageModel CreatePageModel(IPage page, Localization localization)
        {
            MvcData pageMvcData = GetMvcData(page);
            Type pageModelType = ModelTypeRegistry.GetViewModelType(pageMvcData);
            string pageId = GetDxaIdentifierFromTcmUri(page.Id);
            ISchema pageMetadataSchema = page.Schema;

            PageModel pageModel;
            if (pageModelType == typeof(PageModel))
            {
                // Standard Page Model
                pageModel = new PageModel(pageId);
            }
            else if (pageMetadataSchema == null)
            {
                // Custom Page Model but no Page metadata that can be mapped; simply create a Page Model instance of the right type.
                pageModel = (PageModel)Activator.CreateInstance(pageModelType, pageId);
            }
            else
            {
                // Custom Page Model and Page metadata is present; do full-blown model mapping.
                string[] schemaTcmUriParts = pageMetadataSchema.Id.Split('-');
                SemanticSchema semanticSchema = SemanticMapping.GetSchema(schemaTcmUriParts[1], localization);

                MappingData mappingData = new MappingData
                {
                    TargetType = pageModelType,
                    SemanticSchema = semanticSchema,
                    EntityNames = semanticSchema.GetEntityNames(),
                    TargetEntitiesByPrefix = GetEntityDataFromType(pageModelType),
                    Meta = page.MetadataFields,
                    ModelId = pageId,
                    Localization = localization
                };

                pageModel = (PageModel) CreateViewModel(mappingData);
            }

            pageModel.MvcData = pageMvcData;
            pageModel.XpmMetadata = GetXpmMetadata(page);
            pageModel.Title = page.Title;

            // add html classes to model from metadata
            // TODO: move to CreateViewModel so it can be merged with the same code for a Component/ComponentTemplate
            IPageTemplate template = page.PageTemplate;
            if (template.MetadataFields != null && template.MetadataFields.ContainsKey("htmlClasses"))
            {
                // strip illegal characters to ensure valid html in the view (allow spaces for multiple classes)
                pageModel.HtmlClasses = template.MetadataFields["htmlClasses"].Value.StripIllegalCharacters(@"[^\w\-\ ]");
            }

            return pageModel;
        }
        private ViewModel MapEmbeddedFields(IFieldSet embeddedFields, Type modelType, MappingData mapData)
        {
            MappingData embeddedMappingData = new MappingData
                {
                    TargetType = modelType,
                    Content = embeddedFields,
                    Meta = null,
                    EntityNames = mapData.EntityNames, // TODO: should this not be re-determined for the embedded model type?
                    ParentDefaultPrefix = mapData.ParentDefaultPrefix,
                    TargetEntitiesByPrefix = mapData.TargetEntitiesByPrefix, // TODO: should this not be re-determined for the embedded model type?
                    SemanticSchema = mapData.SemanticSchema, // TODO: should this not be re-determined for the embedded model type?
                    EmbedLevel = mapData.EmbedLevel + 1,
                    Localization = mapData.Localization
                };

            return CreateViewModel(embeddedMappingData);
        }
        protected virtual ViewModel CreateViewModel(MappingData mappingData)
        {
            Type modelType = mappingData.TargetType; // TODO: why is this not a separate parameter?

            ViewModel model;
            if (string.IsNullOrEmpty(mappingData.ModelId))
            {
                // Use parameterless constructor
                model = (ViewModel)Activator.CreateInstance(modelType);
            }
            else
            {
                // Pass model Identifier in constructor.
                model = (ViewModel)Activator.CreateInstance(modelType, mappingData.ModelId);
            }

            Dictionary<string, string> xpmPropertyMetadata = new Dictionary<string, string>();
            Dictionary<string, List<SemanticProperty>> propertySemantics = LoadPropertySemantics(modelType);
            propertySemantics = FilterPropertySemanticsByEntity(propertySemantics, mappingData);
            foreach (PropertyInfo pi in modelType.GetProperties())
            {
                bool multival = pi.PropertyType.IsGenericType && (pi.PropertyType.GetGenericTypeDefinition() == typeof(List<>));
                Type propertyType = multival ? pi.PropertyType.GetGenericArguments()[0] : pi.PropertyType;
                if (propertySemantics.ContainsKey(pi.Name))
                {
                    foreach (SemanticProperty info in propertySemantics[pi.Name])
                    {
                        IField field = GetFieldFromSemantics(mappingData, info);
                        if (field != null && (field.Values.Count > 0 || field.EmbeddedValues.Count > 0))
                        {
                            pi.SetValue(model, MapFieldValues(field, propertyType, multival, mappingData));
                            xpmPropertyMetadata.Add(pi.Name, GetFieldXPath(field));
                            break;
                        }

                        // Special mapping cases require SourceEntity to be set
                        if (mappingData.SourceEntity == null)
                        {
                            continue;
                        }

                        bool processed = false;
                        if (info.PropertyName == "_self")
                        {
                            //Map the whole entity to an image property, or a resolved link to the entity to a Url field
                            if (typeof(MediaItem).IsAssignableFrom(propertyType) || typeof(Link).IsAssignableFrom(propertyType) || propertyType == typeof(String))
                            {
                                object mappedSelf = MapComponent(mappingData.SourceEntity, propertyType, mappingData.Localization);
                                if (multival)
                                {
                                    IList genericList = CreateGenericList(propertyType);
                                    genericList.Add(mappedSelf);
                                    pi.SetValue(model, genericList);
                                }
                                else
                                {
                                    pi.SetValue(model, mappedSelf);
                                }
                                processed = true;
                            }
                        }
                        else if (info.PropertyName == "_all" && pi.PropertyType == typeof(Dictionary<string, string>))
                        {
                            //Map all fields into a single (Dictionary) property
                            pi.SetValue(model, GetAllFieldsAsDictionary(mappingData.SourceEntity));
                            processed = true;
                        }

                        if (processed)
                        {
                            break;
                        }
                    }
                }
            }

            EntityModel entityModel = model as EntityModel;
            if (entityModel != null)
            {
                entityModel.XpmPropertyMetadata = xpmPropertyMetadata;
            }

            return model;
        }
        private static IEnumerable <string> GetFieldValuesAsStrings(object fieldValues, MappingData mappingData, bool resolveComponentLinks)
        {
            if (!resolveComponentLinks)
            {
                // Handle Component Links here, because standard model mapping will resolve them.
                Localization localization = mappingData.Localization;
                if (fieldValues is EntityModelData)
                {
                    return(new[] { localization.GetCmUri(((EntityModelData)fieldValues).Id) });
                }
                if (fieldValues is EntityModelData[])
                {
                    return(((EntityModelData[])fieldValues).Select(emd => localization.GetCmUri(emd.Id)));
                }
            }

            // Use standard model mapping to map the field to a list of strings.
            return((IEnumerable <string>)MapField(fieldValues, typeof(List <string>), null, mappingData));
        }
        private object MapFieldValues(IField field, Type modelType, bool multival, MappingData mapData)
        {
            try
            {
                // Convert.ChangeType cannot convert non-nullable types to nullable types, so don't try that.
                Type bareModelType = modelType;
                if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    bareModelType = modelType.GenericTypeArguments[0];
                }

                IList mappedValues = CreateGenericList(modelType);
                switch (field.FieldType)
                {
                    case FieldType.Date:
                        foreach (DateTime value in field.DateTimeValues)
                        {
                            mappedValues.Add(Convert.ChangeType(value, bareModelType));
                        }
                        break;

                    case FieldType.Number:
                        foreach (Double value in field.NumericValues)
                        {
                            mappedValues.Add(Convert.ChangeType(value, bareModelType));
                        }
                        break;

                    case FieldType.MultiMediaLink:
                    case FieldType.ComponentLink:
                        foreach (IComponent value in field.LinkedComponentValues)
                        {
                            mappedValues.Add(MapComponent(value, modelType, mapData.Localization));
                        }
                        break;

                    case FieldType.Embedded:
                        foreach (IFieldSet value in field.EmbeddedValues)
                        {
                            mappedValues.Add(MapEmbeddedFields(value, modelType, mapData));
                        }
                        break;

                    case FieldType.Keyword:
                        foreach (IKeyword value in field.Keywords)
                        {
                            mappedValues.Add(MapKeyword(value, modelType));
                        }
                        break;

                    case FieldType.Xhtml:
                        IRichTextProcessor richTextProcessor = SiteConfiguration.RichTextProcessor;
                        foreach (string value in field.Values)
                        {
                            RichText richText = richTextProcessor.ProcessRichText(value, mapData.Localization);
                            if (modelType == typeof(string))
                            {
                                mappedValues.Add(richText.ToString());
                            }
                            else
                            {
                                mappedValues.Add(richText);
                            }
                        }
                        break;

                    default:
                        foreach (string value in field.Values)
                        {
                            object mappedValue = (modelType == typeof(RichText)) ? new RichText(value) : Convert.ChangeType(value, bareModelType);
                            mappedValues.Add(mappedValue);
                        }
                        break;
                }

                if (multival)
                {
                    return mappedValues;
                }

                return mappedValues.Count == 0 ? null : mappedValues[0];

            }
            catch (Exception ex)
            {
                throw new DxaException(String.Format("Unable to map field '{0}' to property of type '{1}'.", field.Name, modelType.FullName), ex);
            }
        }