Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PATCH not binding request model #1024

Closed
joshuaold opened this issue Jul 4, 2021 · 1 comment · Fixed by #1091
Closed

PATCH not binding request model #1024

joshuaold opened this issue Jul 4, 2021 · 1 comment · Fixed by #1091

Comments

@joshuaold
Copy link

joshuaold commented Jul 4, 2021

Please read our Contributing Guides before submitting a bug.

DESCRIPTION

I have an ASP.Net MVC app that has a mix of JSONAPI and non-JSONAPI apis.
Only one class extends the BaseJsonApiController while all the others extend ControllerBase.

For my JSONAPIs, GET requests work fine with no issues but when I try to call my PATCH API, the request body is not binding at all and instead shows as NULL, throwing me the error Value cannot be null. (Parameter 'resource')

But when I check the debug logs, the JsonApiReader acknowledges the request and even says that it has received the request.

2021-07-04 23:24:52.9458|TRACE|JsonApiDotNetCore.Serialization.JsonApiReader|Received request at 'https://localhost:44370/api/1.0/Employer/Timesheet/member-job-allocations/2c378683-86a9-4e18-9b37-01e85464717c' with body: <<{
  "id": "618AC908-FFF8-4995-9D14-C9A001609DD7",
  "type": "memberJobAllocations",
  "attributes": {
    "allocatedDateUtc": "2021-01-01",
    "deAllocatedDateUtc": "2021-01-01",
    "deAllocatedReason": "Absent every day",
    "deAllocatedReasonCode": "New"
  },
  "relationships": {
    "data": {
      "id": "618AC908-FFF8-4995-9D14-C9A001609DD7",
      "type": "members",
      "attributes": {
        "firstName": "Joe",
        "lastName": "Bloggs",
        "emailAddress": "joe@bloggs.com",
        "address": "342 Wharerata Rd",
        "city": "Gisborne",
        "statusCode": "New",
        "isRegistered": true,
        "isVerified": true
      }
    }
  }
}>>
2021-07-04 23:25:27.3640|TRACE|JsonApiDotNetCore.Controllers.BaseJsonApiController|Entering PatchAsync(id: 2c378683-86a9-4e18-9b37-01e85464717c, resource: null)
2021-07-04 23:25:27.6127|ERROR|JsonApiDotNetCore.Middleware.ExceptionHandler|Value cannot be null. (Parameter 'resource')
2021-07-04 23:25:27.7013|TRACE|JsonApiDotNetCore.Serialization.JsonApiWriter|Sending 500 response for PATCH request at 'https://localhost:44370/api/1.0/Employer/Timesheet/member-job-allocations/2c378683-86a9-4e18-9b37-01e85464717c' with body: <<{"errors":[{"id":"9878f5dd-1f9d-4c28-87cc-51a102cb8e1f","status":"500","title":"An unhandled error occurred while processing this request.","detail":"Value cannot be null. (Parameter 'resource')"}]}>>

I tried two types of request body formats (one generated by Swagger and one that follows JSON API spec) but none seem to work.

Below are the files that I have created.
Note that I have omitted non-JSONAPI code.

Models

public class MemberJobAllocation : Identifiable<Guid>
{
    public override Guid Id { get; set; }

    // Attributes
    [Attr]
    public DateTime? AllocatedDateUtc { get; set; }

    [Attr]
    public DateTime? DeallocatedDateUtc { get; set; }

    [Attr]
    public string DeallocatedReason { get; set; }

    [Attr]
    public string DeallocatedReasonCode { get; set; }

    // Relationships
    [HasOne]
    public Member Member { get; set; }
}
public class Member : Identifiable<Guid>
{
    public override Guid Id { get; set; }

    // Attributes
    [Attr]
    public string FirstName { get; set; }

    [Attr]
    public string LastName { get; set; }

    [Attr]
    public string EmailAddress { get; set; }

    [Attr]
    public string Address { get; set; }

    [Attr]
    public string City { get; set; }

    [Attr]
    public string StatusCode { get; set; }

    [Attr]
    public bool? IsRegistered { get; set; }

    [Attr]
    public bool? IsVerified { get; set; }
}

Controllers

[DisableRoutingConvention]
[ApiVersion("1.0")]
public class MemberJobAllocationController : BaseJsonApiController<MemberJobAllocation, Guid>
{
    private readonly IAuthorizationService _authorizationService;

