Scott Hanselman

ASP.NET Params Collection vs. QueryString, Forms vs. Request["index"] and Double Decoding

December 16, 2004 Comment on this post [4] Posted in ASP.NET
Sponsored By

In ASP.NET you can yank a value out of the QueryString like this, where QueryString is of type NameValueCollection but is internally an HttpValueCollection that includes some extra helper methods.

string foo = Request.QueryString["foo"];

But you can also go like this:

string foo = Request["foo"];

And folks know (passed through myth and legend) that the line above will search through the QueryString, Form, Cookies, and ServerVariables collections. However, it's important (for performance) to know what order the collections are searched. Here's the code from Reflector:  

    1 public string get_Item(string key)
    2 {
    3     string text1 = this.QueryString[key];
    4     if (text1 != null)
    5     {
    6         return text1;
    7     }
    8     text1 = this.Form[key];
    9     if (text1 != null)
   10     {
   11         return text1;
   12     }
   13     HttpCookie cookie1 = this.Cookies[key];
   14     if (cookie1 != null)
   15     {
   16         return cookie1.Value;
   17     }
   18     text1 = this.ServerVariables[key];
   19     if (text1 != null)
   20     {
   21         return text1;
   22     }
   23     return null;
   24 }

So you can see what order things are searched in. However, personally, I don't like this default Item indexer. I prefer to be more explicit. I'd hate to accidentally retrieve a Cookie because a QueryString variable was missing. It's always better to be explicit and ask for what you want.

Interestingly, there is ANOTHER collection of QueryString, Form, Cookies, and ServerVariables, but rather than a "pseudo-collection" as we see above, this is an actual combined collection.

  432 public NameValueCollection Params
433 {
434 get
435 {
436 InternalSecurityPermissions.AspNetHostingPermissionLevelLow.Demand();
437 if (this._params == null)
438 {
439 this._params = new HttpValueCollection();
440 this.FillInParamsCollection();
441 this._params.MakeReadOnly();
442 }
443 return this._params;
444 }
445 }
446
447 private void FillInParamsCollection()
448 {
449 this._params.Add(this.QueryString);
450 this._params.Add(this.Form);
451 this._params.Add(this.Cookies);
452 this._params.Add(this.ServerVariables);
453 }
454

The internal collection "_params" inside is a special derived NameValueCollection of type HttpValueCollection, and is exposed as NameValueCollection.

Important Note: The constructor for HttpRequest will parse the actual string QueryString and UrlDecode the values for you. Be careful not to DOUBLE DECODE. Know what's encoded, when, and who does the decoding.  Likely it's not you that needs to do anything. If you double decode you can get into some weird situations. Ben Suter reminded me that if you pass in /somepage.aspx?someParam=A%2bB you expect to get "A+B" as that param is the equivalent of HttpUtility.UrlEncode("A+B"). But, if you make a mistake and do HttpUtility.UrlDecode(Request.Params("someParam")), you'll get "A B" as the + was double-decoded as a space.

Here's the trick though. If you have BOTH a QueryString parameter "Foo=Bar1" AND a Forms item called "Foo=Bar2" if you say string foo = Request.Params["Foo"]; you'll get a string back "Bar1,Bar2"! It's a collection, not a HashTable. So, never make assumptions when you use HttpRequest.Params, or you will get in trouble. If there's a chance you could get multiple values back, you need to consider using an explicit collection or be smart about your string.Split() code.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

XmlPreprocess is an elegant hack - or - "The hack is in the eye of the beholder"

December 16, 2004 Comment on this post [6] Posted in ASP.NET | XML | Bugs
Sponsored By

Hack? Brilliance? Sloppy? Simple? Here's when I know something is good:

  • It's easily explained.
  • It's not ambiguous.
  • There aren't many files.
  • Self-containment.
  • It's simple

Seeing a pattern? Loren has come up with an interesting and elegant hack called XmlPreprocess to deal with the deployment of config files to multiple environments without the need for multiple copies. It would be EASILY integrated into a Continuous Integration environment like ours.

The idea is bone simple:

<configuration>
<system.web>
<!-- ifdef ${production} -->
<!-- <compilation defaultLanguage="c#" debug="false"/> -->
<!-- else -->
<compilation defaultLanguage="c#" debug="true"/>
<!-- endif -->
</system.web>
</configuration>

