Redirecting users from edit page back to calling page - asp.net

I am working on a project management web application. The user has a variety of ways to display a list of tasks. When viewing a list page, they click on task and are redirected to the task edit page.
Since they are coming from a variety of ways, I am just curious as to the best way to redirect the user back to the calling page. I have some ideas, but would like to get other developers input.
Would you store the calling url in session? as a cookie? I like the concept of using an object handle the redirection.

I would store the referring URL using the ViewState. Storing this outside the scope of the page (i.e. in the Session state or cookie) may cause problems if more than one browser window is open.
The example below validates that the page was called internally (i.e. not requested directly) and bounces back to the referring page after the user submits their response.
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (Request.UrlReferrer == null)
{
//Handle the case where the page is requested directly
throw new Exception("This page has been called without a referring page");
}
if (!IsPostBack)
{
ReturnUrl = Request.UrlReferrer.PathAndQuery;
}
}
public string ReturnUrl
{
get { return ViewState["returnUrl"].ToString(); }
set { ViewState["returnUrl"] = value; }
}
protected void btn_Click(object sender, EventArgs e)
{
//Do what you need to do to save the page
//...
//Go back to calling page
Response.Redirect(ReturnUrl, true);
}
}

This message my be tagged asp.net but I think it is a platform independent issue that pains all new web developers as they seek a 'clean' way to do this.
I think the two options in achieving this are:
A param in the url
A url stored in the session
I don't like the url method, it is a bit messy, and you have to remember to include the param in every relevent URL.
I'd just use an object with static methods for this. The object would wrap around the session item you use to store redirect URLS.
The methods would probably be as follows (all public static):
setRedirectUrl(string URL)
doRedirect(string defaultURL)
setRedirectUrl would be called in any action that produces links / forms which need to redirect to a given url. So say you had a projects view action that generates a list of projects, each with tasks that can be performed on them (e.g. delete, edit) you would call RedirectClass.setRedirectUrl("/project/view-all") in the code for this action.
Then lets say the user clicks delete, they need to be redirected to the view page after a delete action, so in the delete action you would call RedirectClass.setRedirectUrl("/project/view-all"). This method would look to see if the redirect variable was set in the session. If so redirect to that URL. If not, redirect to the default url (the string passed to the setRedirectUrl method).

I agree with "rmbarnes.myopenid.com" regarding this issue as being platform independent.
I would store the calling page URL in the QueryString or in a hidden field (for example in ViewState for ASP.NET). If you will store it outside of the page scope (such as Session, global variable - Application State and so on) then it will not be just overkill as Tom said but it will bring you trouble.
What kind of trouble? Trouble if the user has more than one tab (window) of that browser open. The tabs (or windows) of the same browser will probably share the same session and the redirection will not be the one expected and all the user will feel is that it is a bug.
My 2 eurocents..

I personally would store the required redirection info in an object and handle globally. I would avoid using a QueryString param or the like since they could try bouncing themselves back to a page they are not supposed to (possible security issue?). You could then create a static method to handle the redirection object, which could read the information and act accordingly. This encapsulates your redirection process within one page.
Using an object also means you can later extend it if required (such as adding return messages and other info).
For example (this is a 2 minute rough guideline BTW!):
public partial class _Default : System.Web.UI.Page
{
void Redirect(string url, string messsage)
{
RedirectionParams paras = new RedirectionParams(url, messsage);
RedirectionHandler(paras); // pass to some global method (or this could BE the global method)
}
protected void Button1_Click(object sender, EventArgs e)
{
Redirect("mypage.aspx", "you have been redirected");
}
}
public class RedirectionParams
{
private string _url;
public string URL
{
get { return _url; }
set { _url = value; }
}
private string _message;
public string Message
{
get { return _message; }
set { _message = value; }
}
public RedirectionParams(string url, string message)
{
this.URL = url;
this.Message = message;
}
}

Related

Ajax PageMethod accessing page-level private static property

