I'm trying to connect to a webservice that provides some customer data through a POST request but the response gets cut in the middle (or it might be that the trigger function doesn't await the response to complete).
This is done in a flutter environment and the initState() triggers the request.
I have a data service for the customer stuff, CustomerDataService which extends DataService that contain some common stuff such as sending the request and so on.
So in short initState() invoke CustomerDataService.getCustomers(request) which in turn invokes and await DataService.post(endpoint, request).
Http-package: import 'package:http/http.dart' as http;
initState() which is the starting point:
final CustomerDataService _dataService =
new CustomerDataServiceProvider().getCustomerDataService();
#override
void initState() {
_getCustomers();
super.initState();
}
void _getActors() async {
_dataService.getCustomers(
request: new GetCustomersRequest(
navigations: _dataService.suggestedNavigations
),
).then((response) {
_customersResponse = response;
/// Set the state
refresh();
});
}
And then we have the CustomerDataService:
class _CustomerDataService extends DataService implements CustomerDataService
#override
Future<GetCustomersResponse> getCustomers(
{#required GetCustomersRequest request}) async {
String endpoint = createEndpoint(<String>[
App.appContext.identityInstance.rootUrl,
_CUSTOMERS_CONTROLLER,
_GET_CUSTOMERS_ENDPOINT
]);
http.Response res = await post(endpoint: endpoint, request: request.toJson());
if (res.body == null) {
return null;
}
try {
/// This prints an invalid JSON that is cut in the middle
print(res.body);
/// This one naturally throws an exception since res.body isn't valid.
dynamic json = jsonDecode(res.body);
return new GetCustomersResponse.fromJson(json);
} catch (e) {
print("Exception caught when trying to get customers");
print(e);
}
return null;
}
The exception from jsonDecode is
Bad state: No element
And then we have the DataService:
Future<http.Response> post(
{#required String endpoint,
Map<String, String> header,
Map<String, dynamic> request}) async {
if (header == null) {
header = _getHeader();
} else {
header.putIfAbsent(_AUTHORIZATION_KEY, () => _headerAuthorizationValue());
}
http.Response res = await http.post(Uri.parse(endpoint), headers: header);
_validateReponse(res);
return res;
}
I'm clearly doing something wrong but I just can't see it...
The request in DataService.post doesn't add the body (request parameter) in this code and that is another ticket i will file after I've looked more into it, the workaround for now is to change the service to not expect a body.
I've verified that the service behaves as expected with postman.
I hope someone can see where my error(s) is.
Thanks!
Edit 1:
I changed the code a bit so that initState() doesn't use the DataServices created by me but used the http-package directly.
http.post('http://localhost:50140/api/customer/getcustomers').then((res) {
if(res == null) {
print('Response is empty');
}
print('Status code ${res.statusCode}');
print(res.body);
});
super.initState();
}
And the exact same thing happens so I don't think this is due to the dataservices at least.
Edit 2:
Before someone digs to deep into this I just want to say that it doesn't seem to be the response from the service, the http package or the dataservices.
This blog will be updated as soon as I find the cause of the Bad state: no element exception.
Okay, I don't know how to do this but it turns out the question title is incorrect.
It's the terminal that cuts the text when it's too big...
There were no errors in the dataservices or the http package but rather in the conversion from the response body to my strongly typed model, deep down the model tree.
An associative property to the Customer model have an enum, both server- and client side.
The service serialize the enum with the index, the library I use for mapping tries to get the enum by name (case sensitive).
The entity with the problem
#JsonSerializable()
class Item extends Object with _$ItemSerializerMixin
The auto-generated mapping
json['itemType'] == null
? null
: /// I could probably just remove the singleWhere and add [(int)json['itemType']] instead but that would cause some hassle when I run build_runner again.
ItemType.values
.singleWhere((x) => x.toString() == "ItemType.${json['itemType']}")
So, as soon as I did some changes server side (ignored the serialization of the enum and added another property which returned the enum string value instead, to lower case) it started working. I want to look into this further so that I can serialize the enum index instead of the string value and have it mapped that way instead but unfortunately I don't have the time to do it now.
The packages used for the auto-mapping is build_runner and json_serializable.
I'm glad I found a solution, and I'm sorry that the solution turned out to be completely unrelated to the actual post. I hope this can help someone at least.
Related
I have dotnet WebAPI and I'm trying to get a specific behaviour but am constantly getting 415 responses.
I have reproduced this by starting a new webapi project using dotnet new webapi on the command line. From there, I added two things: a new controller, and a model class. In my real project the model class is obviously a bit more complex, with inheritance and methods etc...
Here they are:
[HttpGet("/data")]
public async Task<IActionResult> GetModel(BodyParams input)
{
var response = new { Message = "Hello", value = input.valueOne };
return Ok(response);
}
public class BodyParams {
public bool valueOne { get; set; } = true;
}
My goal is that the user can call https://localhost:7222/data with no headers or body needed at all, and will get the response - BodyParams will be used with the default value of true. Currently, from postman, or from the browser, I get a 415 response.
I've worked through several suggestions on stack and git but nothing seems to be working for me. Specifically, I have tried:
Adding [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] into the controller, but this makes no difference unless I provide an empty {} json object in the body. This is not what I want.
Making BodyParams nullable - again, no change.
Adding .AddControllers(opt => opt.AllowEmptyInputInBodyModelBinding = true)... again, no change.
I Implemented the solution suggested here using the attribute modification in the comment by #HappyGoLucky. Again, this did not give the desired outcome, but it did change the response to : 400 - "The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true."
I tried modifying the solution in (4) to manually set context.HttpContext.Request.Body to an empty json object... but I can't figure out the syntax for this because it need to be a byte array and at that point I feel like I am way over complicating this.
How can I get the controller to use BodyParams with default values in the case that the user provides no body and no headers at all?
You can achieve that using a Minimal API.
app.MapGet("/data",
async (HttpRequest httpRequest) =>
{
var value = true;
if (Equals(httpRequest.GetTypedHeaders().ContentType, MediaTypeHeaderValue.Parse("application/json")))
{
var bodyParams = await httpRequest.ReadFromJsonAsync<BodyParams>();
if (bodyParams is not null) value = bodyParams.ValueOne;
}
var response = new {Message = "Hello", value};
return Results.Ok(response);
});
So, as there doesn't seem to be a more straightforward answer, I have currently gone with the approach number 5) from the OP, and just tweaking the code from there very slightly.
All this does is act as an action which checks the if the user has passed in any body json. If not, then it adds in an empty anonymous type. The behaviour then is to use the default True value from the BodyParams class.
The full code for the action class is:
internal class AllowMissingContentTypeForEmptyBodyConvention : Attribute, IActionModelConvention
{
public void Apply(ActionModel action)
{
action.Filters.Add(new AllowMissingContentTypeForEmptyBodyFilter());
}
private class AllowMissingContentTypeForEmptyBodyFilter : IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
if (!context.HttpContext.Request.HasJsonContentType()
&& (context.HttpContext.Request.ContentLength == default
|| context.HttpContext.Request.ContentLength == 0))
{
context.HttpContext.Request.ContentType = "application/json";
var str = new { };
//convert string to jsontype
var json = JsonConvert.SerializeObject(str);
//modified stream
var requestData = Encoding.UTF8.GetBytes(json);
context.HttpContext.Request.Body = new MemoryStream(requestData);
}
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
// Do nothing
}
}
}
Then you can add this to any of your controllers using [AllowMissingContentTypeForEmptyBodyConvention]
This is actually a 2-part question related directly to .net core 3.0 and specifically with PipeWriter: 1) How should I read in the HttpResponse body? 2) How can I update the HttpResponse? I'm asking both questions because I feel like the solution will likely involve the same understanding and code.
Below is how I got this working in .net core 2.2 - note that this is using streams instead of PipeWriter and other "ugly" things associated with streams - eg. MemoryStream, Seek, StreamReader, etc.
public class MyMiddleware
{
private RequestDelegate Next { get; }
public MyMiddleware(RequestDelegate next) => Next = next;
public async Task Invoke(HttpContext context)
{
var httpResponse = context.Response;
var originalBody = httpResponse.Body;
var newBody = new MemoryStream();
httpResponse.Body = newBody;
try
{
await Next(context);
}
catch (Exception)
{
// In this scenario, I would log out the actual error and am returning this "nice" error
httpResponse.StatusCode = StatusCodes.Status500InternalServerError;
httpResponse.ContentType = "application/json"; // I'm setting this because I might have a serialized object instead of a plain string
httpResponse.Body = originalBody;
await httpResponse.WriteAsync("We're sorry, but something went wrong with your request.");
return;
}
// If everything worked
newBody.Seek(0, SeekOrigin.Begin);
var response = new StreamReader(newBody).ReadToEnd(); // This is the only way to read the existing response body
httpResponse.Body = originalBody;
await context.Response.WriteAsync(response);
}
}
How would this work using PipeWriter? Eg. it seems that working with pipes instead of the underlying stream is preferable, but I can not yet find any examples on how to use this to replace my above code?
Is there a scenario where I need to wait for the stream/pipe to finish writing before I can read it back out and/or replace it with a new string? I've never personally done this, but looking at examples of PipeReader seems to indicate to read things in chunks and check for IsComplete.
To Update HttpRepsonse is
private async Task WriteDataToResponseBodyAsync(PipeWriter writer, string jsonValue)
{
// use an oversized size guess
Memory<byte> workspace = writer.GetMemory();
// write the data to the workspace
int bytes = Encoding.ASCII.GetBytes(
jsonValue, workspace.Span);
// tell the pipe how much of the workspace
// we actually want to commit
writer.Advance(bytes);
// this is **not** the same as Stream.Flush!
await writer.FlushAsync();
}
I have a Web API that gets call by this method:
public async Task<Model> AddModel(string token, Model newModel)
{
HttpContent content = await HttpHelper.Request(token, baseUrl, url, newModel, HttpRequestType.POST);
return await content.ReadAsAsync<Model>();
}
The Web API is successfully called and I can add a Model. This is the Web API method that gets called:
[Route("webapi/models/addModel")]
[HttpPost]
public async Task<ModelDto> AddWithoutResetingDefault(ModelDto newModel)
{
ModelService modelService = new ModelService();
return modelService.AddModel(newModel);
}
The problem is after the successful add, it doesn't return to the calling code anymore (I have a breakpoint that doesn't get hit). There are no console errors in the browser, I enclosed in a try-catch the calling code and the called code but there were no exceptions thrown.
Also, after the first click to add, if I try to refresh the browser, it takes a really long time to reload the browser (I don't know if being async has something to do with it).
(I don't know if being async has something to do with it) - Yes it has
your Api method
public async Task<ModelDto> AddWithoutResetingDefault(ModelDto newModel)
{
ModelService modelService = new ModelService();
return modelService.AddModel(newModel);
}
is marked as Async method, but the code inside is Sync. And that is the problem, if your modelService.AddModel(newModel); is async, then do
return await modelService.AddModel(newModel);
if its not then there is no point in making the AddWithoutResetingDefault
method async, hence remove Aysnc and simply do a sync method like
public ModelDto AddWithoutResetingDefault(ModelDto newModel)
{
ModelService modelService = new ModelService();
return modelService.AddModel(newModel);
}
Asp.Net Web API Odata Controller Action:
public async Task<IHttpActionResult> Post(Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Products.Add(product);
await db.SaveChangesAsync();
return Created(product);
}
Odata client code:
(Odata v4 client code generator v4)
static void AddProduct(Default.Container container, ProductService.Models.Product product)
{
container.AddToProducts(product);
var serviceResponse = container.SaveChanges();
foreach (var operationResponse in serviceResponse)
{
Console.WriteLine("Response: {0}", operationResponse.StatusCode);
}
}
I would like to handle exception in a proper way inside AddProducts() Method while saving the changes.
How can I catch process the ModelState error which is sent from server return BadRequest(ModelState);?
Finally I just want to show the error message to the end uses which was sent from server.
Example:
"Product category is required."
What is the use of ODataException class? Will this help me?
Please help me.
if I understood well, you want to intercept that the ModelState is not valid, and customize the OData error that is shown to the user.
If you just want that the errors of the invalid model show up in the returned payload, you can use:
if (!ModelState.IsValid)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
If you want to fully control the exceptions handling and messages shown, I'd suggest several action points for you to accomplish this:
Intercept ModelState is not valid: you can do this with a custom ActionFilterAttribute. In there, you can override the method OnActionExecuting(HttpActionContext actionContext). You can access the ModelState through actionContext.ModelState, check if it is valid, check the fields that have errors, check the nature of these errors and the generated messages for these errors, etc. The ModelState may be not valid for different reasons, like different types than the expected, not meet requirements specified by DataAnnotations, etc. You can check more on Model validation in here. For your case, I guess the Product entity will have a Required data annotation in the Category field.
After checking all errors, you can throw a custom Exception with the error/list of errors with the messages you want. This is necessary to later intercept your custom exception and be able to return your custom message in the error payload.
Intercept your custom exception: create a custom ExceptionFilterAttribute to intercept your thrown exceptions. Overriding the
OnException(HttpActionExecutedContext filterContext) you will have access to the exception, and inspecting it you will be able to build your proper OdataError:
In here you should return the HttpResponseMessage with the BadRequest http status code and the created ODataError as a payload. As an example of very simple code (you can see that it would depend on how you build your custom exception):
public override void OnException(HttpActionExecutedContext filterContext)
{
Exception ex = filterContext.Exception;
HttpRequestMessage currentRequest = filterContext.Request;
if (filterContext.Exception.GetType() == typeof(YourCustomValidationException))
{
var oDataError = new ODataError()
{
ErrorCode = "invalidModel",
Message = "Your model is not valid.",
InnerError = new ODataInnerError()
{
TypeName = ex.TheEntityThatHasErrors
},
};
foreach (var validationError in ex.ValidationErrors)
{
oDataError.InnerError.Message += validationError + ", ";
}
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.BadRequest);
response.RequestMessage = currentRequest;
response.Content = new StringContent(JsonConvert.SerializeObject(oDataError));
filterContext.Response = response;
}
}
Finally, you will have to setup the custom ActionFilterAttribute and the custom ErrorFilterAttribute to be used each time that a request reach your controller. You can decorate your actions, controllers, or you can set the filters for all your API controllers in the WebApiConfig, with config.Filters.Add(...);
You can find more information about all of this in here. In the end, the error and exception handling is the same for ASP.Net Web API, with or without OData; difference is that if you have an OData API, you should return errors in OData style.
Hope all this info is understandable and helps you somehow.
My goal is to authenticate Web API requests using a AuthorizationFilter or DelegatingHandler. I want to look for the client id and authentication token in a few places, including the request body. At first it seemed like this would be easy, I could do something like this
var task = _message.Content.ReadAsAsync<Credentials>();
task.Wait();
if (task.Result != null)
{
// check if credentials are valid
}
The problem is that the HttpContent can only be read once. If I do this in a Handler or a Filter then the content isn't available for me in my action method. I found a few answers here on StackOverflow, like this one: Read HttpContent in WebApi controller that explain that it is intentionally this way, but they don't say WHY. This seems like a pretty severe limitation that blocks me from using any of the cool Web API content parsing code in Filters or Handlers.
Is it a technical limitation? Is it trying to keep me from doing a VERY BAD THING(tm) that I'm not seeing?
POSTMORTEM:
I took a look at the source like Filip suggested. ReadAsStreamAsync returns the internal stream and there's nothing stopping you from calling Seek if the stream supports it. In my tests if I called ReadAsAsync then did this:
message.Content.ReadAsStreamAsync().ContinueWith(t => t.Result.Seek(0, SeekOrigin.Begin)).Wait();
The automatic model binding process would work fine when it hit my action method. I didn't use this though, I opted for something more direct:
var buffer = new MemoryStream(_message.Content.ReadAsByteArrayAsync().WaitFor());
var formatters = _message.GetConfiguration().Formatters;
var reader = formatters.FindReader(typeof(Credentials), _message.Content.Headers.ContentType);
var credentials = reader.ReadFromStreamAsync(typeof(Credentials), buffer, _message.Content, null).WaitFor() as Credentials;
With an extension method (I'm in .NET 4.0 with no await keyword)
public static class TaskExtensions
{
public static T WaitFor<T>(this Task<T> task)
{
task.Wait();
if (task.IsCanceled) { throw new ApplicationException(); }
if (task.IsFaulted) { throw task.Exception; }
return task.Result;
}
}
One last catch, HttpContent has a hard-coded max buffer size:
internal const int DefaultMaxBufferSize = 65536;
So if your content is going to be bigger than that you'll need to manually call LoadIntoBufferAsync with a larger size before you try to call ReadAsByteArrayAsync.
The answer you pointed to is not entirely accurate.
You can always read as string (ReadAsStringAsync)or as byte[] (ReadAsByteArrayAsync) as they buffer the request internally.
For example the dummy handler below:
public class MyHandler : DelegatingHandler
{
protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
var body = await request.Content.ReadAsStringAsync();
//deserialize from string i.e. using JSON.NET
return base.SendAsync(request, cancellationToken);
}
}
Same applies to byte[]:
public class MessageHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var requestMessage = await request.Content.ReadAsByteArrayAsync();
//do something with requestMessage - but you will have to deserialize from byte[]
return base.SendAsync(request, cancellationToken);
}
}
Each will not cause the posted content to be null when it reaches the controller.
I'd put the clientId and the authentication key in the header rather than content.
In which way, you can read them as many times as you like!