Using Tuple for passing multiple models - c#

I have a view includes login and registration form.
LoginAndRegister.cshtml file:
#model Tuple<Models.LoginViewModel, Models.RegisterViewModel>
#using (Html.BeginForm("Login", "Account", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(false)
// Form Login here
}
#using (Html.BeginForm("Register", "Account", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(false)
// Form Register here
}
AccountController file:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register([Bind(Prefix = "Item2")] RegisterViewModel model)
{
if (ModelState.IsValid)
{
// enter code here
}
// If we got this far, something failed, redisplay form
var tupleModel = new Tuple<LoginViewModel, RegisterViewModel>(null, model);
return View("LoginAndRegister", tupleModel);
}
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login([Bind(Prefix = "Item1")] LoginViewModel model, string returnUrl)
{
var tupleModel = new Tuple<LoginViewModel, RegisterViewModel>(model, null);
if (!ModelState.IsValid)
{
return View("LoginAndRegister", tupleModel);
}
}
I have 2 question, if you guys don't mind could you help me?
When I pass model (just one item of tuple) from controller to View, i have to change it to Tuple<> type and pass 1 value is null. Does this way is correct? It's working for me but I afraid that my way isn't correct.
And then, when model is invalid (example: values's input in to Login form is invalid), error messages will bind into #Html.ValidationSummary(false). But it's showed in 2 places (register and login form). How to resolve this issue?
https://gyazo.com/e9146059a6a098ee787565222d8dc744
Thanks for kind helping

Login and register are two different models. You can get around using Tuples in asp.net with the html helpers. Using a tuple just makes things messy.
What you probably want is something like this:
Register.cshtml file:
#model Models.RegisterViewModel
#using (Html.BeginForm("Register", "Account", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(false)
// Form Register here
}
<div>Or if you already have an account then login:</div>
#Html.RenderAction("Login")
Controller:
public ActionResult Login()
{
if (Request.IsAjaxRequest())
{
return PartialView();
}
}
This will render the login view in the register view, you can also do this the other way around. Although I'd personally just offer a link to the user to redirect them to login page rather than using renderaction.

Related

Mvc View rendering

I'm trying to create contact us page where user fill's in the detail and submit and at the bottom display message which comes from server.
The way i have implemented is something like this.
[HttpGet]
public ActionResult ContactUs()
{
//Process the stuff
return View("~Views/Contact/Contact.cshtml", model)
}
now when page load it hits above method and display form with the layout including header and footer.
Once user submits form it hits below method
[HttpPost]
public ActionResult ContactUs(ContactUs form)
{
//Process the stuff
View.Message="Thank you for your enquiry."
return View("~Views/Contact/Contact.cshtml", model)
}
It returns to the same page but it doesnt render the body layout not even header or footer simply display outputs form.
Not sure what im doing wrong there, is there any better approach ?
Thanks
Based on the code above, I believe you're attempting something like:
public class UxController : Controller
{
public ActionResult WithResponse(ActionResult result, string message)
{
PageResponse(message);
return result;
}
protected void PageResponse(string message)
{
TempData["Ux_Response"] = message;
}
}
That would be your Controller, then the Controller for that specific page, it would look like:
public class HomeController : UxController
{
public ActionResult Index()
{
return View();
}
public ActionResult SubmitForm(string message)
{
return WithResponse(RedirectToAction("Index"), "Thank you for feedback.");
}
}
Then in your front-end code, you would do the following:
#if(TempData["Ux_Response"] != null)
{
<div>#TempData["Ux_Response"]</div>
}
<form action="/Home/SubmitForm" method="post">
<input type="text" name="message" />
<input type="submit" value="Submit" />
</form>
Obviously you could enhance this, with more versatility. However, you're relying on Post, which will cause a screen flicker. So the better route, may be to do Ajax, then return a JsonResult. Hopefully this helps you out.
It should work if you change your controller/view like this.
Controller;
public ActionResult Contact(ContactModel model)
{
ViewBag.Message = "Your contact page.";
return View(model);
}
public ActionResult SaveContact(ContactModel model)
{
//process values in your model and then rest model
ContactModel.Message = "Thank you for contacting us"; //show thank you message
return RedirectToAction("Contact",model);
}
View;
#model MvcApplication1.Models.ContactModel
#{
ViewBag.Title = "Contact";
}
#using (Html.BeginForm("SaveContact", "Home", Model, FormMethod.Post))
{
#Html.DisplayFor(m => m.Message);
<button type="submit">Submit</button>
}
I manged to solve this. the issue was the because i was using sitecore cms the form action wasnt processing it full work flow, after i removed the action, it defaults to action method which defined in cms and triggers the cms workflow.

