locked
Read custom data annotation and its parameters on View RRS feed

  • Question

  • User1501362304 posted

    Hello,

    Is there any way to get custom data annotation applied on a model property in view so that we can do custom client check before submitting form.

    Use case:
    I have a below model with custom data annotation

    public class User
    {
            [Display(Name = "Profile image")]
            [Required(ErrorMessage = "{0} is required")]
            [DataType(DataType.Upload)]
            [MaxFileSize(maxFileSizeInBytes: (1 * 1024 * 1024), ErrorMessage = "{0} size should be maximum {1}")]
            [AllowedExtensions(allowedExtensions: new string[] { ".jpg", ".png", ".jpeg" }, ErrorMessage = "{0} should be {1} only")]
            public IFormFile UserProfileImageFile { get; set; }
    }
    

    Now in View form where I am binding this with file upload control I would like to get AllowedExtensions -> allowedExtensions
    & MaxFileSize -> maxFileSizeInBytes values and assign in respective data- attributes.

    <form>
    <div class="form-group form-group-half">
       <label asp-for="UserProfileImageFile">User profile image *</label>
       <input asp-for="UserProfileImageFile" 
    accept="[read from AllowedExtensions -> allowedExtensions]"
    data-maxsize="[read from MaxFileSize -> maxFileSizeInBytes]"
    class="form-control sl-form-control"> <span asp-validation-for="UserProfileImageFile" class="text-danger"></span> </div> </form>

    Thanks


    Friday, October 23, 2020 5:04 PM

