ASP.NET: Compress ViewState - asp.net

What are the latest and greatest ways to compress the ASP.NET ViewState content?
What about the performance of this? Is it worth it to keep the pages quick and minimize data-traffic?
How can I make:
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE"
value="/wEPDwUKMTM4Mjc3NDEyOWQYAQUeX19Db250cm9sc1JlcXVpcmVQb3N0QmFja0tleV9fFgkFLGN0b
DAwJENvbnRlbnRQbGFjZUhvbGRlcl9NYWluQ29udGVudCRSYWRCdXQxBSxjdGwwMCRDb250ZW50UGxhY2VIb
2xkZXJfTWFpbkNvbnRlbnQkUmFkQnV0MQUsY3RsMDAkQ29udGVudFBsYWNlSG9sZGVyX01haW5Db250ZW50J
FJhZEJ1dDIFLGN0bDAwJENvbnRlbnRQbGFjZUhvbGRlcl9NYWluQ29udGVudCRSYWRCdXQyBSxjdGwwMCRDb
250ZW50UGxhY2VIb2xkZXJfTWFpbkNvbnRlbnQkUmFkQnV0MwUsY3RsMDAkQ29udGVudFBsYWNlSG9sZGVyX
01haW5Db250ZW50JFJhZEJ1dDQFLGN0bDAwJENvbnRlbnRQbGFjZUhvbGRlcl9NYWluQ29udGVudCRSYWRCd
XQ0BSxjdGwwMCRDb250ZW50UGxhY2VIb2xkZXJfTWFpbkNvbnRlbnQkUmFkQnV0NQUsY3RsMDAkQ29udGVud
FBsYWNlSG9sZGVyX01haW5Db250ZW50JFJhZEJ1dDXz21BS0eJ7991pzjjj4VXbs2fGBw==" />
Into sometning like this:
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE"
value="/wEPDwUKMTM4Mjc3N==" />

The simple answer might not be what you want to hear. Too often, controls on the page have viewstate by default when they really don't need it. It's a good idea to switch off viewstate until you know you're going to need it, and only switch it on for the (hopefully) few cases where you actually want to keep the view state.

Avoid using ViewState
Use compression on the IIS server.
You can wireup something that will compress the viewstate into and out of a page by doing something like:
public abstract class PageBase : System.Web.UI.Page
{
private ObjectStateFormatter _formatter = new ObjectStateFormatter();
private static byte[] Compress( byte[] data )
{
var compressedData = new MemoryStream();
var compressStream = new GZipStream(output, CompressionMode.Compress, true);
compressStream.Write(data, 0, data.Length);
compressStream.Close();
return compressedData.ToArray();
}
private static byte[] Uncompress( byte[] data )
{
var compressedData = new MemoryStream();
input.Write(compressedData, 0, compressedData.Length);
input.Position = 0;
var compressStream = new GZipStream(compressedData, CompressionMode.Decompress, true);
var uncompressedData = new MemoryStream();
var buffer = new byte[64];
var read = compressStream.Read(buffer, 0, buffer.Length);
while (read > 0)
{
uncompressedData.Write(buffer, 0, read);
read = compressStream.Read(buffer, 0, buffer.Length);
}
compressStream.Close();
return uncompressedData.ToArray();
}
protected override void SavePageStateToPersistenceMedium(object viewState)
{
var ms = new MemoryStream();
_formatter.Serialize(ms, viewState);
var viewStateBytes = ms.ToArray();
ClientScript.RegisterHiddenField("__COMPRESSED_VIEWSTATE"
, Convert.ToBase64String( Compress(viewStateArray)) );
}
protected override object LoadPageStateFromPersistenceMedium()
{
var compressedViewState = Request.Form["__COMPRESSED_VIEWSTATE"];
var bytes = Uncompress( Convert.FromBase64String( compressedViewState ) );
return _formatter.Deserialize( Convert.ToBase64String( bytes ) );
}
}

I realize this is an old thread, but we have been using Telerik's RadCompression HttpModule for a while now and it works incredibly well at compressing ViewState, AJAX and Web Service responses. You can also cheat and save ViewState in session - good for low traffic sites.
http://www.telerik.com/help/aspnet-ajax/radcompression.html