I have a page that uses Ajax Page Methods. When the page first loads, the user is prompted to select a year. This is the only time that a PostBack occurs. The year is stored in a private static page-level integer property named SelectedYear. There are several page methods that pass data from the client to the server, but the year is always stored on the server, so that it won't have to be to be passed in again. The problem is, in a few cases, within the server WebMethod, the SelectedYear property seems to be reverting to 0. I can test for 0 and throw the error back to the client, but it would help if I could explain why it happened. At this point, I don't know. Any ideas? I'm a bit new to this style of programming. Here's a (very simplified) example of the code. The user MUST have selected a year in order to ever have reached the save function.
Here is my C# server code:
public partial class Default : System.Web.UI.Page
{
private static int SelectedYear;
protected void YearSelected(object sender, EventArgs e)
{
if (sender.Equals(btnCurrentYear))
SelectedYear = 2013;
else
SelectedYear = 2014;
}
[WebMethod]
public static bool Save(string FirstName, string LastName)
{
try
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
//Right here, SelectedYear is sometimes 0.
SaveApplication(FirstName, LastName, SelectedYear);
else
throw new Exception("User is not logged in.");
}
catch (Exception ex)
{
throw;
}
}
}
Here is my JavaScript client code:
function Save(FirstName, LastName) {
PageMethods.Save(firstName, LastName, SaveSucceeded, SaveFailed);
}
function SaveSucceeded(result) {
//Notify user that save succeeded.
}
function SaveFailed(error) {
//Notify user that save failed.
}
Your problem is this:
private static int SelectedYear;
You'll want to remove the static. Static means it's global and will be shared for all users/requests... so when you set it to 2013 for one user, and another user hits that page who hasn't yet selected a year, it will be set to 0... for both of them. Yikes!
Trace through your postbacks to see what is happening to that variable during your AJAX methods.
You should consider storing the value in a session variable or maybe in a hidden field on the page.
More reading on a similar post: ASP.NET C# Static Variables are global?

Call the default asp.net HttpHandler from a custom handler

I'm adding ASP.NET routing to an older webforms app. I'm using a custom HttpHandler to process everything. In some situations I would like to map a particular path back to an aspx file, so I need to just pass control back to the default HttpHandler for asp.net.
The closest I've gotten is this
public void ProcessRequest(HttpContext context) {
// .. when we decide to pass it on
var handler = new System.Web.UI.Page();
handler.ProcessRequest(context);
MemoryStream steam = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
HtmlTextWriter htmlWriter = new HtmlTextWriter(writer);
handler.RenderControl(htmlWriter);
// write headers, etc. & send stream to Response
}
It doesn't do anything, there's nothing output to the stream. MS's documentation for System.Web.UI.Page (as an IHttpHandler) say something to the effect of "do not call the ProcessRequest method. It's for internal use."
From looking around it seems like you can do this with MVC, e.g. : MvcHttpHandler doesn't seem to implement IHttpHandler
There is also this thing System.Web.UI.PageHandlerFactory which appears that it would just produce a Page handler for an aspx file, but it's internal and I can't use it directly.
This page: http://msdn.microsoft.com/en-us/library/bb398986.aspx refers to the "default asp.net handler" but does not identify a class or give any indication how one might use it.
Any ideas on how I can do this? Is it possible?
Persistence pays off! This actually works, and since this information seems to be available pretty much nowhere I thought I'd answer my own question. Thanks to Robert for this post on instantiating things with internal constructors, this is the key.
http://www.rvenables.com/2009/08/instantiating-classes-with-internal-constructors/
public void ProcessRequest(HttpContext context) {
// the internal constructor doesn't do anything but prevent you from instantiating
// the factory, so we can skip it.
PageHandlerFactory factory =
(PageHandlerFactory)System.Runtime.Serialization.FormatterServices
.GetUninitializedObject(typeof(System.Web.UI.PageHandlerFactory));
string newTarget = "default.aspx";
string newQueryString = // whatever you want
string oldQueryString = context.Request.QueryString.ToString();
string queryString = newQueryString + oldQueryString!="" ?
"&" + newQueryString :
"";
// the 3rd parameter must be just the file name.
// the 4th parameter should be the physical path to the file, though it also
// works fine if you pass an empty string - perhaps that's only to override
// the usual presentation based on the path?
var handler = factory.GetHandler(context, "GET",newTarget,
context.Request.MapPath(context,newTarget));
// Update the context object as it should appear to your page/app, and
// assign your new handler.
context.RewritePath(newTarget , "", queryString);
context.Handler = handler;
// .. and done
handler.ProcessRequest(context);
}
... and like some small miracle, an aspx page processes & renders completely in-process without the need to redirect.
I expect this will only work in IIS7.
I'm you're using Routing in webforms you should be able to just add an ignore route for the specific .aspx files you want. This will then be handled by the default HttpHandler.
http://msdn.microsoft.com/en-us/library/dd505203.aspx
Another option is to invert the logic by handling the cases in which you do NOT want to return the default response and remap the others to your own IHttpHandler. Whenever myCondition is false, the response will be the "default". The switch is implemented as an IHttpModule:
public class SwitchModule: IHttpModule
{
public void Init(HttpApplication context)
{
context.PostAuthenticateRequest += app_PostAuthenticateRequest;
}
void app_PostAuthenticateRequest(object sender, EventArgs e)
{
// Check for whatever condition you like
if (true)
HttpContext.Current.RemapHandler(new CustomHandler());
}
public void Dispose()
}
internal class CustomHandler: IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.Write("hallo");
}
public bool IsReusable { get; }
}

