Sunday, January 23, 2011

Enhanced ASP.NET input sanitation with AntiXSS

Need for input sanitation

The applications you build for the internet can have quite a big audience. It is possible for the whole connected world to visit your website and use the app you built on it. But with these visitors, you will attract dark forces who can misuse your application for their own agenda. It is therefore needed to harden your application, and be sure no-one can send email, inject custom HTML and javascript and hijack user information. It is needed you built secure code and protect yourself.

The main characteristic of an application is that it needs input data from the end user. With this input data, something is done. If this data is sent back to the user, is can be displayed on the screen. In it most simple form, this looks like:


<div>
<asp:TextBox ID="inputBox" runat="server" TextMode="MultiLine" />
<br />
<asp:Button ID="submitButton" runat="server" Text="Submit"
onclick="submitButton_Click" />
<br />
<asp:Label runat="server" EnableViewState="True" AssociatedControlID="resultLiteral" Text="Html output:" />
<asp:Literal ID="resultLiteral" runat="server"></asp:Literal>
</div>



With the following button_click code:


protected void submitButton_Click(object sender, EventArgs e)
{
string input = inputBox.Text;
string output = input;
resultLiteral.Text = output;
}


With this example code, it is possible to inject javascript into the response. Try pasting <script language="javascript">alert('Hello World!');</script> in the input box and see.

Out of the box ASP.NET functionality

