When calling an HTTP get method, the query parameters are not correctly filled in.
Let me explain with the code.
[HttpGet]
public async Task<IActionResult> GetTasks(Guid propertyId, [FromQuery] TaskFilterParams filterParams, [FromQuery] SortingParams sortingParams, [FromQuery] PagingParams pagingParams)
{
// Do some stuff, not important
}
So we call this endpoint with the following url: someurl.com/api/v1/properties/[some-guid]/tasks?&roomClass=Single&category=Cleaning
As you can see, we only have provided three values, a GUID, a type of room and the category of the task. The sorting and paging parameters are empty.
The problem is that the 'category' parameter is not filled in. It is always null, while the other parameters are filled in correctly. No matter what we try (url encoding, re-arranging the order of the parameters, ...).
The real catch is however, if we restart the service, it sometimes works for a while. But every so often it fails and we need to restart evertything again.
Has anyone encountered the same issue? Or, even better, a solution?
PS: The code is running in a docker container in AKS (FROM mcr.microsoft.com/dotnet/core/aspnet:3.1).
[DBG] Get tasks parameters: PropertyId: [some-guid], TaskFilterParams:{"Category": null, "RoomClass": {"Value": "Single", "Operator": "Equal", "$type": "FilterParam`1"}, "$type": "TaskFilterParams"}
public class TaskFilterParams
{
public FilterParam<IssueCategory> Category { get; set; }
public FilterParam<RoomClass> RoomClass { get; set; }
public FilterParam<Guid?> Room { get; set; }
}
public abstract class FilterParam
{
public FilterOperator Operator { get; set; }
}
[TypeDescriptionProvider(typeof(FilterParamTypeDescriptionProvider))]
[JsonObject]
public class FilterParam<T> : FilterParam
{
public T Value { get; set; }
public static bool TryParse(string s, out FilterParam<T> result)
{
result = default(FilterParam<T>);
var filterOperators = Enum.GetValues(typeof(FilterOperator)).Cast<FilterOperator>();
foreach (var filterOperatorString in filterOperators.Select<FilterOperator, string>(x => x.ToFilterOperatorString()).OrderByDescending(x => x.Length))
{
if (s.StartsWith(filterOperatorString))
{
if (s.TryParse(filterOperatorString, out result))
{
return true;
}
return false;
}
}
return false;
}
}
public enum IssueCategory
{
Cleaning = 0,
Inspection = 1,
Maintenance = 2
}
public enum RoomClass
{
Single = 0,
Double = 1,
Family = 2
}
UPDATE:
We have added more logging. It seems the data is present in the query string but the model is not binding correctly.
Log.Debug("Query: {#query}", HttpContext.Request.Query);
Log.Debug(
"Get tasks parameters: PropertyId:{#propertyId}, TaskFilterParams:{#filterParams}, SortingParams:{#sortingParams}, PagingParams:{#pagingParams}",
propertyId, filterParams, sortingParams, pagingParams);
[16:51:35 DBG] Query: [{"Key": "category", "Value": ["Cleaning"], "$type": "KeyValuePair`2"}, {"Key": "roomClass", "Value": ["Single"], "$type": "KeyValuePair`2"}]
[16:51:35 DBG] Get tasks parameters: PropertyId:[some-guid], TaskFilterParams:{"Category": null, "RoomClass": {"Value": "Single", "Operator": "Equal", "$type": "FilterParam`1"}, "$type": "TaskFilterParams"}
Related
I'm working on a webapi project using .netcore.
I have a model with the following properties:
public class Criterial {
[Required]
public string Field { get; set; }
[Required]
public Operator Operator { get; set; }
[Required]
public string Value { get; set; }
public bool Result { get; set; }
}
public enum Operator {
greater_than,
equal_to,
lower_than
}
I'm trying to use enum to restrict the values that the Operator propertie can receive, but when I make a POST request to the API I got the following scenario:
POST Request Body:
"criterials": [
{
"field": "amount",
"operator": "greater_than",
"value": "50"
}
]
Response from the API:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|7e53377-444fa4a723ac655c.",
"errors": {
"$.criterials[0].operator": [
"The JSON value could not be converted to LeagueOfFateApi.Models.Operator. Path: $.criterials[0].operator | LineNumber: 5 | BytePositionInLine: 26."
]
}
}
Searching about the issue on the internet I found the [JsonConverter(typeof(JsonStringEnumConverter))] Data Annotation.
So I added it to my code and the issue was "solved":
[Required]
[JsonConverter(typeof(JsonStringEnumConverter))]
public Operator Operator { get; set; }
New response from the API:
"criterials": [
{
"field": "amount",
"operator": "greater_than",
"value": "50",
"result": false
}
]
The problem is: in my MongoDB collection a new document was saved with the int value 0 of the enums, and not the string value "greater_than":
"Criterials" : [
{
"Field" : "amount",
"Operator" : 0,
"Value" : "50",
"Result" : false
}
]
Besides, another problem is that the "criterial" field can receive any int value with no restrictions.
Is there any other practical way to restrict a string's options without using enums? Or is there anything I can add to this solution using enums?
Thank you very much for your attention and your time!
According to your description, I suggest you could write custom set and get method for the Operator property.
You could set the Operator's type is string and use Enum.IsDefined to check the Operator value is enum Operator or not.
More details, you could refer to below codes:
public class Criterial
{
[Required]
public string Field { get; set; }
private string _Operator;
[Required]
public string Operator {
get {
return this._Operator;
}
set {
if (Enum.IsDefined(typeof(Operator), value))
{
this._Operator = value;
}
else
{
this._Operator = "Error you used wrong string";
}
}
}
[Required]
public string Value { get; set; }
public bool Result { get; set; }
}
public enum Operator
{
greater_than,
equal_to,
lower_than
}
Result:
Following is the Model class that I use for the application
public class APIVesselFilter
{
[Required(ErrorMessage = "Vessel is required")]
public Guid VesselID { get; set; }
public Guid? AnchorageID { get; set; }
}
Following is the Validation Filter that will check if the ModelState is Valid and if not valid I will send the error message.
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid == false)
{
List<string> errorList = new List<string>();
foreach (var err in actionContext.ModelState.Values)
{
foreach (var er in err.Errors)
{
errorList.Add(er.ErrorMessage);
}
}
actionContext.Response = actionContext.Request.CreateResponse(APIResponse.SendResponse(RequestStatus.GetValidationFailedMessage(), actionContext.ModelState));
}
}
}
Here, Following is the response that I get using the above Model State. Over here the error message is not in a user understandable way and so I have added the ErrorMessage in Require attribute of Vessel and I loop through the errors in ModelState. But my error message is always empty (I checked this using a debugger). What am I missing here so that the error message will be bound directly to the ModelState?
{
"Status": {
"StatusCode": 620,
"StatusMessage": "Validation Failed"
},
"Data": {
"filter": {
"_errors": [
{
"<Exception>k__BackingField": {
"ClassName": "Newtonsoft.Json.JsonSerializationException",
"Message": "Required property 'VesselID' not found in JSON. Path '', line 1, position 56.",
"Data": null,
"InnerException": null,
"HelpURL": null,
"StackTraceString": " at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EndObject(Object newObject, JsonReader reader, JsonObjectContract contract, Int32 initialDepth, Dictionary`2 propertiesPresence)",
"RemoteStackTraceString": null,
"RemoteStackIndex": 0,
"ExceptionMethod": "8\nEndObject\nNewtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed\nNewtonsoft.Json.Serialization.JsonSerializerInternalReader\nVoid EndObject(System.Object, Newtonsoft.Json.JsonReader, Newtonsoft.Json.Serialization.JsonObjectContract, Int32, System.Collections.Generic.Dictionary`2[Newtonsoft.Json.Serialization.JsonProperty,Newtonsoft.Json.Serialization.JsonSerializerInternalReader+PropertyPresence])",
"HResult": -2146233088,
"Source": "Newtonsoft.Json",
"WatsonBuckets": null
},
"<ErrorMessage>k__BackingField": ""
}
],
"<Value>k__BackingField": null
}
}
}
Try decorating your model property with a DisplayAttribute:
public class APIVesselFilter
{
[Required]
[Display(Name="Vessel is required")]
public Guid VesselID { get; set; }
public Guid? AnchorageID { get; set; }
}
I know this is the way to customise your messages when using the Html.ValidationSummary and a quick test showed this comes up when inspecting the ModelState in an action.
using CreateErrorResponse instead of CreateResponse might solve your problem.
I faced a smiliar problem and was fixed with that.
In your case this will be
actionContext.Response = actionContext.Request.CreateErrorResponse(APIResponse.SendResponse(RequestStatus.GetValidationFailedMessage(), actionContext.ModelState));
I have an ASP.NET application and I'm trying to use ModelState.IsValid to return error messages if the query is flawed. I tried something like this:
[HttpGet]
[Route("")]
[Route("{id:int}")]
public IQueryable<ToDoTable> Get(int id = -1)
{
if (!ModelState.IsValid)
{
var errorList = (from item in ModelState
where item.Value.Errors.Any()
select item.Value.Errors[0].ErrorMessage).ToList();
return errorList.AsQueryable();
}
else
{
if (id == -1)
return db.ToDoTables;
else
return db.ToDoTables.Where(lt => lt.ID == id);
}
}
However, the problem is that errorList is of type string and the function is expecting a return type of ToDoTable, a class that I made. How do I get the correct return type? Or do I need to change the function's expectations? Do I have to add the method into the class instead (not even sure that would work)?
When ModelState.IsValid is true, the function is returning class objects with information gathered from the database I'm querying and outputs it as JSON. Example:
[
{
"ID": 11,
"Title": "this is a test",
"Description": "this is specifically to test the put method",
"Due": null,
"Completed": true
},
{
"ID": 15,
"Title": "date test",
"Description": "datetime format",
"Due": "2015-08-10T02:41:29",
"Completed": true
}
]
An approach that I have used in the past is to return an HttpResponseMessage from the method.
This will allow you to return an 'error' type (for example https://datatracker.ietf.org/doc/html/draft-nottingham-http-problem) and appropriate response code (e.g. 400).
Your function then becomes:
[HttpGet]
[Route("")]
[Route("{id:int}")]
public HttpResponseMessage Get(int id = -1)
{
if (!ModelState.IsValid)
{
var errorList = (from item in ModelState
where item.Value.Errors.Any()
select item.Value.Errors[0].ErrorMessage).ToList();
return Request.CreateResponse(HttpStatusCode.BadRequest, errorList);
}
else
{
var tables = (id == -1) ? db.ToDoTables : db.ToDoTables.Where(lt => lt.ID == id);
return Request.CreateResponse(HttpStatusCode.OK, tables);
}
}
This means that you can cope with differing response types and statuses without throwing HttpResponseExceptions.
Think from your consumers. How are you going to communicate and document this? "This API call returns a Foo, except for in cases where it returns Bar".
That is possible, but then you need to either change the return type to an IHttpActionResult or throw new HttpResponseException.
(Arguably) better would be to introduce a response container with nullable properties, something like this:
public class ApiResponse<T>
{
public bool Success { get; set; }
public int? RecordCount { get; set; }
public T Content { get; set; }
public ErrorDetail Error { get; set; }
}
This way all your operations can benefit from being statically-typed (think unit tests) and return success and failure in the same way.
Related: Handle ModelState Validation in ASP.NET Web API, Best practice to return errors in ASP.NET Web API, Storing result of Web API call into Generic type class,
For the life of me, I can't figure out how to parse the collection of device_tokens out of this using JSON.Net. I can parse out the top level collection fine, but am bombing on parsing out the device tokens in any way shape or form. Anyone have any ideas?
{
"next_page": "https://go.urbanairship.com/api/device_tokens/?start=<MY_TOKEN>&limit=2",
"device_tokens_count": 87,
"device_tokens": [
{
"device_token": "<MY_TOKEN>",
"active": false,
"alias": null,
"tags": []
},
{
"device_token": "<MY_TOKEN>",
"active": true,
"alias": null,
"tags": ["tag1", "tag2"]
}
],
"active_device_tokens_count": 37
}
Heres how you can do it using Json.NET
First create a class to represent a single device_token:
public class DeviceToken
{
public string device_token { get; set; }
public bool active { get; set; }
public object alias { get; set; }
public List<object> tags { get; set; }
}
Then using the JsonConvert class you can deserialize the json device_token array to a list of DeviceToken objects.
string json = "{\"next_page\": \"https://go.urbanairship.com/api/device_tokens/?start=07AAFE44CD82C2F4E3FBAB8962A95B95F90A54857FB8532A155DE3510B481C13&limit=2\",\"device_tokens_count\": 87,\"device_tokens\": [{\"device_token\": \"0101F9929660BAD9FFF31A0B5FA32620FA988507DFFA52BD6C1C1F4783EDA2DB\",\"active\": false,\"alias\": null,\"tags\": []},{\"device_token\": \"07AAFE44CD82C2F4E3FBAB8962A95B95F90A54857FB8532A155DE3510B481C13\",\"active\": true,\"alias\": null,\"tags\": [\"tag1\", \"tag2\"] }],\"active_device_tokens_count\": 37}";
JObject obj = JObject.Parse(json);
var deviceTokens = JsonConvert.DeserializeObject<List<DeviceToken>>(obj["device_tokens"].ToString());
I want to return a JSON from ASP.NET MVC ActionResult type method that looks something like this:
{
success: true,
users: [
{id: 1, FileName: 'Text22'},
{id: 2, FileName: 'Text23'}
]
}
How would I format it? Right now I have something like this
Return Json(New With {Key .success = "true", Key .users = responseJsonString}, JsonRequestBehavior.AllowGet)
Edit: I am using VB.NET but answers in C# are fine too.
I prefer using ViewModels, rather than manually constructing complex JSON responses. It ensures consistency against all methods that return the data, and is easier to work with strongly typed properties IMHO.
public class Response
{
public bool Success { get; set; }
public IEnumerable<User> Users { get; set; }
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
and then just:
Response response = new Response();
response.Success = true;
// populate the rest of the data
return Json(response);
This also has the advantage of letting you use a base class for every response, if there's common data like a success status, or error messages.
public class ResponseBase
{
public bool Success { get; set; }
public string Message { get; set; }
}
public class UserResponse : ResponseBase
{
IENumerable<User> Users { get; set }
}
Now, if you have an error:
return Json(new ResponseBase() { Success = false, Message = "your error" });
or if it succeeds
return Json(new UserResponse() { Success = true, Users = users });
If you want to manually craft the JSON, then just:
return Json(new { success = true, users = new[] { new { id = 1, Name = "Alice"}, new { id = 2, Name = "Bob"} } });
in C#
return Json(new
{
success = true,
users = new[]
{
new {id = 1, FileName = "Text22"}, new {id = 2, FileName = "Text23"}
}
}, JsonRequestBehavior.AllowGet);
returns
{"success":true,"users":[{"id":1,"FileName":"Text22"},{"id":2,"FileName":"Text23"}]}