Model Validation using Fluent Validation on WEB API - c#

I am trying to use fluent validation for my models on Web API project. I have two classes name OrderHeader and Items . And i have property name as OrderQty on Items class which is an integer and i have applied a rule for OrderQty as it should be number only (i.e. 0-9) . whenever i get the JSON request for OrderQty as alphanumeric (like 1A) i cannot serialize the JSON and could not get the errormessage from fluent validation on Modelstate . How to achieve this could someone help me on this please ? Thanks in advance !!!
I have tried to convert the OrderQty to ToString() and applied rule but i could not get the errormessage while serialiaing the JSON .
My modal classes :
public class OrderHeader
{
public string OrderNumber { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public string Phone { get; set; }
public string Created { get; set; }
public List<Items> Items { get; set; }
}
public class Items
{
public string ItemNumber { get; set; }
public string Description { get; set; }
public int OrderQty { get; set; }
public double Weight { get; set; }
public double Price { get; set; }
public string Status { get; set; }
}
Fluent Validations
public class OrderHeaderValidator : AbstractValidator<OrderHeader>
{
public OrderHeaderValidator()
{
RuleFor(x => x.OrderNumber.Trim()).NotEmpty().WithMessage("OrderNumber : cannot be blank.").Length(1, 6).WithMessage("OrderNumber : cannot be more than 6 characters.").Matches("^[0-9]*$").WithMessage("OrderNumber : must contains only numbers");
RuleFor(x => x.Items).SetCollectionValidator(new ItemValidator());
}
}
public class ItemsValidator : AbstractValidator<Items>
{
public ItemsValidator()
{
RuleFor(x => x.OrderQty.ToString()).NotNull().WithMessage("TotalOrderQuantity : cannot be blank").Matches("^[0-9]*$").WithMessage("TotalOrderQuantity : must contains only numbers");
RuleFor(x => x.Status.ToUpper()).NotEmpty().WithMessage("Status : Provide the Status").Length(0, 1).WithMessage("Status : cannot be more than 1 character").Matches("O").WithMessage("Status : Must be 'O'");
}
}
Serializing and getting error message :
string errors = JsonConvert.SerializeObject(ModelState.Values
.SelectMany(state => state.Errors)
.Select(error => error.ErrorMessage));
I expect the output should be if the value for is 1A from JSON request then it displays the error message as:
TotalOrderQuantity : must contains only numbers

You can't deserialize 1A into int OrderQty. Use string OrderQty instead and check .Must(x => int.TryParse(x.OrderQty, out _)) in validator.
It's OK that your API has specification like OrderQty must be integer - int OrderQty. If someone tries to send string instead of integer - you can catch deserializion exception and reject the request with message like invalid request: ...

Related

How to make out of three property only two can be null in C#

I am new to asp.net core web api. I want to make announcement module for my school project. What i want to make is that admins can send announcement based on role or target user or target section. when the request come it must contain at least one not null, How can I achieve this in the class property.
public class Announcement
{
public int Id { get; set; }
public string Title { get; set; }
public string Detail { get; set; }
public DateTime Date { get; set; }
public string? Attachment { get; set; }
public AppUser Poster { get; set; }
public string PosterUserId { get; set; }
public AppUser? Receiver { get; set; }
public string? ReceiverUserId { get; set; }
public Section? Section { get; set; }
public int? SectionId { get; set; }
public IdentityRole? Role { get; set; }
public string? RoleId { get; set; }
}
I can achieve this in the controller but, Is there any method I can use so that the model binder will reject it if three of them are null.
Going by the documentation #Rand Random linked. You want to make your class an IValidatableObject and then implement the Validate method. Something like this:
public class Announcement : IValidatableObject
{
...
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (IdentityRole == null && Section == null && Receiver == null)
{
yield return new ValidationResult(
$"You must have at least one field of Role, Target User, or Target Section selected.",
new[] { nameof(IdentityRole), nameof(Section), nameof(Receiver) });
}
}
}

Searching in json array