With the standard ASP.NET setup, the last script example didn’t worked, but threw an exception with the message ‘A potentially dangerous Request.Form value was detected from the client (inputBox="<script language="ja...").’. This is because out of the box, ASP.NET will scan each input parameter to dangerous characters. But don’t let this filter mislead you. The default characters will be the less than character (<) and some ampersand-hash encoded characters. There are also scenario’s you will be switching off the request validation. Simply because some business scenario (maybe you want some html from your user).

You can switch your validation off for all your pages in ASP.NET in the web.config, or specify ValidateRequest="false" in the page directive for the specific page you want to disable this check (note that in ASP.NET 4, there are some additional steps needed). But don’t rely on request validation to heavily, because there are situations in can’t detect malicious input.

Shortcomings of Request Validation

Think about the following situation. You want the user of your site to specify the background color of an element on your page, and you use the following submit event:

protected void submitButton_Click(object sender, EventArgs e)
{
string color = inputBox.Text;
string output = "<div style=\"width:200px;height:200px;background-color:" + color + ";\">Colored Box</div>";
resultLiteral.Text = output;
}


Because the input is directly pasted into an attribute of the div tag, you don’t need HTML directly to inject javascript. The following line will pass through input validation, and inject the page with some potentially evil javascript:
red;" onclick="alert('Hello World!');" "
/



Filtering input

The first thing to prevent the injection of those unwanted code, is to filter the user input if used for generating output. Standard built-in in ASP.NET is the HttpUtility class with some Encoding methods. They can help with the above two attacks, but it is still possible to inject URL backgrounds in the second example. Because these filters are limited in usage, Microsoft started a new library quite some time ago.

AntiXSS library

The two main features in the AntiXSS library are the Encoder and Sanitizer classes. Like the HttpUtility, the encoder class has several specific Encoding methods. These methods are a lot more specific than HttpUtility though. In the second example, it is possible filter the color input with a call to Encoder.CssEncode. The output will be CSS encoded into ‘red\00003B\000022\000020onclick\00003D\000022alert\000028\000027Hello\000020World\000021\000027\000029\00003B\000022\000020\000022’.

The Sanitizer class is even more powerful. It can strip certain HTML tags out of the input. It can transform <script language="javascript">alert('Hello World!');</script><strong>bla</strong><img src="bla.gif" /><ul><li>one<li>Two<li>Three</ul> into <strong>bla</strong><img src="bla.gif"> <ul> <li>one</li><li>Two</li><li>Three</li></ul>


Install AntiXSS with NuGet

AntiXSS used to be a very hidden gem somewhere deep on Microsoft Download or CodePlex. But with the new NuGet, it is on page one of packages. First you need to install NuGet into your Visual Studio. After that, right click your project and choose ‘Add Library Package Reference’.



imageimage


As of today, AntiXSS is the eighth package in the list. If you click install, all assemblies for AntiXSS will be automatically downloaded and added to your solution. So it won’t be very hard anymore to write really secure code!

Wednesday, December 15, 2010

Conversion of a RTF document to PDF with C#

In my current project, I need to convert in an easy way RTF documents to PDF from managed code. I don't want to rebuild these documents in PDF, because the RTF's still have some need in the application.

After google-ing around for possible solutions, I tried two components: the last version of iTextSharp with RTF support (from version five on, the RTF support is split into a new project and removed from iTextSharp) and Aspose.Words.

After a quick conversion with iTextSharp, I understand why RTF support was dropped. The produced PDF was one piece of garbage. The code I used was:

using (FileStream outstream = File.Create(outFile))
{
Document docText = new Document();
PdfWriter writer = PdfWriter.GetInstance(docText, outstream);
docText.Open();
RtfParser rtf = new RtfParser(null);
using (FileStream rtfStream = File.OpenRead(inFile))
{
rtf.ConvertRtfDocument(rtfStream, docText);
}
docText.Close();
}


Aspose.Words worked like a charm, while using only two lines of code:

Aspose.Words.Document doc = new Aspose.Words.Document(inFile);
doc.Save(outFile, SaveFormat.Pdf);

The only drawback is iTextSharp is OpenSource and Aspose.Words isn't. But I guess there isn't another reliable OS alternative.

Thursday, August 19, 2010

VBScript to detect if there is a COM+ application instance running

I had to figure out how to query the COM Administration catalog to detect if there is a specified COM+ application running.
Here is the quickly written vbscript:

running = False
Dim oCatalog
Set oCatalog = CreateObject("COMAdmin.COMAdminCatalog")
sName = "Tridion Content Manager"
Dim result
set apps = oCatalog.GetCollection("Applications")
apps.Populate

For Each app In apps
  If app.Name = sName Then
    Set objAppInst = apps.GetCollection("ApplicationInstances",app.Key)
    objAppInst.Populate
    for each inst in objAppInst
      pId = inst.Value("ProcessID")
      running = not ( inst.Value("IsPaused") or inst.Value("HasRecycled"))
      if running then
        Exit For
      end if
      Set objAppInst = nothing
    Next
    Exit For
  End If
Next
set apps = Nothing
set oCatalog = nothing
MsgBox pid & " " & running

Wednesday, August 11, 2010

Migrating Tridion templates from 5.2 to 2009

Upgrading a Tridion environment from Tridion 5.2 SP1 to Tridion 2009 SP1 isn’t a straightforward procedure, especially when you have to cope with a lot of custom functionalities. In this post, I write down some experience whith the upgrade of the old templating framework to a new one.


Tridion 5.2 situation


The templating framework used in 5.2 (pre-compound templating) was we built as a ScriptExtension in C#, and this ScriptExtension was registered one in the Tridion MMC snap-in. All the present VBScript based templates call this ScriptExtension with some templating parameters. This templating setup is very easy to deploy (register one dll and you're ready to go),


The drawback of this solution was the usage of the Tridion COM API, and the managed to native interop issues you have to deal with (like releasing your references to COM objects), but with Tridion 5.2 (and before that, with Tridion 5.1SP4) it worked.


First try, migrating this solution to 2009 SP1


While building our solution with Tridion 5.1SP4 and 5.2SP1, you had to built the Interop assemblies yourself. With version 2009, Tridion delivered these in the bin\client folder of the Tridion installation. Linking the existing solution to these new assemblies, fixing small API changes and recompiling the whole worked like a charm. But testing the code gave some major issues. The internals of the deployment packages changed for binaries and was killing the custom metadata for indexing purposes. But the whole publishing process became a large memory hog. So it was needed to rewrite the solution (or restart the TDSE process once in a short while). That was basically what was done, the legacy rendering and publishing was ditched for the new way of publishing and rendering content.

Monday, July 5, 2010

Upgraded Certification


With the new version of the .NET Framework, I needed to upgrade my .NET Certifications to this new version. A couple of months ago, I did the beta versions of these exams, and last week I got the results. I passed all the exams I did. They were not easy, so I was quite surprised to receive all the results positively back.

Monday, January 18, 2010

Updated XSLT for CC.NET in SharePoint

Just modded my XSLT for viewing the CC.NET status of the buildserver in the XML Webpart of Sharepoint 2007:


<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" encoding="utf-8" indent="yes" omit-xml-declaration="yes" />
  <xsl:template match="/">
    <table border="0" cellpadding="0" cellspacing="0">
      <thead class="ms-viewheadertr">
        <th style="text-align:left;" scope="col" class="ms-vh2-nograd" nowrap="nowrap">Project</th>
        <th class="ms-vh2-nograd" >&#160;</th>
        <th style="text-align:left;" scope="col" class="ms-vh2-nograd" nowrap="nowrap">Status</th>
        <th class="ms-vh2-nograd" >&#160;</th>
        <th style="text-align:left;" scope="col" class="ms-vh2-nograd" nowrap="nowrap">Last buildtime</th>
        <th class="ms-vh2-nograd" >&#160;</th>
        <th style="text-align:left;" scope="col" class="ms-vh2-nograd" nowrap="nowrap">Buildlabel</th>
        <th class="ms-vh2-nograd" >&#160;</th>
        <th style="text-align:left;" scope="col" class="ms-vh2-nograd" nowrap="nowrap">Activity</th>
      </thead>
      <tbody>
        <xsl:apply-templates select="/CruiseControl/Projects/Project">
          <xsl:sort select="@name"/>
        </xsl:apply-templates>
      </tbody>
    </table>
  </xsl:template>
 
  <xsl:template match="Project">
    <tr>
  <xsl:if test="position() mod 2 != 1">
    <xsl:attribute  name="class">ms-alternating</xsl:attribute>
  </xsl:if>
      <td class="ms-vb2" align="top" nowrap="nowrap">
        <xsl:element name="a">
          <xsl:attribute name="onfocus">OnLink(this)</xsl:attribute>
          <xsl:attribute name="href">
            <xsl:value-of select="@webUrl"/>
          </xsl:attribute>
          <xsl:attribute name="onclick">GoToLink(this);return false;</xsl:attribute>
          <xsl:attribute name="target">_self</xsl:attribute>
          <xsl:value-of select="@name"/>
        </xsl:element>
      </td>
      <td>&#160;</td>
      <xsl:element name="td">
        <xsl:attribute name="class">ms-vb2</xsl:attribute>
        <xsl:attribute name="align">top</xsl:attribute>
        <xsl:attribute name="style">padding-bottom: 3px;
        <xsl:choose>
          <xsl:when test="@lastBuildStatus='Failed'">
            color:red;
          </xsl:when>
          <xsl:when test="@lastBuildStatus='Exception'">
            color:red;
          </xsl:when>
          <xsl:when test="@lastBuildStatus='Unknown'">
            color:yellow;
          </xsl:when>
          <xsl:when test="@lastBuildStatus='Failure'">
            color:red;
          </xsl:when>
          <xsl:otherwise>
            color:green;
          </xsl:otherwise>
        </xsl:choose>
        </xsl:attribute>
        <xsl:value-of select="@lastBuildStatus"/>
      </xsl:element>
      <td>&#160;</td>
      <td class="ms-vb2" style="padding-bottom: 3px;" align="top">
        <xsl:value-of select="substring-before(@lastBuildTime,'T')"/>&#160;
        <xsl:value-of select="substring-before(substring-after(@lastBuildTime,'T'),'.')"/>
      </td>
      <td>&#160;</td>
      <td class="ms-vb2" style="padding-bottom: 3px;text-align:right;" align="top">
        <xsl:value-of select="@lastBuildLabel"/>
      </td>
      <td>&#160;</td>
      <xsl:element name="td">
        <xsl:attribute name="class">ms-vb2</xsl:attribute>
        <xsl:attribute name="align">top</xsl:attribute>
        <xsl:attribute name="style">
          padding-bottom: 3px;
          <xsl:choose>
            <xsl:when test="@activity='Building'">
              color:red;
            </xsl:when>
           <xsl:when test="@activity='CheckingModifications'">
            color:yellow;
          </xsl:when>
            <xsl:otherwise></xsl:otherwise>
          </xsl:choose>
        </xsl:attribute>
        <xsl:value-of select="@activity"/>
      </xsl:element>
    </tr>
  </xsl:template>
</xsl:stylesheet>

Saturday, October 24, 2009

Getting Hippo Site Toolkit demo to work

While figuring out Hippo and its architecture, I found some annoying errors in the documentation. While the documentation at http://www.onehippo.org/site-toolkit/home reads:
1. Download the wars, drop them in your Tomcat servlet container and start Tomcat (see in_servlet_container, but with different wars).
2. Checkout the HST2 project, start the cms and the site of the demosuite in the buildin Jetty servlet container.

So I use Tomcat, and its saying in another page to rename the cms war to cms.war, there is no mention of renaming the site war to site.war. But in the rest of the documentation the assumption is made to use the /site URL. I'm no Tomcat guru, so how should I know that the webapp wars are used as URI shortcuts ;).

But that's the main pain in the ass with OSS documentation, it's mostly incomplete. How hard should it be to test your HOWTO for a plain and simple demo. Adding a simple step 1.5: "rename the cms war to cms.war and the site war to site.war" isn't a big deal, but makes a difference of about 1 hour for a Hippo n00b figuring out what's going on!

Wednesday, September 30, 2009

Demo Parallel Extensions .NET 4.0

Yesterday I a gave a presentation/demonstration about the upcoming .NET 4.0 framework release. Unfortunately, there wasn't enough time to show the complete parallel demo. Here I show a couple of ways to display all integers between 0 and 100 that are dividable by 10.


class Program
{
// objects for classic parallel code
static object locker = new object();
static Queue<int> integers;
//with concurrent Queue, we don't need a locker
static ConcurrentQueue<int> concurrentIntegers;

static void Main(string[] args)
{
int steps = 100;
steps.WriteOnConsole(); //extension method to display an integer on the console
Classic(); //classic for loop, no parallel code
ClassicParallel(); //classic parallel invocation

New(steps); //parallel for loop
NewMoreLikeClassic(steps); //parallel the new way, but looks like classic parallel

Linq(steps); //old LINQ way
PLinq(steps); //new Parallel LINQ
}

private static void Classic(int steps = 100)
{
for (int i = 0; i < steps; i++)
{
if (i % 10 == 0)
{
i.WriteOnConsole();
}
}
}

private static void ClassicParallel(int steps = 100)
{
integers = new Queue<int>(steps);
// fill the queue with all integers
for (int i = 0; i < steps; i++)
{
integers.Enqueue(i);
}

int workerCount = 5; //arbitrary number for workercount.
Thread[] workers = new Thread[workerCount];
// Create and start a separate thread for each worker
for (int i = 0; i < workerCount; i++)
{
workers[i] = new Thread(Consume);//.Start();
}
foreach (Thread worker in workers)
{
worker.Start();
}
}

private static void Consume()
{
while (true)
{
int i;
lock (locker)
{
if (integers.Count == 0) return; // run until the queue is empty
i = integers.Dequeue();
}
if (i % 10 == 0)
{
i.WriteOnConsole();
}
}
}

private static void New(int steps)
{
ParallelLoopResult result = Parallel.For(0, steps, (i, loop) =>
{
if (i % 10 == 0)
{
i.WriteOnConsole();
}
//With the LoopState, we can break and terminate the processing of the loop
if (i == 50)
{
loop.Break();
}
}
);
Console.WriteLine("Completed: {0}, Breaked at iteration {1}",
result.IsCompleted,
result.LowestBreakIteration);
}

private static void NewMoreLikeClassic(int steps = 100)
{
concurrentIntegers = new ConcurrentQueue<int>(Enumerable.Range(0, steps));
Parallel.Invoke(() =>
{
ConsumeQueue();
}
);
}

private static void ConsumeQueue()
{
// note: there is no locking used here, because we use a ConcurrentQueue
int i;
bool success = concurrentIntegers.TryDequeue(out i);
if (success && (i % 10 == 0))
{
i.WriteOnConsole();
}
}

private static void Linq(int steps)
{
Enumerable.Range(0,steps).Where(i => i % 10 == 0)
.ToList<int>().ForEach(i=>i.WriteOnConsole());
}

private static void PLinq(int steps)
{
Enumerable.Range(0, steps).Where(i => i % 10 == 0).AsParallel()
.ToList<int>().ForEach(i => i.WriteOnConsole());
}
}

Wednesday, September 16, 2009

ASP.NET favicon.ico routing to deep link

After seeing some errors in my application log complaining about a non existing business object with id favicon.ico, and being sure that I had a <link href="/resources/images/favicon.ico" rel="shortcut icon" /> tag in my header section, I realized that old non-standard-compliant browsers do a request for the hard-coded /favicon.ico url. So I wanted to add a route in my route table to redirect these requests to the proper icon location. Unfortunately, there is no public StaticFileHandler available with a virtual path in its constructor, so I had to built them myself. The following couple of lines do the trick:

Registering the routes in the application startup:

routes.Add(new Route("favicon.ico", new StaticFileRouteHandler("~/Resources/images/favicon.ico")));
// generic, catch-all rule, caused the error for favicon.ico
routes.Add(Routes.BusinessObject, new Route(
string.Format("{{{0}}}", RouteParameters.BusinessObjectIdentifier),
new CustomRouteHandler("~/Pages/BusinessObjectDetails.aspx")));


The StaticFileRouteHandler to serve the the static file from the request.

public class StaticFileRouteHandler : IRouteHandler
{
public string VirtualPath { get; set; }
public StaticFileRouteHandler(string virtualPath)
{
VirtualPath = virtualPath;
}

#region IRouteHandler Members
public System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext)
{
HttpContext.Current.RewritePath(VirtualPath);
return new DefaultHttpHandler();
}
#endregion
}

