- Posted by ian suttle on May 14, 2008
- Filed under .NET 3.5 | .Net Framework | ASP.Net | C# 3.0
If you're like me you've often pondered why controls like CheckBoxList don't provide a way to get a collection of selected items. You've always had the option to iterate the Items collection and evaluate the Selected property, but why should that have to be done over and over again? I want a quick way to get the selected items from a CheckBoxList, ListBox, DropDownList, and RadioButtonList; all items which inherit from ListControl.
The excellent creation of extension methods makes it simple to get the selected items from a ListControl.
using System.Collections.Generic;
using System.Web.UI.WebControls;
namespace ListControlExtensions
{
public static class ListExtensions
{
public static List<ListItem> SelectedItems(this ListControl list)
{
List<ListItem> items = new List<ListItem>();
foreach (ListItem item in list.Items)
{
if (item.Selected)
{
items.Add(item);
}
}
return items;
}
}
}
Have you found an alternative to getting the selected items from a ListControl without looping through the Items collection?
.aspx&bgcolor=0099FF&cfgcolor=FFFFFF&cbgcolor=3300CC)
- Posted by Ian Suttle on March 12, 2008
- Filed under .NET 3.5 | .Net Framework | ASP.Net
Download the sample MVC project for this post
NOTE: You should have ASP.NET MVC Preview 3 or later installed to run the provided download.
So far I’m a huge fan of the ASP.NET 3.5 MVC framework which is currently released as Preview 3. Having worked with primarily Microsoft web frameworks MVC provides a refreshing perspective for what I consider an improved tier separation.
An immediate beef I have with ASP.NET MVC is defining the routes in the Global.asax.cs… I can’t change routes without recompiling and deploying code. I thought it better to configure routes in a more dynamic environment, specifically the database, which is what this post will provide an example for.
I’ve broken the implementation into three main pieces:
- Database schema for storing routes and route default parameters
- LINQ to SQL model for accessing the routes
- Class for handling route routines
Database Schema – “Mvc”
MvcRoutes Table
routeID – the primary key for a route.
routeName – simply for identification purposes.
This isn’t used in the app.
routePattern – the URL structure of the route.
routeIsActive – turn your route on or off.
routeOrder – routes are executed on a first match basis.
It may be important to order your routes so a more specific pattern is caught first before a more general pattern catches it… similar logic to exception handling.
MvcRoutes Sample Data
MvcRouteParams Table
paramID – the primary key and identifier for the route parameter.
routeID – foreign key to the MvcRoutes.RouteID value.
paramKey – route defaults are stored as a Dictionary.
This is the key for a default.
paramValue – the default value for the paramKey.
paramConstraint – a regular expression value specifying allowed values for the parameter
paramDataType – I’m actually not using this right now but it feels right to analyze the expected data type in a more complete sample… or something
J.
MvcRouteParams Sample Data
LINQ to SQL Model
The model is super simple… connect to your MVC routing database and drag the MvcRoutes and MvcRouteParams tables to a new LINQ to SQL class.

