0
votes

I am very much puzzled and hope somebody can shed any light. After upgrading to the latest version of Web API (from Microsoft.AspNet.WebApi.xxx 5.0.0. to Microsoft.AspNet.WebApi.xxx 5.1.2) OData's EntitySetController stopped processing PATCH verbs and returns 406 (Not Acceptable) code instead.

The implementation hasn't changed:

public class ODataAnnouncementController : EntitySetController<TEntity, TKey> where TEntity : class
{
    (...)

    protected override Announcement PatchEntity(Guid key, Delta<Announcement> patch)
    {
        var announcement = getEntityByKeyOrThrowException(key);
        patch.Patch(announcement);
        AnnouncementManager.Instance.Save(announcement, true);
        return announcement;
    }
}

The request hasn't changed either:

Accept: application/json

Content-Type: application/json; charset=utf-8

PATCH: http://{url}/odata/Announcement(guid'62E34FB3-A37E-413F-9843-642D95BA580A')

{"Description":"Patched description from Unit Test"}

Breakpoint inside the method is not being hit as if "Delta<Announcement>> patch" has never deserialized. The only condition under which it actually does hit the breakpoint inside the method is when a request BODY is empty resulting with patch object being null of course.

All other verbs (GET, POST, PUT, DELETE) work OK.

This used to work before. Is anybody aware of any breaking changes for Web API 2.1 in regards to PATCH? How else could I troubleshoot it?

Thank you so much

Bill

UPDATE

Here is the C# model:

[Serializable]
[DataContract]
public class Announcement
{                       
    private Guid _announcementID;
    private string _description;
    private bool _isPrivate;
    private DateTime _createdTime;
    private Nullable<Guid> _membershipID;

    [DataMember]
    public Guid AnnouncementID
    {
        get { return this._announcementID; }
        set { this._announcementID = value; }
    }
    [DataMember]
    public string Description
    {
        get { return this._description; }
        set { this._description = value; }
    }       
    [DataMember]
    public bool IsPrivate
    {
        get { return this._isPrivate; }
        set { this._isPrivate = value; }
    }
    [DataMember]
    public DateTime CreatedTime
    {
        get { return this._createdTime; }
        set { this._createdTime = value; }
    }
    [DataMember]
    public Nullable<Guid> MembershipID
    {
        get { return this._membershipID; }
        set { this._membershipID = value; }
    }
}

I use explicit data model:

var routingConventions = ODataRoutingConventions.CreateDefault();
config.Routes.MapODataRoute(
            routeName: "ODataRoute",
            routePrefix: "odata",
            model: BuildExplicitODataModel(),
            pathHandler: new DefaultODataPathHandler(),
            routingConventions: routingConventions  
        );

public static IEdmModel BuildExplicitODataModel()
{
ODataModelBuilder modelBuilder = new ODataModelBuilder();
var announcementSet = buildAnnouncementModel(modelBuilder);
return modelBuilder.GetEdmModel();
}

private static EntitySetConfiguration<Announcement> buildAnnouncementModel(ODataModelBuilder modelBuilder)
{
        var announcementSet = modelBuilder.EntitySet<Announcement>("Announcement");
        announcementSet.HasEditLink(
            entityContext => entityContext.Url.ODataLink(
                new EntitySetPathSegment(entityContext.EntitySet.Name),
                new KeyValuePathSegment(ODataUriUtils.ConvertToUriLiteral(entityContext.EntityInstance.AnnouncementID, ODataVersion.V3))),
            followsConventions: true);

        announcementSet.HasIdLink(
            entityContext => entityContext.Url.ODataLink(
                new EntitySetPathSegment(entityContext.EntitySet.Name),
                new KeyValuePathSegment(ODataUriUtils.ConvertToUriLiteral(entityContext.EntityInstance.AnnouncementID, ODataVersion.V3))),
                followsConventions: true);

        var announcement = announcementSet.EntityType;
        announcement.HasKey(p => p.AnnouncementID);
        announcement.Property(p => p.Description);
        announcement.Property(p => p.IsPrivate);
        announcement.Property(p => p.CreatedTime);
        announcement.Property(p => p.MembershipID);
        return announcementSet;
}

Here is request payload:

PATCH http://{url}/odata/Announcement(guid'62E34FB3-A37E-413F-9843-642D95BA580A')
Accept: application/json
Content-Type: application/json; charset=utf-8

BODY:

{"Description":"Patched description from Unit Test"}

1

1 Answers

0
votes

After I checked the latest file EntityRoutingConvention.cs, I found that the actionName can only be Patch or PatchAnnouncement.

The PatchEntity is never called.

Can you try renaming the method with Patch or PatchAnnouncement?


UPDATE

However it works fine in my repro project. Can you compare your reference version, WebApiConfig.cs, AnnouncementsController.cs and request with mine.

I notice some difference:

  1. The controller name is ODataAnnouncementController, do you use "ODataAnnouncement" as the entittyset name in the model builder?
  2. In your PATCH request, why use "Announcement" rather than "Announcements"?

Reference

Microsoft.AspNet.WebApi 5.1.2 targetFramework="net45"

WebApiConfig.cs

using System.Web.Http;
using System.Web.Http.OData.Builder;
using ReproPatchNotWork.Models;

namespace ReproPatchNotWork
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.MapHttpAttributeRoutes();

            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
            builder.EntitySet<Announcement>("Announcements");
            config.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel());
        }
    }
}

AnnouncementsController.cs

using System;
using System.Web.Http.OData;

namespace ReproPatchNotWork.Models
{
    public class AnnouncementsController : EntitySetController<Announcement, Guid>
    {
        protected override Announcement PatchEntity(Guid key, Delta<Announcement> patch)
        {
            Announcement announcement = new Announcement();
            patch.Patch(announcement);
            return announcement;
        }
    }
}

Request

PATCH http://{url}/odata/Announcements(guid'62E34FB3-A37E-413F-9843-642D95BA580A')

Accept: application/json

Content-Type: application/json; charset=utf-8

Body: {"Description":"Patched description from Unit Test"}