Wednesday, September 2, 2009

New VS2010 / .NET 4.0 features

I already love the new dynamic keyword in VS2010, and missing it in my day to day work with Tridion COM objects. Although the drawback is no IntelliSense in VS on the object, see the following code to get the item title from a Tridion TOM item:

Old code

object obj = tdse.GetObject(tcmUri, EnumOpenMode.OpenModeView, null, XMLReadFilter.XMLReadAll );
string title = null;
if (obj is Component)
{
Component component = (Component) obj;
title = component.Title;
}
if (obj is Folder)
{
Folder folder = (Folder) obj;
title = folder.Title;
}
if (obj is Page)
{
Page page = (Page) obj;
title = page.Title;
}


New code

dynamic tridionItem = tdse.GetObject(tcmUri, EnumOpenMode.OpenModeView);
string title = tridionItem.Title;


Another long-missing-but-finally-added feature is the support of optional COM Interop parameters in C#. Before 4.0, you needed to specify all optional parameters in Interop method calls, even if you wanted to use the default values, leading to extremely long method calls (maybe not in Tridion, but infamous in Office Interop with Type.Missing). These method parameters do now have the block parentheses around them in IntelliSense:

Wednesday, July 15, 2009

Using MySQL Providers with ASP.NET

MySQL Logo
I'm figuring out how to use MySQL with Visual Studio and ASP.NET. The first thing to install (besides MySQL Server) is the MySQL Connector for .NET, currently at version 6.0.4.
After installing this connector, you've all the .NET providers and entity framework stuff to go. One thing I should mention is that the .NET Connectors will install MySql providers which you can use in your website, MySql won't popup in the Express editions of Visual Studio. So you can't connect to a MySQL database with your server explorer with these versions.

