Redmond's Gray Skies of Summer

As of December 2011, this topic has been archived. As a result, it is no longer actively maintained. For more information, see Archived Content. For information, recommendations, and guidance regarding the current version of Internet Explorer, see Internet Explorer Developer Center.

By Jay Allen, Mark Davis, Heidi Housten, and Tosh Meston
Microsoft Corporation

July 2, 2002

Well, it is summer time in Redmond and that means rain. That's typical of life in the Pacific Northwest, but no matter. It just means that the Web Team has more time to prepare a great column for you.

This month, we explain how to add custom headers to your HTTP requests, give all the information you ever wanted on IFRAMEs, show how to use the trusty old ADODB library in a Web Form, and even give a spiffy behavior for resizing columns in a table. Plus, the Web Team in Short section offers more tips for you to glean in building your pages.

Contents

Getting A-Header—adding custom headers to HTTP requests
Windows on the World—working with IFRAMEs
Old School Data Access in ASP.NET—using the ADODB library in ASP.NET

Web Team in Short

Getting A-Header

Dear Web Team:

I am trying to find a way to insert a custom header field into all HTTP requests sent out by IE. I've had no luck so far. Please help!

Thanks,
Dale Liao

The Web Team replies:

You've come to the right place, Dale. Dispensing rays of hope is the Web Team's specialty. C++ and Visual Basic® developers who reuse the Internet Explorer WebBrowser control, automate Internet Explorer, or write Browser Helper Objects (BHOs) have at their disposal an event interface called DWebBrowserEvents2. If you only need to send a custom header for the initial HTML page, you can use the BeforeNavigate2() event, whose Headers parameter lets you insert arbitrary headers into the outgoing transaction. However, since this event fires for the main URL and not any of its embedded content, you will not receive this event for images, script files, and so on.

There is one surefire way to add data to every outgoing transaction. Let's say you have a custom software package installed—maybe with some ActiveX® controls that you host in Web pages off of your site—and want to signal this to the server regardless of whether users access your site using Internet Explorer or a WebBrowser host. You can append such information to the Internet Explorer User-Agent string by inserting a new String value into the following registry key:

\\HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentControlSet\
Internet Settings\5.0\User Agent\Post Platform

The name of any new String value defined under this key will be tacked onto the end of the User Agent string that Internet Explorer sends to all Web sites. Those of you with .NET installed will notice that a value is already defined for the version of the common language runtime installed on your machine. This predefined value enables Web sites to conditionally serve managed content to your Web browser.

