Client WebServiceException has ResponseStatus null without explicit ResponseStatus - error-handling

I am quite new to ServiceStack, I am following the example at http://nilsnaegele.com/codeedge/servicestack1.html which I have been finding useful.
I read that explicit StatusResponse fields in DTO Response declarations were not required in the new API, but I dont appear to be getting the expected behaviour here.
Using ServiceStack 3.9.71.
I introduced an Exception in the EntryService post to get a feel for the client handling.
public object Post(Entry request)
{
if (request.Quantity == 3)
{
throw new WebException("post entry");
}
}
With
public class EntryResponse
{
public int Id { get; set; }
}
Then in the client side when posting an Entry handle the exception.
try
{
var entryRequest = new Entry {Quantity = quantity, EntryTime = DateTime.Now};
var response = client.Send(entryRequest);
Console.WriteLine("Response: {0}", response.Id);
}
catch (WebServiceException wse)
{
// At this point wse.ResponseStatus field is null.
}
I tested out explicitly adding the ResponseStatus field to EntryResponse and this produced the ResponseStatus filled in on the client with no change to the client code.
I then tried throwing an exception in StatusRequestService as follows to see if the second web service client request would behave the same way, and it appears it behaves differently.
public object Any(StatusRequest request)
{
if (request.Lever == 3)
{
throw new WebException("get status.");
}
}
With the following.
public class StatusResponse
{
public int Total { get; set; }
public int Goal { get; set; }
}
Then catching this in the client as per
try
{
var postResponse = client.Post<StatusResponse>("status", new StatusRequest { Date = DateTime.Now, Lever = 3 });
Console.WriteLine("{0} of {1} achieved", postResponse.Total, postResponse.Goal);
}
catch (WebServiceException wse)
{
// At this point wse.ResponseStatus field is valid and filled in.
}

If you want to use the {RequestDto}Response convention and also ensure a ResponseStatus is returned you have to opt-in and add it to the Response DTO, e.g:
public class StatusResponse
{
public int Total { get; set; }
public int Goal { get; set; }
public ResponseStatus ResponseStatus { get; set; }
}
This is because there is an explicit exception for Responses that follow the convention {RequestDto}Response naming convention:
If it exists:
The {RequestDto}Response is returned, regardless of the service method's response type. If the {RequestDto}Response DTO has a ResponseStatus property, it is populated otherwise no ResponseStatus will be returned. (If you have decorated the {ResponseDto}Response class and properties with [DataContract]/[DataMember] attributes, then ResponseStatus also needs to be decorated, to get populated).
Otherwise, if it doesn't:
A generic ErrorResponse gets returned with a populated ResponseStatus property.
The Service Clients transparently handles the different Error Response types, and for schema-less formats like JSON/JSV/etc there's no actual visible difference between returning a ResponseStatus in a custom or generic ErrorResponse - as they both output the same response on the wire.

Related

POST null because property type does not match?

I have a asp .net core API with simple REST methods like this:
[Route("api/[controller]")]
public class SomeController : ControllerBase
{
[HttpPost]
public async Task<IActionResult> Insert([FromBody] ItemClass newItem)
{
[...]
return Ok();
}
}
The model class is pretty simple as well:
public class ItemClass
{
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; }
}
When I POST this JSON to the service
{
"id": null,
"name": "Some name",
"description": "Some description",
"isActive": null
}
the method is called with newItem set to null. No error, no exception, the the object was just null. It took me quite a while to figure out the parameter isActive was the cause of the problem. In the class I defined it as bool, but JSON defined it as null. When I either change the value to true or false or when I leave it out completely or when I change the parameter to bool? it works again. The object is deserialized correctly.
I'm used to asp net core APIs to be very resilient. Usually when a parameter is simply set to its default when it can not be parsed correctly from the message.
But why does the whole object become null here?
Is there at least a way to tell asp net core to throw an exception when the deserialization fails?
When null is mapped to the bool, resulting in a type mismatch exception, it will cause the whole object become null.
If this api is 3.x, it will trigger 400. But in 2.x and 5.0, they have no exception. You can handle the null value through NewtonSoft.json.
A simple way to handel it with this configuration, it can assign a default value to bool, but it doesn't trigger exception.
In 2.x
services.AddMvc()
.AddJsonOptions(options=>
{
options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
});
5.0
services.AddControllers()
.AddNewtonsoftJson(option=>
{
option.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
});
If must return an error, you have to use a custom model binding.
public class CustomBindClassBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var itemClass = new ItemClass();
using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body))
{
var body = reader.ReadToEndAsync();
var mydata = JsonConvert.DeserializeObject<JObject>(body.Result);
if(mydata["isActive"].ToString()==""|| mydata["isActive"].ToString() == null)
{
bindingContext.Result= ModelBindingResult.Failed();
throw new Exception("isActive is not correct");
}
else
{
itemClass.Id = mydata["id"].ToString();
itemClass.Name = mydata["name"].ToString();
itemClass.Description = mydata["description"].ToString();
itemClass.IsActive = bool.Parse(mydata["isActive"].ToString());
bindingContext.Result = ModelBindingResult.Success(itemClass);
}
}
return Task.CompletedTask;
}
}
In action
[HttpPost]
public async Task<IActionResult> Insert([FromBody][ModelBinder(typeof(CustomBindClassBinder))] ItemClass newItem)
{
return Ok(newItem);
}

