Search code examples
entity-frameworkpartial-viewsasp.net-core-3.1validationattribute

How to implement validation rules for elements from a partial view


We are developing a .net core 3.1 MVC application (actual with MVVMC).

We have implemented custom validation attributes. And want them to be checked on server and on client side.

The view is a standard view, however the user has the possibility to add multiple partial views to the standard view (via a button).

In the partial view we were not able to use the 'asp-for' tag helper within the input fields, because one can have several times the same item added, and we need to be able to create a list in the ViewModel out of those additional partial views selected. That's why we tried to build the tags by ourselfes.

Once a partial view has been requested via ajax we are calling the validator again. Server validation works, client validation only works for those inputs, which are on the main view, not for the inputs on the partial views. However, after refreshing or when the server sends the user back after a validation error, client validation starts working (because site is completely refreshed and jquery validation takes input fields of partial views into account).

Please find the code below:

Implementation of validation attribute

public class AllowedExtensionsAttribute: ValidationAttribute, IClientModelValidator
{
    private readonly List<string> _extensions = new List<string>();

    public AllowedExtensionsAttribute(string extensions)
    {
        _extensions.Add(extensions);
    }

    protected override ValidationResult IsValid(object value, ValidationContext context)
    {
        if (value is IFormFile file)
        {
            var extension = Path.GetExtension(file.FileName);

            if (!_extensions.Contains(extension.ToLower()))
            {
                return new ValidationResult(ErrorMessage);
            }
        }

        return ValidationResult.Success;
    }

    public void AddValidation(ClientModelValidationContext context)
    {
        context.Attributes.Add("data-val", "true");
        context.Attributes.Add("data-val-extension", ErrorMessage);
    }
 

}

MainViewModel

[AllowedExtensions(".pdf", ErrorMessage = "This data type is not allowed!")]
public IFormFile PdfDocumentOne { get; set; }

PartialViewModel

[AllowedExtensions(".pdf", ErrorMessage = "This data type is not allowed!")]
public IFormFile PdfDocumentTwo { get; set; }

Main View

<form method="post" asp-controller="Home" asp-action="MainView" enctype="multipart/form-data">
    <div class="form-group top-buffer">
        <div class="row">
            <div class="col-2">
                <label asp-for="PdfDocumentOne" class="control-label"></label>
            </div>
            <div class="col-3">
                <input asp-for="PdfDocumentOne" class="form-control-file" accept="application/pdf" />
                <span asp-validation-for="PdfDocumentOne" class="text-danger"></span>
            </div>
        </div>
    </div>
    
    
    <div id="container">
        <div id="containerFull" class="form-group">
            <div class="row">
                <div class="col-2">
                    <label class="control-label">...</label>
                </div>
                <div class="col-10">
                    <div id="containerPartialView">
                    </div>
                </div>
            </div>
            <div class="row">
                <div class="col-2">
                </div>
                <div class="col-3">
                    <button id="AddPartialView" type="button" class="form-control">...</button>
                </div>
            </div>
        </div>
    </div>

    ...
    
     <div class="form-group top-buffer">
         <div class="row">
             <div class="col-2">
                 <input type="submit" value="Submit" class="form-control" id="checkBtn" />
             </div>
         </div>
     </div>
</form>

Partial View

<input id="Lists[@Model.ViewId].ViewId" name="Lists[@Model.ViewId].ViewId" class="partialViewModel" type="hidden" value="@Model.ViewId" />

<div id="Lists[@Model.ViewId].MainContainer" class="partialView">
    <div class="form-group">
        <div> 
            <div class="col-2">
                <label asp-for="PdfDocumentTwo" class="control-label"></label>
            </div>
            <div class="col-3">
                <input name="Lists[@Model.ViewId].PdfDocumentTwo" id="Lists[@Model.ViewId].PdfDocumentTwo " type="file" class="form-control-file" accept="application/pdf" 
                               data-val="true" data-val-extension="This data type is not allowed!"/>
                <span class="text-danger field-validation-valid" data-valmsg-for="Lists[@Model.ViewId].PdfDocumentTwo" data-valmsg-replace="true"></span>
            </div>
        </div>  
    </div>          
    ...
</div>

Javascript

function AddPartialView() {
    var i = $(".partialView").length;
    $.ajax({
        url: '/Home/AddPartialView?index=' + i,
        success: function (data) {
            $('#containerPartialView').append(data);
            $().rules('remove','extension');
            jQuery.validator.unobtrusive.adapters.addBool("extension");
        },
        error: function (a, b, c) {
            console.log(a, b, c);
        }
    });
}

$('#AddPartialView').click(function () {
    AddPartialView();
});

jQuery.validator.addMethod("extension",
    function (value, element, param) {

        var extension = value.split('.').pop().toLowerCase();

        if ($.inArray(extension, ['pdf']) == -1) {
            return false;
        }
        return true;
    });

jQuery.validator.unobtrusive.adapters.addBool("extension");

HomeController

[HttpPost]
public IActionResult MainView(MainViewModel vm)
{
    if (vm == null)
    {
        return RedirectToAction("Index");
    }

    DatabaseHelper.GetMainViewModel(vm);
        
    if (!ModelState.IsValid)
    {
        @ViewData["StatusMessageNegative"] = "The entered data is not valid. Please scroll down to correct your data.";
         return View(vm);
     }
    
     return RedirectToAction("UploadDocument", new { Id = vm.Id});
 }


 [HttpGet]
 public ActionResult AddPartialView(int index)
 {
     PartialViewModel pvm = DatabaseHelper.GetPartialViewModel(index);
     return PartialView("Partial", pvm);
 }

After searching in the internet we found the following argument (data-ajax="true"). However, we don't know where to place it or how to use it correctly.


Solution

  • Have you tried re-apply validation after loading your partial view?

    $.ajax({
        url: '/Home/AddPartialView?index=' + i,
        success: function (data) {
            $('#containerPartialView').append(data);
            $().rules('remove','extension');
            jQuery.validator.unobtrusive.adapters.addBool("extension");
        },
        error: function (a, b, c) {
            console.log(a, b, c);
        }
    }).then(function () {
    $("form").each(function () { $.data($(this)[0], 'validator', false); });
    $.validator.unobtrusive.parse("form");
    

    });