mcintyre321 / FormFactory

MVC5, Core or standalone - Generate rich HTML5 forms from your ViewModels, or build them programatically
http://formfactoryaspmvc.azurewebsites.net/
MIT License
304 stars 103 forks source link

Programmatic Form Client Validation #76

Closed jeremytbrun closed 5 years ago

jeremytbrun commented 5 years ago

Do the programmatically created forms support client-side and server-side validation? Do you have an example of what that's look like since I assume it must be different than the traditional model validation?

I'm creating a form with two PropertyVm fields - one of which has a RegularExpressionAttribute set in the custom attributes. The form builds and displays, but there doesn't appear to be any validation occurring despite having set up the client-side jquery plugins to do unobtrusive validation. I'm passing my programmatic form from my controller to the view through the ViewBag because I wasn't sure I could do it by any other means.

mcintyre321 commented 5 years ago

Does the generated HTML have the data-val- validation attributes on it?

<input class="form-control" data-val="true" data-val-required="Name is required" data-val-length="Name must have between 8 and 32 letters" data-val-length-min="8" data-val-length-max="32" name="Name" size="30" type="text" value="">
mcintyre321 commented 5 years ago

You can add a IEnumerable<PropertyVm> Properties {get;set;} property on your strongly typed ViewModel type

mcintyre321 commented 5 years ago

For server side validation, you'll need to iterate through the PropertyVm and their ValidationAttributes and execute them, I think

jeremytbrun commented 5 years ago

It does not appear that my generated HTML input element has any data-val- attributes on it.

jeremytbrun commented 5 years ago

I figured out the first problem of getting the data-val- attributes to show up. Turns out I was passing the wrong type of Data Annotation Attribute (System.ComponentModel.DataAnnotation instead of FormFactory.Attributes) Now that I'm using FormFactory.Attributes my data-val- attributes show up, but still not getting client side validation.

jeremytbrun commented 5 years ago

I'd also mention it appears my data-val- attributes are being populated for a datetime type PropertyVm, but there is no datetime picker UI being presented. What am I missing there? I've checked the boxes for the pre-requisites/installation steps at the top of the examples site - http://formfactoryaspmvc.azurewebsites.net/.

mcintyre321 commented 5 years ago

The docs might not mention it (any PRs containing improvements to docs very welcome! ), but if you do view source : view-source:http://formfactoryaspmvc.azurewebsites.net/

     <link href="/Content/bootstrap/bootstrap.min.css" rel="stylesheet" type="text/css" />

   <link href="/Content/themes/base/jquery-ui.css" rel="stylesheet" type="text/css" />
    
   <script src="/Scripts/jquery-1.9.1.js" type="text/javascript"></script>
   <script src="/Scripts/jquery-migrate-1.1.0.min.js" type="text/javascript"></script>
   <script src="/Scripts/bootstrap.min.js" type="text/javascript"></script>
   <script src="/Scripts/bootstrap3-typeahead.js" type="text/javascript"></script>
   <script src="/Scripts/bootstrap3-typeahead.unobtrusive.js" type="text/javascript"></script>
   <script src="/Scripts/jquery-ui-1.8.23.min.js" type="text/javascript"></script>
   <script src="/Scripts/jquery.validate.js" type="text/javascript"></script>
   <script src="/Scripts/jquery.validate.unobtrusive.js" type="text/javascript"></script>
   <script src="/Scripts/jquery.validate.unobtrusive.dynamic.js" type="text/javascript"></script>
    
   <script src="/Scripts/jqueryui_timepicker_addon/dist/jquery-ui-sliderAccess.js"></script>
   <script src="/Scripts/jqueryui_timepicker_addon/dist/jquery-ui-timepicker-addon.js"></script>
   <script src="/Scripts/jqueryui_timepicker_addon/dist/jquery-ui-timepicker-addon.min.js"></script>
   <link href="/Scripts/jqueryui_timepicker_addon/dist/jquery-ui-timepicker-addon.css" rel="stylesheet" />
   <link href="/Scripts/jqueryui_timepicker_addon/dist/jquery-ui-timepicker-addon.min.css" rel="stylesheet" />
    
   <link href="/Content/FormFactory/FormFactory.css" rel="stylesheet" type="text/css" />
   <script src="/Scripts/FormFactory/FormFactory.js" type="text/javascript"></script>

you can see which scripts are being used.

So I think you will need script tags for the various timepicker scripts

jeremytbrun commented 5 years ago

Thanks for all of the tips. I've figured out the validation and styling issues I encountered. Now, I am using ASP.NET MVC (not Core). I have a view that lists my strong typed models (which all have a PropertyVM[] member). I'd like to select a model and be able to pass it back to another controller which builds the form for that model. From your experience what is the best approach to doing that? Here is what I have so far.

List View

@foreach (ScriptResource scriptResource in Model.ScriptResourceList)
{
        <div class="col-md-4">
            <h3>@scriptResource.name</h3>
            <p>
                @scriptResource.description
            </p>
            <p>
                @Html.ActionLink("Go »", "Index", "ScriptResource", scriptResource, null)
            </p>
        </div>
}

Show Form Controller

public ActionResult Index(ScriptResource model)
 {
        model.buildFormProperties();
        return View(model);
 }

Show Form View

@using (Html.BeginForm("Run", "ScriptResource", FormMethod.Post, new { @id = "scriptForm", role = "form" }))
{
    @Html.AntiForgeryToken()

    @Model.formProperties.Render(Html);

    <div class="form-actions">
        <input class="btn btn-primary" type="submit" value="Submit">
    </div>
}

The List View loads perfectly and when I click on one of the action links it loads a URL with the following format.

https://localhost:44360/ScriptResource?name=The%20Tool&filePath=C%3A%5CTemp%5CScript.ps1&description=Something%20really%20useful%21

I don't like this because it is expanding the whole object in plain text. What would you consider best practice for this?

mcintyre321 commented 5 years ago

I'm not sure I understand 100%, but it's common for links from list views to pass the ID of the item in question in the URL, rather then it's property values

On Tue, 22 Jan 2019, 16:30 Jeremy Brun <notifications@github.com wrote:

Thanks for all of the tips. I've figured out the validation and styling issues I encountered. Now, I am using ASP.NET MVC (not Core). I have a view that lists my strong typed models (which all have a PropertyVM[] member). I'd like to select a model and be able to pass it back to another controller which builds the form for that model. From your experience what is the best approach to doing that? Here is what I have so far.

List View

@foreach (ScriptResource scriptResource in Model.ScriptResourceList)

{

    <div class="col-md-4">

        <h3>@scriptResource.name</h3>

        <p>

            @scriptResource.description

        </p>

        <p>

            @Html.ActionLink("Go »", "Index", "ScriptResource", scriptResource, null)

        </p>

    </div>

}

Show Form Controller

public ActionResult Index(ScriptResource model)

{

    model.buildFormProperties();

    return View(model);

}

Show Form View

@using (Html.BeginForm("Run", "ScriptResource", FormMethod.Post, new { @id = "scriptForm", role = "form" }))

{

@Html.AntiForgeryToken()

@Model.formProperties.Render(Html);

<div class="form-actions">

    <input class="btn btn-primary" type="submit" value="Submit">

</div>

}

The List View loads perfectly and when I click on one of the action links it loads a URL with the following format.

https://localhost:44360/ScriptResource?name=The%20Tool&filePath=C%3A%5CTemp%5CScript.ps1&description=Something%20really%20useful%21

I don't like this because it is expanding the whole object in plain text. What would you consider best practice for this?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/mcintyre321/FormFactory/issues/76#issuecomment-456465585, or mute the thread https://github.com/notifications/unsubscribe-auth/AAQ0-graBF9gXGALvXhETGsMvwMHN_CWks5vFzyfgaJpZM4aLhjs .

jeremytbrun commented 5 years ago

No problem. I've made some progress through trial and error. I think I've got passing the data back and forth figured out for now. 😄

Is there a way to customize the error/help message for any of the FormFactory.Attributes annotation classes?

For example System.ComponentModel.DataAnnotations.RegularExpressionAttribute class has a ErrorMessage property, but the FormFactory.Attributes.RegularExpressionAttribute does not seem to. I did notice it has a FormatErrorMessage method, but when I set it to a string it does not seem to get displayed in the form at any time.

jeremytbrun commented 5 years ago

OK. So. I have a strongly typed model that has a PropertyVm[] member which I'm using to store the programmatically created form. My controller properly passes the model to my view and then I'm running the following.

@using (Html.BeginForm("Run", "ScriptResource", FormMethod.Post, new { @id = "scriptForm", role = "form" }))
{
    @Html.AntiForgeryToken()

    @Model.formModel.Render(Html);

    @Html.HiddenFor(m => m.id)

    <div class="form-actions">
        <input class="btn btn-primary" type="submit" value="Submit">
    </div>
}

This displays the form perfectly. BUT, how am I supposed to retrieve the form values from the programmatic form in a submit POST action?

jeremytbrun commented 5 years ago

It appears I may have answered another of my questions. Would standard practice for a dynamically built form to validate input be to get the values of my form from a FormCollection object in my POST action and do server-side validation by validating it against my "metamodel" data that I built the form based off of?

jeremytbrun commented 5 years ago

My remaining two questions are these.

Would standard practice for a dynamically built form to validate input be to get the values of my form from a FormCollection object in my POST action and do server-side validation by validating it against my "metamodel" data that I built the form based off of?

Is there a way to customize the error/help message for any of the FormFactory.Attributes annotation classes?

For example System.ComponentModel.DataAnnotations.RegularExpressionAttribute class has a ErrorMessage property, but the FormFactory.Attributes.RegularExpressionAttribute does not seem to. I did notice it has a FormatErrorMessage() method, but when I set it to a string it does not seem to get displayed in the form at any time.

mcintyre321 commented 5 years ago

Would standard practice for a dynamically built form to validate input be to get the values of my form from a FormCollection object in my POST action and do server-side validation by validating it against my "metamodel" data that I built the form based off of?

Yes, you've got it. It can be useful to bind to a JObject if you are doing an AJAX call.

Is there a way to customize the error/help message for any of the FormFactory.Attributes annotation classes?

Not currently as it hasn't been implemented in the FormFactory attributes - yet! It should be quite easy to add, if you have the time and are willing to give it a go.

If you

  1. fork FormFactory,
  2. and add an abstract ValidationAttribute class with an ErrorMessage string property,
  3. make the various FormFactory validation attributes inherit from it
  4. update the various FormFactory validation attributes to use the ErrorMessage property, if it is set

    e.g. in RangeAttribute.cs, change

public string FormatErrorMessage(string fieldName)
{
  return string.Format(Resources.Range, fieldName, Minimum, Maximum);
}

to

public string FormatErrorMessage(string fieldName)
{
  return string.Format(ErrorMessage ?? Resources.Range, fieldName, Minimum, Maximum);
}
  1. Send me a pull request!

then I will merge the changes and release a new version

mcintyre321 commented 5 years ago

Or you can maybe use the resx resource file system to change the values, now I think about it, although I'm not 100% sure how

jeremytbrun commented 5 years ago

Thank you so much for the helpful information it all makes sense. I've been learning a lot about FF by combing through the source code as well as other closed issues on this repository. That, and your additional comments has been immensely helpful.

learner291 commented 4 years ago

Hi @jeremytbrun

I am learning about this library and in a similar position as you. Do you have a small one page project which creates a dynamic form (2 or 3) fields that you can share? This will greatly help my learning curve.

Thanks

leodeveloper commented 3 years ago

Any update regarding programmatically create form validation on form post?