MaxLength attributes depends on consuming class - c#

How do you set different MaxLength attributes on a class consumed by many classes where the MaxLength can be different for each consuming class.
In my case, I am using Entity Framework and MVC. I have a complex type in my Entity Data Model for BilingualStrings which consist of an English and a French string. I have many entities that have a bilingual string which uses the complex type to map the English and French to the correct database table/column. So each table has two columns, but the entity has one property of type BilingualString. The field length is always the same for each English or French in a single table, but can be different for each table.
Here is a simplified example of what I am trying to achieve:
public partial class BilingualString
{
//[MaxLength(40)] Cannot put MaxLength here because it would apply to all instances of BilingualString
public string English { get; set; }
public string French { get; set; }
}
public class ClassWithShortDescription
{
//[MaxLength(20)] Cannot put MaxLength here because it does not makes sense. It needs to be on each English and French properties.
public BilingualString Description { get; set; }
}
public class ClassWithLongDescription
{
//[MaxLength(200)] Cannot put MaxLength here because it does not makes sense. It needs to be on each English and French properties.
public BilingualString Description { get; set; }
}

After some reading and lots of googling, I concluded that the underlying issue is that the meta data set by data attributes is static. Therefore the nested class properties cannot have different meta data even if consumed by different classes. The solution is to put the meta data on the property of the consuming class. Create a custom maxlength attribute that is applied to the property of type BilingualString.
Custom MaxLength Attribute for BilingualString
public class MaxLengthBilingualStringAttribute : MaxLengthAttribute
{
public MaxLengthBilingualStringAttribute(int length) : base(length)
{
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
BilingualString bilingualString = new BilingualString();
if (value.GetType() == typeof(EdmBilingualStringVarCharSingleLine))
{
var bs = value as EdmBilingualStringVarCharSingleLine;
bilingualString.English = bs.English;
bilingualString.French = bs.French;
}
else
return new ValidationResult("MaxLengthBilingualString Attribute does cannot be used with this type.");
if (bilingualString.English != null && bilingualString.English.Length > this.Length )
return new ValidationResult(string.Format("The maximum field length of {0} has been exceed for {1} English.", this.Length, validationContext.DisplayName));
if (bilingualString.French != null && bilingualString.French.Length > this.Length)
return new ValidationResult(string.Format("The maximum field length of {0} has been exceed for {1} French.", this.Length, validationContext.DisplayName));
return ValidationResult.Success;
}
}
Example Implementation:
public partial class BilingualString
{
public string English { get; set; }
public string French { get; set; }
}
public class ClassWithShortDescription
{
[MaxLengthBilingualString(20)]
public BilingualString Description { get; set; }
}
public class ClassWithLongDescription
{
[MaxLengthBilingualString(200)]
public BilingualString Description { get; set; }
}

As English and French are hard-coded in BilingualString, you can also hard-code two attributes instead of one:
public class ClassWithShortDescription
{
[MaxLengthForEnglish(20)]
[MaxLengthForFrench(25)]
public BilingualString Description { get; set; }
}
public class ClassWithLongDescription
{
[MaxLengthForEnglish(200)]
[MaxLengthForFrench(250)]
public BilingualString Description { get; set; }
}

Related

Map JSON column from MySql Database to C# Class from Web Api

