Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

validate form before submit asp.net core 2.0

First of all, I am a beginner at this. Have some experience in asp.net, but that i 8 years ago, and a lot has changed since then.

I am sure my problem is quite simple for the experienced developer, I just do not have the experience and knowledge to fix it.

I produce a viewmodel that is passed to a razor view. This view generates a dynamic form, and all that works well. It basically asks a number of questions in groups, and I need to verify that the user has selected an answer for each question before submitting.

A test form looks like this: test form

Each question is created as radio button list, and I need to ensure that all questions has an answer, before it can be submitted.

The current view, which contains a javascript function that works some of the way.. it prevents me from submitting at all.. so looking at something which is not quite right, but has most of it right. So I guess I need a little help to fix that part, or change the way it is done to something better?:

@using RefereeOnline.Models.FormsViewModels
@model RenderFormViewModel

@{
    ViewData["Title"] = "Evaluate";
}

<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script>
    function ValidateForm() {
        var isFormValid = true;
        $("#evaluateform input,select").each(function () {
            var FieldId = "span_" + $(this).attr("id");
            if ($.trim($(this).val()).length == 0 || $.trim($(this).val()) == 0) {
                $(this).addClass("highlight");

                //Show required message along with the field
                if ($("#" + FieldId).length == 0) {
                    $("<span class='error' id='" + FieldId + "'>Required</span>").insertAfter(this);
                }
                //If you fill and again make the field empty in that case again show the message
                if ($("#" + FieldId).css('display') == 'none') {
                    $("#" + FieldId).fadeIn(500);
                }
                //$(this).focus();
                isFormValid = false;

            }
            else {
                $(this).removeClass("highlight");
                if ($("#" + FieldId).length > 0) {
                    // Hide the message with the fade out effect
                    $("#" + FieldId).fadeOut(1000);
                }
            }
        });
        return isFormValid;
    }
</script>
<h2>Evaluate (@Model.FormTitle @Model.FormVersion)</h2>

@using (Html.BeginForm("Evaluate", "Forms", FormMethod.Post, new { id = "evaluateform" }))
{
    <div>
        <hr />
        <dl class="dl-horizontal">
            <dt>Name</dt>
            <dd>@Model.PersonName</dd>
            <dt>Match</dt>
            <dd>@Model.ActivityInfo</dd>
            <dt>Level</dt>
            <dd>@Model.LevelName</dd>
        </dl>
        <hr />
        @for (int g = 0; g < Model.Groups.Count; g++)
        {
            <table class="table">
                @Html.Hidden("Model.Groups[" + @g + "].GroupId", Model.Groups[g].GroupId)
                @if (Model.Groups[g].Answers.Any())
                {
                    <tr>
                        <td></td>
                        @foreach (var answer in Model.Groups[g].Answers)
                        {
                            <td>@answer.Text</td>
                        }
                        @if (Model.Groups[g].Questions.Any(x => x.AllowComment))
                        {
                            <td>Comment</td>
                        }
                    </tr>
                }
                @for (int i = 0; i < Model.Groups[g].Questions.Count; i++)
                {
                    <tr>
                        <td>@Model.Groups[g].Questions[i].Text</td>
                        @if (Model.Groups[g].Answers.Any() && !Model.Groups[g].Questions[i].Answers.Any())
                        {
                            foreach (var answer in Model.Groups[g].Answers)
                            {
                                <td>
                                    @Html.RadioButton("Model.Groups[" + g + "].Questions[" + i + "].SelectedAnswer", answer.Id)
                                    @Html.Label(answer.Value.ToString(), answer.Value.ToString())
                                    @Html.Hidden("Model.Groups[" + g + "].Questions[" + i + "].FieldId", Model.Groups[g].Questions[i].FieldId)
                                </td>
                            }
                        }
                        else if (Model.Groups[g].Questions[i].Answers.Any()) //single question with answers
                        {
                            foreach (RenderAnswer answer in Model.Groups[g].Questions[i].Answers)
                            {
                                <td>
                                    @Html.RadioButton("Model.Groups[" + g + "].Questions[" + i + "].SelectedAnswer", answer.Id)
                                    @Html.Label(answer.Value.ToString(), answer.Text)
                                    @Html.Hidden("Model.Groups[" + g + "].Questions[" + i + "].FieldId", Model.Groups[g].Questions[i].FieldId)
                                </td>
                            }
                        }
                        else //single question with textbox
                        {
                            <td>@Html.TextBox("Model.Groups[" + g + "].Questions[" + i + "].AnswerValue", Model.Groups[g].Questions[i].AnswerValue)</td>
                        }
                        @if (Model.Groups[g].Questions.Any(x => x.AllowComment))
                        {
                            <td>
                                @if (Model.Groups[g].Questions[i].AllowComment)
                                {
                                    @Html.TextArea("Model.Groups[" + g + "].Questions[" + i + "].Comment", Model.Groups[g].Questions[i].Comment, 2, 40, null)
                                }
                            </td>
                        }
                    </tr>
                }
            </table>
        }
    </div>
    @Html.Hidden("Model.CustomerId", Model.CustomerId)
    @Html.Hidden("Model.FormId", Model.FormId)
    @Html.Hidden("Model.UserId", Model.UserId)
    @Html.Hidden("Model.ActivityId", Model.ActivityId)

    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="Save" class="btn btn-default" onclick="return ValidateForm();" /> |
            @Html.ActionLink("Back", "PlannedEvaluations", "Person", new { userid = Model.UserId })
        </div>
    </div>
}

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

