There are two applications:
Blazor server-side
Web API (written long ago)
WebApi action
public async Task<object> GetAllAsync(...)
{
...
// Какая то проверка
throw new Exception("Что то пошло не так");
...
}
An example view of a method in a client application(Blazor server-side)
public async Task GetAllAsync()
{
var httpClient = clientFactory.CreateClient();
var responseMessage = await httpClient.GetAsync($"{address}/api/foo");
if (responseMessage.IsSuccessStatusCode)
{
// successfully
}
else
{
// How to get the error message here?
}
}
}
The question is: how to properly handle this kind of error from API?
ps
var exception = await responseMessage.Content.ReadAsAsync<HttpError>();
HttpError pulls a dependency with .NetFramework 4.6 (but initially I use .net core 3 preview)
The HttpContent class does not define a ReadAsAsync method. This is an extension method which I believe is considered obsolete,and is no longer supported. You may use ReadAsStringAsync(), ReadAsStreamAsync(), etc.
Example how to serialize the exception content to string:
var exception = await responseMessage.Content.ReadAsStringAsync();
The following code snippet demonstrates how to use HttpContent.ReadAsByteArrayAsync() to serialize the HTTP content to a byte array as an asynchronous operation, and then parse the array to a type you may define to hold the http error (something similar to the HttpError object, which you should not use, but simpler).
var responseBytes = await responseMessage.Content.ReadAsByteArrayAsync();
Note: Change the type specifier T to your custom type, or use built-in types such as string, according to your design...
JsonSerializer.Parse<T>(responseBytes, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
Hope this helps
Related
Problem is, I have an API that concatenates pdfs from Urls, and it's working in .NET 5 , but when starting to migrate to .NET 6, the use of IEnumerable<> , IFormFile, and IFormFileCollection , simply only accepts requests application/json.
Here is the endpoint in .NET 5 (Working)
[HttpPost]
public async Task<IActionResult> ConcatenarPdfsByUrl([FromForm] IEnumerable<string> urls)
{
var output = await TransformaPdfCore.PdfConcatenation(urls);
return File(output, "application/octet-stream");
}
result: Imagem 1 (.net 5)
And so is Endpoint in Minimal .net6
app.MapPost("/ConcatenaPdfsByUrl", async Task<IResult> (IEnumerable<string> urls, TransformaPdfCore transforma) =>
{
{
var output = await transforma.PdfConcatenation(urls);
return Results.File(output, "application/octet-stream");
}
}).Accepts<IEnumerable<string>>("multipart/form-data");
But the result is this: Imagem 2 (.net 6)
The question is, why does IEnumerable not have the same behavior? and if there is any solution, for example using IOperationFilter, so that I can make it work.
The IFormFileCollection Interface had the same problem
Currently binding from form is not supported for minimal APIs. You can track this issue on github - the support is investigated for .NET 7. You can try to implement custom binding from form via BindAsync method using HttpContext.Request.Form or just add HttpContext parameter to your handler method and use it there:
app.MapPost("/ConcatenaPdfsByUrl", async (HttpContext context, TransformaPdfCore transforma) =>
{
// use context.Request.Form or context.Request.Form.Files
var output = await transforma.PdfConcatenation(urls);
return Results.File(output, "application/octet-stream");
})
I'm working on a project where we have to develop a web API with ASP .NET Core 3.x. So far, so good, it is running well. Now, I'm writing some integration tests for this web API and I have some trouble to get the tests for everything else than GET request to work.
We're using the Clean Architecture from Jason Taylor. This means we have a core project with all our request handler, a domain project with all database entities and a presentation project for the API controllers. We use MediatR and dependency injection for the communication between these projects.
Now, we have the problem that the body data of the reuqest doesn't reach the controller.
This is how the Update method in the controller looks like:
[ApiController]
[Route("api/[controller]/[action]")]
public abstract class BaseController : ControllerBase
{
private IMediator _mediator;
protected IMediator Mediator => _mediator ??= HttpContext.RequestServices.GetService<IMediator>();
}
public class FavoriteController : BaseController
{
[HttpPut("{id}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Update(long id, UpdateFavoriteCommand command)
{
if (command == null || id != command.Id)
{
return BadRequest();
}
// Sends the request to the corresponding IRequestHandler
await Mediator.Send(command);
return NoContent();
}
}
We use xUnit.net as test framework.
For the integration tests, we're using an InMemory SQLite database which is setup in a fixture class.
The test looks like the following:
public class UpdateFavoritesTestSqlite : IClassFixture<WebApplicationFactoryWithInMemorySqlite<Startup>>
{
private readonly WebApplicationFactoryWithInMemorySqlite<Startup> _factory;
private readonly string _endpoint;
public UpdateFavoritesTestSqlite(WebApplicationFactoryWithInMemorySqlite<Startup> factory)
{
_factory = factory;
_endpoint = "api/Favorite/Update/";
}
[Fact]
public async Task UpdateFavoriteDetail_WithFullUpdate_ShouldUpdateCorrectly()
{
// Arange
var client = _factory.CreateClient(); // WebApplicationFactory.CreateClient()
var command = new UpdateFavoriteCommand
{
Id = 5,
Caption = "caption new",
FavoriteName = "a new name",
Public = true
};
// Convert to JSON
var jsonString = JsonConvert.SerializeObject(command);
var httpContent = new StringContent(jsonString, Encoding.UTF8, "application/json");
var stringUri = client.BaseAddress + _endpoint + command.Id;
var uri = new Uri(stringUri);
// Act
var response = await client.PutAsync(uri, httpContent);
response.EnsureSuccessStatusCode();
httpContent.Dispose();
// Assert
response.StatusCode.ShouldBe(HttpStatusCode.NoContent);
}
}
If we run the test, we get an 400 Bad Request error.
If we run the test in Debug mode, we can see that the code throws a custom ValidationException because of a model state error. This is configured in the DependencyInjection of the presentation project:
services
.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var failures = context.ModelState.Keys
.Where(k => ModelValidationState.Invalid.Equals(context.ModelState[k].ValidationState))
.ToDictionary(k => k, k => (IEnumerable<string>)context.ModelState[k].Errors.Select(e => e.ErrorMessage).ToList());
throw new ValidationException(failures);
};
})
.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<IWebApiDbContext>());
The failures object contains one error which says:
The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 | BytePositionInLine: 0.
Here is a screenshot: Visual Studio in Debugging mode with json error.
In one article in stackoverflow I've read, that removing the [ApiController] class attribute can result in a more detailed error description. During debugging again the test and setting a breakpoint int the Update method from the FavoriteController at the line with await Mediator.Send(command);, I was able to see, that the command object arriving the Update method contains only null or default values, except the id, which was 5.
command
Caption null string
FavoriteName null string
Id 5 long
Public false bool
The most confusing (and frustrating) part is, that a manual test with swagger or postman are both successfull. The way I understand it, there has to be a problem during the integration test.
I hope, someone can help me and see what I'm missing. Could it be possible that there is something wrong with the HttpClient of the Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory?
We've found the problem in our LoggingMiddleware of the web api presentation project.
Before writing this question, we already hat a look at another article on stackoverflow:
ASP.NET MVC Core 3.0 - Why API Request from body keeps returning !ModelState.IsValid?
But we already had the request.Body.Seek(0, SeekOrigin.Begin); in our code. So, we thought that's it and it couldn't be the problem.
But now, we've found this article:
.net core 3.0 logging middleware by pipereader
Instead of reading the request body like this:
await request.Body.ReadAsync(buffer, 0, buffer.Length);
... where the stream is closed after read, we're using now the BodyReader as a stream and leave the stream open:
var stream = request.BodyReader.AsStream(true); // AsStream(true) to let stream open
await stream.ReadAsync(buffer, 0, buffer.Length);
request.Body.Seek(0, SeekOrigin.Begin);
I am creating a package lib for all the errors in a Webapi service. This library will be used for providing custom responses for BadRequest, BadArgument, ApiVersionsing etc.. related errors. I need help in customizing Apiversion related errors for - ApiVersionUnspecified, UnsupportedApiVersion, InvalidApiVersion, AmbiguousApiVersion. I have follow this article to include api-versioning for my project - https://www.hanselman.com/blog/ASPNETCoreRESTfulWebAPIVersioningMadeEasy.aspx
I have checked the github wiki for the above package and found that "Depending on the desired behavior, you can extend the DefaultErrorResponseProvider or you can implement your own IErrorResponseProvider from stratch.
To wire up an alternate error response behavior, replace the default provider with your own:"
options => options.ErrorResponses = new MyErrorResponseProvider();
However; I am not quite getting how can I customize the default error responses in MyErrorResponseProvider class. Can somebody please provide me with any example so I can get started with this?
Thanks in advance!
Found the way of implementing above as -
class MyErrorResponseProvider : DefaultErrorResponseProvider
{
// note: in Web API the response type is HttpResponseMessage
public override IActionResult CreateResponse( ErrorResponseContext context )
{
switch ( context.ErrorCode )
{
case "UnsupportedApiVersion":
context = new ErrorResponseContext(
context.Request,
context.StatusCode,
context.ErrorCode,
"My custom error message.",
context.MessageDetail );
break;
}
return base.CreateResponse( context );
}
}
Thanks to github issue # - https://github.com/Microsoft/aspnet-api-versioning/issues/233
The answer customizes only the error message returned by the ASP.NET API Versioning.
To customize the whole response you can implement it by returning ObjectResult.
Startup.cs
// Add API Versioning to the service container to your project
services.AddApiVersioning(config =>
{
// Advertise the API versions supported for the particular endpoint
config.ReportApiVersions = true;
config.ErrorResponses = new ApiVersioningErrorResponseProvider();//Send standard error response when API version error.
});
ApiVersioningErrorResponseProvider.cs
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Versioning;
public class ApiVersioningErrorResponseProvider : DefaultErrorResponseProvider
{
public override IActionResult CreateResponse(ErrorResponseContext context)
{
//You can initialize your own class here. Below is just a sample.
var errorResponse = new
{
ResponseCode = 101,
ResponseMessages = "Something went wrong while selecting the api version",
HelpLink = "https://github.com/microsoft/aspnet-api-versioning/wiki/Error-Response-Provider"
};
var response = new ObjectResult(errorResponse);
response.StatusCode = (int)HttpStatusCode.BadRequest;
return response;
}
}
Which produces below output:
{
"ResponseCode": 101,
"ResponseMessages": "Something went wrong while selecting the api version",
"HelpLink": "https://github.com/microsoft/aspnet-api-versioning/wiki/Error-Response-Provider"
}
I am very, very new to the entire idea of REST and calling an API from http in general, but for a project I am working on, it has become necessary.
I am using ASP.NET Core, so I've been trying to find a REST library. When I was using standard .NET, I could use RestSharp, but the community made RestSharp.Core is pretty out of date and has many incompatibilities with newer versions of .NET Standard 1.6+.
To that end, I've explored other options, but my inexperience just makes it frustrating. Inevitably, I'm thinking it is best if I just use the built in HttpClient class. But I'm not sure how to do that for this exact scenario. I'm having a very hard time understanding how to give the parameters to the request, and what I'm specifically looking for in the return value.
My needs are pretty simple;
create a connection to $url
specify that it is a POST operation.
pass an existing JSON object to the server when making the request.
get JSON data back.
My code, using old RestSharp.Core, looks a bit like this - obviously keys and such omitted for privacy.
public async Task<string> OpenApiAsync() {
var token = await Task.Run(async () => {
var httpClient = new RestClient("https://[OMITTED].auth0.com/oauth/token");
var httpRequest = new RestRequest() {
Serializer = new RestSharp.Serializers.JsonSerializer(),
Method = Method.POST
};
httpRequest.AddHeader("content-type", "application/json");
httpRequest.AddJsonBody(new {
client_id = _settings.Value.ClientId,
client_secret = _settings.Value.ClientSecret,
audience = _settings.Value.Audience,
grant_type = _settings.Value.GrantType
});
var httpResponse = await httpClient.Execute(httpRequest);
var deserializer = new RestSharp.Deserializers.JsonDeserializer();
return deserializer.Deserialize<Dictionary<string, string>>(httpResponse);
});
return token["access_token"]);
}
The _settings object is injected with IOptions<Auth0Settings>, which has this shape and general data.
"authentication": {
"Domain": "[auth0-domain]",
"Audience": "https://[OMITTED].auth0.com/api/v2/",
"ClientId": "ABCDEFGHIJKLMNOP....",
"ClientSecret": "A22u5hgbnwifhwihfwi20u559f...",
"CallbackUrl": "http://localhost:5000/signin-auth0",
"GrantType": "client_credentials"
}
Can anyone help me understand how this could be ported to the native HttpClient that is in .NET Standard 1.6+? I specifically need one that is compatible with netstandard1.6 and netcoreapp1.1.
HttpClient is a very good http client and should be used in .NET core moving forward :)
private static async Task<string> OpenApiAsync()
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("content-type", "application/json");
var body = JsonConvert.SerializeObject(YOUR_BODY);
var result = await client.PostAsync("https://[OMITTED].auth0.com/oauth/token", new StringContent(body , Encoding.UTF8));
var deserialized = JsonConvert.DeserializeObject<Dictionary<string, string>>(await result.Content.ReadAsStringAsync());
return deserialized["access_token"];
}
I like a lot how the HttpClient is architectured - but I can't figure out how to add a "not quite standard" media type to be handled by the XmlSerializer.
This code:
var cli = new HttpClient();
cli
.GetAsync("http://stackoverflow.com/feeds/tag?tagnames=delphi&sort=newest")
.ContinueWith(task =>
{
task.Result.Content.ReadAsAsync<Feed>();
});
works fine when pointed to atom feeds that have Content-Type of "text/xml", but the one in the example fails with the "No 'MediaTypeFormatter' is available to read an object of type 'Feed' with the media type 'application/atom+xml'" message.
I tried different combinations of specifying MediaRangeMappings for the XmlMediaTypeFormatter (to be passed as an argument to ReadAsAsync) but with no success.
What is the "recommended" way to configure the HttpClient to map "application/atom+xml" and "application/rss+xml" to XmlSerializer?
Here is the code that works (credits to ASP.net forum thread):
public class AtomFormatter : XmlMediaTypeFormatter
{
public AtomFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/atom+xml"));
}
protected override bool CanReadType(Type type)
{
return base.CanReadType(type) || type == typeof(Feed);
}
}
var cli = new HttpClient();
cli
.GetAsync("http://stackoverflow.com/feeds/tag?tagnames=delphi&sort=newest")
.ContinueWith(task =>
{
task.Result.Content.ReadAsAsync<Feed>(new[] { new AtomFormatter });
});
Still, would like to see a solution without subclassing XmlMediaTypeFormatter - anybody?
The problem is that you are trying to convert the result straight to Feed. As error is clearly saying, it cannot figure our how to convert the application/atom+xml into Feed.
You would have to perhaps return as XML and then use and XmlReader to initialise your Feed.
Alternative is to provide your own media formatter - and implementation which encapsulates this.