Data Annotation Always Searches Parent Model for Property - asp.net-mvc

Update
I have since realised the underlying cause of this problem, and have detailed it in another question, here: How Can I Use Custom Validation Attributes on Child Models of a DB Entity?
I have a WebsiteConfiguration model that consists of a number of sub models, broken down as such for convenience.
public class WebsiteConfiguration
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public TitleAuthorAndPublishingConfiguration TitleAuthorAndPublishing { get; set; }
public BookChaptersAndSectionsConfiguration BookChaptersAndSections { get; set; }
public SocialMediaLoginsConfiguration SocialMediaLogins { get; set; }
public TagGroupsConfiguration TagGroups { get; set; }
}
I am trying to add a DataAnnotation to one of the sub models, making certain properties required if another is marked as true. Like this:
public class SocialMediaLoginsConfiguration
{
public bool Initialised { get; set; }
public bool IsLoginWithFacebookEnabled { get; set; }
[RequiredIfEnabled("IsLoginWithFacebookEnabled")]
public string LoginWithFacebookAppID { get; set; }
[RequiredIfEnabled("IsLoginWithFacebookEnabled")]
public string LoginWithFacebookAppSecret { get; set; }
}
The DataAnnotation code is:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public class RequiredIfEnabledAttribute : ValidationAttribute
{
private string _ifWhatIsEnabled { get; set; }
public RequiredIfEnabledAttribute(string IfWhatIsEnabled)
{
_ifWhatIsEnabled = IfWhatIsEnabled;
}
protected override ValidationResult IsValid(object currentPropertyValue, ValidationContext validationContext)
{
var isEnabledProperty = validationContext.ObjectType.GetProperty(_ifWhatIsEnabled);
if (isEnabledProperty == null)
{
return new ValidationResult(
string.Format("Unknown property: {0}", _ifWhatIsEnabled)
);
}
var isEnabledPropertyValue = (bool)isEnabledProperty.GetValue(validationContext.ObjectInstance, null);
if (isEnabledPropertyValue == true)
{
if (String.IsNullOrEmpty(currentPropertyValue.ToString()))
{
return new ValidationResult(String.Format("This field is required if {0} is enabled", isEnabledProperty));
}
}
return ValidationResult.Success;
}
}
When I attempt to get the value of IsLoginWithFacebookEnabled it looks for this property in the WebsiteConfiguration class, rather than the SocialMediaLoginsConfiguration. Even though the annoted property is in the latter.
How can I make it look for the property within the same class as the annotation?
Update
I think this is happening because I am calling DB.SaveChanges() on the WebsiteConfiguration, like this:
public void SeedWebsiteConfiguration()
{
var titleAuthorAndPublishingConfiguration = new TitleAuthorAndPublishingConfiguration()
{
// seed values
};
var bookChaptersAndSectionsConfiguration = new BookChaptersAndSectionsConfiguration()
{
// seed values
};
var socialMediaLoginConfiguration = new SocialMediaLoginsConfiguration()
{
// seed values
};
var tagGroupsConfiguration = new TagGroupsConfiguration()
{
// seed values
};
var websiteConfiguration = new WebsiteConfiguration()
{
TitleAuthorAndPublishing = titleAuthorAndPublishingConfiguration,
BookChaptersAndSections = bookChaptersAndSectionsConfiguration,
SocialMediaLogins = socialMediaLoginConfiguration,
TagGroups = tagGroupsConfiguration
};
DB.WebsiteConfiguration.Add(websiteConfiguration);
DB.SaveChanges();
}
But I don't want to create separate DB tables for each of the sub models. I'd like them stored together in one table, but in the code I'd like to manage them as sub models.

Related

How Can I Use Custom Validation Attributes on Child Models of a DB Entity?