<!--onclick="return ValidateForm();" -->

My view model:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace RefereeOnline.Models.FormsViewModels
{
    public class RenderFormViewModel
    {
        public Guid CustomerId { get; set; }
        public Guid FormId { get; set; }
        public string UserId { get; set; }

        public string FormTitle { get; set; }
        public string FormVersion { get; set; }
        public Guid ActivityId { get; set; }
        public string PersonName { get; set; }
        public string ActivityInfo { get; set; }
        public string LevelName { get; set; }

        public List<RenderGroup> Groups { get; set; } = new List<RenderGroup>();
    }

    public class RenderGroup
    {
        public string GroupId { get; set; }

        public List<RenderQuestion> Questions { get; set; } = new List<RenderQuestion>();

        /// <summary>
        /// Contains a list of possible answers to limit to
        /// If empty, no limited answers
        /// </summary>
        public List<RenderAnswer> Answers { get; set; } = new List<RenderAnswer>();
    }

    public class RenderQuestion
    {
        public Guid FieldId { get; set; }
        public string Text { get; set; }

        /// <summary>
        /// Specific answers for this field
        /// Used if in a group, but answers not re-used
        /// </summary>
        public List<RenderAnswer> Answers { get; set; } = new List<RenderAnswer>();

        public string AnswerValue { get; set; }

        public Guid SelectedAnswer { get; set; }
        public bool AllowComment { get; set; }

        public string Comment { get; set; }
    }

    public class RenderAnswer
    {
        public Guid Id { get; set; }
        public string Text { get; set; }
        public int Value { get; set; }
    }
}

My controller (has a EF core context), have removed all irrelevant methods:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Rewrite.Internal.UrlActions;
using Microsoft.EntityFrameworkCore;
using RefereeDb.Entities;
using RefereeOnline.Data;
using RefereeOnline.Data.Entities;
using RefereeOnline.Models.FormsViewModels;
using Activity = RefereeOnline.Data.Entities.Activity;

namespace RefereeOnline.Controllers
{
    [Authorize]
    [Route("[controller]/[action]")]
    public class FormsController : Controller
    {
        private readonly RefereeContext _context;

        public FormsController(
            RefereeContext context)
        {
            _context = context;
        }