CustomActionFilter not getting called for POST/PUT endpoint in web api [duplicate]

I am using .NET Core 2.2 with Web API. I have created one class, i.e., as below:
public class NotificationRequestModel
{
[Required]
public string DeviceId { get; set; }
[Required]
public string FirebaseToken { get; set; }
[Required]
public string OS { get; set; }
public int StoreId { get; set; }
}
Using the above class I have created one method. Now I want to return a custom object, but it's returning its own object.
API method is:
public ActionResult<bool> UpdateFirebaseToken(NotificationRequestModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(FormatOutput(ModelState.Values));
}
var result = _notificationService.InsertOrUpdateFirebaseToken(model);
return Ok(result);
}
Here FormatOutput method is format the output.
protected Base FormatOutput(object input, int code = 0, string message = "", string[] details = null)
{
Base baseResult = new Base();
baseResult.Status = code;
baseResult.Error = message;
baseResult.TimeStamp = CommonHelper.CurrentTimeStamp;
baseResult.Code = code;
baseResult.Details = details;
baseResult.Message = message; //Enum.Parse<APIResponseMessageEnum>(code.ToString(), true); // (enum of code get value from language)
return baseResult;
}
But the issue is it returns:
{
"errors": {
"DeviceId": [
"The DeviceId field is required."
]
},
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "80000049-0001-fc00-b63f-84710c7967bb"
}
I want to customize this error with my model. I need error message and details from return output and passed it to my model. How can I do that? I had try to debug my code and found that breakpoint on API method is not calling. So I can't handle my custom method. Is there any solution? What am I doing wrong?
When using a controller with the ApiController attribute applied, ASP.NET Core automatically handles model validation errors by returning a 400 Bad Request with ModelState as the response body. As such, your conditional testing ModelState.IsValid is essentially always false (and therefore not entered) because the only requests that will ever get this far are valid ones.
You could simply remove the ApiController attribute, but that removes a bunch of other beneficial stuff the attributes adds as well. The better option is to use a custom response factory:
services.Configure<ApiBehaviorOptions>(o =>
{
o.InvalidModelStateResponseFactory = actionContext =>
new BadRequestObjectResult(actionContext.ModelState);
});
That's essentially what's happening by default, so you'd simply need to change the action provided there accordingly to customize it to your whims.
As Chris analyzed, your issue is caused by Automatic HTTP 400
responses.
For the quick solution, you could suppress this feature by
services.AddMvc()
.ConfigureApiBehaviorOptions(options => {
options.SuppressModelStateInvalidFilter = true;
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
For an efficient way, you could follow the suggestion from Chris, like below:
services.AddMvc()
.ConfigureApiBehaviorOptions(options => {
//options.SuppressModelStateInvalidFilter = true;
options.InvalidModelStateResponseFactory = actionContext =>
{
var modelState = actionContext.ModelState.Values;
return new BadRequestObjectResult(FormatOutput(modelState));
};
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
And, there isn't any need to define the code below any more in your action.
if (!ModelState.IsValid)
{
return BadRequest(FormatOutput(ModelState.Values));
}

How to validate for illegal fields in Model Validation

I have a .NET Core 2.2 web-api that accepts a PersonDto, it is getting validated with Model Validation, but it does not check for illegal fields. It only checks if matching fields are valid.
I want to make sure that the supplied JSON contains only the fields that are in my Dto (Class).
public class PersonDto
{
public string firstname { get; set; }
public string lastname { get; set; }
}
My controller looks simplified like this:
public async Task<ActionResult<Person>> Post([FromBody] PersonDto personDto)
{
// do things
}
I send it incorrect fields (name does not exist in my dto) and the ModelState is valid.
{
"name": "Diego"
}
I expected the Model Validation to complain that the field "Name" does not exist.
How can I check for illegal fields?
You could use ActionFilter and Reflection to compare the request body content to the model fields. If there are unexpected fields, manually add model errors and the ModelState.IsValid will be false.
1.Create an ActionFilter
public class CompareFieldsActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
//get all fields name
var listOfFieldNames = typeof(PersonDto).GetProperties().Select(f => f.Name).ToList();
var request = context.HttpContext.Request;
request.Body.Position = 0;
using (var reader = new StreamReader(request.Body))
{
//get request body content
var bodyString = reader.ReadToEnd();
//transfer content to json
JObject json = JObject.Parse(bodyString);
//if json contains fields that do not exist in Model, add model error
foreach (JProperty property in json.Children())
{
if (!listOfFieldNames.Contains(property.Name))
{
context.ModelState.AddModelError("Filed", "Field does not exist");
}
}
}
base.OnActionExecuting(context);
}
}
2.Use the filter on your action:
[HttpPost]
[CompareFieldsActionFilter]
public async Task<ActionResult<Person>> Post([FromBody] PersonDto personDto)
{
if (ModelState.IsValid)
{
// do things
}
// do things
}

How can I customize the error response in Web API with .NET Core?

I am using .NET Core 2.2 with Web API. I have created one class, i.e., as below:
public class NotificationRequestModel
{
[Required]
public string DeviceId { get; set; }
[Required]
public string FirebaseToken { get; set; }
[Required]
public string OS { get; set; }
public int StoreId { get; set; }
}
Using the above class I have created one method. Now I want to return a custom object, but it's returning its own object.
API method is:
public ActionResult<bool> UpdateFirebaseToken(NotificationRequestModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(FormatOutput(ModelState.Values));
}
var result = _notificationService.InsertOrUpdateFirebaseToken(model);
return Ok(result);
}
Here FormatOutput method is format the output.
protected Base FormatOutput(object input, int code = 0, string message = "", string[] details = null)
{
Base baseResult = new Base();
baseResult.Status = code;
baseResult.Error = message;
baseResult.TimeStamp = CommonHelper.CurrentTimeStamp;
baseResult.Code = code;
baseResult.Details = details;
baseResult.Message = message; //Enum.Parse<APIResponseMessageEnum>(code.ToString(), true); // (enum of code get value from language)
return baseResult;
}
But the issue is it returns:
{
"errors": {
"DeviceId": [
"The DeviceId field is required."
]
},
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "80000049-0001-fc00-b63f-84710c7967bb"
}
I want to customize this error with my model. I need error message and details from return output and passed it to my model. How can I do that? I had try to debug my code and found that breakpoint on API method is not calling. So I can't handle my custom method. Is there any solution? What am I doing wrong?
When using a controller with the ApiController attribute applied, ASP.NET Core automatically handles model validation errors by returning a 400 Bad Request with ModelState as the response body. As such, your conditional testing ModelState.IsValid is essentially always false (and therefore not entered) because the only requests that will ever get this far are valid ones.
You could simply remove the ApiController attribute, but that removes a bunch of other beneficial stuff the attributes adds as well. The better option is to use a custom response factory:
services.Configure<ApiBehaviorOptions>(o =>
{
o.InvalidModelStateResponseFactory = actionContext =>
new BadRequestObjectResult(actionContext.ModelState);
});
That's essentially what's happening by default, so you'd simply need to change the action provided there accordingly to customize it to your whims.
As Chris analyzed, your issue is caused by Automatic HTTP 400
responses.
For the quick solution, you could suppress this feature by
services.AddMvc()
.ConfigureApiBehaviorOptions(options => {
options.SuppressModelStateInvalidFilter = true;
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
For an efficient way, you could follow the suggestion from Chris, like below:
services.AddMvc()
.ConfigureApiBehaviorOptions(options => {
//options.SuppressModelStateInvalidFilter = true;
options.InvalidModelStateResponseFactory = actionContext =>
{
var modelState = actionContext.ModelState.Values;
return new BadRequestObjectResult(FormatOutput(modelState));
};
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
And, there isn't any need to define the code below any more in your action.
if (!ModelState.IsValid)
{
return BadRequest(FormatOutput(ModelState.Values));
}

Model Binding for multipart/form-data (File + JSON) post in ASP.NET Core 1.1

I'm attempting to build an ASP.NET Core 1.1 Controller method to handle an HTTP Request that looks like the following:
POST https://localhost/api/data/upload HTTP/1.1
Content-Type: multipart/form-data; boundary=--------------------------625450203542273177701444
Host: localhost
Content-Length: 474
----------------------------625450203542273177701444
Content-Disposition: form-data; name="file"; filename="myfile.txt"
Content-Type: text/plain
<< Contents of my file >>
----------------------------625450203542273177701444
Content-Disposition: form-data; name="text"
Content-Type: application/json
{"md5":"595f44fec1e92a71d3e9e77456ba80d0","sessionIds":["123","abc"]}
----------------------------625450203542273177701444--
It's a multipart/form-data request with one part being a (small) file and the other part a json blob that is based on a provided specification.
Ideally, I'd love my controller method to look like:
[HttpPost]
public async Task Post(UploadPayload payload)
{
// TODO
}
public class UploadPayload
{
public IFormFile File { get; set; }
[Required]
[StringLength(32)]
public string Md5 { get; set; }
public List<string> SessionIds { get; set; }
}
But alas, that doesn't Just Work {TM}. When I have it like this, the IFormFile does get populated, but the json string doesn't get deserialized to the other properties.
I've also tried adding a Text property to UploadPayload that has all the properties other than the IFormFile and that also doesn't receive the data. E.g.
public class UploadPayload
{
public IFormFile File { get; set; }
public UploadPayloadMetadata Text { get; set; }
}
public class UploadPayloadMetadata
{
[Required]
[StringLength(32)]
public string Md5 { get; set; }
public List<string> SessionIds { get; set; }
}
A workaround that I have is to avoid model binding and use MultipartReader along the lines of:
[HttpPost]
public async Task Post()
{
...
var reader = new MultipartReader(Request.GetMultipartBoundary(), HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
var filePart = section.AsFileSection();
// Do stuff & things with the file
section = await reader.ReadNextSectionAsync();
var jsonPart = section.AsFormDataSection();
var jsonString = await jsonPart.GetValueAsync();
// Use $JsonLibrary to manually deserailize into the model
// Do stuff & things with the metadata
...
}
Doing the above bypasses model validation features, etc. Also, I thought maybe I could take that jsonString and then somehow get it into a state that I could then call await TryUpdateModelAsync(payloadModel, ...) but couldn't figure out how to get there either - and that didn't seem all that clean either.
Is it possible to get to my desired state of "transparent" model binding like my first attempt? If so, how would one get to that?
The first problem here is that the data needs to be sent from the client in a slightly different format. Each property in your UploadPayload class needs to be sent in its own form part:
const formData = new FormData();
formData.append(`file`, file);
formData.append('md5', JSON.stringify(md5));
formData.append('sessionIds', JSON.stringify(sessionIds));
Once you do this, you can add the [FromForm] attribute to the MD5 property to bind it, since it is a simple string value. This will not work for the SessionIds property though since it is a complex object.
Binding complex JSON from the form data can be accomplished using a custom model binder:
public class FormDataJsonBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if(bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));
// Fetch the value of the argument by name and set it to the model state
string fieldName = bindingContext.FieldName;
var valueProviderResult = bindingContext.ValueProvider.GetValue(fieldName);
if(valueProviderResult == ValueProviderResult.None) return Task.CompletedTask;
else bindingContext.ModelState.SetModelValue(fieldName, valueProviderResult);
// Do nothing if the value is null or empty
string value = valueProviderResult.FirstValue;
if(string.IsNullOrEmpty(value)) return Task.CompletedTask;
try
{
// Deserialize the provided value and set the binding result
object result = JsonConvert.DeserializeObject(value, bindingContext.ModelType);
bindingContext.Result = ModelBindingResult.Success(result);
}
catch(JsonException)
{
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
}
You can then use the ModelBinder attribute in your DTO class to indicate that this binder should be used to bind the MyJson property:
public class UploadPayload
{
public IFormFile File { get; set; }
[Required]
[StringLength(32)]
[FromForm]
public string Md5 { get; set; }
[ModelBinder(BinderType = typeof(FormDataJsonBinder))]
public List<string> SessionIds { get; set; }
}
You can read more about custom model binding in the ASP.NET Core documentation: https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding
I'm not 100% clear on how this would work for ASP.NET Core but for Web API (so I assume a similar path exists here) you'd want to go down the road of a Media Formatter. Here's an example (fairly similar to your question) Github Sample with blog post
Custom formatters might be the ticket? https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-formatters