Summary:
I want a data annotation validator to reference another property in the same class (TitleAuthorAndPublishingConfiguration).
However, DB.SaveChanges() is not being called on this class directly. Rather it is being called on the parent of this class (WebsiteConfiguration).
Therefore validationContext.ObjectType is returning WebsiteConfiguration and I am unable to refer to properties of TitleAuthorAndPublishingConfiguration within the data annotation validator.
WebsiteConfiguration.cs
public class WebsiteConfiguration
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public TitleAuthorAndPublishingConfiguration TitleAuthorAndPublishing { get; set; }
public BookChaptersAndSectionsConfiguration BookChaptersAndSections { get; set; }
public SocialMediaLoginsConfiguration SocialMediaLogins { get; set; }
public TagGroupsConfiguration TagGroups { get; set; }
}
public class TitleAuthorAndPublishingConfiguration
{
public string BookTitle { get; set; }
public bool IsPublished { get; set; }
// how do I access a property of current model when calling DB.SaveChanges() on parent?
[RequiredIfOtherFieldIsEnabled("IsPublished")]
public string Publisher { get; set; }
}
// ... and other sub models...
ApplicationDbContext.cs
DbSet<WebsiteConfiguration> WebsiteConfiguration {get;set;}
Example Update Code
public void SeedWebsiteConfiguration()
{
var titleAuthorAndPublishingConfiguration = new TitleAuthorAndPublishingConfiguration()
{
// seed values
};
var bookChaptersAndSectionsConfiguration = new BookChaptersAndSectionsConfiguration()
{
// seed values
};
var socialMediaLoginConfiguration = new SocialMediaLoginsConfiguration()
{
// seed values
};
var tagGroupsConfiguration = new TagGroupsConfiguration()
{
// seed values
};
var websiteConfiguration = new WebsiteConfiguration()
{
TitleAuthorAndPublishing = titleAuthorAndPublishingConfiguration,
BookChaptersAndSections = bookChaptersAndSectionsConfiguration,
SocialMediaLogins = socialMediaLoginConfiguration,
TagGroups = tagGroupsConfiguration
};
DB.WebsiteConfiguration.Add(websiteConfiguration);
DB.SaveChanges();
}
Validator Code
public class RequiredIfOtherFieldIsEnabledAttribute : ValidationAttribute
{
private string _ifWhatIsEnabled { get; set; }
public RequiredIfOtherFieldIsEnabledAttribute(string IfWhatIsEnabled)
{
_ifWhatIsEnabled = IfWhatIsEnabled;
}
protected override ValidationResult IsValid(object currentPropertyValue, ValidationContext validationContext)
{
var isEnabledProperty = validationContext.ObjectType.GetProperty(_ifWhatIsEnabled);
if (isEnabledProperty == null)
{
return new ValidationResult(
string.Format("Unknown property: {0}", _ifWhatIsEnabled)
);
}
var isEnabledPropertyValue = (bool)isEnabledProperty.GetValue(validationContext.ObjectInstance, null);
if (isEnabledPropertyValue == true)
{
if (String.IsNullOrEmpty(currentPropertyValue.ToString()))
{
return new ValidationResult(String.Format("This field is required if {0} is enabled", isEnabledProperty));
}
}
return ValidationResult.Success;
}
}
Questions
Is there a way for me to access child model properties from validationContext?
Am I misguided in my approach? Is there a better way to store multiple models as part of a larger model in a single DB table?
I was hoping not to have multiple config tables and calls to the DB. (There are 4 child models in this example, but there may be 10+ in the next app.)
The setup above meets my needs in so many ways. But I don't want to give up the functionality of DataAnnotations on the sub models!
Bonus Question
I have come across a few posts like this one:
How can I tell the Data Annotations validator to also validate complex child properties?
But that is 4 years old, and I'm wondering if anything has changed since then.
Am I trying to do something that is basically impossible (or at least very difficult)?
Am I trying to do something that is basically impossible (or at least
very difficult)?
No, there is a very simple solution that integrates perfectly with the framework and technologies using DataAnnotations.
You can create a custom ValidationAttribute that is called by EF Validation and call Validator.TryValidateObject inside. This way, when CustomValidation.IsValid is called by EF you launch child complex object validation by hand and so on for the whole object graph. As a bonus, you can gather all errors thanks to CompositeValidationResult.
i.e.
using System;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
public class Program
{
public static void Main() {
var person = new Person {
Address = new Address {
City = "SmallVille",
State = "TX",
Zip = new ZipCode()
},
Name = "Kent"
};
var context = new ValidationContext(person, null, null);
var results = new List<ValidationResult>();
Validator.TryValidateObject(person, context, results, true);
PrintResults(results, 0);
Console.ReadKey();
}
private static void PrintResults(IEnumerable<ValidationResult> results, Int32 indentationLevel) {
foreach (var validationResult in results) {
Console.WriteLine(validationResult.ErrorMessage);
Console.WriteLine();
if (validationResult is CompositeValidationResult) {
PrintResults(((CompositeValidationResult)validationResult).Results, indentationLevel + 1);
}
}
}
}
public class ValidateObjectAttribute: ValidationAttribute {
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
var results = new List<ValidationResult>();
var context = new ValidationContext(value, null, null);
Validator.TryValidateObject(value, context, results, true);
if (results.Count != 0) {
var compositeResults = new CompositeValidationResult(String.Format("Validation for {0} failed!", validationContext.DisplayName));
results.ForEach(compositeResults.AddResult);
return compositeResults;
}
return ValidationResult.Success;
}
}
public class CompositeValidationResult: ValidationResult {
private readonly List<ValidationResult> _results = new List<ValidationResult>();
public IEnumerable<ValidationResult> Results {
get {
return _results;
}
}
public CompositeValidationResult(string errorMessage) : base(errorMessage) {}
public CompositeValidationResult(string errorMessage, IEnumerable<string> memberNames) : base(errorMessage, memberNames) {}
protected CompositeValidationResult(ValidationResult validationResult) : base(validationResult) {}
public void AddResult(ValidationResult validationResult) {
_results.Add(validationResult);
}
}
public class Person {
[Required]
public String Name { get; set; }
[Required, ValidateObject]
public Address Address { get; set; }
}
public class Address {
[Required]
public String Street1 { get; set; }
public String Street2 { get; set; }
[Required]
public String City { get; set; }
[Required]
public String State { get; set; }
[Required, ValidateObject]
public ZipCode Zip { get; set; }
}
public class ZipCode {
[Required]
public String PrimaryCode { get; set; }
public String SubCode { get; set; }
}

