3
\$\begingroup\$

I have an auto generated partial class in my Model folder with lots of properties and I need to modify some of the setters in these properties(annotate property), to ensure that I don't repeat myself in the future (after a refresh) I have another partial class with the same properties by use of attributes, where these properties cannot be modified, only via provided attributes.

So I have built a custom attribute class to modify those properties. Now my question is, am I doing this correctly or am I just creating a mess. Is there a better or another way to achieve my goal? Please explain how to approach this delicate matter.

This is an auto generated class in Models folder:

public partial class Stud
{
    public string Property1{ get; set; }
    public string Property20{ get; set; }
}


This is the class for mine metadata with properties that I want to modify:

public class MetadataClasses
{

    public class StudMetadata
    {
        [Required, DisplayName("Property one"), UpperCaseAttribute]
        public string Property1 { get; set; }

        [Required, DisplayName("Property two"), ReplaceNrAttribute]
        public string Property2{ get; set; }
    }
}


This is an custom attribute class in different folder called Attributes:

public class UpperCaseAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        try
        {
            validationContext
                .ObjectType
                .GetProperty(validationContext.MemberName)
                .SetValue(validationContext.ObjectInstance, value.ToString().ToUpper(), null);
        }
        catch (System.Exception)
        {
        }
        return null;
    }
}


This is an partial class named "PartialClasses.cs" in Models folder:

[MetadataType(typeof(MetadataClasses.StudMetadata))] 
public abstract partial class Stud 
{
} 
\$\endgroup\$
2
\$\begingroup\$

I hope it's not too short for an answer but:

VALIDATION attributes MUST NOT manipulate and change the property they decorate.

Do it in the property logic or use some other kind of attribute for that (with your own logic) but do not misuse validation.

Working with generated code isn't easy in this case. It depends on how much freedom you have to instruct generator (for example using a template for setter you may easily achieve desired result). EF (in case you're using it) lets you customize .tt file (but that's usually a pain, I find easier to move to a code-first approach). If you don't have usable extension points you then you may give a chance to an AOP.


About your code, ignoring attribute usage, you should not catch Exception. In this case you don't know what may happen but at least you should ignore critical exceptions like OutOfMemoryException and similar.

try
{
}
catch (Exception e) when (IsNotCriticalException(e))
{
}

Where IsNotCriticalException() is a method you write to determine if exception can be ignored or it must escalate.

If value might be null, in this case you're throwing a NullReferenceException. Handle the case instead of throwing.

[UpperCaseAttribute] may simply be [UpperCase] or, better, [TransformToUpperCase] because it communicates what it will do.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Your answer is clear, I had my doubts about my own solution that's why asked here, I want a DRY and clean code. Anyways do you have any idea how to use an attribute to annotate properties in the partial class(the correct way)? \$\endgroup\$ – H35am Aug 23 '17 at 14:01
  • \$\begingroup\$ Working with generated code isn't easy in this case. It depends on how much freedom you have to instruct generator (for example using a template for setter you may easily achieve desired result). EF (in case you're using it) lets you customize .tt file (but that's usually a pain, I find easier to move to a code-first approach). If you don't have usable extension points you then you may give a chance to an AOP approach. \$\endgroup\$ – Adriano Repetti Aug 23 '17 at 14:18
  • \$\begingroup\$ So basically I cant use a custom designed attribute? \$\endgroup\$ – H35am Aug 23 '17 at 14:49
  • \$\begingroup\$ You can, and it's a nice thing to do, but you shouldn't rely on Validation infrastructure. If you do not want to/can't change code generator and AOP isn't an option then write your own class to go through all the properties and to apply your custom attributes (it will also be faster because you won't need to use Reflection to get the job done). \$\endgroup\$ – Adriano Repetti Aug 23 '17 at 14:55
1
\$\begingroup\$

As Adriano Repetti's answer has stated, a ValidationAttribute is for validating data, not for altering it. The method should only ever return either true or false (using the bool IsValid() overload), or a ValidationResult (with your current overload), and the result is that an error message will be displayed to the user indicating the value they entered is not valid.

But you should not be dictating the the users of your app that they use their CAPSLOCK key to enter data. If you want it to be saved in UPPERCASE, then that is your responsibility to do so, not the users, so your attribute is not even appropriate.

In MVC, especially when editing data, you should always use a view model, not partial classes. View models have multiple benefits including Separation of Concerns, protection against under and over posting attack, being able to include view specific properties (e.g. IEnumerable<SelectListItem> properties for use in dropdownlists) and annotations (e.g. [DisplayFormat]) that are not appropriate in data models, including only those properties of your data model that are needed in the view etc.

Your view model would be (say)

public class StudVM
{
    public string Property1 { get; set; }
    ...
}

and in the POST method, you map the properties of your view model to an instance of your data model. For a Create() method, a new instance of Stud, and for a Edit() method, you would get the original data model from the database. Then when mapping, you just convert the value in the view model to uppercase, for example

public ActionResult Create(StudVM model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }
    Stud stud = new Stud
    {
        Property1 = model.Property1.ToUpper()
        ....
    };
    // Save and redirect
}

Refer also What is ViewModel in MVC?

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Your answer is indicating that I should use MVVM, but I don't want that because in MVC it is advised to use partial classes and validation thru validation attributes(at least this is what I've learned at our university) also you mentioned under and over posting attack this must be taken care off in your Controller not in your View or Model. Anyways thanks for this new perspective. \$\endgroup\$ – H35am Aug 24 '17 at 8:25
  • \$\begingroup\$ Its not MVVM. And we all use view models in asp.net-mvc. Its hard to believe that you were told that at a University. And clearly you do not understand what I have said about over and under posting attacks! \$\endgroup\$ – Stephen Muecke Aug 24 '17 at 8:31
  • \$\begingroup\$ For example, refer to this answer to explain under-posting attacks (you cannot do that in a Controller!) \$\endgroup\$ – Stephen Muecke Aug 24 '17 at 8:33
  • \$\begingroup\$ I meant Model not Controller (sorry) but somehow I can't edit my comment :) \$\endgroup\$ – H35am Aug 24 '17 at 8:52
  • \$\begingroup\$ Yes exactly - so if your data model contains a property which is typeof DateTime, then your view model contains DateTime? (nullable) and is marked with the [Required] attribute \$\endgroup\$ – Stephen Muecke Aug 24 '17 at 8:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.