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
Related
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.
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;
}
}
I've been trying to fetch exception text from backend (ASP.NET Core) in Angular app.
I've seen examples where the controller action's return type is JsonResult or ActionResult.
In this case we can do the following:
[HttpGet]
public ActionResult GetSomething()
{
...
}
catch (Exception ex)
{
return Json(new { error = $"{ex.GetType().FullName}: '{ex.Message}'" }, JsonRequestBehavior.AllowGet);
}
}
All controller actions I have return DTOs, e.g.
public async Task<List<OrderDto>> GetMany(long clientId)
{
....
Since I'm returning DTO - I can't seem to return Json, so the approach above doesn't work.
I wonder if there's a way to pass exception description other than via Json(...).
Does anyone have an idea of how to handle this?
You can create a middleware:
public class ExceptionHandleMiddleware
{
private readonly RequestDelegate next;
public ExceptionHandleMiddleware(RequestDelegate next)
{
this.next = next ?? throw new ArgumentNullException(nameof(next));
}
public async Task Invoke(HttpContext context)
{
try
{
await next(context);
}
catch (Exception ex)
{
context.Response.Clear();
context.Response.ContentType = #"application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(new { error = $"{ex.GetType().FullName}: '{ex.Message}'"}));
}
}
}
And then add it to application Builder in Configure method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
....
app.UseMiddleware<ExceptionHandleMiddleware>();
}
Pay attention that with this middleware you shouldn't catch your exception on Controller level.
Below is my SignalR Hub class code.
public class ChatHub : Hub
{
public void Send(string name, string message)
{
// Call the addNewMessageToPage method to update clients.
Clients.All.addNewMessageToPage(name, message);
}
public async void webAPIRequest()
{
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync("https://jsonplaceholder.typicode.com/posts");
//Clients.All.addWebAPIResponseToPage(response);
Clients.Caller.addWebAPIResponseToPage(response);
await Task.Delay(1000);
response = await client.GetAsync("http://www.google.com");
Clients.Caller.addWebAPIResponseToPage(response);
//Clients.All.addWebAPIResponseToPage(response);
await Task.Delay(1000);
response = await client.GetAsync("https://jsonplaceholder.typicode.com/posts?userId=1");
//Clients.All.addWebAPIResponseToPage(response);
Clients.Caller.addWebAPIResponseToPage(response);
}
}
As per my understanding ,
Clients.Caller.addWebAPIResponseToPage(response);
sends message only to caller client , whereas
Clients.All.addWebAPIResponseToPage(response);
sends the message to all the clients.
Is my understanding correct ?
If No , then what method needs to be called to send message only to caller client.
Yes your understanding is correct. Read it here
https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/hubs-api-guide-server#selectingclients
You can use caller, you can provide current user connection id and send message to that or I have seen a group called self in some places which keeps user logged in from various devices and send message to that.
For example if you are logged in on a desktop and on mobile as well then you will have two connection IDs but you are same user. You can add this user to a self_username_unique_group_name kind of group and then send a message to that group which will be sent to all devices where user is connected.
You can also manage connection IDs for a single user in a separate table and send message to all of those connection IDs if you want.
Too much flexibility and magic
Enjoy
I found this to work quite well where ConnectionMapping is described in https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/mapping-users-to-connections
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<SomeService>();
services.AddScoped<SessionService>();
services.AddScoped<ProgressHub>();
}
}
public class SomeService
{
ProgressHub _hub;
public SomeService(ProgressHub hub)
{
_hub = hub;
}
private async Task UpdateProgressT(T value)
{
_hub.Send(value);
}
}
public class ProgressHub : Hub
{
private readonly static ConnectionMapping<string> _connections = new ConnectionMapping<string>();
private readonly IHubContext<ProgressHub> _context;
private readonly SessionService _session;
public ProgressHub(IHubContext<ProgressHub> context, SessionService session)
{
_context = context;
_session = session;
}
public override Task OnConnectedAsync()
{
_connections.Add(_session.SiteId, Context.ConnectionId);
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
_connections.Remove(_session.SiteId, Context.ConnectionId);
return base.OnDisconnectedAsync(exception);
}
public async Task Send(object data)
{
foreach (var connectionId in _connections.GetConnections(_session.SiteId))
{
await _context.Clients.Client(connectionId).SendAsync("Message", data);
}
}
}
public class SessionService
{
private readonly ISession _session;
public SessionService(IHttpContextAccessor accessor)
{
_session = accessor.HttpContext.Session;
if (_session == null) throw new ArgumentNullException("session");
}
public string SiteId
{
get => _session.GetString("SiteId");
set => _session.SetString("SiteId", value);
}
}
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();
}