So installing the MySQL connector on your server will modify the server's machine.config and add the provider config in it. Not installing the connector on your server means you need to configure the providers in the system.web section of the web.config of the site (and bin deploying the MySql dll's of course):

<membership defaultProvider="MySQLMembershipProvider">
<providers>
<remove name="MySQLMembershipProvider" />
<add connectionStringName="MySqlServer" enablePasswordRetrieval="false"
enablePasswordReset="true" requiresQuestionAndAnswer="true"
applicationName="/" requiresUniqueEmail="false" passwordFormat="Clear"
maxInvalidPasswordAttempts="5" minRequiredPasswordLength="7"
minRequiredNonalphanumericCharacters="1" passwordAttemptWindow="10"
passwordStrengthRegularExpression="" name="MySQLMembershipProvider"
type="MySql.Web.Security.MySQLMembershipProvider, MySql.Web, Version=6.0.4.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" />
</providers>
</membership>
<profile>
<providers>
<remove name="MySQLProfileProvider"/>
<add name="MySQLProfileProvider" type="MySql.Web.Profile.MySQLProfileProvider, MySql.Web, Version=6.0.4.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" connectionStringName="MySqlServer" applicationName="/" />
</providers>
</profile>
<roleManager defaultProvider="MySQLRoleProvider">
<providers>
<remove name="MySQLRoleProvider" />
<add connectionStringName="MySqlServer" applicationName="/" name="MySQLRoleProvider"
type="MySql.Web.Security.MySQLRoleProvider, MySql.Web, Version=6.0.4.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" />
</providers>
</roleManager>


