Access ModelState in Asp.net core Middleware - c#

I need to access ModelState in Asp.net Core 2.1 Middleware, but this is just accessible from Controller.
For example I have ResponseFormatterMiddleware and in this Middleware I need to ignore ModelState error and show it's errors in 'Response Message':
public class ResponseFormatterMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ResponseFormatterMiddleware> _logger;
public ResponseFormatterMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
_logger = loggerFactory?.CreateLogger<ResponseFormatterMiddleware>() ?? throw new ArgumentNullException(nameof(loggerFactory));
}
public async Task Invoke(HttpContext context)
{
var originBody = context.Response.Body;
using (var responseBody = new MemoryStream())
{
context.Response.Body = responseBody;
// Process inner middlewares and return result.
await _next(context);
responseBody.Seek(0, SeekOrigin.Begin);
using (var streamReader = new StreamReader(responseBody))
{
// Get action result come from mvc pipeline
var strActionResult = streamReader.ReadToEnd();
var objActionResult = JsonConvert.DeserializeObject(strActionResult);
context.Response.Body = originBody;
// if (!ModelState.IsValid) => Get error message
// Create uniuqe shape for all responses.
var responseModel = new GenericResponseModel(objActionResult, (HttpStatusCode)context.Response.StatusCode, context.Items?["Message"]?.ToString());
// Set all response code to 200 and keep actual status code inside wrapped object.
context.Response.StatusCode = (int)HttpStatusCode.OK;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(responseModel));
}
}
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class ResponseFormatterMiddlewareExtensions
{
public static IApplicationBuilder UseResponseFormatter(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ResponseFormatterMiddleware>();
}
}
[Serializable]
[DataContract]
public class GenericResponseModel
{
public GenericResponseModel(object result, HttpStatusCode statusCode, string message)
{
StatusCode = (int)statusCode;
Result = result;
Message = message;
}
[DataMember(Name = "result")]
public object Result { get; set; }
[DataMember(Name = "statusCode")]
public int StatusCode { get; set; }
[DataMember(Name = "message")]
public string Message { get; set; }
[DataMember(Name = "version")]
public string Version { get; set; } = "V1.0"
}
and this is my excpected result:
{
"result": null,
"statusCode": 400,
"message": "Name is required",
"version": "V1"
}
but now the observed result is:
{
"result": {
"Name": [
"Name is required"
]
},
"statusCode": 400,
"message": null,
"version": "V1"
}

