Hotchocolate 13: Mutation does not work with schema first approach - c#

I am trying to use hotchocolate 13. I have declared a simple mutation as below and have used 'AddGlobalObjectIdentification' to configure relay schema.
I have used schema first approach and the mutation looks like below
SDL
type Mutation {
updatePost(input: UpdatePostInput!): UpdatePostPayload!
}
input UpdatePostInput {
id: ID!
title: String!
content: String!
tags: [PostTagInput!]!
}
type UpdatePostPayload {
post: Post
errors: [ApiError!]
}
and the API looks like below
[UseDbContext(typeof(ApiContext))]
public async Task<UpdatePostPayload> UpdatePostAsync(UpdatePostInput input,
[Service] ITopicEventSender eventSender,
CancellationToken cancellationToken,
ApiContext context)
{
....
}
with UpdatePostInput as below
public record UpdatePostInput([ID(nameof(Post))] int Id,
string Title,
string Content,
List<PostTagInput> Tags);
I was expecting that the mutation request should work
mutation($input: UpdatePostInput!) {
updatePost(input: $input) {
post{
id
}
}
}
variables:
{
"input": {
"id": "UG9zdAppNA==",
"title": "React",
"content": "React is an awesome frontend technology.",
"tags": []
}
}
however I get the error "Cannot convert type 'string' to 'System.ToInt32'" for the 'id' field. This works fine with the code first approach though. It also works fine with the 'Query'.
Could anyone please suggest me if something is missing in my code or should I do it differently?

public record UpdatePostInput([ID(nameof(Post))] int Id,
string Title,
string Content,
List<PostTagInput> Tags);
In this code you define Id as a integer. The correct type based on the error message you get is string. Try updating the code to let id be a string instead.

Related

JObject.ToObject with errors to Model State

In an ASP.NET core project, I've switched from using ResourceModel to using JObject as my [FromBody] parameter. I then pass JObject.ToObject<ResourceModel>() into a service, but want to maintain the JObject itself for the ContainsKey and similar functionality.
If JObject.ToObject<T> succeeds, I can use TryValidateModel() and, if that fails, return BadRequest(ModelState) simply enough. However, the problem I'm running into is when JObject.ToObject<T> throws an exception - I'm unsure how to capture the serialization errors in the ModelState.
Example:
public class Person {
public string FirstName {get;set;}
public string LastName {get;set;}
public uint Age {get;set;}
}
public class CommonParameters {
[FromQuery] public string? Fields {get;set;}
public string UserName {get;set;}
public IPAddress? RequestorIp {get;set;}
public JObject? JsonBody {get;set;}
}
public class PersonController : ControllerBase {
private readonly PersonService _personService;
// this is how I had been doing it
[HttpPatch("obsolete/{id}")] // this method doesn't actually exist, but is shown for the example
public async Task<IActionResult> UpdatePerson([FromRoute] int id, [FromBody] Person parameters, [FromHeader] CommonParameters commonParameters) {
SetCommonParameters(commonParameters, parameters);
// if parameters.Age in the json body was a string, BadRequest is returned before this method even starts
_personService.Set(parameters);
await _personService.SaveChangesAsync();
return NoContent();
}
// this is how I'm trying to do it now, so that I only update Age if JObject.ContainsKey("Age")
// instead of parameters.Age != default, etc.
[HttpPatch("{id}")]
public async Task<IActionResult> UpdatePerson([FromRoute] int Id, [FromBody] JObject parameters, [FromHeader] CommonParameters commonParameters) {
SetCommonParameters(commonParameters, parameters);
// if parameters.Age is a string in this version, a JsonReaderException
// or JsonSerializationException is thrown when I call JObject.ToObject<>()
// and I have to validate the model separately after that
// what this means is, a BadRequest with the ModelState errors is not
// returned if there's an issue with the json.
var model = parameters.ToObject<Person>();
if (!TryValidateModel(model)) return BadRequest(ModelState);
_personService.Set(person);
await _personService.SaveChangesAsync();
return NoContent();
}
private void SetCommonParameters(CommonParameters commonParameters, JObject? jsonBody = null) {
commonParameters.JsonBody = jsonBody;
commonParameters.UserName = User.Identity?.Name;
commonParameters.RequestorIp = Request.HttpContext.Connection.RemoteIpAddress;
}
}
EDIT: As Guru Stron has asked, here's an example of an invalid model/json object.
Model:
public class ContactInfo {
// PhoneNumber being a struct representing an 11-digit phone number
// PhoneType being an enum of Cell, Work, Home, etc.
public Dictionary<PhoneType, PhoneNumber[]> PhoneNumbers {get;set;} = new();
public string? Apartment {get;set;}
public int AddressNumber {get;set;}
public string StreetName {get;set;}
public string City {get;set;}
// State being an enum of the 50 United States of America
public State State {get;set;}
}
The invalid json
{
"phoneNumbers": {
"work": [ "1-111-111-1111" ],
"home": [ "2-222-222-2222" ],
"cell": [ "1-800-CALL-NOW" ]
},
"apartment": null,
"addressNumber": "3a",
"streetName": "Imagination St.",
"city": "Atlantis",
"state": "Atlantic"
}
In this case, passing the above json for ContactInfo to a controller's method that asks for a ContactInfo parameter will return a bad request indicating that the state was invalid, the address number was invalid, and a phone number was invalid - without testing this specifically, something like the following.
{
"errors": {
"phoneNumbers": [
"Could not parse phone number \"1-800-CALL-NOW\". The value must be an 11-digit numeric value."
],
"addressNumber": [
"The value could not be parsed as int."
],
"state": [
"\"Atlantic\" is an invalid value for State. The following values are valid: ..."
]
}
}
If instead the controller asks for JObject and I then call JObject.ToObject and catch an exception, I'll get one of those errors - such as Could not parse phone number "1-800-CALL-NOW". The value must be an 11-digit numeric value. The others will be ignored until that one is fixed and the user tries again.
I've at last figured it out... All that I need to do is provide a JsonSerializer to JObject.ToObject<> and include an error handler that marks errors as handled. For example:
var serializer = new JsonSerializer();
serializer.Error += (sender, args) =>
{
if (args.ErrorContext.Error is JsonException)
{
ModelState.AddModelError(args.ErrorContext.Path, args.ErrorContext.Error.Message);
args.ErrorContext.Handled = true;
}
};
var value = parameters.ToObject<Person>(serializer);
if (!TryValidateModel(value)) return ValidationProblem(ModelState);
It is my understanding that the default Microsoft.AspNetCore.Mvc.Formatters.NewtonsoftJsonInputFormater performs similar error handling; I'm still not sure how to use that handler directly, but what I have here is working for what I need.

