I have the following js code:
$.get('/desk/AddTicketToCart', { clientId: clientId}, function(data) {
});
And controller's action:
public ActionResult AddTicketToCart(int clientId)
{
// do work
return new RedirectResult("/", true);
}
But, I get white page and url not changed in address bar. I also tryed the following:
return RedirectToAction("Index", "Home")
but, I also get white page.
How to make redirect properly?
Thanks.
Redirect in server won't help you here since you're making a ajax call. You need to set the new url in js in the callback.
$.get('/desk/AddTicketToCart', { clientId: clientId}, function(data) {
window.location = // the url
});
Related
We are using Ajax call across the application- trying to find out a global solution to redirect to login page if session is already expired while trying to execute any Ajax request. I have coded following solution taking help from this post - Handling session timeout in ajax calls
NOT SURE WHY IN MY CARE EVENT "HandleUnauthorizedRequest" DOES NOT GET FIRED.
Custom Attribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CheckSessionExpireAttribute :AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
var url = new UrlHelper(filterContext.RequestContext);
var loginUrl = url.Content("/Default.aspx");
filterContext.HttpContext.Session.RemoveAll();
filterContext.HttpContext.Response.StatusCode = 403;
filterContext.HttpContext.Response.Redirect(loginUrl, false);
filterContext.Result = new EmptyResult();
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
}
Using Above custom attribute as follow in controller action:
[NoCache]
[CheckSessionExpire]
public ActionResult GetSomething()
{
}
AJAX Call(JS part):
function GetSomething()
{
$.ajax({
cache: false,
type: "GET",
async: true,
url: "/Customer/GetSomething",
success: function (data) {
},
error: function (xhr, ajaxOptions, thrownError) {
}
}
Web Config Authentication settings:
<authentication mode="Forms">
<forms loginUrl="default.aspx" protection="All" timeout="3000" slidingExpiration="true" />
</authentication>
I am try to check it by deleting browser cooking before making ajax call but event "CheckSessionExpireAttribute " does not get fired- any idea please.
Thanks,
#Paul
If I got the question right (and even if I didn't, thanks anyway, helped me solve my own situation), what you wanted to avoid was having your login page to load inside an element which was supposed to display a different View via Ajax. That or get an exception/error status code during a Ajax form post.
So, in short, the annotation class will need to override 2 methods, not just HandleUnauthorizedRequest, and it will redirect to a JsonResult Action that will generate the parameters for your Ajax function to know what to do.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class SessionTimeoutAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
IPrincipal user = filterContext.HttpContext.User;
base.OnAuthorization(filterContext);
if (!user.Identity.IsAuthenticated) {
HandleUnauthorizedRequest(filterContext);
}
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new RedirectToRouteResult(new
RouteValueDictionary(new { controller = "AccountController", action = "Timeout" }));
}
}
}
Then set this annotation in your authentication Action, so every time it gets called, it will know where the request came from, and what kind of return it should give.
[AllowAnonymous]
[SessionTimeout]
public ActionResult Login() { }
Then your redirected Json Action:
[AllowAnonymous]
public JsonResult Timeout()
{
// For you to display an error message when the login page is loaded, in case you want it
TempData["hasError"] = true;
TempData["errorMessage"] = "Your session expired, please log-in again.";
return Json(new
{
#timeout = true,
url = Url.Content("~/AccountController/Login")
}, JsonRequestBehavior.AllowGet);
}
Then in your client function (I took the privilege of writing it as $.get() instead of $.ajax():
$(document).ready(function () {
$("[data-ajax-render-html]").each(function () {
var partial = $(this).attr("data-ajax-render-html");
var obj = $(this);
$.get(partial, function (data) {
if (data.timeout) {
window.location.href = data.url;
} else {
obj.replaceWith(data);
}
}).fail(function () {
obj.replaceWith("Error: It wasn't possible to load the element");
});
});
});
This function replaces the html tag with this data-ajax-render-html attribute, which contains the View address you want to load, but you can set it to be loaded inside the tag by changing replaceWith for the html() property.
I think that is only a client-side problem.
In web server you can just use the classic Authorize attribute over actions or controllers.
That will validate that the request is authenticated (if there's a valid authentication cookie or authorization header) and sets HTTP 401 if not authenticated.
Note: a session will automatically be recreated if you don't send authorization info in the request, but the request will not be authorized
Solution
Then the javascript client you must handle the redirect (browsers do it automatically but with ajax you need to do it manually)
$.ajax({
type: "GET",
url: "/Customer/GetSomething",
statusCode: {
401: function() {
// do redirect to your login page
window.location.href = '/default.aspx'
}
}
});
I checked and tested the code, looks like clearly.. the problem is that the ajax call is wrong..
I fix Ajax code, try this..
function GetSomething() {
$.ajax({
cache: false,
type: "GET",
async: true,
url: "/Customer/GetSomething",
success: function (data) {
},
error: function (xhr, ajaxOptions, thrownError) {
}
});
}
On HttpContext.Request.IsAjaxRequest()
Please see this related article on why an Ajax request might not be recognized as such.
XMLHttpRequest() not recognized as a IsAjaxRequest?
It looks like there is a dependency on a certain header value (X-Requested-With) being in the request in order for that function to return true.
You might want to capture and review your traffic and headers to the server to see if indeed the browser is properly sending this value.
But, are you even sure it's hitting that line of code? You might also want to debug with a break point and see what values are set.
On Session vs Authentication
Authorization and Session timeout are not always exactly the same. One could actually grant authorization for a period longer than the session, and if the session is missing, rebuild it, as long as they are already authorized. If you find there is something on the session that you'd be loosing that can't be rebuilt, then perhaps you should move it somewhere else, or additionally persist it somewhere else.
Form Authentication cookies default to timeout after 30 minutes. Session timeout default is 20 minutes.
Session timeout in ASP.NET
HandleUnauthorizedRequest not overriding
Sorry to say that: The solution you need is impossible. The reason is:
To redirect user to login page, we have 2 methods: redirect at server, redirect at client
In your case, you're using Ajax so we have only 1 method: redirect at client (reason is, basically, Ajax means send/retrieve data to/from server. So it's impossible to redirect at server)
Next, to redirect at client. Ajax need to information from server which say that "redirect user to login page" while global check session method must be return Redirect("url here").
clearly, global check session method can not return 2 type (return Redirect(), return Json,Xml,Object,or string)
After all, I suggest that:
Solution 1: Don't use ajax
Solution 2: You can use ajax, but check session timeout method at server which is not globally. Mean that you must multiple implement (number of ajax call = number of implement)
// AngularJs controller method
$scope.onLogOutClick = function () // called when i clicked on logout button
{
//i want here to redirect to login.cshtml file.
$http({ method: 'GET', url: "/Home/Login"});
// this line of code is not working.
}
//controller method return logic.cshtml view.
[HttpGet]
public ActionResult Login(string theme, string feature)
{
return View("login"); // i want to render this view in angularJs
}
When i clicked on logout button than onLogOutClick() method is called. i want to redirect back login.cshtml page when i clicked on logout button.
Using AndularJs we can develop Single page application(SPA). In SPA has only one CSHTML file(Main Page) and we change views(render html) using ng-view and $roughtConfig directive. So there is no case to arise to switch cshtml page.
1) Add Blank Logout.cshtml page.
2) Add routing for Logout page
.when('/Logout', { templateUrl: '/Login/LogoutUser', controller: 'LogoutController' })
3) Add following controller to redirect user to login page
angular.module('Application').controller("LogoutController", ['$scope', '$http', '$timeout', function LogoutController($scope, $http, $timeout) {
var requestLogout = $http({
method: "POST",
url: "/api/LoginUser/LogoutUser"
});
requestLogout.success((function myNewfunction() {
window.location.href = "/Login?session=false";
}));
}]);
Lets say we have the followings:
Login Action:
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
Register Action:
public ActionResult Register()
{
return View();
}
Login View:
<script>
$(function () {
var url = '#Url.Action("Login", "Account")';
$.ajax({
url: url,
type: 'POST',
data: { UserName: "user1", Password: password1 },
success: function () { },
error: function () { }
});
});
</script>
#{
ViewBag.Title = "Login";
}
Register View:
#{
ViewBag.Title = "Register"; }
<h2>Register</h2>
To process the ajax post, this is the login post action
[HttpPost]
public ActionResult Login(string UserName, string Password)
{
if (WebSecurity.Login(UserName, Password) == true)
{
return RedirectToAction("Register");
else
{
this.ControllerContext.HttpContext.Response.StatusCode = 404;
return new EmptyResult();
}
}
Now the issue and the question
Okay, assuming that when the application started it goes to Account/Login, and assuming there exists account for user1, I would expect the Register View will be rendered in the browser (I am using IE 8).
However, what I see, only the Login View is rendered eventhough when I trace the code in debug, the Register View is getting processed. I don't understand why I don't see the Register View?
Is my understanding wrong or is there a bug in asp.net mvc?
What is happening is the ajax call itself is receiving the redirect and sending another request to the new Register URL. Because this is not a request from your browser, but instead the ajax, the response from the Register page is in the success response of the ajax call which your doing nothing with.
See: Returning redirect as response to XHR request
Solution
You can either use a standard form.submit instead of ajax post, or you can return a json response that includes the url to redirect to return Json( new { redirectTo = Url.Action("Register")}); and code your javascript success handler to use that redirectTo property to set window.location to cause the browser to navigate to the new URL.
I want to redirect to a view, but the view is loading in the partial view which the [Authorize] attribute is on.
is there something else than response.redirect ?
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
string authUrl = this.redirectUrl; //passed from attribute NotifyUrl Property
//if null, get it from config
if (String.IsNullOrEmpty(authUrl))
{
authUrl = System.Web.Configuration.WebConfigurationManager.AppSettings["RolesAuthRedirectUrl"];
}
if (!String.IsNullOrEmpty(authUrl))
{
filterContext.HttpContext.Response.Redirect(authUrl);
}
}
base.HandleUnauthorizedRequest(filterContext);
}
[AuthorizeUsers(Roles = "Administrator", NotifyUrl = "/CustomErrors/Error404")]
public ActionResult addToCart(int ProductID, string Quantity)
{
...
}
It appears that you are invoking the controller action that is decorated with this Authorize attribute using an AJAX call. And you want to fully reload the page if the user is not authorized.
Phil Haack wrote an very nice blog post illustrating how you could achieve that: http://haacked.com/archive/2011/10/04/prevent-forms-authentication-login-page-redirect-when-you-donrsquot-want.aspx/
The idea is simple. Since you made an AJAX request to this controller action, the only way to move out from the partial is to make the redirect using javascript. So for example your AJAX code could detect if the contoller action returned 401 HTTP status code and then use the window.location.href to redirect away to the login page:
$.ajax({
url: '/protected',
type: 'POST',
statusCode: {
200: function (data) {
// Success
},
401: function (data) {
// Handle the 401 error here.
window.location.href = '/login'
}
}
});
The only difficulty here is how to make the standard Authorize attribute return 401 instead of attempting to redirect to the login page. And Phil Haack illustrated the AspNetHaack NuGet which achieves this.
I have a view (Index.cshtml) with a grid (Infragistics JQuery grid) with an imagelink. If a user clicks on this link the following jquery function will be called:
function ConfirmSettingEnddateRemarkToYesterday(remarkID) {
//Some code...
//Call to action.
$.post("Home/SetEnddateRemarkToYesterday", { remarkID: remarkID }, function (result) {
//alert('Succes: ' + remarkID);
//window.location.reload();
//$('#remarksgrid').html(result);
});
}
Commented out you can see an alert for myself and 2 attempts to refresh the view. The location.reload() works, but is basically too much work for the browser. The .html(result) posts the entire index.cshtml + Layout.cshtml double in the remarksgrid div. So that is not correct.
This is the action it calls (SetEnddateRemarkToYesterday):
public ActionResult SetEnddateRemarkToYesterday(int remarkID) {
//Some logic to persist the change to DB.
return RedirectToAction("Index");
}
This is the action it redirects to:
[HttpGet]
public ActionResult Index() {
//Some code to retrieve updated remarks.
//Remarks is pseudo for List<Of Remark>
return View(Remarks);
}
If I don't do window.location.reload after the succesfull AJAX post the view will never reload. I'm new to MVC, but i'm sure there's a better way to do this. I'm not understanding something fundamental here. Perhaps a nudge in the right direction? Thank you in advance.
As you requesting AJAX call, you should redirect using its response
Modify your controller to return JSONResult with landing url:
public ActionResult SetEnddateRemarkToYesterday(int remarkID) {
//Some logic to persist the change to DB.
var redirectUrl = new UrlHelper(Request.RequestContext).Action("Index", "Controller");
return Json(new { Url = redirectUrl });
}
JS Call:
$.post("Home/SetEnddateRemarkToYesterday", { remarkID: remarkID }, function (result) {
window.location.href = result.Url
});
After Ajax post you need to call to specific Url..
like this..
window.location.href = Url
When using jQuery.post the new page is returned via the .done method
jQuery
jQuery.post("Controller/Action", { d1: "test", d2: "test" })
.done(function (data) {
jQuery('#reload').html(data);
});
HTML
<body id="reload">
For me this works. First, I created id="reload" in my form and then using the solution provided by Colin and using Ajax sent data to controller and refreshed my form.
That looks my controller:
[Authorize(Roles = "User")]
[HttpGet]
public IActionResult Action()
{
var model = _service.Get()...;
return View(model);
}
[Authorize(Roles = "User")]
[HttpPost]
public IActionResult Action(object someData)
{
var model = _service.Get()...;
return View(model);
}
View:
<form id="reload" asp-action="Action" asp-controller="Controller" method="post">
.
.
.
</form>
Javascript function and inside this function I added this block:
$.ajax({
url: "/Controller/Action",
type: 'POST',
data: {
__RequestVerificationToken: token, // if you are using identity User
someData: someData
},
success: function (data) {
console.log("Success")
console.log(data);
var parser = new DOMParser();
var htmlDoc = parser.parseFromString(data, 'text/html'); // parse result (type string format HTML)
console.log(htmlDoc);
var form = htmlDoc.getElementById('reload'); // get my form to refresh
console.log(form);
jQuery('#reload').html(form); // refresh form
},
error: function (error) {
console.log("error is " + error);
}
});