These new providers need a connection string, so add to your connectionString section:

<connectionStrings>
<add name="MySqlServer" providerName="MySql.Data.MySqlClient" connectionString="server=mysql1;user id=webuser;password=xxxxxx;persist security info=True;database=website" />
</connectionStrings>

Next you need the schema. The docs will say they will be created automatically, but I didn't saw a single table created. By looking at the connector source code, I distilled the following script to create the schema in your database:

CREATE TABLE `my_aspnet_applications` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(256) DEFAULT NULL,
`description` varchar(256) DEFAULT NULL,
PRIMARY KEY (`id`)
) ;

CREATE TABLE `my_aspnet_membership` (
`userId` int(11) NOT NULL DEFAULT '0',
`Email` varchar(128) DEFAULT NULL,
`Comment` varchar(255) DEFAULT NULL,
`Password` varchar(128) NOT NULL,
`PasswordKey` char(32) DEFAULT NULL,
`PasswordFormat` tinyint(4) DEFAULT NULL,
`PasswordQuestion` varchar(255) DEFAULT NULL,
`PasswordAnswer` varchar(255) DEFAULT NULL,
`IsApproved` tinyint(1) DEFAULT NULL,
`LastActivityDate` datetime DEFAULT NULL,
`LastLoginDate` datetime DEFAULT NULL,
`LastPasswordChangedDate` datetime DEFAULT NULL,
`CreationDate` datetime DEFAULT NULL,
`IsLockedOut` tinyint(1) DEFAULT NULL,
`LastLockedOutDate` datetime DEFAULT NULL,
`FailedPasswordAttemptCount` int(10) unsigned DEFAULT NULL,
`FailedPasswordAttemptWindowStart` datetime DEFAULT NULL,
`FailedPasswordAnswerAttemptCount` int(10) unsigned DEFAULT NULL,
`FailedPasswordAnswerAttemptWindowStart` datetime DEFAULT NULL,
PRIMARY KEY (`userId`)
) COMMENT='2';

