MVC 4 - LogOff controller action giving 404 not found - c#

I'm just wrapping up a college project, I'm not sure if I've been staring at my computer for too long and am missing something obvious, but when I try to log a user out, I'm getting a 404 not found for the URL /Account/LogOff.
I have a navbar that shows Log in/Log out depending on whether a user is, logged in, or, logged out:
<div class="nav-collapse collapse">
<ul class="nav pull-right">
<li class="dropdown" id="dropdown-login-div">
#if (!Request.IsAuthenticated)
{
<a class="dropdown-toggle" href="#" data-toggle="dropdown">Sign In <strong class="caret"></strong></a>
}
else
{
#Html.ActionLink("Log Off", "LogOff", "Account")
}
<div class="dropdown-menu" id="dropdown-login">
#Html.Partial("~/Views/Account/_LoginPartial.cshtml", new ViewDataDictionary<LetLord.Models.LoginModel>())
</div>
</li>
</ul>
</div>
In my Account controller the default LogOff action that comes with the Internet template:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
WebSecurity.Logout();
return View("Index");
}
Could someone tell me why this happening - before I throw my laptop against the wall. Cheers.

You use a link (<a/> tag) to log off which results in HTTP GET request when user clicks on it, but your action is constrained to serve POST request only (because it is decorated with [HttpPost] attribute).
You either need to put your link into a form and generate POST request, or remove [HttpPost] and [ValidateAntiForgeryToken] (credits to GalacticCowboy) from your action.

Since logout modifies server state, I wouldnt remove [HttpPost] and [ValidateAntiForgeryToken]
Instead I will replace the link (anchor tag) with the following
#using (Html.BeginForm("Log Out", "Account", FormMethod.Post,
new { id = "logoutForm" }))
{
#Html.AntiForgeryToken()
Log Out
}

I ran into this issue on a legacy app. The way that I fixed it was to detect when the supplied return Url was '/Account/LogOff' and act accordingly.
From the 'AccountController.cs' file, 'Login' method:
if (returnUrl == "/Account/LogOff")
{
return this.RedirectToLocal(null);
}
else
{
return this.RedirectToLocal(returnUrl);
}

Related

Cannot reach Controller/Action on LogOff button click

I have used the MVC template in an application and modified it as I need. On Logout button click, I should be able to reach the logout action where I define my logout logic. But when I click the button, it directly takes me to the login screen without hitting the logoff action in the controller.
I tried adding an action link and specifying explicitly to reach the logoff action and it did not work either.
If I remove this action, it throws an error that it's not found. I checked in the fiddler tool and this action is called on the button click and redirected to Login, but the breakpoint is never hit or if I change the content inside the logoff action, it is not reflected.
I also tried using a different name for the same action, no change in the behavior was found.
Here is the view and controller action.
// POST: /Account/LogOff
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
//AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
return RedirectToAction("Login", "Account");
}
The partial view embedded in layout:
#if (1==1)
{
using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm" }))
{
#Html.AntiForgeryToken()
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<p class="navbar-text">Allturna's Insights Portal</p>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>#Html.ActionLink("Home", "Index", "Home")</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<p class="navbar-text">Hello!</p> #*Use username here from viewdata*#
</li>
<li>
<li>
#Html.ActionLink("Tenants", "GetTenant", "Home", new { ReturnUrl = ViewBag.ReturnUrl }, null)
</li>
<li>Log off</li>
<li><img src="~/Content/Image/Allturna_logo.png" height="50" width="220" /></li>
</ul>
</div>
}
}
The RouteConfig.cs is as below:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Account", action = "Login", id = UrlParameter.Optional }
);
}
}
I have not been able to figure out why the logoff button click/form submission can not reach the logoff action. I would appreciate any help.
As I am working on it, I wanted to add some additional notes. If I use the default aspnet template with identity, it reaches the logoff button. But I am trying to implement my own login and logout logic without using Identity. That's when it does not hit the logoff.
I have posted both logic (with/without identity) hits the logoff as expected.
The following with Identity works:
[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:
//Add tenantId to session from user info (temporary testing with Allturna)
Session["TenantId"] = 1;
Session["AccessToken"] = "my token";
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);
}
}
when I use the following login logic (without identity), the Logoff does not get hit.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
//get the authentication URL
string strJwtToken = string.Empty;
string strAuthenticationUrl = ConfigurationManager.AppSettings.Get("AuthenticationServerEndPoint");
if (String.IsNullOrEmpty(strAuthenticationUrl))
throw new Exception("Authentication Endpoint is not specified.");
ServiceClient client = new ServiceClient(strAuthenticationUrl, "Authenticate");
dynamic objAuthentication = new ExpandoObject();
objAuthentication.UserName = model.Email;
objAuthentication.Password = model.Password;
client.AddJsonBody(objAuthentication);
HttpStatusCode httpReturnCode = client.Post(out strJwtToken);
if (httpReturnCode == HttpStatusCode.OK)
Session["AccessToken"] = strJwtToken;
return RedirectToLocal(returnUrl);
}
I would need to understand what does the identity framework/SignInManager does that my logic is missing and how I can add that to my logic and make the logout work correctly. Thanks in advance.

