Example of an asp.net application vulnerable to the Padding Oracle Attack? - asp.net

Does anyone could put me a very basic example of an asp.net web application which is vulnerable to the padding oracle attack.

Try the steps at the following two sites to test your site.
http://blog.dotsmart.net/2010/09/22/asp-net-padding-oracle-detector/
http://www.troyhunt.com/2010/09/fear-uncertainty-and-and-padding-oracle.html
Hope that helps

I know it's a very late answer, but maybe someone will be looking for this info.
Old versions of ASP.NET were vulnerable to the Padding Oracle Attack. It is still possible to enforce the "old" behavior through some tweaks. I described them in detail on my blog and the sample code is on GitHub.
We will be attacking the VIEWSTATE field. First, you need to disable ViewState signing. To do that, make sure you have the following setting in the web.config file:
<appSettings>
<add key="aspnet:UseLegacyMachineKeyEncryption" value="true" />
</appSettings>
And a sample .ashx file vulnerable to the Padding Oracle Attack:
<%# WebHandler Language="C#" Class="EncryptionHandler" %>
using System;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Security;
using System.Text;
public class EncryptionHandler : IHttpHandler
{
static readonly byte[] secret = Encoding.UTF8.GetBytes("Some text to break.");
public void ProcessRequest(HttpContext context)
{
var viewState = context.Request.Form["VIEWSTATE"];
if (viewState == null) {
viewState = MachineKey.Encode(secret, MachineKeyProtection.Encryption);
context.Response.ContentType = "text/html";
context.Response.Write("<!doctype html><html><form action=\"/EncryptionHandler.ashx\" method=\"POST\">" +
"<input type=\"hidden\" name=\"VIEWSTATE\" value=\"" + viewState + "\" />" +
"<input type=\"submit\" value=\"Test\" /></form></html>");
return;
}
var v = MachineKey.Decode(viewState, MachineKeyProtection.Encryption);
context.Response.ContentType = "text/plain";
if (v.SequenceEqual(secret)) {
context.Response.Write("I know the secret");
} else {
context.Response.Write("Something is wrong with my secret.");
}
}
public bool IsReusable {
get {
return false;
}
}
}
Now, based on the HTTP code (HTTP 500 when the cipher is invalid) you may try attacking the site (as described here).

Related

Where should I use anti forgery header verification in ASP.NET Web API?

