Return custom HTTP code from ActionFilterAttribute - asp.net

I use the code below to throttle my ASP.NET Web Api:
public class Throttle : ActionFilterAttribute
{
public override async Task OnActionExecutingAsync(HttpActionContext context, CancellationToken cancellationToken)
{
// ...
if (throttle)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Conflict));
}
}
}
However, I cannot return error code 429, because it's not in HttpStatusCode enum. Is there a way to return a custom error code?

I found this over here.
var response = new HttpResponseMessage
{
StatusCode = (HttpStatusCode)429,
ReasonPhrase = "Too Many Requests",
Content = new StringContent(string.Format(CultureInfo.InvariantCulture, "Rate limit reached. Reset in {0} seconds.", data.ResetSeconds))
};
response.Headers.Add("Retry-After", data.ResetSeconds.ToString(CultureInfo.InvariantCulture));
actionContext.Response = response;
Hope this helps

This is what I did based on another response on StackOverflow.
Create Class (in controller file worked for me)
public class TooManyRequests : IHttpActionResult
{
public TooManyRequests()
{
}
public TooManyRequests(string message)
{
Message = message;
}
public string Message { get; private set; }
public HttpResponseMessage Execute()
{
HttpResponseMessage response = new HttpResponseMessage((HttpStatusCode)429);
if (!string.IsNullOrEmpty(Message))
{
response.Content = new StringContent(Message); // Put the message in the response body (text/plain content).
}
return response;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(Execute());
}
}
Use in controller
public IHttpActionResult Get()
{
// with message
return new TooManyRequests("Limited to 5 request per day. Come back tomorrow.");
// without message
// return new TooManyRequests();
}

Related

HttpClientFactory HttpClient Cannot access a disposed object

I am implementing a service for posting data to an external RestAPI.
What I did as below:
Service definition:
public class ExternalOutputService : IExternalOutputService
{
private readonly HttpClient _httpClient;
public ExternalOutputService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<object> Send(object data, string baseAddress, string uri)
{
try
{
HttpResponseMessage response = await _httpClient.PostAsJsonAsync(uri, data);
response.EnsureSuccessStatusCode();
}
catch (Exception ex) {
Console.Write(ex.Message);
}
return response.Content;
}
}
Add services.AddHttpClient<IExternalOutputService, ExternalOutputService>(); in Startup
Use the injected the service and call the Send method.
public class ConfigurableOutput
{
private readonly IExternalOutputService _externalOutputService;
public ConfigurableOutput(IExternalOutputService externalOutputService)
{
_externalOutputService = externalOutputService;
}
public override async Task<object> Run(object input)
{
await _externalOutputService.Send(input.data, "URI address");
}
}
But when I run it and hit the httpclient send line, it would throw an exception with 'Cannot access a disposed object'
Anyone has idea or advice?
Hi guys, I finally find the issue.
In another DI extension class, the class has already been registered.
context.Services.AddTransient<IExternalOutputService, ExternalOutputService>();
So removed this line and only keeps
services.AddHttpClient<IExternalOutputService, ExternalOutputService>();
It is all good now.

.Net Core Handle exceptions that returned from web api

