ASP.NET Core: validation attributes are ignored on fields defined in children types - asp.net-core

My ASP.NET Core 2.1 API exposes the following input DTO in a POST endpoint:
[Route("test")]
[ApiController]
public class TestController : ControllerBase
{
[HttpPost("endpoint")]
public async Task<IActionResult> Post([Required]MyDTO dto)
{
// Some code
}
}
public class MyDTO
{
[JsonProperty("foo")]
[Required]
public Foo Foo { get; set; }
}
The Foo class is defined as follow:
[JsonConverter(typeof(FooConverter))]
public abstract class Foo
{
[JsonProperty("foo_type")]
[Required]
public string FooType { get; set; }
}
The FooConverter class is able to instantiate the right implementation based on the foo_type field:
public class FooConverter : JsonConverter<Foo>
{
public override bool CanRead => true;
public override bool CanWrite => false;
public override Foo ReadJson(JsonReader reader, Type objectType, Foo existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
Foo target = this.CreateFoo(jObject);
serializer.Populate(jObject.CreateReader(), target);
return target;
}
public override void WriteJson(JsonWriter writer, Foo value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
private Foo CreateFoo(JObject jObject)
{
string fooType = jObject.Value<string>("foo_type");
switch (fooType)
{
case "foo1":
return new Foo1();
case "foo2":
return new Foo2();
default:
throw new JsonSerializationException($"Invalid 'foo_type' '{fooType}'");
}
}
}
Here is one of the implementations of the Foo abstract class:
public class Foo1 : Foo
{
[JsonProperty("bar")]
[Required]
public string Bar { get; set; }
}
My problem is that the [Required] attribute on Foo1.Bar is ignored by ASP.NET validation, even though the [Required] attribute on Foo.FooType works as expected. How can I automatically validate the fields defined in the implementation types so that it works the same as with other fields?

Replace:
var jObject = JObject.Load(reader);
with:
JToken jObject = JToken.ReadFrom(reader);

Related

How to use Patch method in minimal api application?

I am trying to use patch method in my minimal api application this is my code :
Car class
public class Car
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "producent")]
public Producent Producent { get; set; }
[JsonProperty(PropertyName = "age")]
public int Age { get; set; }
[JsonProperty(PropertyName = "yearCreated")]
public int YearCreated { get; set; }
[JsonProperty(PropertyName = "engine")]
public Engine Engine { get; set; }
public Car()
{
Id= Guid.NewGuid().ToString();
YearCreated = DateTime.Now.Year - Age;
}
}
ICarService:
public interface ICarService
{
Task<IEnumerable<Car>> GetAll();
Task<Car> GetById(string id,string partitionKey);
Task Create(Car car);
Task<bool> Update(Car car);
Task<Car> UpdatePatchAsync(string id, string partitionKey,List<PatchOperation> patchOperations);
Task<bool> Delete(string id,string partitionKey);
}
patch method in service
public async Task<Car> UpdatePatchAsync(string id, string partitionKey, List<PatchOperation> patchOperations)
{
var result = await _container.PatchItemAsync<Car>(id, new PartitionKey(partitionKey),
patchOperations:patchOperations );
return result;
}
my requests:
[HttpPatch]
public static async Task<IResult> Patch(ICarService service,string id,string partitionKey,
[FromBody]List<PatchOperation> operations)
{
var updatedCar = await service.UpdatePatchAsync(id,partitionKey,operations);
if (updatedCar == null)
{
return Results.NotFound();
}
return Results.Ok(updatedCar);
}
app.MapMethods("/cars/patch/{id}/{partitionKey}", new string[] { "PATCH" }, CarRequests.Patch);
I use cosmosDb database and when i code like this:
{
"op": "replace",
"path": "age",
"value": 22
}
i get the error
System.NotSupportedException: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'Microsoft.Azure.Cosmos.PatchOperation'. Path: $[0] | LineNumber: 3 | BytePositionInLine: 3.
---> System.NotSupportedException: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'Microsoft.Azure.Cosmos.PatchOperation'.
The problem is you are using [FromBody]List<PatchOperation> operations.
This is making ASP.NET attempt to deserialize a JSON raw string into PatchOperation, which when we look at our source code, has no parameterless constructor: https://github.com/Azure/azure-cosmos-dotnet-v3/blob/master/Microsoft.Azure.Cosmos/src/Patch/PatchOperation.cs, it is an abstract class.
The way it is meant to be used is through PatchOperation.XXXX, like PatchOperation.Add, you cannot use it to deserialize into it. You could have your own MyPatchOperation<Car> that has the exposed properties for deserialization, and then simply have a method in that class that based on which properties are populated, calls PatchOperation<Car>.Add (or whatever operation).
Not the full code but:
public class MyPatchOperation
{
[JsonProperty(PropertyName = "op")]
public string Operation { get; set; }
[JsonProperty(PropertyName = "path")]
public string Path { get; set; }
[JsonProperty(PropertyName = "value")]
public string Value { get; set; }
public PatchOperation<Car> ToPatchOperation()
{
switch (this.Operation)
{
case "replace":
return PatchOperation<Car>.Replace(this.Path, this.Value);
/* other operations */
}
}
}