CREATE TABLE `my_aspnet_profiles` (
`userId` int(11) NOT NULL,
`valueindex` longtext,
`stringdata` longtext,
`binarydata` longblob,
`lastUpdatedDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`userId`)
) ;


CREATE TABLE `my_aspnet_roles` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`applicationId` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ROW_FORMAT=DYNAMIC;

CREATE TABLE `my_aspnet_schemaversion` (
`version` int(11) DEFAULT NULL
) ;

CREATE TABLE `my_aspnet_users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`applicationId` int(11) NOT NULL,
`name` varchar(256) NOT NULL,
`isAnonymous` tinyint(1) NOT NULL DEFAULT '1',
`lastActivityDate` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ;

CREATE TABLE `my_aspnet_usersinroles` (
`userId` int(11) NOT NULL DEFAULT '0',
`roleId` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`userId`,`roleId`)
) ROW_FORMAT=DYNAMIC;

INSERT my_aspnet_SchemaVersion (version) VALUES (4);


Finally, you can use the ASP.NET Configuration website to configure users. To get only the providers working, it took a whole blog post. The .NET connector for MySQL is still a little bit rough on the edges, but after figuring the above things out, it will work!

Friday, July 10, 2009

Combining XML documents into one big one with XInclude

I needed to figure out how to include transparently some xml documents into another xml last week. We store our site navigation in one XML, but due to a decentralized management of this file, I need to cut this navigation into different file parts.

For this, the W3C XInlude standard was developed. Unfortunately, there is still no support for this in the .NET Framework. The solution is the use the Mvp.Xml library from codeplex, which includes XInclude support. A strong point of XInclude is the fallback possibilities of this standard, so when the included file isn't present, the output is predictable.

With XInclude, a XML fragment can be included from the original document:

<topic name="External Topic" path="/external-topic" id="18442">
<xi:include href="../../external-topic/navigation.xml"
xmlns:xi="http://www.w3.org/2003/XInclude"
xpointer="xpointer(/navigation/*)">
<xi:fallback>
<subject name="Fallback Subject" pad="/external-topic/FB"
id="19672" />
</xi:fallback>
</xi:include>
</topic>


Where the included XML fragment will be:

<?xml version="1.0"?>
<navigation name="External Topic" path="/external-topic"
id="18442" xmlns:xi="http://www.w3.org/2001/XInclude">
<subject name="Poll" path="/external-topic/poll" id="19670" />
<subject name="Text" path="/external-topic/text" id="19671" />
<subject name="Links" path="/external-topic/links id="19672" />
</navigation>

While XInclude describes the format of the XML document, we still need a XInclude aware XmlReader. There is one within the Mvp.Xml package. The old XML reading code:

mXmlDoc = new XmlDocument();
mXmlDoc.Load(mXmlFile.FullName);

should be changed to:

mXmlDoc = new XmlDocument();
using (XmlReader reader = new XmlIncludingReader(mXmlFile.FullName))
{
mXmlDoc.Load(reader);
}

The resulting mXmlDoc document representations are equal in both situations. Only the XInclude result includes extra xml:base attributes.

The resulting XML is:

<code style="xml">
<topic name="External Topic" path="/external-topic" id="18442">
<subject name="Poll" path="/external-topic/poll" id="19670" xml:base="../../external-topic/navigation.xml" />
<subject name="Text" path="/external-topic/text" id="19671" xml:base="../../external-topic/navigation.xml" />
<subject name="Links" path="/external-topic/links id="19672" xml:base="../../external-topic/navigation.xml" />
</topic>

These xml:base attributes can be used to add extra CacheDependencies for the storage of the resulting XML document to the application cache, so the cache will be invalidated as one of the included XML documents change.

Monday, May 4, 2009

Cleanup artifacts in CC.Net with artifactcleanup

I got an email from the system admin that the CC.Net buildserver used a lot of diskspace. It turned out that I got 1000 buildlogs for one project on the server...

The solution was to configure a artifactcleanup in the cc.config. See the CC.Net documentation.

Saturday, May 2, 2009

Code generation for a COM wrapper assembly

For my last post, I needed some quick and dirty code generator. Basically, I've a COM Iterop assembly full with interfaces I need to wrap with a specified template.

The following code works, but still needed some handwork (like hand coding the ref parameters). Use it on your own risk ;)


using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.IO;

