Base solution for your next web application
Open Closed

Form Post via MVC controller vs javascript? #2366


User avatar
0
joshboilered created

I'm following the step by step tutorial and understanding everything so far. However, while I like JS modal posts, I'm not a JS expert and usually structure the modal to post the form direct to an MVC controller.

Below is my modification to the tutorial. What I'd like to do is target the MVC controller direct, check model state, then call the ApplicationService to Automap to the domain model to post to the db.

The issue I encounter is that button click event (if no data is filled in the fields)doesn't trigger a validation check based on the ViewModel(CreatePersonInput) and the form doesn't post to the MVC controller.

Index Controller

public ActionResult Index(GetPeopleInput input)
        {
            var peeps = _personAppService.GetPeople(input);
            var model = new PersonListViewModel();
            model.People.AddRange(peeps);
            model.NewPerson = new CreatePersonViewModel();
            return View(model);
        }

ViewModel

public class PersonListViewModel
    {
        public PersonListViewModel()
        {
            People = new List<PersonListDto>();
            NewPerson = new CreatePersonViewModel();
        }

        public List<PersonListDto> People { get; set; }
        public CreatePersonViewModel NewPerson { get; set; }
    }

DTO

[AutoMapTo(typeof(Person))]
    public class CreatePersonInput
    {
        [Required]
        public string Name { get; set; }

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

        [EmailAddress]
        public string EmailAddress { get; set; }
    }

INDEX View

@using System.Threading.Tasks
@using TwoZero.Web.Areas.App.Startup
@model TwoZero.Web.Areas.App.Models.PhoneBook.PersonListViewModel

@{
    ViewBag.CurrentPageName = AppPageNames.Tenant.PhoneBook;
}

<div class="row margin-bottom-5">
    <div class="col-xs-12">
        <div class="page-head">
            <div class="page-title">
                <h1>
                    <span>@L("PhoneBook")</span>
                </h1>
            </div>
        </div>
    </div>
    <div class="col-xs-12 text-right ">
        <button class="btn btn-primary blue" data-toggle="modal" data-target="#createPerson"><i class="fa fa-plus"></i> New Person</button>
    </div>
</div>
<div class="portlet light">
    <div class="portlet-body">
        <h3>My Peeps</h3>
        <div class="list-group">
            @foreach (var person in Model.People)
            {
                <a href="javascript:;" class="list-group-item">
                    <h4 class="list-group-item-heading">
                        @person.Name @person.LastName
                    </h4>
                    <p class="list-group-item-text">
                        @person.EmailAddress
                    </p>
                </a>
            }
        </div>
    </div>
</div>


@Html.Partial("_CreatePersonModal", Model.NewPerson);

CREATE MODAL

@model TwoZero.Web.Areas.App.Models.PhoneBook.CreatePersonViewModel


@* Create Modal *@
<div class="modal fade" id="createPerson" tabindex="-1" role="dialog">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-hidden="true"></button>
                <h4 class="modal-title">
                    <span>Create A Person</span>
                </h4>
            </div>
            <form asp-action="CreatePerson" asp-controller="PhoneBook" method="post" role="form" novalidate class="form-validation">
                <div class="modal-body">
                    <div class="form-group form-md-line-input form-md-floating-label no-hint">
                        <input class="form-control" type="text" asp-for="@Model.Name"  >
                        <label>Name</label>
                    </div>
                    <div class="form-group form-md-line-input form-md-floating-label no-hint">
                        <input type="text" asp-for="@Model.LastName"  class="form-control" >
                        <label>Last Name</label>
                    </div>
                    <div class="form-group form-md-line-input form-md-floating-label no-hint">
                        <input type="email" asp-for="@Model.EmailAddress"  class="form-control">
                        <label>Email</label>
                    </div>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn default close-button" data-dismiss="modal">Cancel</button>
                    <button type="submit" class="btn blue save-button"><i class="fa fa-save"></i> <span>Save</span></button>
                </div>
            </form>
        </div>
    </div>
</div>

Create CONTROLLER

[HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult CreatePerson(PersonListViewModel vm)
        {
            if (ModelState.IsValid)
            {
                var person = vm.NewPerson.MapTo<CreatePersonInput>();

                _personAppService.CreatePerson(person);
            }
            
            return RedirectToAction("Index");
        }

11 Answer(s)
  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    Please check this comment for your answer <a class="postlink" href="https://github.com/aspnetboilerplate/aspnetboilerplate/issues/1756#issuecomment-272394531">https://github.com/aspnetboilerplate/as ... -272394531</a>

  • User Avatar
    0
    joshboilered created

    Thanks, ismcagdas.

    I understand that the [DisableValidation] attribute on a controller will trigger the controller to allow ModelState.IsValid logic so that we can handle posts to the server manually.

    What I'm after is;

    1. I'd like to leave the Javascript work in place, that is, users, roles, etc. I don't want to disable it completely using (Configuration.Modules.AbpMvc().IsValidationEnabledForControllers = false;) otherwise I'm assuming I'd have to rewrite all the pre-built views (users, roles permissions etc) to follow standard MVC convention.

    2. What I'd like to do is, for any new development, I want to route it through the standard convention of the controller, then to the service, then to the dynamically created repo.

    For example, if I'm adding a Person domain entity.

    In the view, for the 'create' new Person, I'd like to have a partial view with a model for create. On 'create' Person button click, I bring up a modal. The modal has a standard razor view form with a method="Post". The ViewModel for CreatePersonInput has three fields, a FirstName, LastName, Email. I data annotate the First and Last name with [Required]. If the form is not filled out, but the user clicks submit, I want the standard form validation to execute and display the 'This field is required' label. Secondary security layer; if the user disables javascript, I want the Create Controller to have [ValidateAntiforgery] and [HTTPPost] with if (ModelState.IsValid) {} logic.

    With the [DisableValidation] annotation, I can implement the secondary security. However, the clientside form validation is not working. What do I need to do to enable client side validation using the standard form post methodology of MVC?

    Thank you.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    DisableValidation attribute is an attribute defined by ABP and it should not effect client side validation. <a class="postlink" href="https://github.com/aspnetboilerplate/aspnetboilerplate/blob/f10fa5205c780bcc27adfe38aaae631f412eb7df/src/Abp/Runtime/Validation/DisableValidationAttribute.cs">https://github.com/aspnetboilerplate/as ... tribute.cs</a>

    Can you check if there is something effects client side validation ? If you cannot find anything, can you share your code ?

    Thanks.

  • User Avatar
    0
    joshboilered created

    Happy to share it, I'm walking through the PhoneBook Demo trying to do modal form posts to a controller direct for adding the person vs via JS. Using the data annotation, it hits the 'post' method in the controller but the clientside validation is not being triggered.

    Where can I post it?

  • User Avatar
    0
    ismcagdas created
    Support Team

    Please send it to <a href="mailto:[email protected]">[email protected]</a>.

    Thanks.

  • User Avatar
    0
    joshboilered created

    Hi ismcagdas,

    I sent you a copy a couple of days ago, have you had a chance to look?

    Thanks.

    Josh

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @joshBoilered,

    Thanks, I have got the project. I coulnd't have a chance to look at it.

    I will get back to you today or tomorrow.

  • User Avatar
    0
    joshboilered created

    <cite>ismcagdas: </cite> Hi @joshBoilered,

    Thanks, I have got the project. I coulnd't have a chance to look at it.

    I will get back to you today or tomorrow.

    Thanks.

    Also, can you have a look at your HostSettingsAppService? I'm following the logic for 'allowing' tenants to self register. When the checkbox is true, it doesn't create a line item in dbo.AbpSettings for "App.TenantManagement.AllowSelfRegistration = true", it just removes it completely. However, when you disable this option, it creates a line item in the dbo.AbpSettings "App.TenantManagement.AllowSelfRegistration = false".

    What I'm trying to accomplish is to allow the tenant to self register. If I manually change "App.TenantManagement.AllowSelfRegistration = true", in the db, it doesn't enable the service to show the registration page. If I leave the logic as it is, where if its enabled, there's no dbo record, I still don't see the registration page.

    Something is off here, need guidance on fixing the bug.

    Thanks. Josh

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    I have worked on your solution and these are what I have done to get it working.

    I think you are working on this doc <a class="postlink" href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/">https://docs.microsoft.com/en-us/aspnet ... t-mvc-app/</a>. It also does validation on the client side using javascript.

    1. In order to make it working first you need to create a partial view named "_ValidationScriptsPartial.cshtml" under "Areas\App\Views\Shared" and put below content in it.
    <environment names="Development">
        <script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
        <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
    </environment>
    <environment names="Staging,Production">
        <script src="//ajax.aspnetcdn.com/ajax/jquery.validation/1.11.1/jquery.validate.min.js"
                asp-fallback-src="~/lib/jquery-validation/jquery.validate.js"
                asp-fallback-test="window.jquery && window.jquery.validator">
        </script>
        <script src="//ajax.aspnetcdn.com/ajax/mvc/5.2.3/jquery.validate.unobtrusive.min.js"
                asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
                asp-fallback-test="window.jquery && window.jquery.validator && window.jquery.validator.unobtrusive">
        </script>
    </environment>
    
    1. Remove @scripts section from your Index.cshtml for Customers and add below code to bottom of it.
    @section Scripts {
        <environment names="Development">
        </environment>
    
        <environment names="Staging,Production">
        </environment>
        @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
    }
    
    1. Then, in your _CreateCustomer.cshtml file, change form-group for your Name input like this. You can use a similar approach for your other fields.
    <div class="form-group">
        <label asp-for="Name" class="col-md-2 control-label"></label>
        <div class="col-md-10">
            <input asp-for="Name" class="form-control" />
            <span asp-validation-for="Name" class="text-danger"></span>
        </div>
    </div>
    
    1. Put your model to CreateCustomer action in your CustomersController
    public IActionResult CreateCustomer(CreateCustomerViewModel model)
    

    "~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js" is not included in AspNet Zero template by default, don't forget to download and include it into your project.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    There are two different things here.

    1. Registration of new host users
    2. Registration of new tenants

    The link on the Login page is used for 1. option (Registration of new host users) when you switch to host on login page. This is disabled by default.

    The link for Option 2, Registration of new tenants is placed in Home/Index(Fontend section), but it seems like you have deleted the HomeController from your project.

    You can put a link to "TenantRegistration/Index" anywhere you like.

  • User Avatar
    0
    joshboilered created

    <cite>ismcagdas: </cite> Hi,

    There are two different things here.

    1. Registration of new host users
    2. Registration of new tenants

    The link on the Login page is used for 1. option (Registration of new host users) when you switch to host on login page. This is disabled by default.

    The link for Option 2, Registration of new tenants is placed in Home/Index(Fontend section), but it seems like you have deleted the HomeController from your project.

    You can put a link to "TenantRegistration/Index" anywhere you like.

    Perfect, thanks!