I'm using .Net 5.0 as backend and .Net 5.0 for client-side.
I want to know how to handle exceptions that returned from web api in Client Side and show them to client.
The api result on exception is like :
{
"Version": "1.0",
"StatusCode": 500,
"ErrorMessage": "User not found!"
}
How to handle this type of exception globally in the client side (using .Net Core MVC)?
According to your description, I suggest you could use try catch on the server-side to capture the exception and return as a json response.
In the client side, you could use deserlize the response and create a new view named Error to show the response message.
More details, you could refer to below codes:
Error Class:
public class APIError
{
public string Version { get; set; }
public string StatusCode { get; set; }
public string ErrorMessage { get; set; }
}
API:
[HttpGet]
public IActionResult Get()
{
try
{
throw new Exception("UserNotFound");
}
catch (Exception e)
{
return Ok(new APIError { Version="1.0", ErrorMessage=e.Message, StatusCode="500" });
}
}
Application:
var request = new HttpRequestMessage(HttpMethod.Get,
"https://localhost:44371/weatherforecast");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var responseStream = await response.Content.ReadAsStringAsync();
APIError re = JsonSerializer.Deserialize<APIError>(responseStream, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
});
if (re.StatusCode == "500")
{
return View("Error", new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier, Version = re.Version, StatusCode = re.StatusCode, ErrorMessage = re.ErrorMessage });
}
}
else
{
// Hanlde if request failed issue
}
Notice: I created a new Error view, you could create it by yourself or modify the default error view.
Error Viewmodel:
public class ErrorViewModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public string Version { get; set; }
public string StatusCode { get; set; }
public string ErrorMessage { get; set; }
}
Error view:
#model ErrorViewModel
#{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
#if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>#Model.RequestId</code>
</p>
}
<h3>#Model.StatusCode</h3>
<p>
#Model.ErrorMessage
</p>
Result:
If you don't want to use exceptions in the backend, you could just send the http status code to the client. Here is an example of reaching out to an external api via service and returning that status to the backend controller. You would then just GET this result via client side. You could also just send over the full http response to the client, instead of solely the HttpStatusCode if needed.
A little more elaboration here: https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/calling-a-web-api-from-a-net-client
//Backend Service..
private const string baseUrl = "https://api/somecrazyapi/";
public async Task<HttpStatusCode> GetUserStatusAsync(string userId)
{
var httpResponse = await client.GetAsync(baseUrl + "userId");
return httpResponse.StatusCode;
}
//Backend Controller
[ApiController]
[Route("[controller]")]
public class UserController
{
private readonly IUserService service;
public UserController(IUserService service)
{
this.service = service;
}
......
[HttpGet("{userId}")]
public HttpStatusCode GetUserStatus(string userId)
{
return service.GetUserStatusAsync(userId).Result;
}
}

Deserialize HttpContext context response in asp.net core middleware

