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














