TempData & Redirect for Errors - asp.net-mvc

I have a controller action in my project that has a situation where it needs to display an error message to the user under certain scenarios. This action occurs in a POST:
[HttpPost]
public ActionResult DoSomeAction() {
if( someCondition )
return RedirectToAction("SomeActionError");
return RedirectToAction("Index");
}
public ActionResult SomeActionError() {
return View();
}
Currently I have it set up so that it will redirect to an error controller action. I'm not really fond of this approach because in the URL they see /SomeActionError and it also means that the user can directly navigate to this URL.
Is it a bad design/approach to put some flag in TempData and redirect to another controller that checks for the TempData error flag?
Example:
[HttpPost]
public ActionResult DoSomeAction() {
if( someCondition ) {
TempData["DoSomeActionError"] = true;
}
return RedirectToAction("Index");
}
public ActionResult Index() {
// check for error
if( TempData["DoSomeActionError"] ) {
return View("SomeActionError");
}
}
Is this a bad idea? Is there another approach that does something similar (doesn't allow the user to directly navigate to the error action)? I don't want to return the View on the POST action because I don't want them to refresh and cause another POST.

TempData is not per se a bad concept. TempData is for transporting an information to some consumer that reads that information and the information should vanish after it's been read.
The way your're using TempData is odd. A more elegant implementation for your requirements (you should show an error message) is to implement an equivalent to the rails flash concept and don't redirect to an error page but display an error message in your index view. Something like a red banner that says "The record could not be saved".
This question shows a nice flash implementation including the view stuff (not the accepted answer but the answer by #jim)

using tempdata in mvc is not a good approach.
If i were you i'll do as following:
[HttpPost]
public ActionResult DoSomeAction() {
if( someCondition ) {
return RedirectToAction("Index", new{error=true}
}
return RedirectToAction("Index");
}
public ActionResult Index(bool? error) {
// check for error
if(error?? false ) {
return View("SomeActionError");
}
}

While I don't agree TempData is always bad (I find it great for status messages I absolutely don't want passed on the url such as "record saved", I think in your case there may be a better option.
First you don't want an error page to be accessible - may I ask why?
To do a redirect when an error happens only to redirect again is a bit odd. I would throw the exception and handle that exception by your error view. MVC automatically adds the [HandleError] attribute as a global filter, so throw your exception (a custom type if necessary) and handle it in your error page as you see fit since you can access exception details there and it doesn't require a redirect.

Related

RedirectToAction after successful post to show a message

When people register on my ASP.NET MVC website I send an activation link via email so they can activate their account and login. I want to show a message when the registration is successful and the email is sent. To do that I redirect to another page.
I do not want to show this page when people go directly to this URL because it is not a normal page. I use TempData to check if they are coming from the registration page.
public ActionResult Register()
{
AccountRegisterView accountView = InitializeAccountRegisterViewWithIssue(false, "");
return View(accountView);
}
[HttpPost]
public ActionResult Register(AccountRegisterView accountView)
{
if (!ModelState.IsValid)
{
return View(accountView);
}
// Register user and send activation link via email...
TempData["success"] = true;
return RedirectToAction("RegisterEmail");
}
public ActionResult RegisterEmail()
{
if (TempData["success"] != null)
{
return View();
}
return RedirectToAction("Login");
}
I would like to know if this is considered best practice. Or should I do this differently?
I think this is a perfectly good use of TempData[]. You need a variable for a one time request to decide if you should show the page or not.
I suppose you could use a session variable, but you would need to remember to clear the session. You could use some type of registration key, but then you would need to track those as well. Long story short, nope, you are good.

RedirectToAction is not redirecting at all

i'm using RedirectToAction to redirect after a post to another controller and it's working. so i tried to Redirect to another action in the same controller and it's NOT working too.
public ActionResult finalize(int id)
{
Meeting meeting = db.Meetings.Find(id);
meeting.meetingStatus = "finalized";
db.SaveChanges();
return RedirectToAction("Index");
}
public ActionResult Create()
{
return View();
}
[HttpPost]
public ActionResult Create(Meeting meeting)
{
if (ModelState.IsValid)
{
db.Meetings.Add(meeting);
db.SaveChanges();
// return RedirectToAction("Invitation");
return (RedirectToAction("finalize", new { id = meeting.meetingID}));
}
return View(meeting);
}
you cant make 2 redirects in the same call , a redirect just return an http redirect code to another page , redirecting 2 times just wont work, don't treat action methods like normal methods they are different
for example when you request a page example.com/controller/action
action will be executed and for example it has a return value with RedirectToAction("NotFound")
what will happen in this scenario is
action body will be executed and the return value will send to the client an http header of 302 that says your new destination is /controller/NotFound
so RedirectToAction just return an http code to the client its not calling another method
Update
i was wrong i checked with fiddler2 , you can use redirect to action many times , what will happen the server will send multiple http redirect headers for each one
i jumped to conclusion this fast because i didnt think multiple redirect is the right way to do it, so after i tried it , i can say it works , just create a new project to see where is the problem exactly or use a tool like fiddler2

In Asp.Net MVC 2 is there a better way to return 401 status codes without getting an auth redirect

I have a portion of my site that has a lightweight xml/json REST API. Most of my site is behind forms auth but only some of my API actions require authentication.
I have a custom AuthorizeAttribute for my API that I use to check for certain permissions and when it fails it results in a 401. All is good, except since I'm using forms auth, Asp.net conveniently converts that into a 302 redirect to my login page.
I've seen some previous questions that seem a bit hackish to either return a 403 instead or to put some logic in the global.asax protected void Application_EndRequest()
that will essentially convert 302 to 401 where it meets whatever criteria.
Previous Question
Previous Question 2
What I'm doing now is sort of like one of the questions, but instead of checking the Application_EndRequest() for a 302 I make my authorize attribute return 666 which indicates to me that I need to set this to a 401.
Here is my code:
protected void Application_EndRequest()
{
if (Context.Response.StatusCode == MyAuthAttribute.AUTHORIZATION_FAILED_STATUS)
{
//check for 666 - status code of hidden 401
Context.Response.StatusCode = 401;
}
}
Even though this works, my question is there something in Asp.net MVC 2 that would prevent me from having to do this? Or, in general is there a better way? I would think this would come up a lot for anyone doing REST api's or just people that do ajax requests in their controllers. The last thing you want is to do a request and get the content of a login page instead of json.
How about decorating your controller/actions with a custom filter:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class RequiresAuthenticationAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
var user = filterContext.HttpContext.User;
if (!user.Identity.IsAuthenticated)
{
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.HttpContext.Response.End();
}
}
}
and in your controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[RequiresAuthentication]
public ActionResult AuthenticatedIndex()
{
return View();
}
}
Another way of doing this is to implement a custom ActionResult. In my case, I wanted one anyway, since I wanted a simple way of sending data with custom headers and response codes (for a REST API.) I found the idea of doing a DelegatingActionResult and simply added to it a call to Response.End(). Here's the result:
public class DelegatingActionResult : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
throw new ArgumentNullException("context");
Command(context);
// prevent ASP.Net from hijacking our headers
context.HttpContext.Response.End();
}
private readonly Action<ControllerContext> Command;
public DelegatingActionResult(Action<ControllerContext> command)
{
if (command == null)
throw new ArgumentNullException("command");
Command = command;
}
}
The simplest and cleanest solution I've found for this is to register a callback with the jQuery.ajaxSuccess() event and check for the "X-AspNetMvc-Version" response header.
Every jQuery Ajax request in my app is handled by Mvc so if the header is missing I know my request has been redirected to the login page, and I simply reload the page for a top-level redirect:
$(document).ajaxSuccess(function(event, XMLHttpRequest, ajaxOptions) {
// if request returns non MVC page reload because this means the user
// session has expired
var mvcHeaderName = "X-AspNetMvc-Version";
var mvcHeaderValue = XMLHttpRequest.getResponseHeader(mvcHeaderName);
if (!mvcHeaderValue) {
location.reload();
}
});
The page reload may cause some Javascript errors (depending on what you're doing with the Ajax response) but in most cases where debugging is off the user will never see these.
If you don't want to use the built-in header I'm sure you could easily add a custom one and follow the same pattern.
TurnOffTheRedirectionAtIIS
From MSDN, This article explains how to avoid the redirection of 401 responses : ).
Citing:
Using the IIS Manager, right-click the
WinLogin.aspx file, click Properties,
and then go to the Custom Errors tab
to Edit the various 401 errors and
assign a custom redirection.
Unfortunately, this redirection must
be a static fileā€”it will not process
an ASP.NET page. My solution is to
redirect to a static Redirect401.htm
file, with the full physical path,
which contains javascript, or a
meta-tag, to redirect to the real
ASP.NET logon form, named
WebLogin.aspx. Note that you lose the
original ReturnUrl in these
redirections, since the IIS error
redirection required a static html
file with nothing dynamic, so you will
have to handle this later.
Hope it helps you.
I'm still using the end request technique, so I thought I would make that the answer, but really
either of the options listed here are generally what I would say are the best answers so far.
protected void Application_EndRequest()
{
if (Context.Response.StatusCode == MyAuthAttribute.AUTHORIZATION_FAILED_STATUS)
{
//check for 666 - status code of hidden 401
Context.Response.StatusCode = 401;
}
}

