26 November 2007

String extension methods

C# 3.0 features the new extension methods. This basically seems to allow you to add your own methods to sealed classes. That seems a bit weird, and Microsoft gives the following advice in the Visual Studio 2008 help
In general, we recommend that you implement extension methods sparingly and only when you have to. Whenever possible, client code that must extend an existing type should do so by creating a new type derived from the existing type.
Full article Then try to make a List<> and bring up the Intellisense box. You will see 40-50 new methods, and by now you will guess they are all extension methods. Practiced what thy preached, Microsoft! Anyway, if Microsoft makes a lot of extension methods, so can we. We can now finally get rid of our 'utility' classes and make something like this:
namespace LocalJoost.Utilities
{
    public static class StringExtensions
    {
        public static string Reverse(this string s)
        {
            StringBuilder builder = new StringBuilder();
            for (int i = s.Length - 1; i >= 0; i--)
            {
                builder.Append(s.Substring(i, 1));
            }
            return builder.ToString();
        }
    }
}
And then we can do something like this:
string test = "Hello hello";
string reversed = test.Reverse();
The string "reversed" will now contain the string "olleh olleH". Notice the "this" keyword before the string s in method Reverse of class StringExtensions - that is the thing that defines Reverse as an extension method of string. The only further requirements are that both class and method implementing the extension are static.

19 November 2007

Creating a simple proxy

Sometimes you want to give web browsers access to web resources that are best not opened indiscriminatly to the world. A good example are WMS servers: you want the users to be able to see maps, but putting a WMS server directly online is like putting a read-only sql client online. The solution is put the web resource on a server that is not accessible by the internet, but is accessible by the web application - i.e. the limited resource is put inside the DMZ, and create some kind of proxy page that streams data from the server inside the DMZ to the outside world - after doing some validations, of course. A setup for a proxy page (that only does the streaming, not the validation) may be as follows: Create an aspx page MyProxyPage.aspx and add the following code:
void ProcessProxyRequest( string request, HttpResponse response)
 {
   HttpWebRequest webRequest = HttpWebRequest.Create(request) as HttpWebRequest;
 
   // Important! Keeps the request from blocking after the first time!
   webRequest.KeepAlive = false;
   webRequest.Credentials = CredentialCache.DefaultCredentials;
   using (HttpWebResponse backendResponse = (HttpWebResponse)webRequest.GetResponse())
   {
     Stream receiveStream = backendResponse.GetResponseStream();
 
     // Clear whatever is already sent
     response.Clear();
     response.ClearContent();
     response.ClearHeaders();
 
     // Copy headers
     // Check if header contains a contenth-lenght since IE
     // goes bananas if this is missing
     bool contentLenghtFound = false;
     foreach (string header in backendResponse.Headers)
     {
       if (string.Compare(header, "CONTENT-LENGTH", true) == 0) 
       { 
         contentLenghtFound = true;
       }
       response.AppendHeader(header, backendResponse.Headers[header]);
     }
     // Copy content           
     byte[] buff = new byte[1024];
     long length = 0;
     int bytes;
     while ((bytes = receiveStream.Read(buff, 0, 1024)) > 0)
     {
       length += bytes;
       response.OutputStream.Write(buff, 0, bytes);
     }
 
     // Manually add content-lenght header to satisfy IE
     if (!contentLenghtFound) 
     {
       response.AppendHeader("Content-Length", 
         length.ToString());
     }
 
     receiveStream.Close();
     backendResponse.Close();
     response.Flush();
     response.Close();
   }
 }
Now of course, in the Page_Load you have to do something first:
protected void Page_Load(object sender, EventArgs e)
{
   string requestInsideDMZ = "http://somewhereinyourdmz/somepage.aspx";
   ProcessProxyRequest( requestInsideDMZ, 
                        Response);
}
If the user accesses MyProxyPage.aspx he gets the contents of http://somewhereinyourdmz/somepage.aspx instead.