validate required field depend on other field in model MVC

I want create model that will validate required field in model that depend on other field condition.
public class FixedDeposit
{
public int DebitAmount { get; set; }
public string PAN { get; set; }
}
Now if the DebitAmount is greater than 50,000 then PAN field is must be required.
You can implement IValidatableObject
public class FixedDeposit : IValidatableObject
{
public int DebitAmount { get; set; }
public string PAN { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (DebitAmount > 50000 && string.IsNullOrEmpty(PAN))
{
yield return new ValidationResult("PAN required for debits > 50,000.", new [] { "PAN" } );
}
}
}
http://weblogs.asp.net/scottgu/class-level-model-validation-with-ef-code-first-and-asp-net-mvc-3
You can also use MVC Foolproof validation package. This package provides you with many conditional validations in the form of annotations.
Complete list is here:
http://foolproof.codeplex.com/
You can add this library to your VS project as a package:
And, for your FixedPayment class, it should look something like this:
using Foolproof;
public class FixedDeposit
{
public int DebitAmount { get; set; }
[RequiredIf("DebitAmount", Operator.GreaterThan, 50000)]
public string PAN { get; set; }
}
Alternate code
using Foolproof;
public class FixedDeposit
{
public int DebitAmount { get; set; }
private bool _panRequired { get { return DebitAmount > 50000; } }
[RequiredIf("_panRequired", true, ErrorMessage="PAN is required if Debit Amount is greater than 50000")]
public string PAN { get; set; }
}
There are two options which you can use.
The first is the very easy to use and quite concise ExpressiveAnnotations JS library developed by Jaroslaw Waliszko. Follow this link to https://github.com/jwaliszko/ExpressiveAnnotations for more information. This library allows you to perform different conditional validations.
Similarly to Foolproof it is added to your Visual Studio environment through adding the NuGet package. Once added, within your model add the using statement using ExpressiveAnnotations.Attributes; Then simply use the RequiredIf declaration to do what you need. For example:
public class FixedDeposit
{
public int DebitAmount { get; set; }
[RequiredIf("DebitAmount >= 50000")]
public string PAN { get; set; }
}
The second option is to use ModelState.AddModelError(). This is done within your controller. Simply create a new method:
private void ValidateRequiredFields(modelname)
{
if(modelname.DebitAmount >= 50000)
{
if(modelname.PAN == null)
{
ModelState.AddModelError("PAN", "Place whatever error message you want here");
}
}
}
Next you place a reference to your validation method in whichever view method you want this to be called. The line to reference is ValidateRequiredFields(ModelName);
public class RequiredIfAttribute : RequiredAttribute
{
private String PropertyName { get; set; }
private Object DesiredValue { get; set; }
public RequiredIfAttribute(String propertyName, Object desiredvalue)
{
PropertyName = propertyName;
DesiredValue = desiredvalue;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
Object instance = context.ObjectInstance;
Type type = instance.GetType();
Object proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
if (proprtyvalue.ToString() == DesiredValue.ToString())
{
ValidationResult result = base.IsValid(value, context);
return result;
}
return ValidationResult.Success;
}
}
Usage
[RequiredIf("DebitAmount",50000, ErrorMessage = "PAN field is required")]
public string PAN
{get;set;
}