Extending EF Core Identity UserStore with integer user ID

Using Blazor server, dotnet 5 and Entity Framework Core, I have successfully customised Identity using integer IDs
e.g
public class ApplicationUser : IdentityUser<int>
{
[Required]
[MaxLength(50)]
public string FirstName { get; set; }
[Required]
[MaxLength(50)]
public string LastName { get; set; }
}
I now want to extend UserStore for custom login to store password history to prevent duplication. The documented way of doing this seems to be:
public class ApplicationUserStore : UserStore<ApplicationUser>
{
public ApplicationUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
{
}
public override async Task<IdentityResult> CreateAsync(ApplicationUser appUser)
{
await base.CreateAsync(appUser);
await AddToUsedPasswordAsync(appUser, appUser.PasswordHash);
}
public Task AddToUsedPasswordAsync(ApplicationUser appuser, string userpassword)
{
appuser.UserUsedPassword.Add(new UsedPassword() { UserID = appuser.Id, HashPassword = userpassword });
return UpdateAsync(appuser);
}
}
This works for the default GUID Id but when using an integer it throws the error:
The type ApplicationUser cannot be used as type parameter 'TUser' in the generic type or method 'UserStore'. There is no implicit reference conversion from ApplicationUser' to 'Microsoft.AspNetCore.Identity.IdentityUser'.
What is the correct way to do this?
EDIT:
Per #Yinqiu, modified for custom ApplicationRole to
public class ApplicationUserStore : UserStore<ApplicationUser, ApplicationRole, ApplicationDbContext, int>
{
public ApplicationUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
{
}
Builds successfully and creates users but gives run time error when trying to access ApplicationUserManager.IsInRoleAsync:
{"Cannot create a DbSet for 'IdentityUserRole' because this type is not included in the model for the context."}
However I have a custom User Role:
public class ApplicationUserRole : IdentityUserRole<int>
{
public ApplicationUserRole() : base() { }
public bool IsSystemEssential { get; set; }
}
With a definition in ApplicationDbContext :
public DbSet ApplicationUserRoles { get; set; }
SOLVED (provisionally)
After looking at the overloaded inheritance options you have to extend as follows:
public class ApplicationUserStore : UserStore<ApplicationUser, ApplicationRole, ApplicationDbContext, int, ApplicationUserClaim, ApplicationUserRole, ApplicationUserLogin, ApplicationUserToken, ApplicationRoleClaim>
{
public ApplicationUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
{
}
}
This seems to work. Still need to check all role and claim updates.
You can change your ApplicationUserStore as following.
public class ApplicationUserStore : UserStore<ApplicationUser,IdentityRole<int>, ApplicationDbContext,int>
{
public ApplicationUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
{
}
//...
}

exception:"type was not mapped" in entityframework codefirst with layers

i'm trying to apply LAYERS Concept on demo project developed using mvc and entity framework both
Data Annotations : for validations in Data Access Layer and
Fluent API : for mapping and tables relations
Problem : DbContext didn't Create DB and there is a Runtime Exception :
The type 'Domain.DataLayer.Member' was not mapped. Check that the type has not been explicitly excluded by using the Ignore method or NotMappedAttribute data annotation. Verify that the type was defined as a class, is not primitive, nested or generic, and does not inherit from EntityObject.
Code : my solutions consists of :
1- class library (Domain.Classes project): where i wrote all of my classes
public class Member
{
public int Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string FullName { get; set; }
}
2- DAL (Domain.DataLayer project): also another class library and i referenced domain.classes
namespace Domain.DataLayer.Repositories
{
[MetadataType(typeof(MemberMetadata))]
public partial class Member : Classes.Member , IValidatableObject
{
public Member()
{
Tasks = new HashSet<Task>();
History = new HashSet<Commint>();
}
public string ConfirmPassword { get; set; }
public HashSet<Task> Tasks { get; set; }
public HashSet<Commint> History { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var result = new List<ValidationResult>();
if (!string.Equals(Password,ConfirmPassword))
{
result.Add(new ValidationResult("mismatch pwsd", new[] {"ConfirmPassword" }));
}
return result;
}
}
}
and i used repository pattern :
public class MemberRepository : IRepository<Member>
{
public Task<IQueryable<Member>> GetAllEntities()
{
return Task<IQueryable<Member>>.Factory.StartNew(() => new Context().Members.AsQueryable());
}
}
3-BLL : for sake of simplicity : there is no Business Logic Layer
4- PL (Domain.Application MVC Project) : Member Controller :
public async Task<ActionResult> Index()
{
var members = await _repository.GetAllEntities();
return View(members);
}
Note : i depended on DbContext to create DB with name like : Domain.DataLayer.Context but it didn't craete DB so i created the DB and passed the connectionString through Context constructor like this :
namespace Domain.DataLayer
{
public class Context : DbContext
{
public Context(): base("InterviewDemo") // i tried also base("name=InterviewDemo")
{
}
public DbSet<Member> Members { get; set; }
public DbSet<Task> Tasks { get; set; }
public DbSet<Commint> TaskHistory { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new MemberConfig());
modelBuilder.Configurations.Add(new TaskConfig());
modelBuilder.Configurations.Add(new CommintConfig());
base.OnModelCreating(modelBuilder);
}
}
}