HttpModule is breaking PostBack events

I'm trying to setup a simple HttpModule to handle authentication between my single sign on server. I've included code for the module below. The module is hitting my SSO and properly authenticating; however, on pages with forms the postback events are not occurring properly (e.g. isPostBack value is always false even though a POST occurred, button click events don't get hit, etc.).
public sealed class MyAuthenticationModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.AuthenticateRequest += OnAuthenticateRequest;
}
public void Dispose()
{
}
public static void OnAuthenticateRequest(object sender, EventArgs e)
{
FormsAuthentication.Initialize();
HttpContext context = HttpContext.Current;
HttpRequest request = context.Request;
HttpResponse response = context.Response;
// Validate the ticket coming back from the authentication server
if (!string.IsNullOrEmpty(request["ticket"]))
{
// I can include code for this if you want, but it appears to be
// working correct as whenever I get a ticket from my SSO it is processed
// correctly. I only get a ticket after coming from the SSO server and
// then it is removed from the URL so this only gets hit once.
MyAuthentication.ProcessTicketValidation();
}
if (!request.IsAuthenticated)
{
// redirect to the login server
response.Redirect("https://sso.example.com/login.aspx" + "?" + "service=" +
HttpUtility.UrlEncode(context.Request.Url.AbsoluteUri), false);
}
}
}
EDIT
I would also like to note that if I change the line:
if (!string.IsNullOrEmpty(request["ticket"]))
to:
if (!string.IsNullOrEmpty(request.QueryString["ticket"]))
the problem goes away.
Is it possible that your postbacks have a duplicate form data variable, "ticket"? That would seem to explain the behavior to me.
Aside from that, this line is suspicous:
FormsAuthentication.Initialize();
The FormsAuthentication class uses the "Provider" pattern, which means it's a singleton. You should not re-initialize. From the msdn documentation:
The Initialize method is called when the FormsAuthenticationModule
creates an instance of the FormsAuthentication class. This method is
not intended to be called from your code.

ASP.Net MVC 3 Strange Session Behaviour