Again, after some research into this I summarized my findings in a blog-post about Compressing View State.
To save a compressed View State, this is what I did:
protected override void SavePageStateToPersistenceMedium(object state) {
SaveCompressedPageState(state);
}
private void SaveCompressedPageState(object state) {
byte[] viewStateBytes;
using(MemoryStream stream = new MemoryStream()) {
ObjectStateFormatter formatter = new ObjectStateFormatter();
formatter.Serialize(stream, state);
viewStateBytes = stream.ToArray();
}
byte[] compressed = CompressionHelper.Compress(viewStateBytes);
string compressedBase64 = Convert.ToBase64String(compressed);
ClientScript.RegisterHiddenField(ViewStateFieldName, compressedBase64);
}
And for the loading-part, this code made it work for me:
protected override object LoadPageStateFromPersistenceMedium() {
return LoadCompressedPageState();
}
private object LoadCompressedPageState() {
string viewState = Request.Form[ViewStateFieldName];
if(string.IsNullOrEmpty(viewState)) {
return string.Empty;
}
byte[] decompressed = CompressionHelper.Decompress(viewState);
string decompressedBase64 = Convert.ToBase64String(decompressed);
ObjectStateFormatter formatter = new ObjectStateFormatter();
return formatter.Deserialize(decompressedBase64);
}

Seb, ViewState is already compressed... that is what you are seeing... a compressed version of your controls. If you want less overhead, then don't use viewstate :)
Viewstate use should be kept to a minimum!

This is an XML-lized visualization of your posted viewstate:
<viewstate>
<Pair>
<Pair>
<String>1382774129</String>
</Pair>
</Pair>
</viewstate>
<controlstate>
<HybridDictionary>
<DictionaryEntry>
<String>__ControlsRequirePostBackKey__</String>
<ArrayList>
<String>ctl00$ContentPlaceHolder_MainContent$RadBut1</String>
<String>ctl00$ContentPlaceHolder_MainContent$RadBut1</String>
<String>ctl00$ContentPlaceHolder_MainContent$RadBut2</String>
<String>ctl00$ContentPlaceHolder_MainContent$RadBut2</String>
<String>ctl00$ContentPlaceHolder_MainContent$RadBut3</String>
<String>ctl00$ContentPlaceHolder_MainContent$RadBut4</String>
<String>ctl00$ContentPlaceHolder_MainContent$RadBut4</String>
<String>ctl00$ContentPlaceHolder_MainContent$RadBut5</String>
<String>ctl00$ContentPlaceHolder_MainContent$RadBut5</String>
</ArrayList>
</DictionaryEntry>
</HybridDictionary>
</controlstate>
Basically just a few radiobuttons which like to know of their existance. (browsers don't send an <input type="radio"> field with the postdata if it is not checked). This is pretty minimal already.
It can likely be compressed by hooking in the load/save methods or HTTP modules, but this may not be really practical nor really needed.
In case the viewstate is much bigger in your real app, avoid getting objects in the viewstate at all. This can be achieved by initializing the controls in the OnInit() or Page_Init() methods instead of the default Page_Load().
The rationale behind this can be found at
http://weblogs.asp.net/infinitiesloop/archive/2006/08/03/Truly-Understanding-Viewstate.aspx
and http://msdn.microsoft.com/en-us/library/ms972976.aspx
A quick summary:
ViewState is just the backing store for almost all control properties, including defaults.
After the defaults are set by OnInit(), the TrackViewState() method will is called.
Any subsequent changes (e.g. by Page_Load()) or an eventhandler, will be tracked and submitted to the client. This way those controls can restore their state at the next request.
Instead of relying at the framework to restore objects, restore objects in OnInit() when needed. (e.g. repopulating the options of a DropDownList from the database).
One exception:
If a control is dynamically added to the control tree, it plays a catch-up. Their OnInit() method may run at a later moment, causing those properties to end up in the viewstate after all. If the initialization of the control can't happen in OnInit(), setting EnableViewState="false" can be used as workaround.
Each time my viewstate grows unexpectedly, I'm using the "ViewState Decoder 2.2" app to find out what ended up in the viewstate. Often, it's not needed for the data to be there.
And a final word:
The viewstate is not used for repopulating forms!!
Those values are already submitted with the postdata.

Compressing view state fails in certain cases:
- If you are using update panel on page don’t use compression mode.
- If somehow you are changing the view state in result of ICallBack code don’t use compression mode, as this will don’t reflect the correct view state on post back.

The best way to minimize the view state is just to not use it. It will cause you to do some extra work programming (repopulating control values etc on post back, but it will save you on the amount of information you send to the browser). You can't tamper with it.
Here is a link to the view state on MSDN:
http://msdn.microsoft.com/en-us/library/ms972976.aspx
Here is a link describing some best practices:
http://mnairooz.blogspot.com/2007/01/aspnet-20-viewstate-and-good-practices.html
And One on disabling the ViewState:
http://www.codeproject.com/KB/aspnet/ASPNET_Best_Practices.aspx

Related

How can I place an ASP.NET menu control into cache?

I have a SharePoint2010 site that I have created an ASP.NET menu control for.
The ASP.NET menu's content is initially empty, and on Page_Load I load its content from standard HTML files on the server:
protected void Page_Init(object sender, EventArgs e)
{
string MenuPath = (string)ConfigurationSettings.AppSettings["RootMenuPath"].ToString();
Menu1.Items[0].ChildItems[0].Text = File.ReadAllText(MenuPath + "\\About.htm");
//etc...
}
I notice this is a horrible way to do things. It hits the disk every single time a user loads a page.
How can I either:
a) Cache the code and asp.net menu item so that it stays in memory?
b) Use another method to ensure it isn't loaded from the disk?
Thanks
You can wrap data load into property and use at least page Cache there:
readonly object cacheLock = new object();
string AboutHTM
{
get
{
if (Cache.Get("page.about") == null)
lock (cacheLock)
{
if (Cache.Get("page.about") == null)
Cache.Insert(File.ReadAllText(MenuPath + "\\About.htm"));
}
return Cache["page.about"].ToString();
}
}
You could indeed use the cache or some variable that is initialized only once in Application_Start and reused later but I am afraid that you are doing some premature optimization here. You probably shouldn't be doing it unless you have identified that this is a bottleneck for your application performance. Reading files from disk is a fast operation especially if they are small.
If possible, I would store the menu data in an XML file, and cache the XML file.
XmlDocument xDoc = new XmlDocument();
if (Cache.Get("MenuData") == null)
{
xDoc.Load(Server.MapPath("/MenuData.xml"));
Cache.Insert("SiteNav", xDoc, new CacheDependency(Server.MapPath("/MenuData.xml")));
}
else
{
xDoc = (XmlDocument)HttpContext.Current.Cache.Get("MenuData");
}