If you don't need to use the WebBrowser control, but need to consume raw data using HTTP instead, another option is to use URL Monikers (URLMON) directly, which will allow you to add additional headers to a request using the IHttpNegotiate interface. MFC makes this easy with the CAsyncMonikerFile class, a virtual class that you implement to provide code for the IBindStatusCallback methods used by URLMON. You can override the default implementation of CAsyncMonikerFile's Open() method to provide your implementation of the IHttpNegotiate interface.

  1. Create a new MFC class that inherits from CCmdTarget. For this sample, we'll call the class NegotiateCallback.
  2. Create an inner class using MFC's BEGIN_INTERFACE_PART macro that implements the IHttpNegotiate interface. MFC will delegate queries for IHttpNegotiate to this inner class. This should go in your class' .h file.
    BEGIN_INTERFACE_PART(HttpNegotiateObj, IHttpNegotiate)
          STDMETHOD_(HRESULT, BeginningTransaction)(LPCWSTR szUrl, 
    LPCWSTR szHeaders, DWORD dwReserved, LPWSTR *pszAdditionalHeaders);
          STDMETHOD_(HRESULT, OnResponse)(DWORD dwResponseCode, 
    LPCWSTR szResponseHeaders, LPCWSTR szRequestHeaders, LPWSTR* 
    pszAdditionalRequestHeaders);
    END_INTERFACE_PART(HttpNegotiateObj)
    
  3. Add IHttpNegotiate as a QI-able interface using MFC's BEGIN_INTERFACE_MAP macro. This is located in your class' .cpp file.
    BEGIN_INTERFACE_MAP(NegotiateCallback, CCmdTarget)
       INTERFACE_PART(NegotiateCallback, IID_INegotiateCallback, Dispatch)
       INTERFACE_PART(NegotiateCallback, IID_IHttpNegotiate, HttpNegotiateObj)
    END_INTERFACE_MAP()
    
  4. Supply implementations of the IHttpNegotiate methods to add headers or check response headers. The following code adds an X-Custom-App HTTP header to the outgoing transaction. Note that you are responsible for inserting a CR/LF (carriage return/line feed) at the end of every custom header; otherwise your custom header will elide with the header immediately following, corrupting your HTTP transaction. Also, make sure to use CoTaskMemAlloc() instead of the C Runtime memory-management functions, so that URLMON can free your data with a call to CoTaskMemFree().
    STDMETHODIMP NegotiateCallback::XHttpNegotiateObj::BeginningTransaction(LPCWSTR 
    szUrl, LPCWSTR szHeaders, DWORD dwReserved, LPWSTR 
    *pszAdditionalHeaders) {
       LPWSTR pszHeader = (LPWSTR)CoTaskMemAlloc(74);
       memcpy((void*)pszHeader, (void*)L"X-Custom-App: Version 
    1.1.3748.9\r\n", 74);
       *pszAdditionalHeaders=pszHeader;
    
       return S_OK;
    }
    
  5. Create a new MFC class that subclasses CAsyncMonikerFile. We'll call this class CFetchData.
  6. Override CAsyncMonikerFile's Open() method. This method is responsible for creating the IBindStatusCallback implementation MFC will use to receive events on the status of the download. You need to create this callback yourself using a pointer to the IUnknown of NegotiateCallback, causing MFC's IBindStatusCallback implementation and your IHttpNegotiate implementation to aggregate. Aggregation is a COM technique that extends one class with the capabilities of another. In this instance, it means that all queries URLMON performs on MFC's IBindStatusCallback for IHttpNegotiate will be delegated to your IHttpNegotiate implementation.
    BOOL CFetchData::Open(LPCTSTR lpszURL, CFileException* pError) {
       nc = new NegotiateCallback();
       LPDISPATCH pNegCallDisp = nc->GetIDispatch(FALSE);
       
       // I borrowed this from oleasmon.cpp. 
       IPTR(IBindHost) pBindHost(CreateBindHost(), FALSE);
       IPTR(IBindCtx) pBindCtx(CreateBindContext(pError), FALSE);
       if (pError && (pError->m_cause != CFileException::none))
          return FALSE;
       // Instantiate our NegotiateCallback object, and aggregate with the 
       // IBindStatusCallback supplied by MFC. 
       IPTR(IUnknown) pBSCUnk(CreateBindStatusCallback(pNegCallDisp), FALSE);
    
       IPTR(IBindStatusCallback) pBSC;
       pBSCUnk->AddRef();
       pBSC.QueryInterface(pBSCUnk);
    
       return Attach(lpszURL, pBindHost, pBSC, pBindCtx, pError);
    }
    
  7. Implement the IBindStatusCallback methods on CAsyncMonikerFile in order to receive your data as it is downloaded. CAsyncMonikerFile already implements this interface, so all you need to do is provide overrides.

Windows on the World

Dear Web Team:

I'm developing a Web-based application with a rich client interface that uses IFRAMEs to mimic a Windows MDI (Multiple Document Interface) application. However, my prototype seems slow in Internet Explorer. The IFRAMEs often take too long to load, and page performance often suffers. Is this an advisable architecture, or is this too much for Internet Explorer to handle?

The Web Team replies:

The IFRAME, or inline frame, is a powerful tool originally introduced in Internet Explorer 3.02 to enable undocked frame windows. Increasingly, it is used to hold problem domain-specific detail data within a digital dashboard. This is commonly known as "Web Part" architecture. The main page serves as the navigational shell, while the faux child windows host HTML snippets ("Web Parts") from various data sources. The SQL Server Digital Dashboard uses this architecture, and you can pull it off as well, so long as you keep a few key points in mind.

The first issue to contend with, of course, is cross-frame scripting security. As detailed in the Dynamic HTML SDK, if your IFRAME content resides in a different domain than the main page itself, the main page will be unable to access the content of the IFRAME. This is a security measure designed to prevent frame spoofing, where a non-trusted site can gain information from, or pump content into, the pages of a trusted site. While the document.domain property can sometimes ease this restriction, it depends on the content from both sites residing in the same top-level domain; for example, the main site resides in http://www.microsoft.com, and the IFRAME content resides in http://www.msn.com. However, it's often the case that Web Part content will come from partner companies, or from a recently acquired company still operating under its original domain. In this circumstance, where you have your main page in http://www.microsoft.com and your IFRAME in http://www.msn.com, document.domain will not save you.

The only known workaround for this latter setup is to use content wrapping. With ASP.NET, it is now both easy and efficient to request data from other Web sites through an ASPX page using either the System.Net or System.Xml classes. Say that your pages are hosted on http://www.microsoft.com, but you wish to retrieve data from http://www.msn.com and http://msdn.microsoft.com. You can write an ASPX page, hosted on http://www.microsoft.com, which retrieves data from these sites and returns it to the browser. This masks the true origin of the data; the browser sees the data coming from http://www.microsoft.com, and is ignorant of which other sites might have been used to retrieve that data.

The other subtler problem is the per-server connection limit built into Internet Explorer. If you have more than two IFRAMEs on the page at a time, which is very common in Web Part architecture, then you are likely to experience connection blocking if you retrieve your data from a single server. As stated in Knowledge Base article Q183110, WinInet, Internet Explorer's HTTP protocol stack, limits you to two simultaneous connections to a Web site. If you have four IFRAMEs on a page all pointing to the same site, then the first two pages will download instantly and the next two requests will block until one of the pages and all its constituent data—images, scripting includes, and so on—is fully loaded. You can test this out by loading the following simple page and using Network Monitor to record HTTP requests:

<HTML>
<SCRIPT>
function loadPages()
{
   fm1.document.location = "http://server1/page1.htm";   
   fm2.document.location = "http://server1/page2.htm";
   fm3.document.location = "http://server1/page3.htm";
   fm4.document.location = "http://server1/page4.htm";
}
</SCRIPT>
<BODY>
<IFRAME ID="fm1"></IFRAME><BR>
<IFRAME ID="fm2"></IFRAME><BR>
<IFRAME ID="fm3"></IFRAME><BR>
<IFRAME ID="fm4"></IFRAME><BR>
<BUTTON ONCLICK="loadPages();">
</BODY>
</HTML>

This behavior is built into the HTTP/1.1 protocol to help improve Web site performance. Unless you're willing and able to hack the registry (a solution we frown upon even if you can pull it off), your best bet is to limit the number of simultaneous requests through the UI. For example, forbid selection of a new Web part from your navigational bars until a previously selected Web Part is fully loaded. The readyState property of IFRAMEs is, unfortunately, flaky, so you'll need to use one of the methods discussed in Knowledge Base Article Q239638 to decide when an IFRAME is ready. Some developers have gone so far as to write an ActiveX wrapper for the ServerXMLHTTP object—a server-side networking component based on WinHTTP and HTTP/1.0—to circumvent this limit. However, since ServerXMLHTTP is not available by default on many clients and not marked as safe for scripting, this is not recommended.

Finally, let's have a word about memory utilization. Each IFRAME contains a fully loaded copy of the DHTML Document Object Model (DOM), so you can expect each IFRAME to consume anywhere from 300 KB to 1 MB of extra memory, depending upon the complexity of the hosted Web Part. When dealing with so much data and scripting across frames, it's important to keep in mind that any objects allocated within JScript® will eventually be garbage-collected by the JScript engine, but that any references held by JScript to DOM objects will cause those DOM objects to be retained in memory, as the DOM is built using the Component Object Model (COM). While you can garner significant performance improvements by caching DOM object references, make sure you eventually set such references to null. For example, ensure you set the reference in the onbeforeunload event handler for the page. Also, try and keep such references within the current page's scope as caching references in other FRAME or IFRAME objects will likely cause build-up, and make you believe that Internet Explorer is leaking gross amounts of memory.

If you want to learn more about this topic, please visit the Support WebCast site and watch the presentation by the Web Team's own Jay Allen.

Old School Data Access in ASP.NET

Dear Web Team:

Data access in ASP.NET seems very powerful, but I don't have a lot of time to learn how to use ADO.NET and the DataGrid control right now. All I want to do is connect to a database, run a query and output the result set in an HTML table. I am familiar with ADO, and I want to know how to use the ADO library in ASP.NET.

Kapil Chopra

The Web Team replies:

The Web Team knows how you feel. There are so many great new .NET technologies out there. We'd love to sit around and play with them all day, but we've got work to do first. Well Kapil, you're in luck. The Web Team has looped over more recordsets than we can count, and you can still use the trusty old ADODB library, with a few modifications, in ASP.NET.

Begin by opening Visual Studio® .NET and creating a new ASP.NET Web Application in C#. Add a new Web Form named Example1.aspx and copy the following code into it.

Example1.aspx

<%@ Page language="c#" Codebehind="Example1.aspx.cs" 
AutoEventWireup="false" Inherits="WebApplication1.Example1" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
<HEAD>
<TITLE>Example1</TITLE>
<META name="GENERATOR" Content="Microsoft Visual Studio 7.0">
<META name="CODE_LANGUAGE" Content="C#">
<META name=vs_defaultClientScript content="JavaScript">
<META name=vs_targetSchema 
content="http://schemas.microsoft.com/intellisense/ie5">
</HEAD>
<BODY>
   <FORM ID="Form1" METHOD="post" RUNAT="server">
      <DIV ID="div1" RUNAT="server"></DIV>
   </FORM>
</BODY>
</HTML>

Now open the code-behind Example1.aspx.cs and add the following code.

Example1.aspx.cs

using System;
using System.Text;
using System.Web.UI.HtmlControls;

namespace WebApplication1
{
   public class Example1 : System.Web.UI.Page 
   {
      protected System.Web.UI.HtmlControls.HtmlGenericControl div1;

      private void Page_Load(object sender, System.EventArgs e)
      {
         ADODB.ConnectionClass conn = new ADODB.ConnectionClass();
         string strConn = "Driver={Microsoft Access Driver 
(*.mdb)};DBQ=C:\\data.mdb;";
         conn.Open(strConn, "", "", 0);

         string strSQL = "SELECT * FROM Table1";
         ADODB.RecordsetClass rst = new ADODB.RecordsetClass();
         rst.Open(strSQL, conn, ADODB.CursorTypeEnum.adOpenStatic, ADODB.LockTypeEnum.adLockReadOnly, 0);
         
         StringBuilder sb1 = new StringBuilder();
         sb1.Append("<TABLE>");
         while (!rst.EOF)
         {
            sb1.Append("<TR><TD>" );
            sb1.Append(rst.Fields["Field0"].Value.ToString());
            sb1.Append("</TD><TD>");
            sb1.Append(rst.Fields["Field1"].Value.ToString());
            sb1.Append("</TD></TR>");
            rst.MoveNext();
         }
         sb1.Append("</TABLE>");
         div1.InnerHtml = sb1.ToString();
         rst.Close();
         conn.Close();
      }

      #region Web Form Designer generated code
      override protected void OnInit(EventArgs e)
      {
         //
         // CODEGEN: This call is required by the ASP.NET Web Form Designer.
         //
         InitializeComponent();
         base.OnInit(e);
      }
      
      /// <summary>
      /// Required method for Designer support - do not modify
      /// the contents of this method with the code editor.
      /// </summary>
      private void InitializeComponent()
      {    
         this.Load += new System.EventHandler(this.Page_Load);
      }
      #endregion
   }
}

