UseExceptionHandler is not working with validation errors - c#

In Asp.Net Core 5 I am using UseExceptionHandler to handle exceptions globally and it works fine unless I send an invalid object. For example I send an object with null value for the required property "Name" I receive the following error in client but the Run function does not hit in debugger.
{"type":"https://tools.ietf.org/html/rfc7231#section-6.5.1" ,"title":"One
or more validation errors
occurred.","status":400,"traceId":"00-2428f0fb9c415843bca2aaef08eda6f6-11ea476efb792849-00","errors":{"Name":["The
field Name is required"]}}
(as the first middleware in the pipeline :)
app.UseExceptionHandler(a => a.Run(async context =>
{
//this does not being exceuted for validation errors
var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
var exception = exceptionHandlerPathFeature.Error;
var exceptionManager = a.ApplicationServices.GetRequiredService<IExceptionManager>();
await context.Response.WriteAsJsonAsync(exceptionManager.Manage(exception, context));
}));

This may help you.
services.AddControllers().ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var errors = context.ModelState.Keys
.SelectMany(key => context.ModelState[key].Errors.Select(x => $"{key}: {x.ErrorMessage}"))
.ToArray();
var apiError = new CustomErrorClass()
{
Message = "Validation Error",
Errors = errors
};
var result = new ObjectResult(apiError);
result.ContentTypes.Add(MediaTypeNames.Application.Json);
return result;
};
});

Related

In C#, using a loan pattern, TryAsync, and RestClient.ExecuteAsync(), how can I get the system to wait for the result of the second callout?