Getting model data without a viewbag

I'm an intern working on some quality-of-life improvements for a site. Nothing essential, but since this is actually going to be deployed I want to keep things tidy.
I need to pull a table from a database, and display it in in a shared header on a site. I already have the database set up, the model getting data from the database, and a test view just to see if it's displaying correctly. However, the model is being passed by the page controller through the viewbag, which I've been told not to do.
So, how can I have my page print out my model data without passing it through a viewbag?
You can do that using this format
public ActionResult Index(YourModelHere model)
{
return View(model);
}
In your view, add this
#model yournamespacehere.Models.YourModelHere
UPDATE
Create a new controller for partial view data
//partial view call
public ActionResult GetHeaderPartial()
{
var model = new DataModel();
model.data1 = "Menu 1";
model.data2 = "Menu 2";
return PartialView("_HeaderPartial", model);
}
Create the Partial View for the shared header. In this example, i've named it as "_HeaderPartial"
#model yournamespace.Models.DataModel
<li>#Model.data1</li>
<li>#Model.data2</li>
In your shared header layout, add #Html.Action("GetHeaderPartial","ControllerHere")
<header>
<div class="content-wrapper">
<div class="float-left">
<p class="site-title">#Html.ActionLink("your logo here", "Index", "Home")</p>
</div>
<div class="float-right">
<section id="login">
#Html.Partial("_LoginPartial")
</section>
<nav>
<ul id="menu">
<li>#Html.ActionLink("Home", "Index", "Home")</li>
<li>#Html.ActionLink("About", "About", "Home")</li>
<li>#Html.ActionLink("Contact", "Contact", "Home")</li>
#Html.Action("GetHeaderPartial","Sample")
</nav>
</div>
</div>
</header>
Here's a good place to start. As the article states, there are basically three ways to pass data to the view:
ViewBag
Dynamic objects
Strongly Typed objects
With the latter approach (generally a good idea), you just have to pass the model instance to the ActionResult you're returning (in your case, that would be a ViewResult instance).
So instead of this....
public ActionResult Index()
{
ViewBag.Foo= new Foo();
return View();
}
you do this...
public ActionResult Index()
{
return View(new Foo());
}
Make sure your model in the Index view has the following line:
#model Foo
Now you can use Razor or whatever syntax you're using to do what you need to do in your view.
Setup a view model that is created in the controller and passed to the view. This link will help you!

Redirecting parent page from Html.renderAction child without using Ajax, Java, Jquery or such

I have a problem where I have a form in a Html.RenderAction and after submitting the form I have to reload the parent but I keep getting "Child actions can not perform redirect actions". So how can I solve it without Ajax etc.
In my parent I have:
#{
var UserReviewExist = Model.Reviews.FirstOrDefault(x => x.AspNetUser.UserName == Name.AspNetUser.UserName);
}
#{if (UserReviewExist == null)
{
Html.RenderAction("ReviewCreate", "Reviews", new { BookID = Model.Id });
}
}
My RenderAction View contains this:
#model Trigger_Happy_Bunnies.Models.Review
#{
Layout = null;
}
#{
if (true)
{
Trigger_Happy_Bunnies.Models.Review newReview = new Trigger_Happy_Bunnies.Models.Review();
<div style="border:1px black">
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
and ends with
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
</div>
}
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
And lastly I have this in my controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ReviewCreate([Bind(Include = "Id,BookId,UserId,Text,Title,Rating,IsActive,IsReported,ReportedBy,ReportReason,ModifiedDate,ModifiedBy,CreatedDate")] Review review)
{
if (ModelState.IsValid)
{
db.Reviews.Add(review);
db.SaveChanges();
return View("~/Views/Reviews/ReviewCreate.cshtml");
}
ViewBag.UserId = new SelectList(db.AspNetUsers, "Id", "Email", review.UserId);
ViewBag.BookId = new SelectList(db.Books, "Id", "UserId", review.BookId);
return PartialView();
}
So how can I update the parent view when submitting the form?
I'm not sure what your issue is here. A child action merely dumps its response into the view. So at the end of the day, whether you used a child action, a partial or just plopped the code right in the view, you just have a one HTML document that includes a form.
Calling Html.BeginForm with no parameters says basically that it should use the current action, but even in the context of child action, that's still going to be the main action being rendered. So, your form will post to that main action, not your child action.
That's how it should be. You cannot post to a child action, because that makes no sense in the context of a web page. Technically, you can as long as it's not marked as [ChildActionOnly], but the entire page will change to the partial view that's returned as the response, sans layout. If you want to replace just the area that was rendered via the child action, you must submit an AJAX request that returns the partial response and manually replace the appropriate node in the DOM with that.
In other words, that's why a child action can't redirect. It's not a true action and it hasn't been routed to. It's not rendered until the response preparation phase, and by that point, there's already data in the response, preventing any changes, like a redirect. If you need to redirect after the post of the form, you should have that already in place, just make sure your main action has a version that handles post, and redirect from there.

