Tuesday, April 22, 2008

XHTML

Dorian: Never really looked at XHTML before. All I know was you had to close your tags. I learned a little more today.

All tags have to be lower case e.g. <img .. instead of <IMG.

I had to convert some HTML 4.01 markup to XHTML 1.0. Here is what I did.

I used http://validator.w3.org/ to check my code. It will take a URL, a file upload or you can cut and paste your source in. I just cut and pasted my source as the site wasn’t public and it was derived markup from ASP.Net 3.5.

At first seeing 50 errors was daunting but it wasn’t hard to trim down to perfection.

The bulk of the issues it found were unclosed <br> tags which should be <br/> and unclosed <img …> tags. Every upper case <IMG> tag generated lots of errors because it also complained about each attribute because upper case IMG is an unknown tag. Making the <IMG> tags lower case stripped off heaps of errors.

The other big error count creator is no quotes on attributes. E.g. <table cellspacing=0> instead of <table cellspacing=”0”>

The one that took me a little more time to absorb was the markup like this:

<div>

<span>

<div>

</div>

<div>

</div>

</span>

</div>

The inner divs are invalid. You can’t have a div in a span in XHTML. The probably was the inner divs needed to be full width blocks. The span was automatically generated by a standard ASP.Net control. The way to solve it was this:

<div>

<span>

<span style=”display:block;”>

</span>

<span style=”display:block;”>

</span>

</span>

</div>

Of course I used CSS rather than doing a ugly style attribute hack.

So that was my 10 mins self learning intro into HTML to XHTML conversion. Interestingly while it seems popular Thinking that XHTML is the better way to go for future proofing HTML 5 which is still in draft has support for both nice XML tagging and old style HTML using different doc types. They probably don’t want to break the world.

One thing I’m yet to solve is that this is invalid.

<a href=”http://somewhere” disabled=”disabled”>something</a>

There appears to be no way to disable a link from being clicked without javascript. ASP.Net generates this markup. The only work around it to render the disabled links as labels.

ASP.NET tips

From Dorian: Here is a summary of the tips I’ve come up with so that you can build websites that can be deployed with precompilation.

TIP: Don’t use duplicate class names

While developing ASP.Net will keep all your pages and controls in separate dlls. It groups some controls together into one dll if they are in the same folder but at other times is keeps them separate. I’ve not determine exactly how it chooses. With this partitioning you would not very lucky run into a problem with two classes in your web site with the same name. If you attempt to merge the site into one dll (using aspnet_merge) however it would fail. I recommend naming your classes based on the directory structure using an _ instead of a slash. That will keep class names unique.

TIP: Don’t assume .ascx or .master files exist

Whilst in a development environment the .ascx and .master files exist under the website. After precompilation they do not. Never assume that they exist. The reason for doing this would be dynamic user control loading. Dynamic user control loading should be done by attempting to load the class and caching its presence. You will need to catch an exception to detect failure. I’ve spent a couple of hours looking for a work around and there is none that I can see.

TIP: Don’t reference generated classes

The following shows examples of good and bad. Whilst the bad code will work while you develop you will not be able to deploy with precompiliation.


User Control Reference
Bad:

Dim singleProductDisplay As ASP.bvmodules_controls_singleproductdisplay_ascx = DirectCast(row.FindControl("SingleProductDisplay"), ASP.bvmodules_controls_singleproductdisplay_ascx)

Good:

Dim singleProductDisplay As BVModules_Controls_SingleProductDisplay = DirectCast(row.FindControl("SingleProductDisplay"), BVModules_Controls_SingleProductDisplay)

Master Page Reference
Bad:

If TypeOf Me.Master Is ASP.bvadmin_bvadminproduct_master Then
DirectCast(Me.Master, ASP.bvadmin_bvadminproduct_master).ShowProductDisplayPanel = False

Good:

If TypeOf Me.Master Is BVAdmin_BVAdminProduct Then
DirectCast(Me.Master, BVAdmin_BVAdminProduct).ShowProductDisplayPanel = False