IEnumerable<SelectListItem> error in Simple Membership in ASP.NET MVC 5

I use Simple Membership in ASP.NET MVC 5 to use my existing SQL Server. In Register page I use dropdownbox to polpulate the user roles. In my Account controller I use [HttpGet] and [HttpPost] for Register Action. When I run my web app and goto Register page I can see my DropDownBox poplulated with data from my SqlServer.
But when I try to create new user and select a role I get error as;
"There is no ViewData item of type 'IEnumerable' that has the key 'roleName'"
Here is my Account Controller code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using SimpleMemberShip.Models;
using WebMatrix.WebData;
using System.Web.Security;
namespace SimpleMemberShip.Controllers
{
public class AccountController : Controller
{
// ****** REGISTER PAGE ACTION ******
[HttpGet]
public ActionResult Register()
{
//Roles for DropDown Box
IEnumerable<SelectListItem> RolesList = new System.Web.Mvc.SelectList(System.Web.Security.Roles.GetAllRoles(), "roleName");
ViewData["roleName"] = RolesList;
return View();
}
// ****** REGISTER PAGE ACTION with PARAMETERS ******
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Register(Register registerdata, string RoleName)
{
if (ModelState.IsValid)
{
try
{
WebSecurity.CreateUserAndAccount(registerdata.Username, registerdata.Password);
System.Web.Security.Roles.AddUserToRole(registerdata.Username, RoleName);
return RedirectToAction("Index", "Home");
}
catch (MembershipCreateUserException exception)
{
ModelState.AddModelError("", "Warning: Username exist...");
return View(registerdata);
}
}
ModelState.AddModelError("", "Warning: Username exist....");
return View(registerdata);
}
}
}
And here is the my Register.cshtml code look like:
#using SimpleMemberShip.Models
#model Register
#{
ViewBag.Title = "Register";
}
<h2>New User Registration</h2>
<br />
<div>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary()
<label>User Name:</label>
#Html.TextBoxFor(m => m.Username, new { #class = "form-control" })
<br />
<label>User Password:</label>
#Html.PasswordFor(m => m.Password, new { #class = "form-control" })
<br />
<label>Roles</label>
#Html.DropDownList("roleName", (System.Web.Mvc.SelectList)ViewBag.roleName, "Select a Role")
<br />
<br />
<button class="btn btn-primary">Register</button>
}
</div>
Does anyone know where I am making mistakes?
Kind Regards,
What most likely happens in that you have some kind of error in the way you register a user, and the validation fails. In that case you are returning the same view, which is fine:
return View(registerdata);
However you are not repopulating the ViewData["roleName"] element which the view depends on. Since ASP.NET MVC does not maintain any kind of state, this element needs to be repopulated on each request. So consider doing that, exactly the same way you were doing it before in the GET action, and the error should go away.
As a side note, you are mixing two ways of passing data from controller into view, model and ViewData. This is not very maintainable and obvious, so I suggest you stick to model approach only. Which should not be very hard - add a list of role names as a property of the model, and make sure to initialize it on every request.
In your HttpPost action, If some error happens, or if your model validation fails, you are returning the same model to the same view. So you need to reload the ViewData["roleName"] with the Role list collection again as your view is trying to read from it to load the dropdown.
Remember, HTTP is stateless. ASP.NET MVC does not work like web forms where it stores the form data in view state across multiple http requests(form posts)
My recommendation is to not use dynamic stuff like ViewBag and ViewData. You should be using a strongly typed viewmodel to transfer data from your action methopd to your views.
so create a view model like
public class CreateUser
{
public string UserName {set;get;}
public string Password{set;get;}
public List<SelectListItem> RolesList {set;get;}
public int SelectedRoleId {set;get;}
}
And in your GET action method, You get all the roles ( Which could be a collection of Role class or some custom class), convert that to a collection of SelectListItem and assign that to the RolesList property of the CreateUser object
public ActionResult Register()
{
var vm=new CreateUser();
vm.RolesList =GetRoles();
return View(vm);
}
private List<SelectListItem> GetRoles()
{
var allRoles = System.Web.Security.Roles.GetAllRoles();
return allRoles.Select(s => new SelectListItem
{
Value = s.roleId.ToString(),
Text = s.roleName
});
}
and in your razor view which is bound to CreateUser class.
#using YourNamespace.CreateUser
#using(Html.Beginform())
{
#Html.TextBoxFor(s=>s.UserName)
#Html.TextBoxFor(s=>s.Password)
#Html.DropDownListFor(x=> x.SelectedRoleId, Model.RolesList,"Select")
<input type="submit" />
}
And now in your Http post action method, Make sure you reload the RolesList collection before you return the posted model back to the same view (If Mode Validation fails or an Exception occurs)
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Register(CreateUser model)
{
if (ModelState.IsValid)
{
try
{
// to do : Save something
// read model.UserName & model.SelectedRoleId etc.
return RedirectToAction("Index", "Home");
}
catch (MembershipCreateUserException exception)
{
ModelState.AddModelError("", "Warning: Username exist...");
//Reload the data now
model.RolesList =GetRoles();
return View(model);
}
}
ModelState.AddModelError("", "Warning: Username exist....");
//Reload the data now
model.RolesList =GetRoles();
return View(model);
}