Asp.Net single control render for AJAX calls

I'm trying to implement something similar to this or this.
I've created a user control, a web service and a web method to return the rendered html of the control, executing the ajax calls via jQuery.
All works fine, but if I put something in the user control that uses a relative path (in my case an HyperLink with NavigateUrl="~/mypage.aspx") the resolution of relative path fails in my developing server.
I'm expecting:
http://localhost:999/MyApp/mypage.aspx
But I get:
http://localhost:999/mypage.aspx
Missing 'MyApp'...
I think the problem is on the creation of the Page used to load the control:
Page page = new Page();
Control control = page.LoadControl(userControlVirtualPath);
page.Controls.Add(control);
...
But I can't figure out why....
EDIT
Just for clarity
My user control is located at ~/ascx/mycontrol.ascx
and contains a really simple structure: by now just an hyperlink with NavigateUrl like "~/mypage.aspx".
And "mypage.aspx" really resides on the root.
Then I've made up a web service to return to ajax the partial rendered control:
[ScriptService]
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class wsAsynch : System.Web.Services.WebService
{
[WebMethod(EnableSession = true)]
public string GetControl(int parma1, int param2)
{
/* ...do some stuff with params... */
Page pageHolder = new Page();
UserControl viewControl = (UserControl)pageHolder.LoadControl("~/ascx/mycontrol.ascx");
Type viewControlType = viewControl.GetType();
/* ...set control properties with reflection... */
pageHolder.Controls.Add(viewControl);
StringWriter output = new StringWriter();
HttpContext.Current.Server.Execute(pageHolder, output, false);
return output.ToString();
}
}
The html is correctly rendered, but the relative path in the NavigateUrl of hyperlink is incorrectly resolved, because when I execute the project from developing server of VS2008, the root of my application is
http://localhost:999/MyApp/
and it's fine, but the NavigateUrl is resolved as
http://localhost:999/mypage.aspx
losing /MyApp/ .
Of Course if I put my ascx in a real page, instead of the pageHolder instance used in the ws, all works fine.
Another strange thing is that if I set the hl.NavigateUrl = Page.ResolveUrl("~/mypage.aspx") I get the correct url of the page:
http://localhost:999/MyApp/mypage.aspx
And by now I'll do that, but I would understand WHY it doesn't work in the normal way.
Any idea?
The problem is that the Page-class is not intented for instantiating just like that. If we fire up Reflector we'll quickly see that the Asp.Net internals sets an important property after instantiating a Page class an returning it as a IHttpHandler. You would have to set AppRelativeTemplateSourceDirectory. This is a property that exists on the Control class and internally it sets the TemplateControlVirtualDirectory property which is used by for instance HyperLink to resolve the correct url for "~" in a link.
Its important that you set this value before calling the LoadControl method, since the value of AppRelativeTemplateSourceDirectory is passed on to the controls created by your "master" control.
How to obtain the correct value to set on your property? Use the static AppDomainAppVirtualPath on the HttpRuntime class. Soo, to sum it up... this should work;
[WebMethod(EnableSession = true)]
public string GetControl(int parma1, int param2)
{
/* ...do some stuff with params... */
var pageHolder = new Page() { AppRelativeTemplateSourceDirectory = HttpRuntime.AppDomainAppVirtualPath };
var viewControl = (UserControl)pageHolder.LoadControl("~/ascx/mycontrol.ascx");
var viewControlType = viewControl.GetType();
/* ...set control properties with reflection... */
pageHolder.Controls.Add(viewControl);
var output = new StringWriter();
HttpContext.Current.Server.Execute(pageHolder, output, false);
return output.ToString();
}
The tildy pust the path in the root of the app, so its going to produce a the results you are seeing. You will want to use:
NavigateUrl="./whatever.aspx"
EDIT:
Here is a link that may also prove helpful...http://msdn.microsoft.com/en-us/library/ms178116.aspx
I find the /MyApp/ root causes all sorts of issues. It doesn't really answer your question 'why is doesn't work the normal way', but do you realize you can get rid of the /MyApp/ and host your website at http:/localhost/...?
Just set Virtual Path in the website properties to '/'.
This clears everything up, unless of course you are trying to host multiple apps on the development PC at the same time.
It might be that the new page object does not have "MyApp" as root, so it is resolved to the server root as default.
My question is rather why it works with Page.ResolveUrl(...).
Maybe ResolveUrl does some more investigation about the location of the usercontrol, and resolves based on that.
Weird, I recreated the example. The hyperlink renders as <a id="ctl00_hlRawr" href="Default.aspx"></a> for a given navigation url of ~/Default.aspx. My guess is that it has something to do with the RequestMethod. On a regular page it is "GET" but on a webservice call it is a "POST".
I was unable to recreate your results with hl.NavigateUrl = Page.ResolveUrl("~/mypage.aspx")
The control always rendered as <a id="ctl00_hlRawr" href="Default.aspx"></a> given a virtual path. (Page.ResolveUrl gives me "~/Default.aspx")
I would suggest doing something like this to avoid the trouble in the future.
protected void Page_Load(object sender, EventArgs e)
{
hlRawr.NavigateUrl = FullyQualifiedApplicationPath + "/Default.aspx";
}
public static string FullyQualifiedApplicationPath
{
get
{
//Return variable declaration
string appPath = null;
//Getting the current context of HTTP request
HttpContext context = HttpContext.Current;
//Checking the current context content
if (context != null)
{
//Formatting the fully qualified website url/name
appPath = string.Format("{0}://{1}{2}{3}",
context.Request.Url.Scheme,
context.Request.Url.Host,
(context.Request.Url.Port == 80 ? string.Empty : ":" + context.Request.Url.Port),
context.Request.ApplicationPath);
}
return appPath;
}
}
Regards,
It is hard to tell what you are trying to achieve without posting the line that actually sets the Url on of the HyperLink, but I think I understand your directory structure.
However, I have never run into a situation that couldn't be solved one way or another with the ResolveUrl() method. String parsing for a temporary path that won't be used in production is not recommended because it will add more complexity to your project.
This code will resolve in any object that inherits from page (including a usercontrol):
Page page = (Page)Context.Handler;
string Url = page.ResolveUrl("~/Anything.aspx");
Another thing you could try is something like this:
Me.Parent.ResolveUrl("~/Anything.aspx");
If these aren't working, you may want to check your IIS settings to make sure your site is configured as an application.