ModelState is only available after model binding . Just store the ModelState automatically with an action filter , thus you can use it within middleware .
Firstly , add a action filter to set the ModelState as an feature :
public class ModelStateFeatureFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var state = context.ModelState;
context.HttpContext.Features.Set<ModelStateFeature>(new ModelStateFeature(state));
await next();
}
}
Here the ModelStateFeature is a dummy class that holds the ModelState:
public class ModelStateFeature
{
public ModelStateDictionary ModelState { get; set; }
public ModelStateFeature(ModelStateDictionary state)
{
this.ModelState= state;
}
}
to make the action filter take place automatically , we need configure the MVC
services.AddMvc(opts=> {
opts.Filters.Add(typeof(ModelStateFeatureFilter));
})
And now we can use the ModelState within your Middleware as below:
public class ResponseFormatterMiddleware
{
// ...
public async Task Invoke(HttpContext context)
{
var originBody = context.Response.Body;
using (var responseBody = new MemoryStream())
{
context.Response.Body = responseBody;
// Process inner middlewares and return result.
await _next(context);
var ModelState = context.Features.Get<ModelStateFeature>()?.ModelState;
if (ModelState==null) {
return ; // if you need pass by , just set another flag in feature .
}
responseBody.Seek(0, SeekOrigin.Begin);
using (var streamReader = new StreamReader(responseBody))
{
// Get action result come from mvc pipeline
var strActionResult = streamReader.ReadToEnd();
var objActionResult = JsonConvert.DeserializeObject(strActionResult);
context.Response.Body = originBody;
// Create uniuqe shape for all responses.
var responseModel = new GenericResponseModel(objActionResult, (HttpStatusCode)context.Response.StatusCode, context.Items?["Message"]?.ToString());
// => Get error message
if (!ModelState.IsValid)
{
var errors= ModelState.Values.Where(v => v.Errors.Count > 0)
.SelectMany(v=>v.Errors)
.Select(v=>v.ErrorMessage)
.ToList();
responseModel.Result = null;
responseModel.Message = String.Join(" ; ",errors) ;
}
// Set all response code to 200 and keep actual status code inside wrapped object.
context.Response.StatusCode = (int)HttpStatusCode.OK;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(responseModel));
}
}
}
}
Let's test with a simple Model
public class MyModel {
[MinLength(6)]
[MaxLength(12)]
public string Name { get; set; }
public int Age { get; set; }
}
and a simple controller:
public class HomeController : Controller
{
public IActionResult Index(string name)
{
return new JsonResult(new {
Name=name
});
}
[HttpPost]
public IActionResult Person([Bind("Age,Name")]MyModel model)
{
return new JsonResult(model);
}
}
If we send a request with a valid payload :
POST https://localhost:44386/Home/Person HTTP/1.1
content-type: application/x-www-form-urlencoded
name=helloo&age=20
the response will be :
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json
Server: Kestrel
X-SourceFiles: =?UTF-8?B?RDpccmVwb3J0XDIwMThcOVw5LTE4XEFwcFxBcHBcQXBwXEhvbWVcUGVyc29u?=
X-Powered-By: ASP.NET
{
"result": {
"name": "helloo",
"age": 20
},
"statusCode": 200,
"message": null,
"version": "V1.0"
}
And if we send a request with an invalid model :
POST https://localhost:44386/Home/Person HTTP/1.1
content-type: application/x-www-form-urlencoded
name=hello&age=i20
the response will be
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json
Server: Kestrel
X-SourceFiles: =?UTF-8?B?RDpccmVwb3J0XDIwMThcOVw5LTE4XEFwcFxBcHBcQXBwXEhvbWVcUGVyc29u?=
X-Powered-By: ASP.NET
{
"result": null,
"statusCode": 200,
"message": "The value 'i20' is not valid for Age. ; The field Name must be a string or array type with a minimum length of '6'.",
"version": "V1.0"
}

I also faced issues in .net core 2.2 and seems IAsyncActionFilter was not working in my case, but worked with IActionResult. Below is my modified code, but not sure if this is what intended.
public class ModelStateFeatureFilter : IActionResult
{
public Task ExecuteResultAsync(ActionContext context)
{
var state = context.ModelState;
context.HttpContext.Features.Set(new ModelStateFeature(state));
return Task.CompletedTask;
}
}
and startup class like below
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = ctx => new ModelStateFeatureFilter();
});

If you are implementing something like action filter, you can access it via context parameter of overriden method OnActionExecuting of 'ActionFilterAttribute' base class
public class ModelStateValidationFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
// You can access it via context.ModelState
ModelState.AddModelError("YourFieldName", "Error details...");
base.OnActionExecuting(context);
}
}

Related

How to return the same status code from a second API call