Now add a reference to the ADODB interop assembly. Do this by choosing Add Reference... from the Project menu. On the .NET tab, select the adodb component, then click Select, and then click OK. Now set Example1.aspx as the start page of your Web application by right-clicking on it in the solution explorer and choosing Set As Start Page. Then build the solution and make sure there are no errors.

There's not much to the ASPX file. The only thing we have added to the default HTML is a DIV with an ID and a RUNAT="server" attribute. With this, we can use the DIV as HtmlGenericControl and set the InnerHtml property of it in our Page_Load() function.

Now, let's look at the Page_Load() function. In here, we will use the ConnectionClass and RecordsetClass classes of the ADODB interop assembly to retrieve our data from an Access database file. These classes are functionally the same as the Connection and Recordset objects of the ADODB libraries that we've used many times before. Finally, we build up a StringBuilder object containing an HTML table with our query data in it.

You will of course have to specify your own database, modify the SQL statement, and fields in the RecordsetClass object.

The first item of business is to create a new ConnectionClass and open a connection to a database.

   ADODB.ConnectionClass conn = new ADODB.ConnectionClass();
   string strConn = "Driver={Microsoft Access Driver 
(*.mdb)};DBQ=C:\\data.mdb;";
   conn.Open(strConn, "", "", 0);