namespace CodeGeneration
{
class Program
{
static Dictionary<string, string> keywordType;

static void Main(string[] args)
{
FillKeywordType();

Assembly interop = Assembly.Load(@"Tridion.ContentManager.Interop.cm_tom");
foreach (Type t in interop.GetTypes())
{
//Console.WriteLine(t.Name);
if (t.Name.StartsWith("_"))
{
WriteOut(t, t.Name.Replace("_",string.Empty));
}
}
Console.ReadLine();
}

private static void FillKeywordType()
{

keywordType = new Dictionary<string, string>();
keywordType.Add("System.String", "string");
keywordType.Add("System.Void", "void");
keywordType.Add("System.Int32", "int");
keywordType.Add("System.DateTime", "DateTime");
keywordType.Add("System.Boolean", "bool");
keywordType.Add("System.Object", "object");
}

private static void WriteOut(Type t, string className)
{
Console.WriteLine("Info of {0}", t.Name);

using (StreamWriter writer = new StreamWriter(string.Format("D:\\Work\\DisposableOut\\{0}.cs", className) ))
{

WriteHeader(writer, className);


foreach (MethodInfo mi in t.GetMethods())
{
if (!(mi.Name.StartsWith("get_") || mi.Name.StartsWith("set_") || mi.Name.StartsWith("let_")))
{
string methodSig = string.Format("{1} {0}", mi.Name, GetTypeName(mi.ReturnType.Name, mi.ReturnType.Namespace));
bool isVoid = (GetTypeName(mi.ReturnType.Name, mi.ReturnType.Namespace)=="void");
List<string> parameters = new List<string>();
List<string> parameterNames = new List<string>();
foreach (ParameterInfo pi in mi.GetParameters())
{
parameters.Add(string.Format("{1} {0}", pi.Name, GetTypeName(pi.ParameterType.Name, pi.ParameterType.Namespace)));
parameterNames.Add(pi.Name);
}
string parameter = string.Join(", ", parameters.ToArray());

writer.Write(@"public {0} ({2})" + Environment.NewLine +
@" {{" + Environment.NewLine +
@" checkNotDisposed();" + Environment.NewLine +
@" {5}com{1}.{3}({4});" + Environment.NewLine +
@" }}" + Environment.NewLine + Environment.NewLine,
methodSig, className, string.Join(", ", parameters.ToArray()), mi.Name, string.Join(", ", parameterNames.ToArray()),
isVoid?string.Empty:"return ");
}
}

foreach (PropertyInfo pi in t.GetProperties())
{
writer.Write(@"public {0} {1} {{" + Environment.NewLine, GetTypeName(pi.PropertyType.Name, pi.PropertyType.Namespace), pi.Name);
if (pi.CanRead)
{
//get
writer.Write(@" get {{" + Environment.NewLine +
@" checkNotDisposed();" + Environment.NewLine +
@" return com{1}.{0};" + Environment.NewLine +
@" }}" + Environment.NewLine ,
pi.Name, className);
}
if (pi.CanWrite)
{
//set
writer.Write(@" set {{" + Environment.NewLine +
@" checkNotDisposed();" + Environment.NewLine +
@" com{1}.{0} = value;" + Environment.NewLine +
@" }}" + Environment.NewLine,
pi.Name, className);
}
writer.Write("}" + Environment.NewLine + Environment.NewLine);
}

WriteFooter(writer);
writer.Flush();
writer.Close();
}
}

private static string GetTypeName(string name, string nameSpace)
{
string typeName = string.Format("{0}.{1}", nameSpace, name);
if (keywordType.ContainsKey(typeName))
{
return keywordType[typeName];
}
return typeName;
}

private static void WriteFooter(StreamWriter writer)
{
writer.Write(@" #endregion" + Environment.NewLine +
@" }" + Environment.NewLine +
@"}" + Environment.NewLine);
}

private static void WriteHeader(StreamWriter writer, string className)
{
StringBuilder fileHeader = new StringBuilder();
fileHeader.AppendFormat(@"using System;{0}", Environment.NewLine);
fileHeader.AppendFormat(@"using System.Collections.Generic;{0}", Environment.NewLine);
fileHeader.AppendFormat(@"using System.Text;{0}", Environment.NewLine);
fileHeader.AppendFormat(@"using System.Runtime.InteropServices;{0}", Environment.NewLine);
fileHeader.AppendFormat(@"using Tridion.ContentManager.Interop.TDS;{0}", Environment.NewLine);
fileHeader.AppendFormat(@"{0}", Environment.NewLine);
fileHeader.AppendFormat(@"namespace DisposableTridion{0}", Environment.NewLine);
fileHeader.AppendFormat(@"{{{0}", Environment.NewLine);
fileHeader.AppendFormat(@" public class {1} : DisposableBase {0}", Environment.NewLine, className);
fileHeader.AppendFormat(@" {{{0}", Environment.NewLine);
fileHeader.AppendFormat(@" private Tridion.ContentManager.Interop.TDS.{1} com{1};{0}", Environment.NewLine, className);
fileHeader.AppendFormat(@"{0}", Environment.NewLine);
fileHeader.AppendFormat(@" public {1}(Tridion.ContentManager.Interop.TDS.{1} {2}){0}", Environment.NewLine, className, className.ToLower());
fileHeader.AppendFormat(@" {{{0}", Environment.NewLine);
fileHeader.AppendFormat(@" com{1} = {2};{0}", Environment.NewLine, className, className.ToLower());
fileHeader.AppendFormat(@" }}{0}", Environment.NewLine);
fileHeader.AppendFormat(@"{0}", Environment.NewLine);
fileHeader.AppendFormat(@" ~{1}(){0}", Environment.NewLine, className);
fileHeader.AppendFormat(@" {{{0}", Environment.NewLine);
fileHeader.AppendFormat(@" Dispose(false);{0}", Environment.NewLine);
fileHeader.AppendFormat(@" }}{0}", Environment.NewLine);
fileHeader.AppendFormat(@"{0}", Environment.NewLine);
fileHeader.AppendFormat(@" protected override void DisposeMembers(){0}", Environment.NewLine);
fileHeader.AppendFormat(@" {{{0}", Environment.NewLine);
fileHeader.AppendFormat(@" DisposableBase.ReleaseComObject(com{1});{0}", Environment.NewLine, className);
fileHeader.AppendFormat(@" }}{0}", Environment.NewLine);
fileHeader.AppendFormat(@"{0}", Environment.NewLine);
fileHeader.AppendFormat(@" #region _{1} Members{0}", Environment.NewLine, className);

writer.Write(fileHeader.ToString());
}
}
}