I have an mvc 3 app for which I'm implementing authorization using my own login view which checks if the users name and password are allowed and then sets a variable in the session to say that the user is loggged in. This kind of works but for one particular view it is behaving in a strange undesirable way. The said view contains a form which I use to input some data and upload a file. For some reason which I can't figure out, after this form is posted a new session is started and therefore the variable which remembered that the user was logged in is reset to false and subsequently the login page is displayed again.
I'm lost as to why the application is starting a new session at this point? I have not instructed it to do this. Can anyone recommend solutions to stop this behaviour and get it to keep the old session?
Thanks.
UPDATE - Some Code:
Note the session seems to be terminated immediately after the response to the posted Create form
CMS controller which uses a custom Autorize attribute called "RDAutorize" on all actions:
[RDAuthorize]
public class PhotoCMSController : Controller
{
public ActionResult Create()
{
/* Code omitted: set up a newPhoto object with default state */
/* Display view containing form to upload photo and set title etc. */
return View("../Views/PhotoCMS/Create", newPhoto);
}
[HttpPost]
public ContentResult Upload(int pPhotoId)
{
/* Code ommited: receive and store image file which was posted
via an iframe on the Create view */
string thumbnail = "<img src='/path/to/thumb.jpg' />";
return Content(thumbnail);
}
[HttpPost]
public ActionResult Create(string pPhotoTitle, string pCaption etc...)
{
/*Code omitted: receive the rest of the photo data and save
it along with a reference to the image file which was uploaded
previously via the Upload action above.*/
/* Display view showing list of all photo records created */
return View("../Views/PhotoCMS/Index", qAllPhotos.ToList<Photo>());
/* **Note: after this view is returned the Session_End() method fires in
the Global.asax.cs file i.e. this seems to be where the session is
being lost** */
}
}/*End of CMS Controller*/
Custom Authorize action filter:
public class RDAuthorize : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Boolean authorized = Convert.ToBoolean(
HttpContext.Current.Session["UserIsAuthorized"]
);
if (!authorized) {
/* Not logged in so send user to the login page */
filterContext.HttpContext.Response.Redirect("/Login/Login");
}
}
public override void OnActionExecuted(ActionExecutedContext filterContext) {}
public override void OnResultExecuting(ResultExecutingContext filterContext) {}
public override void OnResultExecuted(ResultExecutedContext filterContext) {}
}/*End of Authorize Action Filter*/
Login controller:
public class LoginController : Controller
{
private PhotoDBContext _db = new PhotoDBContext();
public ActionResult Login()
{
string viewName = "";
Boolean authorized = Convert.ToBoolean(Session["UserIsAuthorized"]);
if (authorized)
{
viewName = "../Views/Index";
}
else
{
viewName = "../Views/Login/Login";
}
return View(viewName);
}
[HttpPost]
public ActionResult Login(string pUsername, string pPassword)
{
string viewName = "";
List<Photo> model = new List<Photo>();
var qUsers = from u in _db.Users
select u;
foreach (User user in qUsers.ToList<User>())
{
/* If authorized goto CMS pages */
if (pUsername == user.Username && pPassword == user.Password)
{
Session["UserIsAuthorized"] = true;
var qPhotos = from p in _db.Photos
where p.IsNew == false
select p;
model = qPhotos.ToList<Photo>();
viewName = "../Views/PhotoCMS/Index";
break;
}
}
return View(viewName, model);
}
}/* End of Login controller */
Turns out the whole ASP.Net application was restarting because as part of the photo upload I was storing the image file in a temporary folder and then deleting the directory after moving the file to a permanent location. Apparently its default behaviour for ASP.Net to restart if a directory within the web site is deleted. I found this post
which describes the problem and offers a solution whereby the following code is added to the Global.asax.cs file. Implementing this solution has fixed the problem. The fix is applied by calling FixAppDomainRestartWhenTouchingFiles() from the Application_Start() event:
protected void Application_Start()
{
FixAppDomainRestartWhenTouchingFiles();
}
private void FixAppDomainRestartWhenTouchingFiles()
{
if (GetCurrentTrustLevel() == AspNetHostingPermissionLevel.Unrestricted)
{
/*
From: http://www.aaronblake.co.uk/blog/2009/09/28/bug-fix-application-restarts-on-directory-delete-in-asp-net/
FIX disable AppDomain restart when deleting subdirectory
This code will turn off monitoring from the root website directory.
Monitoring of Bin, App_Themes and other folders will still be
operational, so updated DLLs will still auto deploy.
*/
PropertyInfo p = typeof(HttpRuntime).GetProperty(
"FileChangesMonitor", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
object o = p.GetValue(null, null);
FieldInfo f = o.GetType().GetField(
"_dirMonSubdirs", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase);
object monitor = f.GetValue(o);
MethodInfo m = monitor.GetType().GetMethod(
"StopMonitoring", BindingFlags.Instance | BindingFlags.NonPublic);
m.Invoke(monitor, new object[] { });
}
}
private AspNetHostingPermissionLevel GetCurrentTrustLevel()
{
foreach (AspNetHostingPermissionLevel trustLevel in
new AspNetHostingPermissionLevel[] {
AspNetHostingPermissionLevel.Unrestricted,
AspNetHostingPermissionLevel.High,
AspNetHostingPermissionLevel.Medium,
AspNetHostingPermissionLevel.Low,
AspNetHostingPermissionLevel.Minimal }
)
{
try
{
new AspNetHostingPermission(trustLevel).Demand();
}
catch (System.Security.SecurityException)
{
continue;
}
return trustLevel;
}
return AspNetHostingPermissionLevel.None;
}
Since sessions are associated with cookies, they are available for a specific domain.
It's a common mistake to ask for a session variable in the same application while the domain has changed (i.e. redirecting to a subdomain).
Does the controller action that you are posting the form contains any [Authorize] attribute. You need to post some code.
Verify a new session is really started every time. Check Trace output for the user's session id to ensure it realllly has changed.
Ensure the cookie getting sent over is actually getting set and sent over. (called ASPsessionIDSOMETHING ) and if that is being sent by the browser. Download the tool Fiddler to check the cookies easily (set cookie header coming from the server and the request cookies going back to the server from the browser. Make sure your browser is accepting the cookie and you dont say... have cookies turned off.
If your session id is changing at every request then your session isn't properly getting set the first time, set a break point on that code if you havent already.
You can log when the worker process resets - ensure that isn't the case. see http://www.microsoft.com/technet/prodtechnol/windowsserver2003/library/IIS/87892589-4eda-4003-b4ac-3879eac4bf48.mspx
I had the same problem. The problem only occured when a post request was send to the server and the session was not modified during that request. What I did as a workaround was, to write a custom filter which does nothing more than writing a key / value into the session on each request and added that filter to the GlobalFilter collection in the global.asax.
public class KeepSessionAlive : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
if(filterContext.HttpContext.Session != null)
{
filterContext.HttpContext.Session["HeartBeat"] = DateTime.Now.ToShortDateString();
}
}
public void OnActionExecuted(ActionExecutedContext filterContext) { }
}
And in the global.asax:
protected override void AddCustomGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new KeepSessionAlive());
}
This might be not the best solution but it helped me in my case.