I have an ASP.NET Core API calling a second API.
I throw an exception in my services layer, if there is an error from the second API:
var response = await httpClient.SendAsync(request); //call second API
if (!response.IsSuccessStatusCode)
{
//return HTTP response with StatusCode = X, if response.StatusCode == X
throw new HttpRequestException(await response.Content.ReadAsStringAsync());
//this always returns 400
}
How can I throw an exception that will return a response with the same status code from the second API call?
If I use HttpRequestException it will always return 400, even if the response object had StatusCode = 500.
EDIT:
The first API endpoint looks like this:
public async Task<ActionResult<HttpResponseMessage>> CreateTenancy([FromBody]TenancyRequest tenancy)
{
//Make some calls...
return Created(string.Empty, new { TenancyID = newTenancyExternalId });
}
The second API endpoint looks like this:
[HttpPost]
public IHttpActionResult CreateTenancy([FromBody]TenancyDTO tenancyDTO)
{
var tenancy = GetTenancy();
return Created(string.Empty, tenancy);
}
I've tried using throw new HttpResponseException(response); but this removes the descriptive Exception message, the payload ends up like this:
{
"Code": 500,
"CorrelationId": "2df08016-e5e3-434a-9136-6824495ed907",
"DateUtc": "2020-01-30T02:02:48.4428978Z",
"ErrorMessage": "Processing of the HTTP request resulted in an exception. Please see the HTTP response returned by the 'Response' property of this exception for details.",
"ErrorType": "InternalServerError"
}
I'd like to keep the ErrorMessage value in the original payload:
{
"Code": 400,
"CorrelationId": "ff9466b4-8c80-4dab-b5d7-9bba1355a567",
"DateUtc": "2020-01-30T03:05:13.2397543Z",
"ErrorMessage": "\"Specified cast is not valid.\"",
"ErrorType": "BadRequest"
}
The end goal is to have this returned:
{
"Code": 500,
"CorrelationId": "ff9466b4-8c80-4dab-b5d7-9bba1355a567",
"DateUtc": "2020-01-30T03:05:13.2397543Z",
"ErrorMessage": "\"Specified cast is not valid.\"",
"ErrorType": "InternalServerError"
}
I tried something simple as changing the return type of the API endpoint and returning the object as it when there is an error. Otherwise, build your own HttpResponseMessage and return that. This snippet below uses text but you can use a serializer to serialize other content if you have.
public async Task<HttpResponseMessage> Test(string str)
{
var httpClient = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, $"myAPI that returns different errors 400, 404, 500 etc based on str");
var response = await httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
return response;
// do something else
return new HttpResponseMessage(System.Net.HttpStatusCode.OK) { Content = new StringContent("Your Text here") };
}
Other approach of using Filters
The other approach of using IHttpActionResult as your return type, you can use Filters to conform all your HttpResponseMessages to IHttpActionResult.
Filter: Create a separate cs file and use this filter definition.
public class CustomObjectResponse : IHttpActionResult
{
private readonly object _obj;
public CustomObjectResponse(object obj)
{
_obj = obj;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = _obj as HttpResponseMessage;
return Task.FromResult(response);
}
}
and in your API, you would use your filter like so,
public async Task<IHttpActionResult> Test(string str)
{
var httpClient = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, $"http://localhost:4500/api/capacity/update-mnemonics/?mnemonic_to_update={str}");
var response = await httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
return new CustomObjectResponse(response);
// Other Code here
// Return Other objects
KeyValuePair<string, string> testClass = new KeyValuePair<string, string>("Sheldon", "Cooper" );
return new OkWithObjectResult(testClass);
// Or Return Standard HttpResponseMessage
return Ok();
}
You could simply make your API call and copy its response code into something compatible with IStatusCodeActionResult.
An alternative s to throw a custom exception. Create something like
public class ApiCallException : Exception
{
public APiCallException(int statusCode, ...)
{
ApiStatusCode = statusCode;
}
int ApiStatusCode { get; }
...
}
and copy over the status code from your API result, and then throw the exception.
var response = await httpClient.SendAsync(request); //call second API
if (!response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
throw new ApiCallException(500, content);
}
You can then register an exception filter to deal with the result when calling AddMvc.
services.AddMvc(options => options.Filters.Add<ExceptionFilter>());
where ExceptionFilter could be something like
public class ExceptionFilter : IExceptionFilter
{
// ...
public void OnException(ExceptionContext context)
{
if (context.Exception is ApiCallException ace)
{
var returnObject = CreateReturnObjectSomehow();
context.Result = new ObjectResult(returnObject) { StatusCode = ace.StatusCode };
}
else
{
// do something else
}
}
}
Thanks Jawad and Kit for providing great answers which helped me work out the solution below:
Turns out there was some middleware handling the exception:
public async Task Invoke(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch (Exception exception)
{
if (httpContext.Response.HasStarted) throw;
var statusCode = ConvertExceptionToHttpStatusCode(exception);
httpContext.Response.Clear();
httpContext.Response.StatusCode = (int)statusCode;
httpContext.Response.ContentType = "application/json";
if (statusCode != HttpStatusCode.BadRequest)
{
_logger.Error(exception, "API Error");
}
await httpContext.Response.WriteAsync(JsonConvert.SerializeObject(new Error(statusCode, httpContext.Request.CorrelationId(), exception.Message, statusCode.ToString())));
}
}
The Error class looks like this:
public class Error
{
public int Code { get; }
public Guid? CorrelationId { get; }
public DateTime DateUtc { get; }
public string ErrorMessage { get; }
public string ErrorType { get; }
public Error(HttpStatusCode code, Guid? correlationId, string errorMessage, string errorType)
{
Code = (int)code;
CorrelationId = correlationId;
DateUtc = DateTime.UtcNow;
ErrorMessage = errorMessage;
ErrorType = errorType;
}
}
I created this class:
public class ApiCallException : Exception
{
public int StatusCode { get; }
public override string Message { get; }
public ApiCallException(int statusCode, string message)
{
StatusCode = statusCode;
Message = message;
}
}
Then updated my original code to have this:
if (!response.IsSuccessStatusCode)
{
throw new ApiCallException((int)response.StatusCode, await response.Content.ReadAsStringAsync());
}

