Get Data Annotations attributes from model
Asked Answered
C

2

9

I want to create custom client-side validator, but I want define validation rules via Data Annotations attributes at business logic layer. How can I access model validation attributes in runtime?

I want to write 'generator', which will convert this code:

public class LoginModel
{
    [Required]
    [MinLength(3)]
    public string UserName { get; set; }

    [Required]
    public string Password { get; set; }
}

into this one:

var loginViewModel= {
    UserName: ko.observable().extend({ minLength: 3, required: true }),
    Password: ko.observable().extend({ required: true })
};

But not from .cs sources, of course. =)

Maybe reflection?

UPD

I've found this method: MSDN. But can't understand how to use it.

Cooncan answered 28/10, 2013 at 14:57 Comment(3)
Yes, Reflection. What else?Soften
Reflection is always an option, but any particular reason you want to avoid doing this from sources? T4 + EnvDTE seems like a solid choice here.Hough
@HenkHolterman I've read mvc sources, and found this method: msdn.microsoft.com/en-us/library/… but can't understand how to use it. Maybe someone have better idea, than reflection? =)Cooncan
J
15

This is the universal way how to do this:

private string GenerateValidationModel<T>()
{
    var name = typeof(T).Name.Replace("Model", "ViewModel");
    name = Char.ToLowerInvariant(name[0]) + name.Substring(1);

    var validationModel = "var " + name + " = {\n";

    foreach (var prop in typeof(T).GetProperties())
    {
        object[] attrs = prop.GetCustomAttributes(true);
        if (attrs == null || attrs.Length == 0)
            continue;

        string conds = "";

        foreach (Attribute attr in attrs)
        {
            if (attr is MinLengthAttribute)
            {
                conds += ", minLength: " + (attr as MinLengthAttribute).Length;
            }
            else if (attr is RequiredAttribute)
            {
                conds += ", required: true";
            }
            // ...
        }

        if (conds.Length > 0)
            validationModel += String.Format("\t{0}: ko.observable().extend({{ {1} }}),\n", prop.Name, conds.Trim(',', ' '));
    }

    return validationModel + "};";
}

Usage:

string validationModel = GenerateValidationModel<LoginModel>();

Output:

var loginViewModel = {
    UserName: ko.observable().extend({ minLength: 3, required: true}),
    Password: ko.observable().extend({ required: true}),
};

It's good idea to cache the output

Jauregui answered 28/10, 2013 at 15:21 Comment(0)
H
3

As commented above - I believe T4 might be worth a shot here. A huge benefit is that it's not executed at runtime (though it can, if that's your requirement) and you can avoid all possible issues with runtime file generation. Hopefully a sufficient starting point:

<#@ template language="C#" debug="True" hostspecific="true" #>
<#@ output extension="js" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="EnvDTE" #>
<#
    var serviceProvider = Host as IServiceProvider;
    if (serviceProvider == null)
    {
        throw new InvalidOperationException("Host is not IServiceProvider");
    }

    var dte = serviceProvider.GetService(typeof(DTE)) as DTE;
    if (dte == null)
    {
        throw new InvalidOperationException("Unable to resolve DTE");
    }

    var project = dte.Solution.Projects
                              .OfType<Project>()
                              .Single(p => p.Name == "ConsoleApplication2");

    var model = project.CodeModel
                       .CodeTypeFromFullName("MyApp.LoginModel")
                   as CodeClass;
    //might want to have a list / find all items matching some rule

#>
var <#= Char.ToLowerInvariant(model.Name[0])
        + model.Name.Remove(0, 1).Replace("Model", "ViewModel") #>= {
<#
    foreach (var property in model.Members.OfType<CodeProperty>())
    {
        var minLength = property.Attributes
                                    .OfType<CodeAttribute>()
                                    .FirstOrDefault(a => a.Name == "MinLength");
        var required = property.Attributes
                               .OfType<CodeAttribute>()
                               .FirstOrDefault(a => a.Name == "Required");

        var koAttributes = new List<String>();
        if (minLength != null)
            koAttributes.Add("minLength: " + minLength.Value);
        if (required != null)
            koAttributes.Add("required: true");
#>
    <#= property.Name #>: ko.observable().extend({<#=
String.Join(", ", koAttributes) #>}),
<#
    }
#>
}
Hough answered 28/10, 2013 at 15:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.