Cannot get MVC 5 Attribute Routing to work

I am working on an MVC5 application with C# back-end. I am attempting to get change /Controller/Action?parameter=value show as /Controller/Action/Value.
Currently I have an action link on a page setup in hopes to be able to view the user's profile info:
#{ var test = new RouteValueDictionary {{"username", "testman"}}; }
<h2>#ViewBag.Title</h2>
<div class="row">
<div class="col-md-2" style="border-right:1px solid #EEE; min-height: 250px;">
Sidebar 1
</div>
<div class="col-md-8">
test link
</div>
<div class="col-md-2" style="border-left:1px solid #EEE; min-height: 250px;">
Sidebar 2
</div>
</div>
And here is the Action:
[RequireRequestValue(new[] { "username" })]
[Route("Test/{username}")]
public ActionResult Test(string username)
{
var route = RouteData.Route;
try
{
ViewBag.Page = 9;
ViewBag.Title = "Testing";
ViewBag.Message = username;
return View();
}
catch (Exception ex)
{
var error = new HandleErrorInfo(ex, "Home", "Test");
ViewBag.errorMessage = ex.Message;
return View("Error", error);
}
}
I have added routes.MapMvcAttributeRoutes(); to my RouteConfig.cs file.
If I do not add the [Route("Test/{username}")] Attribute to my action, the link renders like this: Home/Test?username=testman. If I add the Attribute the link renders to my liking: Home/Test/testman.
Unfortunately, I get this server 500 error when I click on the link, and this is where I have been stuck:
No matching action was found on controller '<project>.Controllers.HomeController'.
This can happen when a controller uses RouteAttribute for routing, but no action on
that controller matches the request.
For fun I even tried creating an explicit ActionResult to see if I could get something different, but no luck there.
I am obviously missing something...can anyone help me get on the right track?
Thanks!

MVC project, Error because model not passed to view?

The Scenario is this...I goto the login screen on my web app. I login with wrong username/password. I get an error on the line in my HTML for tab-6 #HTML.Partial("../../Account/Register") because the RegisterModel is not passed in(Exact error below). Well I'm not doing anything with the Register screen, just logging in.
However, I think the problem is because the Register section is always loaded. So I'm thinking, I don't need to pass in both LogOnModel and RegisterModel, to fix the problem, but maybe should not load the Register page until it is specifically called, I think? Is that the correct thing to do and how would I load the Register page only when it is called/clicked. I'm using JQuery UI tabs and asp.net mvc.
ERROR:
The model item passed into the dictionary is of type 'Portal.Web.Models.LogOnModel', but this dictionary requires a model item of type 'Portal.Web.Models.RegisterModel'.
Thanks a lot!
<div id="tabs" class="ui-tabs ui-widget ui-widget-content ui-corner-all" style=" position:relative; border:0px;" >
<ul class="ui-tabs-nav">
<li><a href="#tabs-1" >Home</a></li>
<li><a href="#tabs-2" >Statistics</a></li>
<li><a href="#tabs-3" >Topo Maps</a></li>
<li><a href="#tabs-4" >FAQs</a></li>
<li style="display:none;">Login</li>
<li style="display:none;">SignUp</li>
</ul>
<div id="tabs-1" class="ui-tabs-hide ui-tabs-panel">#Html.Partial("../Home/Home") </div>
<div id="tabs-2" class="ui-tabs-hide ui-tabs-panel">#Html.Partial("../Statistics/Statistics")</div>
<div id="tabs-3" class="ui-tabs-hide ui-tabs-panel">#Html.Partial("../Maps/Maps")</div>
<div id="tabs-4" class="ui-tabs-hide ui-tabs-panel">#Html.Partial("../Home/FAQs")</div>
<div id="tabs-5" class="ui-tabs-hide ui-tabs-panel">#Html.Partial("../Account/LogOn")</div>
<div id="tabs-6" class="ui-tabs-hide ui-tabs-panel">#Html.Partial("../Account/Register")</div>
</div>
Here is my AccountController with action method
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
FormsService.SignIn(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
return View(model);
}
As Requested Here is the additional code:
public ActionResult LogOn()
{
return View();
}
Double-check that you've set your partial view to inherit from the appropriate model. At the top of your partial view code, you should have a line like this.
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Portal.Web.Models.LogOnModel>" %>

Categories