Icemanind
Icemanind

Reputation: 48686

MVC: Two form tags and one ViewModel

I have a single page on my MVC application. On this page, a user can enter their user name and password and click a login button, or the user can enter their first name, email address and click a signup button.

My initial thought was to create a ViewModel with UserName, Password, FirstName and Email properties, all with a [Required] attribute. Then having a view with two Html.BeginForm()'s. Although this would probably work, I have the feeling that when I post my data back to my controller, ModelState.IsValid will always return false since the ViewModel, is indeed, invalid.

So can someone tell me the proper way to handle a situation like this?

Upvotes: 0

Views: 562

Answers (4)

Murtaza Tahir Ali
Murtaza Tahir Ali

Reputation: 609

If you want to try different library then you can go with FluentValidation

FluentValidation provides elegant way to customize validation for single class for different methods.

For e.g

[Validator(typeof(LoginRegisterModelValidator))]
public class LoginRegisterViewModel
{
    public string UserName { get; set; }

    public string Password { get; set; }

    public string FirstName{ get; set; }

    public string Email { get; set; }
}

You can have multiple rules defined for different actions The validator class would look like this

public class LoginRegisterModelValidator : AbstractValidator<LoginRegisterViewModel>
    {
        public RegistryAddEditModelValidator()
        {
            /* Define the rule set to call them specifically inside contrller action parameter with CustomizeValidator Attribute */
            RuleSet("LoginRuleSet", LoginRuleSet);
            RuleSet("RegisterRuleSet", RegisterRuleSet);            
        }

protected void LoginRuleSet()
        {
           RuleFor(x => x.UserName).NotEmpty();
           RuleFor(x => x.Password).NotEmpty();
        }

        protected void RegisterRuleSet()
        {
           RuleFor(x => x.Email).NotEmpty();
           RuleFor(x => x.FirstName).NotEmpty();
        }
}

The Controller Action would look like this

 [HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login([CustomizeValidator(RuleSet = "LoginRuleSet")] LoginRegisterViewModel model)
        { ...
        }

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Register([CustomizeValidator(RuleSet = "RegisterRuleSet")] LoginRegisterViewModel model)
        { ...
        }
}

Hope this helps you to validate different rule with same class.

Upvotes: 1

Voque
Voque

Reputation: 168

I have implemented very similiar scenario in my project i think the best way of achievie this is to create a viewmodel that will have 2 child viewmodel's inside something like this:

public class AuthModelView
{
    public MemberLoginViewModel LoginModel { get; set; }
    public MemberRegisterViewModel RegisterModel { get; set; }

    [HiddenInput]
    public string ReturnUrl { get; set; }
}

MemberLoginViewModel:

public class MemberLoginViewModel
{
    [Required(ErrorMessage = "")]
    [Display(Name = "")]
    [EmailAddress]
    public string Email { get; set; }


    [DataType(DataType.Password)]
    [Display(Name = "")]
    [Required(ErrorMessage = "")]
    public string Password { get; set; }

    [Display(Name = "")]
    public bool RememberMe { get; set; }
}

MemberRegisterViewModel:

public class MemberRegisterViewModel
{
    [Required(ErrorMessage = "")]
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }

    [Required(ErrorMessage = "")]
    [DataType(DataType.Password)]
    [Display(Name = "")]
    public string Password { get; set; }

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

    [HiddenInput(DisplayValue = false)]
    public string ReturnUrl { get; set; }
}

Then you create the View that will render 2 partial views

  @Html.Partial("MemberLoginSummary", Model)
  @Html.Partial("MemberRegisterSummary", Model)

Where the "Model" is your parent Viewmodel, then you will have 2 separated forms in one view. In your partial view you simply do something like :

@Html.TextBoxFor(m => m.LoginModel.Email, null, new { @class = "form-control", placeholder = "email", id="Email" })

Upvotes: 2

T&#226;n
T&#226;n

Reputation: 1

You can define 2 view models.

Login view model:

public class LoginViewModel
{
    [Requried]
    public string UserName { get; set; }

    [Requried]
    public string Password { get; set; }
}

Register view model:

public class RegisterViewModel
{
    [Requried]
    public string UserName { get; set; }

    [Requried]
    public string Password { get; set; }

    [Requried]
    public string FirstName{ get; set; }

    [Requried]
    public string Email { get; set; }
}

Login view:

@Html.BeginForm("Login", "Account", FormMethod.Post)
{
    <!-- login form implements... -->
}

Register view:

@Html.BeginForm("Register", "Account", FormMethod.Post)
{
    <!-- register form implements... -->
}

Controller:

public IActionResult Login(LoginViewModel model)
{
    if (ModelState.IsValid)
    {

    }
}

public IActionResult Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {

    }
}

Or, if you want only 1 model. Try to remove [Required] attribute. You can check it inside the action. Like this:

public IActionResult Login(YourViewModel model)
{
    if (!string.IsNullOrEmpty(model.UserName) && !string.IsNullOrEmpty(model.Password))
    {

    }
}

public IActionResult Register(YourViewModel model)
{
    if (!string.IsNullOrEmpty(model.UserName) &&
        !string.IsNullOrEmpty(model.Password) &&
        !string.IsNullOrEmpty(model.FirstName) &&
        !string.IsNullOrEmpty(model.Email))
    {

    }
}

Hope this help!

Upvotes: 1

Ben Hall
Ben Hall

Reputation: 1423

I believe the proper way is to have a ViewModel for each. Your underlying business logic and/or code talking to the database can still work with a single model if you want.

Your options are discussed well here.

Although I would add that having more than one ViewModel on an MVC page is a pain. Some guidance here on how.

Upvotes: 0

Related Questions