Next, we create a RecordsetClass object, prepare our SQL statement, and open up the recordset. We use the static cursor type and read-only lock type as we are only displaying data.

   string strSQL = "SELECT * FROM Table1";
   ADODB.RecordsetClass rst = new ADODB.RecordsetClass();
   rst.Open(strSQL, conn, ADODB.CursorTypeEnum.adOpenStatic, _
      ADODB.LockTypeEnum.adLockReadOnly, 0);

Now, we create a new StringBuilder object and loop over the RecordsetClass building up our HTML string, moving to the next record, until finally the EOF property is true.

   StringBuilder sb1 = new StringBuilder();
   sb1.Append("<TABLE>");
   while (!rst.EOF)
   {
      sb1.Append("<TR><TD>" );
      sb1.Append(rst.Fields["Field0"].Value.ToString());
      sb1.Append("</TD><TD>");
      sb1.Append(rst.Fields["Field1"].Value.ToString());
      sb1.Append("</TD></TR>");
      rst.MoveNext();
   }
   sb1.Append("</TABLE>");

The StringBuilder class is a member of the System.Text namespace and is very handy if you are going to be making a number of string concatenations. In the .NET Framework, each time two strings are concatenated, a new buffer is created and the resulting string is placed into it. With StringBuilder, you can specify an initial buffer size and avoid reallocating on each concatenation.