asp.net mvc, wrong function being fired. - EDITED

I have master page that has this control that accepts Post verb. My LogOn page is also tied to the master page. When I enter wrong username/password, method of that control that accepts verb also gets fired along with the method to accept username password.
This is on HomeConroller:
[ActionName("ControlTemp"), AcceptVerbs(HttpVerbs.Post)]
public ActionResult ControlTemp(TempClass temp)
{
return PartialView("ControlTemp");
}
This is on AccountController:
[AcceptVerbs(HttpVerbs.Post)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
Justification = "Needs to take same parameter type as Controller.Redirect()")]
public ActionResult LogOn(string userName, string password, bool rememberMe, string returnUrl)
{
if (!ValidateLogOn(userName, password))
{
return View();
}
FormsAuth.SignIn(userName, rememberMe);
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
Now, why do you think things are being posted to ControlTemp too?
EDIT: This is how I am referring to ControlTemp control on masterpage.
<div id = "divControlTemp"> <% Html.RenderAction("ControlTemp", "Home"); %></div>
So after watching debugger, I saw it returns View() if username/password is invalid. It then hits the divControlTemp control, but instead of firing
public ActionResult ControlTemp(), it fires
[ActionName("ControlTemp"), AcceptVerbs(HttpVerbs.Post)]
public ActionResult ControlTemp(TempClass temp)
and that is all I can see from the debugger. So my question is why do you think it thinks it is a post? Is it because the reurn View() was called from a method that was accepting Post verb?
If your question is "can a single http requests fire two separate action methods", the answer is no. Unless of course there is a redirect from one action method to the other or one of the methods calls the other directly. If there are redirections, you can see them in the Firebug console.
But frankly, if this was my software, I'd simply fire up the debugger and see what's going wrong. It shouldn't be too difficult to spot the problem this way.

xVal error messages appearing twice

I'm trying to setup xVal with an ASP.NET MVC 2 Preview 1 project. I'm basically following the example at http://blog.codeville.net/2009/01/10/xval-a-validation-framework-for-aspnet-mvc/ to the letter (server-side only, so far).
I have annotated a BlogPost entity, and here is the Post action:
[HttpPost]
public ActionResult Index(BlogPost b)
{
try
{
_blogService.Insert(b);
}
catch (RulesException ex)
{
ex.AddModelStateErrors(ModelState, "");
}
return (View(b));
}
And here's the service method:
public void Insert(BlogPost post)
{
var errors = DataAnnotationsValidationRunner.GetErrors(post);
if(errors.Any())
{
throw new RulesException(errors);
}
_blogRepo.Insert(post);
}
(Note that the DataAnnotationsValidationRunner is verbatim from the example blog post). When I submit a totally invalid BlogPost form, I get this list of validation errors:
A value is required.
Please enter a title
Please enter a posted date
Please enter some content
Please enter a title
Please enter a posted date
Please enter some content
I don't even know what the first message is for, but as you can see, the other errors are appearing twice. What am I doing wrong? Or is this a problem with MVC V2?
Starting in ASP.Net MVC 2 Preview 1 we now get DataAnnotation validation support out of the box, so I guess your issue is that when the ModelBinder logic runs it is applying the DataAnnotation rules:
public ActionResult Index(BlogPost b) //Create BlogPost object and apply rules
and then with your XVal logic you are requesting the check again:
var errors = DataAnnotationsValidationRunner.GetErrors(post);
This is backed up by the fact they are repeated in the same order.
Your code would have worked fine in version 1 of MVC as public ActionResult Index(BlogPost b) would not have run the DataAnnotation rules. I have not read anywhere if it is possible to turn off the new DataAnnotation logic and just use XVal.
There is more information about this on Scott's post able preview 1
To find out what the first item is run debug and check what errors are on the ModelState, as this will tell you what property on the object the errors are related to.
[HttpPost]
public ActionResult Index(BlogPost b)
{
try
{
_blogService.Insert(b); //Add breakpoint here and check ModelState
}
catch (RulesException ex)
{
ex.AddModelStateErrors(ModelState, "");
}
return (View(b));
}

Resources