I have an item that I store in the HttpContext:
HttpContext.Current.Items["myItem"] = "123";
I can access this no problem from any of a page's methods. For example:
protected override void OnLoad(EventArgs e)
{
string l_myItemVal = HttpContext.Current.Items["myItem"] as string; // "123"
}
This works fine.
However, when calling one of the page's web methods via AJAX, this fails:
[System.Web.Services.WebMethod]
[System.Web.Script.Services.ScriptMethod]
public static string MyWebMethod()
{
string l_myItemVal = HttpContext.Current.Items["myItem"] as string; // NULL
}
Is the HttpContext of an asynchronous call different from the HttpContext for the page?
HttpContext.Items only holds items during a single request. Your AJAX request is a second request, and has it's own Items property.
Maybe you need to enable session state to make this work:
[System.Web.Services.WebMethod(true)]
Related
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; }
}
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.
I'd like to have a utility function that conditionally updates my request and response across several pages in my site.
Using a standard .CS class doesn't seem to give me access to these objects. How can I (generall speaking) create a utility function that checks for a cookie and update it across multiple pages?
You can always get at these things via
System.Web.HttpContext.Current.Request
System.Web.HttpContext.Current.Response
HttpContext Class and the Current Property
Encapsulates all HTTP-specific information about an individual HTTP request.
And to manage some cookie value throughout your site I would suggest either create a BasePage class that all of your Pages inherited from and do the checks there:
public class BasePage : System.Web.UI.Page
{
protected override void OnPreRender(EventArgs e)
{
UpdateCookie();
base.OnPreRender(e);
}
}
do the same in your MasterPage:
public class SiteMasterPage : MasterPage
{
protected override void OnPreRender(EventArgs e)
{
UpdateCookie();
base.OnPreRender(e);
}
}
public static void UpdateCookie()
{
HttpContext context = System.Web.HttpContext.Current;
HttpCookie cookie = context.Response.Cookies.Get("Update")
?? new HttpCookie("Update");
int value = 0;
int.TryParse(cookie.Value, out value);
value++;
cookie.Expires = DateTime.Now.AddDays(30);
cookie.Value = value.ToString();
context.Response.Cookies.Set(cookie);
}
use HttpContext.Current.Request and HttpContext.Current.Response
Use the fully qualified namespace:
System.Web.HttpContext.Current.Request
System.Web.HttpContext.Current.Response
-- or --
using System.Web.HttpContext.Current;
Then you should be able to access Request/Response throughout your class.
There are several ways to do this. Other have mentioned doing this with System.Web.HttpContext.Current, but I'd think (guessing from what I think your intent is) that doing this on a method that runs on load on your master pages is a better idea.
I've a static page method in web form application and I want to call method on private class level variable from it as shown below. I'm using jQuery to call the page method.
private readonly ICatalogBLL _catalogBLL = new CatalogBLL();
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
_catalogBLL.GetSomething();
}
}
[WebMethod]
public static UpdateSomething(int i)
{
//Want to do as below. But can't call it from a static method.
_catalogBLL.UpdateSomething();
}
UPDATE
If I call it as said by John Saunders, won't it use the same instance for requests from different users as it is within a static method?
You can't. The page method is static. Your _catalogBLL is an instance member.
However, since you create a new instance of CatalogBLL on every request, why not do so once more?
[WebMethod]
public static UpdateSomething(int i)
{
CatalogBLL catalogBLL = new CatalogBLL();
catalogBLL.UpdateSomething();
}
You can't call because pagemethods are static...
A static method is simply one that is disassociated from any instance of its containing class. The more common alternative is an instance method, which is a method whose result is dependent on the state of a particular instance of the class it belongs to.
Look at John saunder's answer..
I have the pattern User/{domain}/{username} set up via Routing. Everything works except for one thing. I can't figure out how to get the domain and username variables passed to my redirected page. Below is my GetHttpHandler method from my IRouteHandler implementation.
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
string basePath;
basePath = "~/UserPage.aspx";
string domain = requestContext.RouteData.GetRequiredString("domain");
string username = requestContext.RouteData.GetRequiredString("username");
string virtualPath =
string.Format(basePath + "?domain={0}&username={1}", domain, username);
return (Page)BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(Page));
}
I get the error from the last line of code:
UserPage.aspx?domain=SOMEDOMAIN&username=SOMEUSER is not a valid virtual path.
So how are you supposed to pass variables to the target page? what am I missing?
I think I solved this one myself.
Found this loop
foreach (KeyValuePair<string, object> token in requestContext.RouteData.Values)
{
requestContext.HttpContext.Items.Add(token.Key, token.Value);
}
from http://www.codethinked.com/post/2008/08/20/Exploring-SystemWebRouting.aspx
Its like the 4th code sample down.
UPDATE:
Not sure if this will work... requestContext.HttpContext seems to be "readonly". Back to the drawing board.
UPDATE 2:
Looks like this will work if you add in a reference to System.Web.Abstractions
Started mucking around with things and saw the IHttpHandler interface provides the RequestContext to the GetHttpHandler method.
So, I modified my base page class (I always put a layer between System.Web.UI.Page and my own pages, calling it BasePage or similar just for the purpose). So I added a public property on PVBasePage to receive a RequestContext object.
public RequestContext RequestContext { get; set; }
Then, my Routing class code is as follows:
IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
{
// create the page object as my own page...
var page = BuildManager.CreateInstanceFromVirtualPath(VirtualPath
, typeof(PVBasePage)) as PVBasePage;
// pass in the request context
page.RequestContext = requestContext;
// return this page in the form of a IHttpHandler
return page as IHttpHandler;
}
So instead of, as in the sample code, creating the instance directly as the IHttpHandler, I create it as my own page. Set the request context property, and then return the page to the caller AS a IHttpHandler.
Tested and it works. WOO HOO!
Then in the instance page, you can hit the RequestContext.GetValues collection to read out your passed in parameters.
HTH
#B.Tyndall
I just got this working with a solution similar to yours.
found at: http://msmvps.com/blogs/luisabreu/archive/2008/03/12/using-the-routing-mvc-api-with-classic-asp-net.aspx
foreach (var aux in requestContext.RouteData.Values)
{
HttpContext.Current.Items[aux.Key] = aux.Value;
}
So in effect you're no longer using the Request.QueryString but instead Context.Items collection
HttpContext.Current.Items["RouteName"]
or
Context.Items["RouteName"]
It appears as though other are also taking the route (no pun intended) of putting the parameters in the context Items collection.
http://bbits.co.uk/blog/archive/2008/05/19/using-asp.net-routing-independent-of-mvc---passing-parameters-to.aspx
I combined a couple of these approaches for pages that have a specific parameter, I created a UserNameRouteHandler for pages that accept that type of parameter. In my PageBase class I checked the context items for that parameter and then set a property so that my pages that inherit from PageBase can use it.
public class UserNameRouteHandler : IRouteHandler
{
#region Implementation of IRouteHandler
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
string pageName = requestContext.RouteData.GetRequiredString("PageName");
string employeeUserName = requestContext.RouteData.GetRequiredString("UserName");
if(!string.IsNullOrEmpty(employeeUserName))
{
requestContext.HttpContext.Items["UserName"] = employeeUserName;
}
pageName = pageName.ToLower() == "home" ? "default" : pageName;
string virtualPath = string.Format("~/{0}.aspx", pageName);
return (Page)BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(Page));
}
#endregion
}
And in my OnLoad of PageBase I set the property to pages that need it can have it...definitely looking for a more elegant solution though.
protected override void OnLoad(EventArgs e)
{
if (!IsPostBack)
{
if (Context.Items["UserName"] != null)
{
EmployeeUserName = Context.Items["UserName"].ToString();
}
}
base.OnLoad(e);
}