Friday, May 1, 2009

Disposing COM objects

The Tridion solutions at my current assignment are working great, but now and then the CM website freezes. After investigating the issue with Tridion customer support, there is a suspicion the Tridion COM objects freeze under memory load.
This load is generated because our custom solutions do not release the COM objects they use. So now the codebase should be recoded with this cleanup operation.

When you don't cleanup this code, the cleanup will be initiated by the .NET Garbage Collector (GC). But the GC isn't working continually, but will only be called under memory pressure.
Because COM objects aren't run under as managed code, the GC doesn't know how much memory is used by them. Therefore, waiting until the GC will cleanup your code can take a long time, especially because COM objects are small objects on the managed memory.

The pattern to use for cleaning up unmanaged resources is the IDisposable interface. So with this pattern, we can release a COM object after you're done with it by calling the Marshal.ReleaseComObject method.

Because the Tridion Object Model (TOM) has a lot of objects, I'm creating a managed wrapper assembly, where on each object I can implement the IDisposable interface. Besides the interface, I need some more plumbing, so the first class to create is an abstract DisposableBase class:

public abstract class DisposableBase : IDisposable
{
private bool disposed = false;

protected void checkNotDisposed()
{
if (this.disposed)
{
string message = "Object of type " + base.GetType().Name + " cannot be accessed because it was disposed.";
throw new ObjectDisposedException(base.GetType().Name, message);
}
}

public void Dispose()
{
if (!this.disposed)
{
this.Dispose(true);
}
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
this.DisposeMembers();
}
this.disposed = true;
}

protected abstract void DisposeMembers();

public static void ReleaseComObject(object interopObject)
{
if (interopObject != null)
{
Marshal.ReleaseComObject(interopObject);
}
interopObject = null;
}
}



So I can use this base class in each wrapper class for TOM classes, like the Component class:

public class Component : DisposableBase
{
private Tridion.ContentManager.Interop.TDS.Component comComponent;

public Component(Tridion.ContentManager.Interop.TDS.Component component)
{
comComponent = component;
}

~Component()
{
Dispose(false);
}

protected override void DisposeMembers()
{
DisposableBase.ReleaseComObject(comComponent);
}

public void CheckIn(bool permanentLock)
{
checkNotDisposed();
comComponent.CheckIn(permanentLock);
}

_Component Members
}


So now it's perfectly safe to use the TOM to check in a component with the following code:

using (Component component = tdse.GetObject("tcm:8-123", EnumOpenMode.OpenModeView))
{
component.CheckIn(true);
}

Saturday, January 31, 2009

RTFM!



I'm going to be short on this. Microsoft 2008 Server R2 is 64-bit only. So there is no chance installing the beta will work on your 32-bit-guest-pc-only VPC. A pity for the wasted bandwidth...

Friday, January 30, 2009

MCTS WinForms 3.5

Just got the email:
Congratulations on earning your Microsoft Certified Technology Specialist: .NET Framework 3.5, Windows Forms Applications certification! We hope you enjoy the benefits of your certification and of membership in the Microsoft Certified Professional community.

Saturday, January 24, 2009

MCPD ASP.NET 3.5


After two months, I finally got the result of the beta exam for my MCPD ASP.NET 3.5 certification. I passed!
I am still waiting for two more exams from the beginning of December, but hopefully they will follow shortly!

Friday, January 16, 2009

Installing Windows 7

StartingW7I’ve installed Windows 7 last week. Unfortunately I hadn’t much time to play with it because we had a serious escalation on my work. The intranet server was performing badly, so I did a crash course performance counters for ASP.NET and now know that the caching service is an essential component for the Tridion Content Management system.

For improving ASP.NET Performance, the following articles helped:

InstallingW7

Monday, December 22, 2008

Analyzing Risk

Just read a great article about risk on MSDN. While evaluating risks in my daily job, it puts some things in context (like the top 10 project risks) .