Why is not Entity Framework initializing my navigation collections?

I'm creating an ASP.NET MVC4 tournament administration system which as a containing class Tournament with a collection of Round objects. I'm new to EF Code First, but I've understood that EF is supposed to initialize my collection objects with observed proxy classes upon instantiation, as long as I've declared them as virtual. Why are they null when I try to add elements to them from the ASP.NET controller in the code below?
public class Tournament
{
public int Id { get; set; }
[Required]
public String Name { get; set; }
public virtual ICollection<Contestant> Contestants { get; set; }
public virtual ICollection<Round> Rounds { get; set; }
public void InitializeDefaults()
{
var round = new Round("Round 1");
Rounds.Add(round); // Generates NullReferenceException when called from controller
}
}
public class Round
{
public long Id { get; set; }
public int MaxContestants { get; set; }
public String Title { get; set; }
public Round() { }
public Round(string title) : this()
{
Title = title;
}
}
public class MainController {
// (...)
[HttpPost]
public ActionResult CreateTournament(Tournament tournament)
{
var db = new DataContext();
var dbTournament = db.Tournaments.Create();
dbTournament.Name = tournament.Name;
db.Tournaments.Add(dbTournament);
dbTournament.InitializeDefaults();
db.SaveChanges();
return RedirectToRoute(new { action = "Main", tournamentId = dbTournament.Id });
}
}
public class DataContext : DbContext
{
public IDbSet<Tournament> Tournaments { get; set; }
public IDbSet<Judge> Judges { get; set; }
public IDbSet<Contestant> Contestants { get; set; }
}
update
Reinitializing the dataContext after saving the entity, solves my problem. But in not the right way. Original question stands.
Altered CreateTournament-method
[HttpPost]
public ActionResult CreateTournament(Tournament tournament)
{
var db = App.ServiceLocator.GetDataBase();
db.Tournaments.Add(tournament);
db.SaveChanges();
db.Dispose();
// Reinitializing the datacontext
db = App.ServiceLocator.GetDataBase();
var dbTournament = db.GetTournament(tournament.Id);
dbTournament.InitializeDefaults();
db.SaveChanges();
return RedirectToRoute(new { action = "Main", tournamentId = dbTournament.Id });
}
It would only work as you expect if you were attaching the created dbTournament to the context. Only in that case creating the collection and preparing it for lazy loading makes sense. But you are adding the dbTournament as a new entity and in this case there can't be any dependent Rounds in the database that could refer to the new tournament, hence a lazy loading query wouldn't return a result anyway and EF doesn't create the collection for lazy loading in the first place.
You could apply tricks like attaching the dbTournament first and after that adding it to the context. But that would only yield unnecessary database queries triggered by lazy loading with no result and is rather hacky. In my opinion the simplest and most reasonable solution is either the standard collection initialization in a default constructor...
public Tournament()
{
Rounds = new HashSet<Round>();
}
...or at least a guard in your InitializeDefaults method:
public void InitializeDefaults()
{
var round = new Round("Round 1");
if (Rounds == null)
Rounds = new HashSet<Round>();
Rounds.Add(round);
}