gets loaded into "XmlPreprocess.exe /d production" and you get:

<configuration>
<system.web>
<compilation defaultLanguage="c#" debug="false"/>
</system.web>
</configuration>

Cool, eh? Now, some may thing preprocessor/if statements "tunneled" in XML Comments are gross, but to the nay-sayers I say show me one man's extensibility hack and I'll show you xs:annotation. Would you feel better if the "language" was hidden in other XML elements? Or another namespace? Sure, it's possible, but the goal isn't a new language (if statement = new language) it's #ifdef for XML for a specific purpose.

Reasons it's cool:

  • The files are well-formed and legit XML before and after the process. No processing is needed to use them as found in Source Control.
  • The files are self-describing. Just look at them, it's clear what's up.
  • As Loren says, Get and Go.

Charlie says in comments on Jon Galloway's blog that it's a hack that this is "definitely not the way most natural way of dealing with XML" and he'd prefer XSLT. He does point out that XSLT isn't for newbies. However, for this small task I say, Wow, no way.

Using XSLT to change a config file in such a small way seems like hammering a screw. It's possible, but overkill that WILL get messy. Look at the previous requirements and apply XSLT. XSLT is great for transforming Infosets into other totally different Infosets, and while I'm deep into XSLT, I'm not a fan of using XSLT for small "pruning" changes.

I think XmlPreprocess is clever thing that's good for exactly what it's good for. Check it out!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

An IP Address Blocking HttpModule for ASP.NET in 9 minutes

December 14, 2004 Comment on this post [11] Posted in ASP.NET | HttpModule | Bugs
Sponsored By

I'm sure this has been done before, but it was faster to write it than to google for it. There's some IP Addresses that have been bothering me and I don't have access to a firewall or IIS at my ISP, so...

I can upload a text file called blockedips.txt to my site and the changes happen immediately.

    9 namespace YourModuleNameHere
   10 {
   11     public class IPBlackList : IHttpModule
   12     {
   13         private EventHandler onBeginRequest;
   14 
   15         public IPBlackList()
   16         {
   17             onBeginRequest = new EventHandler(this.HandleBeginRequest);
   18         }
   19 
   20         void IHttpModule.Dispose()
   21         {
   22         }
   23 
   24         void IHttpModule.Init(HttpApplication context)
   25         {
   26             context.BeginRequest += onBeginRequest;
   27         }
   28 
   29         const string BLOCKEDIPSKEY = "blockedips";
   30         const string BLOCKEDIPSFILE = "SiteConfig/blockedips.config";
   31 
   32         public static StringDictionary GetBlockedIPs(HttpContext context)
   33         {
   34             StringDictionary ips = (StringDictionary)context.Cache[BLOCKEDIPSKEY ];
   35             if (ips == null)
   36             {
   37                 ips = GetBlockedIPs(GetBlockedIPsFilePathFromCurrentContext(context));
   38                 context.Cache.Insert(BLOCKEDIPSKEY , ips, new CacheDependency(GetBlockedIPsFilePathFromCurrentContext(context)));
   39             }
   40             return ips;
   41         }
   42 
   43         private static string BlockedIPFileName = null;
   44         private static object blockedIPFileNameObject = new object();
   45         public static string GetBlockedIPsFilePathFromCurrentContext(HttpContext context)
   46         {
   47             if (BlockedIPFileName != null)
   48                 return BlockedIPFileName;
   49             lock(blockedIPFileNameObject)
   50             {
   51                 if (BlockedIPFileName == null)
   52                 {
   53                     BlockedIPFileName = context.Server.MapPath(BLOCKEDIPSFILE);
   54                 }
   55             }
   56             return BlockedIPFileName;
   57         }
   58 
   59         public static StringDictionary GetBlockedIPs(string configPath)
   60         {
   61             StringDictionary retval = new StringDictionary();
   62             using (StreamReader sr = new StreamReader(configPath))
   63             {
   64                 String line;
   65                 while ((line = sr.ReadLine()) != null)
   66                 {
   67                     line = line.Trim();
   68                     if (line.Length != 0)
   69                     {
   70                         retval.Add(line, null);
   71                     }
   72                 }
   73             }
   74             return retval;
   75         }
   76 
   77         private void HandleBeginRequest( object sender, EventArgs evargs )
   78         {
   79             HttpApplication app = sender as HttpApplication;
   80 
   81             if ( app != null )
   82             {
   83                 string IPAddr = app.Context.Request.ServerVariables["REMOTE_ADDR"];
   84                 if (IPAddr == null || IPAddr.Length == 0)
   85                 {
   86                     return;
   87                 }
   88 
   89                 StringDictionary badIPs = GetBlockedIPs(app.Context);
   90                 if (badIPs != null && badIPs.ContainsKey(IPAddr))
   91                 {
   92                     app.Context.Response.StatusCode = 404;
   93                     app.Context.Response.SuppressContent = true;
   94                     app.Context.Response.End();
   95                     return;
   96                 }
   97             }
   98         }
   99     }
  100 }