Only POSTs with both ActionLink and submit button; redirects otherwise

I'm trying to setup a simple form submission in MVC5, but I'm finding that my method doesn't get fired unless I have both an ActionLink and submit button.
I have a simple model:
public class LoginModel
{
public string username { get; set; }
}
Then I have two methods in my controller, one for when a form submission is available and one when not:
[HttpGet]
public ActionResult Login()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel myModel)
{
var username = myModel.username;
// do something with username
return View();
}
Finally, my View creates a POSTing form:
#using (Html.BeginForm("Login", "Home", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.TextBox("username", string.Empty)
#Html.ActionLink("Enter", "Login")
<input type="submit" name="submit" value="Enter" />
}
I don't really care whether I use an ActionLink or whether I have a submit button (MSDN implies it should be the latter), but if I have only one of them, my [HttpPost] method is not called, and my page is redirected with the username in the query string:
/Home/Login?ReturnUrl=%2F%3Fusername%3DmyUsernameHere
If I have both on the page, I can click the ActionLink and I see that the appropriate method is called with myModel.username containing the value I provided. The submit button, however, will still redirect.
I want this form method to be POST, not GET (which it is in the generated HTML), and for failures to not contain the key as a GET param. What's the problem with having only one of these form submission mechanisms? Why do they not trigger the POST as expected? Is there something more I need to do to 'register' my model with the view, even though it is submitted properly in my workaround scenario?
Edit -- My configured routes are typically as follows:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.RouteExistingFiles = true;
So normally, I'm going to /Login instead of /Home/Login.
Also, I do have some authentication setup, with my HomeController decorated with [Authorize] and my Login methods both with [AllowAnonymous]. When I remove all annotations, I still find that my [HttpPost] is not called, and username shows up as a GET parameter instead of being POSTed.
I believe that the application doesn't understand that you're trying to make a model with just username. So, you are sending a string username and attempting to place it into a model. I'm not entirely sure about that. Could you try binding your form to your model? Here's an example:
View:
#model YourApplication.Models.LoginModel
#using (Html.BeginForm("Login", "Home"))
{
#Html.AntiForgeryToken()
#Html.TextBoxFor(model => model.username, string.Empty)
<input type="submit" value="Enter" />
}
Controller:
[HttpGet]
public ActionResult Login()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login([Bind(Include="username")] LoginModel myModel)
{
var username = myModel.username;
// do something with username
return View("Congrats");
}
Here's an alternate option. If you wanted to do something else, maybe try accepting the string "username" and creating your model after? Example:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(string username)
{
LoginModel myModel = new LoginModel();
myModel.username = username;
// do something with username
return View("Congrats");
}
Either way, you should be using the submit button.

asp.net MVC 4. Log out functionality not working