Caching http handler .ashx output

Im creating an image which has some text in it, for every customer, the image contains their name and I use the Graphics.DrawString function to create this on the fly, however I should not need to create this image more than once, simply because the name of the customer should hardly change, but I do not want to store it on disk.
Now I am creating the image in a handler i.e :
<asp:Image ID="Image1" runat="server" ImageUrl="~/imagehandler.ashx?contactid=1" />
What is the best way to cache the image that comes back? Should I cache the bitmap it creates? Or cache the stream that I pass back? And which cache object should I use, I gather there are many different ways? But output caching doesn't work on http handlers right? What is the recommended way? (I'm not bothered about caching on client side, I'm on about server side) Thanks!
The simplest solution I can think of would be to just cache the Bitmap object in the HttpContext.Cache after you've created it in the image handler.
private Bitmap GetContactImage(int contactId, HttpContext context)
{
string cacheKey = "ContactImage#" + contactId;
Bitmap bmp = context.Cache[cacheKey];
if (bmp == null)
{
// generate your bmp
context.Cache[cacheKey] = bmp;
}
return bmp;
}
David,
you can use output caching on handler. not declaratively but in code behind.
see if you can use following snippet.
TimeSpan refresh = new TimeSpan(0, 0, 15);
HttpContext.Current.Response.Cache.SetExpires(DateTime.Now.Add(refresh));
HttpContext.Current.Response.Cache.SetMaxAge(refresh);
HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.Server);
HttpContext.Current.Response.Cache.SetValidUntilExpires(true);
//try out with – a simple handler which returns the current time
HttpContext.Current.Response.ContentType = "text/plain";
HttpContext.Current.Response.Write("Hello World " + DateTime.Now.ToString("HH:mm:ss"));