        public IActionResult Evaluate(Guid eventid, Guid activityid)
        {
            var eventData = _context.Events.Find(eventid);
            var customer = _context.Customers.Find(eventData.CustomerId);
            var activityData = _context.Activities.Include(m => m.MetaData).ThenInclude(f => f.Field)
                .ThenInclude(mf => mf.MetaField).Include(l => l.Level).ThenInclude(t => t.Type)
                .ThenInclude(r => r.CustomerReferences).First(x => x.Id == activityid);

            EvaluationForm form = null;
            try
            {
                form = _context.EvaluationForms.Include(f => f.Fields).ThenInclude(a => a.Answers)
                    .First(x => x.Id == activityData.Level.Type.CustomerReferences
                                    .First(c => c.CustomerId == customer.Id &&
                                                c.LicenseTypeId == activityData.Level.LicenseTypeId).EvaluationFormId);
            }
            catch (Exception ex)
            {
                return RedirectToAction("ShowMessage", "Site",
                    new
                    {
                        message = $"Evaluation forms not configured correctly for {activityData.Level.Name}",
                        returnurl = HttpUtility.HtmlEncode(Url.Action("Index", "Home"))
                    });
            }

            var model = BuildViewModel(form);
            model.ActivityId = activityData.Id;
            var user = _context.Users.First(x => x.Id == activityData.PersonId);
            model.PersonName = user.FullName;
            model.LevelName = activityData.Level.Name;
            model.ActivityInfo =
                $"{activityData.Date.ToShortDateString()} {activityData.Date.ToShortTimeString()} {activityData.Place}";
            foreach (CustomerMetaFieldData data in activityData.MetaData.OrderBy(o => o.Field.MetaField.Order))
                model.ActivityInfo += $" {data.FieldValue}";

            return View(model);
        }

        [HttpPost]
        public IActionResult Evaluate(RenderFormViewModel model)
        {
            var activity = _context.Activities.Include(l => l.Level).ThenInclude(r => r.Rules).Include(p => p.Person)
                .ThenInclude(l => l.Licenses).ThenInclude(t => t.Type).First(x => x.Id == model.ActivityId);
            _context.Entry(activity.Person).Collection(x => x.Batches).Load();

            //batch id is assigned in post processing
            Evaluation evaluation = new Evaluation { ActivityId = activity.Id, EvaluationFormId = model.FormId};
            activity.EvaluationData = evaluation;

            var customer = _context.Customers.Include(t => t.AssociatedTypes).ThenInclude(s => s.EvaluationSetup)
                .First(x => x.Id == model.CustomerId);

            var setups = customer.AssociatedTypes.First(t => t.LicenseTypeId == activity.Level.LicenseTypeId)
                .EvaluationSetup.Where(x => x.LicenseLevelId == activity.LicenseLevelId);

            _context.SaveChanges();

            try
            {
                //load the form
                _context.Entry(activity.EvaluationData).Reference(f => f.EvaluationForm).Load();
                _context.Entry(activity.EvaluationData.EvaluationForm).Collection(f => f.Fields).Load();
                foreach (EvaluationFormField field in activity.EvaluationData.EvaluationForm.Fields)
                    _context.Entry(field).Collection(a => a.Answers).Load();

                Dictionary<string, int> points = new Dictionary<string, int>();

                foreach (RenderGroup renderGroup in model.Groups.Where(x => !string.IsNullOrEmpty(x.GroupId)))
                {
                    var groupSetup = setups.FirstOrDefault(x => x.Group == renderGroup.GroupId);

                    if (renderGroup.GroupId != null)
                        points.Add(renderGroup.GroupId, 0);

                    foreach (RenderQuestion question in renderGroup.Questions)
                    {
                        activity.EvaluationData.Data.Add(new EvaluationData
                        {
                            FieldId = question.FieldId,
                            AnswerId = question.SelectedAnswer,
                            EvaluationId = activity.EvaluationData.Id,
                            Comment = question.Comment
                        });

                        if (renderGroup.GroupId != null)
                        {
                            var currentField =
                                activity.EvaluationData.EvaluationForm.Fields.First(f => f.Id == question.FieldId);

                            FieldAnswer currentAnswer = null;
                            if (currentField.SameAnswersForAll)
                            {
                                var field = activity.EvaluationData.EvaluationForm.Fields.First(x =>
                                    x.Answers.Any() && x.Group == renderGroup.GroupId);
                                var answers = field.Answers;
                                currentAnswer = answers.FirstOrDefault(a => a.Id == question.SelectedAnswer);
                            }
                            else
                            {
                                currentAnswer = currentField.Answers.First(a => a.Id == question.SelectedAnswer);
                            }

                            points[renderGroup.GroupId] += currentAnswer.Points;
                        }
                    }

                    if (renderGroup.GroupId != null)
                    {
                        var fields =
                            activity.EvaluationData.EvaluationForm.Fields.Where(x => x.Group == renderGroup.GroupId);
                        int max = 0;
                        if (fields.Any(x => x.SameAnswersForAll))
                        {
                            max = fields.First(x => x.Answers.Any()).Answers.Max(m => m.Points) * fields.Count();
                        }
                        else
                        {
                            max = fields.Sum(x => x.Answers.Max(a => a.Points));
                        }

                        EvaluationPointSums newPoints =
                            new EvaluationPointSums
                            {
                                GroupId = renderGroup.GroupId,
                                EvaluationId = evaluation.Id,
                                Points = points[renderGroup.GroupId],
                                Threshold = groupSetup?.PassThreshold ?? 0,
                                Maximum = max
                            };
                        evaluation.Points.Add(newPoints);
                    }
                }

                _context.Audit.Add(new Audit(User.Identity.Name, evaluation.Id, "Evaluation added"));

                _context.SaveChanges();
            }
            catch (Exception)
            {
                //reverting the evaluation
                _context.Evaluations.Remove(evaluation);

                _context.SaveChanges();

                //todo: go to message
            }

            //post processing the evaluation... should new license be created? or expired..
            PostProcessEvaluation(activity, evaluation);

            return RedirectToAction("EvaluationResult", new { evaluationid = evaluation.Id });
        }

