How to provide sample response in Swagger when using common Response structure? - asp.net-core

I am using asp.net core 3.1 with Swashbuckle 5.6. I am using a common class ApiResponse to standardize the response structure. So for both http status codes 404 and 500, my response structure will use same class.
But in the generated swagger documentation, I want to provide different examples for different response codes. If I use typeof(ApiResponse) with either ProducesResponseType or SwaggerResponse, it will end up showing same "Example value" for both 404 and 500 status codes. I tried providing sample in the XML documentation. But that does not come with schema.
ApiResponse class structure is same as that used in below link.
https://www.devtrends.co.uk/blog/handling-errors-in-asp.net-core-web-api
public class ApiResponse
{
public int StatusCode { get; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string Message { get; }
public ApiResponse(int statusCode, string message = null)
{
StatusCode = statusCode;
Message = message ?? GetDefaultMessageForStatusCode(statusCode);
}
private static string GetDefaultMessageForStatusCode(int statusCode)
{
switch (statusCode)
{
...
case 404:
return "Resource not found";
case 500:
return "An unhandled error occurred";
default:
return null;
}
}
}
Both statusCode and Message will be different for 404 and 500.
I have another similar issue with Ok Response also. By using generics, I am able to get proper example for class type. But for status code and Message, I am unable to provide specific values.
public class ApiResponseOk<T> : ApiResponse
{
public T Result { get; }
public ApiResponseOk()
{
}
public ApiResponseOk(T result, string message = null)
: base(200, message)
{
Result = result;
}
}
Please let me know how can I provide separate examples when using same type for response.
Thanks!

Related

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));
}

Add a message to your HTTP 400 Bad Request in ASP.NET Core 3.1

Is there a way I can add a message to a BadRequest action result, and for that message to be visible to external clients, such as Postman? I am using ASP.NET Core 3.1.
Part of my code is included below. I want to say what the problem is—e.g., that the id sent in the body is not the same as the one taken from the URL. For now, I am using an Error object that I’ve made, which has the error code and message. But those aren't visible in Postman when I send the request.
public ActionResult PutColour(int id, Colour colour)
{
if (id != colour.Id)
{
return BadRequest(new Error("IDNotTheSame","ID from URL is not the same as in the body."));
}
}
What you pass to BadRequest is serialized and returned as the response body. If nothing is coming through, the only explanation is that you don't have any public properties on Error that can be serialized. For example, if you had something like:
public class Error
{
public Error(string type, string description)
{
Type = type;
Description = description;
}
public string Type { get; private set }
public string Description { get; private set; }
}
Then, you get a response like:
{
"type": "IDNotTheSame",
"description": "ID from URL is not the same as in the body."
}
Not sure what your Error class currently does. However, this is probably unnecessary anyways, as you can just use ModelState:
ModelState.AddModelError("Id", "ID from URL is not the same as in the body.");
return BadRequest(ModelState);
Finally, it should probably be said that this is a pointless validation in the first place. You shouldn't be sending an id with the model at all (always use a view model, not an entity class), and even if you do send it, you can simply just overwrite it with the value from the URL:
model.Id = id;
Done. No issues and you don't need to worry about sending an error back.
Please follow the following steps to show Bad Request with your custom Class.
Create constructor here of that class. And add manual description and many more
Step 1
Add a custom class and inherit this class with ValidationProblemDetails. After that initialize below base class constructor by passing context.ModelState.
public ValidationProblemDetails(ModelStateDictionary modelState)
Here is implementation-
public class CustomBadRequest : ValidationProblemDetails
{
public CustomBadRequest(ActionContext context) : base(context.ModelState)
{
Detail = "add details here";
Instance = "add extension here";
Status = 400;
Title = "add title here";
Type = "add type here";
}
}
Step 2
Add this Custom Bad request class in ConfigureServices method in Startup.cs.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var problems = new CustomBadRequest(context);
return new BadRequestObjectResult(problems);
};
});
}
Step 3
Build and run.
and if any bad request come then you will see output like below-

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));
}

Client WebServiceException has ResponseStatus null without explicit ResponseStatus

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.

How to force WebFaultException to return custom error?

I have working WCF REST web service and can set status codes and status descriptions as usual:
OutgoingWebResponseContext response = WebOperationContext.Current.OutgoingResponse;
response.StatusCode = statusCode;
response.StatusDescription = detail.Error;
But I want to use WebFaultException. Unfortunately it alvays return {"Detail":"Not Found"} when I run my code:
[Serializable]
[DataContract]
public class DtoError
{
public DtoError()
{
}
public DtoError(string error)
{
Error = error;
}
[DataMember]
public string Error { get; private set; }
}
var error = new DtoError(entityName + " is not existing");
throw new WebFaultException<DtoError>(error, HttpStatusCode.NotFound);
Can I return my custom error json object?
After investigation I found that instead of WebServiceHost we are using WebServiceHost2 from Rest Starter Kit. And inside that host we have to use WebProtocolException. So now my working code looks like that:
throw new WebProtocolException(HttpStatusCode.NotFound, "Not Found", error, null);