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; }
}