        public IActionResult EvaluationDetails(Guid activityid, Guid evaluationid, bool score, bool data, string userid)
        {
            //getting event, activity, metadata, form etc...
            var activity = _context.Activities.Include(e => e.EvaluationData).ThenInclude(f => f.EvaluationForm)
                .ThenInclude(ff => ff.Fields).ThenInclude(fc => fc.Answers).Include(m => m.MetaData)
                .ThenInclude(fd => fd.Field).ThenInclude(d => d.MetaField).Include(e => e.Event).Include(l => l.Level)
                .First(x => x.Id == activityid);

            _context.Entry(activity.EvaluationData).Collection(x => x.Data).Load();
            _context.Entry(activity.EvaluationData).Collection(x => x.Points).Load();
            foreach (var evaluationData in activity.EvaluationData.Data)
                _context.Entry(evaluationData).Reference(x => x.Answer).Load();

            DisplayEvaluationViewModel model = new DisplayEvaluationViewModel { UserId = userid };

            model.Activity = activity;
            model.RenderModel = BuildViewModel(activity.EvaluationData.EvaluationForm);

            return View(model);
        }

        private void PostProcessEvaluation(Activity activity, Evaluation evaluation)
        {
            EvaluationRule rule = activity.Person.HasLicense(activity.LicenseLevelId)
                ? activity.Level.Rules.FirstOrDefault(x => x.Scope == LicenseScope.Licensed)
                : activity.Level.Rules.FirstOrDefault(x => x.Scope == LicenseScope.Trainee);

            if (rule != null) //if no rule, nothing happens
            {
                var batch = activity.Person.Batches.FirstOrDefault(x => x.LevelId == activity.LicenseLevelId);

                if (batch == null)
                {
                    //creating new batch, marking evaluation with it
                    batch = new EvaluationIdentityBatch { CurrentBatch = Guid.NewGuid(), LevelId = activity.LicenseLevelId, PersonId = activity.PersonId };
                    evaluation.BatchId = batch.Id;
                    activity.Person.Batches.Add(batch);

                    _context.SaveChanges();
                }

                //get all evaluations belonging to this batch
                var evals = _context.Evaluations.Where(x => x.BatchId == batch.CurrentBatch);

                if (evals.Count(x => x.IsPassed) == rule.Goal)
                {
                    //target hit, all is good, execute passed action
                    ExecuteAction(rule.SuccessAction, activity);
                }
                else if (evals.Count() == rule.Tries)
                {
                    //execute failed action
                    ExecuteAction(rule.FailAction, activity);
                }
                else
                {
                    //log that nothing happens....
                    Trace.TraceError("Rule found, but not triggered");

                }
            }
        }