Object reference not set to an instance of an object.

i have 3 model:
1st one:
public class CreateFieldModel
{
public FieldModel fm { get; set; }
public CategoryModel cm { get; set; }
}
2nd one:
public class FieldModel
{
public string field_Name { get; set; }
public InputTypeModel itm { get; set; }
public string input1 { get; set; }
public string input2 { get; set; }
public string input3 { get; set; }
public string input4 { get; set; }
public List<InputTypeModel> inputs { get; set; }
}
3rd One:
public class InputTypeModel
{
public string inputTypeName { get; set; }
public string inputTypeDesc { get; set; }
}
2 methods:
1st One:
public List<InputTypeModel> getInputTypes()
{
var inptypes = edu.InputTypes;
List<InputTypeModel> listInputTypes = new List<InputTypeModel>();
foreach (var inpType in inptypes)
{
listInputTypes.Add(new InputTypeModel { inputTypeName = inpType.Input_Type_Name, inputTypeDesc = inpType.Input_Type_Description });
}
return listInputTypes;
}
when this method executes listInputTypes has three different values.. i check it by debugging.. so no roblem here. This methos is under the class FormManagement.. I am calling this method from the following action method:
[HttpGet]
public ActionResult createNewField(CreateFieldModel cfm, string fcode)
{
FormManagement ffm = new FormManagement();
cfm.fm.inputs = ffm.getInputTypes();
return View(cfm);
}
when cfm.fm.inputs = ffm.getInputTypes(); executes it is showing "Object reference not set to an instance of an object." message... I am quite beginner to mvc.. please help
Without knowing what you really want to achieve with cfm-parameter in your action, the only thing I can suggest is to check for null references and create new instances before you assign them:
[HttpGet]
public ActionResult createNewField(CreateFieldModel cfm, string fcode)
{
FormManagement ffm = new FormManagement();
if (cfm == null)
{
cfm = new CreateFieldModel();
}
if (cfm.fm == null)
{
cfm.fm = new FieldModel();
}
cfm.fm.inputs = ffm.getInputTypes();
return View(cfm);
}
Of course, this supposes that your not relying on incoming data through your route parameters. If you are, you need to check why the values are not getting passed in, but I'm guessing you don't need it as a parameter in the first place.

Partial Updates for Entities with Repository/DTO patterns in MVC (prepping for API)