I have a MySql database with columns Id int and Name:json
Places Table Sample
Id Name
1 {"en":"Sphinx","ar":"أبو الهول","fr":"Le sphinx"}
C# Place class
public class Place
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public string Name { get; set; }
}
I'm connecting with EntityFramework 6 and connection success and retrieve data like this
{Id = 1, Name = "{\"en\":\"Sphinx\", \"ar\":\"أبو الهول\", \"fr\":\"Le sphinx\"}" }
What I want how to Map Name to new Object not JSON string
something like this
Place class
public class Place
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public Localized<string> Name { get; set; }
}
Localized class
public class Localized<T>
{
public T en { get; set; } // english localization
public T ar { get; set; } // arabic localization
public T fr { get; set; } // french localization
}
when I do this Name property come with NULL value
Code in Repository
using (var context = new PlacesEntityModel())
{
return context.Places.Take(5).ToList();
}
I don't want to use AutoMapper,
I want something in EntityFramework to select only one language in Database Level without fetching all other data and then map it
how to fix this?
You can try extension method to map from your entity type.
public class Place
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public string Name { get; set; }
}
public class PlaceDTO
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public Localized<string> Name { get; set; }
}
public class Localized<T>
{
public T en { get; set; } // english localization
public T ar { get; set; } // arabic localization
public T fr { get; set; } // french localization
}
Extenstion Method ToDto
public static class Extensions
{
public static PlaceDTO ToDto(this Place place)
{
if (place != null)
{
return new PlaceDTO
{
Id = place.Id,
Name = JsonConvert.DeserializeObject<Localized<string>>(place.Name)
};
}
return null;
}
}
Usage
var place = new Place() { Id = 1, Name = "{\"en\":\"Sphinx\", \"ar\":\"أبو الهول\", \"fr\":\"Le sphinx\"}" };
var placeDTO = place.ToDto();
Console.WriteLine($"{placeDTO.Id}-{placeDTO.Name.ar}-{placeDTO.Name.en}-{placeDTO.Name.fr}");
First of all, by using a class with a property per language, you restrict yourself. You'd always have to add new properties if you add new languages, which would of course be feasible, but unnecessary complicated. Furthermore you'd usually have the language as a string-ish object (or be able to convert), hence this would lead to code like this
Localized<string> name = ...;
switch(language)
{
case "en":
return name.en;
case "ar":
return name.ar;
case "fr":
return name.fr;
default:
throw new LocalizationException();
}
which is error-prone and overly complicated. For your problem, I think I'd opt to use some kind of dictionary
IDictionary<string, string> names = ...;
if(names.ContainsKey(language))
{
return names[language];
}
else
{
throw new LocalizationException();
}
which is easily extensible by just adding more translations to the dictionary.
To convert your JSON string to an IDcitionary<string, string>, you could use the following code
localizedNames = JObject.Parse(Name)
.Children()
.OfType<JProperty>()
.ToDictionary(property => property.Name,
property => property.Value.ToString());
From within your class this would effectively be
public class Place
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public string Name { get; set; }
public Dictionary<string, string> LocalizedNames
{
get
{
return JObject.Parse(Name)
.Children()
.OfType<JProperty>()
.ToDictionary(property => property.Name,
property => property.Value.ToString());
}
}
}
The localized values can be accessed like
var localizedPlaceName = place.LocalizedNames[language];
Please note: Depending on your needs and use cases, you should consider the following issues:
Caching
In my snippet, the JSON string is parsed every time the localized names are accessed. Depending on how often you access it, this might be detrimental to performance, which could be mitigated by caching the result (don't forget to delete the cache when Name is set).
Separation of concerns
The class as is is supposed to be a pure model class. You might want to introduce domain classes that encapsulate the presented logic, rather than adding the logic to the model class. Having a factory that creates readily localized objects based on the localizable object and the language could be an option, too.
Error handling
In my code there is no error handling. Depending on the reliability of input you should consider additional error handling.
devart.com/dotconnect/mysql/docs/EF-JSON-Support.html
Like what #Nkosi said
In that case then, take a look at this article devart.com/dotconnect/mysql/docs/EF-JSON-Support.html
It probably can given that the library was able to build that feature in. You would need to figure out what they they did (reverse engineer)
I usually just use JSON.Net, I notice that another answer referenced JObject, but without going into whether your data-model is the right model, I generally find that you can do:
var MyObjectInstance = JObject.Parse(myJsonString).ToObject<MyObjectType>();
I notice that you have ComponentModel attributes on your class. I don't know off hand how many of these JSon.Net supports, and you'd have to research that. It definitely supports some attributes from XML serialization, and also has some of it's own.
Note that you can also convert a JSOn array into a list:
var MyObjectList = JArray.Parse(myJsonString).ToObject<IEnumerable<MyObjectType>();
I want something in EntityFramework to select only one language in
Database Level without fetching all other data and then map it
if you want it to be from database level, you can always create a view and then include this view in your project.
Example :
CREATE VIEW `PlacesLocalized` AS
SELECT
Id
, TRIM(REPLACE(name->'$.en', '"','')) AS en
, TRIM(REPLACE(name->'$.ar', '"','')) AS ar
, TRIM(REPLACE(name->'$.fr', '"','')) AS fr
FROM
places
This would create a model class Like :
public class PlacesLocalized
{
public int Id { get; set; }
public string en {get; set;}
public string ar {get; set;}
public string fr {get; set;}
}
Then, you can do :
var places = context.PlacesLocalized.Where(x=> x.en == "Sphinx");
But if you don't have enough permissions to do this in the database level, then you would need to specify the query in your EF. There is no easy way to change the execution logic of Entity Framework just for specific classes. That's why Entity Framework included SqlQuery method, which would give more flexibility to have custom queries when needed (like yours).
So, if you need to specify the localization from Entity Framework, then you would do a repository class to specify all custom queries you need including creating any DTO needed.
The basic way would be something like this :
public enum Localized
{
English,
Arabic,
French
}
public class PlaceRepo : IDisposable
{
private readonly PlacesEntityModel _context = new PlacesEntityModel();
public List<Place> GetPlacesLocalized(Localized localized = Localized.English)
{
string local = localized == Localized.Arabic ? "$.ar"
: localized == Localized.French ? "$.fr"
: "$.en";
return _context.Places.SqlQuery("SELECT Id, name-> #p0 as Name FROM places", new[] { local })
.Select(x=> new Place { Id = x.Id, Name = x.Name.Replace("\"", string.Empty).Trim() })
.ToList();
}
private bool _disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_context.Dispose();
}
_disposed = true;
}
}
~PlaceRepo()
{
Dispose(false);
}
}
now, you can do this :
using(var repo = new PlaceRepo())
{
var places = repo.GetPlacesLocalized(Localized.Arabic);
}
public class Place
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public string Name { get; set; }
public static explicit operator Place(PlaceDTO dto)
{
return new Place()
{
Id = dto.Id,
Name = dto.Name
};
}
}
public class PlaceDTO
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public Localized<string> Name { get; set; }
public static explicit operator PlaceDTO(Place pls)
{
return new PlaceDTO()
{
Id = pls.Id,
Name = pls.Name
};
}
}
var placeDTO = (placeDto)place;
we can achieve this using explicit operator without using auto mapper