        private void ExecuteAction(EvalAction action, Activity activity)
        {
            switch (action)
            {
                case EvalAction.Issue:
                    License newLicense = new License
                    {
                        Assigned = DateTime.Now,
                        CustomerId = activity.Person.CustomerId,
                        LicenseLevelId = activity.LicenseLevelId,
                        LicenseTypeId = activity.Level.LicenseTypeId,
                        PersonId = activity.Person.Id,
                        Recorded = DateTime.Now
                    };
                    activity.Person.Licenses.Add(newLicense);

                    _context.Audit.Add(new Audit(User.Identity.Name, newLicense.Id, "Created by rule"));
                    break;
                case EvalAction.Expire:
                    var license =
                        activity.Person.CurrentLicenses.First(x => x.LicenseLevelId == activity.LicenseLevelId);
                    license.LastActivity = DateTime.Now;
                    license.ForceExpiry = true;

                    _context.Audit.Add(new Audit(User.Identity.Name, license.Id, "Expired by rule"));

                    break;
            }

            var batch = activity.Person.Batches.First(x => x.LevelId == activity.LicenseLevelId);
            activity.Person.Batches.Remove(batch);

            _context.SaveChanges();
        }

        public IActionResult EvaluationResult(Guid evaluationid)
        {
            EvaluationResultViewModel model = new EvaluationResultViewModel();

            var evaluation = _context.Evaluations.Include(p => p.Points).Include(a => a.Activity)
                .ThenInclude(m => m.MetaData).First(x => x.Id == evaluationid);
            _context.Entry(evaluation.Activity).Reference(p => p.Person).Load();
            _context.Entry(evaluation.Activity).Reference(l => l.Level).Load();
            _context.Entry(evaluation.Activity).Collection(r => r.Relations).Load();

            model.Evaluation = evaluation;

            return View(model);
        }

        private RenderFormViewModel BuildViewModel(EvaluationForm form)
        {
            ApplicationUser user = _context.Users.Include(m => m.AssociationMembers).First(x => x.UserName == User.Identity.Name);

            RenderFormViewModel model = new RenderFormViewModel { CustomerId = form.CustomerId, FormId = form.Id, FormVersion = form.FormVersion, FormTitle = form.Name, UserId = user.Id };

            foreach (EvaluationFormField field in form.Fields.OrderBy(x => x.SortOrder))
            {
                if (string.IsNullOrEmpty(field.Group))
                {
                    //normal field
                    RenderGroup group = new RenderGroup();

                    RenderQuestion newQuestion = new RenderQuestion { Text = field.Text, FieldId = field.Id };
                    newQuestion.Answers.AddRange(field.Answers.OrderBy(o => o.SortOrder).Select(x => new RenderAnswer { Id = x.Id, Text = x.DisplayText, Value = x.Points }));

                    group.Questions.Add(newQuestion);

                    model.Groups.Add(group);
                }
                else
                {
                    //grouped field
                    RenderGroup group = model.Groups.FirstOrDefault(x => x.GroupId == field.Group);
                    if (group == null)
                    {
                        //group does not exist... create + add answers
                        group = new RenderGroup { GroupId = field.Group };

                        if (field.SameAnswersForAll)
                        {
                            var answerfield = form.Fields.Where(x => x.Group == field.Group && x.Answers.Any())
                                .OrderBy(o => o.SortOrder).FirstOrDefault();

                            if (answerfield != null)
                            {
                                //adding general answers
                                group.Answers.AddRange(answerfield.Answers.OrderBy(o => o.SortOrder).Select(x => new RenderAnswer { Id = x.Id, Text = x.DisplayText, Value = x.Points }));
                            }
                        }

                        model.Groups.Add(group);
                    }

                    //creating the question
                    RenderQuestion newQuestion = new RenderQuestion { FieldId = field.Id, Text = field.Text, AllowComment = field.AddComment };

                    //adding specific answers
                    if (!field.SameAnswersForAll && field.Answers.Any())
                        newQuestion.Answers.AddRange(field.Answers.OrderBy(o => o.SortOrder).Select(x => new RenderAnswer { Id = x.Id, Text = x.DisplayText }));

                    group.Questions.Add(newQuestion);
                }
            }

            return model;
        }

