locked
HttpClient handle non success status code RRS feed

  • Question

  • User1501362304 posted

    Hi,

    I am using HTTPClient to call api from MVC, with this I am using DelegatingHandler to regenerate token if it is expired. So far so good.
    I just need help with redirecting to error page or returning error status code so that global exception handling can handle that.

    Below is DelegatingHandler code

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancelToken)
            {
                 var response = await base.SendAsync(request, cancelToken);
                 if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
                {
                   // Generate Refresh token and repeat the request
                   response = await base.SendAsync(request, cancelToken);         
                }
    
                return response;
         }

    In above code I want to redirect to login page if repeating request also returns 401, return to unauthorized view if API returns 403, or return to global error view if API returns 500.

    API using http client is being called in action method like this

    var response = _apiClient.GetUserInfo(int userId);

    And configured, error handling middlewares in startup like this

    if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                else
                {
                    app.UseExceptionHandler("/Error");
    
                    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                    app.UseHsts();
                }
    
                app.UseStatusCodePagesWithReExecute("/Error/{0}");

    Above middlewares work as expected if mvc application itself returns 401, 403 or 500 but not when API is returning these stated codes.

    Thanks!

    Friday, March 5, 2021 4:30 PM

All replies

  • User475983607 posted

    Your design is very confusing and, as far as I can tell, does not follow standards.   It seem like you have combined MVC/Razor Page authentication with token authentication.  These are two different types of authentication for two different applications (clients).  An expired token for accessing secured REST services does mean the UI application is no longer authenticated.  It simply means the C# client must refresh the access token.  Why are you combining the two?  Can you explain the security design intent?

    Friday, March 5, 2021 4:50 PM
  • User1501362304 posted

    I am not mixing 2 things. I'm just calling API from mvc application using httpclient and if API returns anything other than 200, I want to handle that response. Hope this clears my intent

    Friday, March 5, 2021 4:58 PM
  • User475983607 posted

    I am not mixing 2 things. I'm just calling API from mvc application using httpclient and if API returns anything other than 200, I want to handle that response. Hope this clears my intent

    As far as I can tell you are confusing two different clients.  Your middleware is running in the MVC application HTTP pipeline.  The browser is the client to the MVC app.  Http requests from the browser run through the MVC pipeline.

    The C# code (HttpClient) is the client for the Web API application.  HTTP request from HttpClient run through the Web API Http pipeline not the MVC Http pipeline.   The HTTP response is returned the MVC application code and does NOT pass through the MVC middleware.    It is up to you to write code in the MVC application that evaluate the response.  The MVC application can do whatever at that point like redirect if that's really necessary.  

    Friday, March 5, 2021 5:18 PM
  • User1501362304 posted

    I would be calling 100's of APIs using HTTP client, so isn't there any way to handle httpclient's not success response globally by either means so that we don't need to add if else condition on every api call result?   

    Thanks!

    Friday, March 5, 2021 5:37 PM
  • User475983607 posted

    I would be calling 100's of APIs using HTTP client, so isn't there any way to handle httpclient's not success response globally by either means so that we don't need to add if else condition on every api call result? 

    The standard ASP.NET Core solution is a designing a service.  It is up to you to write whatever logic you like within the service.  The official documentation covers the details.

    https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-5.0

    I'm not sure if you understand that the original code while incomplete returns an Ok 200 status.  The contents of the HTTP response is a serialized HttpResponseMessage type.  Either the custom middleware (which you did not share) has a problem parsing the HttpResponseMessage within the response body or the middleware expects an HTTP 40X status.  In the later, case you could return a standard IActionResult.

            [HttpGet]
            public IActionResult Test()
            {          
                return StatusCode(401);
            }

    However, this is poor design IMHO.  

    Friday, March 5, 2021 7:33 PM
  • User1120430333 posted

    If the response comes back from the call to the WebAPI  as unsuccessful, I throw a new exception carrying the  response to global exception handler Error page and ending the application.

    What does your Error page controller code look like?

    Saturday, March 6, 2021 3:18 PM
  • User1501362304 posted

    Hi,

    Below is my code to handle different kinds of errors

    [Route("[action]/{statusCode}")]
            public IActionResult Error(int statusCode)
            {
                if (statusCode == 401)
                {
                    return RedirectToAction("Login", "Account");
                }
                else if (statusCode == 403)
                    return View("Unauthorized");
                else if (statusCode == 404)
                    return View("NotFound");
                else
                {
                    return View();
                }
            }
    
            [Route("[action]")]
            public IActionResult Error()
            {
                return View();
            }

    And below is middleware registration

    if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                else
                {
                    app.UseExceptionHandler("/Error");
                    app.UseHsts();
                }
    
                app.UseStatusCodePagesWithReExecute("/Error/{0}");

    I agree with @mgebhard's 2nd post in thread but @DA924 if I throw error for non success HTTP client response, it will go to 500 status handling code not 401 or 403.

    Thanks!

    Saturday, March 6, 2021 4:03 PM
  • User475983607 posted

    but @DA924 if I throw error for non success HTTP client response, it will go to 500 status handling code not 401 or 403.

    Designing exceptions as part of the logical flow is not a best practice.  

    I, as well as many others on these forum, build multi-tier applications.  Typically there is a separation between the UI and the services.  A 400, 404, or 500 status response from an application server usually does not pass from the application server to the web application.  Usually the status is caught and a message is returned to UI which lets the user know what happened.

    Passing a 401 very unusual as well.  It assume the authentication cookie on the UI side is expired.  It is possible that you redirect to the login page when the user has a valid auth cookie.    IMHO, the security design does not makes sense. 

    Saturday, March 6, 2021 5:17 PM
  • User1120430333 posted

    To me, you don't have enough code in the Error controller. And also, what is the need to return a code? What I want is a user friendly message to the user that something happened and the application is going down. I logging the exception. Now if I needed to know what status code that could determine a redirect, I could certainly go into the exceptionThatOccurred and pick out the error code,  or maybe take the 500 and let it get logged and terminate the application. Maybe the status code would be differnet if ity exception came form the WebAPI.

    using System;
    using System.Diagnostics;
    using Microsoft.AspNetCore.Diagnostics;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Logging;
    using PublishingCompany.Models;
    
    namespace PublishingCompany
    {
        public class ErrorController : Controller
        {
            private readonly ILogger _logger;
    
            public ErrorController(ILogger<ErrorController> logger)
            {
                _logger = logger;
            }
    
            
            [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
            public IActionResult Error()
            {
                var exceptionFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
    
                // Get which route the exception occurred at
    
                string routeWhereExceptionOccurred = exceptionFeature.Path;
    
                // Get the exception that occurred
    
                var requestid = "";
    
                requestid = Activity.Current?.Id != null ? Activity.Current?.Id : HttpContext.TraceIdentifier;
    
                Exception exceptionThatOccurred = exceptionFeature.Error;
    
                _logger.LogError("Request Id: " + requestid + " " + routeWhereExceptionOccurred + " " + exceptionThatOccurred);
    
                return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
            }
        }
    }
    public DtoArticle Find(int id)
            {
                DtoArticle dto;
    
                using (var client = new HttpClient())
                {
                    var uri = new Uri("http://localhost/WebAPI/api/article/Find?id=" + id);
                    HttpResponseMessage getResponseMessage = client.GetAsync(uri).Result;
    
                    if (!getResponseMessage.IsSuccessStatusCode)
                        throw new Exception(getResponseMessage.ToString());
    
                    var responsemessage = getResponseMessage.Content.ReadAsStringAsync().Result;
    
                    dynamic article = JsonConvert.DeserializeObject(responsemessage);
    
                    dto = article.ToObject<DtoArticle>();
                }
    
                return dto;
            }

    Saturday, March 6, 2021 5:31 PM
  • User475983607 posted

    The follow code will not pass most, if not all, code reviews.  A 404 (not found) is a perfectly acceptable status for a REST Service and should not cause the web application to throw an exception.

    if (!getResponseMessage.IsSuccessStatusCode)
        throw new Exception(getResponseMessage.ToString());

    Saturday, March 6, 2021 7:45 PM
  • User1120430333 posted

    The follow code will not pass most, if not all, code reviews.  A 404 (not found) is a perfectly acceptable status for a REST Service and should not cause the web application to throw an exception.

    if (!getResponseMessage.IsSuccessStatusCode)
        throw new Exception(getResponseMessage.ToString());

    I am not looking for a 404. per say to be coming back from the WebAPI  and even if I was, I could interrogate the response in the GEH in the MVC Error page.  I am looking at what status code is coming back from the WebAPI when the WebAPI is being used in a Intranet application and the global exception handling in the WebAPI is returning the exception from the WebAPI, becuase there are no try/catches in any code in the WebAPI or any code the WebAPI references.

    So I don't see the problem if somehow a 200 status code is not coming back from the WebAPI, throwing an exception that could be potentially swallowed by the GEH Error page on a 404 if need be.  But I don't even see how a 404 would becoming back from a WebAPI being used in an Intranet solution, unless it is deliberately done, and  I don't think is a good approach in regards to the WebAPI solely being used in CRUD operations with the database.   

    Now,  if the WebAPI was being used in an Internet solution, I might take a different approach or if it was not solely doing CRUD. 

    But I don't see any problem in the approach that is being used for an Intranet application where both the MVC client and WebAPI service are being used by the company's internal network infrastructure behind the firewall. I don't see how one could be returning a status code other than 200 or a 500 from a WebAPI used in an Intranet solution when the WebAPI's purpose is to do CRUD with the database.

     

    namespace WebAPI
    {
        public class ErrorHandlingFilter : ExceptionFilterAttribute
        {
            private readonly IConfiguration _configuration;
    
            public ErrorHandlingFilter(IConfiguration configuration)
            {
                _configuration = configuration;
            }
    
            public override void OnException(ExceptionContext context)
            {
                var log = new LoggerConfiguration().ReadFrom.Configuration(_configuration).CreateLogger();
    
                var exception = context.Exception;
    
                if (exception.InnerException != null)
                {
                    log.Error("Message = " + exception.Message + " Inner.Exception.Message = " +
                              exception.InnerException.Message
                              + " Stack Trace = " + exception.StackTrace);
                }
                else
                {
                    log.Error("Message = " + exception.Message + " Stack Trace = " + exception.StackTrace);
                }
    
                context.ExceptionHandled = false; //optional 
            }
        }
    }
    

     

    Saturday, March 6, 2021 11:19 PM
  • User475983607 posted

    So I don't see the problem if somehow a 200 status code is not coming back from the WebAPI, throwing an exception that could be potentially swallowed by the GEH Error page on a 404 if need be.

    Designing exceptions as part of the logical flow is not a best practice.  

    But I don't even see how a 404 would becoming back from a WebAPI being used in an Intranet solution, unless it is deliberately done, and  I don't think is a good approach in regards to the WebAPI solely being used in CRUD operations with the database.   

    A 404 just means not found.  A 404 can happen on a simple search page.  

    Sunday, March 7, 2021 11:46 AM
  • User1120430333 posted

    mgebhard

    DA924

    So I don't see the problem if somehow a 200 status code is not coming back from the WebAPI, throwing an exception that could be potentially swallowed by the GEH Error page on a 404 if need be.

    Designing exceptions as part of the logical flow is not a best practice.  

    But it's done just like swallowing an exception in some situation in flow logic  to allow execution to continue, which is not best practice, but nevertheless, it's done.

    DA924

    But I don't even see how a 404 would becoming back from a WebAPI being used in an Intranet solution, unless it is deliberately done, and  I don't think is a good approach in regards to the WebAPI solely being used in CRUD operations with the database.   

    A 404 just means not found.  A 404 can happen on a simple search page.  

    A not found indication can be sending back an empty collection or a null object from a Web service. It doesn't  necessarily require that a 404 be sent back.

    Sunday, March 7, 2021 5:27 PM
  • User475983607 posted

    DA924

    But it's done just like swallowing an exception in some situation in flow logic  to allow execution to continue, which is not best practice, but nevertheless, it's done.

    This is an assumption you made up and has nothing to do with this post. 

    DA924

    A not found indication can be sending back an empty collection or a null object from a Web service. It doesn't  necessarily require that a 404 be sent back.

    I recommend going through any beginning level tutorial to learn the REST fundamentals.  Typically when a resource is not found, the standard Web API pattern returns a not found.

    // GET: api/TodoItems/5
    [HttpGet("{id}")]
    public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);
    
        if (todoItem == null)
        {
            return NotFound();
        }
    
        return todoItem;
    }

    There is nothing stopping you from returning an empty object but or an empty array but doing so is not RESTful.  

    Sunday, March 7, 2021 5:45 PM
  • User1120430333 posted

    DA924

    But it's done just like swallowing an exception in some situation in flow logic  to allow execution to continue, which is not best practice, but nevertheless, it's done.

    This is an assumption you made up and has nothing to do with this post. 

    However, you're the one that brought up exception usage. And besides, maybe you have not worked in Windows desktop solutions that much to know that it's not something I made up at all. :)

    DA924

    A not found indication can be sending back an empty collection or a null object from a Web service. It doesn't  necessarily require that a 404 be sent back.

    I recommend going through any beginning level tutorial to learn the REST fundamentals.  Typically when a resource is not found, the standard Web API pattern returns a not found.

    Like I said, I don't necessarily see that being warranted in an Intranet solution using a WebAPI. One shoe doesn't fit all has been my experiences over the years.

    // GET: api/TodoItems/5
    [HttpGet("{id}")]
    public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);
    
        if (todoItem == null)
        {
            return NotFound();
        }
    
        return todoItem;
    }

    There is nothing stopping you from returning an empty object but or an empty array but doing so is not RESTful.  

    It all depends and the situation.

    Sunday, March 7, 2021 6:13 PM
  • User475983607 posted

    DA924

    However, you're the one that brought up exception usage. And besides, maybe you have not worked in Windows desktop solutions that much to know that it's not something I made up at all. :)

    I simply agreed with the OP that your use of exceptions as part of the logical flow is not a best practice and should be avoided.  

    DA924

    There is nothing stopping you from returning an empty object but or an empty array but doing so is not RESTful.  

    It all depends and the situation.

    REST is an an architecture style for designing HTTP services.  Client that follows REST standards will have a hard time consuming services if the services do not follow the openly published standards.

    Anyway, this thread is moving away from the original problem.  

    Sunday, March 7, 2021 6:42 PM
  • User1120430333 posted

    Well, In my usage, there had only be one status coming back from the WebAPI that I have full control of the client and WebAPI  with the WebAPI doing solely CRUD operations. It had better be HTTP 200. The client sees the status is not  HTTP 200 coming back from the WebAPI, then I am going to throw a new exception, becuase I want to know what happened and where it took place  and, with the Error page logging the needed information. And if I need the Error page in GEH to be a swiss army knife looking at other status codes not taking down the solution, then I'll code for it.

    I have seen MS consultants people from MS do worst. :)

    I know where it went down and why. I could go into the status code in the non success message sent back and captured  at the Error page and make other decisions if need be.

    In general, there are no guessing game as to what happened on the WebAPI side. I want to know what happened.

    2020-02-26 19:12:13.697 -05:00 [Error] An unhandled exception has occurred while executing the request.
    System.Exception: test
       at WebRazor3.x.Pages.Article.IndexModel.OnGet() in C:\Projects\PubCompanyCore3.x\WebBlazor3.x\Pages\Article\Index.cshtml.cs:line 22
       at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.ActionResultHandlerMethod.Execute(Object receiver, Object[] arguments)
       at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeHandlerMethodAsync()
       at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeNextPageFilterAsync()
       at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Rethrow(PageHandlerExecutedContext context)
       at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
       at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeInnerFilterAsync()
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
    --- End of stack trace from previous location where exception was thrown ---
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
       at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
       at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
       at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
    2020-02-26 19:12:21.321 -05:00 [Error] Request Id: |21852258-408b88a0d2d479d0. /Article System.Exception: test
       at WebRazor3.x.Pages.Article.IndexModel.OnGet() in C:\Projects\PubCompanyCore3.x\WebBlazor3.x\Pages\Article\Index.cshtml.cs:line 22
       at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.ActionResultHandlerMethod.Execute(Object receiver, Object[] arguments)
       at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeHandlerMethodAsync()
       at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeNextPageFilterAsync()
       at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Rethrow(PageHandlerExecutedContext context)
       at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
       at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeInnerFilterAsync()
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
    --- End of stack trace from previous location where exception was thrown ---
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
       at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
       at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
       at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
    2020-02-26 23:32:31.466 -05:00 [Error] An unhandled exception has occurred while executing the request.
    System.Exception: StatusCode: 503, ReasonPhrase: 'Service Unavailable', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
    {
      Server: Microsoft-HTTPAPI/2.0
      Date: Thu, 27 Feb 2020 04:30:23 GMT
      Connection: close
      Content-Type: text/html; charset=us-ascii
      Content-Length: 326
    }
       at ServiceLayer.AuthorSvc.GetAll() in C:\Projects\PubCompanyCore3.x\ServiceLayer\AuthorSvc.cs:line 27
       at WebRazor3.x.Models.AuthorDM.GetAll() in C:\Projects\PubCompanyCore3.x\WebBlazor3.x\Models\AuthorDM.cs:line 19
       at WebRazor3.x.Pages.Article.IndexModel.OnGet() in C:\Projects\PubCompanyCore3.x\WebBlazor3.x\Pages\Article\Index.cshtml.cs:line 21
       at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.ActionResultHandlerMethod.Execute(Object receiver, Object[] arguments)
       at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeHandlerMethodAsync()
       at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeNextPageFilterAsync()
       at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Rethrow(PageHandlerExecutedContext context)
       at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
       at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeInnerFilterAsync()
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
    --- End of stack trace from previous location where exception was thrown ---
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
       at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
       at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
       at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
    

      

    Sunday, March 7, 2021 8:10 PM
  • User1501362304 posted

    I think thread moved to some other direction!

    I am still left with the question that how to handle httpclient's non success responses globally in .Net Core MVC at controller or middleware level when using DelegatingHandler.

    I don't wanna throw exception until it is the last solution of the problem stated above.

    Thanks for looking into the post. 

    Wednesday, March 10, 2021 6:41 PM
  • User1120430333 posted

    Your Error page is for exception handling. That should be its main purpose. I think you have a misusage IMHO.

    Maybe you can do something with UseStatusCodePagesWithRedirects in the middelware as depicted in the link. Maybe think outside the box in trying to implement something that fits your needs.

    Handle errors in ASP.NET Core | Microsoft Docs

    BTW, if an unhandled exception was thrown, your Error page is not capturing the when, what, where, why, how and logging the information with redirect to a user friendly message  page not dumping the exception in the user's face. 

    I have no try/catches in the MVC project and anything the project references that I have coded, and the same thing in the WebAPI project of no try/catches and let the ErrorHandlingFilter capture and log all unhandled exceptions thrown.

    Well, at least you are doing some kind of global exception handling, becuase most developers on the Web or Windows desktop kind of like don't know it even exist to be implemented in a solution. :)

    Wednesday, March 10, 2021 9:08 PM
  • User475983607 posted

    vkagrawal

    I am still left with the question that how to handle httpclient's non success responses globally in .Net Core MVC at controller or middleware level when using DelegatingHandler.

    I'm pretty sure you are not understanding that the Web API response returns to code.  The delegate handler irrelevant because it has already run. It looks like you are trying to short circuit the MVC pipeline using the Web API response.  This technique is very similar to throwing an exception.  

    I handle this situation very differently.  It takes a bit of coding.  I create a generic service, as I explained above, that handles the HttpClient request/response.  I use a generic class to persist what happened.  From here I can whatever I want because I know what happened.

    public class ResponseMessage<Tout>
    {
        public ResponseMessage(Tout data, ErrorMessage message, System.Net.HttpStatusCode status)
        {
            Data = data;
            Error = message;
            Status = status;
        }
        public ErrorMessage Error { get; set; }
        public Tout Data { get; set; }
        public System.Net.HttpStatusCode Status { get; set; }
    }

    Wednesday, March 10, 2021 9:27 PM
  • User475983607 posted

    Very basic example.  This is not production code.  It is designed as an example.  In a real application I would not return the Web API Status code from the MVC action.  I would return a message for display in the UI.  

        public interface IApiService
        {
            Task<ResponseMessage<T>> GetAsync<T>(string restApi);
            Task<ResponseMessage<T>> PostAsync<T>(string restApi, T input);
        }
    
        public class ApiService : IApiService
        {
            private IHttpClientFactory _httpClientFactory;
            private readonly ILogger<ApiService> _logger;
            private readonly string _apiFactoryName = "apiClient";
    
            public ApiService(IHttpClientFactory httpClientFactory,
                ILogger<ApiService> logger)
            {
                _httpClientFactory = httpClientFactory;
                _logger = logger;
            }
    
            public async Task<ResponseMessage<T>> GetAsync<T>(string restApi)
            {
                _logger.LogDebug($"Api = {restApi}");
    
                T results = (T)Activator.CreateInstance(typeof(T));
                var client = _httpClientFactory.CreateClient(_apiFactoryName);
                ResponseMessage<T> msg;
    
                var request = new HttpRequestMessage(HttpMethod.Get, restApi);
                using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
    
                if (response.IsSuccessStatusCode)
                {
                    results = await response.Content.ReadFromJsonAsync<T>();
                    msg = new ResponseMessage<T>(results, "Ok", response.StatusCode);
                }
                else
                {
                    var error = await response.Content.ReadAsStringAsync();
                    msg = new ResponseMessage<T>(results, error, response.StatusCode);
                }
                return msg;
            }
    
            public async Task<ResponseMessage<T>> PostAsync<T>(string restApi, T input)
            {
                string serInput = JsonSerializer.Serialize(input);
                _logger.LogDebug($"Api = {restApi}");
                _logger.LogDebug($"Input Data : {serInput}");
    
                T results = (T)Activator.CreateInstance(typeof(T));
                ResponseMessage<T> msg;
    
                var client = _httpClientFactory.CreateClient(_apiFactoryName);
    
                HttpResponseMessage response = await client.PostAsJsonAsync($"{restApi}", input);
    
                if (response.IsSuccessStatusCode)
                {
                    results = await response.Content.ReadFromJsonAsync<T>();
                    msg = new ResponseMessage<T>(results, "Ok", response.StatusCode);
                }
                else
                {
                    var error = await response.Content.ReadAsStringAsync();
                    msg = new ResponseMessage<T>(results, error, response.StatusCode);
                }
                return msg;
            }
        }
    
        public class ResponseMessage<T>
        {
            public ResponseMessage(T data, string message, System.Net.HttpStatusCode status)
            {
                Data = data;
                Error = message;
                Status = status;
            }
            public string Error { get; set; }
            public T Data { get; set; }
            public System.Net.HttpStatusCode Status { get; set; }
        }
        public class WeatherForecast
        {
            public DateTime Date { get; set; }
            public int TemperatureC { get; set; }
            public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
            public string Summary { get; set; }
        }
    
        //apiclient
        public class ApiClientController : Controller
        {
            private readonly ILogger<ApiClientController> _logger;
            private readonly IApiService _apiService; 
            public ApiClientController(ILogger<ApiClientController> logger, IApiService apiService)
            {
                _logger = logger;
                _apiService = apiService;
    
            }
            public async Task<IActionResult> Index()
            {
                var response =  await _apiService.GetAsync<List<WeatherForecast>>("WeatherForecast");
                if(response.Status == System.Net.HttpStatusCode.OK)
                {
                    return Json(response);
                }
                else
                {
                    return StatusCode((int)response.Status);
                }   
            }
    
            public async Task<IActionResult> Save()
            {
                WeatherForecast forcast = new WeatherForecast()
                {
                    Date = DateTime.Now,
                    Summary = "Hello World",
                    TemperatureC = 10
                };
    
                var response = await _apiService.PostAsync<WeatherForecast>("WeatherForecast", forcast);
    
                if (response.Status == System.Net.HttpStatusCode.OK)
                {
                    return Json(response);
                }
                else
                {
                    return StatusCode((int)response.Status);
                }
            }
        }

    Config

    //HttpClient factory configuration
    services.AddHttpClient("apiClient", c =>
    {
        c.BaseAddress = new Uri("https://localhost:44352/");
        c.DefaultRequestHeaders.Add("Accept", "application/json");
    });
    services.AddScoped<IApiService, ApiService>();

    Thursday, March 11, 2021 3:55 PM