Validation Annotation - Entity Framework Core - .net-core

I have a model which is posted to my backend from a cshtml view. This model is also a representation of a Database-Table.
This code looks for the HTTP POST like the following:
[HttpPost]
public IActionResult CreateAKG(Conversation akg)
{
if (ModelState.IsValid && ValideD3OrD4Values(akg))
{
akg.RDPflichfelderBefuellt = true;
}
else
{
akg.RDPflichfelderBefuellt = false;
}
if (akg.Kommentare == null)
{
akg.Kommentare = new List<Kommentar>();
}
if (akg.AuftragsklaerungsgespraechId == 0)
{
this.MyDatabase.Conversation.Add(akg);
}
else
{
this.MyDatabase.Conversation.Update(akg);
}
this.MyDatabase.SaveChanges();
return RedirectToAction("Index");
}
The class which represents the model is called Conversation. There are some properties which are annotated by Validation Annotations. The annotation should only be used by the Controller / ModelState.IsValid and not for the Database-Table.
Here is the code sample:
public class Conversation
{
public int ConversationId { get; set; }
[Required(ErrorMessage = "This field is required.")]
public DateTime? DatumAKG { get; set; }
[MaxLength(256, ErrorMessage = "This field cannot be more than 256 characters")]
public string KontierungFertigungskosten { get; set; }
[MaxLength(256, ErrorMessage = "This field cannot be more than 256 characters")]
public string KontierungQVP { get; set; }
[MaxLength(256, ErrorMessage = "This field cannot be more than 256 characters")]
public string KontierungLayoutkosten { get; set; }
[MaxLength(256, ErrorMessage = "This field cannot be more than 256 characters")]
public string KontierungBeschaffung { get; set; }
public bool RDPflichfelderBefuellt { get; set; }
}
The ModelState.IsValid is only used to validate if a boolean is true or false. Other validation is not needed.
My problem now is, that in reason of the data-annotation a string which could normally be NULL in a Database is now in the database-design configured as NOT NULL.
If i try to store a new conversation in the database, a error is throw that some string values could not be null.
What i want to do is:
Validation on Controller
No Validation Annotation which changes the Database-Design

Since properties and requirements are different you should have 2 different objects
// this is you database object
public class Conversation {
[MaxLength(256, ErrorMessage = "This field cannot be more than 256 characters")]
public string KontierungQVP { get; set; }
}
// this your Data Transfer Object
public class ConversationDTO {
[MaxLength(256, ErrorMessage = "This field cannot be more than 256 characters")]
[Required]
public string KontierungQVP { get; set; }
}
Your EF models should always represent the database. If you want to add extra validations or conditions you should do it with another object and simply transfer between those objects. Having 2 different types of objects gives you more modularity.
EDIT:
There is a way to do what you ask but it's not a recommended approach and it might cause issues. You want to use 2 contexts and configure the required properties using FluentAPI and not data annotations.
// Call this method in your context.
protected override void OnModelCreating(your_builder){
modelBuilder.Entity<Conversation>()
.Property(p => p.KontierungQVP)
.IsRequired();
}
Generally you want to have 2 different contexts. One where you will initialize your database. And another one where you will have your required attributes defined by the FluentAPI (not data annotations).
So to recap one DbContext for your DB creation and one other for your operations. Of course this will lead to disparencies and it is easy to forget a validation on the database and so on.

Related

How to get class name in nested fluentvalidation validators