I'm currently refactoring a microservice which uses RestSharp's RestClient to call out to Personio, in order to use the latest version of RestSharp (v107), as well as to use ExecuteAsync instead of Execute.
I have the following method:
[SuppressMessage("Style", "IDE0053:Use expression body for lambda expressions", Justification = "lambda leave Match ambiguous.")]
public TryAsync<T> WithAuthorization<T>(Func<Token, Task<T>> doSomething, CancellationToken cancellationToken) =>
TryAsync(async () =>
{
T? result = default;
Exception? resultException = null;
TryAsync<Token> authorizationAttempt = TryAuthorize(cancellationToken);
_ = await apply(token => doSomething(token), authorizationAttempt)
.Match(
Succ: async dataTask =>
{
result = await dataTask;
},
Fail: exception =>
{
resultException = exception;
}
)
.ConfigureAwait(false);
// FIXME: Does not wait!!!!
return result
?? ((resultException == null)
? throw new PersonioRequestException("Could not get data from Personio: Reason unknown.")
: throw new PersonioRequestException($"Could not get data from Personio: {resultException.Message}", resultException)
);
});
As indicated in the code above, the method does not wait before returning the result, or as the case happens to be throwing an exception with reason unknown.
With the debugger, I've been able to determine authorizationAttempt gets a value and doSomething() is invoked, but while waiting for a response, the error is thrown.
The code which consumes the above method, providing the function for doSomething() is this:
public TryAsync<RequestResponse<T>> TryGet<T>(RequestOptions options, CancellationToken cancellationToken) =>
_authorizationClient.WithAuthorization<RequestResponse<T>>(
async token =>
{
UriBuilder urlBuilder = new(_personioConfig.BaseUrl.AppendPathSegment(options.Endpoint))
{
// This is used to add parameters which are used for filtering and may be unique for the record type, such as "updated_from".
Query = options.QueryParameters.ToString()
};
RestRequest request = new(urlBuilder.Uri, Method.Get);
request.Timeout = -1;
if (options.Pagination.IsActive)
{
request = request.AddQueryParameters(options.Pagination);
}
request = request
.AddHeader("Accept", "application/json")
.AddHeader("Authorization", $"Bearer {token.Value}");
return await GetRecords<T>(request, cancellationToken);
},
cancellationToken
);
private async Task<RequestResponse<T>> GetRecords<T>(RestRequest request, CancellationToken cancellationToken)
{
RestResponse<RequestResponse<T>> requestResponse = await _restClient.ExecuteAsync<RequestResponse<T>>(request, cancellationToken);
// FIXME: The next line is never executed.
RequestResponse<T>? dataResponse = JsonConvert.DeserializeObject<RequestResponse<T>>(requestResponse?.Content ?? "");
return (requestResponse?.IsSuccessful ?? false)
? (dataResponse != null && dataResponse.WasSuccessful)
? dataResponse
: throw new PersonioRequestException("Connected to Personio, but could not get records.")
: throw (
(requestResponse?.ErrorException != null)
? new("Could not get records from Personio.", requestResponse.ErrorException)
: new($"Could not get records from Personio. {dataResponse?.Error?.Message ?? UnknownProblem}."));
}
As indicated in the code above, the method GetRecords() is invoked, but the system throws an error from result (back in the first method above) being unpopulated before ExecuteAsync() has any sort of result.
An earlier form of the code, with an earlier version of RestSharp(v106) and synchronous execution worked fine. At that time, the TryGet looked like:
public TryAsync<RequestResponse<T>> TryGet<T>(RequestOptions options) =>
_authorizationClient.WithAuthorization<RequestResponse<T>>(token =>
{
UriBuilder urlBuilder = new(_personioConfig.BaseUrl.AppendPathSegment(options.Endpoint))
{
// This is used to add parameters which are used for filtering and may be unique for the record type, such as "updated_from".
Query = options.QueryParameters.ToString()
};
_restClient.BaseUrl = urlBuilder.Uri;
_restClient.Timeout = -1;
IRestRequest request = new RestRequest(Method.GET);
if (options.Pagination.IsActive)
{
request = request.AddQueryParameters(options.Pagination);
}
request = request
.AddHeader("Accept", "application/json")
.AddHeader("Authorization", $"Bearer {token.Value}");
return Task.FromResult(GetRecords<T>(request));
});
private RequestResponse<T> GetRecords<T>(IRestRequest request)
{
IRestResponse<RequestResponse<T>> requestResponse = _restClient.Execute<RequestResponse<T>>(request);
RequestResponse<T>? dataResponse = JsonConvert.DeserializeObject<RequestResponse<T>>(requestResponse.Content);
return requestResponse.IsSuccessful
? (dataResponse != null && dataResponse.WasSuccessful)
? dataResponse
: throw new PersonioRequestException("Connected to Personio, but could not get records.")
: throw (
(requestResponse.ErrorException != null)
? new("Could not get records from Personio.", requestResponse.ErrorException)
: new($"Could not get records from Personio. {dataResponse?.Error?.Message ?? UnknownProblem}."));
}
What am I doing wrong or missing?
How can I make this work using RestSharp 107.x and ExecuteAsync()?
With thanks to #AlexeyZimarev, I was able to get the TryGet() method working.
It now looks like:
public TryAsync<RequestResponse<T>> TryGet<T>(RequestOptions options, CancellationToken cancellationToken) =>
TryAsync(async () =>
{
_restClient.Authenticator = _authorizationClient;
Uri uri = new UriBuilder(_personioConfig.BaseUrl.AppendPathSegment(options.Endpoint)).Uri;
RestRequest request = new RestRequest(uri, Method.Get)
.AddQueryParameters(options.QueryParameters);
if (options.Pagination.IsActive)
{
request = request.AddQueryParameters(options.Pagination);
}
request.Timeout = -1;
return await GetRecords<T>(request, cancellationToken);
});
The line _restClient.Authenticator = _authorizationClient; does not very safe here, since it is being assigned on an injected instead of RestClient, which in the future might be used elsewhere, but I "solved" this by making the client transient instead of a singleton.
I also removed all the TryAsync from PersonioAuthorizationClient since it didn't seem to play well with whatever AuthenticatorBase is doing.

C#: Using Odata with Array Input in API