ASP.Net Precompilation

ASP.Net precompilation has a variety of options. You can chose to leave your aspx and ascx files in place and precompile the rest. This lets you change the HTML of those controls after deployment without recompilation. This is the same as VS2003 did it. You can chose to precompile everything. And you can go one step further to merge all the dlls into one big dll. For both VS2005 and VS2008 you can install a Web Deployment Project type into VS that will make it easier to do all this.

Thursday, April 10, 2008

Validating web service content against an XSD


Rodney has been working with a SOA implementation at one of our clients. As a result, we have changed how we deal with web services, to provide additional clarity and robustness to the WS-I standards that Microsoft utilises.

Recently we decided to split our web service definitions into 3 distinct files:

  1. An XSD to define the structure of the data (the parameters and returned data).
  2. A WSDL to define the operations that the web service will make available.
  3. A second WSDL to define the bindings (such as SOAP) used to communicate with the web service.

We create a C# interface from these three files using wsdl.exe. We create a web service in our web site, add this interface into our library and tweak the code to make it implicitly implement the interface.

This is quite easy and works very effectively. There is however one overlooked problem with this approach. Although the interface and associated classes were generated from an XSD and WSDL, the XSD is not strongly enforced from the web service. For example, the XSD may define a string maximum length:

  
   <xsd:simpleType name="String20">
      <xsd:restriction base="xsd:string">
         <xsd:maxLength value="20"/>
      </xsd:restriction>
   </xsd:simpleType>

The problem is that the web service created in this manner will allow a client to pass a string of length > 20. Not the desired outcome, since the XSD should be doing the work for us.


