I am trying to provide a progress monitoring mechanism for a longish-running request implemented by an AsyncController. A web page invokes (via JQuery $.post) the asynchronous StartTask action on the following controller...
[NoAsyncTimeout]
public class TaskController: AsyncController
{
[HttpPost]
public void StartTaskAsync()
{
AsyncManager.OutstandingOperations.Increment();
Session["progress"] = "in progress";
Task.Factory.StartNew(DoIt);
}
public ActionResult StartTaskCompleted()
{
return Json(new {redirectUrl = Url.Action("TaskComplete", "First")});
}
private void DoIt()
{
try
{
// Long-running stuff takes place here, including updating
// Session["progress"].
}
finally
{
AsyncManager.OutstandingOperations.Decrement();
}
}
}
The same web page sets up a 2-second timer (via window.setInterval) to call the following controller (via JQuery $.getJSON) that has read-only access to the session...
[SessionState(SessionStateBehavior.ReadOnly)]
public class ProgressController: Controller
{
[HttpGet]
public ActionResult Current()
{
var data = Session["progress"];
return Json(data, JsonRequestBehavior.AllowGet);
}
}
The function invoked after getJSON returns updates a DIV to show the current progress. Ultimately the progress will be updated in the session by the 'long-running stuff takes place here' code, but as it is the call to ProgressController.Current() does not find any data in the session and always returns null.
I had assumed that JQuery AJAX requests sent from the same browser to the same IIS server would end up with the same session, but it seems not. Do I need to explicitly set the ASP.NET session key on these $.post and $.getJSON calls? Or is it just not possible to share session state between Controllers (even if one has R/O access) in this way (I can fall back to a slightly hacky solution with Application state and GUIDs).
That's normal behavior. Your StartTask action blocks until it completes. It doesn't return any response until the DoIt method has finished executing. I suppose that's why you are calling it with AJAX $.post. The problem is that while this method runs and writes to the session and all other requests from the same session will be queued by the ASP.NET engine. You cannot write and read from the session at the same time. You will have to find another thread-safe storage for the progress data other than the session.
Related
I need an advice. I wrote an MVC app to access Google Gmail API (using Google OAuth 2 Authentication), as explained in Google's Tutorial: https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web-applications-aspnet-mvc
The idea is - Index action initializes
private static AuthorizationCodeWebApp.AuthResult result;
after it performs Google OAuth2 authentication. That result object has Credentials.Token property that contains issued AccessToken and RefreshToken. Next, Index action returns a vew (rendered HTML) that has a DIV where I want to load Gmail messages asynchronously. When the page loads, there is a Javascript function that fires on page load even in a browser. Simple. That Javascript function makes an AJAX call to Gmail() action in HomeController. It makes these calls with a 1 minute interval (js timer). Since result object is marked as static it should be available to pass Credentials to Gmail API Service method that is implemented in GmailManager helper class:
return PartialView(GmailManager.GetGmail(result.Credential));
The problem is sometimes the result object is null inside the Gmail() action and the NullReferenceException is thrown
I dont understand why this is happening if the result was initialized in Index action and it is static, so it should be alive by the time the call is made to Gmail() action. It should never be null. If it was constantly null I would understand and try to debug and fix it, but it is random and I cannot understand the logic.
If someone understands what is happening please advice.
Below is my HomeController code:
public class HomeController : Controller
{
private static AuthorizationCodeWebApp.AuthResult result;
public async Task<ActionResult> Index(CancellationToken cancellationToken)
{
result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).AuthorizeAsync(cancellationToken);
if (result.Credential == null)
return new RedirectResult(result.RedirectUri);
if (!string.IsNullOrEmpty(result.Credential.Token.RefreshToken))
SaveRefreshTocken(result.Credential.Token.RefreshToken);
return View();
}
public ActionResult Gmail()
{
result.Credential.Token.RefreshToken = WebConfigurationManager.AppSettings["RefreshToken"];
return PartialView(GmailManager.GetGmail(result.Credential));
}
private static void SaveRefreshTocken(string refreshToken)
{
Configuration config = WebConfigurationManager.OpenWebConfiguration("~");
config.AppSettings.Settings["RefreshToken"].Value = refreshToken;
config.Save();
}
}
I need to do some authentication for a web app with MVC3. The customer would like there to be a generic page to show if they do not have any of the role groups in windows AD that are allowed to use the app. I found a pretty simple way to do it, but just curious if it is a valid way or if there is something better out there.
Basically in the Session_Start in the global I am checking for User.IsInRole() and if that returns false then I do a Response.Redirect(). This question is: after it his the code in the IF statement and hits the Response.Redirect() code then it hits the session one more time before it goes to the AccessDenied page in the root of the app. Is this okay? Will it cause any issues If they are valid and does not enter the If to do the response.redirect?
//if (!User.IsInRole("test_user"))
//{
// Response.Redirect("~/AccessDenied.aspx", true);
//}
I would recommend you to write your Authorization filter for MVC3 and do this type of logic there:
public class RoleFilter: AuthorizeAttribute
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext filterContext)
{
if (!User.IsInRole("test_user"))
{
filterContext.HttpContext.Response.StatusCode = 302;
filterContext.Result = new RedirectResult("~/AcessDenied.aspx");
}
}
}
Also I wouldn't recommend you to use Response.Redirect because it aborts current thread.
I have a JSF Phase Listerner that checks to see if the user is logged in, and if not, redirects them to the login page. This is working fine for non-ajax requests. However, if the user is on a page, in my case, one that has a primefaces data table, and clicks on a button that invokes an ajax request -- but their session has timed out -- the code gets executed that issues the redirect (using ExternalContext#redirect), however the user is not navigated to the login page.
Any idea why this is not working?
Here is my phase listener:
private static final String IS_LOGGED_IN_INDICATOR = "loggedIn";
private static final String LOGIN_PAGE = "/login.jsp";
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
public void beforePhase(PhaseEvent event) {
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
HttpSession session = (HttpSession)ec.getSession(false);
if (session==null || session.getAttribute(IS_LOGGED_IN_INDICATOR) == null) {
try {
ec.redirect(LOGIN_PAGE);
}
catch(IOException e) {
// log exception...
}
}
}
public void afterPhase(PhaseEvent event) {
// no-op
}
}
It failed because the ajax context is trying to obtain the render kit from the view root, while there is no view root at all. It has not been restored at that point yet. This resulted in a NullPointerException in PartialViewContext#createPartialResponseWriter(). This exception is in turn not been thrown, but instead been put in an ajax exception queue which is supposed to be handled by a custom ExceptionHandler. You apparently don't have any one. This exception is visible if you create/use such one like the FullAjaxExceptionHandler (see also this blog for more detail).
To fix the particular problem, do the job in afterPhase() instead. The view root is then fully restored and the ajax context can obtain the render kit from it in order to write a specialized XML response which instructs the JSF ajax engine in JavaScript to change the window location. Without ajax, a render kit was not necessary as a redirect is basically just a matter of setting a response header.
Whether the particular NullPointerException is in turn a bug in Mojarra or not is a different question which can better be posted in flavor of an issue report at their own issue tracker.
this is because you have to send a special response in XML for Ajax request in order to do redirect (check this answer) , I have implemented this in a Filter like this..
// Check if it's an Ajax Request
if ("partial/ajax".equals(((HttpServletRequest) request).getHeader("Faces-Request"))) {
//redirect
response.setContentType("text/xml");
response.getWriter()
.append("<?xml version= \"1.0\" encoding=\"UTF-8\"?>")
.printf("<partial-response><redirect url=\"%s\"></redirect></partial-response>",url);
you should port this to your Phase Listener.
I had my NHibernate session management setup like follows:
protected MvcApplication()
{
BeginRequest += delegate
{
NHibernateSessionManager.Instance.OpenSession();
};
EndRequest += delegate
{
NHibernateSessionManager.Instance.CloseSession();
};
}
And for when I needed to save to the database, I made an ActionFilterAttribute that looked like this:
public class TransactionAttribute: ActionFilterAttribute
{
private ITransaction _currentTransaction;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
_currentTransaction = NHibernateSessionManager.Instance.CurrentSession.Transaction;
_currentTransaction.Begin();
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (_currentTransaction.IsActive)
{
if (filterContext.Exception == null)
_currentTransaction.Commit();
else
{
_currentTransaction.Rollback();
}
}
_currentTransaction.Dispose();
}
}
and then I could just add [Transaction] to my action method. This seemed to work in initial testing, but I then I tried using at HttpWebRequest to call an action method from another app multiple times and I had issues. Testing with Fiddler I setup a POST request and then fired them off in quick succession and it showed up the following:
THe red ones are various errors that I believe is to do with threading.
My NHibernateSessionManager uses the HTtpContext to store the session like this:
public ISession CurrentSession
{
get { return (ISession)HttpContext.Current.Items["current.session"]; }
set { HttpContext.Current.Items["current.session"] = value; }
}
So, to fixed it, I moved my Transaction code into my BeginRequest and EndRequest methods - and then I could fire off heaps in succession.
My question is - why did this fix it? I would have thought that I would have had something similar to this:
Begin Request - opens session
OnActionExecuting - starts transaction
action code
OnActionExecuted - commits transaction
End Request - closes session
and that this would be unique to each request, so it shouldn't interfere with one another, because there should be a different HttpContext for each request shouldn't there? Or are they shared or something??
Can someone enlighten me?
Quote from the release notes of ASP.NET MVC 3:
In previous versions of ASP.NET MVC,
action filters were created per
request except in a few cases. This
behavior was never a guaranteed
behavior but merely an implementation
detail and the contract for filters
was to consider them stateless. In
ASP.NET MVC 3, filters are cached more
aggressively. Therefore, any custom
action filters which improperly store
instance state might be broken.
This basically means that the _currentTransaction instance you have in your action filter might not be what you think it is. So be careful how/when is this property injected => it is not clear from the code you have shown.
Hi have the following code on my view (JQuery):
$.post('<%=Url.Content("~/Customer/StartLongRunningProcess")%>');
Wich invokes an asynchronous call (C#):
public void StartLongRunningProcess()
{
ProcessTask processTask = new ProcessTask();
processTask.BeginInvoke(new AsyncCallback(EndLongRunningProcess), processTask);
}
Finally, the result of the call:
public void EndLongRunningProcess(IAsyncResult result)
{
ProcessTask processTask = (ProcessTask)result.AsyncState;
string id = processTask.EndInvoke(result);
RedirectToAction("~/SubscriptionList/SubscribedIndex/" + id);
}
The redirect is ignored. Response.Redirect also fails, since the HTTP headers has been already sent. I've tried change the window.location from javascript, this works, but I'm unable to pass the parameter id by ViewData. Any idea to resolve this?
Are you sure the headers have already been sent? I'm not really up on asynchronous controllers, but I would doubt that it would start sending any headers right away. My first thought would be that a redirect response to an ajax call isn't handled by the browser. You will probably need to implement some logic that sends back a result with the URL and have your success delegate in jQuery look for that piece of data and then do the redirect through javascript (i.e. window.location).
HTH