        #region create
    }
}
like image 772
Hans-Henrik Møller Avatar asked Oct 29 '25 09:10

Hans-Henrik Møller


1 Answers

NOTE Updated the script section

This is only a partial answer that does not address any shortcomings other than the script. I would take small steps to refactor but ultimately I would use the built in validation on your model but that requires more learning/discussion.

This may be something you can change to keep moving forward, the below snippets are what has worked for me in the past but there are tons of opinions so see if works or helps:

Controller

[Route("VerifyForm")]
public IActionResult VerifyForm()
{
    var viewModel = new VerifyModel()
    {
        Store = "",
        DateFrom = DateTime.Now,
    };

    return View(viewModel);
}


/// <summary>
///     Called by a form post
/// </summary>
[HttpPost("VerifyForm")]
public IActionResult VerifyForm(VerifyModel model)
{
    if (!ModelState.IsValid)
    {
        // finding modelstate validation errors
        var errors = ModelState.Values.SelectMany(v => v.Errors);
        foreach (var err in errors)
        {
            _logger.LogError($"Model State err: {err.ErrorMessage}");
        }
        return View(model);
    }

    // do something with the model data

    // . . .

    // return back to the view - or another view - whatever you wish
    return View(model);
}

View

<form asp-action="VerifyForm" asp-controller="Admin" method="post">

    <!-- form fields here -->

    <!-- this triggers the post, called in .js -->
    <input type="submit" id="inputSave" style="display: none" />
</form>

<!-- my visible button bound to in .js -->
<button id="buttonSaveUser" class="btn btn-sm btn-primary" title="Click to save">
    Save
</button>

Javascript

First, see my comments about the view and your use of the submit function, change your view and then given that you can just modify your script

Second, move your script below to this point I saw in your view, the script should not run at the top:

<script>

    // initialize the buttons or other ui events
    // for those who complain:  Yes, there are other ways to do things but this is simple

    $(function(){ 

        // I'll assume this is the name of your new button
        // bind to the button this way, get rid of onClick in your view

        $('#buttonSaveUser').on('click', function ()
        {   
            // this is my call to do some other validation, if not valid just exit
            if (ValidateForm() === false) return;

            // triggers the submit button which triggers the post
            $('#inputSave').click();
        });
    });


    function ValidateForm() {
        var isFormValid = true;

        $("#evaluateform input,select").each(function () {
            var FieldId = "span_" + $(this).attr("id");
            if ($.trim($(this).val()).length == 0 || $.trim($(this).val()) == 0) {
                $(this).addClass("highlight");

                //Show required message along with the field
                if ($("#" + FieldId).length == 0) {
                    $("<span class='error' id='" + FieldId + "'>Required</span>").insertAfter(this);
                }
                //If you fill and again make the field empty in that case again show the message
                if ($("#" + FieldId).css('display') == 'none') {
                    $("#" + FieldId).fadeIn(500);
                }
                //$(this).focus();
                isFormValid = false;

            }
            else {
                $(this).removeClass("highlight");
                if ($("#" + FieldId).length > 0) {
                    // Hide the message with the fade out effect
                    $("#" + FieldId).fadeOut(1000);
                }
            }
        });
        return isFormValid;
    }

</script>
  • don't use the submit to run client side script - it's meant to trigger the form POST - I would create a basic button that is bound client side
  • get rid of the onClick='' and bind in javascript

Hope this helps somewhat.

like image 132
Tab Avatar answered Oct 31 '25 00:10

Tab