Postback problem for my custom control load wizard

I have some problem that happens when controls are loaded in init and it still doesn't help me to get proper postback event fired on time.
I am trying to create a rich wizard control that will enable switching, links with description, completely customized steps, integration of substeps - by using dynamic control load that is avoids standard asp.net wizard way of loading.
Idea is to have on left part navigation, on right part content, or substeps that are run from right part and that go over whole area.
Download source project
Ok, I re-read the question, and here is what you have to do. You have to re-load these controls on each postback, give them always the same "Id". This can be done in Page_Init or in Page_Load event. And of course, you have to re-attach event handlers on each post back.
Many thanks.. well i found the answer - id was the problem, in load control method. I was doing this wizard.. well most of things work now.
If someone is interested to see how does this works.. there are some updates:
public void LoadSplitViewControl(string path)
{
SwitchNavigationView(NavigationView.SplitView);
LastNavigationView = NavigationView.SplitView;
LoadControl(SplitControlLoader, path, "LoadedControlSplit");
}
public void LoadSingleViewControl(string path)
{
SwitchNavigationView(NavigationView.SingleView);
LastNavigationView = NavigationView.SingleView;
LoadControl(SingleControlLoader, path, "LoadedControlSingle");
}
public void LoadSingleViewControlAsClear(string path)
{
SwitchNavigationView(NavigationView.SingleView);
LastNavigationView = NavigationView.SingleView;
LoadControlAsClear(SingleControlLoader, path, "LoadedControlSingle");
}
private void LoadControl(PlaceHolder holder, string path, string ID)
{
UserControl ctrl = (UserControl)Page.LoadControl(path);
ctrl.ID = ID;
LastControlPath = path;
holder.Controls.Clear();
holder.Controls.Add(ctrl);
}
//as i am using steps loaded controls using splitview and substeps controls using single view sometimes viewstate will not be valid so error will be thrown but u can resolve this by using LoadSingleViewControlAsClear that will load below method.
private void LoadControlAsClear(PlaceHolder holder, string path, string ID)
{
UserControl ctrl = (UserControl)Page.LoadControl(path);
ctrl.ID = ID;
LastControlPath = path;
ctrl.EnableViewState = false;
holder.Controls.Add(ctrl);
}
/another cool idea i am using for such an wizard is that i am not using viewstate but rather session object for saving values collected over steps. My session object key is generated by authenticated username and pageguid - so u can have many loaded pages and each of them will handle different session object./
public Guid PageGuid
{
get
{
if (PageGuidField.Value == "")
{
var _pageGuid = Guid.NewGuid();
PageGuidField.Value = _pageGuid.ToString();
return _pageGuid;
}
return new Guid(PageGuidField.Value);
}
}

