0

Suppose I have below URL template in my API:
/obj/execute/{compositeIdOne:int:required}/{compositeIdTwo:int:required}

Which is mapped to the following controller action
public async Task<ActionResult> SomeTask(RequestObject requestObj, CancellationToken ct = default) { ... }

With below request body:

{
  "status": "string",
  "name": "string",
  "amount": 10
}

Now suppose I want both the request body and route parameters inserted into the RequestObject parameter specified. Of course I could supply the ids in the route as separate parameters and then set the properties of the object in the function body, but that feels ugly.

Would I be able to specify the parameter location in the class definition and have .NET autofill the object that way.
In example:

public class RequestObject
{
    [FromRoute("compositeIdOne")]
    public int IdOne { get; set; }

    [FromRoute("compositeIdTwo")]
    public int IdTwo { get; set; }

    [FromBody("status")]
    public string Status { get; set; }

    [FromBody("name")]
    public string Name { get; set; }

    [FromBody("amount")]
    public int Amount { get; set; }
}

I realise this may be a very abstract use case, however it would make for much cleaner controllers as well as make the API much cleaner to consume.

Any other suggestions to achieve this are of course appreciated.
Thank you in advance!

thebugsdontwork
  • 401
  • 3
  • 17
  • It depends on the Verb you are calling, Why not sending all the values in the body for a POST call? or all parameters in Url path or Query string for a GET? To me is not the best practice to split your parameters except for PUT or PATCH where a resource ID is needed. – Mauricio Atanache Jul 27 '22 at 15:02
  • @MauricioAtanache the URL is a task in the background, rather than a CRUD operation. But the task is called on a resource, so I'm guessing that would be a PATCH? – thebugsdontwork Jul 28 '22 at 06:42
  • It depends, on whether it's idempotent or not, think about it, if you run it 3 times will everything be the same in your application's state or you will have incremental changes. If you find every time you run your endpoint with the same parameters your application state will change, then you should use POST – Mauricio Atanache Jul 28 '22 at 15:21
  • @MauricioAtanache the API is stateless, the concerning requests are handled and forwarded to another web service. In that sense, the only POST requests are the ones that create resources. – thebugsdontwork Jul 29 '22 at 07:50
  • Key word is Idempotency, think about it, if you send the same PUT request multiple times, it's idempotent since the after changing the state the first time it will never change again, Same with Delete, after you delete and entity by ID, further times you call it won't impact the state of your data, with a POST however every time you call the same request, is very likely a new entity will be created changing the state of your data. API is stateless, the underlying application is not, again, what is the impact of your endpoint in your backend application? – Mauricio Atanache Jul 29 '22 at 17:00
  • https://restfulapi.net/idempotent-rest-apis/ – Mauricio Atanache Jul 29 '22 at 17:04
  • @MauricioAtanache in the specific case I have in mind, the backend application would generate a "context" for a specific data model based on some condition (context being what other tasks I can perform that **would** mutate the data, so they would be POST or PUT requests). The context itself however does not generate new data, it returns a couple booleans. – thebugsdontwork Aug 01 '22 at 11:33

1 Answers1

0

I think you'd better use two objects,one for query data,one for body data:

Models:

public class QueryObject
    {
        public int compositeIdOne { get; set; }
        public int compositeIdTwo { get; set; }


    }
    public class RequestObject
    {
        public string Status { get; set; }

        public string Name { get; set; }

        public int Amount { get; set; }
    }

action:

[HttpPatch("/obj/execute/{compositeIdOne:int:required}/{compositeIdTwo:int:required}")]
public async Task<ActionResult> SomeTask(QueryObject q,[FromBody]RequestObject requestObj, CancellationToken ct = default) { ... }

Or you can try to use custom model binding,here is a link about binding query data.And here is the link about binding body data.But in this way,you need to bind a custom model binder for each model.

Yiyi You
  • 16,875
  • 1
  • 10
  • 22