More Fun with your ODATA Models and AutoMapper

In our last post Separating your ODATA Models from the Persistence Layer with AutoMapper I showed how we can separate our public API Model from the internal persistence layer. In this post I will show how we can re-model the public API model without changing the internal data model.

In the example from the previous post I showed our model Customer that contained a few properties regarding the creation and modification of the entity:

public class Customer
{
  [Key]
  public long Id { get; set; }

  [Required]
  public string Name { get; set; }

  public string Description { get; set; }

  [Required]
  public string ContractId { get; set; }

  [Required]
  public long CreatedById { get; set; }

  [ForeignKey(nameof(ModifiedById))]
  public User ModifiedBy { get; set; }

  [Required]
  public long ModifiedById { get; set; }

  [ForeignKey(nameof(ModifiedById))]
  public User ModifiedBy { get; set; }

  [Required]
  public DateTimeOffset Created { get; set; }

  [Required]
  public DateTimeOffset Modified { get; set; }

  [Timestamp]
  public byte[] RowVersion { get; set; }
}

However it seems that most of the information is not really necessary or useful for the caller. Therefore I would like to hide these properties in a normal response and only reveal them upon sepcific request via an ODATA $expand option.

This is possible with the help of AutoMapper. We just have to refactor our model to this:

public class EntityDetails
{
  [Required]
  public long CreatedById { get; set; }

  [Required]
  public long ModifiedById { get; set; }

  [Required]
  public DateTimeOffset Created { get; set; }

  [Required]
  public DateTimeOffset Modified { get; set; }

  [Timestamp]
  public byte[] RowVersion { get; set; }
}

public class Customer
{
  [Key]
  public long Id { get; set; }

  [Required]
  public string Name { get; set; }

  public string Description { get; set; }

  [Required]
  public string ContractId { get; set; }

  public EntityDetails Details { get; set; }
}

Now our model contains a subclass with the required information. In order to map the information from the underlying data access model we have to extend our mapping profile (from the previous post) to this:

public class EntityAutoMapperProfile : Profile
{
  public EntityAutoMapperProfile()
  {
    CreateMap<CustomerDataAccess, Customer>()
    .ForMember(dest => dest.Details, opt => opt.MapFrom(src => new EntityDetails
    {
      CreatedById = src.CreatedById,
      ModifiedById = src.ModifiedById,
      Created = src.Created,
      Modified = src.Modified,
      RowVersion = src.RowVersion,
    }))
    .ReverseMap();
  }
}

With this mapping we transform the separate properties from the data access entity to the EntityDetails class (we just have to make sure that we use a Lambda Expression).

The only thing that is missing is to tell ODATA that EntityDetails is a known entity, otherwise we will get an error similar to this:

message=No NavigationLink factory was found for the navigation property 
'Details' from entity type 'Net.Appclusive.Public.Domain.EntityDetails' 
on entity set 'Customers'. Try calling HasNavigationPropertyLink on the 
EntitySetConfiguration.

Parameter name: navigationProperty

For more information on this error have a look at ODataController throws ‘No NavigationLink factory was found’ when returning a different type in IHttpActionResult.

void Initialise(System.Web.Http.OData.Builder.ODataConventionModelBuilder builder)
{
  builder.ComplexType<EntityDetails>();
}

Note: if you use builder.EntitySet("EntityDetails") the retrieval/return of these objects works as well, but we cannot use these objects to post/send them from the client to the server.

After adding the line above to our model builder we can query our entity set via Invoke-RestMethod 'http://appclusive/api/Core/Customers(1)?$expand=Details' and get the following output:

{
  "odata.metadata":  "http://appclusive/api/Core/$metadata#Customers/@Element",
  "Id":  "1",
  "Name": "Edgar Schnittenfittich",
  "Description":  "Our first customer"
  "ContractId":  "arbitrary-contract-id",
  "Details":
  {
    "CreatedById":  "1",
    "ModifiedById":  "1",
    "Created":  "2016-08-15T05:42:08.01Z",
    "Modified":  "2017-01-01T08:05:42.01Z",
    "RowVersion":  "AAAAAAABvVY="
  }
}

And when querying the entity without any options it just returns:

{
  "odata.metadata":  "http://appclusive/api/Core/$metadata#Customers/@Element",
  "Id":  "1",
  "Name": "Edgar Schnittenfittich",
  "Description":  "Our first customer"
  "ContractId":  "arbitrary-contract-id"
}

With this approach we can now freely modify the layout of our public models without changing the internal database models and schema.

Trackbacks

  1. […] model (as described in Separating your ODATA Models from the Persistence Layer with AutoMapper and More Fun with your ODATA Models and AutoMapper). In addition, we want to implement and enforce entity based permission checks on the data layer, […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: