2

I am using .Netcore MVC. I have a registration page and I want mail, password and password confirmation from user. I can check if password and confirmation password are equal, but still my controller action triggers which is saving the user data to db. I don't want my controller to save the user to db if passwords are different.

View:

<form class="user" action="/account/adduser" method="post">
                        @Html.AntiForgeryToken()
                        <div class="form-group">

                            <input type="text" class="form-control form-control-user" aria-describedby="emailHelp" name="Mail" placeholder="Kullanıcı Adı">
                        </div>

                        <div class="form-group">

                            <input type="password" class="form-control form-control-user" id="Password" name="Password" placeholder="Şifre">

                        </div>

                        <div class="form-group">

                            <input type="password" class="form-control form-control-user" id="Password2" name="Password2" placeholder="Şifreyi Onayla">

                        </div>
                        <hr>
                        <button type="submit" class="btn btn-primary btn-user btn-block" id="buton" name="buton" onclick="confirmation()">
                            Ekle
                        </button>
                        @if (ViewBag.UserAdded != null)
                        {
                            <div class="toast-header">
                                Kullanıcı Eklendi.
                            </div>
                        }
                        @if (ViewBag.duplicate == "true")
                        {
                            <div class="toast-header">
                                Mail zaten kullanımda.
                            </div>
                        }
                    </form>                    

    <script>
            function confirmation() {
                var pw1 = $("#Password").val();
                var pw2 = $("#Password2").val();
                if (pw1 == pw2) {
                    alert(1);
                    return true;
                }
                else {
                    alert(2);
                    return false;
                }
            }
        </script>

Controller:

[HttpPost]
        public IActionResult AddUser(User user)
        {
            if (IsDuplicate(user)) {
                ViewBag.duplicate = "true";
                return View();
            }
            
            _context.Users.Add(user);
            _context.SaveChanges();
            ViewBag.UserAdded = "success";
            return View();
        }

I don't want to add an extra string to my class user as confirmationPassword even that would work. I want to check it in view and cancel the action if they are not equal. How can I achieve this? Thanks in advance!

aoiTenshi
  • 547
  • 1
  • 6
  • 20
  • 2
    you should be using a viewModel not a database DTO - your "User" viewmodel should have properties of just what the form submits - you then call your save with either a DTO (copy values from viewmodel) or something which creates one from your viewmodel - architecture issue - never expose DTOs on your UI - bad things can happen – developer Jun 23 '21 at 14:21
  • 1
    Also you should not be doing DB work in Controller - it's not the controllers job - put your database code in a separate domain / lib. and reference it in controller... maybe consider implement a "unit-of-work" pattern ref: ```https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application``` Simply following the basic c# examples/tutorials online doesn't make code production ready / safe – developer Jun 23 '21 at 14:25
  • 1
    ref ```https://stackoverflow.com/questions/11064316/what-is-viewmodel-in-mvc``` – developer Jun 23 '21 at 14:30
  • @developer I created a view model as you suggested and added a confirmationpassword to it and now I can check and deny the action. This is not the way I thought but if you say this is the right way I will make that. Anyway if there would be a way to do it as in my question I would still want to know that. Thanks. – aoiTenshi Jun 23 '21 at 14:41
  • 1
    no this is the correct way - adhering to SOLID principles - separation of concerns - you should have UI domain and a separate DB/model domain. Think of it this way - it you wanted to expose an API - you would have to duplicate all your DB functions again - and TBH DB actions are not usually simple commits - but typically implementations of business logic - which both your UI and some future API must adhere/interface to, so having these in a separate lib is the way. – developer Jun 24 '21 at 08:35
  • @developer Looks like rather than coding I should also learn about architectures. Thanks for the information. – aoiTenshi Jun 25 '21 at 07:25

2 Answers2

2

ViewModel

public class RegisterViewModel
{
    [Required]
    [EmailAddress]
    public string Email { get; set; }

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

    [Required]
    [DataType(DataType.Password)]
    public string Password { get; set; }

    [DataType(DataType.Password)]
    [Compare("Password", ErrorMessage = "")]
    public string ConfirmPassword { get; set; }
}

controller

    [HttpPost]
    public IActionResult AddUser(RegisterViewModel model)
    {
        if (ModelState.IsValid)
        {
            var user = new User { UserName = model.Email, Email = model.Email, FullName = model.FullName };
            _context.Users.Add(user);
            _context.SaveChanges();
            ViewBag.UserAdded = "success";
        }
        return View(model);
    }

view

@model RegisterViewModel

<form class="user" action="/account/adduser" method="post">
    @Html.AntiForgeryToken()
    <div class="form-group">
        <input asp-for="Email" class="form-control" />
        <span asp-validation-for="Email" class="text-danger"></span>
    </div>
    <div class="form-group">
        <input asp-for="Password" class="form-control" />
        <span asp-validation-for="Password" class="text-danger"></span>
    </div>
    <div class="form-group">
        <input asp-for="ConfirmPassword" class="form-control" />
        <span asp-validation-for="ConfirmPassword" class="text-danger"></span>
    </div>
    <hr>
    <button type="submit" class="btn btn-primary btn-user btn-block" id="buton" name="buton" onclick="confirmation()">
        Ekle
    </button>
</form>

<script src="https://cdn.jsdelivr.net/npm/jquery-validation@1.19.3/dist/jquery.validate.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.12/jquery.validate.unobtrusive.min.js"></script>
İbrahim
  • 141
  • 5
  • Yes, that's what I did after learning my path is wrong. So can I assume that it is not possible doing it only in view? – aoiTenshi Jun 23 '21 at 22:40
  • 1
    You should never just implement checks in client code - your client code and be easily bypassed - the server must perform the checks - you client can do them in addition - to save a server round trip, and to improve user experience - but server must check all submissions – developer Jun 24 '21 at 08:40
1

view

 <input asp-for="ConfirmPassword" equalto="#Password" class="form-control" /> // equalto


    <script type="text/javascript">
        $(document).ready(function () {
            $("form.user").validate({
                rules: {
                    Password: {
                        minlength: 6,
                        maxlength: 30,
                    },
                    ConfirmPassword: {
                        equalTo: "#Password"
                    },
                    Email: {
                        required: true, 
                        email: true
                    },
                },
                messages: {
                    Email: "Required",
                    Password: "Required",
                    ConfirmPassword: "Required"
                }
            });
        });
    </script>
İbrahim
  • 141
  • 5