What I should return from WebAPI ?
There are several approaches when returning code from WebAPI . Let’s frame the problem: say we have a Person controller with 2 actions:
– a GET {id} – that retrieves the Person with id
– a POST {Peron} – that tries to verify the validity of the Person and then saves to database.
We will answer to some questions:
1.What we will return if the person with id does not exists ?
2. What happens with the validation about the Person ?
Let’s see the Person class.
01 02 03 04 05 06 07 08 09 10 | public class Person: IValidatableObject { public int ID { get ; set ; } public string Name { get ; set ; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { yield return new ValidationResult( "not valid not matter what" ); } } |
First approach: Do nothing
What I mean is just return the Person , even if it is null.
1.1. The GET
1 2 3 4 5 6 | [HttpGet( "{id}" )] public Person GetPerson( int id) { var p = RetrieveFromDatabase(id); return p; } |
The problem is that when we return null from an action, there are no response data from the HTTP call- and the javascript should handle it .
1.2 The POST – no problem . [ApiController] should handle the validation and return BadRequest if not validate
Second approach: Return standard HttpCodes
2.1 The GET
01 02 03 04 05 06 07 08 09 10 | [HttpGet( "{id}" )] public ActionResult<Person> GetPerson404( int id) { var p = RetrieveFromDatabase(id); if (p == null ) return NotFound($ "{nameof(Person)} with {id} are not found" ); return p; } |
We intercept the null and return standard 404 HttpCode . In that case, I strongly suggest to add a message to the end-caller – to know that the reason is not that the http call was a problem, but the Person was not found
2.2 The POST – no problem . [ApiController] should handle the validation and return BadRequest if not validate
Third approach: Return OK with a custom ReplyData class
The ReplyData can look this way
1 2 3 4 5 6 | public class ReplyData<T> { public bool Success { get ; set ; } public string Message { get ; set ; } public T ReturnObject { get ; set ; } } |
2.1 The GET
This action is over simplified – the code just returns the ReplyData from the database.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | [HttpGet( "{id}" )] public ReplyData<Person> GetWithReply( int id) { return RetrieveWithReplyFromDatabase(id); } private ReplyData<Person> RetrieveWithReplyFromDatabase( int id) { try { Person p = null ; //retrieve somehow if (p == null ) { var r = new ReplyData<Person>(); r.Success = false ; r.Message = "Cannot find person with id " + id; return r; } else { var r = new ReplyData<Person>(); r.Success = true ; r.ReturnObject = p; return r; } } catch (Exception ex) { var r = new ReplyData<Person>(); r.Success = false ; r.Message = ex.Message; return r; } } |
2.2 The POST
This is somehow complicated – the [ApiController] , if not valid, return a 400 BadRequest . So we should modify this with a custom middleware to return the same ReplyData
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | public class From400ValidationToReply : IMiddleware { public async Task InvokeAsync(HttpContext context, RequestDelegate next) { try { var originalStream = context.Response.Body; var bufferStream = new MemoryStream(); context.Response.Body = bufferStream; await next(context); bufferStream.Seek(0, SeekOrigin.Begin); if (context.Response.StatusCode != 400) { await bufferStream.CopyToAsync(originalStream); return ; } var reader = new StreamReader(bufferStream); var response = await reader.ReadToEndAsync(); if (!response.Contains( "errors" )) { await bufferStream.CopyToAsync(originalStream); return ; } context.Response.StatusCode = 200; var r = new ReplyData<Person>(); r.Success = false ; r.Message=response; var text = JsonSerializer.Serialize(r); var bit = Encoding.UTF8.GetBytes(text); context.Response.ContentLength = bit.Length; await originalStream.WriteAsync(bit); } catch (Exception ex) { Console.WriteLine(ex.Message); throw ; } } |
And the problem is that will affect all the request pipeline….
Conclusion:
I would choose 2 . But … your choice… ( and , for an advanced discussion, please read https://blog.ploeh.dk/2013/05/01/rest-lesson-learned-avoid-hackable-urls/ )