Running with Code Like with scissors, only more dangerous

3Oct/131

I’m going to be on Channel9

Posted by Rob Paveza

I went and hung out with Larry Larson and Andrew Richards from the Channel9 show Defrag Tools today to record an episode! It was way cool, even though I got a little lost heading to the building which was literally right across the street from my own. (In my defense, I was up really early and I thought the building was further down the street).

All said and done, I got a cool Channel9 dude and I got to take a picture in the studio!

I'm @ Channel9 on 10/14/2013

I'm @ Channel9 on 10/14/2013

Be sure to check out Defrag Tools on 10/14 to see it, because I'll be talking about Just My Code in Visual Studio. If you haven't seen the blog I linked to last time, it's definitely worthwhile, but at the very least, you ought to watch the video, because it's AWESOME!

Update: It's live! Check it out!

19Sep/130

Just My Code for JavaScript

Posted by Rob Paveza

Super-excited that we FINALLY get to talk about a new feature that went out in Visual Studio 2013 RC last week. Check out the details in Andrew Hall's blog post on MSDN.

13May/080

Creating a Sniffer for ASP.NET

Posted by Rob

This afternoon, some co-workers were a bit perplexed about why an 'ñ' character in a Spanish name wasn't displaying on a Facebook application we were working on, but rather was coming across as a '?'.  Because we were pretty sure that Facebook was UTF-8-encoded, we thought the problem was on our side.  Unfortunately, we were on a client's staging/test server, and we weren't able to debug on that machine (we have Visual Studio installed on our own test server).  Since we didn't have privileges on the server, we couldn't even jump on WireShark to validate the outgoing packets from our server.  We needed an alternative method.

One of the cool things about HTTP Modules in ASP.NET is that they sit between the client and the server; with them, we can get all the information we need.  In addition, we can pass the output through a filter - a Stream object through which the page output passes.  This is exposed through the HttpResponse.Filter property.

The solution to this problem is straightforward: we'll inject a filter into the response stream and write all of the output to a log file.  Then, we'll be able to see whether we're sending the offending ?, or if Facebook is misreading our character and translating it to a ?.

First, I created the HTTP Module:

   1: using System;
   2: using System.Web;
   3:  
   4: public class SnifferHttpModule : IHttpModule
   5: {
   6:     #region IHttpModule Members
   7:  
   8:     public void Dispose()
   9:     {
  10:         
  11:     }
  12:  
  13:     public void Init(HttpApplication context)
  14:     {
  15:         context.BeginRequest += new EventHandler(context_BeginRequest);
  16:     }
  17:  
  18:     void context_BeginRequest(object sender, EventArgs e)
  19:     {
  20:         HttpContext.Current.Response.Filter = GetFilterFor(HttpContext.Current);
  21:     }
  22:     #endregion
  23:  
  24:     private static System.IO.Stream GetFilterFor(HttpContext httpContext)
  25:     {
  26:         return new LogFilter(httpContext, httpContext.Response.Filter);
  27:     }
  28: }