How do I get Odata working with a List variable?
The following API is not working and giving error.
HttpGet
Error: {"type":"https://tools.ietf.org/html/rfc7231#section-6.5.13","title":"Unsupported Media Type","status":415,"traceId":"00-b0b48a41f445ac4bbcebba902f8cca95-a714db4f4fd0c146-00"}
All my other APIs work,
"http://localhost:4547/api/properties/GetIdentifierPaged"
Controller:
[HttpGet[("[Action]")]
public ActionResult GetIdentifierPaged(List<string> propertyListRequest, ODataQueryOptions<PropertyDto> queryOptions)
{
propertyListRequest.Add("1110200100"); // fake data
var model = _propertyService.GetByPropertyIdentifierPaged(propertyListRequest).ToODataPageResult(queryOptions);
return Ok(model);
}
Service:
public IQueryable<PropertyDto> GetByPropertyIdentifierPaged(List<string> identifiers)
{
var identifiersEnumerable = identifiers.AsEnumerable();
var properties = _dataContext.Property
.Include(x => x.PropertyStatus)
Where(x => identifiersEnumerable.Contains(x.PropertyIdentifier))
return properties;
}
Page Result:
public static PageResult<T> ToODataPageResult<T>(this IQueryable<T> query, ODataQueryOptions<T> queryOptions)
{
var settings = new ODataQuerySettings();
settings.EnsureStableOrdering = false;
var countQuery = (IQueryable<T>)queryOptions.ApplyTo(query, settings, AllowedQueryOptions.Skip | AllowedQueryOptions.Top | AllowedQueryOptions.Select | AllowedQueryOptions.OrderBy);
long count = countQuery.Count();
var results = (IQueryable<T>)queryOptions.ApplyTo(query, settings, AllowedQueryOptions.None);
return new PageResult<T>(results, null, count);
}
This problem indicates that there is a problem with your request, but even if you came to respond, there are errors you will receive. You cannot give a value that session is open in response
return new PageResult<T>(**results.ToList()**, null, count);

Why a controller method is acting as POST instead GET?

I have a kendoGrid that read data with .Read(r => r.Action("CargaNotificacionesPorCliente", "bff").Data("getClaveCliente"))), the call works and the values are displayed in the grid correctly, nothing weird here.
When I open the Chrome DevTools it show the following error:
POST http://localhost:52881/bff/CargaNotificacionesPorCliente 500
(Internal Server Error)
I don't know why is marking it as a POST instead GET.
This my method (actually works)
public ActionResult CargaNotificacionesPorCliente([DataSourceRequest] DataSourceRequest request, string numeroCliente)
{
bff.Configuration.LazyLoadingEnabled = false;
var lstNotificaciones = new List<NotificacionesModel>();
var lstNoPartesNuevas = bff.inspListaNoPartes
.Where(x => x.codeCustomer == numeroCliente &&
x.Estatus == Constantes.EstatusNumParteNuevo &&
x.VistoCte == false)
.Include(i => i.CustomerCUSTOMER)
.ToList();
var cantidadComentariosNuevos = bff.inspCommentNoPartes
.Count(x => x.inspListaNoParte.codeCustomer == numeroCliente &&
x.Visto == false &&
x.Usuario != User.Identity.Name);
if (lstNoPartesNuevas.Any())
{
foreach (var item in lstNoPartesNuevas)
{
if (lstNotificaciones.All(a => a.IdCustomer != item.IdCustomer))
{
lstNotificaciones.Add(new NotificacionesModel
{
Cantidad = lstNoPartesNuevas.Count(x => x.IdCustomer == item.IdCustomer && x.Estatus == Constantes.EstatusNumParteNuevo && x.VistoSup == false) + cantidadComentariosNuevos,
Name = item.CustomerCUSTOMER.name,
IdCustomer = item.IdCustomer,
IdNumPart = item.IdNumPart
});
}
}
}
return Json(lstNotificaciones.ToList().ToDataSourceResult(request), JsonRequestBehavior.AllowGet);
}
And this is the Preview tab of the error in the DevTools
Server Error in '/' Application.
Input string was not in a correct format.
Description: An unhandled exception occurred during the execution of
the current web request. Please review the stack trace for more
information about the error and where it originated in the code.
Exception Details: System.FormatException: Input string was not in a
correct format.
But as I said, it's weird because it works
Tried adding [HttpGet] but doesn't work, in fact the method is never called with that annotation.
EDIT:
I did the binding by this way:
$("#Notificaciones").data("kendoGrid").dataSource.read(); // Read data
$("#Notificaciones").data("kendoGrid").dataSource.page(1); // Go to page 1
I removed the second line (.dataSource.page(1);) and now the error disappeared, but how this affected the call?, I can't figure it out
Should be able to do something like this
.DataSource(dataSource => dataSource
.Ajax()
.Read(read => read.Action("Index", "Banques").Type(HttpVerbs.Get))
.PageSize(8)
)
http://www.telerik.com/forums/post-vs-get-request
In the tool you are using, make sure that the HTTP verb is selected as Get. See below for a screenshot from Chrome's Postman

Calling wpf signalr client methods from outside hub in asp.net