WCF possibly serialization related issue

On server I have the following class
public class A
{
string a1 {get; set ;}
string a2 {get; set;}
}
I have defined a service with the following operation contract
[OperationContract]
public list<A> GetAll()
{
return new List<A> {new A {a1="1", a2="2"}, new A{a1="3", a2="4"}};
}
in the reference there is defined a shallow copy of A in the following way
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="GetAll", Namespace="http://schemas.datacontract.org/2004/07/SomeModel")]
[System.SerializableAttribute()]
public partial class A: object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged {
[System.NonSerializedAttribute()]
private System.Runtime.Serialization.ExtensionDataObject extensionDataField;
[System.Runtime.Serialization.OptionalFieldAttribute()]
private string A1Field;
[System.Runtime.Serialization.OptionalFieldAttribute()]
private string A2Field;
[global::System.ComponentModel.BrowsableAttribute(false)]
public System.Runtime.Serialization.ExtensionDataObject ExtensionData {
get {
return this.extensionDataField;
}
set {
this.extensionDataField = value;
}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public string A1{
get {
return this.A1Field;
}
set {
if ((object.ReferenceEquals(this.A1Field, value) != true)) {
this.A1Field= value;
this.RaisePropertyChanged("A1");
}
}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public string A2{
get {
return this.A2Field;
}
set {
if ((object.ReferenceEquals(this.A2Field, value) != true)) {
this.A2Field= value;
this.RaisePropertyChanged("A2");
}
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) {
System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if ((propertyChanged != null)) {
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
On the client I call the service.GetAll() and I use the shallow copy of A, defined in the proxy which defines my model for a view. the call is something similar to
ActionResult GetAll()
{
List<A> allAs = service.GetAll();
return new View (allAs);
}
However the list on the client is always emtpy. What is the problem?
You should define your data class, A as a datacontract:
[DataContract]
public class A
{
[DataMember]
public string a1 {get; set ;}
[DataMember]
public string a2 {get; set ;}
}
On the class you will need DataContract attribute from System.Runtime.Serialization.DataContractAttribute namespace.
Some thing like this
[DataContract]
public class A
{
[DataMember]
public string a1 {get; set ;} //This should be public
[DataMember]
public string a2 {get; set;}//This should be public
}
Read more on MSDN

JSON.Net - DeserializeObject Format

I'm using JSON.Net to try and deserialize some survey responses from SurveyGizmo.
Here's a snapshot of the data I'm reading in:
{"result_ok":true,
"total_count":"44",
"page":1,
"total_pages":1,
"results_per_page":50,
"data":[
{"id":"1",
"contact_id":"",
"status":"Complete",
"is_test_data":"0",
"datesubmitted":"2011-11-13 22:26:53",
"[question(59)]":"11\/12\/2011",
"[question(60)]":"06:15 pm",
"[question(62)]":"72",
"[question(63)]":"One",
"[question(69), option(10196)]":"10",
I've setup a class as far as datesubmitted but I'm not sure how to setup the class to deserialize the questions given that the amount of questions will change? I also need to capture the option if it's present.
I'm using this code to use the JSON.NET Deserialize function:
Dim responses As Responses = JsonConvert.DeserializeObject(Of Responses)(fcontents)
Classes:
Public Class Responses
Public Property result_OK As Boolean
Public Property total_count As Integer
Public Property page As Integer
Public Property total_pages As Integer
Public Property results_per_page As Integer
Public Overridable Property data As List(Of surveyresponse)
End Class
Public Class SurveyResponse
Public Property id As Integer
Public Property status As String
Public Property datesubmitted As Date
End Class
This trick to support totally crazy mappings is to use JsonConverter and completely replace the parsing for that object, (I apologize for the C#, but I'm no good at VB syntax):
class Program
{
static void Main(string[] args)
{
var result = JsonConvert.DeserializeObject<Responses>(TestData);
}
const string TestData = #"{""result_ok"":true,
""total_count"":""44"",
""page"":1,
""total_pages"":1,
""results_per_page"":50,
""data"":[
{""id"":""1"",
""contact_id"":"""",
""status"":""Complete"",
""is_test_data"":""0"",
""datesubmitted"":""2011-11-13 22:26:53"",
""[question(59)]"":""11\/12\/2011"",
""[question(60)]"":""06:15 pm"",
""[question(62)]"":""72"",
""[question(63)]"":""One"",
""[question(69), option(10196)]"":""10"",
}]}";
}
[JsonObject]
class Responses
{
public bool result_ok { get; set; }
public string total_count { get; set; }
public int page { get; set; }
public int total_pages { get; set; }
public int results_per_page { get; set; }
public SurveyResponse[] Data { get; set; }
}
[JsonObject]
// Here is the magic: When you see this type, use this class to read it.
// If you want, you can also define the JsonConverter by adding it to
// a JsonSerializer, and parsing with that.
[JsonConverter(typeof(DataItemConverter))]
class SurveyResponse
{
public string id { get; set; }
public string contact_id { get; set; }
public string status { get; set; }
public string is_test_data { get; set; }
public DateTime datesubmitted { get; set; }
public Dictionary<int, string> questions { get; set; }
}
class DataItemConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(SurveyResponse);
}
public override bool CanRead
{
get { return true; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = (SurveyResponse)existingValue;
if (value == null)
{
value = new SurveyResponse();
value.questions = new Dictionary<int, string>()
}
// Skip opening {
reader.Read();
while (reader.TokenType == JsonToken.PropertyName)
{
var name = reader.Value.ToString();
reader.Read();
// Here is where you do your magic
if (name.StartsWith("[question("))
{
int index = int.Parse(name.Substring(10, name.IndexOf(')') - 10));
value.questions[index] = serializer.Deserialize<string>(reader);
}
else
{
var property = typeof(SurveyResponse).GetProperty(name);
property.SetValue(value, serializer.Deserialize(reader, property.PropertyType), null);
}
// Skip the , or } if we are at the end
reader.Read();
}
return value;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Now obviously there's a lot more you would want to do to get this really robust, but this gives you the basics of how to do it. There are more lightweight alternatives if you simply need to change property names (either JsonPropertyAttribute or overriding DefaultContractResolver.ResolvePropertyName(), but this gives you full control.