I've built my Domain model layer, my repository layer, and now I'm working on my DTO layer to be used by a webApi project. I'm in the middle of implementing an Update service method, and I'm wondering about partial updates. Here's my DTO class:
public class FullPersonDto
{
public FullPersonDto()
{
Friends = new List<Person>();
}
public FullPersonDto(Person person)
{
PersonId = person.PersonId;
DateCreated = person.DateCreated;
Details = person.Details;
Friends = new List<Person>();
foreach (Person friend in person.Friends)
{
Friends.Add(new PersonDto(friend));
}
}
[Key]
public int PersonId { get; set; }
[Required]
public virtual DateTime DateCreated { get; set; }
public virtual string Details { get; set; }
public List<Person> Friends { get; set; }
public Person ToEntity()
{
var person = new Person
{
PersonId = PersonId,
DateCreated = (DateTime) DateCreated,
Details = Details,
Friends = new List<Person>()
};
foreach (PersonDto friend in Friends)
{
person.Friends.Add(friend.ToEntity());
}
return person;
}
}
Here's my Update method in my Repository:
public Person UpdatePerson(Person person)
{
var entry = _db.Entry(person);
if (entry.State == EntityState.Detached)
{
var dbSet = _db.Set<Person>();
Person attachedPerson = dbSet.Find(person.PersonId);
if (attachedPerson != null)
{
var attachedEntry = _db.Entry(attachedPerson);
attachedEntry.CurrentValues.SetValues(person); // what if values are null, like ID, or DateCreated?
}
else
{
entry.State = EntityState.Modified;
}
}
SaveChanges();
return person;
}
My question is: What if I only need to update the Details of a person via my webAPI? Is the convention to construct an entire PersonDto and Update the entire object using SetValues, or is there any way I can specify that I only want a single field updated so that I don't have to send a ton of data over the wire (that I don't really need)?
If it is possible to do partial updates, when is it ever good to update the entire entity? Even if I have to update 5/7 properties, it requires that I send old data for 2/7 to re-write so that SetValues doesn't write nulls into my fields from my DTO.
Any help here would be awesome... totally new to this stuff and trying to learn everything right. Thank you.
I've taken similar approach to do optimization, and I've faced same issues with null values when attaching (not just null, you'll have issue with boolean as well). This is what I've come up with:
public static void Update<T>(this DbContext context, IDTO dto)
where T : class, IEntity
{
T TEntity = context.Set<T>().Local.FirstOrDefault(x => x.Id == dto.Id);
if (TEntity == null)
{
TEntity = context.Set<T>().Create();
TEntity.Id = dto.Id;
context.Set<T>().Attach(TEntity);
}
context.Entry(TEntity).CurrentValues.SetValues(dto);
var attribute = dto.GetAttribute<EnsureUpdatedAttribute>();
if (attribute != null)
{
foreach (var property in attribute.Properties)
context.Entry(TEntity).Property(property).IsModified = true;
}
}
That is extension method for DbContext. Here are the interfaces IDTO and IEntity:
public interface IDTO
{
int Id { get; set; }
}
public interface IEntity
{
int Id { get; set; }
Nullable<DateTime> Modified { get; set; }
Nullable<DateTime> Created { get; set; }
}
I'm using my custom EnsureUpdatedAttribute to annotate what properties should always be updated (to deal with nulls / default values not being tracked):
public class EnsureUpdatedAttribute : Attribute
{
public IEnumerable<string> Properties { get; private set; }
public EnsureUpdatedAttribute(params string[] properties)
{
Properties = properties.AsEnumerable();
}
}
And this is a sample of usage:
public class Sample : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public Nullable<DateTime> Modified { get; set; }
public Nullable<DateTime> Created { get; set; }
}
[EnsureUpdated("Active")] /// requirement for entity framework change tracking, read about stub entities
public class SampleDTO : IDTO
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[JsonIgnore] /// How to exclude property from going on the wire / ignored for serialization
public bool Active { get; set; }
}
[HttpPost]
public HttpResponseMessage SaveSample(SampleDTO dto)
{
dto.Active = true;
_ctx.AddModel<Sample>(dto);
_ctx.SaveChanges();
return NoContent();
}
return NoContent() is just extension for returning 204 (NoContent).
Hope this helps.
Theres a few options you have, you can create a stored procedure to update the required parts (I wouldnt do this), or you can manually select the fileds to update on the model before saving the context changes with EF.
Heres an example how to update a specific field:
public void UpdatePerson(int personId, string details)
{
var person = new Person() { Id = personId, Details = details };
db.Persons.Attach(personId);
db.Entry(person).Property(x => x.Details).IsModified = true;
db.SaveChanges();
}
It will depend on your scenario what you want to do, but generally speaking its fine to send your whole entity to be updated, and this is how i would approach your situation potentially changing in the future if needed.

Resources