public async Task ApplyChangedSettings()
{
if (ConnectionTimeEntryHub.State == Microsoft.AspNet.SignalR.Client.ConnectionState.Connected)
{
var d = await HubProxy.Invoke<TimeEntryV2.Models.Models.UpdatingSettings>("GetSettings", User.UserIdentity);
TimeEntrySettings = d;
}
}
Above method is created in wpf client viewmodel , I want to call this method from class outside hub in my asp.net web application as per documetation
var hubContext = Microsoft.AspNet.SignalR.GlobalHost.ConnectionManager.GetHubContext<TimeEntryHub
s.TimeEntrySettingsHub>();
if (TimeEntryHubs.TimeEntrySettingsHub.Users.Any(f => f.Value == userId))
{
var connectionID = TimeEntryHubs.TimeEntrySettingsHub.Users.Where(f => f.Value == userId).Single().Key;
hubContext.Clients.Client(connectionID).ApplyChangedSettings();
}
I am getting hub context as above , but its not doing anything ,It gets executed silently without any errors and does not reflect any changes
I stepped through the code its not calling the method on .net client , breakpoint on client method is not reached
Using Dependencyresolver solved my problem
on Server Side
var dependencyResolver = GlobalHost.DependencyResolver;
var connectionManager = dependencyResolver.Resolve<IConnectionManager>();
var hubContext = connectionManager.GetHubContext<TimeEntryHubs.TimeEntrySettingsHub>();
if (TimeEntryHubs.TimeEntrySettingsHub.Users.Any(f => f.Key == userId))
{
var connectionID = TimeEntryHubs.TimeEntrySettingsHub.Users.Where(f => f.Key == userId).Single().Value;
hubContext.Clients.Client(connectionID).ApplyChangedSettings();
}
On client Side
HubProxy.On("ApplyChangedSettings", () => ApplyChangedSettings().ConfigureAwait(false));
public async Task ApplyChangedSettings()
{
if (ConnectionTimeEntryHub.State == Microsoft.AspNet.SignalR.Client.ConnectionState.Connected)
{
await ApplySettings().ConfigureAwait(false);
}
}

Is it possible to pass 2 types of objects to Restsharp?

We have scenario where external API returns either User XML or Error XML based on whether request succeed or failed.
At the moment I'm passing User POCO to the restsharp and works fine. But if it fails, this object is NULL. And we won't know why it failed unless we parse the Error XML manually.
Is there a way to workaround this?
e.g.
var restClient = new RestClient(baseURL);
var request = new RestRequest(uri);
request.Method = Method.POST;
var response = restClient.Execute<User>(request);
On execution of above method the API can return Error xml object. How do I get Error object on fail and User on success?
This is possible, although the code is a little ugly. RestSharp allows you to specify your own XML deserializer, so we'll need to do that in order to make this work.
First, though, you need a data type that can store either an Error or a User (I made it generic so it works for more than just Users):
public class Result<T>
{
public T Data { get; set; }
public Error Error { get; set; }
}
So the idea is, now when you execute the request, you ask RestSharp for a Result<User> instead of just a User, i.e.:
var result = client.Execute<Result<User>>(request);
Now here's the magic required to deserialize as either an Error or a User. It's a custom deserializer that inherits from RestSharp's XmlDeserializer. Warning: this code is not tested at all, but it can hopefully point you in the right direction.
public class XmlResultDeserializer : XmlDeserializer
{
public override T Deserialize<T>(IRestResponse response)
{
if (!typeof(T).IsGenericType || typeof(T).GetGenericTypeDefinition() != typeof(Result<>))
return base.Deserialize<T>(response);
// Determine whether the response contains an error or normal data.
var doc = XDocument.Parse(response.Content);
var result = Activator.CreateInstance<T>();
if (doc.Root != null && doc.Root.Name == "Error")
{
// It's an error
var error = base.Deserialize<Error>(response);
var errorProperty = result.GetType().GetProperty("Error");
errorProperty.SetValue(result, error);
}
else
{
// It's just normal data
var innerType = typeof(T).GetGenericArguments()[0];
var deserializeMethod = typeof(XmlDeserializer)
.GetMethod("Deserialize", new[] { typeof(IRestResponse) })
.MakeGenericMethod(innerType);
var data = deserializeMethod.Invoke(this, new object[] { response });
var dataProperty = result.GetType().GetProperty("Data");
dataProperty.SetValue(result, data);
}
return result;
}
}
Then you would wire it all up like this:
var restClient = new RestClient(baseURL);
client.AddHandler("application/xml", new XmlResultDeserializer());
var request = new RestRequest(uri);
request.Method = Method.POST;
var result = restClient.Execute<Result<User>>(request);
if (response.Data.Data != null)
{
var user = response.Data.Data;
// Do something with the user...
}
else if (response.Data.Error != null)
{
var error = response.Data.Error;
// Handle error...
}

Categories