Finally, we set the InnerHtml property of the DIV we added to the ASPX file to the TABLE we have built up in our StringBuilder object. We do this by calling the ToString() method on it. We reference the DIV as a protected HtmlGenericControl in our Web Form class.

   protected System.Web.UI.HtmlControls.HtmlGenericControl div1;
   …
   div1.InnerHtml = sb1.ToString();

Well, that's it. Naturally, we want to encourage our readers to explore the new classes in ADO.NET and use the DataGrid, but we also understand many of you have products and tools to build in a short amount of time. Hope this helps, Kapil!

Web Team in Short

My Favorite Site…

Q: Mike Joe asks, "I want have a button on a Web page, that when clicked, adds the page to the browser's Favorites list. Is this possible?"

A: Internet Explorer will let you do this through a method called AddFavorite on the window.external object. The browser's security model will prompt to confirm that you would like to add the item to the favorites list before actually doing it. AddFavorite takes two parameters—a string for the URL and an optional string for the name to be shown in the favorites menu.

<HTML>
<SCRIPT>
function addFav()
{
   window.external.AddFavorite( "http://msdn.microsoft.com/columns/
   webmen.asp", "Web Team Talking" );
}
</SCRIPT>
<BODY>
<BUTTON onclick="addFav()">Add Favorite...</BUTTON>
</BODY>
</HTML>

Fonts of Wisdom?

Q: Hsun-Ming Chou asks how can he retrieve a list of all fonts installed on a system through script on a Web page.

A: Look no further than the dialogHelper object that is included with Internet Explorer 6. The dialogHelper exposes the fonts and blockFormats collections and is very helpful if you are implementing an HTML editor inside of the browser. You can even open a system color picker by calling the ChooseColorDlg method on the object. Here is some script to write out all the fonts on your system.

<HTML>
<SCRIPT>
function getFonts()
{
   var nFontLen = dlgHelper.fonts.count;
   var rgFonts = new Array();

   for ( var i = 1; i < nFontLen + 1; i++ )
   { 
      rgFonts[i] = dlgHelper.fonts(i);
   } 
   rgFonts.sort();

   for ( var j = 0; j < nFontLen; j++ )
   {
      document.write( rgFonts[j] + "<BR>" );
   }
}
</SCRIPT>
<BODY ONLOAD="getFonts()">
<OBJECT ID=dlgHelper CLASSID="clsid:3050f819-98b5-11cf-bb82-
00aa00bdce0b" WIDTH="0px" HEIGHT="0px">
</OBJECT> 
</BODY>
</HTML>

Resize Your HTML Tables

Q: Barbara wants to know how a user can drag a column border to resize a table in an HTML page.

A: The way we tackled this idea was to create a DHTML behavior that, when attached to an HTML table, listens to the mouse events. When the cursor is moved over the column border, a column-resize cursor is displayed. The user is then able to click and drag the column border to resize the column. The behavior can be attached to your HTML table using the STYLE attribute as follows:

STYLE="behavior:url(resize_table.htc)"

Here are the contents of resize_table.htc:

<PUBLIC:COMPONENT>
<PUBLIC:ATTACH EVENT="onmousedown" ONEVENT="mouseDown()"/>
<PUBLIC:ATTACH EVENT="onmouseup" ONEVENT="mouseUp()"/>
<PUBLIC:ATTACH EVENT="onmousemove" ONEVENT="mouseMove()"/>
<SCRIPT LANGUAGE="JScript">
var x, y;
var elem;
var bColSelected = false;
var bResizing = false;
var t = 3;   // selection tolerance
function mouseDown()
{
   // Capture mouse if column border has been selected
   if ( bColSelected )
   {
      element.setCapture();
      bResizing = true;
      xOrig = x;
   }
}
function mouseUp()
{
   // Release mouse capture
   element.releaseCapture();
   bResizing = false;
}
function mouseMove()
{
   x = window.event.x;
   y = window.event.y;
   if ( bResizing )
   {
      var row = elem.parentElement;
      var iCell;
      var nCells = row.cells.length;

      // Locate cell index
      for (var i = 0; i < nCells; i++ )
      {
         if ( row.cells[i] == elem )
         {
            iCell = i;
            break;
         }
      }
      var rows = row.parentElement.rows;
      var nRows = rows.length;
      var cell;
      var width;

      // Adjust width of each cell in the column
      for ( var i = 0; i < nRows; i++ )
      {
         cell = rows[i].cells[iCell];
         width = x - cell.offsetLeft;
         if ( width > 0 )
            cell.width = x - cell.offsetLeft;
      }
   }
   else
   {
      elem = window.document.elementFromPoint( x, y );
      if ( elem.tagName == "TD" )
      {
         var rect = elem.getClientRects()[0];
         var left = rect.left;
         var right = rect.right;
         var top = rect.top;
         var bottom = rect.bottom;

         // Detect if cursor is on vertical cell border
         if ( ( x >= left - t && x <= left + t
             && y >= top + t && y <= bottom -t )
           || ( x >= right - t && x <= right + t
            && y >= top + t && y <= bottom -t ) )
         {
            bColSelected = true;
            element.style.cursor = "col-resize";
         }
         else
         {
            bColSelected = false;
            element.style.cursor = "";
         }
      }
      else
      {
         bColSelected = false;
         element.style.cursor = "";
      }
   }
}
</SCRIPT>
</PUBLIC:COMPONENT>

Scripting the Easy Way

Q: Gary wants to know where to find the Microsoft Windows® Script control.

A: The Microsoft Windows Script control provides an easy way make your application scriptable, which enables your customers to write scripts that can automate routine tasks, much like the way Visual Basic for Applications is provided by Microsoft Office applications. The control takes the work out of hosting Active Scripting in your application by implementing COM interfaces such as IActiveScriptSite. Because it is implemented as an ActiveX component, you can use this in your Visual Basic or Visual C++® application. You will also need to provide a way for your user to create and run script, extend the script language by exposing your own object model, and handle any events that are raised. You can download the Windows Script control from the Web Development section of the MSDN Downloads Center.

Stay Out of the Cache

Q: John wants to know how to force Microsoft Internet Explorer to refresh an HTML page each time the site is visited, rather than retrieving the page from the cache.

A: The following HTML META tag will prevent the HTML page from being stored in the local browser cache:

<META HTTP-EQUIV="Expires" CONTENT="-1">

This HTTP header specifies that the page will be cached on the local machine, but will immediately expire. For more information about preventing caching in Internet Explorer, please read Knowledge Base article Q234067.

For secure HTML pages, you can use the Pragma, which is a no-cache HTTP header that prevents the page from being cached on the local machine.

If you are using a proxy server, then you may need to check with the documentation to see if the proxy server respects this HTTP header.

What's Your Platform of Choice?

Q: Vikas would like to know how to detect a user's operating system from ASP.

A: Details of the user's browser and operating system are found in the user-agent string that the browser passes to the Web server with the HTTP request. The following ASP code will display the user agent string:

<%= Request.ServerVariables( "HTTP_USER_AGENT" ) %>

The user-agent string will look something like this for Microsoft Internet Explorer:

Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)

You'll need to parse this string to find the information you're looking for, as well as account for other Web browsers.

The Web Team

Mark Davis is a software design engineer on the Internet Explorer SDK team. Mark originates from England and is currently training to climb the major summits in the Northwest.

Heidi Housten works as a Consultant with Microsoft Consulting Services in Sweden after spending some time in Developer Support and MSDN. It is only a rumor that she moved there to escape the drizzle of Seattle; she really went for the traditional crayfish parties in August.

Jay Allen, a Support Engineer for the Internet Client team in Microsoft Developer Support, longs for the integration of Notepad and Emacs Lisp. What little time is not consumed by his four children is usually spent reading math books, studying Japanese and programming in Haskell.

Tosh Meston is a Web developer on the Outlook Web Access team. He comes to Microsoft with a background in physics and spends his free time reading and perfecting his three-point shot on the basketball court.


The Web Team's Greatest Hits

List of Web Team Topics


  
Show: