Web api - DataAnnotations: optional properties, require the rest if one is present - c#

I am writing a web api application, where i need to deserialize json from a POST request into a DTO that looks like the following
public class UserDto
{
[Required(AllowEmptyStrings = false, ErrorMessage = "User.Email is required")]
[StringLength(128, ErrorMessage = "The email length cannot exceed 128 characters.")]
[EmailAddress]
public string Email { get; set; } //Always required
public string UserId { get; set; }
public string UserType { get; set; }
}
The rules is as following: Email is always required, UserId/UserType is optional, but they are linked, as in; if one of the two is present in the json the other property is required to be there.
Currently we have this handled in code in our service layer, but i would like to move this into the model, with DataAnnotations, like [Required]. This way its validated before it reaches the controller and i don't have to make try/catch blocks when invoking my service from the controllers
The json format we accept is as following:
{
"User": {
"Email": "example#email.com",
"UserId": "userId", //Optional, but required if 'UserType' is present
"UserType": "type" //Optional, but required if 'UserId' is present
}
}
Is there a way to achieve this with data annotation?
Ideally i would like to simply link the two properties together. I was thinking of creating an object for the two properties like UserData and have my json (de)serializer unwrap the object so we still accept the same format, and only our DTO have to change. (i know this is possible with fx. jackson in java). But it would be even cooler if it was possible with my current class-structure, with something like [RequireIf(link = anotherProperty)] annotation.

Related

Parsing Json array to C# Object

I'm building a .NET WebAPI that receives Json through a Post operation. The Json that's being received could look like the following:
{
"site": "00131231201d010231",
"publishTime": 123123123123,
"domains": [
"example.com"
],
"publishedBy": {
"name": "John Doe",
"id": "00211231201d010231"
}
}
I converted my Json response type to C# objects which look like the following:
public class Project
{
[Key]
[JsonPropertyName("site")]
public string Site { get; set; }
[JsonPropertyName("publishTime")]
public long PublishTime { get; set; }
[JsonPropertyName("domains")]
public List<Domain> Domains { get; set; }
[JsonPropertyName("publishedBy")]
public PublishedBy PublishedBy { get; set; }
}
public class PublishedBy
{
[JsonPropertyName("name")]
public string Name { get; set; }
[Key]
[JsonPropertyName("id")]
public string Id { get; set; }
}
public class Domain
{
[Key]
public string Id { get; set; }
public string Name { get; set; }
}
As you can see, my goal is to add the contents to my database. Only when I use List Domains, it gives me an error saying I can't use strings in EFCore when I try to add a migration.
So, I created an object called Domain. But now when I try to deserialize it gives me the following error:
System.Text.Json.JsonException: The JSON value could not be converted to spine_management.Models.Domain.
Does anyone happen to know what type I should make Domains and/or what the best way to deserialize this object is?
EDIT:
I want to keep the domains attribute, I don't want to ignore or delete them.
It's not uncommon to split between two different model structures for interacting with different infrastructure points. In this case your infrastructure points are:
Deserializing JSON input
Persisting data with EF
You can treat the JSON input like a "view model". It's not your core model which maps to EF database entities, but rather just an anemic DTO for deserializing data. For that model, Domains is simply a list of strings:
[JsonPropertyName("domains")]
public List<string> Domains { get; set; }
This view model is local to the application layer, not part of the core domain. Within the application logic, after deserializing the input, you can map it to the domain object. That's where you would translate the list of simple strings into a list of Domain objects. (And translate back in any output operations.)
As long as the mapping logic (which might be made simple by using tools like AutoMapper, though in this case the logic is pretty straightforward and doesn't really necessitate adding more tools) is encapsulated within that application layer, it won't pollute the rest of the domain logic.
Though it may certainly be possible to configure one or both of these tools to work together more smoothly, I often find that a simple translation layer between dependency-specific DTOs and core domain models is much simpler to build and maintain.
I currently use Newtonsoft JSON for serialize and deserializing. I think the reason for this error is you wrote named the string in domain "Name", but it has to be "name". Hope it works!

Model Binding ignoring properties that have the JsonIgnore attribute

I'm building a web api microservice using Core 3. I have a class defined as follows:
public class UserSourceList
{
[JsonIgnore]
public string UserId { get; set; }
[JsonIgnore]
public string ListId { get; set; }
public string Name { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public ListTypes ListType { get; set; }
public List<string> Ids { get; set; }
public DateTimeOffset CreationTime { get; set; }
}
When the framework attempts to bind the data provided by a HTTP PUT, it will not populate the UserId and ListId fields. As a result, model binding is failing during validation and returning a HTTP 400, stating that UserId and ListId are required.
The controller's action method is defined as follows:
[HttpPut("{userId:userid}/{listId:listid}", Name = "ReplaceUserList")]
public ActionResult Replace(string userId, string listId, UserSourceList model)
{
return Ok(_listManager.ReplaceUserList(model.UserId, model.ListId, model));
}
A typical call to the API would look similar to this:
PUT /api/v1/listmgmt/abc123def456/c788f2f7b7984424910726d4a290be26
PUT Body
{
"name": "Test",
"listType": "Eans",
"ids": ["97814571867716", "9781430257615", "9780982550670"],
"userId":"abc123def456",
"listId":"c788f2f7b7984424910726d4a290be26"
}
If I removed the JsonIgnore Attribute from the UserId and ListId properties of the model, everything binds as expected.
Is it expected behavior that model binding will ignore fields flagged with JsonIgnore?
I know I can work around it by changing how my validation code works or I can split my model. I would like to understand the current behavior as it is different from what I expected and experienced with ASP.NET MVC 4 and WebApi 2.
Thanks
Short answer, Newtonsoft Json.Net is being used to deserialize the post/put body when the content type is application/json. Therefore, the userId and listId parameters are being ignored during deserialization, but evaluated during model validation.
I removed the JsonIgnore Attribute as well as all the Data Annotations, and changed to the FluentValidation package which provided the ability at runtime to configure how the body should be validated based up the type of call made.
I think the reason is because of this:
[HttpPut("{userId:userid}/{listId:listid}", Name = "ReplaceUserList")]
userId and listId are required and cannot be ignored because they are defined in the annotation HttpPut. I think you need to remove them from HttpPut's parameters and find another way to get around this.
Hope this helps!

Return only a subset of properties of an object from an API

Say I have a database in which I am storing user details of this structure:
public class User
{
public string UserId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string PasswordHash { get; set; }
}
I have a data access layer that works with this that contains methods such as GetById() and returns me a User object.
But then say I have an API which needs to return a users details, but not sensitive parts such as the PasswordHash. I can get the User from the database but then I need to strip out certain fields. What is the "correct" way to do this?
I've thought of a few ways to deal with this most of which involve splitting the User class into a BaseClass with non sensitive data and a derived class that contains the properties I would want kept secret, and then converting or mapping the object to the BaseClass before returning it, however this feels clunky and dirty.
It feels like this should be a relatively common scenario, so am I missing an easy way to handle it? I'm working with ASP.Net core and MongoDB specifically, but I guess this is more of a general question.
It seems for my purposes the neatest solution is something like this:
Split the User class into a base class and derived class, and add a constructor to copy the required fields:
public class User
{
public User() { }
public User(UserDetails user)
{
this.UserId = user.UserId;
this.Name = user.Name;
this.Email = user.Email;
}
public string UserId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
public class UserDetails : User
{
public string PasswordHash { get; set; }
}
The data access class would return a UserDetails object which could then be converted before returning:
UserDetails userDetails = _dataAccess.GetUser();
User userToReturn = new User(userDetails);
Could also be done using AutoMapper as Daniel suggested instead of the constructor method. Don't love doing this hence why I asked the question but this seems to be the neatest solution and requires the least duplication.
There are two ways to do this:
Use the same class and only populate the properties that you want to send. The problem with this is that value types will have the default value (int properties will be sent as 0, when that may not be accurate).
Use a different class for the data you want to send to the client. This is basically what Daniel is getting at in the comments - you have a different model that is "viewed" by the client.
The second option is most common. If you're using Linq, you can map the values with Select():
users.Select(u => new UserModel { Name = u.Name, Email = u.Email });
A base type will not work the way you hope. If you cast a derived type to it's parent type and serialize it, it still serializes the properties of the derived type.
Take this for example:
public class UserBase {
public string Name { get; set; }
public string Email { get; set; }
}
public class User : UserBase {
public string UserId { get; set; }
public string PasswordHash { get; set; }
}
var user = new User() {
UserId = "Secret",
PasswordHash = "Secret",
Name = "Me",
Email = "something"
};
var serialized = JsonConvert.SerializeObject((UserBase) user);
Notice that cast while serializing. Even so, the result is:
{
"UserId": "Secret",
"PasswordHash": "Secret",
"Name": "Me",
"Email": "something"
}
It still serialized the properties from the User type even though it was casted to UserBase.
If you want ignore the property just add ignore annotation in you model like this, it will skip the property when model is serializing.
[JsonIgnore]
public string PasswordHash { get; set; }
if you want ignore at runtime(that means dynamically).there is build function avilable in Newtonsoft.Json
public class User
{
public string UserId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string PasswordHash { get; set; }
//FYI ShouldSerialize_PROPERTY_NAME_HERE()
public bool ShouldSerializePasswordHash()
{
// use the condtion when it will be serlized
return (PasswordHash != this);
}
}
It is called "conditional property serialization" and the documentation can be found here. hope this helps
The problem is that you're viewing this wrong. An API, even if it's working directly with a particular database entity, is not dealing with entities. There's a separation of concerns issue at play here. Your API is dealing with a representation of your user entity. The entity class itself is a function of your database. It has stuff on it that only matters to the database, and importantly, stuff on it that does not matter to your API. Trying to have one class that can satisfy multiple different applications is folly, and will only lead to brittle code with nested dependencies.
More to the point, how are you going to interact with this API? Namely, if your API exposes your User entity directly, then any code that consumes this API either must take a dependency on your data layer so it can access User or it must implement its own class representing a User and hope that it matches up with what the API actually wants.
Now imagine the alternative. You create a "common" class library that will be shared between your API and any client. In that library, you define something like UserResource. Your API binds to/from UserResource only, and maps that back and forth to User. Now, you have completely segregated your data layer. Clients only know about UserResource and the only thing that touches your data layer is your API. And, of course, now you can limit what information on User is exposed to clients of your API, simply by how you build UserResource. Better still, if your application needs should change, User can change without spiraling out as an API conflict for each consuming client. You simply fixup your API, and clients go on unawares. If you do need to make a breaking change, you can do something like create a UserResource2 class, along with a new version of your API. You cannot create a User2 without causing a whole new table to be created, which would then spiral out into conflicts in Identity.
Long and short, the right way to go with APIs is to always use a separate DTO class, or even multiple DTO classes. An API should never consume an entity class directly, or you're in for nothing but pain down the line.

JSON.NET - Ignore properties for serialization

I am currently developing a ASP.NET WebAPI using JSON.NET.
I am looking to reduce traffic and want to ignore certain properties of my models for serialization, i.e. I don't want to return them in my JSON response, but I want to accept them when they are passed to my endpoint.
Example class
public class User {
public int Id { get; set; }
public string Name { get; set; }
public string Role { get; set; }
}
Use-cases
I've got a POST endpoint that takes a User model as a parameter. The request contains Name and Role. Those props should be parsed into my User.
I've got a GET endpoint that returns a User. I only want the response to contain Id and Name. Role should be ignored.
Problem
When I use the JsonIgnore attribute from JSON.NET, the property is ignored entirely. It is not serialized for my response, but the prop of my User is null, when I post the JSON User to my endpoint.
Is there a way to ignore a prop only for serialization?
Thank you in advance!
That's exactly what Data Transfer Objects are for. You should create different DTOs for different purposes (GET/POST).
Use this and pass null when you want to ignore it!
[JsonProperty("property_name", NullValueHandling=NullValueHandling.Ignore)]

WebAPI Validation DataAnnotation creates runtime exception [HttpStatusCode 500]

I can't seems to find anything related to this issue on Google.
Please help !!
SCENARIO:
Mainly I have a WebAPI server with a controller method that expects a simple type as parameter.
That API looks like this:
public HttpResponseMessage Foo([FromBody] LoginModel form)
{
// ...some code
return this.Request.CreateResponse(HttpStatusCode.OK);
}
and the LoginType class looks like this:
public class LoginModel
{
[Required]
[EmailAddress(ErrorMessage = "Please have a Email address format")]
public string Email;
[Required]
[StringLength(20, MinimumLength = 6, ErrorMessage = "Password must be between 6 and 20 characters")]
public string Password;
}
Problem occurs when the Client tries to run the API method. I pass a json that looks like this
{ "Email" : "xxx#xxx.com" , "Password" : "oooooo" }
....I get the following exception
EXCEPTION MESSAGE:
"Field 'Email' on type 'XXXX.Models.Login' is attributed with one or more validation attributes. Validation attributes on fields are not supported. Consider using a public property for validation instead.
The same happens when I ran the api call from Fiddler !!
NOTE:
If I remove the various Attributes like [Required], it works smoothly. The client call never gets to the method when the Attributes are in place.
Help is truly appreciated !!!
Like it says:
Validation attributes on fields are not supported. Consider using a public property for validation instead.
So use properties;
public string Email { get; set; }

Categories

Resources