I'm developing a fairly simple Blazor app using a lot of default template functionality, and I can't figure out how to handle an ActionResult from the server that isn't the normal return value.
On the server side I've slightly modified the default controller template to look something like this:
public async Task<ActionResult<MyData>> GetSession(int id)
{
var myData= await FetchMyData(id);
if (myData== null)
{
return NotFound();
}
return myData;
}
That check for a null value was in the original template - it seems like a good idea so I kept it. On the client side my code looks like this:
public async Task<MyData> GetMyData(int id)
{
return await Http.GetJsonAsync<MyData>("/api/MyData/" + id);
}
It all works quite well, except the client side code doesn't handle the case where the server side returns a "NotFound()" result. It's not a show stopper, but it's driving me crazy that I don't know how to do it.
It seems that the GetJsonAsync() call on the client is silently unwrapping the return Value from the ActionResult wrapper (I guess?). Does that mean if I want to handle a NotFound condition I should be using a different httpclient function and maybe deserializing the object Value myself? If so, anyone want to volunteer an example?
Or am I missing something and there's an easier way?
It seems stupid to check for a condition on the server side just to send the client a warning that ultimately results in an unhandled exception.
I tried Henk Holterman's suggestion of just adding a try/catch, and it turns out the exception that was thrown had the information I wanted - that is the status being returned by the server. So what I should have done was this:
public async Task<MyData> GetMyData(int id)
{
try
{
return await Http.GetJsonAsync<MyData>("/api/MyData/" + id);
}
catch (System.Net.Http.HttpRequestException e)
{
.... process the exception
}
}
Turns out HttpRequestException has the HResult, which is what I was looking for.
Thanks Henk.
Related
I have a .net core MVC project that uses AJAX to retrieve data when loading grids.
Today, both the function returning the view and the function returning the data for the grid, is in the same controller. This is not optimal for many reasons. I.e. I would like a Json ProblemResult to be returned if a exception occurs when calling function using AJAX, but when returning a View, I would like the Developer Exception page to be shown if an error occurs.
I could split the functions into different Controllers and annotate one of them with the ApiController attribute, but since the project has several hundreds of controllers it would be a significant task to do so.
What I would like is this:
If context type is application/json: Do model validation and return a “ProblemResult” if a exception occurs, otherwise use the Developers Exception Page to show the error.
Can this be done in a easy way, or do I need to build a middleware and handle it all by myself?
The easy way, meaning, doing this from a function that returns either a view or a type of JSON result would look something like this:
public IActionResult AjaxOrView(CheckModel model)
{
var isAjax = Request.Headers["X-Requested-With"] == "XMLHttpRequest";
var modelStateValid = ModelState.IsValid;
if (isAjax)
{
if (!modelStateValid)
{
return JsonProblemResult();
}
return Json();
}
if (!modelStateValid)
{
// this will throw and the exception page will be shown
throw new Exception();
}
return View();
}
I'm writing a web api using PostgreSQL and am checking database constraints as part of the validation process, but I also have a global exception filter as a fallback in case something gets by when saving. My problem is that the exception doesn't seem to have any message that I can present to the client without some processing. The added image is of the PostgresException data from a breakpoint. For example, in this case I would want something along the lines of "Asset Number x already exists" or just "Asset Number must be unique". Is this something that can be configured somewhere? The place that makes the most sense is at the constraint creation code, but I couldn't find an option to do so.
modelBuilder.Entity<AssetItem>().HasIndex(item => new { item.AssetNumber }).IsUnique();
public class DbExceptionFilter : IExceptionFilter
{
private const string UNIQUE_EXCEPTION = "23505";
public async void OnException(ExceptionContext context)
{
var exceptionType = context.Exception.InnerException.GetType().FullName;
if (exceptionType == "Npgsql.PostgresException")
{
var pgException = (PostgresException) context.Exception.InnerException;
switch(pgException.SqlState)
{
case UNIQUE_EXCEPTION:
var error = new {error = "Unique Error Here"};
await WriteJsonErrorResponse(context.HttpContext.Response, HttpStatusCode.BadRequest, error);
return;
}
}
else
{
var error = new { error = "Unexpected Server Error"};
await WriteJsonErrorResponse(context.HttpContext.Response, HttpStatusCode.InternalServerError, error);
return;
}
}
private async Task WriteJsonErrorResponse(HttpResponse response, HttpStatusCode statusCode, dynamic error)
{
response.ContentType = "application/json";
response.StatusCode = (int) statusCode;
await response.Body.WriteAsync(Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(error)));
}
}
The closest thing to a user-readable message that PostgreSQL provides is the message text exposed on PostgresException.
However, as a general rule it is not a good idea to expose database errors directly to users (including web API users): these are intended to the application directly interacting with the database (i.e. your application). These messages generally don't mean much to the users of your API, and more importantly they leak potentially sensitive information about your database schema and are therefore not secure. It's especially problematic to dump/serialize the entire exception to the user as you seem to be doing (with JsonConvert.SerializeObject).
The best practice here would be to identify legitimate database exceptions that the user may trigger, intercept these and return and appropriately-worded message of your own (e.g. "A user with that name already exists").
As a side note, to identify PostgresException, rather than getting the name of the exception and comparing to that, you can simply use C# pattern matching:
if (context.Exception.InnerException is PostgresException postgresException)
{
// ...
}
Im currently having an issue where after I fetch data from my api, the view still seems to think the data is null. Please see below code:
View:
#if (goals == null)
{
<Loader />
}
else if (goals.Count == 0)
{
<div class="center-text">
<h2>It doesn't look like you have any goals create yet!</h2>
</div>
}
else
{
<h1>found stuff!</h1>
}
#functions {
List<GoalViewModel> goals;
protected override async Task OnInitAsync()
{
var token = await LocalStorage.GetItem<string>("AuthToken");
var httpClient = HttpClientExtensions.GetAuthHttpClient(token);
goals = await httpClient.GetJsonAsync<List<GoalViewModel>>(ClientSettings.GetHostRoot() + "/api/Goals/");
}
}
the controller action being hit looks like the following (I've verified that it is being called correctly and is returning data via the chrome network tab and fiddler):
[HttpGet]
public IActionResult Get()
{
try
{
var goalIndexDtos = _goalManager.GetAll(_claimResolver.User.FindFirstValue(ClaimTypes.NameIdentifier));
var goalViewModels = Mapper.Map<List<GoalViewModel>>(goalIndexDtos);
return Ok(goalViewModels);
}
catch (Exception ex)
{
log.Error(FormatError(nameof(GoalsController), nameof(Get), ex));
return BadRequest(ex);
}
}
My issue is that the <Loader /> always displays whereas I should be seeing a <h1>found stuff!</h1> since my controller is returning data appropriately and the blazor view code is successfully deserializing the data.
What am I missing here? I'm doing very similar things in other places in my application and they seem to work fine. I've compared this use case to the others over and over and don't see anything different.
I've even stuck a Console.WriteLine(goals.Count); line after the call to retrieve goals from the server and it correctly prints 1 to the console.
UPDATE: after playing around with everything I could think of, I deleted all data from the db table that goals is being retrieved from. this returns an empty list from my controller. In this case <h2>It doesn't look like you have any goals create yet!</h2> does print as expected! HOWEVER, the second I add a row and a list of count 1 is returned, all I get is a loader
SOLVED: After cleaning, building, rebuilding and closing/opening visual studio multiple times, my issue remained. It was only after restarting my machine that this began to work as expected.
I have a Web API ApiController base class and I would like to perform some validations in the constructor. This might include checking the current load on the server. If it's high, I'd like to return an appropriate HttpResponseMessage indicating the requestor should try again later.
Is something like this possible?
I Haven't tested it but that's not what the constructor is for. I don't think all plumbing is set at that time.
You could use global filters for this purpose. Here you have a sample that sets a global filter for authorization, you should use a similar logic but creating your own filter for your specific purposes.
A global filter would intercept all your requests and is executed before the controller actions so is a good place to perform your task.
Even though what you are doing sounds like it may be better to revise the approach. Note that you can throw HttpResponseException since the WebApi is Rest Service HttpResponseException is the recommended way to throw Exceptions back to the client.
var resp = new HttpResponseMessage(HttpStatusCode.NotFound)
{
Content = new StringContent("No idea what happened "),
ReasonPhrase = "Something was not Not Found"
}
throw new HttpResponseException(resp);
As long as you're using .NET 4.5, then you'd be better off creating a custom MessageHandler. You'll need to extend DelegatingHandler in order to do that.
public class MyHandler : DelegatingHandler {
protected override async Task<HttpResponseMessage> SendAsync(
HttpMessageRequest request, CancellationToken cancellationToken) {
// Access the request object, and do your checking in here for things
// that might cause you to want to return a status before getting to your
// Action method.
// For example...
return request.CreateResponse(HttpStatusCode.Forbidden);
}
}
Then inside your WebApiConfig, just add the following code to use the new Handler:
config.MessageHandlers.Add(new MyHandler());
You can't throw HttpResponseException in constructor, that will always cause 500.
Easiest way is to override ExecuteAsync():
public override Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken) {
if (!myAuthLogicCheck()) {
// Return 401 not authorized
var msg = new HttpResponseMessage(HttpStatusCode.Unauthorized) { ReasonPhrase = "User not logged in" };
throw new HttpResponseException(msg);
}
return base.ExecuteAsync(controllerContext, cancellationToken);
}
I have an operation handler that checks for authentication and throws an exception when authentication fails using
throw new WebFaultException(HttpStatusCode.Unauthorized);
However this still returns a 404 Not Found status code to the client/test client.
This is my operation handler
public class AuthOperationHandler : HttpOperationHandler<HttpRequestMessage, HttpRequestMessage>
{
RequireAuthorizationAttribute _authorizeAttribute;
public AuthOperationHandler(RequireAuthorizationAttribute authorizeAttribute) : base("response")
{
_authorizeAttribute = authorizeAttribute;
}
protected override HttpRequestMessage OnHandle(HttpRequestMessage input)
{
IPrincipal user = Thread.CurrentPrincipal;
if (!user.Identity.IsAuthenticated)
throw new WebFaultException(HttpStatusCode.Unauthorized);
if (_authorizeAttribute.Roles == null)
return input;
var roles = _authorizeAttribute.Roles.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
if (roles.Any(role => user.IsInRole(role)))
return input;
throw new WebFaultException(HttpStatusCode.Unauthorized);
}
}
Am I doing something wrong?
I have good and bad news for you. The framework your are using has evolved into ASP.NET Web API. Unfortunately, OperationHandlers no longer exist. Their closest equivalent are ActionFilters.
Having said that, WCF Web API never supported throwing WebFaultException, that is a vestige of WCF's SOAP heritage. I think the exception was called HttpWebException, however, I never used it, I just set the status code on the response.