02 November 2007

Expanding the length of JSON data returned from ASP.NET web services

A very small example this time, but one that can be very time consuming to find out. The basic example I described earlier works fine, but just try to send back a larger string. Suppose, you change the Hello World Method into this:
[WebMethod]
public string HelloWorld( string ToSomeone)
{
    StringBuilder b = new StringBuilder();
    while (b.Length < 50000)
    {
       b.AppendFormat("Hello World {0}. ", ToSomeone);
    }

    return b.ToString();
}
You get al LOT of "Hello Worlds". Now change 50000 into 200000... you get no error message, but in fact, you get no response at all. To solve this, you need to delve into your web.config. Make sure the JSON configuration declaration at the top looks like this. I highlighted the important part:
<configSections>
  <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
    <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
        <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="MachineToApplication"/>
      <sectionGroup name="webServices" type="System.Web.Configuration.ScriptingWebServicesSectionGroup, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
        <section name="jsonSerialization" type="System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="Everywhere" />
        <section name="profileService" type="System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="MachineToApplication" />
        <section name="authenticationService" type="System.Web.Configuration.ScriptingAuthenticationServiceSection, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="MachineToApplication" />
      </sectionGroup>
    </sectionGroup>
  </sectionGroup>
</configSections>
This may look a bit intimidating, but just copy it. In the system.web part you can define the following tag:
<system.web.extensions>
  <scripting>
    <webServices>
      <jsonSerialization maxJsonLength="999999999"/>
    </webServices>
  </scripting>
</system.web.extensions>
And there you go. The number 999999999 is the absolute max value - so if you send stuff to your client longer than that, you have to think of something else. But for the mapping applications I made on Google Maps this works fine. Bear in mind that sending VERY large portions of data can degrade your user experience considerably, although the asynchronous nature of the ASP.NET JSON service might diminish that problem. By the way, in the latest templates for ASP.NET Ajax enabled web sites the whole configuration declaration is already included, as well as the declaration for the json serialization - but it is commented out. It looks something like this.
<!-- Uncomment this line to customize maxJsonLength and add a custom converter -->
<!--
<jsonSerialization maxJsonLength="500">
  <converters>
    <add name="ConvertMe" type="Acme.SubAcme.ConvertMeTypeConverter"/>
  </converters>
</jsonSerialization>
-->
You have to delete the part that I printed in italics, because that is just an example that does not point to an actual class

29 October 2007

The right way of accessing Master page properties from a child page

Master pages are a superbe idea. But sometimes you need to access Master Page properties from the child page. Suppose, for instance, you have a button on the Master Page with a text that you want to be able to set:
<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="MyDemoMaster.master.cs" Inherits="MasterPageDemo.MyDemoMaster" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:button id="btnWithText" runat="server" text="Button">
         <asp:contentplaceholder id="ContentPlaceHolder1" runat="server">
        </asp:ContentPlaceHolder>
    </div>
    </form>
</body>
</html>
In the Master Page, you can create a property like this:
public string ButtonText
{
   get { return this.btnWithText.Text;  }
   set { this.btnWithText.Text  = value; }
}
Unfortunately, if you try to find the property "Master.ButtonText" in the childpage, it won't be available since the "Master" property is of type System.Web.UI.Page, and that does not contain properties of your derived class "MyDemoMaster" This, of course, can be solved by changing the Page_Load of the child page like this:
protected void Page_Load(object sender, EventArgs e)
{
  MyDemoMaster m = Master as MyDemoMaster;
  m.ButtonText = "My button text";
}
Congratulations. Your button shows the right text. And you have succeeded into locking your child page to a single master. Your web page will not run when you try to use another master page. Go back to programming class 101 and you don't get any cookies today ;-) The right solution is: create an interface like this:
namespace MasterPageDemo
{
    public interface IButtonText
    {
        string ButtonText { get;set;}
    }
}
Open the code behind file of your Master Page and let it implement the interface:
public partial class MyDemoMaster : System.Web.UI.MasterPage, IButtonText
{
    protected void Page_Load(object sender, EventArgs e)
    {
    }

