What am i doing wrong here (i'm using .net core 3.1):
Object:
public class Member
{
public int NUM {get;set;}
}
Post Action
[HttpPost]
public async Task<IActionResult> Post([FromBody] IEnumerable<Member> members)
JSON
{
[
{"NUM": 5},
{"NUM": 4}
]
}
Error i'm getting from Postman
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|fc86d5d0-498dec466e59f3c5.",
"errors": {
"$": [
"The JSON value could not be converted to System.Collections.Generic.IEnumerable`1[TRS.Gemini.MemberApi.Controllers.Member]. Path: $ | LineNumber: 0 | BytePositionInLine: 1."
]
}
}
I tried to post a single object rather than a collection and that worked fine. The object in the end of course will be a lot more complex but i wanted to start simple.
For the object you've defined, the valid JSON input would be:
[
{"NUM": 5},
{"NUM": 4}
]
Note the lack of { } brackets.
The payload you've specified is also not valid JSON.
Related
How would I write a CloudWatch filter with a property selector for a JSON property containing a period character "."?
The CloudWatch documentation describes using a property selector to filter log data (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html#metric-filters-extract-json):
Documentation Syntax:
{ PropertySelector EqualityOperator String }
Documentation Example:
{ $.eventType = "UpdateTrail" }
If a log event contains a JSON property containing a period character ".", is it possible to describe it with a property selector?
I have attempted the following property selectors:
Filter encounters error { $.attributes.http\.method = "POST" }
Filter encounters error { $.attributes["http.method"] = "POST" }
Filter produces zero results { $.attributes.http.method = "POST" }
Example Event
{
"traceId": "75e07edf6f50ddf0d4a8239cbe91d60a",
"parentId": "f8875623e2e77de4",
"name": "request handler - /subscriptions",
"id": "7f8b38dcbbb15983",
"kind": 0,
"timestamp": 1673621283065,
"duration": 3,
"attributes": {
"http.route": "/subscriptions",
"express.name": "/subscriptions",
"express.type": "request_handler",
"http.method": "POST",
"http.url": "/subscriptions",
"http.body": "{\"foo\":\"bar\"}"
},
"status": {
"code": 0
},
"events": [],
"links": []
}
I recently rewrote some GraphQL services from Java to .NET Core.
In Java, I was able to provide custom error messages to the clients using the errors.extensions in the response, ie:
{
"data": {
"someMutation": null
},
"errors": [{
"cause": null,
"message": "Unauthorized",
"httpStatusCode": 0,
"extensions": {
"uiMessage": "Oh no, your session expired. You'll need to login again to continue.",
"httpStatusDescription": "Unauthorized",
"httpStatusCode": 401
},
"errorType": "ValidationError",
"path": null,
"localizedMessage": "Unauthorized",
"suppressed": []
}
]
}
However, in .NET, I don't seem to be able to replicate this format.
ErrorInfo.Extensions is added to the root of the response, not to the the Errors object itself, eg:
{
"data": {
"someMutation": null
},
"errors": [{
"message": "Auth token not provided"
}
],
"extensions": {
"httpStatusCode": 401,
"httpStatusDescription": null,
"uiMessage": "Oh no, your session expired. You'll need to login again to continue.",
}
}
The GraphQL spec reads (ref https://spec.graphql.org/October2021/#sec-Errors, https://spec.graphql.org/October2021/#example-8b658):
GraphQL services may provide an additional entry to errors with key
extensions. This entry, if set, must have a map as its value. This
entry is reserved for implementors to add additional information to
errors however they see fit, and there are no additional restrictions
on its contents.
eg:
{
"errors": [
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [{ "line": 6, "column": 7 }],
"path": ["hero", "heroFriends", 1, "name"],
"extensions": {
"code": "CAN_NOT_FETCH_BY_ID",
"timestamp": "Fri Feb 9 14:33:09 UTC 2018"
}
}
]
}
I created a new test project (.NET Core 3.1) using the latest versions of the libraries (GraphQL 7.1.1 et al) but am still unable to add custom properties to errors.extensions.
This is the test mutation which intentionally throws an exception:
Field<StringGraphType>("greet")
.Argument<NonNullGraphType<StringGraphType>>("name")
.Resolve(context => {
try {
throw new Exception("Invalid input");
return "Hello " + context.GetArgument<String>("name");
} catch(Exception ex) {
// This doesn't seem to get returned anywhere in the response
Dictionary<String, object> extraData = new Dictionary<string, object>();
extraData.Add("error1", "message1");
// Add the error to the response using the overloaded constructor
context.Errors.Add(new ExecutionError("Oh dear, that went wrong", extraData));
// This gets added to the root of the response
context.OutputExtensions.Add("error2", "message2");
return null;
}
});
the mutation to invoke it:
mutation {greet(name:"Chewbacca")}
and the response (I don't know where errors.extensions.details comes from):
{
"errors": [
{
"message": "Oh dear, that went wrong",
"extensions": {
"details": "GraphQL.ExecutionError: Oh dear, that went wrong"
}
}
],
"data": {
"greet": null
},
"extensions": {
"error2": "message2"
}
}
I would imagine that the GraphQL.NET library would expose an Extensions dictionary on the ExecutionError object so one could add custom values in the usual manner, eg:
ExecutionError executionError = new ExecutionError("Oh dear, that went horribly wrong");
executionError.Extensions.Add("customError", "Your custom error here")
context.Errors.Add(executionError);
Which would result in a response similar to this:
{
"data": {
"someMutation": null
},
"errors": [{
"message": "Oh dear, that went horribly wrong",
"extensions": {
"customError": "Your custom error here"
}
}
]
}
I am hopeful that some bright individual in the community can (slap me upside the head and) point me in the right direction.
In a scenario where an input value is mapped to a response object which contains either a success message or a failure message along with an error message, how can I handle errors correctly without dropping any elements from the publisher?
I have a domain object which follows the Builder pattern, and it performs validation on build, throwing an Exception which includes the object's ID.
To process this I've tried the following 2 attempts:
public Flux<GenericResponse> handle(Publisher<DomainDto> input) {
return Flux.from(input)
.map(c -> c.toDomain()) // this is what throws the exception
// some other processing here
.map(c -> GenericResponse.accepted(c.getId()))
.onErrorResume(e ->
Flux.just(GenericResponse.error(((BadRequestException)e).getId(), e.getMessage()))
);
}
public Flux<GenericResponse> handle(Publisher<DomainDto> input) {
return Flux.from(input)
.map(c -> c.toDomain()) // this is what throws the exception
// some other processing here
.concatMap(c ->
Flux.just(GenericResponse.accepted(c.getId()))
.onErrorResume(e ->
Flux.just(GenericResponse.error(((BadRequestException)e).getId(), e.getMessage()))
)
);
}
With the first, if I send through 5 inputs and the third is expected to throw an error, I get 2 success messages, and one failure message as expected:
[
{ "id": 1, "code": "ACCEPTED" },
{ "id": 2, "code": "ACCEPTED" },
{ "id": 3, "code": "ERROR", "description": "Some error message" }
]
However with the second attempt using the same inputs, I get an actual stack trace with no results:
{
"timestamp": 1627880204616,
"path": "/",
"status": 500,
"error": "Internal Server Error",
"message": "Some error message",
"requestId": "2917b3af-1",
"trace": "com.example.BadRequestException: Some error message..."
}
What can I do to get a response for all inputs as below:
[
{ "id": 1, "code": "ACCEPTED" },
{ "id": 2, "code": "ACCEPTED" },
{ "id": 3, "code": "ERROR", "description": "Some error message" },
{ "id": 4, "code": "ACCEPTED" },
{ "id": 5, "code": "ACCEPTED" }
]
If you want to manage error element per element, your best option is to use an intermediate Mono to manage errors like so :
public Flux<GenericResponse> handle(Publisher<DomainDto> input) {
return Flux.from(input)
.flatMap(c -> Mono.fromCallable(() -> c.toDomain())
.map(c -> GenericResponse.accepted(c.getId()))
.onErrorResume(BadRequestException.class, e
-> Mono.just(GenericResponse.error(e.getId(), e.getMessage())));
}
Another option is to use the onErrorContinue operator. It is very close to your original code. However, as stated by the documentation, it is not always safe to use, because in case an error has broken the upstream pipeline, it cannot properly "continue" the flow operations.
Example using onErrorContinue:
public Flux<GenericResponse> handle(Publisher<DomainDto> input) {
return Flux.from(input)
.map(c -> c.toDomain()) // this is what throws the exception
// some other processing here
.map(c -> GenericResponse.accepted(c.getId()))
.onErrorContinue(BadRequestException.class, e ->
Flux.just(GenericResponse.error(e.getId(), e.getMessage()))
);
}
I have a get operation that I want to return a string from. An example would be
"000875"
When I return this string from a controller in my Web API Controller in full .NET, it formats it like this:
{
"Property": "000875"
}
When I return the string in my converted .NET Core Controller it returns this:
{
"$id": "1",
"$type": "System.Net.Http.HttpResponseMessage, System.Net.Http",
"Version": "1.1",
"Content": {
"$id": "2",
"$type": "System.Net.Http.StringContent, System.Net.Http",
"Headers": [
{
"Key": "Content-Type",
"Value": [
"application/json; charset=utf-8"
]
}
]
},
"StatusCode": "OK",
"ReasonPhrase": "OK",
"Headers": [],
"TrailingHeaders": [],
"RequestMessage": null,
"IsSuccessStatusCode": true
}
It is interesting to note that the value is not even in there!
I am running some interesting JSON Serialization to make BreezeJs work with .NET Core. It is possible that it is the cause of this weirdness:
.AddNewtonsoftJson(opt =>
{
// Let Breeze say how we serialize. This adds in the Type and Id options the way breeze expects
var jsonSerializerSettings = JsonSerializationFns.UpdateWithDefaults(opt.SerializerSettings);
......
I am hoping for a way to get strings through without all this mess. Can that be done?
I get the impression that the subject action definition returns HttpResponseMessage.
public HttpResponseMessage MyAction(....
HttpRequestMessage is no longer a first class citizen in asp.net-core framework and will be treated as a normal model and serialized.
That explains the JSON you are seeing with your controller
The syntax needs to be updated to return IActionResult derived responses
public IActionResult MyAction() {
//...
return Ok("000875");
}
ActionResult<T>
public ActionResult<string> MyAction() {
//...
if(somecondition)
return NotFound();
return "000875";
}
or the model itself.
public string MyAction() {
//...
return "000875";
}
Reference Controller action return types in ASP.NET Core Web API
My posted JSON object is this:
{{
"emails": [
{
"To": "info#gmail.com",
"Subject": "Subject",
"Body": "Body",
"ID": "d3d13242-6eff-4c57-b718-ef5ad49fe301"
},
{
"To": "hr#gmail.com",
"Subject": "Subject",
"Body": "Body",
"ID": "101edaf0-fcb4-48fc-9e9e-0d7492b591b0"
}
]
}}
By default ASP.NET model binder will not bind this JSON object and as you can see here I get always null when I send post request to the API:
[HttpPost, Route("Send")]
public async Task<IActionResult> Send(Email[] emails)
{
var toSave = from email in emails
select new EmailQueueItem
{
Html = true,
To = email.To,
Subject = email.Subject,
Body = email.Body
};
await Database.BulkInsert(toSave.ToArray());
return Ok();
}
emails property is always null. I would appreciate any help for creating custom model binder that handel this kind of JSON objects.
The issue is that you are actually sending an object containing one property named emails, not an array, to the controller
Option one:
The client object needs to contain just the array
[
{
"To": "info#gmail.com",
"Subject": "Subject",
"Body": "Body",
"ID": "d3d13242-6eff-4c57-b718-ef5ad49fe301"
},
{
"To": "hr#gmail.com",
"Subject": "Subject",
"Body": "Body",
"ID": "101edaf0-fcb4-48fc-9e9e-0d7492b591b0"
}
]
Then read the array from the request body
public async Task<IActionResult> Send([FromBody]Email[] emails)
Option 2:
When you define the array like this in the client
{
"emails":...
}
You need to match that object setup on the controller by defining a model which contains a property called emails
public class RequestModel
{
public Email[] emails { get; set; }
}
Then in the controller method, use the model and read it from the body
public async Task<IActionResult> Send([FromBody]RequestModel emails)