Validation strategy

I'm trying to build a series of attribute classes to make it easier for our development team to validate objects. The objects are POCO classes like this.
public class User
{
public string Name { get; set; }
public string Company { get; set; }
}
I want to decorate this model with a custom attribute.
public class User
{
[MustHaveValue]
public string Name { get; set; }
public string Company { get; set; }
}
Then I would create my own class implementing ValidationAttribute, the base class in .NET Framework, which belongs to System.ComponentModel.DataAnnotations.
public class MustHaveValueAttribute : ValidationAttribute
{
.
.
public override IsValid(object value)
{
// validation logic.
}
}
And then I can validate the User model whenever I want by making the set of instances like ValidationContext, List<ValidationResult>.
But in an enterprise environment, problems just can't be solved by a specific class. My validation scenario requires more complex and more flexible ways. Imagine that one of the required validation scenarios would something like this.
public class User
{
public string Name { get; set; }
public string Company { get; set; }
// Check if an item exists in this list.
[MustHaveMoreThanOneItem]
public IList<Client> Clients { get; set; }
}
Then I would need to make another attribute class
public class MustHaveMoreThanOneItemAttribute : ValidationAttribute
{
.
.
public override IsValid(object value)
{
// Let's assume this value is List<Client> for now.
// I know the exact type, so I'm going to cast it to List<Client> without further considerations
List<Client> clients = value as List<Client>;
if(clients.Count > 0) {
return true;
} else {
return false;
}
}
}
But the problem is that there are a lot of other models that have a nested list items. Try to imagine the time when I want to reuse the MustHaveMoreThanOneItem in one of the other models like...
public class Department
{
public string Name { get; set; }
[MustHaveMoreThanOneItem]
public IList<Employee> { get; set; }
}
You already know that it's not going to work because it was strongly typed only for List<Client>. So I decided to use Generic there to solve this problem.
But to my disappointment, the _Attribute interface doesn't support Generic. There's no additional implementation like _Attribute<T> : Attribute and therefore, no ValidationAttribute<T> alas!! I just cannot use Generic here !!
public class Department
{
public string Name { get; set; }
// No way to use this syntax.
[MustHaveMoreThanOneItem<Employee>]
public IList<Employee> { get; set; }
}
So I made a conclusion that Attribute must have been designed for a fixed set of validations like email format, card format, null check, and etc IMAO.
But I still want to use an attribute and give a lot of flexibilities in it to prevent the duplicated, verbose validation codes like this.
if(model.Clients.Count > 0) ...
if(model.Name != null) ...
if(model.Clients.GroupBy(x => x.Country == Country.USA).Count >= 1) ...
if(model.Clients.Where(x => x.CompanyName == Company.Google).ToList().Count > 1 ) ...
.
.
.
I want to pose two questions here.
If Attirbute supports Generic, this problem will be solved?
Is there any way to implement Generic Attribute? in order to use
[MustHaveMoreThanOneItem<Employee>] annotation on a class member?
You can generically check any object that implements IEnumerable like this:
public class MustHaveMoreThanOneItemAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
// omitted null checking
var enumerable = value as IEnumerable;
var enumerator = enumerable.GetEnumerator();
if (!enumerator.MoveNext())
{
return false;
}
if (!enumerator.MoveNext())
{
return false;
}
return true;
}
}
C# by definition does not support generic type attributes, although this has been requested actively for a long time:
https://github.com/dotnet/roslyn/issues/953
https://github.com/dotnet/csharplang/issues/124
However, you can still inject a type into a validation attribute via constructor. You then can use reflection or whatever you need to define your custom validation criteria.
public class MustHaveMoreThanOneItemAttribute : ValidationAttribute
{
public Type EnumerableType { get; }
public MustHaveMoreThanOneItemAttribute(Type t)
=> this.EnumerableType = typeof(ICollection<>).MakeGenericType(t);
public override bool IsValid(object value)
{
var count = this.EnumerableType.GetProperty("Count").GetValue(value) as int?;
return (count ?? 0) > 1;
}
}
Now this allows you to use something similar to your goal:
public class Department
{
public string Name { get; set; }
[MustHaveMoreThanOneItem(typeof(Employee))]
public IList<Employee> { get; set; }
}

