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.
Related
I set up the fluent validator in my web api project and validations are working fine. But I want to create one common response structure for every API. When fluent validation getting failed I get response in BadRequest response structure like given below.
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-f3b3363d741fe7416fe40aea0ff13c36-d08e5eefbc2ff0ea-00",
"errors": {
"Code": [
"'Code' must not be empty."
],
"Name": [
"'Name' must not be empty."
]
}
}
And my domain code is like below
public class AddRegionRequestValidator : AbstractValidator<AddRegionRequest>
{
public AddRegionRequestValidator()
{
RuleFor(r=>r.Code).NotEmpty().NotNull().WithMessage("{property} is required");
RuleFor(r=>r.Name).NotEmpty().NotNull().WithMessage("{property} is required");
}
}
now I want to create one common response structure for every API where I can return isSuccessful flag, data(if present),errorList and message.
public class Response{
public bool IsSuccessful{get;set;}
public dynamic Data{get;set;}
public List<string> ErrorList{get;set;}
public string Message{get;set;}
}
but I need help to know how I can get only errors from fluent validation Bad Request structure and create custom response like "Response" class.
I want response like below.
{
isSuccessful:false,
data:null,
errorList:[
"'Code' must not be empty.",
"'Name' must not be empty."
],
message:null
}
I tried to readonly error message and add it in collection, but I failed and don't know how to create common response structure and pass fluent validation error messages string thorough list in common response structure.
.NET7 Includes a lot of improvements for System.Text.Json serializer, one of which is polymorphic serialization of types using the new [JsonPolymorphic] attribute. I am trying to use it in my Asp.Net web API, however it doesn't seem to serialize the type discriminator despite the fact that the model is properly setup.
It only happens when trying to send the objects over the wire, when using JsonSerializer, everything appears to be working well. For example:
// This is my data model
[JsonPolymorphic]
[JsonDerivedType(typeof(SuccesfulResult), typeDiscriminator: "ok")]
[JsonDerivedType(typeof(ErrorResult), typeDiscriminator: "fail")]
public abstract record Result;
public record SuccesfulResult : Result;
public record ErrorResult(string Error) : Result;
// Some test code that actually works
var testData = new Result[]
{
new SuccesfulResult(),
new ErrorResult("Whoops...")
};
var serialized = JsonSerializer.SerializeToDocument(testData);
// Serialized string looks like this:
// [{ "$type": "ok" }, { "$type": "fail", "error": "Whoops..." }]
// So type discriminators are in place and all is well
var deserialized = serialized.Deserialize<IEnumerable<Result>>()!;
// This assertion passes succesfully!
// We deserialized a collection polymorphically and didn't lose any type information.
testData.ShouldDeepEqual(deserialized);
// However, when sending the object as a response in an API, the AspNet serializer
// seems to be ignoring the attributes:
[HttpGet("ok")]
public Result GetSuccesfulResult() => new SuccesfulResult();
[HttpGet("fail")]
public Result GetFailResult() => new ErrorResult("Whoops...");
Neither of these responses are annotated with a type discriminator
and my strongly-typed clients can't deserialize the results into a proper hierarchy.
GET /api/ok HTTP/1.1
# =>
# ACTUAL: {}
# EXPECTED: { "$type": "ok" }
GET /api/fail HTTP/1.1
# =>
# ACTUAL: { "error": "Whoops..." }
# EXPECTED: { "$type": "fail", "error": "Whoops..." }
Am I missing some sort of API configuration that would make controllers serialize results in a polymorphic manner?
Specifying [JsonDerivedType(...)] on individual subclasses and on the base type seems to resolve an issue but barely seems intentional. This possibly might be fixed in future releases.
[JsonPolymorphic]
[JsonDerivedType(typeof(SuccesfulResult), typeDiscriminator: "ok")]
[JsonDerivedType(typeof(ErrorResult), typeDiscriminator: "fail")]
public abstract record Result;
[JsonDerivedType(typeof(SuccesfulResult), typeDiscriminator: "ok")]
public record SuccesfulResult : Result;
[JsonDerivedType(typeof(ErrorResult), typeDiscriminator: "fail")]
public record ErrorResult(string Error) : Result;
I am using .NET Core 3.1 to develop REST API. This is the controller that I am using (stripped down to basics just to demonstrate the issue, it returns the same data as it received):
OrdersController.cs
[Route("Payments/[controller]")]
[ApiController]
public class OrdersController : Controller
{
[HttpPost]
[Route("AddOrder")]
public IActionResult AddOrder(Order order)
{
return Json(order);
}
}
Order.cs
public class Product
{
[Required]
public string Manufacturer { get; set; }
[Required]
public string Code { get; set; }
}
public class Order
{
[Required]
public string Recipient { get; set; }
[Required]
public Product Product { get; set; }
}
When I call Payments/Orders/AddOrder with Postman with the following body (notice empty nested Code field):
{
"Recipient": "John Doe",
"Product": {
"Manufacturer": "Company Inc.",
"Code": ""
}
}
... I get the following error which is expected since Code is annotaded with [Required]:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|7558e077-4f9147019767a0cf.",
"errors": {
"Product.Code": [
"The Code field is required."
]
}
}
However, if I try to validate the Order object with the same data in one of the services, it doesn't detect that field Code is empty.
// manually initialized order with same data as in Postman request
Order order = new Order()
{
Recipient = "John Doe",
Product = new Product()
{
Manufacturer = "Company Inc.",
Code = string.Empty
}
};
ValidationContext context = new ValidationContext(order, serviceProvider: null, items: null);
var validationResults = new List<ValidationResult>();
bool isValid = Validator.TryValidateObject(order, context, validationResults, true);
Here, isValid is true which means that object is valid. How can it be valid if Code is empty? Why does controller automatically detect that nested property is invalid but Validator.TryValidateObject doesn't? Does controller validation work recursively and Validator.TryValidateObject does not? Can I use the same recursive (nested fields) validation that controller uses somewhere else in the code?
EDIT: Why do we even want to validate the object on the service layer?
We developed a shared project to be used in other solutions. It calls REST API with correctly formatted payload and headers. We want to validate object inside shared project's code (service) before it is even sent to REST API server. We want to avoid situations where request is sent out by the shared client but is then rejected at REST API's server.
I'm creating an ASP.net Core Web API (.net 5) to serve data to a Single Page Application (SPA). I'm using DataAnnotations (System.ComponentModel.DataAnnotations) to carry out model state validation in a very standard way in my Controller. I want to ensure my error responses come back in a very consistent manner (and are translatable in the front end!).
MVC Controller
Example controller is as follows:
[ApiController, Route("[controller]")]
public class AgencyController : Controller
{
private readonly IOptions<ApiBehaviorOptions> _apiBehaviorOptions;
public AgencyController(IOptions<ApiBehaviorOptions> apiBehaviorOptions)
{
_apiBehaviorOptions = apiBehaviorOptions;
}
[HttpPost]
[ProducesResponseType(typeof(void), (int) HttpStatusCode.OK)]
[ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)]
public IActionResult Create([FromBody] ExampleCreateModel createModel)
{
// If the email already exists, add the custom error.
var emailExists = EmailAlreadyExists(createModel.Email);
if (emailExists)
{
ModelState.AddModelError("Email", "Email already exists");
return _apiBehaviorOptions.Value.InvalidModelStateResponseFactory(ControllerContext);
}
// Return some relevant status...
return Ok();
}
// Real verification implementation would go here....
private bool EmailAlreadyExists(string email) => true;
}
This sample code above demonstrates a controller that takes takes the POCO model (shown below) and if it passes the attribute validation, carries out additional inline validation to ensure the email address doesn't already exist (pseudo code for the email exists check).
Sample Model
public class ExampleCreateModel
{
[Required]
public string Name { get; set; } = "";
[Required]
[EmailAddress(ErrorMessage = "Invalid email format")]
public string Email { get; set; } = "";
}
Error Response Format
Validation failure on the model shown above yields the standard error object from the MVC app in this format (sample):
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-6b30709ed71b7347afd41e0ed58e1ccb-e3ede2ff7591a24d-00",
"errors": {
"Email": [
"Email already exists"
]
}
}
this format is consistent for both regular attribute validation and the custom inline validation in the code above
My Question
The advantage of using the built in errors is consistency (and follows a standard pattern). BUT one down side for me is that I'd like to return error codes as well as text as part of the error object, so the UI can translate them but it doesn't seem to easily support that i.e. an "Email already exists" error could be a 3001 error and the UI could show a 3001 in various languages.
Is there any standard way to use the existing DataAnnotation attributes to include additional information? Such that the POCO model would become something like this:
public class ExampleCreateModel
{
[Required(ErrorCode = 3000)]
public string Name { get; set; } = "";
[Required]
[EmailAddress(ErrorMessage = "Invalid email format", ErrorCode = 3001)]
public string Email { get; set; } = "";
}
Resulting in an error object similar to this:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-6b30709ed71b7347afd41e0ed58e1ccb-e3ede2ff7591a24d-00",
"errors": {
"Email": [
{ "message": "Email already exists", "errorCode": 3002 }
]
}
}
Just to restate for clarity - my aim here is to give the UI an opportunity to easily translate error code, while achieving error consistency using the out-of-the-box error response.
Thanks in advance for any pointers!
Is there any standard way to use the existing DataAnnotation attributes to include additional information?
No.
I think that the simplest (but pretty hacky) approach could be to include the error code in the message string itself and then extract it client-side:
public class ExampleCreateModel
{
// ...
[Required]
[EmailAddress(ErrorMessage = "ErrorCode:3001 - Invalid email format")]
public string Email { get; set; } = "";
}
You could even go one step further and put a serialized JSON object in there to make it more structured (but pay attention to escaping). Of course you should centralize the logic of creating such 'custom' error messages into some kind of static utilty class in order to make it consistent throughout your application.
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>();