I'm using web API in asp.net. Also i'm trying to secure my API against CSRF attacks.
I tried to use the following tutorial from Microsoft documentations.
In the Anti-CSRF and AJAX section, I successfully put the RequestVerificationToken header in the AJAX code. So the header is ok but i don't know where should i use the ValidateRequestHeader function mentioned in the tutorial.
void ValidateRequestHeader(HttpRequestMessage request)
{
string cookieToken = "";
string formToken = "";
IEnumerable<string> tokenHeaders;
if (request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
{
string[] tokens = tokenHeaders.First().Split(':');
if (tokens.Length == 2)
{
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
}
}
AntiForgery.Validate(cookieToken, formToken);
}
Please help me with the problem.
You should create a custom attribute which implements FilterAttribute, IAuthorizationFilter.
And add it on top of your Controller/Action you want to validate.
You can find many guide on this subject.

Has anyone used Sage Pay with asp.net Webpages

Has anyone used Sage Pay with asp.net Webpages??
I have downloaded the integration kit from Sage Pay but this is made in webforms and i am having trouble converting it the WebPages format.
Sage Pay are no help so i'm hoping someone out there has done this. Or can point me in the right direction.
I have managed to do this using the class from the SagePay template you can download.
Put these files in your bin folder:-
SagePay.IntegrationKit.DotNet.dll
SagePay.IntegrationKit.DotNet.pdb
Put these files in your App_Code folder:-
SagePayConfiguration.cs
SagePayAPIIntegration.cs
SagePayFormIntegration.cs
You also need to add some stuff to your web.config file
<SagePayConfiguration>
<!--Mandatory
Set to TEST for the Test Server and LIVE for the live environment-->
<add key="sagepay.api.env" value="TEST" />
<!--Transaction Settings -->
<add key="sagepay.api.protocolVersion" value="3.00" />
<add key="sagepay.kit.vendorName" value="your Name" />
<add key="sagepay.kit.fullUrl" value="your url" />
<add key="sagepay.kit.currency" value="GBP" />
<!--Optional setting. It's recommended to set the siteFqdn value to the Fully
Qualified Domain Name of your server.
This should start http:// or https:// and should be the name by which our servers can call back to yours
i.e. it MUST be resolvable externally, and have access granted to the Sage Pay servers
examples would be https://yoursite or http://212.111.32.22/
NOTE: Do not include any URI path.
If you leave this value blank the kit will use the current host name-->
<add key="sagepay.kit.siteFqdn.LIVE" value="http://your web address" />
<add key="sagepay.kit.siteFqdn.TEST" value="http://your web address" />
<!--Mandatory. Usually PAYMENT. This can be DEFERRED or AUTHENTICATE if your Sage Pay
account supports those payment types
NB Ideally all DEFERRED transaction should be released within 6 days (according to card scheme rules).
DEFERRED transactions can be ABORTed before a RELEASE if necessary-->
<add key="sagepay.kit.defaultTransactionType" value="PAYMENT" />
<!--0 = If AVS/CV2 enabled then check them. If rules apply, use rules (default).
1 = Force AVS/CV2 checks even if not enabled for the account. If rules apply, use rules.
2 = Force NO AVS/CV2 checks even if enabled on account.
3 = Force AVS/CV2 checks even if not enabled for the account but DON'T apply any rules.-->
<add key="sagepay.kit.applyAvsCv2" value="0" />
<!--0 = If 3D-Secure checks are possible and rules allow, perform the checks and apply the authorisation rules. (default)
1 = Force 3D-Secure checks for this transaction if possible and apply rules for authorisation.
2 = Do not perform 3D-Secure checks for this transaction and always authorise.
3 = Force 3D-Secure checks for this transaction if possible but ALWAYS obtain an auth code, irrespective of rule base.-->
<add key="sagepay.kit.apply3dSecure" value="0" />
<!--FORM Protocol Only Settings
Set this value to the Encryption password assigned to you by Sage Pay -->
<add key="sagepay.kit.form.encryptionPassword.TEST" value="Your password" />
<add key="sagepay.kit.form.encryptionPassword.LIVE" value="Your password" />
<!--The Sage Pay server URLs to which customers will be sent for payment for each environment-->
<add key="sagepay.api.formPaymentUrl.LIVE" value="https://live.sagepay.com/gateway/service/vspform-register.vsp" />
<add key="sagepay.api.formPaymentUrl.TEST" value="https://test.sagepay.com/gateway/service/vspform-register.vsp" />
</SagePayConfiguration>
To make this easier to manage i have but this web.config file in the checkout folder so it is easy to keep updated.
I also created the following class to encrypt and dencrypt the data:-
you can call it what ever you want but it needs to be saved in the App_Code folder.
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.IO;
public static class EncryptionHelper
{
private static byte[] keyAndIvBytes;
static EncryptionHelper()
{
// You'll need a more secure way of storing this, I this isn't
// a real key
keyAndIvBytes = UTF8Encoding.UTF8.GetBytes("123123123123123b");
}
public static string ByteArrayToHexString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-", "");
}
public static byte[] StringToByteArray(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
public static string DecodeAndDecrypt(string cipherText)
{
string DecodeAndDecrypt = AesDecrypt(StringToByteArray(cipherText));
return (DecodeAndDecrypt);
}
public static string EncryptAndEncode(string plaintext)
{
return ByteArrayToHexString(AesEncrypt(plaintext));
}
public static string AesDecrypt(Byte[] inputBytes)
{
Byte[] outputBytes = inputBytes;
string plaintext = string.Empty;
using (MemoryStream memoryStream = new MemoryStream(outputBytes))
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, GetCryptoAlgorithm().CreateDecryptor(keyAndIvBytes, keyAndIvBytes), CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(cryptoStream))
{
plaintext = srDecrypt.ReadToEnd();
}
}
}
return plaintext;
}
public static byte[] AesEncrypt(string inputText)
{
byte[] inputBytes = UTF8Encoding.UTF8.GetBytes(inputText);//AbHLlc5uLone0D1q
byte[] result = null;
using (MemoryStream memoryStream = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, GetCryptoAlgorithm().CreateEncryptor(keyAndIvBytes, keyAndIvBytes), CryptoStreamMode.Write))
{
cryptoStream.Write(inputBytes, 0, inputBytes.Length);
cryptoStream.FlushFinalBlock();
result = memoryStream.ToArray();
}
}
return result;
}
private static RijndaelManaged GetCryptoAlgorithm()
{
RijndaelManaged algorithm = new RijndaelManaged();
//set the mode, padding and block size
algorithm.Padding = PaddingMode.PKCS7;
algorithm.Mode = CipherMode.CBC;
algorithm.KeySize = 128;
algorithm.BlockSize = 128;
return algorithm;
}
}
I call the the class like so:-
string crypt = "blahblahblah";
string EncryptAndEncode = EncryptionHelper.EncryptAndEncode(crypt);
string DecodeAndDecrypt = EncryptionHelper.DecodeAndDecrypt(EncryptAndEncode);
When the transaction is complete i get the crypt with this code:-
IFormPaymentResult PaymentStatusResult = new DataObject();
if (Request.QueryString["crypt"] != null && !string.IsNullOrEmpty(Request.QueryString["crypt"]))
{
SagePayFormIntegration sagePayFormIntegration = new SagePayFormIntegration();
PaymentStatusResult = sagePayFormIntegration.ProcessResult(Request.QueryString["crypt"]);
}
you can then call the needed information from the calss like so
if (PaymentStatusResult.Status == ResponseStatus.NOTAUTHED)
{reason = "You payment was declined by the bank. This could be due to insufficient funds, or incorrect card details.";}
You can see all the fields in the Result.aspx of the SagePay template.