NEST : Issue in getting data from search response (ISearchResponse) in ElasticSearch

I wrote a C# code using NEST, which makes search queries to my ES database. I can see these queries succeed and give a json response body through Postman. I want to use these responses in my code. For example,
ISearchResponse<class> myquery = client.Search<class>(...)
(some successful api call)
The response body is something like:
{
"took": 5,
...
...
"hits": {
"max_score": 1.2,
"hits": [
{
"_index": "sample",
...
"_source": {
"name": "generic name",
"profession": "lawyer",
...
}
}
]
}
"aggs" : {
...
}
}
I can get the "took" value here by doing myquery.Took. Similarly I can see the definition of ISearchResponse<> contains data members for MaxScore, TimedOut etc.
My question is, In the same way if I want to get the value of, say the name field or some bucket in aggr, to use in my code. How can I do this? Please Help.
Note :
The Documentation only explained how to handle error responses and I can see in the debugger that probably .Documents is storing this somehow, but I'm not able to retrieve the data (or probably I can't understand how to). So please also explain how to get it from .Documents if that is the case.
The "_source" JSON object of each hit will be deserialized into the type T specified as the generic type parameter on Search<T>. In your example, "_source" will be deserialized into class, so simply define properties on class for the properties on "_source". For example
public class MyDocument
{
[PropertyName(Name = "name")]
public string Name {get;set;}
[PropertyName(Name = "profession")]
public string Profession {get;set;}
}
var response = client.Search<MyDocument>();

Implementing JSON Merge Patch in ASP.NET Core WebAPI

I am interested in adding support for partial updates in my ASP.NET Core WebAPI where I only update the properties on a resource that the caller provided, leaving excluded properties unchanged.
For context, imagine I have a resource that can be described as follows:
GET /users/1
{
title: "Mister",
firstName: "Frederick",
middleName: "McFeely",
lastName: "Rodgers"
}
If I wanted to allow consumers to change the value stored in the firstName property from "Frederick" to "Fred" in isolation, I should be able to expose a PATCH endpoint that supports the JSON Merge Patch Content-Type, like so:
PATCH /users/1
Content-Type: application/merge-patch+json
{
firstName: "Fred"
}
However, I see no easy way for me to know that firstName is the only property being updated. For example, if I were to make a controller that accepted PATCH verbs, it could be scaffolded like this:
[Route("users")]
public class UsersController : Controller {
[HttpPatch("{userId:int}")]
public User Patch([FromRoute] int userId, [FromBody] User user) {
// How do I know which properties were set on User at this point?
}
}
public class User {
public String Title { get; set; }
public String FirstName { get; set; }
public String MiddleName { get; set; }
public String LastName { get; set; }
}
But I don't see how I can extract which properties' had keys defined on the JSON object before it was hydrated as a User and passed to my controller. I cannot assume a value of null to mean a property was excluded as the caller could be explicitly setting an optional property to null.
Edit
I am aware of the Microsoft.AspNetCore.JsonPatch library. This, unfortunately, expects the caller to use the "[description of changes]" to define a PATCH as described in RFC 5789, which I find unintuitive and verbose. I am referring to the "JSON Merge Patch" defined in RFC 7396.
I found a library that works: https://github.com/Morcatko/Morcatko.AspNetCore.JsonMergePatch
[HttpPatch]
[Consumes(JsonMergePatchDocument.ContentType)]
public void Patch([FromBody] JsonMergePatchDocument<Model> patch)
{
...
patch.ApplyTo(backendModel);
...
}
Or use patch.JsonPatchDocument.Operations to walk through patch request fields manually.
for simple types, I found a very simple solution using Newtonsoft.Json merge of JObjects:
public static T Patched<T>(T source, JObject patch) where T : class
{
var sourceObject = JObject.FromObject(source);
sourceObject.Merge(patch, new JsonMergeSettings() {MergeArrayHandling = MergeArrayHandling.Union});
return sourceObject.ToObject<T>();
}
public static T Patched<T>(T source, string patchCode) where T : class
{
return Patched<T>(source, JObject.Parse(patchCode));
}
Hope this helps someone searching for this topic and looking for a simple solution without external packages.
It appears like, for merge patch you will have to wait for odata support.
It is in beta at the moment and supports the merge semantics with the Delta<> class.
https://www.nuget.org/packages/Microsoft.AspNetCore.OData/
For doing a patch, you have to define PatchDocument.
More about it you can find PatchDocument
Example of method.
[HttpPatch("{userId:int}")]
public IActionResult UserPatch(int userId, [FromBody] JsonPatchDocument<User> patchDocument) {
var user = new User();
// Because it comes from url.
user.Id = userId;
patchDocument.ApplyTo(user);
// Here you call context or repository to save.
}
Example of document.
[
{ "op": "replace", "path": "/firstName", "value": "boo" },
]
That will update firstName field to 'boo' in user model.
What you might be looking for is ASP.Net Core JsonPatchDocument
https://github.com/aspnet/JsonPatch
https://learn.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.jsonpatch