ASP.NET MVC Core 3.0 - Why API Request from body keeps returning !ModelState.IsValid?

I'm currently using ASP.NET MVC Core 3.0 to create an API project. I was successful to send a POST request without parameter. But currently I'm having a problem when trying to send a POST request with the parameter in JSON via Postman, always getting invalid request as shown below.
Notice that there's also key param in the query string to authorize the request using the middleware I created. This part has no problem.
Here's the code of the controller:
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[action]")]
[ApiController]
public class ValuesController : ControllerBase
{
// POST api/values
[HttpPost]
public IActionResult Post([FromBody] UserRequest model)
{
if (!ModelState.IsValid)
return BadRequest(new ApiResponse(400, "Model state is not valid."));
return Ok($"Hello world, {model.Id}!");
}
}
The odd thing is, I've already created and used the class UserRequest as a parameter input, as shown below:
public class UserRequest
{
public string Id { get; set; }
}
Here's my Startup.cs settings, I've already added AddNewtonsoftJson to enable JSON serializer input:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(option => option.EnableEndpointRouting = false)
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
.AddNewtonsoftJson(opt => opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);
/*Other API, DB settings and services goes here*/
...
}
Here's my attempts so far:
Added [BindProperties] on UserRequest class. Still returning same error.
Removed [FromBody] on the parameter of controller. Still returning same error.
Renamed id to Id to follow the naming inside UserRequest class. Still returning same error.
Added this code on Startup.cs, this will execute return BadRequest(new ApiResponse(400, "Model state is not valid."));:
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressModelStateInvalidFilter = true;
})
Removed this code on Startup.cs
.AddNewtonsoftJson(opt => opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore)
It will return this instead:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|f6037d12-44fa46ceaffd3dba.",
"errors": {
"$": [
"The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 | BytePositionInLine: 0."
]
}
}
Any help would be greatly appreciated.
Updated 12/11/2019: Here's how I handle the API key request:
public async Task Invoke(HttpContext httpContext, IApiKeyService apiKeyService)
{
var remoteIpAddress = httpContext.Connection.RemoteIpAddress;
if (httpContext.Request.Path.StartsWithSegments("/api"))
{
_logger.LogInformation($"Request from {remoteIpAddress}.");
var queryString = httpContext.Request.Query;
queryString.TryGetValue("key", out var keyValue);
if (keyValue.ToString().Any(char.IsWhiteSpace))
keyValue = keyValue.ToString().Replace(" ", "+");
if (httpContext.Request.Method != "POST")
{
httpContext.Response.StatusCode = StatusCodes.Status405MethodNotAllowed;
await WriteJsonResponseAsync(httpContext, "Only POST method is allowed.");
return;
}
if (keyValue.Count == 0)
{
httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
await WriteJsonResponseAsync(httpContext, "API Key is missing.");
return;
}
var isKeyValid = await apiKeyService.IsApiKeyValidAsync(keyValue);
var isKeyActive = await apiKeyService.IsApiKeyActiveAsync(keyValue);
if (!isKeyValid)
{
httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
await WriteJsonResponseAsync(httpContext, "Invalid API Key.");
return;
}
if (!isKeyActive)
{
httpContext.Response.StatusCode = StatusCodes.Status406NotAcceptable;
await WriteJsonResponseAsync(httpContext, "Service is Deactivated.");
return;
}
}
await _next.Invoke(httpContext);
}
private static async Task WriteJsonResponseAsync(HttpContext httpContext, string message = null)
{
httpContext.Response.ContentType = "application/json";
var response = new ApiResponse(httpContext.Response.StatusCode, message);
var json = JsonConvert.SerializeObject(response);
await httpContext.Response.WriteAsync(json);
}
As discussed in the comments your logging middleware is causing the problem.
When you read the request body, or response body, you need to reset the stream so that other middleware can read it (in this case the JsonSerializer).
In your logging middleware you will have a call like:
var body = await new StreamReader(request.Body).ReadToEndAsync();
Before the method returns you need to reset that stream:
request.Body.Seek(0, SeekOrigin.Begin);
This is the same for the response code e.g.
response.Body.Seek(0, SeekOrigin.Begin);
EDIT
As requested in the comments here is an example of what the middleware code might be:
public class LoggingMiddleware
{
private readonly RequestDelegate _next;
public LoggingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
context.Request.EnableBuffering();
var body = await new StreamReader(context.Request.Body).ReadToEndAsync();
// Log the contents of body...
context.Request.Body.Seek(0, SeekOrigin.Begin);
await _next(context);
}
}
The code to reset the Body stream position needs to come before the call to _next(context)