I am trying to implement a complex nested validator using FluentValidation in .Net Core. Suppose there's a failure in one of the nested validators and the error messages are available at validationResult.Errors. I was wondering if is there a way that I can access the name of the object that has the error.
Entities
Constituent
public class Constituent : BaseAuditedEntity
{
[Required]
[MaxLength(50)]
public string ClientProspectID { get; set; }
[InverseProperty("Client")]
public virtual ICollection<Solicit> Solicits { get; set; }
}
Solicit
public class Solicit: BaseAuditedEntity
{
[Required]
[ForeignKey("Client")]
public long ClientId { get; set; }
public StudyType StudyType { get; set; }
public virtual Constituent Client { get; set; }
}
Validators
ConstituentValidator and its nested SolicitValidator
public class ConstituentValidator : AbstractValidator<Constituent>
{
public ConstituentValidator()
{
RuleFor(x => x.ClientProspectID).NotEmpty().WithMessage("Invalid ClientProspectID");
RuleForEach(x => x.Solicits).SetValidator(new SolicitValidator());
}
class SolicitValidator : AbstractValidator<Solicit>
{
public SolicitValidator()
{
RuleFor(x => x.StudyType).NotEqual(StudyType.Invalid).WithMessage("Invalid StudyType");
}
}
}
Just to highlight the question, in the case of failing the validation rule, is there a way that I can find which main or nested validator has failed.
var validationResult = validator.Validate(item);
if (!validationResult.IsValid)
{
invalidData.AddRange(validationResult.Errors.Select(q => new DataImportValidationResult
{
DataImportId = dataImportData.Id,
ClientProspectID = item.ClientProspectID,
ValidationResult = q.ErrorMessage,
AttemptedValue = q.AttemptedValue.ToString(),
Field = q.PropertyName,
Category = "", //Here I need a way to find which object has failed.
}));
}
If it is not possible to access the failed object, is there a way that we can set a property in the validations rule that can be accessible in the validationResult errors?

How to prevent under-posting in ASP.NET Web API OData service?