All replies

  • User-474980206 posted

    You need to extend the input tag helper. Here a sample that adds an attribute, 

      http://rion.io/2017/04/27/extending-tag-helpers-in-asp-net-core-applications/

    but you want to use reflection instead to get the max length of the attribute.

      https://docs.microsoft.com/en-us/dotnet/standard/attributes/retrieving-information-stored-in-attributes

    its a pretty trivial update. Give it a try, and post any questions if stuck.

    Friday, October 23, 2020 11:51 PM
  • User1312693872 posted

    Hi,vkagrawal

    By using bruce's link, I made a demo for you to refer, if you have any questions, welcome to post your questions:

    CustomTagHelper:

    [HtmlTargetElement("input", Attributes = "asp-for")]
        public class CustomTagHelper : TagHelper
        {
            public override int Order { get; } = int.MaxValue;
    
            [HtmlAttributeName("asp-for")]
            public ModelExpression For { get; set; }
    
            public override void Process(TagHelperContext context, TagHelperOutput output)
            {
                base.Process(context, output);
    
                if (context.AllAttributes["data-maxsize"] == null)
                {
                    var maxLength = GetMaxLength(For.ModelExplorer.Metadata.ValidatorMetadata);
                    if (maxLength > 0)
                    {
                        output.Attributes.Add("data-maxsize", maxLength);
                    }
                }
                if (context.AllAttributes["accept"] == null)
                {
                    string[] allowed = Getallowed(For.ModelExplorer.Metadata.ValidatorMetadata);
                    if (allowed !=null)
                    {
                        string allowed1 = string.Join(",", allowed);     //convert array to string
                        output.Attributes.Add("accept",allowed1);
                    }
                }
            }
    
            private static int GetMaxLength(IReadOnlyList<object> validatorMetadata)
            {
                for (var i = 0; i < validatorMetadata.Count; i++)
                {              
                    if (validatorMetadata[i] is MaxFileSizeAttribute maxFileSizeAttribute && maxFileSizeAttribute.maxFileSizeInBytes > 0)
                    {
                        return maxFileSizeAttribute.maxFileSizeInBytes;
                    }              
                }
                return 0;
            }
            private static string[] Getallowed(IReadOnlyList<object> validatorMetadata)
            {
                for (var i = 0; i < validatorMetadata.Count; i++)
                {
                    if (validatorMetadata[i] is AllowedExtensionsAttribute allowedExtensionsAttribute && allowedExtensionsAttribute.allowedExtensions!=null)
                    {
                        return allowedExtensionsAttribute.allowedExtensions;
                    }
                }
                return null;
            }
        }

    CustomAttribute class (almost the same like another):

    public class AllowedExtensionsAttribute : ValidationAttribute
        {
            public string[] allowedExtensions;
            public AllowedExtensionsAttribute(string[] allowedExtensions)
            {
                this.allowedExtensions = allowedExtensions;
            }
        }

    Result:

    Best Regards,

    Jerry Cai

    Monday, October 26, 2020 8:25 AM
  • User1501362304 posted

    Hi @Bruce and @Jerry, thanks for the reference and assistance but I have question here that how do I use that custom tag helper on View? 
    It should work with only desired input control.

    Monday, October 26, 2020 3:40 PM
  • User1312693872 posted

    Hi,vkagrawal

    vkagrawal

    It should work with only desired input control.

    What is 'desired input control' means? The offered method is used to override the <input> tag, you just need to use <input> and then use F12 to check the generated html.

    Example: 

    <input asp-for="UserProfileImageFile" class="form-control sl-form-control">

    will be the following code in F12 html:

    <input class="form-control sl-form-control" type="file" data-val="true" data-val-required="Profile image is required" id="UserProfileImageFile" name="UserProfileImageFile" data-maxsize="1048576" accept=".jpg,.png,.jpeg">

    And if the 'desired input control' means specific tag, then create a customed TagHelper, the procedure is almost the same. 

    Best Regards,

    Jerry Cai

    Tuesday, October 27, 2020 8:18 AM
  • User1501362304 posted

    Hi @jerry,

    Desired input control means that there are many input fields on view(one for email, another for password, another for username, another for image upload). So I want those additional attributes only on image upload input not on all other input elements of view.

    Also, does not want that to be applied on all views, it should be applied on specified view and specified input control.

    Thanks

    Tuesday, October 27, 2020 3:22 PM
  • User1312693872 posted

    Hi,vkagrawal

    Also, does not want that to be applied on all views, it should be applied on specified view and specified input control.

    Ok, then you can use a customed tag helper for the image upload input , you can check my demo:

    [HtmlTargetElement("custominput", Attributes = "asp-for")]
        public class CustomTagHelper : TagHelper
        {
            public override int Order { get; } = int.MaxValue;
    
            [HtmlAttributeName("asp-for")]
            public ModelExpression For { get; set; }
    
            public override void Process(TagHelperContext context, TagHelperOutput output)
            {
                output.TagName = "input";
                output.Attributes.RemoveAll("asp-for");
                output.Attributes.SetAttribute("type", "file");
                output.Attributes.SetAttribute("data-val", "true");
                output.Attributes.SetAttribute("data-val-required", "Profile image is required");
                output.Attributes.SetAttribute("id", "UserProfileImageFile");
                output.Attributes.SetAttribute("name", "UserProfileImageFile");
                base.Process(context, output);
    
                // Process only if 'maxlength' attribute is not present already
                if (context.AllAttributes["data-maxsize"] == null)
                {
                    // Attempt to check for a MaxLength annotation
                    var maxLength = GetMaxLength(For.ModelExplorer.Metadata.ValidatorMetadata);
                    if (maxLength > 0)
                    {
                        output.Attributes.Add("data-maxsize", maxLength);
                    }
                }
                if (context.AllAttributes["accept"] == null)
                {
                    // Attempt to check for a MaxLength annotation
                    string[] allowed = Getallowed(For.ModelExplorer.Metadata.ValidatorMetadata);
                    if (allowed !=null)
                    {
                        string allowed1 = string.Join(",", allowed);
                        output.Attributes.Add("accept",allowed1);
                    }
                }
            }
    
    
            private static int GetMaxLength(IReadOnlyList<object> validatorMetadata)
            {
                for (var i = 0; i < validatorMetadata.Count; i++)
                {              
                    if (validatorMetadata[i] is MaxFileSizeAttribute maxFileSizeAttribute && maxFileSizeAttribute.maxFileSizeInBytes > 0)
                    {
                        return maxFileSizeAttribute.maxFileSizeInBytes;
                    }              
                }
                return 0;
            }
            private static string[] Getallowed(IReadOnlyList<object> validatorMetadata)
            {
                for (var i = 0; i < validatorMetadata.Count; i++)
                {
                    if (validatorMetadata[i] is AllowedExtensionsAttribute allowedExtensionsAttribute && allowedExtensionsAttribute.allowedExtensions!=null)
                    {
                        return allowedExtensionsAttribute.allowedExtensions;
                    }
                }
                return null;
            }
        }

    View:

    <input asp-for="UserProfileImageFile" class="form-control sl-form-control" />
    <custominput asp-for="UserProfileImageFile" class="form-control sl-form-control" />

    Result in F12, only <custominput> tag will show the size and accept:

    <input class="form-control sl-form-control" type="file" data-val="true" data-val-required="Profile image is required" id="UserProfileImageFile" name="UserProfileImageFile" />
    <input class="form-control sl-form-control" type="file" data-val="true" data-val-required="Profile image is required" id="UserProfileImageFile" name="UserProfileImageFile" data-maxsize="1048576" accept=".jpg,.png,.jpeg" />

    Best Regards,

    Jerry Cai

    Wednesday, October 28, 2020 8:16 AM
  • User1501362304 posted

    Hi @Jerry,

    Custom tag helper is not being called, I have tried combinations.

    My custom tag helper's outer body

    namespace Sample.WEB.Helper
    {
        [HtmlTargetElement("customfileinput", Attributes = "asp-for")]
        public class CustomFileTagHelper : TagHelper
        {
            public override int Order { get; } = int.MaxValue;
    
            [HtmlAttributeName("asp-for")]
            public ModelExpression For { get; set; }
    
            public override void Process(TagHelperContext context, TagHelperOutput output)
            {
                output.TagName = "input";
                output.Attributes.RemoveAll("asp-for");
                output.Attributes.SetAttribute("type", "file");
                output.Attributes.SetAttribute("id", "UserProfileImageFile");
                output.Attributes.SetAttribute("name", "UserProfileImageFile");
                base.Process(context, output);
    
                if (context.AllAttributes["data-maxsize"] == null)
                {
                    // Rest of the code
                }
         }
    }

    Calling it in combination but nothing working

    <CustomFileTagHelper asp-for="UserProfileImageFile" class="form-control sl-form-control" />
    <customfileinput asp-for="UserProfileImageFile" class="form-control sl-form-control" />

    and registered namespace in _viewimports file

    @addTagHelper *, Sample.WEB.Helper

    Can you please let me know what I may be missing or doing wrong?

    Wednesday, October 28, 2020 3:51 PM
  • User1312693872 posted

    Hi,vkagrawal

    @addTagHelper *, Sample.WEB.Helper

    You can try to use your project root name only.

    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
    @addTagHelper *, Sample

    Best Regards,

    Jerry Cai

    Thursday, October 29, 2020 1:33 AM
  • User1501362304 posted

    Ok, got it working.. had to provide assembly name in viewimports file.. but this entire custom tag helper solution does not seem good because there may be multiple file controls and I can't explicitly want to specify property name and id "UserProfileImageFile" and many more within tag helper.

    Friday, October 30, 2020 6:40 PM
  • User1312693872 posted

    Hi,vkagrawal

    and I can't explicitly want to specify property name and id "UserProfileImageFile" and many more within tag helper.

    If you want to make Tag Helper usage explicit, then you can use @tagHelperPrefix to enable the specific TagHelpers, details are in

    https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro?view=aspnetcore-2.1#managing-tag-helper-scope

    You can see the rules of targeting tag helpers there

    Best Regards,

    Jerry Cai

    Monday, November 2, 2020 2:34 AM