    public MemberJobAllocationController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService<MemberJobAllocation, Guid> resourceService, IAuthorizationService authorizationService)
    : base(options, loggerFactory, resourceService)
    {
        _authorizationService = authorizationService;
    }

    // THIS IS WHERE THE ERROR OCCURS
    // memberJobAllocation does not bind the request body and instead shows NULL 
    [HttpPatch]
    [Microsoft.AspNetCore.Mvc.Route("api/{version:apiVersion}/Employer/Timesheet/member-job-allocations/{id}")]
    public override async Task<IActionResult> PatchAsync(Guid id, [FromBody] MemberJobAllocation memberJobAllocation, CancellationToken cancellationToken)
    {
        return await base.PatchAsync(id, memberJobAllocation, cancellationToken);
    }
}

DbContext

public class TimesheetDbContext : DbContext
{
    public TimesheetDbContext(DbContextOptions<TimesheetDbContext> options) : base(options)
    {
    }

    public DbSet<MemberJobAllocation> MemberJobAllocation { get; set; }

    public DbSet<Member> Member { get; set; }
}

Startup.ConfigureServices

public void ConfigureServices(IServiceCollection services)
{
    // JSON API
    // Add the Entity Framework Core DbContext like you normally would
    services.AddDbContext<TimesheetDbContext>(options =>
    {
        // Use whatever provider you want, this is just an example
        options.UseSqlServer(_someconnectionstringhere_);
    });

    // Add JsonApiDotNetCore
    services.AddJsonApi<TimesheetDbContext>();

    // ...non-JSONAPI code below
}

Startup.Configure

public void Configure(IApplicationBuilder app, AppDbContext context)
{
    // ...non-JSONAPI code here

    app.UseRouting();

    // ...non-JSONAPI code here

    context.Database.EnsureCreated();
    app.UseJsonApi();
    app.UseEndpoints(endpoints => endpoints.MapControllers());
}

PATCH Request URL and Body

https://localhost:44370/api/1.0/Employer/Timesheet/member-job-allocations/2c378683-86a9-4e18-9b37-01e85464717c

Swagger-generated format

{
  "id": "2c378683-86a9-4e18-9b37-01e85464717c",
  "allocatedDateUtc": "2021-07-04T09:16:52.097Z",
  "deallocatedDateUtc": "2021-07-04T09:16:52.097Z",
  "deallocatedReason": "string",
  "deallocatedReasonCode": "string",
  "member": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "firstName": "string",
    "lastName": "string",
    "emailAddress": "string",
    "address": "string",
    "city": "string",
    "statusCode": "string",
    "isRegistered": true,
    "isVerified": true,
    "stringId": "string",
    "localId": "string"
  },
  "stringId": "string",
  "localId": "string"
}

JSONAPI format

{
  "id": "618AC908-FFF8-4995-9D14-C9A001609DD7",
  "type": "memberJobAllocations",
  "attributes": {
    "allocatedDateUtc": "2021-01-01",
    "deAllocatedDateUtc": "2021-01-01",
    "deAllocatedReason": "Absent every day",
    "deAllocatedReasonCode": "New"
  },
  "relationships": {
    "data": {
      "id": "618AC908-FFF8-4995-9D14-C9A001609DD7",
      "type": "members",
      "attributes": {
        "firstName": "Joe",
        "lastName": "Bloggs",
        "emailAddress": "joe@bloggs.com",
        "address": "342 Wharerata Rd",
        "city": "Gisborne",
        "statusCode": "New",
        "isRegistered": true,
        "isVerified": true
      }
    }
  }
}

EXPECTED BEHAVIOR

[FromBody] MemberJobAllocation memberJobAllocation binds values coming from the request body

ACTUAL BEHAVIOR

[FromBody] MemberJobAllocation memberJobAllocation is NULL

{
    "errors": [
        {
            "id": "6be4f128-3ec0-402c-b4c6-573d69ad1842",
            "status": "500",
            "title": "An unhandled error occurred while processing this request.",
            "detail": "Value cannot be null. (Parameter 'resource')"
        }
    ]
}

VERSIONS USED

  • JsonApiDotNetCore version: 4.2.0
  • ASP.NET Core version: 3.1
  • Entity Framework Core version: 5.0.7
  • Database provider: SQL Server
@bart-degreed
Copy link
Contributor

Thanks for providing all the details. It looks like your response body does not contain a top-level data element, see here for an example. However, the error you're getting back is not very helpful, so I'd like to keep this issue open to remind us to improve that.

On a side note, Swagger/OpenAPI does not work (tracked here).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging a pull request may close this issue.

2 participants