{
"medic":[
{
"ace":[
{
"name":"lisinopril",
"strength":"10 mg Tab",
"dose":"1 tab",
"route":"PO",
"sig":"daily",
"pillCount":"#90",
"refills":"Refill 3"
}
],
"anti":[
{
"name":"nitroglycerin",
"strength":"0.4 mg Sublingual Tab",
"dose":"1 tab",
"route":"SL",
"sig":"q15min PRN",
"pillCount":"#30",
"refills":"Refill 1"
}
],
"anticoag":[
{
"name":"warfarin sodium",
"strength":"3 mg Tab",
"dose":"1 tab",
"route":"PO",
"sig":"daily",
"pillCount":"#90",
"refills":"Refill 3"
}
],
}
]
}
class Program
{
static void Main(string[] args)
{
// ""reporttype"":""post"",
string jsonString = #"..."; //The above json
Console.WriteLine("Enter the Medication name in which you want to Find STRENGTH value :");
string medicname = Console.ReadLine();
var rootInstance = JsonConvert.DeserializeObject<Rootobject>(jsonString);
}
}
var result = rootInstance.medications[0].Where(x=>x.name == medicname ).Select(t => t.strength).ToList();
But when i run the above query, I get this below error:
'Medication' does not contain a definition for 'Where' and no accessible extension method 'Where' accepting a first argument of type 'Medication' could be found (are you missing a using directive or an assembly reference?)
I have added all necessary namespaces to my code.
and Here is my object class
public class Rootobject
{
public List<Medication> medications { get; set; }
}
public class Medication
{
public List<aceInhibitors> aceinhibitors { get ; set ; }
public List<anti> antianginal {get; set; }
public List<anticoag> anticoagulants {get; set; }
}
public class aceInhibitors
{
[JsonProperty("name")]
public string name { get; set; }
[JsonProperty("strength")]
public string strength { get; set; }
[JsonProperty("dose")]
public string dose { get; set; }
[JsonProperty("route")]
public string route { get; set; }
[JsonProperty("sig")]
public string sig { get; set; }
[JsonProperty("pillCount")]
public string pillCount { get; set; }
[JsonProperty("refills")]
public string refills { get; set; }
}
public class anti
{
public string name { get; set; }
public string strength { get; set; }
public string dose { get; set; }
public string route { get; set; }
public string sig { get; set; }
public string pillCount { get; set; }
public string refills { get; set; }
}
public class anticoag
{
public string name { get; set; }
public string strength { get; set; }
public string dose { get; set; }
public string route { get; set; }
public string sig { get; set; }
public string pillCount { get; set; }
public string refills { get; set; }
}
Your Medication object itself is not searchable. Instead it holds a bunch of list and each contains a different type (where all properties are the same). So maybe you should use some base class for the medicine and add another property to your Medication class. In that case you would have a class layout something like this:
public class Rootobject
{
public List<Medication> medications { get; set; }
}
public class Medication
{
public List<aceInhibitors> aceinhibitors { get; set; }
public List<antianginal> antianginal { get; set; }
public List<anticoagulants> anticoagulants { get; set; }
public List<betaBlocker> betablocker { get; set; }
public List<diuretic> diuretic { get; set; }
public List<Mineral> mineral { get; set; }
public IEnumerable<Medicine> Medicines => Enumerable.Empty<Medicine>()
.Concat(aceinhibitors)
.Concat(antianginal)
.Concat(anticoagulants)
.Concat(betablocker)
.Concat(diuretic)
.Concat(mineral);
}
public class Medicine
{
[JsonProperty("name")]
public string name { get; set; }
[JsonProperty("strength")]
public string strength { get; set; }
[JsonProperty("dose")]
public string dose { get; set; }
[JsonProperty("route")]
public string route { get; set; }
[JsonProperty("sig")]
public string sig { get; set; }
[JsonProperty("pillCount")]
public string pillCount { get; set; }
[JsonProperty("refills")]
public string refills { get; set; }
}
public class aceInhibitors : Medicine
{
}
public class antianginal : Medicine
{
}
public class anticoagulants : Medicine
{
}
public class betaBlocker : Medicine
{
}
public class diuretic : Medicine
{
}
public class Mineral : Medicine
{
}
And prepared with that you could now ask something like that:
var result = rootInstance.medications[0].Medicines
.Where(x => x.name == medicname)
.Select(t => t.strength)
.ToList();
If the model of the classes really matches your desires is up to you, but it should give you starting point.
If you want it more inline you could also do something like this:
public class Medication : IEnumerable<Medicine>
{
public List<aceInhibitors> aceinhibitors { get; set; }
public List<antianginal> antianginal { get; set; }
public List<anticoagulants> anticoagulants { get; set; }
public List<betaBlocker> betablocker { get; set; }
public List<diuretic> diuretic { get; set; }
public List<Mineral> mineral { get; set; }
public IEnumerator<Medicine> GetEnumerator()
{
return Enumerable.Empty<Medicine>()
.Concat(aceinhibitors)
.Concat(antianginal)
.Concat(anticoagulants)
.Concat(betablocker)
.Concat(diuretic)
.Concat(mineral)
.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
And in that case you could write something like this:
var result = rootInstance.medications[0]
.Where(x => x.name == medicname)
.Select(t => t.strength)
.ToList();
Your domain model is bit suboptimal as it was pointed out by Oliver. If you need to stick to this model, then you can do the following.
Introduce an interface for fields that are interesting from your query point of view:
public interface InterestingFields
{
string name { get; }
string strength { get; }
}
Each medication class can be easily adjusted to implement it, like:
public class Mineral: InterestingFields
{
public string name { get; set; }
public string strength { get; set; }
public string dose { get; set; }
public string route { get; set; }
public string sig { get; set; }
public string pillCount { get; set; }
public string refills { get; set; }
}
Make the properties of the Medication class queryable
var properties = typeof(Medication).GetProperties()
.Where(prop => prop.PropertyType.IsGenericType
&& prop.PropertyType.GetGenericTypeDefinition() == typeof(List<>)
&& typeof(InterestingFields).IsAssignableFrom(prop.PropertyType.GetGenericArguments()[0]))
.ToList();
I've used reflection where the property's type is a List<T> and T is assignable to InterestingFields
Go through the properties, retrieve the actual value and do the filtering based on that
var medication = rootInstance.medications[0];
var result = from property in properties
let collection = property.GetValue(medication) as IEnumerable<InterestingFields>
let element = collection?.ToArray().First()
where element?.name == medicname
select element.strength;
Console.WriteLine(result.First());
Proper design would lead to a separation from the data handling and the way that your data is stored. This way, it is easy to reuse the stored data for other handling, it is easier to unit test the data handling with test code, you can change the way that the data is stored, to for instance a CSV file, or XML, without having to change the data handling code.
So you need a class Medication:
class Medication
{
public string Name {get; set;}
public string Strength {get; set;}
public string Dose {get; set;}
... // etc.
}
Consider to change Dose and Strength to a numerical value.
Apparently you have stored all Medications somewhere. A proper software design would hide where it is stored, and what format it is stored in. All you know is, that you can store Medications in it, and fetch it back later, even after your program is restarted. Such a storage is often called a Repository:
class MedicationRepository
{
public IEnumerable<Medication> ReadMedications() {...}
}
The actual implementation is up to you. I think you'll use Nuget Package NewtonSoft Json for this. Maybe you also want methods to Add / Change / Remove Medications?
Consider to let the Repository class implement IEnumerable<Medication>, or even ICollection<Medication>, depending on what is most efficient in your case.
class MedicationRepository : IEnumerable<Medication>
{
public IEnumerator<Medication> GetEnumerator()
{
return this.ReadMedications().GetEnumerator();
}
...
}
Now that you've got a method to read all Medications, we can get back to your LINQ problem:
I need get input string from user(which is medication name in json) i need to check if input matches the name in medication and need to display corresponding strength value.
So you've got a procedure to read the medication name:
public string ReadMedicationName() {...}
And you want the Strength of all Medications with this name.
MedicationRepository medications = ...
string requestedMedicationName = this.ReadMedicationName();
string medicationStrength = medications
.Where(medication => medication.Name == requestedMedicationName)
.Select(medication => medication.Strength)
.FirstOrDefault();
In words: from all Medications, keep only those Medications that have a name that equals requestedMedicationName. If the name is unique, then there will be zero or one Medication left. From all remaining Medications, take only the value of property Strength, and take the first strength, or null if there is no Medication with this Name at all.
Can it be that there are several Medications with this name? Which one do you want in that case, just any Strength (= .FirstOrDefault()), all Strengths (= ToList())? In the latter case: how do you distinguish which Medication with this name contains which Strength? Consider to Select more properties in that case.
Conclusion
By separating the storage of the data and how you get the requested Medication Name from the data handling, it is easier to change the storage (to XML, to CSV, to a database), and it is easier to unit test the LINQ using specific test data.
Similarly: you've hidden how you get the name of the requested Medication: is it a DOS prompt? Did you read it from a file? Maybe you've changed it to a WinForms application and you read it from a Textbox, or a ComboBox. Because you separated, the LINQ doesn't have to change, and can be reused in several platforms.

How to Bind a model based on a parameter in WebAPI

I am working on an API in which I can receive a payload with several possible types based on their type. For example
public class Item{};
public class Book : Item{
public string author { get; set; }
public string title { get; set; }
}
public class Movie{
public string title { get; set; }
public string studio { get; set; }
}
public class VideoGame{
public string Name { get; set; }
}
public class StoreItem{
public string upc { get; set; }
public double price { get; set }
public Item item { get; set; }
}
What I would like to do is have My controller being able to accept a StoreItem object
for example
[HttpPost("postItemForSale")]
public object Post(StoreIrem item)
The way this would be resolved is base on a type enum, so the request json will look like this:
{
upc : "12345",
price : "99.99",
item : {
type : "videoGame",
name : "minesweeper"
}
}
My question is there anyway I can define a mapping, lets assume that the type field is an enum and I have a corresponding Model for each representation.

MongoDB deserialization in C# when custom id field

I've got following structure in the database:
{
"_id" : ObjectId(""),
"title" : "something",
"id" : 1,
(...)
}
Basicly i want to retrive data from following collection to my Class:
[BsonIgnoreExtraElements]
public class Topic
{
[BsonElement("id")]
public int Id { get; set; }
[BsonElement("title")]
public string Name { get; set; }
}
The problem is this code doesn't work -> executes with error message:
Cannot deserialize a 'Int32' from BsonType 'ObjectId',
but this one does:
[BsonIgnoreExtraElements]
public class Topic
{
[BsonIgnore]
public int Id { get; set; }
[BsonElement("title")]
public string Name { get; set; }
[BsonElement("id")]
public int IdTest { get; set; }
Seems like deserialization desperatly tries to match class property with name "Id" with the ObjectId in database which is not correct because i explicitly declare that i want to match it with BsonElement("id") and not ("_id").
I appreciate any ideas how to make it works as I need to.
Your "_id" stored in your mongo document is of type BsonType.ObjectId so when deserializing, the Serializer try to find a property with the name "Id" and with type of ObjectId. In your case it found matched name but not the right type which is int (int32 type in your class): my suggestion is to change
public int Id { get; set; }
to
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
Or
public ObjectId Id { get; set; }
Or to make your class inheriting from class Entity if you use MongoRepository :
[BsonIgnoreExtraElements]
public class Topic : Entity
{
[BsonElement("title")]
public string Name { get; set; }
}
I ended up doing this:
public class Topic
{
public int Id { get; set; }
public string Name { get; set; }
}
[BsonIgnoreExtraElements]
public class TopicMapper
{
[BsonElement("title")]
public string Name { get; set; }
[BsonElement("id")]
public int Identity { get; set; }
}
and this:
var list = await col.Find(new BsonDocument()).ToListAsync().ConfigureAwait(false);
foreach(var doc in list)
{
if(doc.Name != null)
topics.Add(new Topic{
Id = doc.Identity,
Name = doc.Name
});
}

Why can't I send my custom class through my webservice?

I have these classes:
public abstract class CustomField
{
public String Id { get; set; }
public String Name { get; set; }
public String Description { get; set; }
public FieldType Type { get; set; }
public enum FieldType
{
String = 0,
Integer = 1,
Boolean = 2,
List = 3
}
}
public class StringCustomField:CustomField
{
public String Value { get; set; }
public Int32 MinLenght { get; set; }
public Int32 MaxLenght { get; set; }
public StringCustomField()
{
this.Type = FieldType.String;
}
}
public class CustomGroup
{
public String Id { get; set; }
public String Name { get; set; }
public String Description { get; set; }
public List<CustomField> FieldList = new List<CustomField>();
}
When I try to transfer CustomGroup through my webservice I get this error:
The remote server returned an error: NotFound
Serialization is failing when C# tries to transfer my StringField through my CustomField.
What am I doing wrong?
Marc Gravel tell me to do that and i understand the solution but some thing is wrong, no effects, cath the same error!! , help!!
[XmlInclude(typeof(StringCustomField))]
[XmlInclude(typeof(IntegerCustomField))]
[XmlInclude(typeof(BooleanCustomField))]
[XmlInclude(typeof(ListCustomField))]
public abstract class CustomField
{
public String Id { get; set; }
public String Name { get; set; }
public String Description { get; set; }
public FieldType Type { get; set; }
public enum FieldType
{
String = 0,
Integer = 1,
Boolean = 2,
List = 3
}
}
If you are sending subclasses as xml, you will need [XmlInclude]:
[XmlInclude(typeof(StringCustomField))]
public abstract class CustomField
{...}
You can add multiple [XmlInclude(...)] markers for any other subclasses in the model.
List<CustomField> will serialize and deserialize to a CustomField[] if you're using a web service, won't it?
use
public class CustomGroup
{
public String Id { get; set; }
public String Name { get; set; }
public String Description { get; set; }
public List<CustomField> FieldList = new List< StringCustomField >();
}
instead
If i understand you correctly, you should
1. connect your web service to your app
2. use the namespace of the WS, so all the classes will be used from the Proxy
i don't think that the local class will be understood by the remote web serivce correctly, even if you're using the same assembly on both parties

Categories

Resources