I'm passing a string to Redirect but the controller is not sending the browser to the appropriate location.
The string is: "/Admin/SystemSecurity/_PermissionDetail/1"
The code is:
public ActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
// Code get's here, but seems to go to /Submission/Index
return Redirect(returnUrl);
}
return RedirectToAction("Index", "Submission");
}
In this case, the method that calls RedirectToLocal is _Login in the SubmissionController:
[ChildActionOnly]
public ActionResult _Login(string returnUrl)
{
if (Request.Cookies["UserName"] != null && !string.IsNullOrEmpty(Request.Cookies["UserName"].Value))
{
var loginModel = new Login { Email = Request.Cookies["UserName"].Value, ReturnUrl = returnUrl};
return PartialView(loginModel);
}
return PartialView();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> _Login(Login model, string returnUrl)
{
var isLoggedIn = UserLogin(model.Email, model.Password);
if (!isLoggedIn)
{
TempData["ErrorMessage"] = "Invalid email address or password.";
return RedirectToAction("Index", new { returnUrl = returnUrl });
}
// I make the call here, the values is correct here.
return RedirectToLocal(returnUrl);
}
Here's the Index method, also in SubmissionController:
public ActionResult Index(string message, string returnUrl)
{
IsAuthenticated();
if (!string.IsNullOrEmpty(message) )
AddMessage(message);
if (!string.IsNullOrEmpty((string)TempData["ErrorMessage"]))
{
AddError((string)TempData["ErrorMessage"]);
}
ViewBag.ReturnUrl = returnUrl;
return View();
}
After the POST _Login RedirectToLocal, the main Index method gets called again. Not sure who/what calls it. Probably something simple I'm missing.
For clarification I'm posting more of my View data here:
/Submission/Index:
#{
Layout = "~/Views/Shared/_Home.cshtml";
}
<div>
<p>...</p>
</div>
/Shared/_Home
#using PublicationSystem.ViewModels
#{
ViewBag.Title = "_Home";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div class="container-fluid home-banner">
<!--Content from Index Page -------------------------------------->
<div class="clearfix"></div>
<div class="container">
<div class="row">
<div class="col-sm-4 col-md-3 col-lg-3">
<div class="left-side-blue">
...
</div>
</div>
<div class="col-sm-8 col-md-9 col-lg-9">
#{ Html.RenderPartial("_ErrorMessages"); }
#if (!ViewBag.IsAuthenticated)
{
Html.RenderAction("_Login", new { returnUrl = ViewBag.ReturnUrl });
}
else
{
<div class="hp-nav-boxes">...</div>
}
</div>
</div>
</div>
...
</div>
/Shared/_Login:
#model PublicationSystem.ViewModels.Login
<div class="login-box">
<div class="row">
#using (Html.BeginForm("_Login", "Submission", new { returnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { #class = "col-sm-6 col-md-4 col-lg-4 pull-right custm-login ipadsm4" }))
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.TextBoxFor(m => m.Email, new { #class = "form-control", #placeholder = "Email" })
#Html.ValidationMessageFor(m => m.Email, "", new { #class = "text-danger" })
</div>
<div class="form-group">
#Html.PasswordFor(m => m.Password, new { #class = "form-control", #placeholder = "Password" })
#Html.ValidationMessageFor(m => m.Password, "", new {#class = "text-danger"})
</div>
<button type="submit" class="btn btn-default pull-right">
Login <span aria-hidden="true" class="glyphicon glyphicon-play"></span>
</button>
<div class="clearfix"></div>
}
</div>
The login logic works and the user can get logged in. It's just this redirect that's messing up.
You're calling _Login a partial, but partials don't have associated actions. It might be a child action, but you can't post to child actions. As result, _Login is just a standard action, and unless you post to it directly via something like:
#using (Html.BeginForm("_Login"))
{
...
}
And then submit that form on the resulting page, the action will never be hit.
Assuming you're actually doing it that way already, and just confused about the terminology, then the next place to look is at the inconsistency in return URL variables. Your action accepts returnUrl as a param, but you're only using that if the user is not logged in, where you redirect to Index with that return URL. However, if the user is logged in, then you're calling RedirectToLocal with model.ReturnUrl instead.
Related
I'm using an html form to post form data for logging in. I put it in a modal but it does absolutely nothing other than redirect to main page, and append the verification token, id and password to the url. Is there any reason why? It doesn't even enter the account controller
I just took the post request from the default Login class that comes with mvc projects and put it inside the modal.
AccountController.cs:
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return PartialView("Login", model);
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
Login.cshtml
#using Products.Models
#model LoginViewModel
#{
ViewBag.Title = "Log in";
}
<head>
<link rel="stylesheet" href="~/Content/loginmodal.css">
</head>
<!-- Modal -->
<div id="modal" class="modal">
<div class="modal-content">
<div class="modal-body">
<div class="Absolute-Center is-Responsive">
<div id="logo-container"></div>
<div class="col-sm-12 col-md-10 col-md-offset-1">
<form action="" id="loginForm">
#using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
#Html.TextBoxFor(m => m.Email, new { placeholder = "E-mail", #class = "form-control" })
#Html.ValidationMessageFor(m => m.Email, "", new { #class = "text-danger" })
</div>
<div class="form-group input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></span>
#Html.PasswordFor(m => m.Password, new { placeholder = "Password", #class = "form-control" })
#Html.ValidationMessageFor(m => m.Password, "", new { #class = "text-danger" })
</div>
<div class="form-group">
<input type="submit" value="Log in" class="btn btn-def btn-block" />
</div>
<div class="form-group text-center">
#Html.CheckBoxFor(m => m.RememberMe)
#Html.LabelFor(m => m.RememberMe)
Forgot Password | Support
</div>
}
</form>
</div>
</div>
</div>
</div>
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
LoginViewModel:
public class LoginViewModel
{
[Required]
[Display(Name = "Email")]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
In your code you have <form action="" id="loginForm"> and Html.BeginForm() on the following line. The Html.BeginForm() renders another <form> element, nested in your case - which is not valid HTML. Browser probably takes the outer <form> where you specified action="", that is why it is not entering your Account controller.
remove this form
form action="" id="loginForm"
as you are creating 2 forms.
#using (Html.BeginForm . . .) creates a form dynamically which will hit your actionresult.
I am working on asp.net MVC project. I have standard Account controller which it creates it self after change the project authentication to `individual User Accounts after creating the project.
it has Remember me check box it self in login view. my problem is that it remembers the user information (username and password) less than 10 minutes. how I can do something to make this time long lasts more than 2 hours for example or more.
here is my account controller login action:
[Authorize]
public class AccountController : Controller
{
private ApplicationSignInManager _signInManager;
private ApplicationUserManager _userManager;
public AccountController()
{
}
public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager )
{
UserManager = userManager;
SignInManager = signInManager;
}
public ApplicationSignInManager SignInManager
{
get
{
return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
}
private set
{
_signInManager = value;
}
}
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
//
// GET: /Account/Login
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
}
and here is its view if needed:
<div class="row">
<div class="col-md-8">
<section id="loginForm">
#using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.Email, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Email, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Email, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Password, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.PasswordFor(m => m.Password, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Password, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
#Html.CheckBoxFor(m => m.RememberMe)
#Html.LabelFor(m => m.RememberMe)
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Log in" class="btn btn-default" />
</div>
</div>
//Enable this once you have account confirmation enabled for password reset functionality
<p>
#Html.ActionLink("Forgot your password?", "ForgotPassword")
</p>
}
</section>
</div>
<div class="col-md-4">
<section id="socialLoginForm">
#Html.Partial("_ExternalLoginsListPartial", new ExternalLoginListViewModel { ReturnUrl = ViewBag.ReturnUrl })
</section>
</div>
</div>
I am doing a login logout functionality using Ajax partial. For this I have a View which checks if the user is logged in or not and shows login or logout form accordingly.
On submit it does a ajax request and logs in or out the user. after doing so in controller I return same partial view.
So expected behavior is on return partial view must again check for login status and refresh the view accordingly, but instead same form is loaded.
Partial View:
#model Models.LoginModel
#if (Member.MemberIsLoggedOn())
{
using (Ajax.BeginForm("LoginForm", "Account", null, new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "login-form-update",
},new {
#class="loginform form"
}))
{
<div class="col-md-12 padding-zero">
<div class="row flt-right">
Hello #Context.User.Identity.Name, <input type="submit" name="logout" class="btn btn-default" value="Log Out" />
</div>
</div>
}
}
else
{
using (Ajax.BeginForm("LoginForm", "Account", null, new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "login-form-update",
}, new {
#class = "loginform form"
}))
{
<div class="col-md-12 padding-zero">
<div class="row flt-right">
<div class="form-group col-md-5">
#Html.TextBoxFor(x => Model.Username, new { #class = "form-control", #placeholder = "Username" })
</div>
<div class="form-group col-md-5">
#Html.TextBoxFor(x => Model.Password, new { #class = "form-control", #placeholder = "Password", #type = "Password" })
</div>
<div class="form-group col-md-2 flt-right">
<input type="submit" name="login" class="btn btn-default" value="Go" />
</div>
</div>
</div>
}
}
Controller:
public class AccountController : Controller
{
[HttpPost]
public ActionResult LoginForm(LoginModel model)
{
if (!ModelState.IsValid)
{
//Do nothing
}
// Login
if (Membership.ValidateUser(model.Username, model.Password))
{
FormsAuthentication.SetAuthCookie(model.Username, false);
return PartialView("Header/LoginForm", new Models.LoginModel());
}
else
{
ModelState.AddModelError("Username", "Username is not valid");
//do nothing
}
}
public ActionResult Logout()
{
FormsAuthentication.SignOut();
Session.Clear();
return PartialView("Header/LoginForm", new Models.LoginModel());
}
}
Now my problem is login/logout happens properly, but changes do not reflect unless page is refreshed, which is i want to avoid by Ajax.BeginForm(),
update
If i click two times the view changes, but this is not a good user experience.
I think this might be due to cache problem. You need to use output cache attribute to disable the cache for that action method .
You can use something like this.
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
Also clear your ModelState before the return .
ModelState.Clear();
return PartialView(model);
I have an administrator EDIT view created for USERS in my MVC5 application. Down in the bottom right I have a DELETE button that first asks the Admin logged in to confirm user deletion and then is supposed to execute the POST - Delete method in my UserManagement Controller. However when I confirm the deletion, I get an error of:
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Threading.Tasks.Task`1[System.Web.Mvc.ActionResult] Delete(Int32)' in 'PROJECT.Areas.Admin.Controllers.UserManagementController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
Parameter name: parameters
This is my delete button on the View:
<div class="col-md-2" style="width:180px; float: right; padding-top: 12px; padding-bottom: 7px; border-bottom: 1px solid #dddddd; ">
#using (Html.BeginForm("Delete", "UserManagement", new { id = Model.Id }, FormMethod.Post))
{
#Html.AntiForgeryToken()
<input type="submit" class="btn btn-danger btn-xs" value="Delete User" onclick="return confirm('Are you sure you want to delete this User?');" />
}
</div>
And this is the [HttpPost] of my Delete action:
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Delete(int id)
{
ApplicationUser applicationUser = db.Users.Find(id);
if (applicationUser == null)
{
ModelState.AddModelError("", "Failed to find User ID for deletion.");
}
else
{
IdentityResult result = await UserManager.DeleteAsync(applicationUser);
if (result.Succeeded)
{
await db.SaveChangesAsync();
return RedirectToAction("Index", "UserManagement");
}
else
{
ModelState.AddModelError("", "Failed to Delete User.");
var errors = string.Join(",", result.Errors);
ModelState.AddModelError("", errors);
}
}
return View(applicationUser);
}
Clearly somewhere I am failing to get the ID passed from the view to the Action, but I'm not quite sure how to resolve the issue. I'm still faily new to MVC development, let alone ASP.Net Identity. Any assistance appreciated!
EDIT:
I've tweaked my approach a little, but am still getting the same error. I have a Delete Confirmation View which loads the Delete() GET method. The View resembles the following:
#model PROJECT.Models.ApplicationUser
#{
ViewBag.Title = "Delete";
Layout = "~/Areas/Admin/Views/Shared/_LayoutAdmin.cshtml";
string cancelEditUrl = "/Admin/UserManagement/";
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this User?</h3>
#using (Html.BeginForm("Delete", "UserManagement", new { id = Model.Id }, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.Id)
#Html.HiddenFor(model => model.ForumUsername)
#Html.HiddenFor(model => model.ReceiveSystemEmails)
#Html.HiddenFor(model => model.RegisteredDate)
#Html.HiddenFor(model => model.LastVisitDate)
#Html.HiddenFor(model => model.MemberOrgId)
#Html.HiddenFor(model => model.ProfilePictureSrc)
<div class="container">
<div class="row">
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Name)
</div>
</div>
<div class="row">
<div class="editor-label">
#Html.LabelFor(model => model.Position)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.Position, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Position)
</div>
</div>
.....
<div class="row">
<div class="col-md-1" style="padding-left: 0px">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
<div class="col-md-9" style="padding-left: 0px">
Cancel
</div>
</div>
}
My Controller Actions for Delete():
//GET: Admin/UserManagement/Delete/5
public async Task<ActionResult> Delete(string id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
ApplicationUser applicationUser = await UserManager.FindByIdAsync(id);
if (applicationUser == null)
{
return HttpNotFound();
}
return View(applicationUser);
}
// POST: Admin/UserManagement/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Delete(int id)
{
ApplicationUser applicationUser = db.Users.Find(id);
if (applicationUser == null)
{
ModelState.AddModelError("", "Failed to find User ID for deletion.");
}
else
{
IdentityResult result = await UserManager.DeleteAsync(applicationUser);
if (result.Succeeded)
{
await db.SaveChangesAsync();
return RedirectToAction("Index", "UserManagement");
}
else
{
ModelState.AddModelError("", "Failed to Delete User.");
var errors = string.Join(",", result.Errors);
ModelState.AddModelError("", errors);
}
}
return View(applicationUser);
}
ERROR:
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Threading.Tasks.Task`1[System.Web.Mvc.ActionResult] Delete(Int32)' in 'PRISMdev.Areas.Admin.Controllers.UserManagementController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
Parameter name: parameters
I'm not quite sure why my Model ID is coming in as NULL still to the Delete() POST action.
I'm using ajax for two different partial views in the same parent view. The ajax request is submitted successfully, and the OnSuccess() function is called, but the page won't redirect. Here's my code. I've made sure that the returnurl is an absolute address.
VIEW:
#{
ViewBag.Title = "Log in/Register";
}
<hgroup class="title">
<h1>#ViewBag.Title.</h1>
</hgroup>
#section CustomScripts {
<script type ="text/javascript">
function OnSuccess(){
var returnUrl = #ViewBag.ReturnUrl
window.location = returnUrl
}
</script>
}
<section id="loginForm">
#{
if(!WebSecurity.IsAuthenticated){
<h2>Use a Creative Works account to log in.</h2>
#Html.Action("_LoginPartial", new {returnUrl = ViewBag.ReturnUrl })
}
}
</section>
<section id ="RegisterForm">
#{
if(!WebSecurity.IsAuthenticated){
<span>Don't have an account? Make one!</span>
#Html.Action("RegisterPartial", new { returnUrl = ViewBag.ReturnUrl })
}
}
</section>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
PartialView for login:
#{
ViewBag.Title = "LoginPartial";
}
#using (Ajax.BeginForm("_LoginPartial", new {returnUrl = ViewBag.ReturnUrl},
new AjaxOptions(){UpdateTargetId = "loginForm",
InsertionMode = InsertionMode.Replace, OnSuccess = "OnSuccess"
})) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Log in Form</legend>
<ol>
<li>
#Html.LabelFor(m => m.UserName)
#Html.TextBoxFor(m => m.UserName)
#Html.ValidationMessageFor(m => m.UserName)
</li>
<li>
#Html.LabelFor(m => m.Password)
#Html.PasswordFor(m => m.Password)
#Html.ValidationMessageFor(m => m.Password)
</li>
<li>
#Html.CheckBoxFor(m => m.RememberMe)
#Html.LabelFor(m => m.RememberMe, new { #class = "checkbox" })
</li>
</ol>
<input type="submit" value="Log in" />
</fieldset>
}
and the controller:
[AllowAnonymous]
public ActionResult _LoginPartial(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return PartialView(new LoginModel());
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult _LoginPartial(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
return PartialView();
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return PartialView(model);
}
EDITS:
Parent View Controller
[AllowAnonymous]
[DisableCache]
public ActionResult Login(string returnUrl ="/")
{
var path = VirtualPathUtility.ToAbsolute(returnUrl);
var url = new Uri(Request.Url, path).AbsoluteUri;
ViewBag.ReturnUrl = url;
return View();
}
The problem was me accessing viewbag directly in my javascript. The answer was to replace my javascript with this.
#section CustomScripts {
<script type ="text/javascript">
function OnSuccess() {
var returnUrl = #Html.Raw(Json.Encode(ViewBag.ReturnUrl))
window.location = returnUrl;
}
</script>
}