I have created a very simple OData v4 controller. The controller basically contains Entity Framework-backed CRUD methods for the following Pet entity:
public class Pet
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public int Age { get; set; }
}
An important thing here is that Pet.Age is the non-nullable required property.
Here is the controller itself (only Post method is shown):
public class PetController : ODataController
{
private DatabaseContext db = new DatabaseContext();
// POST: odata/Pet
public IHttpActionResult Post(Pet pet)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Pet.Add(pet);
db.SaveChanges();
return Created(pet);
}
// Other controller methods go here...
}
And this is my WebApiConfig controller configuration:
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Pet>("Pet");
config.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
Now if I want to create a new Pet in my database, I issue a POST request like this:
POST http://localhost:8080/odata/Pet
Content-type: application/json
{ Name: "Cat", Age: 5 }
However, I can simply omit the Age property in JSON request payload, so JSON deserializer will use a default value of 0, while I want a 400 Bad Request status to be returned instead. This problem is called under-posting.
It can be easily solved when using regular WebApi controllers (the solution is described here). You just create a PetViewModel and make your controller to accept a PetViewModel instead of an actual Pet entity:
public class PetViewModel
{
// Make the property nullable and set the Required attribute
// to distinguish between "zero" and "not set"
[Required]
public int? Age { get; set; }
// Other properties go here...
}
Then in your controller you just convert PetViewModel to Pet entity and save it to the database as usual.
Unfortunately, this approach does not work with OData controllers: if I change my Post method to accept PetViewModel instead of Pet, I receive the following error:
System.Net.Http.UnsupportedMediaTypeException: No MediaTypeFormatter is available to read an object of type 'PetViewModel' from content with media type 'application/json'.
at System.Net.Http.HttpContentExtensions.ReadAsAsync[T](HttpContent content, Type type, IEnumerable'1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
at System.Net.Http.HttpContentExtensions.ReadAsAsync(HttpContent content, Type type, IEnumerable'1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
at System.Web.Http.ModelBinding.FormatterParameterBinding.ReadContentAsync(HttpRequestMessage request, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
So, is there any way to prevent under-posting when using OData controllers?
After some investigation I have solved this issue. Not sure if it is an "official" or preferred way of solving underposting problem in OData, but at least it works fine for me. So, for the lack of the official information, here is my recipe:
First, create a corresponding validation ViewModel for your OData entity:
public class PetViewModel
{
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
// Make the property nullable and set the Required attribute
// to distinguish between "zero" and "not set"
[Required]
public new int? Age { get; set; }
}
Then, add your own ODataUnderpostingValidationAttribute. My implementation looks like this:
public class ODataUnderpostingValidationAttribute: ActionFilterAttribute
{
public ODataUnderpostingValidationAttribute(Type viewModelType)
{
ViewModelType = viewModelType;
}
public Type ViewModelType { get; set; }
public override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
// Rewind requestStream so it can be read again.
var requestStream = await actionContext.Request.Content.ReadAsStreamAsync();
if (requestStream.CanSeek)
{
requestStream.Position = 0;
}
// Read the actual JSON payload.
var json = await actionContext.Request.Content.ReadAsStringAsync();
// Deserialize JSON to corresponding validation ViewModel.
var viewModel = JsonConvert.DeserializeObject(json, ViewModelType);
var context = new ValidationContext(viewModel);
var results = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(viewModel, context, results);
if (!isValid)
{
// Throw HttpResponseException instead of setting actionContext.Response, so the exception will be logged by the ExceptionLogger.
var responseMessage = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, string.Join(Environment.NewLine, results.Select(r => r.ErrorMessage)));
throw new HttpResponseException(responseMessage);
}
await base.OnActionExecutingAsync(actionContext, cancellationToken);
}
}
After that, apply this custom filter to your ODataController:
[ODataUnderpostingValidation(typeof(PetViewModel))]
public class PetController : ODataController
{ /* Implementation here */ }
Voila! Now you have everything in place. Underposting validation works fine.
You've got a couple options as I see it:
First In your controller you can check the integer value and if its below a certain value return 404.
if (Age <= 0)
return NotFound();
This could be labor intensive and if you're doing it for every controller method it's not very DRY.
Second in your Pet class you can use the DataAnnotations Attribute Range e.g.
[Range(0, 80, ErrorMessage = "Value for {0} must be between {1} and {2}")]
public int Age { get; set; }
Where Age can be a maximum of 80.
https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.rangeattribute(v=vs.110).aspx
Lastly I think your a more permanent solution for you would be to create your own validation:
public class AgeValidation : ValidationAttribute {
public override bool IsValid(object value) {
if (Object.Equals(value, null)) {
return false;
}
int getage;
if (int.TryParse(value.ToString(), out getage)) {
if (getage == 0)
return false;
if (getage > 0)
return true;
}
return false;
}
}
Then in your Pet class add:
[AgeValidation(ErrorMessage = "Age is wack")]
public int Age { get; set; }
Borrowed from How to do Integer model validation in asp.net mvc 2

Only return selected fields in Web API results

First of all, this is not exactly a duplication of the dozens of other posts and I have tried all of them and none of them work.
I have a model that contains many more values than my web api consumers need.
public class Publication
{
[Key]
public int PublicationID { get; set; }
public string PublicationTitle { get; set; }
public string Frequency { get; set; }
public DateTime NextIssueDate { get; set; }
public DateTime SpaceDeadline { get; set; }
public DateTime MaterialsDeadline { get; set; }
public DateTime CreatedDt { get; set; }
public string CreatedBy { get; set; }
public DateTime UpdatedDt { get; set; }
public string UpdatedBy { get; set; }
}
I only want say a few of the fields to be passed in the API. I've tried this code but instead of leaving out say UpdateBy in the Json result, it returns it with a null value. How do I get rid of that? I've tried several dozen variations but they either fail to compile or fail to return results.
public IQueryable<Publication> GetPublications()
{
return db.Publications
.ToList()
.Select(p => new Publication {
PublicationID = p.PublicationID,
PublicationTitle = p.PublicationTitle,
Frequency = p.Frequency,
NextIssueDate = p.NextIssueDate
})
.AsQueryable();
}
Don't serialize your DAO. Create a complete contract and then serialize it selectively. To creating different contracts for different cases, you could simplify it using Json.Net; you could just create a custom contract resolver and use it as a parameter of SerializeObject() like so
static void Main(string[] args)
{
var person = new TestContract {FirstName = "John", LastName = "Doe", Age = 36};
var firstNameContract = new SelectiveSerializer("firstname");
var allPropertiesContract = new SelectiveSerializer("firstname, lastname, age");
var allJson = JsonConvert.SerializeObject(
person,
Formatting.Indented,
new JsonSerializerSettings {ContractResolver = allPropertiesContract});
var firstNameJson = JsonConvert.SerializeObject(
person,
Formatting.Indented,
new JsonSerializerSettings {ContractResolver = firstNameContract});
Console.WriteLine(allJson);
// {
// "FirstName": "John",
// "LastName": "Doe",
// "Age": 36
// }
Console.WriteLine(firstNameJson);
// {
// "FirstName": "John",
// }
}
public class SelectiveSerializer : DefaultContractResolver
{
private readonly string[] _fields;
public SelectiveSerializer(string fields)
{
var fieldColl = fields.Split(',');
_fields = fieldColl
.Select(f => f.ToLower().Trim())
.ToArray();
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
property.ShouldSerialize = o => _fields.Contains(member.Name.ToLower());
return property;
}
}
public class TestContract
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
Without much effort, you could probably work this into your default mediatype formatter (in the pipeline) to look for a parameter in the request called 'fields' or whatever and then use the custom contract resolver if present, and then it would be seamless default behavior to limit fields if specified or serialize the entire object if not specified.
On the academic side, here is the justification:
Any modification to the data is considered a "view concern" which means, in an API, it should controlled by query parameters and accept header. In this case, the "representation" of the data is application/json and you've chose to "filter" the returned fields. All of this can (and should be, imo) be handled during serialization. So your "model" in this case will always be the full model vs. some subset of the model. The full model in this example contains first name, last name, and age. In reality, this could be hundreds of properties. If you want to allow the client to choose a subset of the complete model, this is how you could do it with selective serialization.
You can similar behaviors in graph apis. There, the default for large models is that you get an empty object if you don't specify fields, forcing the client to be very specific about what it asks for, which is great when payload size matters (e.g. mobile applications). And, there's nothing stopping from creating field presets like 'name' which could mean 'firstname, lastname' or 'all' which includes all properties.
I've never been a fan of having hundreds of data objects that all serve some ad hoc requirement for a data set that is used in 20 different contexts where some cases require more data while others require less. IMO if you have to go through the same process to get the data, whether it complete or not, you shouldn't waste your time creating additional objects to frame the data for the sake of the client, and this should help you achieve that.
It's because you're returning a collection of Publication objects so you will get every property that is contained in that class, whether you populate it or not. If you want to return a subset of the properties then create a class that has only the properties you want to return and create an instance of that class in your query.
public IQueryable<WhatIReallyWantToReturn> GetPublications()
{
return db.Publications
.ToList()
.Select(p => new WhatIReallyWantToReturn {
PublicationID = p.PublicationID,
PublicationTitle = p.PublicationTitle,
Frequency = p.Frequency,
NextIssueDate = p.NextIssueDate
})
.AsQueryable();
}
private class WhatIReallyWantToReturn
{
public int PublicationID { get; set; }
public string PublicationTitle { get; set; }
public string Frequency { get; set; }
public DateTime NextIssueDate { get; set; }
}
using Newtonsoft.Json;
public class Publication
{
[Key]
public int PublicationID { get; set; }
public string PublicationTitle { get; set; }
public string Frequency { get; set; }
public DateTime NextIssueDate { get; set; }
public DateTime SpaceDeadline { get; set; }
public DateTime MaterialsDeadline { get; set; }
[JsonIgnore]
public DateTime CreatedDt { get; set; }
[JsonIgnore]
public string CreatedBy { get; set; }
[JsonIgnore]
public DateTime UpdatedDt { get; set; }
[JsonIgnore]
public string UpdatedBy { get; set; }
}
as Craig W. said you can use viewmodel ,also you can use anonymous type
(notice viewmodel is better way because you can use some utilities like automapper for mapping your property automatically)
JsonIgnore annotation has worked for me
[JsonIgnore]
public int Ranking { get; set; }
Here is a great article (Dec 2019) on the subject. It offers a solution for data shaping by making use of ExpandoObject and Type Reflection. The properties that the client requires can then be passed through the request as a query parameter (i.e. separated by a comma). The article also offers solution to the JSON Serialization problem.
Startup.cs file:
services.AddControllers(config =>
{
config.RespectBrowserAcceptHeader = true;
config.ReturnHttpNotAcceptable = true;
})
.AddXmlDataContractSerializerFormatters()
.AddNewtonsoftJson();
+1 for Sinaesthetic's answer.
I just finished reading an article, about GraphQL which solves exactly this problem. You can define exactly which fields do you need in the same request. No need for creating new endpoints every single time, when the caller needs just a specific subset of the properties.
If you can do this in .NET WEB API too without creating new models and endpoints, with just a very little extra effort, why wouldn't you (instead of exchanging Web Api for GraphQL).
Actually his SelectiveSerializer could be upgarded with reflection, so if you want to define which props you need in
C#, you can do this by providing property expressions, so you don't have to worry about misstyping prop names.
I bet there are other solutions for this, but the basic concept is the most important that we can define which fields we need in our json without creating new models.

Integer value model validation

I have a regular Integer (Not nullable) in my model:
[Required]
[Range(0, Int32.MaxValue - 1)]
public int PersonId
{
get;
set;
}
In my WebApi action, I accept an object that has that propery.
public IHttpActionResult Create([FromBody] Person person)
{
if (!ModelState.IsValid)
{
return BadRequest("Some error message.");
}
//Do some stuff with person...
}
Now, altough there is a Required attribute on PersonId, when a person is posted to this action, the ModelState.IsValid property is true.
I guess this is because Person is created with default value, which is 0, I want to throw an error if there is no PersonId field in the incoming JSON / query string request.
I can set PersonId to be Nullable, but that doesn't make sense.
Is there any easy way to validate the field exists and the integer is larger than 0 ? (without custom validators for that simple requirement)
Setting the [Required] attribute doesn't do anything on an int, as far as I know. All [Required] does is make sure the value is not null.
You can set [Range(1, Int32.MaxValue)] to make sure that a correct value is added.
If you don't already do this, it might be a good idea to make a different model for your view and make the data annotations on this model. I use view models to make sure I don't pollute my "real" models with stuff that is not relevant to the whole domain. This way your PersonId can be nullable in your view model only, where it makes sense.
BindRequiredAttribute can be used to
Quoting from this nice blog post about [Required] and [BindRequired]
It works the same way as RequiredAttribute, except it mandates that
the value comes from the request – so it not only rejects null values,
but also default (or “unbound”) values.
So this would reject unbound integer values:
[BindRequired]
[Range(0, Int32.MaxValue - 1)]
public int PersonId
{
get;
set;
}
I tend to use int? (nullable int) in this case and then mark those as required. I then use myInt.Value throughout the code and assume it's safe to use because it wouldn't have passed validation otherwise.
and like #andreas said, I do make sure to use "view models" in times like this so I'm not polluting my view model as a business or data layer model.
Actually for missing not nullable integer parameters model validation doesn't work. There is JSON parsing exception which is thrown by Newtonsoft.Json.
You can have a following workaround to parse and include exceptions in model validations.
Create the custom validation attribute as following and register in WebApiConfig.cs.
public class ValidateModelAttribute : ActionFilterAttribute {
public override void OnActionExecuting(HttpActionContext actionContext) {
// Check if model state is valid
if (actionContext.ModelState.IsValid == false) {
// Return model validations object
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest,
new ValidationResultModel(100001, actionContext.ModelState));
}
}
public class ValidationError {
public string Field { get; }
public string Message { get; }
public ValidationError(string field, string message) {
Field = field != string.Empty ? field : null;
Message = message;
}
}
public class ValidationResultModel {
public int Code { get; set; }
public string Message { get; }
public IDictionary<string, IEnumerable<string>> ModelState { get; private set; }
public ValidationResultModel(int messageCode, ModelStateDictionary modelState) {
Code = messageCode;
Message = "Validation Failed";
ModelState = new Dictionary<string, IEnumerable<string>>();
foreach (var keyModelStatePair in modelState) {
var key = string.Empty;
key = keyModelStatePair.Key;
var errors = keyModelStatePair.Value.Errors;
var errorsToAdd = new List<string>();
if (errors != null && errors.Count > 0) {
foreach (var error in errors) {
string errorMessageToAdd = error.ErrorMessage;
if (string.IsNullOrEmpty(error.ErrorMessage)) {
if (key == "model") {
Match match = Regex.Match(error.Exception.Message, #"'([^']*)");
if (match.Success)
key = key + "." + match.Groups[1].Value;
errorMessageToAdd = error.Exception.Message;
} else {
errorMessageToAdd = error.Exception.Message;
}
}
errorsToAdd.Add(errorMessageToAdd);
}
ModelState.Add(key, errorsToAdd);
}
}
}
}
}
//Register in WebApiConfig.cs
// Model validation
config.Filters.Add(new ValidateModelAttribute());

Web API error failed to serialize the response body

Im fairly new to ASP.NET MCV 4 as well as Mongo DB and trying to build web API.
I thought I had finally got it right but when I start the app and enter: http://localhost:50491/api/document into my browser I get this error message
The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'.
Here is my code
This is the Document Class
public class Document
{
[BsonId]
public ObjectId DocumentID { get; set; }
public IList<string> allDocs { get; set; }
}
This is where the Connection to the DB is made:
public class MongoConnectionHelper
{
public MongoCollection<BsonDocument> collection { get; private set; }
public MongoConnectionHelper()
{
string connectionString = "mongodb://127.0.0.1";
var server = MongoServer.Create(connectionString);
if (server.State == MongoServerState.Disconnected)
{
server.Connect();
}
var conn = server.GetDatabase("cord");
collection = conn.GetCollection("Mappings");
}
Here is the ApiController Class:
public class DocumentController : ApiController
{
public readonly MongoConnectionHelper docs;
public DocumentController()
{
docs = new MongoConnectionHelper();
}
public IList<BsonDocument> getAllDocs()
{
var alldocs = (docs.collection.FindAll().ToList());
return alldocs;
}
}
I read futher on and the error message suggested:
Type 'MongoDB.Bson.BsonObjectId' with data contract name 'BsonObjectId:http://schemas.datacontract.org/2004/07/MongoDB.Bson' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.
That is all good and well but how do I do that?
Either a) don't serialize your document classes over Web API, and create some DTOs meant to be serialized, or b) use something else as ID.
If you want an easy auto-generated ID, and you're OK with the fact that it will consume slightly more space, you can resort to the following "hack":
public class Document
{
public Document()
{
Id = ObjectId.GenerateNewId().ToString();
}
public string Id { get; set; }
}
This way, you'll get MongoIDs, but they'll be stored as a string.
If you need Web API2 responce in XML format , you need to handle the default Id like below
eg: ObjectId("507f191e810c19729de860ea")
Either you need to remove the Id from serialization.
[DataContract]
public class Document
{
[BsonId]
public string Id { get; set; }
[DataMember]
public string Title { get; set; } //other properties you use
}
Or You can change the Type of ID with custom logic
public class GuidIdGenerator : IIdGenerator
{
public object GenerateId(object container, object document)
{
return Guid.NewGuid();
}
public bool IsEmpty(object id)
{
return string.IsNullOrEmpty(id.ToString());
}
}
public class Document
{
[BsonId(IdGenerator = typeof(GuidIdGenerator))]
public string Id { get; set; }
public string Title { get; set; } //other properties you use
}

Resources