Tak jak zapowiadałem, jest to pierwszy i zapewne nie ostatni post na temat jak bym ulepszył patterns & practices SharePoint Guide.
SPG, udostępnia naprawdę niektóre ciekawe rozwiązania, ale nie zawsze spełniające moje oczekiwania. Tak jak ostatnio pisał Jeremy D. Miller, jestem raczej typem programisty numer dwa – wolę porzucić wszystkie frameworki byleby móc pracować tak jak ja chcę. Chciałem mieć mapper pomiędzy polem SharePointa a moim entity. To co udostępniał SPG mi nie odpowiadało:
private readonly Guid ProductSkuFieldId = new Guid("a933192c-4430-4814-97d5-f99b23ace03e"); private readonly Guid PromotionDescriptionFieldId = new Guid("aaed1aca-3f64-4b13-8e87-863ebc659432"); // ... listItemFieldMapper.AddMapping(ProductSkuFieldId, "Sku"); listItemFieldMapper.AddMapping(PromotionImageFieldId, "Image");
Jednym z głównych problemów jest ciąg znaków… którego nie znoszę. Można się łatwo pomylić oraz jak ulegnie zmiana nazwy własności to potem trzeba się męczyć i nie można skorzystać z dobrodziejstw refactoringu.
Procent, też jest podobnego zdania, dlatego swojego czasu napisał post w jaki sposób pozbyć się tej niedogodności.
Dodatkowo, prawie co chwila twórcy odwoływali się do pełnej nazwy typu kiedy mogli to zrobić tylko raz, no i oczywiście przez wykorzystanie ciągu znaków zawsze przy ustawianiu i pobieraniu danych, musieli weryfikować czy dana własność naprawdę istnieje w entity. Teraz udało się to ograniczyć. Dzięki małej zmianie (poniżej), możliwe jest teraz wykonanie następującego kodu:
private readonly Guid ProductSkuFieldId = new Guid("a933192c-4430-4814-97d5-f99b23ace03e"); private readonly Guid PromotionDescriptionFieldId = new Guid("aaed1aca-3f64-4b13-8e87-863ebc659432"); // ... listItemFieldMapper.AddMapping(ProductSkuFieldId, entity => entity.Sku); listItemFieldMapper.AddMapping(PromotionImageFieldId, entity => entity.Image);
Poniżej zamieszczam trochę zmodyfikowaną wersję klasy ListItemFieldMapper a zaraz pod nią FieldToEntityPropertyMapping.
/// <summary> /// Class that maps Fields from a <see cref="SPListItem"/> to properties on a strong typed business entity /// </summary> /// <typeparam name="TEntity">The type of businessentity to map. </typeparam> [SharePointPermission(SecurityAction.InheritanceDemand, ObjectModel = true)] [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)] public class ListItemFieldMapper<TEntity> where TEntity : new() { /// <summary> /// Strings that contains full name of entity type. /// </summary> private readonly string EntityTypeFullName = typeof(TEntity).FullName; /// <summary> /// List of all mappings for given entity. /// </summary> private List<FieldToEntityPropertyMapping> _fieldMappings = new List<FieldToEntityPropertyMapping>(); /// <summary> /// Create an entity, and fill the mapped properties from the specified <see cref="SPListItem"/>. /// </summary> /// <param name="listItem">The listitem to use to fill the entities properties. </param> /// <returns>The created and populated entity.</returns> [SharePointPermission(SecurityAction.InheritanceDemand, ObjectModel = true)] [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)] public TEntity CreateEntity(SPListItem listItem) { TEntity entity = new TEntity(); foreach (FieldToEntityPropertyMapping fieldMapping in _fieldMappings) { EnsureListFieldId(listItem, fieldMapping); fieldMapping.EntityProperty.SetValue(entity, listItem[fieldMapping.ListFieldId], null); } return entity; } /// <summary> /// Gets the list of field mappings that are used by the ListItemFieldMapper class. /// </summary> public List<FieldToEntityPropertyMapping> FieldMappings { get { return _fieldMappings; } } /// <summary> /// Fill a SPListItem's properties based on the values in an entity. /// </summary> /// <param name="listItem">SharePoint list item that needs to be filled.</param> /// <param name="entity">Entity from which data should be taken to fillout <paramref name="listItem"/>.</param> [SharePointPermission(SecurityAction.InheritanceDemand, ObjectModel = true)] [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)] public void FillSPListItemFromEntity(SPListItem listItem, TEntity entity) { foreach (FieldToEntityPropertyMapping fieldMapping in _fieldMappings) { EnsureListFieldId(listItem, fieldMapping); listItem[fieldMapping.ListFieldId] = fieldMapping.EntityProperty.GetValue(entity, null); } } /// <summary> /// Add a mapping between a field in an SPListItem and a property in the entity. /// </summary> /// <param name="fieldId">Unique identifier of the <see cref="SPField"/> that needs to be mapped to property.</param> /// <param name="property"> /// Lambda expression that points to Property. /// </param> public void AddMapping(Guid fieldId, Expression<Func<TEntity, object>> property) { _fieldMappings.Add(new FieldToEntityPropertyMapping { EntityProperty = GetPropertyInfo(property), ListFieldId = fieldId }); } /// <summary> /// Retrives property info from lambda expression. /// </summary> /// <param name="property">Lambda expression that points to valid Property.</param> /// <returns> /// <see cref="PropertyInfo"/> that correspondes to passed labmbda expression. /// </returns> /// <exception cref="ListItemFieldMappingException"> /// If lambda expression <paramref name="property"/> dose not point to valid Property. /// </exception> [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] private PropertyInfo GetPropertyInfo(Expression<Func<TEntity, object>> property) { Expression body = property.Body; MemberExpression memberExpression; if (body is MemberExpression) { memberExpression = (MemberExpression)body; } else if (body.NodeType == ExpressionType.Convert && ((UnaryExpression)body).Operand is MemberExpression) { memberExpression = (MemberExpression)((UnaryExpression)body).Operand; } else { string errorMessage = string.Format("The lambda expression '{0}' should point to a valid Property", property.Body); throw new ListItemFieldMappingException(errorMessage); } return (PropertyInfo)memberExpression.Member; } /// <summary> /// Ensures if <see cref="SPListItem"/> contains <see cref="SPField"/> with given unique id. /// </summary> /// <param name="item">The list item.</param> /// <param name="fieldMapping">The field mapping.</param> /// <exception cref="ListItemFieldMappingException"> /// If <see cref="SPField"/> if <paramref name="fieldMapping.ListFieldId"/> dose not exists. /// </exception> [SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "ensuredField")] [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] private void EnsureListFieldId(SPListItem item, FieldToEntityPropertyMapping fieldMapping) { try { var ensuredField = item.Fields[fieldMapping.ListFieldId]; } catch (ArgumentException argumentException) { string errorMessage = string.Format(CultureInfo.CurrentCulture , "SPListItem '{0}' does not have a field with Id '{1}' which was mapped to property: '{2}' for entity '{3}'." , item.Name , fieldMapping.ListFieldId , fieldMapping.EntityProperty.Name , EntityTypeFullName); throw new ListItemFieldMappingException(errorMessage, argumentException); } } }
/// <summary> /// Mapping between a field in an SPListItem and a property in an entity class. /// </summary> public class FieldToEntityPropertyMapping { /// <summary> /// The guid that corresponds to the Id of the field. /// </summary> public Guid ListFieldId { get; set; } /// <summary> /// The etity property. /// </summary> public PropertyInfo EntityProperty { get; set; } }