Override 415 response in ASP.Net Core

In ASP.net Core 2.1, I want to return a Json response along with Status code 415 instead of just 415 returned by default.
To achieve this I am using a resource filter:
public class MediaTypeResouceFilter : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
if (context.HttpContext.Response.StatusCode == 415)
{
context.Result = new ContentResult() { StatusCode = 415, Content = JsonConvert.SerializeObject(myResponse), ContentType = "application/json" };
}
}
}
In debugging, I see that context.Result is being overrided successfully but postman gets only 415 with no Json response.
In case I put:
context.Result = new ContentResult() { StatusCode = 415, Content = JsonConvert.SerializeObject(myResponse), ContentType = "application/json" };
inside OnResourceExecuting instead of OnResourceExecuted, it works as I wish but the thing is that I can't check for status code before executing resource.
Any ideas on why this is happening?
can you try this?
public class MediaTypeResouceFilter : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
if (context.HttpContext.Response.StatusCode == 415)
{
var jsonString = JsonConvert.SerializeObject(new { data = "this is custom message" });
byte[] data = Encoding.UTF8.GetBytes(jsonString);
context.HttpContext.Response.Body.WriteAsync(data, 0, data.Length);
}
}
}
Then you can get a 415 Status Code and body data is: {"data":"this is custom message"}
Actually OnResourceExecuted fires too late but you can modify the body for your custom message
I think using the middleware component is a good choise. This is the Invoke method of the middleware:
public async Task Invoke(HttpContext context) {
Exception exception = null;
try {
await _next(context);
}
catch (Exception e) {
exception = e;
//try handling exception stuff...
}
//try handling 415 code stuff...
if(context.Response.StatusCode==415){
var yourJsonObj = new { Blah = "blah..." };
string result = JsonConvert.SerializeObject(yourJsonObj);
//context.Response.StatusCode = 200; //You can change the StatusCode here
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(result);
}
}
See the workflow by Microsoft :
The method of IResourceFilter::OnResourceExecuted(ResourceExecutedContext context) runs after the Result execution. Since the result execution has already finished, you can make no change to result.