Response.Redirect with POST instead of Get?

We have the requirement to take a form submission and save some data, then redirect the user to a page offsite, but in redirecting, we need to "submit" a form with POST, not GET.
I was hoping there was an easy way to accomplish this, but I'm starting to think there isn't. I think I must now create a simple other page, with just the form that I want, redirect to it, populate the form variables, then do a body.onload call to a script that merely calls document.forms[0].submit();
Can anyone tell me if there is an alternative? We might need to tweak this later in the project, and it might get sort of complicated, so if there was an easy we could do this all non-other page dependent that would be fantastic.
Anyway, thanks for any and all responses.
Doing this requires understanding how HTTP redirects work. When you use Response.Redirect(), you send a response (to the browser that made the request) with HTTP Status Code 302, which tells the browser where to go next. By definition, the browser will make that via a GET request, even if the original request was a POST.
Another option is to use HTTP Status Code 307, which specifies that the browser should make the redirect request in the same way as the original request, but to prompt the user with a security warning. To do that, you would write something like this:
public void PageLoad(object sender, EventArgs e)
{
// Process the post on your side
Response.Status = "307 Temporary Redirect";
Response.AddHeader("Location", "http://example.com/page/to/post.to");
}
Unfortunately, this won't always work. Different browsers implement this differently, since it is not a common status code.
Alas, unlike the Opera and FireFox developers, the IE developers have never read the spec, and even the latest, most secure IE7 will redirect the POST request from domain A to domain B without any warnings or confirmation dialogs! Safari also acts in an interesting manner, while it does not raise a confirmation dialog and performs the redirect, it throws away the POST data, effectively changing 307 redirect into the more common 302.
So, as far as I know, the only way to implement something like this would be to use Javascript. There are two options I can think of off the top of my head:
Create the form and have its action attribute point to the third-party server. Then, add a click event to the submit button that first executes an AJAX request to your server with the data, and then allows the form to be submitted to the third-party server.
Create the form to post to your server. When the form is submitted, show the user a page that has a form in it with all of the data you want to pass on, all in hidden inputs. Just show a message like "Redirecting...". Then, add a javascript event to the page that submits the form to the third-party server.
Of the two, I would choose the second, for two reasons. First, it is more reliable than the first because Javascript is not required for it to work; for those who don't have it enabled, you can always make the submit button for the hidden form visible, and instruct them to press it if it takes more than 5 seconds. Second, you can decide what data gets transmitted to the third-party server; if you use just process the form as it goes by, you will be passing along all of the post data, which is not always what you want. Same for the 307 solution, assuming it worked for all of your users.
You can use this aproach:
Response.Clear();
StringBuilder sb = new StringBuilder();
sb.Append("<html>");
sb.AppendFormat(#"<body onload='document.forms[""form""].submit()'>");
sb.AppendFormat("<form name='form' action='{0}' method='post'>",postbackUrl);
sb.AppendFormat("<input type='hidden' name='id' value='{0}'>", id);
// Other params go here
sb.Append("</form>");
sb.Append("</body>");
sb.Append("</html>");
Response.Write(sb.ToString());
Response.End();
As result right after client will get all html from server the event onload take place that triggers form submit and post all data to defined postbackUrl.
HttpWebRequest is used for this.
On postback, create a HttpWebRequest to your third party and post the form data, then once that is done, you can Response.Redirect wherever you want.
You get the added advantage that you don't have to name all of your server controls to make the 3rd parties form, you can do this translation when building the POST string.
string url = "3rd Party Url";
StringBuilder postData = new StringBuilder();
postData.Append("first_name=" + HttpUtility.UrlEncode(txtFirstName.Text) + "&");
postData.Append("last_name=" + HttpUtility.UrlEncode(txtLastName.Text));
//ETC for all Form Elements
// Now to Send Data.
StreamWriter writer = null;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = postData.ToString().Length;
try
{
writer = new StreamWriter(request.GetRequestStream());
writer.Write(postData.ToString());
}
finally
{
if (writer != null)
writer.Close();
}
Response.Redirect("NewPage");
However, if you need the user to see the response page from this form, your only option is to utilize Server.Transfer, and that may or may not work.
Something new in ASP.Net 3.5 is this "PostBackUrl" property of ASP buttons. You can set it to the address of the page you want to post directly to, and when that button is clicked, instead of posting back to the same page like normal, it instead posts to the page you've indicated. Handy. Be sure UseSubmitBehavior is also set to TRUE.
This should make life much easier.
You can simply use Response.RedirectWithData(...) method in your web application easily.
Imports System.Web
Imports System.Runtime.CompilerServices
Module WebExtensions
<Extension()> _
Public Sub RedirectWithData(ByRef aThis As HttpResponse, ByVal aDestination As String, _
ByVal aData As NameValueCollection)
aThis.Clear()
Dim sb As StringBuilder = New StringBuilder()
sb.Append("<html>")
sb.AppendFormat("<body onload='document.forms[""form""].submit()'>")
sb.AppendFormat("<form name='form' action='{0}' method='post'>", aDestination)
For Each key As String In aData
sb.AppendFormat("<input type='hidden' name='{0}' value='{1}' />", key, aData(key))
Next
sb.Append("</form>")
sb.Append("</body>")
sb.Append("</html>")
aThis.Write(sb.ToString())
aThis.End()
End Sub
End Module
Thought it might interesting to share that heroku does this with it's SSO to Add-on providers
An example of how it works can be seen in the source to the "kensa" tool:
https://github.com/heroku/kensa/blob/d4a56d50dcbebc2d26a4950081acda988937ee10/lib/heroku/kensa/post_proxy.rb
And can be seen in practice if you turn of javascript. Example page source:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Heroku Add-ons SSO</title>
</head>
<body>
<form method="POST" action="https://XXXXXXXX/sso/login">
<input type="hidden" name="email" value="XXXXXXXX" />
<input type="hidden" name="app" value="XXXXXXXXXX" />
<input type="hidden" name="id" value="XXXXXXXX" />
<input type="hidden" name="timestamp" value="1382728968" />
<input type="hidden" name="token" value="XXXXXXX" />
<input type="hidden" name="nav-data" value="XXXXXXXXX" />
</form>
<script type="text/javascript">
document.forms[0].submit();
</script>
</body>
</html>
PostbackUrl can be set on your asp button to post to a different page.
if you need to do it in codebehind, try Server.Transfer.
#Matt,
You can still use the HttpWebRequest, then direct the response you receive to the actual outputstream response, this would serve the response back to the user. The only issue is that any relative urls would be broken.
Still, that may work.
I suggest building an HttpWebRequest to programmatically execute your POST and then redirect after reading the Response if applicable.
Here's what I'd do :
Put the data in a standard form (with no runat="server" attribute) and set the action of the form to post to the target off-site page.
Before submitting I would submit the data to my server using an XmlHttpRequest and analyze the response. If the response means you should go ahead with the offsite POSTing then I (the JavaScript) would proceed with the post otherwise I would redirect to a page on my site
In PHP, you can send POST data with cURL. Is there something comparable for .NET?
Yes, HttpWebRequest, see my post below.
The GET (and HEAD) method should never be used to do anything that has side-effects. A side-effect might be updating the state of a web application, or it might be charging your credit card. If an action has side-effects another method (POST) should be used instead.
So, a user (or their browser) shouldn't be held accountable for something done by a GET. If some harmful or expensive side-effect occurred as the result of a GET, that would be the fault of the web application, not the user. According to the spec, a user agent must not automatically follow a redirect unless it is a response to a GET or HEAD request.
Of course, a lot of GET requests do have some side-effects, even if it's just appending to a log file. The important thing is that the application, not the user, should be held responsible for those effects.
The relevant sections of the HTTP spec are 9.1.1 and 9.1.2, and 10.3.
Typically, all you'll ever need is to carry some state between these two requests. There's actually a really funky way to do this which doesn't rely on JavaScript (think <noscript/>).
Set-Cookie: name=value; Max-Age=120; Path=/redirect.html
With that cookie there, you can in the following request to /redirect.html retrieve the name=value info, you can store any kind of information in this name/value pair string, up to say 4K of data (typical cookie limit). Of course you should avoid this and store status codes and flag bits instead.
Upon receiving this request you in return respond with a delete request for that status code.
Set-Cookie: name=value; Max-Age=0; Path=/redirect.html
My HTTP is a bit rusty I've been going trough RFC2109 and RFC2965 to figure how reliable this really is, preferably I would want the cookie to round trip exactly once but that doesn't seem to be possible, also, third-party cookies might be a problem for you if you are relocating to another domain. This is still possible but not as painless as when you're doing stuff within your own domain.
The problem here is concurrency, if a power user is using multiple tabs and manages to interleave a couple of requests belonging to the same session (this is very unlikely, but not impossible) this may lead to inconsistencies in your application.
It's the <noscript/> way of doing HTTP round trips without meaningless URLs and JavaScript
I provide this code as a prof of concept: If this code is run in a context that you are not familiar with I think you can work out what part is what.
The idea is that you call Relocate with some state when you redirect, and the URL which you relocated calls GetState to get the data (if any).
const string StateCookieName = "state";
static int StateCookieID;
protected void Relocate(string url, object state)
{
var key = "__" + StateCookieName + Interlocked
.Add(ref StateCookieID, 1).ToInvariantString();
var absoluteExpiration = DateTime.Now
.Add(new TimeSpan(120 * TimeSpan.TicksPerSecond));
Context.Cache.Insert(key, state, null, absoluteExpiration,
Cache.NoSlidingExpiration);
var path = Context.Response.ApplyAppPathModifier(url);
Context.Response.Cookies
.Add(new HttpCookie(StateCookieName, key)
{
Path = path,
Expires = absoluteExpiration
});
Context.Response.Redirect(path, false);
}
protected TData GetState<TData>()
where TData : class
{
var cookie = Context.Request.Cookies[StateCookieName];
if (cookie != null)
{
var key = cookie.Value;
if (key.IsNonEmpty())
{
var obj = Context.Cache.Remove(key);
Context.Response.Cookies
.Add(new HttpCookie(StateCookieName)
{
Path = cookie.Path,
Expires = new DateTime(1970, 1, 1)
});
return obj as TData;
}
}
return null;
}
Copy-pasteable code based on Pavlo Neyman's method
RedirectPost(string url, T bodyPayload) and GetPostData() are for those who just want to dump some strongly typed data in the source page and fetch it back in the target one.
The data must be serializeable by NewtonSoft Json.NET and you need to reference the library of course.
Just copy-paste into your page(s) or better yet base class for your pages and use it anywhere in you application.
My heart goes out to all of you who still have to use Web Forms in 2019 for whatever reason.
protected void RedirectPost(string url, IEnumerable<KeyValuePair<string,string>> fields)
{
Response.Clear();
const string template =
#"<html>
<body onload='document.forms[""form""].submit()'>
<form name='form' action='{0}' method='post'>
{1}
</form>
</body>
</html>";
var fieldsSection = string.Join(
Environment.NewLine,
fields.Select(x => $"<input type='hidden' name='{HttpUtility.UrlEncode(x.Key)}' value='{HttpUtility.UrlEncode(x.Value)}'>")
);
var html = string.Format(template, HttpUtility.UrlEncode(url), fieldsSection);
Response.Write(html);
Response.End();
}
private const string JsonDataFieldName = "_jsonData";
protected void RedirectPost<T>(string url, T bodyPayload)
{
var json = JsonConvert.SerializeObject(bodyPayload, Formatting.Indented);
//explicit type declaration to prevent recursion
IEnumerable<KeyValuePair<string, string>> postFields = new List<KeyValuePair<string, string>>()
{new KeyValuePair<string, string>(JsonDataFieldName, json)};
RedirectPost(url, postFields);
}
protected T GetPostData<T>() where T: class
{
var urlEncodedFieldData = Request.Params[JsonDataFieldName];
if (string.IsNullOrEmpty(urlEncodedFieldData))
{
return null;// default(T);
}
var fieldData = HttpUtility.UrlDecode(urlEncodedFieldData);
var result = JsonConvert.DeserializeObject<T>(fieldData);
return result;
}

Resources