I have this html in my asp.net MVC 4 application:
#if (User.Identity.IsAuthenticated)
<li class="login-link"> {
#Html.ActionLink(User.Identity.Name, "LogOff", "Account")
</li>
}
and this controller action
//[HttpPost]
//[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
WebSecurity.Logout();
return RedirectToAction("Index", "Home");
}
but when i click log out link, user doesnt log out.
Please suggest how to fix it
In your code sample above, you wrote:
#if (User.Identity.IsAuthenticated)
<li class="login-link"> {
#Html.ActionLink(User.Identity.Name, "LogOff", "Account")
</li>
}
Based on how your #if (User.Identity.IsAuthenticated) statement resolves this may lead to some wonky code being rendered for the browser.
Does the following Razor code more correctly reflect your intent?
#if (User.Identity.IsAuthenticated)
{
<li class="login-link">
#Html.ActionLink(User.Identity.Name, "LogOff", "Account")
</li>
}
I couldn't find anything wrong with your code.
But try this and let me know if it works for you:
cshtml :
#Html.ActionLink("Log Off", "LogOff", "LogOn", null, new { #class = "actnclass" })
controller :
public ActionResult LogOff()
{
Request.Cookies.Remove("UserId");
FormsAuthentication.SignOut();
return RedirectToAction("LogOn", "LogOn");
}
Your Razor code looks good. Even a basic html logout code like the one below should do
Logout
Keep a breakpoint in your LogOff action in your Account controller and see what is happening. You seem to be using WebSecurity.Logout(); to logout and then redirect the user to the Home/Index page.
Also would like to know by looking at what are you inferring that user has not logged out.
Nothing wrong with you code. The point is the Action LogOff in Account is HttpPost Method, so it will only accept from a form submit.
You have to 3 ways to achieve Log off functionality
Change LogOff Method to HttpGet instead of HttpPost in Account Controller
Add an HttpGet overload for it take one parameter to leave the HttpPost method in Account Controller
[HttpGet]
public ActionResult LogOff(string token) {
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
return RedirectToAction("Index", "Home");
}
// Already Exists
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff() {
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
return RedirectToAction("Index", "Home");
}
Use Javascript to submit the LogOff form
#using Microsoft.AspNet.Identity
#if (Request.IsAuthenticated)
{
using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm" }))
{
#Html.AntiForgeryToken()
// ...
LogOff
}
}

prevent full page reload when partial fail - login register

I create a single page to manage login and registration task in my mvc 4 app. when i create a user using registration, and registration fails - eg password mismatch - the page process the error and refresh but inserting a complete page in the renderaction part of register.
on the other hand, when login fail - user not exists - the page refresh but direct the user to a page displaying only the renderaction login part, not the whole layout.. any help appreciated:
steps: I created a model
public class Access
{
public LoginModel LoginModel { get; set; }
public RegisterModel RegisterModel { get; set; }
}
then I create a page:
<div class="main-content" style="min-height: 700px;">
<!-- Login form -->
#{ Html.RenderAction("Login", "Account"); }
<!-- Register form -->
#{ Html.RenderAction("Register", "Account"); }
<div class="clear"></div>
</div>
the controller is the typical one provided in mvc4 template. an example for login
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return PartialView(model);
}
You're returning a ParitalView on fail, you may want to return the view that contains the whole page. ie
edit
//recreate the compound viewmodel in the login controller
Access viewModel = new Access();
viewModel.LoginModel = model;
viewModel.RegisterModel = new RegisterModel();
return View("LoginOrRegister", viewModel); //supposing the view name is LoginOrRegister.cshtml
//recreate the compound viewmodel in the register controller
Access viewModel = new Access();
viewModel.LoginModel = new LoginModel();
viewModel.RegisterModel = model;
return View("LoginOrRegister", viewModel); //supposing the view name is LoginOrRegister.cshtml
also the loginorregister.cshtml page will have to use the Access class that encompasses both the LoginModel and RegisterModel as the model for the View, ie.
#model Access
And when you render the login or register model, you need to pass in the model parameters - so that the user doesn't need to re-enter data.
*edit - Also try changing from RenderAction to Html.RenderPartial() so that it doesn't route through the actions for the intial render - do this and add [HttpPost] annotations on the controllers * the code would look something like
#model Access
<div class="main-content" style="min-height: 700px;">
<!-- Login form -->
#{ Html.RenderPartial("Login", new {model = Model.LoginModel, returnUrl = "?"}) };
<!-- Register form -->
#{ Html.RenderPartial("Login", "Account", new { model = Model.RegisterModel) }; }
<div class="clear"></div>
</div>
and
[HttpPost]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
//recreate the compound viewmodel in the login controller
Access viewModel = new Access();
viewModel.LoginModel = model;
viewModel.RegisterModel = new RegisterModel();
return View("LoginOrRegister", viewModel);
}
ah, also instead of using the viewbag to initially pass in the data, you'd have to pass it in using the model - because I've elected to use the code "#Model Acesss" in the razor view, so the register ActionResult would look like...
public ActionResult Register(){
Access viewModel = new Access();
viewModel.LoginModel = new LoginModel();
viewModel.RegisterModel = new RegisterModel();
return View("LoginOrRegister",viewModel);
}
I didn't realize you were so new to mvc. Keep at it. :) Another tutorial may help

Categories