How to override behavior of Json helper method in base controller

I have the following question:
Your application will respond to AJAX requests in JSON format. In
order to maximize control over serialization, you will implement a
custom ActionResult class.
You must override the behavior of the Json helper method in your base
controller so that all JSON responses will use the custom result
class. Which class should you inherit?
The response type is JsonResult. Code-wise I'm having a hard time visualizing the structure. When I read "implement" in the question, I thought of an interface so this is what I came up with:
public class CustAction:ActionResult
{
//max control over serialization
}
public interface ICustAction:CustAction
{
}
public controller MyController:ICustAction, JsonResult
{
//override Json() method in here
}
Would the code above be applicable to the question above?
You can override JsonResult, and return custom JsonResult. For example,
StandardJsonResult
public class StandardJsonResult : JsonResult
{
public IList<string> ErrorMessages { get; private set; }
public StandardJsonResult()
{
ErrorMessages = new List<string>();
}
public void AddError(string errorMessage)
{
ErrorMessages.Add(errorMessage);
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
/* If you do not want to serve JSON on HttpGet, uncomment this. */
/*if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet &&
string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("GET access is not allowed. Change the JsonRequestBehavior if you need GET access.");
}*/
var response = context.HttpContext.Response;
response.ContentType = string.IsNullOrEmpty(ContentType) ? "application/json" : ContentType;
if (ContentEncoding != null)
{
response.ContentEncoding = ContentEncoding;
}
SerializeData(response);
}
protected virtual void SerializeData(HttpResponseBase response)
{
if (ErrorMessages.Any())
{
var originalData = Data;
Data = new
{
Success = false,
OriginalData = originalData,
ErrorMessage = string.Join("\n", ErrorMessages),
ErrorMessages = ErrorMessages.ToArray()
};
response.StatusCode = 400;
}
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Converters = new JsonConverter[]
{
new StringEnumConverter(),
},
};
response.Write(JsonConvert.SerializeObject(Data, settings));
}
}
public class StandardJsonResult<T> : StandardJsonResult
{
public new T Data
{
get { return (T)base.Data; }
set { base.Data = value; }
}
}
Base Controller
public abstract class BaseController : Controller
{
protected StandardJsonResult JsonValidationError()
{
var result = new StandardJsonResult();
foreach (var validationError in ModelState.Values.SelectMany(v => v.Errors))
{
result.AddError(validationError.ErrorMessage);
}
return result;
}
protected StandardJsonResult JsonError(string errorMessage)
{
var result = new StandardJsonResult();
result.AddError(errorMessage);
return result;
}
protected StandardJsonResult<T> JsonSuccess<T>(T data)
{
return new StandardJsonResult<T> { Data = data };
}
}
Usage
public class HomeController : BaseController
{
public ActionResult Index()
{
return JsonResult(null, JsonRequestBehavior.AllowGet)
// Uncomment each segment to test those feature.
/* --- JsonValidationError Result ---
{
"success": false,
"originalData": null,
"errorMessage": "Model state error test 1.\nModel state error test 2.",
"errorMessages": ["Model state error test 1.", "Model state error test 2."]
}
*/
ModelState.AddModelError("", "Model state error test 1.");
ModelState.AddModelError("", "Model state error test 2.");
return JsonValidationError();
/* --- JsonError Result ---
{
"success": false,
"originalData": null,
"errorMessage": "Json error Test.",
"errorMessages": ["Json error Test."]
}
*/
//return JsonError("Json error Test.");
/* --- JsonSuccess Result ---
{
"firstName": "John",
"lastName": "Doe"
}
*/
// return JsonSuccess(new { FirstName = "John", LastName = "Doe"});
}
}
Credit: Building Strongly-typed AngularJS Apps with ASP.NET MVC 5 by Matt Honeycutt
public class customJsonResult : JsonResult
{
//max control over serialization
}
//in the base controller override the Controller.Json helper method:
protected internal override JsonResult Json(object data, string contentType, Encoding contentEncoding, JsonRequestBehavior behavior)
{
return new customJsonResult {
Data = data,
ContentType = contentType,
ContentEncoding = contentEncoding,
JsonRequestBehavior = behavior
};
}