Now we can do some simple queries.
Route Helper Class – Routing.cs
The Routing.cs class provides a handful of methods for working with the RouteTable and interaction with the database.
In my project I included a new controller to handle resetting and displaying the routes which utilizes some of these additional methods.
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
using MvcAltRouting.Models;
namespace MvcAltRouting
{
internal class Routing
{
public static RouteCollection Routes
{
get { return RouteTable.Routes; }
}
/// <summary>
/// Gets the configured routes.
/// </summary>
/// <returns></returns>
public static List<MvcRoute> GetConfiguredRoutes()
{
MvcRoutesDataContext db = new MvcRoutesDataContext();
List<MvcRoute> dbRoutes = (from b in db.MvcRoutes
where b.routeIsActive == true
orderby b.routeOrder
select b).ToList();
return dbRoutes;
}
/// <summary>
/// Clears current and readds application routes.
/// </summary>
public static void ResetAppRoutes()
{
Routes.Clear();
SetAppRoutes();
}
/// <summary>
/// Sets the application routes.
/// </summary>
public static void SetAppRoutes()
{
List<MvcRoute> configuredRoutes = GetConfiguredRoutes();
SetAppRoutes(configuredRoutes);
}
/// <summary>
/// Sets the application routes.
/// </summary>
/// <param name="configuredRoutes">Collection of routes to add to the application's routing table.</param>
public static void SetAppRoutes(List<MvcRoute> configuredRoutes)
{
//add the routes to the RouteCollection
foreach (MvcRoute route in configuredRoutes)
{
//create RouteValueDictionary objects to add the default values and contraints to the route
RouteValueDictionary constraints = new RouteValueDictionary();
RouteValueDictionary routeVals = new RouteValueDictionary();
foreach (MvcRouteParam param in route.MvcRouteParams)
{
routeVals.Add(param.paramKey, param.paramValue);
if (!string.IsNullOrEmpty(param.paramConstraint))
{
constraints.Add(param.paramKey, param.paramConstraint);
}
}
//create the route using the defaults set above
RouteTable.Routes.Add(new Route(route.routePattern, new MvcRouteHandler())
{
Defaults = routeVals,
Constraints = constraints
});
}
}
}
}
Global.asax.cs
Lastly change the Application_Start method to use the new Routing class to populate the application routes, removing the need for the routes defined within the Global.asax.cs.
using System;
namespace MvcAltRouting
{
public class GlobalApplication : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
Routing.SetAppRoutes();
}
}
}
That’s It!
Start up the provided project and navigate to the “Display Routes” link. You can see the routes have been set from the database values we saw from above!
Have you been working with the ASP.NET MVC framework? If so have you moved the route configuration out of the Global.asax.cs or are you happy with the default configuration for editing routes?
Download the sample MVC project for this post
NOTE: You should have ASP.NET MVC Preview 3 or later installed to run the provided download.
- Posted by Ian Suttle on February 28, 2008
- Filed under .Net Framework | ASP.Net
You know the story... guy writes code... guy wants to debug code... guy wants to trace messages to understand what's happenin'... guy uses Trace.Write(). Now the part that smacked me in the tush with a wet towel this late night was the different between ASP.NET Trace.Write() and System.Diagnostics.Trace.Write(). In a default setup of an ASP.NET web app System.Diagnostics.Trace.Write() doesn't output to the Page Trace or the Trace.axd. It goes to the "standard output" whatever that might be (assumed to be a console window). This becomes problematic when you want to trace from a business object, data layer, server control, etc as these items have no HTTP Context which implements Trace as a System.Web.TraceContext property.
Alas my faithful reader you can add a trace listener to your web.config or programmatically to have all Trace.Write() methods output to the ASP.NET trace. There are a variety of trace listeners one can add including the WebPageTraceListener which is what satisfies our case here. Here's how you CaN GeTZ TrACe (my best LOLCats impression).
Trace Listener via config
<system.diagnostics>
<trace>
<listeners>
<add name="WebPageTraceListener"
type="System.Web.WebPageTraceListener, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
</listeners>
</trace>
</system.diagnostics>
Trace Listener programmatically from the Global.asax
void Application_Start(Object sender, EventArgs e)
{
WebPageTraceListener gbTraceListener = new WebPageTraceListener();
System.Diagnostics.Trace.Listeners.Add(gbTraceListener);
}
Keep reading!
For your System.Diagnostics.Trace statements to write to the trace your project MUST be compiled with the TRACE compiler switch. In your project properties the Build tab has this defined as "Define TRACE constant".
Now for the reverse... you want to route all ASP.NET trace to the standard system tracing output. Modify your config to include an appropriate trace listener such as TextWriterTraceListener (note: WebPageTraceListener probably isn't appropriate for this one:) and update your web.config to have the trace element's writeToDiagnostics set to "true".
<system.web>
<trace writeToDiagnosticsTrace="true"/>
</system.web>
- Posted by Ian Suttle on January 6, 2008
- Filed under .NET 3.5 | C# 3.0 | ASP.Net | JSON | .Net Framework
Download the sample code for this post.
In October of 2007 The Gu wrote a ToJSON extension method using the JavaScriptSerializer class. Since that time the JavaScriptSerializer class has been marked obsolete in favor of DataContractJsonSerializer. In this post we’ll write an updated ToJSON method to satisfy every JSON extension method fantasy you have. Quick agenda… the goal is to take a .Net object and convert it in to JSON text which would likely be consumed be a remote client. Let’s start with the object to convert… an Address class.
using System.Runtime.Serialization;
...
[DataContract(Name = "Address", Namespace = "")]
public class PersonAddress
{
[DataMember(Name = "City", Order = 1)]
public string City { get; set; }
[DataMember(Name = "Country", Order = 2)]
public string Country { get; set; }
[DataMember(Name = "PostalCode", Order = 3)]
public string PostalCode { get; set; }
[DataMember(Name = "State", Order = 4)]
public string State { get; set; }
[DataMember(Name = "Street", Order = 5)]
public string Street { get; set; }
}
DataContract and DataMember attributes provide information to the serialization process of how to structure the data. It is possible to use the more familiar System.Serializable attribute however the output isn’t nearly as friendly. More on that later.
The constructor for DataContractJsonSerializer we’re using requires knowledge of the Type of object being serialized to JSON. For the purposes of this method we’re likely to not know which Type is to be serialized. Quite often it will be a custom type. We need our extension method to be more generic… I love .Net… Here’s the method suitable for such a task:
using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;
...
public static string ToJSON<T>(this T obj)
{
MemoryStream stream = new MemoryStream();
try
{
//serialize data to a stream, then to a JSON string
DataContractJsonSerializer jsSerializer = new DataContractJsonSerializer(typeof(T));
jsSerializer.WriteObject(stream, obj);
return Encoding.UTF8.GetString(stream.ToArray());
}
finally
{
stream.Close();
stream.Dispose();
}
}
Be sure to reference System.Runtime.Serialization and System.ServiceModel.Web.
Let’s dissect a bit. DataContractJsonSerializer needs to know the object type. In our case it’s generic, so we’ll let typeof(T) figure it out for us. The serializer WriteObject method wants a stream to write the serialization output to for which we’re using a MemoryStream; an easy object for retrieving text from. And that’s what we do… convert the stream’s byte array into a string; our JSON string.
Let’s run this pup using the following code:
PersonAddress address = new PersonAddress();
address.City = "Newport Beach";
address.Country = "US";
address.PostalCode = "92626";
address.State = "CA";
address.Street = "123 Candyland Way";
string json = address.ToJSON();
Response.Write(json);
And the results:
{"City":"Newport Beach","Country":"US","PostalCode":"92626","State":"CA","Street":"123 Candyland Way"}
Earlier I mentioned if you used the [Serializable] attribute instead of the [DataContract] attribute your solution would compile and function however your results would come out a bit different. Here’s the output when using Serializable:
<{"<City>k__BackingField":"Newport Beach","<Country>k__BackingField":"US","<PostalCode>k__BackingField":"92626","<State>k__BackingField":"CA","<Street>k__BackingField":"123 Candyland Way"}>
As you can see this is far less friendly to work with from a JSON consumer perspective… Make it easier on yourself and stick with DataContract in this case.
I hope this helps in your JSON endeavors. Let me know if you have any questions of problems!
Download the sample code for this post.
- Posted by Ian Suttle on December 10, 2007
- Filed under .NET 3.5 | ASP.Net | .Net Framework
It's late, I'm finally catching Cinderella Man, and I'm heading for vacation this week... of course none of that is interesting to you, but the
CTP release of the ASP.NET 3.5 Extensions CTP Preview probably is. The MVC framework seems to have garnered the most blog entries as of late. I'll be sure to check it out when I return, unless the wife falls asleep early in the hotel and I can sneak the laptop out that is!
- Posted by Ian Suttle on November 29, 2007
- Filed under .NET 3.5 | ASP.Net | AJAX | .Net Framework
Scott Guthrie has announced ASP.NET 3.5 Extensions public preview will be released next week sometime. This is really a big hitter people have been clammoring about... MVC, Entity Framework, Astoria, and more!
After stumbling upon it on a MS labs site I've been following Astoria for a few months now. An Astoria overview describes its purpose better than I can:
"The goal of Astoria is to facilitate the creation of flexible data services that are naturally integrated with the web. As such, Astoria uses URIs to point to pieces of data and simple, well-known formats to represent that data, such as JSON and plain XML. This results in the data service being surfaced to the web as a REST-style resource collection that is addressable with URIs and that agents can interact with using the usual HTTP verbs such as GET, POST or DELETE."
... using a REST-style request to get XML or JSON formatted data in a .NET Framework sanctioned way is just what the doctor ordered. Here's a sample request and output:
Request: http://myserver/data.svc/Customers[ALFKI]
XML:
DataService xml:base="http://myserver/data.svc">
<Customers>
<Customer uri="Customers[ALFKI]">
<CustomerID>ALFKI</CustomerID>
<CompanyName>Alfreds Futterkiste</CompanyName>
<ContactName>Maria Anders</ContactName>
<ContactTitle>Sales Representative</ContactTitle>
<Address>Obere Str. 57</Address>
<City>Berlin</City>
<Region />
<PostalCode>12209</PostalCode>
<Country>Germany</Country>
<Phone>030-0074321</Phone>
<Fax>030-0076545</Fax>
<Orders href="Customers[ALFKI]/Orders" />
</Customer>
</Customers>
</DataService>
JSON:
[
{
__metadata: {
Type: "Customer",
Base: "http://myserver/data.svc",
Uri: "Customers[ALFKI]"
},
CustomerID: "ALFKI",
CompanyName: "Alfreds Futterkiste",
ContactName: "Maria Anders",
ContactTitle: "Sales Representative",
Address: "Obere Str. 57",
City: "Berlin",
Region: null,
PostalCode: "12209",
Country: "Germany",
Phone: "030-0074321",
Fax: "030-0076545",
Orders: {
__metadata: {
Uri: "Customers[ALFKI]/Orders"
}
}
}
]
- Posted by Ian Suttle on July 20, 2007
- Filed under ASP.Net | AJAX
| [Download Project Files] [View Sample] |
| Introduction |
| You’ve heard of it. It is the latest buzz term for web programmers these days. AJAX is an acronym that stands for Asynchronous JavaScript and XML. AJAX gains its popularity by allowing data on a page to be dynamically updated without having to make the browser reload the page. I will describe more about how AJAX works, and then go into some sample code to try out.
This term has been made famous by some of Google’s latest web apps. At the top of this list is Google Suggest, which gives you suggestions (go figure) on what you are searching for as you type based on popularity of the search term. If you aren't familiar with Google Suggest, check it out.
There is more going on here than just AJAX however. The actual drop down list is an additional DHTML piece that we will not be covering in this article.
Is AJAX a new technology? Yes and no would both be incorrect answers. A proper answer would be a new application of current technologies, with emphasis on plurality. This list of technologies includes standard HTML controls, JavaScript, an XML HTTP component, and XML data structures.
|
| |
| The Steps |
The following is an outline of the sequence of events when using AJAX:
- Web page is rendered
- A trigger executes a JavaScript function call (i.e. onKeyUp, button click, setTimeout, page load, etc.)
- JavaScript instantiates an XML HTTP object
- XML HTTP object calls a remote page
- Remote Page transforms an XML structure using XSLT and returns the result
- JavaScript accepts the results and applies it to the page
- Tada! No page reload, just magical dynamic data
|
| Get To It Already! |
| These steps are great, but without a sample application it is tough to envision. Being the responsible author that I am, I have of course included a sample of the steps discussed.
I think that it is important to discuss the XML HTTP object to gain a better understanding of what is going on here. XML HTTP allows code to connect to a remote location and perform GET and POST requests asynchronously. This means that we can connect to a remote host, send a request, and continue on with additional logic. When the remote host returns a response, a function designated to handle the return event is able to accept the data and make decisions based on what was received. The data passed to and from the remote host does not have to be in an XML format. XML is simply a well-formatted string. I have found on multiple occassions that passing a string that is not in an XML format is most appropriate for the given task. The XML HTTP object will not be compatible on all browsers or operating systems. Being that this is a client side function the client machine is responsible for the implementation as opposed to the server.
I have been utilizing this object since ASP 3.0 for making remote calls from a web page. Imagine the power here. Data and processes are accessed on disparate locations without the client ever having to leave the comfort of the domain or page that he/she is on.
|
| |
| The JavaScript |
| The JavaScript is the real meat and potatoes in AJAX. It handles the change detection, data request and receipt, and placing the data on the page.
Be sure to update the requestURL variable with the path that you will be accessing the aspx file from.
The following is the JavaScript code used:
|
<script>
var xmlHttp;
var requestURL = 'http://localhost/misctest/getusernames.aspx?q=';
var is_ie = (navigator.userAgent.indexOf('MSIE') >= 0) ? 1 : 0;
var is_ie5 = (navigator.appVersion.indexOf("MSIE 5.5")!=-1) ? 1 : 0;
var is_opera = ((navigator.userAgent.indexOf("Opera6")!=-1)||(navigator.userAgent.indexOf("Opera/6")!=-1)) ? 1 : 0;
//netscape, safari, mozilla behave the same???
var is_netscape = (navigator.userAgent.indexOf('Netscape') >= 0) ? 1 : 0;
function show_data(strName){
if (strName.length > 0){
//Append the name to search for to the requestURL
var url = requestURL + strName;
//Create the xmlHttp object to use in the request
//stateChangeHandler will fire when the state has changed, i.e. data is received back
// This is non-blocking (asynchronous)
xmlHttp = GetXmlHttpObject(stateChangeHandler);
//Send the xmlHttp get to the specified url
xmlHttp_Get(xmlHttp, url);
}
else {
//Textbox blanked out, clear the results
document.getElementById('nameList').innerHTML = '';
}
}
//stateChangeHandler will fire when the state has changed, i.e. data is received back
// This is non-blocking (asynchronous)
function stateChangeHandler()
{
//readyState of 4 or 'complete' represents that data has been returned
if (xmlHttp.readyState == 4 || xmlHttp.readyState == 'complete'){
//Gather the results from the callback
var str = xmlHttp.responseText;
//Populate the innerHTML of the div with the results
document.getElementById('nameList').innerHTML = str;
}
}
// XMLHttp send GET request
function xmlHttp_Get(xmlhttp, url) {
//Getting a permissions error here? Check the url string to
// ensure it is accurate (defined above)
xmlhttp.open('GET', url, true);
xmlhttp.send(null);
}
function GetXmlHttpObject(handler) {
var objXmlHttp = null; //Holds the local xmlHTTP object instance
//Depending on the browser, try to create the xmlHttp object
if (is_ie){
//The object to create depends on version of IE
//If it isn't ie5, then default to the Msxml2.XMLHTTP object
var strObjName = (is_ie5) ? 'Microsoft.XMLHTTP' : 'Msxml2.XMLHTTP';
//Attempt to create the object
try{
objXmlHttp = new ActiveXObject(strObjName);
objXmlHttp.onreadystatechange = handler;
}
catch(e){
//Object creation errored
alert('IE detected, but object could not be created. Verify that active scripting and activeX controls are enabled');
return;
}
}
else if (is_opera){
//Opera has some issues with xmlHttp object functionality
alert('Opera detected. The page may not behave as expected.');
return;
}
else{
// Mozilla | Netscape | Safari
objXmlHttp = new XMLHttpRequest();
objXmlHttp.onload = handler;
objXmlHttp.onerror = handler;
}
//Return the instantiated object
return objXmlHttp;
}
function UseValue(strVal){
document.frmStuff.txtName.value = strVal;
}
</script> |
| |
| The Client Page (HTML) |
| The client page, excluding the JavaScript, is about as basic as it gets. A simple form with an onKeyUp event in a text box is all that is really required. I included a DIV tag to display the resulting data. The HTML has been provided: |
<html>
<head>
<title>Ian Suttle's AJAX Sample</title>
<style>
body, input {font-family: arial; font-size: 12px;}
</style>
<!-- Insert JavaScript Here -->
</head>
<body>
<form name="frmStuff" id="Form1">
<table border="0" cellpadding="4" cellspacing="0" id="Table2">
<tr>
<td width="100">Name:</td>
<td><input type="text" name="txtName" id="txtName" autocomplete="off" onkeyup="show_data(this.value);"></td>
</tr>
<tr>
<td width="100" valign="top">Suggestions:</td>
<td>
<div id="nameList"></div>
</td>
</tr>
</table>
</form>
</body>
</html> |
| |
| The Remote Page |
| When a request is made from the JavaScript, it makes contact with the “remote page.” A query string variable, “q”, is included representing the data that was keyed by the user. I am going to construct an XML string, only including those elements that are valid based on the search term. To relieve the client side script from doing any formatting, I have applied an XSL transformation on the XML data. Formatting the XML on the server side is a much better solution than formatting within the JavaScript on the client side. A major point to recognize is that certain browsers will not support XML and XSL objects. Assuming you want your data to always be formatted the same, stick with the server side logic.
Create a page called GetUsernames.aspx. Be sure to remove ALL HTML from the page except the required @Page line. The code-behind will create the output to display on the page. Add the following code to the code-behind file:
|
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Xml;
using System.Xml.Xsl;
using System.Xml.XPath;
namespace MiscTest
{
/// <summary>
/// Summary description for GetUsernames.
/// </summary>
public class GetUsernames : System.Web.UI.Page
{
private void Page_Load(object sender, System.EventArgs e)
{
GetUsernameList();
}
#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
private void GetUsernameList()
{
//Get the request query
string strQuery = Request["q"].ToString();
//Create the XML-like string to be sent back to the request
string strXmlNames = "";
//An arbitrary array of names that will be written to an XML Document.
string[] arrStrNames = new string[5]{"Ian Suttle", "John Doe", "Alex Wright", "Albert Einstein", "Sierra Tracy"};
//Loop through the names, creating a psuedo XML element for each
foreach(string strName in arrStrNames)
{
//If the request matches the beginning of a name, then add it to the string
// otherwise it shouldn't be a valid match
if (strName.Length >= strQuery.Length && strName.ToLower().Substring(0, strQuery.Length) == strQuery.ToLower())
strXmlNames += "<user><name>" + strName + "</name></user>";
}
//Prepend and append the parent element
strXmlNames = "<?xml version=\"1.0\" ?><users>" + strXmlNames + "</users>";
//Create an XmlDocument object to store the XML string that we created
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(strXmlNames);
//Create a navigator object to use in the XSL transformation
XPathNavigator xPathNav = xDoc.CreateNavigator();
//Create the XSLTransform object
XslTransform xslt = new XslTransform();
xslt.Load(Server.MapPath("Names.xslt"));
//Do the transformation and send the results out to the Response object's output stream
xslt.Transform(xPathNav, null, Response.OutputStream);
}
}
} |
| |
| The XSL Stylesheet |
| The XSL document is used to format the XML data to a defined presentation. The code to do the transformation is included here, although great detail on how this works is not the topic of this article. Create a new XSL document called Names.xslt and paste the following code into it: |
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="users/user">
<xsl:value-of select="name" /><br />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet> |
| |
| Conclusion |
| So once again, AJAX is nothing more than an application of technologies that have been around for a number of years. ASP.NET isn’t the wizard behind this curtain. There is nothing here that can’t be done in ASP Classic and JavaScript. Not that given a choice one would revert to ASP Classic :). Additionally, ASP.NET 2.0 will have a completely different approach to making remote calls from a page.
The possible implications of AJAX are very impressive. Updating notices, checking email, monitoring processes, etc… The limits of application are virtually up to the imagination of the developer.
|
| |
| You can read more about Ian Suttle at http://www.iansuttle.com |
- Posted by ian suttle on July 20, 2007
- Filed under ASP.Net
| |
| I was discussing Session State interoperability between ASP Classic and ASP.NET with one of my developers. Obviously Microsoft did not provide us, the developer community, with a means of making our ASP.NET applications backwards compatible with ASP Classic. I had come up with a couple of ways to achieve this goal. In running a couple of searches on the subject to see how others had conquered the beast, I came across an article by Peter Bromberg entitled “Transfer Session Variables from Classic ASP to ASP.NET” that described in detail exactly how one of my ideas would work. In reading his article, like Peter, I was amused to see that so many people had declared this task “impossible!” I am writing this example to give you another possibility for the impossible. This article differs from Peter’s in that I will explain how to do the Session State transfer without involving the client, offering an additional level of security (yes, it’s important) and automation. To summarize, the logic would work as follows: - ASP.NET function calls an ASP Classic page
- ASP Classic page dynamically builds an XML structure to represent the ASP Classic Session State contents
- ASP.NET loads the XML output, and loops through each element, creating ASP.NET Session variables
|
| |
| The Code |
| The ASP Classic code: |
| <title>ASPClassicSession.asp</title> <% ' ASPClassicSession.asp will create the XML structure to be gobbled up by ASP.NET 'set the content type to be of type text/xml response.ContentType = "text/xml" 'begin writing the xml structure response.Write "<?xml version=""1.0"" ?>" response.Write "<session>" 'loop through the session contents, writing each item as if it were an XML element for each item in session.Contents 'If the session contents have a value, we need to URLEncode it to avoid ' illegal character sequences. i.e. " & " causes an invalid whitespace ' error if len(session.Contents(item)) > 0 then encItem = server.URLEncode(session.Contents(item)) else encItem = session.Contents(item) end if response.Write "<" & item & ">" & encItem & "</" & item & ">" next 'end the xml structure response.Write "</session>" %> |
| |
| The preceding page would output a page similar to the following: |
<?xml version="1.0" ?> <session> <UserID>87248</UserID> <Email>ian@iansuttle.com</Email> <UserName>isuttle</UserName> </session> |
| |
| The ASP.NET (C#) code behind code: |
| using System.Xml; using System.Text; using System.Net; using System.Net.Sockets; ... private string RetrieveXML(string server, string page, System.Web.HttpRequest request) { /********************************************** * Send the request **********************************************/ //create socket IPHostEntry ipHostInfo = Dns.Resolve(server); IPAddress ipAddress = ipHostInfo.AddressList[0]; IPEndPoint ipe = new IPEndPoint(ipAddress, 80); Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //connect socket to server socket.Connect(ipe); //Create the request to send to the server string strRequest = "GET /" + page + " HTTP/1.1\r\n" + "Host: " + server + "\r\n" + "Connection: Close\r\n" + "Cookie: " + request.Headers["Cookie"] + "\r\n" + "User-Agent: " + request.Headers["User-Agent"] + "\r\n\r\n"; //Convert send data to bytes Byte[] bytesSend = Encoding.ASCII.GetBytes(strRequest); //Send the data to the server socket.Send(bytesSend, bytesSend.Length, 0); /********************************************** * Receive the return data **********************************************/ //Declare variables for data receipt byte[] bytes = new byte[256]; int nBytes = 0; string receive = ""; string xml = ""; // The following will block until the page is transmitted. do { nBytes = socket.Receive(bytes, bytes.Length, 0); receive += Encoding.ASCII.GetString(bytes, 0, nBytes); } while (nBytes > 0); //We have the page data, but it includes the headers // Retrieve XML data from page response xml = receive.Substring(receive.IndexOf("<?xml"), receive.Length - receive.IndexOf("<?xml")); //Cleanup the socket socket.Shutdown(SocketShutdown.Both); socket.Close(); //Return the data return xml; } public void TransferSession(System.Web.HttpRequest request, System.Web.SessionState.HttpSessionState session) { //Clear the session contents to have a clean session - Optional session.RemoveAll(); //Define the URL and page to load the Session XML from string XMLServer = request.ServerVariables["SERVER_NAME"]; string XMLPage = "aspclassicsession.asp"; //Define an XMLDocument to allow easy XML tree navigation XmlDocument doc = new XmlDocument(); //Load the document from the reader doc.LoadXml(RetrieveXML(XMLServer, XMLPage, request)); //Loop through the Session element's child nodes and set //each Session object foreach(XmlNode node in doc.FirstChild.NextSibling.ChildNodes) { session[node.Name.ToString()] = System.Web.HttpUtility.UrlDecode(node.InnerText.ToString()); } } |
| |
| On execution of the Page_Load function, the TransferSession function gets called, which consumes the XML data from the ASPClassicSession.asp page, creates the Session contents in ASP.NET, and redirects the user to the specified destination page. Gathering the XML data is not as easy as it sounds. Since the call from the client browser is going to the server, the server is then making the request for the ASP Classic page to generate the XML. The ASP.NET application does not share your ASP Classic Session State. The relationship between your browser and IIS is dependent on specific headers being passed back and forth. In this example we have ASP.NET gather the headers from the client request, and create our own socket connection to the server using our customized headers to create the illusion that we are the client browser making the request. ASP Classic accepts the request and gladly returns the desired results. With all applications, you must know your data. The current implementation of this solution will not handle objects or arrays. It would not be difficult to customize the solution to work with specific objects if you know how to detect and rebuild them. |
| |
|