Recently I ran into a strange error when working on an ODataController. On several occasions (especially on CREATE or UPDATE operations) we do not want to return the actual underlying entity of the controller, but instead would like to return an entity that can be used for synchronisation such as a Job.

So instead of doing something like this and return the actual entity of the controller …

public class ApprovalsController : ODataController
{
  [Authorize]
  public async Task<IHttpActionResult> Put([FromODataUri] long key, Approval approval)
  {
    Contract.Requires(0 < key, "|400|");
    Contract.Requires(null != approval, "|400|");

    var entity = await db.Approvals.FindAsync(key);
    Contract.Assert(null != entity, "|404|");

    // do something outstanding with the approval ...
    var result = ApprovalManager.ProcessSync(approval);
    Contract.Assert(result, "|410|");

    // ... and return the processed approval
    return Ok(approval);
  }
}

… we would like to return something like that (a job used to track the status of the entity):

public class ApprovalsController : ODataController
{
  [Authorize]
  public async Task<IHttpActionResult> Put([FromODataUri] long key, Approval approval)
  {
    Contract.Requires(0 < key, "|400|");
    Contract.Requires(null != approval, "|400|");

    var entity = await db.Approvals.FindAsync(key);
    Contract.Assert(null != entity, "|404|");

    // do something marvelous with the approval ...
    var job = ApprovalManager.ProcessAsync(approval);
    Contract.Assert(null != job, "|410|");

    // ... and return a job that can be used to track the status of the operation
    return ResponseMessage(ODataControllerHelper.ResponseAccepted<Job>(this, job));
  }
}

In the above example Job is a standard OData (EntityFramework based) entity that has a NavigationLink called EntityKind that is also exposed via an OData controller. The Approval entity (also exposed via an OData controller) does not have the EntityKind property on its model.

When testing the example where we return the Job we get an error on the Web.Api pipeline stating the following:

message=No NavigationLink factory was found for the navigation property 
'EntityKind' from entity type 'biz.dfch.CS.Appclusive.Core.OdataServices.Core.Job' 
on entity set 'Approvals'. Try calling HasNavigationPropertyLink on the 
EntitySetConfiguration.

Parameter name: navigationProperty

The error message is actually true – Approval does not have an EntityKind property (as stated above), but this is actually not wanted. However, the OData framework somehow expects that on the entity, even though we specified the returned type via ResponseMessage(ResponseAccepted).

I tried several approaches (like defining manual NavigationPropertyLinks on the ApprovalsController) but I have not found a way to circumvent this error (except of course by defining the EntityKind on Approval).

The only way I managed to actually return a type that is different from the controller’s type is to create an additional model (called JobResponse in my example) that has no NavigationPropertyLinks at all and to return that to the client. For that model I also had to define a JobResponsesController otherwise I would receive another OData error stating that only defined types could be returned. I put all actions on that controller to throw new NotImplementedException(); as it is not used (and not supposed to be used) at all directly, but only to return tracking information to the caller.

public class JobResponse
{
  [Key]
  public long Id { get; set; }
  public string Message { get; set; }
}

public class ApprovalsController : ODataController
{
  [Authorize]
  public async Task<IHttpActionResult> Put([FromODataUri] long key, Approval approval)
  {
    Contract.Requires(0 < key, "|400|");
    Contract.Requires(null != approval, "|400|");

    var entity = await db.Approvals.FindAsync(key);
    Contract.Assert(null != entity, "|404|");

    // do something marvelous with the approval ...
    var job = ApprovalManager.ProcessAsync(approval);
    Contract.Assert(null != job, "|410|");

    // ... and return a job that can be used to track the status of the operation
    var jobResponse = CreateJobResponseFromJob(job); 
    Contract.Assert(null != jobResponse, "|410|");
    var response = this.ResponseMessage(ODataControllerHelper.ResponseAccepted<JobResponse>(controller, jobResponse));
  }
}

I do not know if this is a bug or by design – at least a major nuisance to me.

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 )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.