Wrapping all responses

I want to wrap all my http responses.
For example we have an action which returns some JSON data:
public IActionResult Get()
{
var res = new
{
MessageBody = "Test",
SomeData = 1
};
return Ok(res);
}
I want my response looks like:
{
"StatusCode":200,
"Result":
{
"MessageBody ":"Test",
"SomeData":1
}
}
If there is error then response must contain ErrorMessage field in a response.
In the mvc 5 I used the DelegationHandler, but in the asp.net core this class is not implemented. Now, we have to use middlewares.
This is code for mvc 5:
public class WrappingHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
return BuildApiResponse(request, response);
}
private static HttpResponseMessage BuildApiResponse(HttpRequestMessage request, HttpResponseMessage response)
{
object content;
string errorMessage = null;
if (response.TryGetContentValue(out content) && !response.IsSuccessStatusCode)
{
HttpError error = content as HttpError;
if (error != null)
{
content = null;
errorMessage = error.Message;
#if DEBUG
errorMessage = string.Concat(errorMessage, error.ExceptionMessage, error.StackTrace);
#endif
}
}
var newResponse = request.CreateResponse(response.StatusCode, new ApiResponse(response.StatusCode, content, errorMessage));
foreach (var header in response.Headers)
{
newResponse.Headers.Add(header.Key, header.Value);
}
return newResponse;
}
}
and, a middleware for asp.net core. There are no TryGetContentValue, HttpError and other stuff in asp.net core. So, I am trying to read response body first:
public class FormatApiResponseMiddleware
{
private readonly RequestDelegate _next;
public FormatApiResponseMiddleware(RequestDelegate next)
{
_next = next;
}
private bool IsSuccessStatusCode(int statusCode)
{
return (statusCode >= 200) && (statusCode <= 299);
}
public async Task Invoke(HttpContext context)
{
object content = null;
string errorMessage = null;
if (!IsSuccessStatusCode(context.Response.StatusCode))
{
content = null;
//how to get error
}
var body= context.Response.Body;
}
}
But, Body stream has CanRead equal false and I get error that stream cannot be read. How to properly wrap response?
I suggest using ExceptionHandlerMiddleware as a template/sample on how your middleware should be implemented.
For example, you should be aware about case, when response has already started
// We can't do anything if the response has already started, just abort.
if (context.Response.HasStarted)
{
_logger.LogWarning("The response has already started, the error handler will not be executed.");
throw;
}
or don't forget to clear current response, if you want to replace it:
context.Response.Clear();
Moreover, maybe you will find useful just to reuse it, and implement your own error handler instead of a full middleware. That way you can send a custom JSON error to the client. For that, define a class that will represent your custom error:
public class ErrorDto
{
public int Code { get; set; }
public string Message { get; set; }
// other fields
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
Then register an exception handler middleware in the Configure method. Pay attention to the order in which the middleware is registered, and make sure it’s registered before MVC for example:
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = 500; // or another Status
context.Response.ContentType = "application/json";
var error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null)
{
var ex = error.Error;
await context.Response.WriteAsync(new ErrorDto()
{
Code = 1, //<your custom code based on Exception Type>,
Message = ex.Message // or your custom message
// … other custom data
}.ToString(), Encoding.UTF8);
}
});
});

Categories

Resources