error 1061. T doesn't contain a definition for <propertyName>

i am having around 7 models who have same properties(atributes). On view page i am using a model(name = commonModel) which contains all those properties and a extra property to choose in which model's database i want to save that sent data so i created a valuesRelocate Method that will assign all the properties of commonModel to the choosen model (in this case article).
The code i gave below is working but i am getting a error when assigning value of a property of commonModel to a property of article.
What is the better way to do this.
Error is at tempModel.question
public ActionResult Create([Bind(Include =
"Id,question,ans,ruleApplicable,hint,exception,modelSelector")]
commonModel commonModel)
{
if (ModelState.IsValid)
{
if (commonModel.modelSelector == "article")
{
article model2 = new article();
article model1 = valuesRelocate<article>(commonModel,
model2);
db.articleDb.Add(model1);
db.SaveChanges();
return RedirectToAction("Index");
}
}
return View(commonModel);
}
private T valuesRelocate<T>(commonModel commonModel, T tempModel) {
tempModel.question = commonModel.question;
return tempModel;
}
I am using a abstract base class named baseGrammar .code for both the class is shown below
public abstract class baseGrammar
{
[Key]
public int Id { get; set; }
[Required]
public string question { get; set; }
[Required]
public string ans { get; set; }
public string ruleApplicable { get; set; }
public string hint { get; set; }
public bool exception { get; set; }
}
the one shown above is base class
and those shown below are derived classes
i use different classes because i wanted to have different classes for different grammatical concepts.
public class article : baseGrammar
{
}
public class commonModel : baseGrammar
{
[Required]
public string modelSelector { get; set; }
}
hope this helps.
You just need to constrain the type parameter T to be derived from your base class:
// Names changed to follow .NET naming conventions
private T RelocateValues<T>(BaseGrammar baseModel, T tempModel)
where T : BaseGrammar
{
tempModel.question = baseModel.question;
return tempModel;
}
However, given that you're modifying the incoming model, you could remove the return value and just change the method to:
private void RelocateValues(BaseGrammar from, BaseGrammar to)
{
to.question = from.question;
}
Then in your calling code:
Article model = new Article();
RelocateValues(model);
db.ArticleDb.Add(model);
There's no need to have two separate variables which will refer to the same object anyway...