Then we had to implement an actual filter stream object:

   1: using System;
   2: using System.Web;
   3: using System.IO;
   4: using MBNCSUtil;
   5: using System.Text;
   6:  
   7: /// <summary>
   8: /// Summary description for LogFilter
   9: /// </summary>
  10: public class LogFilter : Stream
  11: {
  12:     private Stream _logStream, _sink, _binaryStream;
  13:     private StreamWriter _binaryWriter;
  14:     public LogFilter(HttpContext context, Stream baseStream)
  15:     {
  16:         _logStream = File.OpenWrite(
  17:             Path.Combine(
  18:                 context.Server.MapPath("~/logs"),
  19:                 string.Concat(DateTime.Now.ToString("yyyy-MM-dd-hh-mm-ss"), ".txt")
  20:                 )
  21:             );
  22:  
  23:         WriteHeadersToLog(_logStream, "Request Headers", context.Request.Headers);
  24:         
  25:         _binaryStream = File.OpenWrite(
  26:             Path.Combine(
  27:                 context.Server.MapPath("~/logs"),
  28:                 string.Concat(DateTime.Now.ToString("yyyy-MM-dd-hh-mm-ss"), "-binary.txt")
  29:                 )
  30:             );
  31:         _binaryWriter = new StreamWriter(_binaryStream, Encoding.UTF8);
  32:         _sink = baseStream;
  33:     }
  34:  
  35:     private static void WriteHeadersToLog(Stream logStream, string title, System.Collections.Specialized.NameValueCollection nameValueCollection)
  36:     {
  37:         StreamWriter sw = new StreamWriter(logStream);
  38:         sw.WriteLine(title);
  39:         sw.WriteLine();
  40:         foreach (string name in nameValueCollection.AllKeys)
  41:         {
  42:             string value = nameValueCollection[name];
  43:             if (value == null)
  44:                 value = "(nil)";
  45:             else if (value.Length == 0)
  46:                 value = "(empty string)";
  47:  
  48:             sw.WriteLine("{0}: {1}", name, value);
  49:         }
  50:         sw.WriteLine();
  51:         sw.Flush();
  52:     }
  53:  
  54:     public override bool CanRead
  55:     {
  56:         get { return false; }
  57:     }
  58:  
  59:     public override bool CanSeek
  60:     {
  61:         get { return false; }
  62:     }
  63:  
  64:     public override bool CanWrite
  65:     {
  66:         get { return true; }
  67:     }
  68:  
  69:     public override void Flush()
  70:     {
  71:         _logStream.Flush();
  72:         _sink.Flush();
  73:         _binaryStream.Flush();
  74:     }
  75:  
  76:     public override long Length
  77:     {
  78:         get { return _sink.Length; }
  79:     }
  80:  
  81:     public override long Position
  82:     {
  83:         get
  84:         {
  85:             return _sink.Length;
  86:         }
  87:         set
  88:         {
  89:             throw new InvalidOperationException();
  90:         }
  91:     }
  92:  
  93:     public override int Read(byte[] buffer, int offset, int count)
  94:     {
  95:         throw new InvalidOperationException();
  96:     }
  97:  
  98:     public override long Seek(long offset, SeekOrigin origin)
  99:     {
 100:         throw new InvalidOperationException();
 101:     }
 102:  
 103:     public override void SetLength(long value)
 104:     {
 105:         _sink.SetLength(value);
 106:     }
 107:  
 108:     public override void Write(byte[] buffer, int offset, int count)
 109:     {
 110:         _sink.Write(buffer, offset, count);
 111:         _logStream.Write(buffer, offset, count);
 112:         string text = DataFormatter.Format(buffer);
 113:         _binaryWriter.WriteLine(text);
 114:     }
 115:  
 116:     public override void Close()
 117:     {
 118:         base.Close();
 119:         _logStream.Close();
 120:         _sink.Close();
 121:         _binaryWriter.Close();
 122:         _binaryStream.Close();
 123:     }
 124: }

Notable in this class --

  • It's not readable or seekable. 
  • It creates two log files per transaction: a dump of the request headers and the response page, and a dump in hex and binary.  The hex-and-binary dump uses the DataFormatter class (username: 'mbncsutil_anonymous', no password) of the MBNCSUtil library, which I won't include here.
  • The Write, Flush, and Close operations are overridden to perform those operations on both of the logs and the contained stream.
  • The existing Response.Filter object is passed as a constructor to this new filter, and is contained as the member field _sink.  If the existing Response.Filter is not used, an exception is raised, because it means that the output isn't getting written to the stream that will eventually be sent to the client.
  • I implemented a helper method called WriteHeadersToLog because I wanted to dump both request and response headers to the log file.  However, I was rudely informed by ASP.NET that it would require IIS7 Integrated Pipeline mode.  So I removed it.

There are some nice files produced.  Here's an example of one:

   1: Request Headers
   2:  
   3: Connection: keep-alive
   4: Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
   5: Accept-Encoding: gzip, deflate
   6: Accept-Language: en-US
   7: Host: localhost:62905
   8: User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/523.15 (KHTML, like Gecko) Version/3.0 Safari/523.15
   9:  
  10:  
  11:  
  12: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  13:  
  14: <html xmlns="http://www.w3.org/1999/xhtml">
  15: <head><title>
  16:     Untitled Page
  17: </title></head>
  18: <body>
  19:     <form name="form1" method="post" action="Default.aspx" id="form1">
  20: <div>
  21: <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUJNzgzNDMwNTMzZGRurqWwMv9+8ZxlGC8bdg4AJen5vA==" />
  22: </div>
  23:  
  24:     <div>
  25:         This is a test.
  26:     </div>
  27:     </form>
  28: </body>
  29: </html>

And here's an example of the binary:

0000   0d 0a 0d 0a 3c 21 44 4f  43 54 59 50 45 20 68 74    ....<!DOCTYPE ht

0010   6d 6c 20 50 55 42 4c 49  43 20 22 2d 2f 2f 57 33    ml PUBLIC "-//W3

0020   43 2f 2f 44 54 44 20 58  48 54 4d 4c 20 31 2e 30    C//DTD XHTML 1.0

0030   20 54 72 61 6e 73 69 74  69 6f 6e 61 6c 2f 2f 45     Transitional//E

0040   4e 22 20 22 68 74 74 70  3a 2f 2f 77 77 77 2e 77    N" "http://www.w

0050   33 2e 6f 72 67 2f 54 52  2f 78 68 74 6d 6c 31 2f    3.org/TR/xhtml1/