Can I use HttpHandler to fake the existence of aspx pages?

I am building a web site with ASP.NET 3.5, and most of the site structure is static enough to create a folder structure and aspx pages. However, the site administrators want the ability to add new pages to different sections of the site through a web interface and using a WYSIWYG editor. I am using nested master pages to give the different sections of the site their own menus. What I would like to do is have a generic page under each section of the site that uses the appropriate master page and has a place holder for content that could be loaded from a database. I would also like these "fake" pages to have a url like any other aspx page, as if they had corresponding files on the server. So rather than have my url be:
http://mysite.com/subsection/gerenicconent.aspx?contentid=1234
it would be something like:
http://mysite.com/subsection/somethingmeaningful.aspx
The problem is that somethingmeaningful.aspx does not exist, because the administrator created it through the web UI, and the content is stored in the database. What I'm thinking is that I'll implement an HTTP handler that handles requests for aspx files. In that handler, I'll check to see if the URL that was requested is an actual file or one of my "fake pages". If it is a request for a fake page, I'll re-route the request to the generic content page for the appropriate section, change the query string to request the appropriate data from the database, and rewrite the URL so that it looks to the user as if the fake page really exists. The problem I'm having right now is that I can't figure out how to route the request to the default handler for aspx pages. I tried to instantiate a PageHandlerFactory, but the constuctor is protected internal. Is there any way for me to tell my HttpHandler to call the HttpHandler that would normal be used to process a request? My handler code currently looks like this:
using System.Web;
using System.Web.UI;
namespace HandlerTest
{
public class FakePageHandler : IHttpHandler
{
public bool IsReusable
{
get { return false; }
}
public void ProcessRequest(HttpContext context)
{
if(RequestIsForFakedPage(context))
{
// reroute the request to the generic page and rewrite the URL
PageHandlerFactory factory = new PageHandlerFactory(); // this won't compile because the constructor is protected internal
factory.GetHandler(context, context.Request.RequestType, GetGenericContentPath(context), GetPhysicalApplicationPath(context)).ProcessRequest(context);
}
else
{
// route the request to the default handler for aspx pages
PageHandlerFactory factory = new PageHandlerFactory();
factory.GetHandler(context, context.Request.RequestType, context.Request.Path, context.Request.PhysicalPath).ProcessRequest(context);
}
}
public string RequestForPageIsFaked(HttpContext context)
{
// TODO
}
public string GetGenericContentPath(HttpContext context)
{
// TODO
}
public string GetPhysicalApplicationPath(HttpContext context)
{
// TODO
}
}
}
I still have some work to do to determine if the request is for a real page, and I haven't rewritten any URLs yet, but is something like this possible? Is there another way to create a PageHandlerFactory other than calling its constructor? Is there any way I can route the request up to the "normal" HttpHandler for an aspx page? I'd basically be saying "process this ASPX request as you normally would."
If you are using 3.5, look into using asp.net routing.
http://msdn.microsoft.com/en-us/library/cc668201.aspx
You would be better off using an http module for this, as in this case you can use the RewritePath method to route the request for fake pages, and do nothing for actual pages which will allow them to be processed as normal.
There is a good explanation of this here which also covers the benefits of using IIS 7.0 if that is an option for you.
I've just pulled this off a similar system we've just written.
This method takes care of physical pages and "fake" pages. You'll be able to ascertan how this fits with your fake page schema, I'm sure.
public class AspxHttpHandler : IHttpHandlerFactory
{
#region ~ from IHttpHandlerFactory ~
public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
string url=context.Request.Url.AbsolutePath;
string[] portions = url.Split(new char[] { '/', '\\' });
// gives you the path, i presume this will help you identify the section and page
string serverSidePage=Path.Combine(context.Server.MapPath("~"),url);
if (File.Exists(serverSidePage))
{
// page is real
string virtualPath = context.Request.Url.AbsolutePath;
string inputFile = context.Server.MapPath(virtualPath);
try
{
// if it's real, send in the details to the ASPX compiler
return PageParser.GetCompiledPageInstance(virtualPath, inputFile, context);
}
catch (Exception ex)
{
throw new ApplicationException("Failed to render physical page", ex);
}
}
else
{
// page is fake
// need to identify a page that exists which you can use to compile against
// here, it is CMSTaregtPage - it can use a Master
string inputFile = context.Server.MapPath("~/CMSTargetPage.aspx");
string virtualPath = "~/CMSTargetPage.aspx";
// you can also add things that the page can access vai the Context.Items collection
context.Items.Add("DataItem","123");
return PageParser.GetCompiledPageInstance(virtualPath, inputFile, context);
}
public void ReleaseHandler(IHttpHandler handler)
{
}

Resources