ASP.Net is too slow

Name: http://localhost:50692/bfea4cb4df42428dac17db20239d7d53/arterySignalR/poll?transport=longPolling&connectionToken=AQAAANCMnd8BFdERjHoAwE%2FCl%2BsBAAAAY8r9H53SS0mPVZi%2BJaxhmgAAAAACAAAAAAADZgAAwAAAABAAAACxV1Yze0hqYW74IbMt%2F3ccAAAAAASAAACgAAAAEAAAANgm3EjRWVa2PG4Y0yKe170oAAAA3HRUEcQdeyzIzSRY1%2B88s1AWq3zAs3dVYKoE8nrk0XpbjrLFXenhzRQAAACtcSj%2FmTPLei2BFkjrPAqbbTphxA%3D%3D&messageId=d-90D2E0DD-B%2C0%7CC%2C9%7CD%2C0&requestUrl=http%3A%2F%2Flocalhost%3A53648%2F&browserName=Chrome&userAgent=Mozilla%2F5.0+(Windows+NT+6.1%3B+WOW64)+AppleWebKit%2F537.36+(KHTML%2C+like+Gecko)+Chrome%2F43.0.2357.132+Safari%2F537.36&tid=8&_=1436780235916
Status: 200
Type: xhr
Initiator: browserLink:37
Size: 336 B
Time: 6.0 seconds
From the Network tab in Inspect Element in Chrome.
It just does this over and over again without getting anywhere. I have no idea what parts of my code to give you, and I'd rather not swarm you with the whole project. Tell me what you need to see and I'll give you it.
When I click "copy as HAR" I get this: http://pastebin.com/tqqNGYpW
It's interesting that the 'log' at the top shows version 1.2, and I just uninstalled log4net version 1.2 and installed log4net version 2.x instead to fix another problem. The problem was related to some 32-bit 64-bit problems between LinqToExcel (it uses log4net).
This is almost certainly LinqToExcel-related. It only hangs sometimes. It continues doing the above even after I've stopped the loading process.
Further info:
The page that hangs displays Excel files from LinqToExcel. The page that does not use LinqToExcel does not hang. It doesn't matter if the page has Excel files to display, or not.
The hang occurs when I call the Index() method.
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using steer.Models;
using LinqToExcel;
namespace steer.Controllers
{
public class FilesController : Controller
{
private UpFile.UpFileDBContext db = new UpFile.UpFileDBContext();
// GET: Upload
public ActionResult Index()
{
return View(db.UpFiles.ToList());
}
// GET: Upload/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
UpFile upFile = db.UpFiles.Find(id);
if (upFile == null)
{
return HttpNotFound();
}
var excel = new ExcelQueryFactory(AppDomain.CurrentDomain.BaseDirectory+"Uploaded\\"+upFile.Name);
var firstSheet = excel.GetWorksheetNames().First();
var excelRows = from c in excel.Worksheet(firstSheet)
select c;
ViewBag.excelRows = excelRows;
ViewBag.excelColumns = excel.GetColumnNames(firstSheet); ;
ViewBag.numberOfColumns = ViewBag.excelColumns.Count;
return View(upFile);
}
}
}
This is just a guess, but I'm assuming you have files you've uploaded in a database table and the page that is slow is the index which just shows the list of files.
If you have all the file details and the file contents in the same table then I suspect you're loading all those file contents for every single file every time you do an index.
Assuming this is the case you can either tell the linq to just select the info you want, or split out the data across 2 tables to make it so that you only end up with the data if you explicitly link it in.

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.

ASP.NET: Compress ViewState

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

Resources