0060   44 54 44 2f 78 68 74 6d  6c 31 2d 74 72 61 6e 73    DTD/xhtml1-trans

0070   69 74 69 6f 6e 61 6c 2e  64 74 64 22 3e 0d 0a 0d    itional.dtd">...

0080   0a 3c 68 74 6d 6c 20 78  6d 6c 6e 73 3d 22 68 74    .<html xmlns="ht

0090   74 70 3a 2f 2f 77 77 77  2e 77 33 2e 6f 72 67 2f    tp://www.w3.org/

00a0   31 39 39 39 2f 78 68 74  6d 6c 22 3e 0d 0a 3c 68    1999/xhtml">..<h

00b0   65 61 64 3e 3c 74 69 74  6c 65 3e 0d 0a 09 55 6e    ead><title>...Un

00c0   74 69 74 6c 65 64 20 50  61 67 65 0d 0a 3c 2f 74    titled Page..</t

00d0   69 74 6c 65 3e 3c 2f 68  65 61 64 3e 0d 0a 3c 62    itle></head>..<b

00e0   6f 64 79 3e 0d 0a 20 20  20 20 3c 66 6f 72 6d 20    ody>..    <form

00f0   6e 61 6d 65 3d 22 66 6f  72 6d 31 22 20 6d 65 74    name="form1" met

0100   68 6f 64 3d 22 70 6f 73  74 22 20 61 63 74 69 6f    hod="post" actio

0110   6e 3d 22 44 65 66 61 75  6c 74 2e 61 73 70 78 22    n="Default.aspx"

0120   20 69 64 3d 22 66 6f 72  6d 31 22 3e 0d 0a 3c 64     id="form1">..<d

0130   69 76 3e 0d 0a 3c 69 6e  70 75 74 20 74 79 70 65    iv>..<input type

0140   3d 22 68 69 64 64 65 6e  22 20 6e 61 6d 65 3d 22    ="hidden" name="

0150   5f 5f 56 49 45 57 53 54  41 54 45 22 20 69 64 3d    __VIEWSTATE" id=

0160   22 5f 5f 56 49 45 57 53  54 41 54 45 22 20 76 61    "__VIEWSTATE" va

0170   6c 75 65 3d 22 2f 77 45  50 44 77 55 4a 4e 7a 67    lue="/wEPDwUJNzg

0180   7a 4e 44 4d 77 4e 54 4d  7a 5a 47 52 75 72 71 57    zNDMwNTMzZGRurqW

0190   77 4d 76 39 2b 38 5a 78  6c 47 43 38 62 64 67 34    wMv9+8ZxlGC8bdg4

01a0   41 4a 65 6e 35 76 41 3d  3d 22 20 2f 3e 0d 0a 3c    AJen5vA==" />..<

01b0   2f 64 69 76 3e 0d 0a 0d  0a 20 20 20 20 3c 64 69    /div>....    <di

01c0   76 3e 0d 0a 20 20 20 20  20 20 20 20 54 68 69 73    v>..        This

01d0   20 69 73 20 61 20 74 65  73 74 2e 0d 0a 20 20 20     is a test...  
01e0   20 3c 2f 64 69 76 3e 0d  0a 20 20 20 20 3c 2f 66     </div>..    </f

01f0   6f 72 6d 3e 0d 0a 3c 2f  62 6f 64 79 3e 0d 0a 3c    orm>..</body>..<

0200   2f 68 74 6d 6c 3e 0d 0a                              /html>..

Some notes about using this code:

  • It's not meant to be used in production code.  It doesn't even attempt to synchronize file I/O.
  • It *can* be used as a starter if, for instance, you wanted to compress HTTP traffic.  (This is the same method the Blowery compression module takes).
  • You need to add an HTTP Module declaration to your web.config (see below).
  • Facebook is difficult to debug.

Setting up your Web.config file

Remember that you need to add the <httpModules> section to your web.config file.  If you don't already have one, this will suffice:

   1: <httpModules>
   2:     <add name="Filter" type="SnifferHttpModule, __code"/>
   3: </httpModules>

This assumes that the module is being added from App_Code.

Complete source code can be downloaded here.  Totally free, except for DataFormatter - that's licensed under a BSD-ish license. :-)

14Jan/080

Another Reason to Avoid Automatic Properties

Posted by Rob

Another reason to avoid using Automatic Properties:

[Name("Simple Property Example")]
[Browsable(true)]
public string TestProp1
{
    get;
    set;
}

Look at the property in the debugger:

Oh wait, I can't.

I can't even set a breakpoint to let me know when the property is being accessed.  I understand not being able to see the backing store or the value component - they are after all not part of the code.  But you can't even see when they're being hit.

All I have to say is...

STAY AWAY.