    public string ButtonText
    {
        get { return this.btnWithText.Text;  }
        set { this.btnWithText.Text  = value; }
    }
}
And then you make the Page_Load of the child page like this
protected void Page_Load(object sender, EventArgs e)
{
  IButtonText m = Master as IButtonText ;
  if( m != null ) m.ButtonText = "My button text";
}
Now the Master Page and Child Page are no longer coupled by name. Any Master Page implementing the IButtonText interface may be used as a Master Page for your child. Checking if it can be casted by testing m != null is a nice encore.

23 October 2007

Calling ASMX web services directly from javascript

The AJAX hype is all around us, and Microsoft provided us with the very neat ASP.NET AJAX extensions and the control toolkit to make things easier. The UpdatePanel and its nephews enjoy a lot of time in the spotlight. The workhorse in the background that makes this all possible can also be used 'raw' - that is, you can call web services directly from javascript utilizing the standard structure of ASP.NET Ajax. It is a very efficient way to transfer data to and from the browser because the server transforms it into JSON - so no large chunks of verbose XML are send through the wire and you don't have to write elaborate pieces of XML-parsing javascript.
Follow the steps below to get things up and running.

This example assumes you have ASP.NET Ajax and de Web Applications already installed.

1. Create a new project of type "ASP.NET AJAX-Enabled Web Application"
This will create a new Web Application with a Default.aspx that already has a script manager on in, and a preconfigured web.config. Call this project WebServiceDemo

2. Add a web service
Right-click on your web application, click Add/New Item and then Web Service. Call this web service "DemoService".

3. Make a web service callable from script
Open code file "Default.aspx.cs". Notice your class "DemoService" sits in a namespace "WebServiceDemo". You will need this knowlegde later.
Add to the top:

using System.Web.Script.Services;
decorate the class with the attribute [ScriptService] and modify the standard hello world method so it looks like this:

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
[ScriptService]
public class DemoService : System.Web.Services.WebService
{
[WebMethod]
public string HelloWorld( string ToSomeone )
{
return "Hello World" + ToSomeone;
}
}
4. Register the service on the page where you want to call it from
Open Default.aspx in design view and select the ScriptManager1
Select the "Services" property, and click the button that appears
Click "Add", and enter "DemoService.asmx" for the path property
Click OK. The result should look like this:
<asp:ScriptManager ID="ScriptManager1" runat="server" >
  <Services>
    <asp:ServiceReference Path="DemoService.asmx" />
  </Services>
</asp:ScriptManager>
5. Create a client side script to perform the call
Open Default.aspx in source view and enter just before the <head> tag the following code:

<script type="text/javascript">
function CallService()
{
WebServiceDemo.DemoService.HelloWorld( "Yourself",
Callback );
}

function Callback( result )
{
var outDiv = document.getElementById("outputDiv");
outDiv.innerText = result;
}
</script>
6. Create a button to start the web service calling function
Drag a button onto the form
Set the property "OnClientClick" to "CallService();return false;"

7. Create div for the output data
Drag a div (from the HTML tab) onto the form.
Set its id to "outputDiv";

If you did everything correctly, the text "Hello World Yourself" should appear beneath your button, without a visible postback.

Notice the following things:

  • You always need the fully qualified name of the class to call the webservice: WebServiceDemo.DemoService.HelloWorld
  • Calling a webservice from javascript always needs two methods: one to make the actual call, and one to receive the results
  • The callback method is added as an extra parameter to the web service method parameters. In C#, the method has one parameter, so in javascript two.
This sample gives a nice under-the-hood sample of how ASP.NET Ajax transfers data to and from the browser.

Complete code downloadable here.