Azure Functions model binding

I've created an Azure Function and I'm running it locally:
[FunctionName("HttpTriggerCSharpSet")]
public static async Task<HttpResponseMessage> Set([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] MyDocument req, TraceWriter log)
{
// ...
}
Notice that MyDocument is the first parameter instead of HttpRequestMessage. I've read in the documentation that this approach should work, and it seems very similar to ASP.NET model binding (in my mind, anyway). MyDocument is a POCO with just 3 properties.
public class MyDocument
{
public string Name { get; set; }
public int ShoeSize { get; set; }
public decimal Balance { get; set; }
}
When I POST to the function like so (I'm using Postman):
I get an error message: [8/8/2017 2:21:07 PM] Exception while executing function: Functions.HttpTriggerCSharpSet. Microsoft.Azure.WebJobs.Host: Exception binding parameter 'req'. System.Net.Http.Formatting: No MediaTypeFormatter is available to read an object of type 'MyDocument' from content (which you can also see in the screenshot of Postman above)
I've tried form-data and x-www-form-urlencoded and even raw from Postman, same error every time. I've also tried switching back to HttpRequestMessage and using req.Content.ReadAsAsync<MyDocument>, and I get a similar error. Am I constructing my POST incorrectly, or am I writing my Azure Function incorrectly. In either case, what's the correct way?
Make sure to specify the header:
Content-Type: application/json
then the following body should work for your code:
{
"Name": "myUserName",
"Balance": 123.0,
"ShoeSize": 30
}

Build Json String From 2 Different Dictionaries?

I am using json.net(newtonsoft) and I want to build a json request but I have 2 different dictionaries and not sure how to join them.
Dictionary<string, HttpStatusCode> code = new Dictionary<string, HttpStatusCode>();
code.Add("Message", statusCode);
Dictionary<string, IErrorInfo> modelState = new Dictionary<string, IErrorInfo>();
// some code to add to this modelState
Edit
IErrorInfo just has some properties
public interface IErrorInfo
{
SeverityType SeverityType { get; set; }
ValidationType ValidationType { get; set; }
string Msg { get; set; }
}
The result I trying to go for is something like this
{
"Message": 400, // want this to be text but not sure how to do that yet (see below)
"DbError":{
"SeverityType":3,
"ValidationType":2,
"Msg":"A database error has occurred please try again."
}
}
I am basically trying to achieve this.
HttpError and Model Validation
For model validation, you can pass the model state to CreateErrorResponse, to include the validation errors in the response:
public HttpResponseMessage PostProduct(Product item)
{
if (!ModelState.IsValid)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
// Implementation not shown...
}
This example might return the following response:
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Length: 320
{
"Message": "The request is invalid.",
"ModelState": {
"item": [
"Required property 'Name' not found in JSON. Path '', line 1, position 14."
],
"item.Name": [
"The Name field is required."
],
"item.Price": [
"The field Price must be between 0 and 999."
]
}
}
The reason why I am not using this built in method is because I have a separate built in class library that has all my business logic in it. I want to keep it so that it has no dependency on web stuff or mvc stuff(like modelState).
This is why I created my own sort of model state with a bit of extra stuff in it.
You should be able to just use one Dictionary and add items from both of your dictionaries into this dictionary. Json.NET should serialize it all like you're expecting.

Categories