I want to Deserialize HttpContext context response in my exception middleware
like
context.Response.Deserialize<myclasss>();
if it deserilizes successfully according to myclass i want to send a specific respponse object back like
StatusCode = (int)HttpStatusCode.InternalServerError,
Message = "something went wrong"
There is a better way of resolving the same. Define a model for your error messages. Let that be ApiError
public class ErrorDetailsVM
{
public string Message { get; set; }
public string Exception { get; set; }
public string StackTrace { get; set; }
public string Source { get; set; }
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
Then you can create your own Middleware that will always send back the ErrorDetailsVM object after serializing it. Following is an example of the middleware.
public class DeveloperExceptionMiddleware
{
private readonly ILoggerFactory _loggerFactory;
private readonly RequestDelegate _next;
public DeveloperExceptionMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext) //If you have additional dependencies, you can inject them here.
{
try
{
await _next(httpContext);
}
catch (Exception ex)
{
//Log your errors here. Then send back the client a response.
await HandleExceptionAsync(httpContext, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return context.Response.WriteAsync(new ErrorDetailsVM()
{
Message = exception.Message,
Exception=exception.ToString(),
StackTrace=exception.StackTrace,
Source = exception.Source
}.ToString());
}
}
And finally, inside your Startup.cs you can add the following lines to the Configure method.
if (env.IsDevelopment())
app.UseCustomDeveloperException();
Similarly, you can have a separate UseCustomProductionException middleware for production that sends out less internal information. Let me know if this solves your issue.
Happy Coding <3
in this case i use
context.Response.ReadAsString().Deserilize<MyClass>()
using NewtonSoft.Josn library to deserilize

How to deal with the result that i wanted in asp.net web api

I'm trying to write web api for app developers,and i want the api result like the sample below
When Exception:
{
"StatusCode": "0",
"Message": "There's exception when calling web api"
}
Normal: the Result in the json string was the return type in the web api action.
{
"StatusCode": "1",
"Message": "Action completed successful",
"Result": {}
}
If the action is:
public DemoController : ApiController
{
public class DemoModel
{
public string X {get;set;}
public int Y {get;set;}
}
[HttpGet]
public DemoModel GetModel(int id)
{
return new DemoModel() { X = "Demo return string" , Y = 1234};
}
}
The Json string should be the sample below when calling the action successfully.
{
"StatusCode": "1",
"Message": "Action completed successful",
"Result": {
"X": "Demo return string",
"Y": 1234
}
}
and when exception, should be :
{
"StatusCode": "0",
"Message": "There's exception when calling web api"
}
So,the app developers could see the return type details in the web api help page.
Is that easy to implement?and how to do (no detail,just logic,also detail is better.)
thanks for everyone !
You should create DelegatingHandler to wrapper your all response from server:
public class WrappingResponseHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
return BuildApiResponse(request, response);
}
private static HttpResponseMessage BuildApiResponse(HttpRequestMessage request, HttpResponseMessage response)
{
object result;
string message = null;
int status;
if (response.TryGetContentValue(out result) == false || response.IsSuccessStatusCode == false)
{
var error = result as HttpError;
if (error != null)
{
result = null;
}
message = "There's exception when calling web api";
status = 0;
}
else
{
message = "Action completed successful";
status = 1;
}
HttpResponseMessage newResponse = request.CreateResponse(response.StatusCode,
new ApiResponse() { Message = message, Result = result, StatusCode = status });
foreach (KeyValuePair<string, IEnumerable<string>> header in response.Headers)
{
newResponse.Headers.Add(header.Key, header.Value);
}
return newResponse;
}
public class ApiResponse
{
public int StatusCode { get; set; }
public string Message { get; set; }
public object Result { get; set; }
}
}
And add this handler in WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MessageHandlers.Add(new WrappingResponseHandler()); //here
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
And nothing would have to change and add controllers.
Using IHttpActionResult would really helpful esp if your thinking of app developers. It works very with Http response code like 200(Ok), 500(Internal Server Error), 404(Not Found) etc
Here is simple code example, where your getting product and returning appropriate response based on returns
public IHttpActionResult Get (int id)
{
Product product = _repository.Get (id);
if (product == null)
{
return NotFound(); // Returns a NotFoundResult
}
return Ok(product); // Returns an OkNegotiatedContentResult
}
More on this Action Results on Web Api 2, you can even write custom action result.
When app client consumes, it gets proper HTTP response code, any response object or message along.

How to display my 404 page in Nancy?

I need to display my 404 error page in Nancy like this
if (ErrorCode == 404)
{
return View["404.html"];
}
How to do it?
The answer from nemesv is correct, but I just wanted to add an example using the ViewRenderer instead of the GenericFileResponse.
public class MyStatusHandler : IStatusCodeHandler
{
private IViewRenderer viewRenderer;
public MyStatusHandler(IViewRenderer viewRenderer)
{
this.viewRenderer = viewRenderer;
}
public bool HandlesStatusCode(HttpStatusCode statusCode,
NancyContext context)
{
return statusCode == HttpStatusCode.NotFound;
}
public void Handle(HttpStatusCode statusCode, NancyContext context)
{
var response = viewRenderer.RenderView(context, "/status/404");
response.StatusCode = statusCode;
context.Response = response;
}
}
You just need to provide an implementation of the IStatusCodeHandler interface (it will be picked up automatically by Nancy).
In the HandlesStatusCode method return true for the HttpStatusCode.NotFound.
And in the Handle method you need to set the Response property on the NancyContext with a response containing your error page content. You can use for example the GenericFileResponse:
public class My404Hander : IStatusCodeHandler
{
public bool HandlesStatusCode(HttpStatusCode statusCode,
NancyContext context)
{
return statusCode == HttpStatusCode.NotFound;
}
public void Handle(HttpStatusCode statusCode, NancyContext context)
{
var response = new GenericFileResponse("404.html", "text/html");
response.StatusCode = statusCode;
context.Response = response;
}
}

Resources