The reason this happens is that the web service doesn’t reference the XSD, nor does it define any attributes on a property to restrict the length. When you look at the schema automatically generated from the web service (http://myserver/mywebservice.asmx?schema=schema1), it uses the class structure to define the schema and therefore does not have the restrictions.
Validating all the values using code can be quite cumbersome and might not necessarily match the validation of the XSD. If the XSD is changed, then the code will also need to be altered to reflect the changes made to the XSD. Again, not a desired outcome.

Another approach is to validate the input data against the XSD from within the body of the web method. This can cause some problems since every individual parameter needs to be serialized to XML and compared to the XSD. Again, if the XSD changes and a new parameter is added, then the programmer has to remember to add the new parameter and ensure that it is validated against the schema. If this is a really large web service, on a high load site, this can be a significant overhead.

Enter SoapExtensions.

SoapExtensions allow you to perform validation on the incoming XML string before it is de-serialised into CLR objects.

Once Only Setup

SoapExtensions require a once only setup to create custom attribute classes stored in your reusable library.

A number of classes derived from the SoapAttribute abstract class need to be created. Some will be used to decorate the web service class to define the schemas while others are required to decorate the web methods to force validation. Coded correctly, these allow you to pass in a specific XSD or a directory full of XSD files to validate against at runtime.

In addition you will need to create a ValidationExtension class to perform the actual validation using the .NET XML library on the incoming XML message stream and XSD collection.
The full detail of the implementation can be found at http://msdn2.microsoft.com/en-us/magazine/cc164115.aspx.

Web Service Specific Setup

Armed with these new classes, validation on the incoming XML stream is now achieved via the following simple steps:

1. Setup the web.config to use the new ValidationExtension class

<webServices>
    <soapExtensionTypes>
        <add type="MaxSoft.Web.Services.Validation.ValidationExtension, MaxSoftDllpriority="1" group="High" />
    </soapExtensionTypes>
</webServices>

2. Decorate your class to point to the XSD file(s)

If you follow the msdn article above you will be able to point to a specific XSD, or a directory of XSD schema files. Both are shown below.

[ValidationSchemaCache("~/Schemas/")]
[ValidationSchema("~/Schemas/MySchema.xsd")]

3. Decorate your web method to trigger the actual validation

[Validation(CheckAssertions = false)]

4. Optionally you can add more complex validation to compliment the XSD

If CheckAssertions is set to true, you can provide additional validation logic which cannot be expressed in the XSD schema. An example is shown below:

[Assert("(//t:length > //t:width)", "Length must be greater than width")]

Although the capability is there, we don't use this feature. In fact, we have set the default value of CheckAssertions to false.

Not only does CheckAssertions add overhead to the validation, but it stores important business logic in an attribute of the web method rather than in the entity library, obviously poor practice. This additional business logic should be in a library for reuse, testability and for a raft of other reasons.

Note:

This code in Microsoft's was written in 1.1 and you need to make some significant changes for it to run against newer versions of .Net as many of the classes are now marked as obsolete.

Wednesday, April 2, 2008

HP RDYMSG DISPLAY is fun

It was April 01 a few days ago, and I decided to pull a small prank on the office. Unfortunately, it was a little subtle(No one has commented yet), but it was fun to put the code together.

The prank is based on some old l0pht code hanging around on this web site: http://www.irongeek.com/i.php?page=security/hphack which sent some HP Printer Job Language code to a networked printer, changing the "READY" prompt to whatever you choose.

I guess the next logical step is to interrogate the Active Directory and automate the process ... or more evil would be to interrogate an IP range, since that would give you access to printers you cannot access via the AD. Evil.

The entire HP command set I found here: http://printers.necsam.com/public/printers/pclcodes/pcl5hp.htm

Guys, don't do this at work.

As always, blogger eats non breaking spaces, spaces and pretty much any type of indentation I can think of. [Edit and replace all spaces with &#160;]


using System;
//using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace HPPrnDisp
{
    class hp
    {
        const int HP_PJL_PORT = 9100;
        const string HP_RDYMSG_HEADER = "\u001B%-12345X@PJL RDYMSG DISPLAY = \"";
        const string HP_RDYMSG_FOOTER = "\"\r\n\u001B%-12345X\r\n";

        // Changes the READY display text for a range of HP Printers
        public static int Main(string[] args)
        {

            Console.WriteLine("HP Display Hack -- sili@l0pht.com 12/8/97");
            Console.WriteLine("-- Re-coded by Kent Bolton 02 APR 2008 --");


            // Ensure that there are only two arguments
            if ( args.Length != 2 ) {
                Console.WriteLine("Usage: printer \"message\"");
                Console.WriteLine("\tMessage can be up to 16 characters long (44 on 5si's)");
                return 1;
            }

            // Process the arguments
            String host = args[0];
            String message = args[1];

            // No more than 44 chars - not sure what happens if there are more
            if (message.Length > 44)
            {
                message = message.Substring(0, 44);
            }

            // Let the user know we know what is needed
            Console.WriteLine("Hostname: {0}", host);
            Console.WriteLine("Message : {0}", message);

            // Set up the IP Connections ...
            IPAddress addr = null;
            IPEndPoint endPoint = null;
            try
            {
                addr = Dns.GetHostEntry(host).AddressList[0];
                endPoint = new IPEndPoint(addr, HP_PJL_PORT);
            }
            catch (Exception e)
            {
                Console.WriteLine("Unknown host: " + e.ToString());
                return 1;
            }

            // Format strings
            StringBuilder textOut = new StringBuilder(100);
            textOut.Append(HP_RDYMSG_HEADER);
            textOut.Append (message);
            textOut.Append(HP_RDYMSG_FOOTER);

            // Now everything is set up, push the string through as an ASCII byte stream
            Console.WriteLine("Connecting ...");
            try
            {
                // Prepare basic socket and encoding stuff
                Socket sock = null;
                System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();

                // Open socket and pump out the bits
                sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
                sock.Connect(endPoint);
                sock.Send(encoding.GetBytes(textOut.ToString()));
                sock.Close();
            }
            catch (Exception e)
            {
                Console.WriteLine("IO error: " + e.ToString());
                return 1;
            }

            // Happy message and bail with no error code
            Console.WriteLine("Sent {0} bytes", textOut.Length);
            return 0;


        }
    }
}