I am trying to test my API by calling an endpoint which should return an error message (json response). When testing this in Postman, the API indeed returns correct JSON and I am able to see that the status code is not 200. However, when trying to test this while using xunit and HttpClient, I am getting the following error message:
System.Net.Http.HttpRequestException : Error while copying content to a stream.
I am trying to figure out why this is happening. In my API I am checking credentials and if they are not correct, I will throw an Exception. This exception will get caught by a global exception handler, which will set the correct status code and create a json response.
Exception handler:
public class ExceptionHandler : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
int status = (int)HttpStatusCode.InternalServerError;
String message = context.Exception.Message;
var exception = context.Exception;
if (exception is ArgumentNullException)
{
message = ((ArgumentNullException)exception).ParamName + " is required";
status = (int)HttpStatusCode.BadRequest;
}
if (exception is NotFoundException)
{
message = exception.Message;
status = (int)HttpStatusCode.NotFound;
}
if (exception is AuthenticationException)
{
message = exception.Message;
status = (int)HttpStatusCode.Unauthorized;
}
if (exception is DuplicateEntryException)
{
message = exception.Message;
status = 422; // Unprocessable entity, not supported in HttpStatusCode class
}
HttpResponse response = context.HttpContext.Response;
response.StatusCode = status;
response.ContentType = "application/json";
response.WriteAsync(JsonConvert.SerializeObject(new ErrorMessage(message, (int)status)));
}
}
class ErrorMessage
{
public string Message { get; set; }
public int Code { get; set; }
public ErrorMessage(string message, int code)
{
Message = message;
Code = code;
}
}
Integration test:
[Fact]
public async Task ItReturnsAnErrorWhenCredentialsAreIncorrect()
{
var request = new UserAuthenticationRequest();
request.Username = "JohnDoe";
request.Password = "TestPasswordIncorrect";
var stringPayload = JsonConvert.SerializeObject(request);
var httpContent = new StringContent(stringPayload, Encoding.UTF8, "application/json");
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await _client.PostAsync("api/authentication/GetUserAppToken", httpContent);
Assert.Equal(401, (int)response.StatusCode);
}
Anyone having any idea why it is throwing the HttpRequestException?
Update:
Client setup:
public AuthenticationControllerTest()
{
_testServer = new TestServer(new WebHostBuilder().UseStartup<TestStartup>());
_client = _testServer.CreateClient();
}
I did a test by setting up a new client per test method. Unfortunately that also didn't solve the problem
Update 2:
I think it has to do something with either the HttpClient setup or the request message I am sending. Instead of making use of the exception handler, I decided to return a BadRequest() from the controller right away.
public BadRequestResult Post([FromBody]UserAuthenticationRequest userAuthenticationRequest)
{
return BadRequest();
}
When doing that, I am getting a HttpRequestException again, instead of getting a HttpResponse with status code 400.
Related
I have a custom middleware class in asp.net web api which processes and sends a response to the client.
It is class controller action which actually return a response.
During execution of this GetT function in controller it breaks and returns a ninternal server error 500. We want to handle the internal error 500 and what the exception type was.
We still want to send status code 500 error but with a custom error message in the original Response.
Class TResponse, apart from sending data, also sends whether it succeeds or not and some other message can be set if there was a 500 error. But we still want to send this TResponse even after 500 error.
Controller/Action:
public async Task<ActionResult<TResponse>> GetT(TRequest request)
{
.............
TResponse response = await _tService.GetT(request);
.......
return Ok(response).
}
public class ResponseMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var originalBodyStream = context.Response.Body;
try
{
using var memoryStream = new MemoryStream();
context.Response.Body = memoryStream;
await next(context);
memoryStream.Position = 0;
var reader = new StreamReader(memoryStream);
var responseBody = await reader.ReadToEndAsync();
memoryStream.Position = 0;
await memoryStream.CopyToAsync(originalBodyStream);
var requestTelemetry = context.Features.Get<RequestTelemetry>();
requestTelemetry?.Properties.Add("ResponseBody", responseBody);
Log.Information(responseBody);
}
catch (Exception ex)
{
string exType = ex.GetType().ToString();
context.Response.StatusCode = 500;
if (exType=="sql") //Data query error
{
context.Response.ContentType = "application/json";
TResponse response = new TResponse();
// ???????????
}
finally
{
context.Response.Body = originalBodyStream;
}
}
}
So in the catch block of the InvokeAsync(), I added a try catch block to handle 500 error.
I am facing a challenge how to convert a TResponse (after settiing some info on it for 500 related error) object into original body response which somehow gets processed through memory stream just like when there is no error.
In Short, how to handle 500 error and send TResponse from catch block?
I think we want to execute the same line of code in InvokeAsync() even after handling 500 error in order to send correct response.
Hi #user21225864 you mentioned you want to send status code 500 for internal server error which means exception accrued on server side. To return a custom response this might be one approach. Regardless of where you catch exception. Custom response is returned if exception accrued or not.
Controller:
[HttpGet]
[Route("[action]")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List<CompanyDto>))]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> GetAll()
{
var companies = await _compService.GetAllAsync();
return Ok(companies);
}
CompanyService:
public async Task<ServiceResponse<List<CompanyDto>>> GetAllAsync()
{
ServiceResponse<List<CompanyDto>> _response = new();
List<CompanyDto> _listCompanyDto = new List<CompanyDto>();
try
{
//Get set of records from repo
var CompanyList = await _compRepo.GetAllAsync();
var CompanyListDto = new List<CompanyDto>();
foreach (var item in CompanyList)
{
CompanyListDto.Add(_mapper.Map<CompanyDto>(item));
}
_response.Success = true;
_response.Message = "OK";
_response.Data = CompanyListDto;
return _response;
}
catch (Exception)
{
_response.Success = false;
_response.Message = "Error";
_response.Error = "Ininternal server error 500";
}
return _response;
}
Custom Response:
/// <summary>
/// Generic wraper around web api service responce.
/// </summary>
/// <typeparam name="T"></typeparam>
public class ServiceResponse<T>
{
public T? Data { get; set; }
public bool Success { get; set; }
public string? Message { get; set; }
public string? Error { get; set; }
}
I'm getting an exception instead of a HttpStatusCode.BadResult when I use GetAsync RestSharp method.
Request failed with status code BadRequest
Let me say that Postman and Swagger return the proper value, so I'm doing something wrong in my code.
It works fine when it returns Ok().
That's my controller:
[HttpGet]
public async Task<IActionResult> Get()
{
Sysmac dbStore = new();
var result = await dbStore.AturadesSql.Llista();
return (dbStore.Error) ? BadRequest(dbStore.ErrorMessage) : Ok(result);
}
It simply calls a database procedure and returns an error message in case of error. For the sake of the question I'm forcing that error message to be: 'This is an error'. That is the values returned by Postman.
I tried with this code to avoid network related errors:
[HttpGet]
public async Task<IActionResult> Get()
{
return BadRequest("This is an error");
}
And that's my API client method:
public async Task<APIResult<T>> GetAsync<T>(string apiUrl)
{
APIResult<T> result = new APIResult<T>();
try
{
var request = new RestRequest(apiUrl);
var response = await RestClient.GetAsync(request);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"ERROR: API:{apiUrl} (Status: {response.StatusDescription})");
}
var settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
if (response.Content != null)
{
result.Content = JsonConvert.DeserializeObject<T>(response.Content, settings);
}
}
catch (Exception ex)
{
result.Error = true;
result.ErrorMsg = ex.Message;
}
return result;
}
I can't never check response.StatusCode because await RestClient.GetAsync(request) is throwing the exception.
Im getting this Error. System.Net.WebException: 'The remote server returned an error: (401) Unauthorized.' Code is provided below.
When i take out the method "PrintUsefulData(api)", everything seems to work fine
that method has a http client webrequest. Im trying to request the following https://api.spotify.com/v1/albums.
static void Main(string[] args)
{
_clientId = string.IsNullOrEmpty(_clientId)
? Environment.GetEnvironmentVariable("my_clientId")//my id
: _clientId;
_secretId = string.IsNullOrEmpty(_secretId)
? Environment.GetEnvironmentVariable("my_secretId") // my id
: _secretId;
AuthorizationCodeAuth auth =
new AuthorizationCodeAuth(_clientId, _secretId, "http://localhost:5002", "http://localhost:5002", Scope.PlaylistReadPrivate | Scope.PlaylistReadCollaborative);
auth.AuthReceived += AuthOnAuthReceived;
auth.Start();
auth.OpenBrowser();
Console.ReadLine();
auth.Stop(0);
}
private static async void AuthOnAuthReceived(object sender,
AuthorizationCode payload)
{
AuthorizationCodeAuth auth = (AuthorizationCodeAuth)sender;
auth.Stop();
Token token = await auth.ExchangeCode(payload.Code);
SpotifyWebAPI api = new SpotifyWebAPI
{
AccessToken = token.AccessToken,
TokenType = token.TokenType
};
PrintUsefulData(api);
}
private static async void PrintUsefulData(SpotifyWebAPI api)
{
RestClient rClient = new RestClient();
rClient.endPoint = "https://api.spotify.com/v1/albums";
string strResponse = string.Empty;
strResponse = rClient.getRequest();
}
}
}
public enum HttpVerb
{
GET,
POST,
PUT,
DELETE
}
class RestClient
{
public string endPoint { get; set; }
public HttpVerb httpMethod { get; set; }
public RestClient()
{
endPoint = string.Empty;
httpMethod = HttpVerb.GET;
}
public string getRequest()
{
string strResponseVal = string.Empty;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(endPoint);
request.Method = httpMethod.ToString();
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
if (response.StatusCode != HttpStatusCode.OK)
{
throw new ApplicationException("error code: " + response.StatusCode);
}
using (Stream responseStream = response.GetResponseStream())
{
if (responseStream != null)
{
using (StreamReader reader = new StreamReader(responseStream))
{
strResponseVal = reader.ReadToEnd();
}
}
}
}
return strResponseVal;
}
}
}
There could be a couple of things going on here but here's my shot at helping based on what code you've posted:
Token Expiration - If you get a 401 error on a request then you need to use the Refresh Token which should have been supplied at the point of authorisation to get a new Access Token. This should apply to ANY call you make to the API.
Request Parameters - The endpoint you're calling (https://api.spotify.com/v1/albums) requires the parameter ids so the Spotify API knows what 'albums' you would like to return.
More info: https://developer.spotify.com/documentation/web-api/reference/albums/get-several-albums/
Another thing to check:
- Scope - make sure when you Auth to the API you are setting the required scope you need to perform actions in the future. I don't think this applies specifically in this case but worth noting.
I'm trying to implement GlobalExceptionFilter in NET Core WEB API.
This my filter code:
public class GlobalExceptionFilter : IExceptionFilter, IDisposable
{
private readonly ILogger _logger;
public GlobalExceptionFilter(ILoggerFactory logger)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
this._logger = logger.CreateLogger("Global Exception Filter");
}
public void Dispose()
{
}
public void OnException(ExceptionContext context)
{
Dictionary<string, string> data = new Dictionary<string, string>();
HttpStatusCode statusCode = HttpStatusCode.InternalServerError;
String message = String.Empty;
var ex = context.Exception;
TypeSwitch.Do(ex,
TypeSwitch.Case<ArgumentException>(() => { statusCode = HttpStatusCode.BadRequest; }),
TypeSwitch.Case<ArgumentNullException>(() => { statusCode = HttpStatusCode.BadRequest; }),
TypeSwitch.Case<ArgumentOutOfRangeException>(() => { statusCode = HttpStatusCode.BadRequest; }),
TypeSwitch.Case<KeyNotFoundException>(() => { statusCode = HttpStatusCode.NotFound; }),
TypeSwitch.Case<DivideByZeroException>(() => { statusCode = HttpStatusCode.MethodNotAllowed; }),
TypeSwitch.Case<QueryFormatException>(() => { statusCode = HttpStatusCode.MethodNotAllowed; })
);
HttpResponse response = context.HttpContext.Response;
response.StatusCode = (int)statusCode;
response.ContentType = "application/json";
var err = new ErrorPayload()
{
Data = data,
StackTrace = ex.StackTrace,
Message = ex.Message,
StatusCode = (int)statusCode
};
response.WriteAsync(JsonConvert.SerializeObject(err));
}
}
This is my initializing code in
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry(Configuration);
services.AddMvc( config =>
{
config.Filters.Add(typeof(GlobalExceptionFilter));
}
);
}
And i'm testing the error handling in this controller method
[HttpGet("{idCliente}")]
public IActionResult GetCliente(int idCliente)
{
throw new QueryFormatException("My Custom Exception");
}
Any ideas? thanks!
UPDATE
Well, I have to admit that I asume that wasn't working because Postman shows me connection error instead of MethodNotAllowed of NotFound (404). As suggested i examine the debug and the status response and was actually expected value.
As the docs say (last section)
Prefer middleware for exception handling. Use exception filters only
where you need to do error handling differently based on which MVC
action was chosen. For example, your app might have action methods for
both API endpoints and for views/HTML. The API endpoints could return
error information as JSON, while the view-based actions could return
an error page as HTML.
In your case, if the application only serving API:s then use the exception middleware implementation instead. Here's a good example of one
I have a OWIN middleware class to do some authentication based on some custom tokens. All works fine. However I would like to return a useful error response to the client. My reasoning is that if the client asked for a 'application/json' response and they are expecting a serialize object, then that's what they should get, even if it is a 401 status code.
Here is the Invoke section of my middleware:
public override async Task Invoke(IOwinContext context)
{
try
{
this.DoAuthorization(context);
await this.Next.Invoke(context);
}
catch (UnauthorizedAccessException ex)
{
this.GenerateErrorResult(context, HttpStatusCode.Unauthorized, this.ExceptionToString(ex));
}
catch (Exception ex)
{
this.GenerateErrorResult(context, HttpStatusCode.InternalServerError, this.ExceptionToString(ex));
}
}
private void GenerateErrorResult(IOwinContext context, HttpStatusCode code, string errorMessage)
{
var result = new Result { Status = Result.EStatus.Error, ErrorText = errorMessage };
context.Response.StatusCode = (int)code;
context.Response.ContentType = "application/json";
context.Response.Write(JsonConvert.SerializeObject(result));
}
This all works fine, however:
is this the 'correct' way?
what if the client asks for 'application/xml', which obviously Web API is quite capable of supporting
Is there a better way to return a custom response object ('Result' in my case) that is serialized as the client would expect?
Well this seems to work, using an extra OwinMiddleware inserted first:
public override async Task Invoke(IOwinContext context)
{
try
{
await Next.Invoke(context);
}
catch (UnauthorizedAccessException ex)
{
var result = new Result { Status = Result.EStatus.Error, ErrorText = ExceptionToString(ex) };
this.ReturnFormattedResult(result, HttpStatusCode.Unauthorized, context);
}
catch (Exception ex)
{
var result = new Result { Status = Result.EStatus.Error, ErrorText = ExceptionToString(ex) };
this.ReturnFormattedResult(result, HttpStatusCode.InternalServerError, context);
}
}
private void ReturnFormattedResult(Result result, HttpStatusCode code, IOwinContext context)
{
// what should our response be?
var mediaType = context.Request.MediaType ?? context.Request.ContentType;
// use the accept header (unless it is empty or '*/*' in which case use the content-type
if (!string.IsNullOrEmpty(context.Request.Accept) && !context.Request.Accept.Contains("*/*"))
{
mediaType = context.Request.Accept;
}
// find a formatter for this media type, if no match then use the first one
var formatter = this.config.Formatters.FindWriter(typeof(Result), new MediaTypeHeaderValue(mediaType));
if (formatter == null)
{
formatter = this.config.Formatters.First();
mediaType = formatter.SupportedMediaTypes.First().MediaType;
}
context.Response.StatusCode = (int)code;
context.Response.ContentType = mediaType;
formatter.WriteToStreamAsync(typeof(Result), result, context.Response.Body, null, null).Wait();
}