And in your web.config:

   42 <system.web>
   43    <httpModules>
   44         <add type="YourModuleNameHere.IPBlackList, YourAssemblyNameHere"
   45             name="IPBlackList" />
   46    </httpModules>
   47 </system.web>

No warrenty, express or implied. If it sucks or has bugs/security holes, let me know as it's 9 minutes work.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

ServiceProcessInstaller HelpText has very little documentation

December 14, 2004 Comment on this post [0] Posted in Programming
Sponsored By

I googled around for info on the ServiceProcessInstaller, hoping to change the HelpText. It's a getter, so you have derive and override, rather than setting it. No biggie, but I was surprised. I'm used to googling for .NET documentation and finding MASSIVE amounts of stuff...lot's of articles, etc. Apparently the ServiceProcessInstaller just isn't sexy enough to write about.

This overloaded getter will show help text when someone calls "InstallUtil.exe myserviceassembly.exe /?" on my service exe.

Anyway, I did this. It's not rocket science, but I'd have perferred a setter. Maybe I'm old fashioned.

   88 internal class MySpecialServiceProcessInstaller : System.ServiceProcess.ServiceProcessInstaller
   89 {
   90     public override string HelpText
   91     {
   92         get
   93         {
   94             return @"Enter the Username and Password for the Whatever account in the form \\domain\username. For machines not in a domain, enter hostname\username.";
   95         }
   96     }
   97 }

Then I change the InitializeComponent section - you know, the section you're never supposed to change?

   64 private void InitializeComponent()
   65 {
   66     this.serviceProcessInstaller1 = new MySpecialServiceProcessInstaller();
   67     this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller();

Maybe when I google for it again, I'll find my own site. If I can touch the life of just one child who's also using System.ServiceProcess.ServiceProcessInstaller...

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

The Desktop Search battle continues - yawn? Inside MSN Desktop Search

December 13, 2004 Comment on this post [2] Posted in Programming | Tools
Sponsored By

PerfmonsearchWell, I'm deep into the MSN Desktop Search. It's apparently more appropriate to refer to it as the "MSN Toolbar Suite."

Sriram Krishnan has even more technical forensic details on how Google Desktop Search works, but here's what I've gleaned about MSN Desktop Search.

  • The footprint is about 5 megs installed - this changes after you index,
  • The search is per user and everything is in "C:\documents and settings\[username]\local settings\application data\msn toolbar suite\"
    • There's lots of temp files with "Microsoft Search Gatherer Transaction Log. Format Version 4.9" inside them.
  • There's an interesting video with the team up on Channel 9.
  • They don't index your whole harddrive by default - they start with the "My*" files and Outlook* Applications.
  • They are using Microsoft Indexing IFilters - This makes me wonder if I should go download a bunch of new IFilters to get better searching.
  • They index MP3s and Music...
  • When you search, the UI lives in Explorer or Internet Explorer. They've got their own namespace.
  • There are a CRAPLOAD of Perfmon Counters added under "RS Search*" in Perfmon.

MSN Desktop Search vs. Google Desktop Search

MSN: Toolbar pointing you to msn.com
Google: Toolbar pointing you to google.com

MSN: Interface is very shiny and Windowsy and lives in Explorer
Google: Interface is very plain and HTMLy and lives in any browser

MSN: Indexes any IFilter-able thing ala Index Server (this has potential)
Google: Indexes Outlook and a number of text files. (has a hacked .NET plugin API)

MSN: Type-ahead autocomplete ala X1.com
Google: Nope, but I suspect it's coming very soon...

Winner (for now): The user, as now two large companies realize it's hard to find files on your hard drive!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.