ASP.NET MVC: Custom Validation by DataAnnotation

I have a Model with 4 properties which are of type string. I know you can validate the length of a single property by using the StringLength annotation. However I want to validate the length of the 4 properties combined.
What is the MVC way to do this with data annotation?
I'm asking this because I'm new to MVC and want to do it the correct way before making my own solution.
You could write a custom validation attribute:
public class CombinedMinLengthAttribute: ValidationAttribute
{
public CombinedMinLengthAttribute(int minLength, params string[] propertyNames)
{
this.PropertyNames = propertyNames;
this.MinLength = minLength;
}
public string[] PropertyNames { get; private set; }
public int MinLength { get; private set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var properties = this.PropertyNames.Select(validationContext.ObjectType.GetProperty);
var values = properties.Select(p => p.GetValue(validationContext.ObjectInstance, null)).OfType<string>();
var totalLength = values.Sum(x => x.Length) + Convert.ToString(value).Length;
if (totalLength < this.MinLength)
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
}
and then you might have a view model and decorate one of its properties with it:
public class MyViewModel
{
[CombinedMinLength(20, "Bar", "Baz", ErrorMessage = "The combined minimum length of the Foo, Bar and Baz properties should be longer than 20")]
public string Foo { get; set; }
public string Bar { get; set; }
public string Baz { get; set; }
}
Self validated model
Your model should implement an interface IValidatableObject. Put your validation code in Validate method:
public class MyModel : IValidatableObject
{
public string Title { get; set; }
public string Description { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Title == null)
yield return new ValidationResult("*", new [] { nameof(Title) });
if (Description == null)
yield return new ValidationResult("*", new [] { nameof(Description) });
}
}
Please notice: this is a server-side validation. It doesn't work on client-side. You validation will be performed only after form submission.
ExpressiveAnnotations gives you such a possibility:
[Required]
[AssertThat("Length(FieldA) + Length(FieldB) + Length(FieldC) + Length(FieldD) > 50")]
public string FieldA { get; set; }
To improve Darin's answer, it can be bit shorter:
public class UniqueFileName : ValidationAttribute
{
private readonly NewsService _newsService = new NewsService();
public override bool IsValid(object value)
{
if (value == null) { return false; }
var file = (HttpPostedFile) value;
return _newsService.IsFileNameUnique(file.FileName);
}
}
Model:
[UniqueFileName(ErrorMessage = "This file name is not unique.")]
Do note that an error message is required, otherwise the error will be empty.
Background:
Model validations are required for ensuring that the received data we receive is valid and correct so that we can do the further processing with this data. We can validate a model in an action method. The built-in validation attributes are Compare, Range, RegularExpression, Required, StringLength. However we may have scenarios wherein we required validation attributes other than the built-in ones.
Custom Validation Attributes
public class EmployeeModel
{
[Required]
[UniqueEmailAddress]
public string EmailAddress {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public int OrganizationId {get;set;}
}
To create a custom validation attribute, you will have to derive this class from ValidationAttribute.
public class UniqueEmailAddress : ValidationAttribute
{
private IEmployeeRepository _employeeRepository;
[Inject]
public IEmployeeRepository EmployeeRepository
{
get { return _employeeRepository; }
set
{
_employeeRepository = value;
}
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
var model = (EmployeeModel)validationContext.ObjectInstance;
if(model.Field1 == null){
return new ValidationResult("Field1 is null");
}
if(model.Field2 == null){
return new ValidationResult("Field2 is null");
}
if(model.Field3 == null){
return new ValidationResult("Field3 is null");
}
return ValidationResult.Success;
}
}
Hope this helps. Cheers !
References
Code Project - Custom Validation Attribute in ASP.NET MVC3
Haacked - ASP.NET MVC 2 Custom Validation
A bit late to answer, but for who is searching.
You can easily do this by using an extra property with the data annotation:
public string foo { get; set; }
public string bar { get; set; }
[MinLength(20, ErrorMessage = "too short")]
public string foobar
{
get
{
return foo + bar;
}
}
That's all that is too it really. If you really want to display in a specific place the validation error as well, you can add this in your view:
#Html.ValidationMessage("foobar", "your combined text is too short")
doing this in the view can come in handy if you want to do localization.
Hope this helps!

How to differentiate class properties using user-defined tags (custom-attributes)

Background:
I have a custom class, which represents a Data Base Table, each property corresponding to a table column. The properties can be classified in three ways.
Example: Take for example a Person object.
MetaProperties: (Columns that are needed by the program)
Person_ID: used in table for indexing etc...
UserDefinedType: (UDT), complex class handling write-permission on the table.
Timestamp: needed to handle the UDT in C# DataTables
RealProperties: (actual traits that describe the real Person)
FullName
DateOfBirth
PlaceOfBirth
EyeColor
etc... (many more)
RawDataProperties: (these columns hold raw data from external sources)
Phys_EyeColor: the eye-color, as directly imported from the physical traits database, may be in unknown format, may have conflicting value with entry from other db, or any other data quality issue...
HR_FullName: full name as given in HR file
Web_FullName: full name as taken from a web form
Web_EyeColor: eye color as taken from web form
etc...
public class Person
{
#region MetaProperties
public int Person_ID { get; set; }
public UserDefinedType UDT { get; set; }
public DateTime timestamp { get; set; }
#endregion
#region RealProperties
public string FullName { get; set; }
public DateTime DateOfBirth { get; set; }
public string PlaceOfBirth { get; set; }
public Color EyeColor { get; set; }
//...
#endregion
#region RawDataProperties
public string Phys_EyeColor { get; set; }
public string Phys_BodyHeight { get; set; }
public string Web_FullName { get; set; }
public string Web_EyeColor { get; set; }
public string HR_FullName { get; set; }
//...
#endregion
}
Question: How can I programmatically differentiate between these three types of properties in my Person class? The goal is to be able to iterate through properties of a certain type using System.Reflection or some other organisational construct. Pseudocode:
foreach(Property prop in Person.GetPropertiesOfType("RealProperty"){
... doSmth(prop);
}
I'm thinking about writing custom Attributes, and hanging them on to the properties, sort of like taggin.
But since I know nothing about custom Attributes, I would like to ask if I'm going down the proper path, or if there are any other better ways of doing this.
Note: the shown example may may not be the best in terms of program design, and I am well aware that inheritance or splitting up the class otherwise could solve this problem. But that is not my question - I want to know if properties in a class can be tagged or somehow differentiated between using custom categories.
You can do this with custom attributes.
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class PropertyAttribute : System.Attribute
{
public PropertyType Type { get; private set; }
public PropertyAttribute (PropertyType type) { Type = type; }
}
public enum PropertyType
{
Meta,
Real,
Raw,
}
Then, you can do this with each property or field:
[PropertyType(PropertyType.Meta)]
public int Person_ID;
[PropertyType(PropertyType.Real)]
public string FullName;
[PropertyType(PropertyType.Raw)]
public string Phys_EyeColor;
Then you can access it with something like
foreach (PropertyAttribute attr in this.GetType().GetCustomAttributes(typeof(PropertyAttribute), false))
{
// Do something based on attr.Type
}

Categories

Resources