I am developing a rest api on .Net core 2.2 following MVC pattern.
I have a controller with a post method like this...
// POST: api/Todo
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(string param, [FromBody] TodoItem item)
{
// some work...
return CreatedAtAction(nameof(GetTodoItem), new { id = item.Id }, item);
}
And it works fine.
The customer asked to have an api on the same route, but the Json body could have 2 different structures, bearing the same data on different schemas.
I considered using
PostTodoItem(string param, [FromBody] Object item)
{
// TryCast item to one of the possible POCO classes then work with the correct one.
}
Do you know a better way, maybe with some advanced routing and filtering option?
This is not really possible nor desirable. Pretty much the core tenant of REST is a URI uniquely represents a particular resource. If you've got a URI like POST /todo, then the post body should be a "todo" and it should create a new "todo" based on that. Here, that is a TodoItem, so that is all that should ever be posted.
REST aside, this just won't work. When your action is activated, the modelbinder attempts to bind the post body to the param(s) that the action accepts. It basically just news up whatever type the param is, and then attempts to find something from the post body to bind to the various properties on that type. This is an intentionally simplistic description of what's happening; the important part is that the type of the param informs how the post body is bound. If you bind to an object (which has no members) or even a base type, then the only members of the post body that will be bound are those that are present on that type, not derived types thereof. Anything that cannot be bound is discarded.
Long and short, you need a unique route for each type of thing you're working with. Under the hood, you can share or otherwise reuse code by factoring out common functionality into private methods, employing inheritance, etc., but you need a distinct action and route to handle each case.
Related
What do I want:
I want to be able to generate swagger documentation that passes a key/value into the URL. This so that I can use generic arguments controller to handle my requests like Dictionary.
If swagger can't generate it, is there a way to generate the documentation by using reflection on my objects? This so that I can still use generic methods
If not, what would be the best way to let everyone know what the correct approach would be.
Why do I want it
I'm developing a new API and I'm using swagger to create the documentation. In this API I want to work with some generic methods to prevent hardcoding things. For example on the PATCH method I use a Dictionary<string, string> to get the property/value combination and in the GET I use a custom object as the argument. In both cases, swagger can't generate the correct parameter fields, because it takes the argument as url key.
Example action & form - incorrect
public async Task<IActionResult> Patch(int id, Dictionary<string, string> viewModel)
{
return await ConnectionWrapper(() => connector.Patch(id, viewModel));
}
This uses the body, not the query
Other examples - incorrect
In the GET I have a model with a custom modelbinder to handle all the rest URL arguments. The problem is because the model is defined it sees the filter as a property.
Then it is in the URL, but it will look like http://example.com/controller/method/id?sort=prop_asc&filter=propTwo%3D=value, instead of http://example.com/controller/method/id?sort=prop_asc&propTwo=value
Desired output
I've modified the HTML to simulate what I would like in the picture above. The URL that would be called would be http://example.com/controller/method/id?propertyName=propertyValue.
I don't mind if there would be only one option to add a generic key/value pair because with it I can demonstrate what I want.
Expected solution
I think the solution lies in the MapType startup method of swagger or in an implementation of the IOperationFilter, but I haven't been able to figure it out.
I am not so into RESTful web service and I have the following doubt about how to correctly implement RESTful style in my servics working on a Spring Boot application (Spring MVC is the same thing).
So basically I have some controller class like this:
#RestController
#RequestMapping("/RoomMedia")
public class RoomMediaController {
private static final Logger log = LoggerFactory.getLogger(RoomMediaController.class);
#Autowired
private RoomMediaService roomMediaService;
public RoomMediaController() {
log.debug("RoomMediaController init");
}
#RequestMapping(value = "getAllImagesByRoomId",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<byte[]>> getAllImagesByRoomId(Long roomId) throws DataAccessException {
log.debug("getAllImagesByRoomId START");
List<byte[]> result = roomMediaService.getRoomImageListById(roomId);
log.debug(result.toString());
log.debug("getAllImagesByRoomId END");
return ResponseEntity.ok(result);
}
}
I think that, also if the base idea is RESTful like, it can't be considered a true RESTful WS.
I think that the main problem is related to the endpoint: the getAllImagesByRoomId() method handle HTTP GET request toward URL like this: /RoomMedia/getAllImagesByRoomId?roomId=7
From what I have understand reading some tutorial this is not RESTful style because I have to access to a resource without passing parameter, I have to do someting like this:
/RoomMedia/Images/7
Is it my reasoning correct?
Moreover I think that the previous method is pretty trivial also in the implementation: this method return the list of all the images associated to a room having id=7(it is an application related to hotels).
From what I have understand also its logic is againts RESTful principles.
Is it better organize in the following way?
Doing a GET request like this:
/RoomMedia/7/Images/: I have to obtain the list of all the images associated to the room having id=7 as URI (or maybe is better handle a path like /7/RoomMedia/Images/ ?).
/RoomMedia/7/Images/1 I obtain the image with the id=1 associated to the room having id=7.
Are al these reasoning correct or am I missing something?
your assumtions are correct. Which of the mapping that you suggest, is deppending totaly on how your model is. Martin Fowler has a nice article about the Richardson Maturity Model which is sum up as:
Level 1 tackles the question of handling complexity by using divide and conquer, breaking a large service endpoint down into multiple resources.
Level 2 introduces a standard set of verbs so that we handle similar situations in the same way, removing unnecessary variation.
Level 3 introduces discoverability, providing a way of making a protocol more self-documenting.
For your project you could use jhipster because its offer you the best of spring with angular under a REST-ful design.
URLs must not contain a verb. The verb is provided by the method, in your case - surprise, surprise - GET.
ByRoomId is pointless as well, because that's what ?roomId= says.
That leaves AllImages, where All is superfluous.
Which results in the URL /RoomMedia/Images?roomId=7. What's unclear is the relationship between RoomMedia and Images, if there is any. If RoomMedia refers to a room, then your suggestion /RoomMedia/7/Images/ would be correct.
/RoomMedia/7/Images/1 is questionable. It should rather be /Images/1. It's irrelevant in which room the image with the id 1 is in.
When you design a REST API, then the first thing to do is to think about all resources you want to expose and their relationships. After that the URLs will reveal themselves automatically, so to speak.
Basically I'm writing an API using Web API 2 and Entity Framework on the backend.
The thing I'm unsure about is what to do in regards to foreign keys on my models.
Say I got a person property with a foreign key to an order property.
Sometimes the client needs the person property, sometimes it does not. How should I go about this?
Should I create 2 methods:
/api/person/1 <-- returns person without order
/api/personwithorder/1 <-- returns person with order
Seems like an awful lot of methods in my opinion.
I know the queryable attribute exists as well which provides support for the client to use the $extend argument to include properties at will - however I would rather not use the queryable attribute if I can avoid it.
What are your suggestions?
Off the top of my head, here are some options.
Option 1
Multiple methods in API
public Person GetPerson() { ... }
public Person GetPersonWithOrders() { ... }
Option 2
Pass a flag to the method:
public Person GetPerson(bool getOrders) { ... }
Option 3
You could use OData to allow the caller to query the data.
To expand on DavidG's option 3 of using OData, here is how you'd do it:
Install-package Microsoft.AspNet.WebApi.OData
Create a PeopleController inheriting from ODataController
Configure the Web API OData model like so:
modelBuilder.EntitySet<Person>("People");
Define your Get method as returning an IQueryable<Person>
In your calling code, add the expand clause to the URL to specify the child object you would like to expose, like this: /api/People(1)?$expand=Orders
There's a little bit more to it around registering the OData route, but this is all standard configuration that you can find in any sample project.
OData is really very flexible and takes care of tonnes of issues about how you should build your URLs.
I am trying to convert an existing wcf rest api to ServiceStack, and having issues right out of the gate:
[Route("foo/{userId}","POST")]
public class MyInputModel : IReturnVoid
{
public string userId { get; set; }
public SomeOtherObject properties { get; set; }
}
The intention here is that I would provide the userId in the url, and an instance of SomeOtherObject in the post body. The error I get is
<Message>Could not deserialize 'application/xml' request using MyInputModel'
Error: System.Runtime.Serialization.SerializationException:
Error in line 1 position 42. Expecting element 'MyInputModel'
from namespace 'blahblahblah'.. Encountered 'Element' with name
'SomeOtherObject', namespace 'http://blahblahblah'.
The only things I can think of are to wrap my xml in a MyInputModel to make the serializer happy. This is not really an option for backwards compatibility.
I could also modify SomeOtherObject to be the top level input model, and put a UserId property in there, but this also feels suboptimal since it is an object used throughout the api, and is really not tied to a user id. It is also already published independently, so it would be painful to make changes there.
Is there any way to indicate that the root element of the posted data will be a SomeOtherObject insted of a MyInputModel? In WebApi this would be with the [FromBody] attributes and whatnot. Does servicestack have anything similar?
The purpose of a DTO is to auto-generate the wire format which is why ServiceStack requires the Request DTO to match the shape of the incoming request. Part of what makes ServiceStack so productive is that it's a code-first web service framework which encourages starting from C# and projecting out, i.e. your clients should bind to your web service outputs and not the other way round of mapping code-first models to existing schema inputs.
Having said that, the Serialization / Deserialization wiki page lists the different ways to override ServiceStack's default request binding with your own.
Access HTTP Request variables in any Service or Filter
Not everything needs to be mapped to a DTO as any HTTP Variable can still be accessed from the IHttpRequest available from any service or filter, i.e:
base.Request.QueryString
base.Request.FormData
base.Request.Headers[name]
base.Request.PathInfo
base.Request.AbsoluteUri
Let's say the Action below is exposed via REST service and is called from a different appliation how would it handle the posted data/object?
Should I use Create(FormCollection collection) here?
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Member member)
{
....
}
I'd suggest using a model, but one in which all of the parameters are nullable and use the RequiredAttribute for actual, required parameters. This would allow your method to accept invalid requests -- with missing or extra data -- yet have valid data bound to the model. For invalid data, you can supply error returns instead of presuming default values for non-nullable properties. Using the model binding validation architecture provides a convenient way to make sure that the request is legal. It would be up to you how you want to handle "extra" data supplied by the request -- I'd say ignore it.