tag:blogger.com,1999:blog-79290535991296229402023-10-04T13:48:50.074+01:00Ian Picknell - Under the CoversDelving under the covers of various Microsoft development tools and technologies to provide information you won't find in the documentation.Ian Picknellhttp://www.blogger.com/profile/11237307149515983183noreply@blogger.comBlogger25110tag:blogger.com,1999:blog-7929053599129622940.post-72775920696566976762011-11-23T07:41:00.000+00:002011-11-23T07:41:50.418+00:00Execute Procedure for XML Auto<h2>Introduction</h2>
<p>
For several years now, SQL Server has had the ability to convert the results of an arbitrary SELECT statement into
XML by appending the <em>FOR XML AUTO</em> clause. For example, whilst:
</p>
<pre>
<span style="color:blue">SELECT</span> <span style="color:blue">TOP</span> 10 <span style="color:gray">*</span> <span style="color:blue">FROM</span> <span style="color:green">sys</span><span style="color:gray">.</span><span style="color:green">types</span>
</pre>
<p>
generates a standard rowset:
</p>
<table style="font-family:MS Sans Serif,Sans-Serif">
<thead>
<tr><td>name</td><td>system_type_id</td><td>user_type_id</td><td>schema_id</td><td>principal_id</td><td>max_length</td><td>precision</td><td>scale</td><td>collation_name</td><td>is_nullable</td><td>is_user_defined</td><td>is_assembly_type</td><td>default_object_id</td><td>rule_object_id</td><td>is_table_type</td></tr>
</thead>
<tbody>
<tr><td>image</td><td>34</td><td>34</td><td>4</td><td>NULL</td><td>16</td><td>0</td><td>0</td><td>NULL</td><td>1</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr>
<tr><td>text</td><td>35</td><td>35</td><td>4</td><td>NULL</td><td>16</td><td>0</td><td>0</td><td>Latin1_General_CI_AS</td><td>1</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr>
<tr><td>uniqueidentifier</td><td>36</td><td>36</td><td>4</td><td>NULL</td><td>16</td><td>0</td><td>0</td><td>NULL</td><td>1</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr>
<tr><td>date</td><td>40</td><td>40</td><td>4</td><td>NULL</td><td>3</td><td>10</td><td>0</td><td>NULL</td><td>1</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr>
<tr><td>time</td><td>41</td><td>41</td><td>4</td><td>NULL</td><td>5</td><td>16</td><td>7</td><td>NULL</td><td>1</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr>
<tr><td>datetime2</td><td>42</td><td>42</td><td>4</td><td>NULL</td><td>8</td><td>27</td><td>7</td><td>NULL</td><td>1</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr>
<tr><td>datetimeoffset</td><td>43</td><td>43</td><td>4</td><td>NULL</td><td>10</td><td>34</td><td>7</td><td>NULL</td><td>1</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr>
<tr><td>tinyint</td><td>48</td><td>48</td><td>4</td><td>NULL</td><td>1</td><td>3</td><td>0</td><td>NULL</td><td>1</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr>
<tr><td>smallint</td><td>52</td><td>52</td><td>4</td><td>NULL</td><td>2</td><td>5</td><td>0</td><td>NULL</td><td>1</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr>
<tr><td>int</td><td>56</td><td>56</td><td>4</td><td>NULL</td><td>4</td><td>10</td><td>0</td><td>NULL</td><td>1</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr>
</tbody>
</table>
<p>
simply appending a FOR XML clause (in this case FOR XML <em>AUTO</em>):
</p>
<pre>
<span style="color:blue">SELECT</span> <span style="color:blue">TOP</span> 10 <span style="color:gray">*</span> <span style="color:blue">FROM</span> <span style="color:green">sys</span><span style="color:gray">.</span><span style="color:green">types</span> <span style="color:blue">FOR</span> <span style="color:blue">XML</span> <span style="color:blue">AUTO</span>
</pre>
<p>
generates an XML fragment instead:
</p>
<table style="font-family:MS Sans Serif,Sans-Serif">
<thead>
<tr><td>XML_F52E2B61-18A1-11d1-B105-00805F49916B</td></tr>
</thead>
<tbody>
<tr><td nowrap="nowrap" style="color:blue; text-decoration:underline;"><sys.types name="image" system_type_id="34" user_type_id="34" schema_id="4" max_length="16" precision="0" scale="0" is_nullable="1" is_user_defined="0" is_assembly_type="0" default_object_id="0" rule_object_id="0" is_table_type="0"/><sys.types name="text" system_type_id="35" user_type_id="35" schema_id="4" max_length="16" precision="0" scale="0" collation_name="Latin1_General_CI_AS" is_nullable="1" is_user_defined="0" is_assembly_type="0" default_object_id="0" rule_object_id="0" is_table_type="0"/><sys.types name="uniqueidentifier" system_type_id="36" user_type_id="36" schema_id="4" max_length="16" precision="0" scale="0" is_nullable="1" is_user_defined="0" is_assembly_type="0" default_object_id="0" rule_object_id="0" is_table_type="0"/><sys.types name="date" system_type_id="40" user_type_id="40" schema_id="4" max_length="3" precision="10" scale="0" is_nullable="1" is_user_defined="0" is_assembly_type="0" default_object_id="0" rule_object_id="0" is_table_type="0"/><sys.types name="time" system_type_id="41" user_type_id="41" schema_id="4" max_length="5" precision="16" scale="7" is_nullable="1" is_user_defined="0" is_assembly_type="0" default_object_id="0" rule_object_id="0" is_table_type="0"/><sys.types name="datetime2" system_type_id="42" user_type_id="42" schema_id="4" max_length="8" precision="27" scale="7" is_nullable="1" is_user_defined="0" is_assembly_type="0" default_object_id="0" rule_object_id="0" is_table_type="0"/><sys.types name="datetimeoffset" system_type_id="43" user_type_id="43" schema_id="4" max_length="10" precision="34" scale="7" is_nullable="1" is_user_defined="0" is_assembly_type="0" default_object_id="0" rule_object_id="0" is_table_type="0"/><sys.types name="tinyint" system_type_id="48" user_type_id="48" schema_id="4" max_length="1" precision="3" scale="0" is_nullable="1" is_user_defined="0" is_assembly_type="0" default_object_id="0" rule_object_id="0" is_table_type="0"/><sys.types name="smallint" system_type_id="52" user_type_id="52" schema_id="4" max_length="2" precision="5"</td></tr>
</tbody>
</table>
<p>
Clicking on the hyperlink shows you the full XML fragment in all its glory:
</p>
<pre>
<span style="color:blue"><</span><span style="color:#A31515">sys.types</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span>"</span><span style="color:blue">image</span>"<span style="color:blue"> </span><span style="color:red">system_type_id</span><span style="color:blue">=</span>"<span style="color:blue">34</span>"<span style="color:blue"> </span><span style="color:red">user_type_id</span><span style="color:blue">=</span>"<span style="color:blue">34</span>"<span style="color:blue"> </span><span style="color:red">schema_id</span><span style="color:blue">=</span>"<span style="color:blue">4</span>"<span style="color:blue"> </span><span style="color:red">max_length</span><span style="color:blue">=</span>"<span style="color:blue">16</span>"<span style="color:blue"> </span><span style="color:red">precision</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">scale</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_nullable</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">is_user_defined</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_assembly_type</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">default_object_id</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">rule_object_id</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_table_type</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> /></span>
<span style="color:blue"><</span><span style="color:#A31515">sys.types</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span>"</span><span style="color:blue">text</span>"<span style="color:blue"> </span><span style="color:red">system_type_id</span><span style="color:blue">=</span>"<span style="color:blue">35</span>"<span style="color:blue"> </span><span style="color:red">user_type_id</span><span style="color:blue">=</span>"<span style="color:blue">35</span>"<span style="color:blue"> </span><span style="color:red">schema_id</span><span style="color:blue">=</span>"<span style="color:blue">4</span>"<span style="color:blue"> </span><span style="color:red">max_length</span><span style="color:blue">=</span>"<span style="color:blue">16</span>"<span style="color:blue"> </span><span style="color:red">precision</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">scale</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">collation_name</span><span style="color:blue">=</span>"<span style="color:blue">Latin1_General_CI_AS</span>"<span style="color:blue"> </span><span style="color:red">is_nullable</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">is_user_defined</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_assembly_type</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">default_object_id</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">rule_object_id</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_table_type</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> /></span>
<span style="color:blue"><</span><span style="color:#A31515">sys.types</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span>"</span><span style="color:blue">uniqueidentifier</span>"<span style="color:blue"> </span><span style="color:red">system_type_id</span><span style="color:blue">=</span>"<span style="color:blue">36</span>"<span style="color:blue"> </span><span style="color:red">user_type_id</span><span style="color:blue">=</span>"<span style="color:blue">36</span>"<span style="color:blue"> </span><span style="color:red">schema_id</span><span style="color:blue">=</span>"<span style="color:blue">4</span>"<span style="color:blue"> </span><span style="color:red">max_length</span><span style="color:blue">=</span>"<span style="color:blue">16</span>"<span style="color:blue"> </span><span style="color:red">precision</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">scale</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_nullable</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">is_user_defined</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_assembly_type</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">default_object_id</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">rule_object_id</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_table_type</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> /></span>
<span style="color:blue"><</span><span style="color:#A31515">sys.types</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span>"</span><span style="color:blue">date</span>"<span style="color:blue"> </span><span style="color:red">system_type_id</span><span style="color:blue">=</span>"<span style="color:blue">40</span>"<span style="color:blue"> </span><span style="color:red">user_type_id</span><span style="color:blue">=</span>"<span style="color:blue">40</span>"<span style="color:blue"> </span><span style="color:red">schema_id</span><span style="color:blue">=</span>"<span style="color:blue">4</span>"<span style="color:blue"> </span><span style="color:red">max_length</span><span style="color:blue">=</span>"<span style="color:blue">3</span>"<span style="color:blue"> </span><span style="color:red">precision</span><span style="color:blue">=</span>"<span style="color:blue">10</span>"<span style="color:blue"> </span><span style="color:red">scale</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_nullable</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">is_user_defined</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_assembly_type</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">default_object_id</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">rule_object_id</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_table_type</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> /></span>
<span style="color:blue"><</span><span style="color:#A31515">sys.types</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span>"</span><span style="color:blue">time</span>"<span style="color:blue"> </span><span style="color:red">system_type_id</span><span style="color:blue">=</span>"<span style="color:blue">41</span>"<span style="color:blue"> </span><span style="color:red">user_type_id</span><span style="color:blue">=</span>"<span style="color:blue">41</span>"<span style="color:blue"> </span><span style="color:red">schema_id</span><span style="color:blue">=</span>"<span style="color:blue">4</span>"<span style="color:blue"> </span><span style="color:red">max_length</span><span style="color:blue">=</span>"<span style="color:blue">5</span>"<span style="color:blue"> </span><span style="color:red">precision</span><span style="color:blue">=</span>"<span style="color:blue">16</span>"<span style="color:blue"> </span><span style="color:red">scale</span><span style="color:blue">=</span>"<span style="color:blue">7</span>"<span style="color:blue"> </span><span style="color:red">is_nullable</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">is_user_defined</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_assembly_type</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">default_object_id</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">rule_object_id</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_table_type</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> /></span>
<span style="color:blue"><</span><span style="color:#A31515">sys.types</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span>"</span><span style="color:blue">datetime2</span>"<span style="color:blue"> </span><span style="color:red">system_type_id</span><span style="color:blue">=</span>"<span style="color:blue">42</span>"<span style="color:blue"> </span><span style="color:red">user_type_id</span><span style="color:blue">=</span>"<span style="color:blue">42</span>"<span style="color:blue"> </span><span style="color:red">schema_id</span><span style="color:blue">=</span>"<span style="color:blue">4</span>"<span style="color:blue"> </span><span style="color:red">max_length</span><span style="color:blue">=</span>"<span style="color:blue">8</span>"<span style="color:blue"> </span><span style="color:red">precision</span><span style="color:blue">=</span>"<span style="color:blue">27</span>"<span style="color:blue"> </span><span style="color:red">scale</span><span style="color:blue">=</span>"<span style="color:blue">7</span>"<span style="color:blue"> </span><span style="color:red">is_nullable</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">is_user_defined</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_assembly_type</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">default_object_id</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">rule_object_id</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_table_type</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> /></span>
<span style="color:blue"><</span><span style="color:#A31515">sys.types</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span>"</span><span style="color:blue">datetimeoffset</span>"<span style="color:blue"> </span><span style="color:red">system_type_id</span><span style="color:blue">=</span>"<span style="color:blue">43</span>"<span style="color:blue"> </span><span style="color:red">user_type_id</span><span style="color:blue">=</span>"<span style="color:blue">43</span>"<span style="color:blue"> </span><span style="color:red">schema_id</span><span style="color:blue">=</span>"<span style="color:blue">4</span>"<span style="color:blue"> </span><span style="color:red">max_length</span><span style="color:blue">=</span>"<span style="color:blue">10</span>"<span style="color:blue"> </span><span style="color:red">precision</span><span style="color:blue">=</span>"<span style="color:blue">34</span>"<span style="color:blue"> </span><span style="color:red">scale</span><span style="color:blue">=</span>"<span style="color:blue">7</span>"<span style="color:blue"> </span><span style="color:red">is_nullable</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">is_user_defined</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_assembly_type</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">default_object_id</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">rule_object_id</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_table_type</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> /></span>
<span style="color:blue"><</span><span style="color:#A31515">sys.types</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span>"</span><span style="color:blue">tinyint</span>"<span style="color:blue"> </span><span style="color:red">system_type_id</span><span style="color:blue">=</span>"<span style="color:blue">48</span>"<span style="color:blue"> </span><span style="color:red">user_type_id</span><span style="color:blue">=</span>"<span style="color:blue">48</span>"<span style="color:blue"> </span><span style="color:red">schema_id</span><span style="color:blue">=</span>"<span style="color:blue">4</span>"<span style="color:blue"> </span><span style="color:red">max_length</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">precision</span><span style="color:blue">=</span>"<span style="color:blue">3</span>"<span style="color:blue"> </span><span style="color:red">scale</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_nullable</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">is_user_defined</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_assembly_type</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">default_object_id</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">rule_object_id</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_table_type</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> /></span>
<span style="color:blue"><</span><span style="color:#A31515">sys.types</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span>"</span><span style="color:blue">smallint</span>"<span style="color:blue"> </span><span style="color:red">system_type_id</span><span style="color:blue">=</span>"<span style="color:blue">52</span>"<span style="color:blue"> </span><span style="color:red">user_type_id</span><span style="color:blue">=</span>"<span style="color:blue">52</span>"<span style="color:blue"> </span><span style="color:red">schema_id</span><span style="color:blue">=</span>"<span style="color:blue">4</span>"<span style="color:blue"> </span><span style="color:red">max_length</span><span style="color:blue">=</span>"<span style="color:blue">2</span>"<span style="color:blue"> </span><span style="color:red">precision</span><span style="color:blue">=</span>"<span style="color:blue">5</span>"<span style="color:blue"> </span><span style="color:red">scale</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_nullable</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">is_user_defined</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_assembly_type</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">default_object_id</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">rule_object_id</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_table_type</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> /></span>
<span style="color:blue"><</span><span style="color:#A31515">sys.types</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span>"</span><span style="color:blue">int</span>"<span style="color:blue"> </span><span style="color:red">system_type_id</span><span style="color:blue">=</span>"<span style="color:blue">56</span>"<span style="color:blue"> </span><span style="color:red">user_type_id</span><span style="color:blue">=</span>"<span style="color:blue">56</span>"<span style="color:blue"> </span><span style="color:red">schema_id</span><span style="color:blue">=</span>"<span style="color:blue">4</span>"<span style="color:blue"> </span><span style="color:red">max_length</span><span style="color:blue">=</span>"<span style="color:blue">4</span>"<span style="color:blue"> </span><span style="color:red">precision</span><span style="color:blue">=</span>"<span style="color:blue">10</span>"<span style="color:blue"> </span><span style="color:red">scale</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_nullable</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">is_user_defined</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_assembly_type</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">default_object_id</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">rule_object_id</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">is_table_type</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> /></span>
</pre>
<p>
Note that I've referred to this as an XML <i>fragment</i> as it contains no root element. We can add one if we like
by modifying the FOR XML AUTO clause, but I want to keep this as vanilla as possible.
</p>
<p>
Now comes the tricky part - image the data you want to return as XML isn't available directly from a table or view,
but is selected by a stored procedure. How you you select that that data as XML?
</p>
<p>
Well, one option is simply to update the stored procedure such that it selects XML by adding a FOR XML clause within
the procedure itself. But that'll break existing callers of the stored procedure.
</p>
<p>
You could copy/paste the stored procedure into a new one, and alter the new one to select XML. But that creates a
maintenance burden as every time one stored procedure it changed, someone needs to remember to change the other
one too to keep them in sync.
</p>
<p>
You could wrap one stored procedure in another. So your new stored procedures creates a temporary table, or perhaps
a table variable, and does a INSERT INTO ... EXEC to populate the table with the results of the actual stored
procedure, then selects the results from the temporary table with a FOR XML clause. For a lot of people, this is
the option that'll work best. But it relies on the 'outer' stored procedure knowing the exact structure of the
rowset selected by the 'inner' stored procedure, which may change at some point in the future. It's a solution
which only works for the one stored procedure it's been coded for.
</p>
<p>
Isn't there a more flexible, generic solution? Yes there is...
</p>
<a name='more'></a>
<h2>Managed Stored Procedures</h2>
<p>
Since SQL Server 2005 we've been able to create stored procedures in managed code (i.e. in .NET) rather than just
in Transact-SQL. By creating the 'outer' stored procedure mentioned above in managed code, we can have it operate
in an entirely generic manner. This means we can call any existing stored procedure, without modifying it, and have
the results returned to us as XML. Here's how:
</p>
<p>
1. In Visual Studio (I'm using 2010, but 2008 or even 2005 should be fine) create a new Class Library project (I
called mine SqlServerClr) and re-name the default Class1.cs to StoredProcedures.cs.
</p>
<p>
2. Use Project Properties to ensure the Target Framework is .NET Framework 2.0.
</p>
<p>
3. Replace the contents of the StoredProcedures class with:
</p>
<pre>
<span style="color:blue">using</span><span> System;</span>
<span style="color:blue">using</span><span> System.Data;</span>
<span style="color:blue">using</span><span> System.Data.SqlClient;</span>
<span style="color:blue">using</span><span> System.Data.SqlTypes;</span>
<span style="color:blue">using</span><span> Microsoft.SqlServer.Server;</span>
<span style="color:blue">namespace</span><span> SqlServerClr</span>
<span>{</span>
<span style="color:gray">///</span><span style="color:green"> </span><span style="color:gray"><summary></span>
<span style="color:gray">///</span><span style="color:green"> Servers as a container for Common Language Runtime (CLR) stored procedures.</span>
<span style="color:gray">///</span><span style="color:green"> </span><span style="color:gray"></summary></span>
<span style="color:blue">public</span> <span style="color:blue">static</span> <span style="color:blue">partial</span> <span style="color:blue">class</span> <span style="color:#2B91AF">StoredProcedures</span>
{
<span style="color:gray">///</span><span style="color:green"> </span><span style="color:gray"><summary></span>
<span style="color:gray">///</span><span style="color:green"> Mimics the effect of a FOR XML AUTO clause having been applied to a stored procedure.</span>
<span style="color:gray">///</span><span style="color:green"> </span><span style="color:gray"></summary></span>
<span style="color:gray">///</span><span style="color:green"> </span><span style="color:gray"><param name="procedureName"></span><span style="color:green">The name of the stored procedure to execute.</span><span style="color:gray"></param></span>
<span style="color:gray">///</span><span style="color:green"> </span><span style="color:gray"><param name="parameters"></span><span style="color:green">Any parameters which should be passed to the procedure.</span><span style="color:gray"></param></span>
<span style="color:gray">///</span><span style="color:green"> </span><span style="color:gray"><remarks></span>
<span style="color:gray">///</span><span style="color:green"> </span><span style="color:gray"><para></span>
<span style="color:gray">///</span><span style="color:green"> The statement executed by this procedure is "EXECUTE [</span><span style="color:gray"><i></span><span style="color:green">procedureName</span><span style="color:gray"></i></span><span style="color:green">] </span>
<span style="color:gray">///</span><span style="color:green"> <span style="color:gray"><i></span><span style="color:green">parameters</span><span style="color:gray"></i></span><span style="color:green">"; the</span> text in </span><span style="color:gray"><i></span><span style="color:green">parameters</span><span style="color:gray"></i></span><span style="color:green"> should be formatted accordingly.</span>
<span style="color:gray">///</span><span style="color:green"> For example, "'07 Jun 1969'" or "1234, 'Spa%'".</span>
<span style="color:gray">///</span><span style="color:green"> </span><span style="color:gray"></para></span>
<span style="color:gray">///</span><span style="color:green"> </span><span style="color:gray"><para></span>
<span style="color:gray">///</span><span style="color:green"> This code is vulnerable to a SQL injection attack and should not be used in situations where</span>
<span style="color:gray">///</span><span style="color:green"> either </span><span style="color:gray"><i></span><span style="color:green">procedureName</span><span style="color:gray"></i></span><span style="color:green"> or </span><span style="color:gray"><i></span><span style="color:green">parameters</span><span style="color:gray"></i></span><span style="color:green"> are supplied by end-users without thorough</span>
<span style="color:gray">///</span><span style="color:green"> input validation.</span>
<span style="color:gray">///</span><span style="color:green"> </span><span style="color:gray"></para></span>
<span style="color:gray">///</span><span style="color:green"> </span><span style="color:gray"></remarks></span>
<span style="color:blue">public</span> <span style="color:blue">static</span> <span style="color:blue">void</span> ExecuteProcedureForXmlAuto(<span style="color:blue">string</span> procedureName, <span style="color:blue">string</span> parameters)
{
<span style="color:green">// validate our inputs</span>
<span style="color:blue">if</span> (<span style="color:blue">string</span>.IsNullOrEmpty(procedureName))
{
<span style="color:blue">throw</span> <span style="color:blue">new </span><span style="color:#2B91AF">ArgumentNullException</span>(<span style="color:#A31515">"procedureName"</span>);
}
<span style="color:blue">if</span> (procedureName.IndexOfAny(<span style="color:blue">new</span>[] {<span style="color:#A31515">'['</span>,<span style="color:#A31515">']'</span>,<span style="color:#A31515">'"'</span>}) != -1)
{
<span style="color:blue">throw</span> <span style="color:blue">new</span> <span style="color:#2B91AF">ArgumentException</span>(<span style="color:#A31515">"The name of the stored procedure must not be delimited."</span>, <span style="color:#A31515">"procedureName"</span>);
}
<span style="color:green">// establish a connection to the server within which stored procedure is running</span>
<span style="color:blue">using</span> (<span style="color:#2B91AF">SqlConnection</span> sqlConnection = <span style="color:blue">new</span> <span style="color:#2B91AF">SqlConnection</span>(<span style="color:#A31515">"context connection=true"</span>))
{
sqlConnection.Open();
<span style="color:green">// create a memory stream into which we can write our XML</span>
<span style="color:blue">using</span> (System.IO.<span style="color:#2B91AF">MemoryStream</span> memoryStream = <span style="color:blue">new</span> System.IO.<span style="color:#2B91AF">MemoryStream</span>())
{
<span style="color:green">// we'll need an XmlTextWriter with which to write into the memory stream</span>
System.Xml.<span style="color:#2B91AF">XmlTextWriter</span> xmlTextWriter = <span style="color:blue">new</span> System.Xml.<span style="color:#2B91AF">XmlTextWriter</span>(memoryStream, System.Text.<span style="color:#2B91AF">Encoding</span>.Unicode);
<span style="color:green">// create the command we're going to execute</span>
<span style="color:blue">string</span> commandText = <span style="color:blue">string</span>.Format(<span style="color:#A31515">"EXECUTE [{0}] {1}"</span>, procedureName, parameters);
<span style="color:blue">using</span> (<span style="color:#2B91AF">SqlCommand</span> sqlCommand = <span style="color:blue">new</span> <span style="color:#2B91AF">SqlCommand</span>(commandText, sqlConnection))
{
<span style="color:green">// execute the command, obtaining a SqlDataReader with which to read the results</span>
<span style="color:blue">using</span> (<span style="color:#2B91AF">SqlDataReader</span> sqlDataReader = sqlCommand.ExecuteReader())
{
<span style="color:green">// load the contents of the SqlDataReader, as XML, into a MemoryStream</span>
<span style="color:blue">while</span> (sqlDataReader.Read())
{
xmlTextWriter.WriteStartElement(procedureName);
<span style="color:blue">for</span> (<span style="color:blue">int</span> ordinal = 0; ordinal < sqlDataReader.FieldCount; ordinal++)
{
<span style="color:green">// omit null values from the output</span>
<span style="color:blue">if</span> (sqlDataReader.IsDBNull(ordinal) == <span style="color:blue">false</span>)
{
<span style="color:blue">string</span> attributeValue;
<span style="color:blue">switch</span> (sqlDataReader.GetDataTypeName(ordinal))
{
<span style="color:blue">case</span> <span style="color:#A31515">"datetime"</span>:
<span style="color:green">// we can't use "s", the ISO 8601 format, as it omits the three-digit</span>
<span style="color:green">// seconds fraction which FOR XML AUTO uses</span>
attributeValue = sqlDataReader.GetDateTime(ordinal).ToString(<span style="color:#A31515">"yyyy-MM-ddTHH:mm:ss.fff"</span>, System.Globalization.<span style="color:#2B91AF">CultureInfo</span>.CurrentCulture);
<span style="color:blue">break</span>;
<span style="color:blue">case</span> <span style="color:#A31515">"float"</span>:
<span style="color:green">// use a exponential format, with 15 digits after the decimal point</span>
attributeValue = sqlDataReader.GetDouble(ordinal).ToString(<span style="color:#A31515">"e15"</span>, System.Globalization.<span style="color:#2B91AF">CultureInfo</span>.CurrentCulture);
<span style="color:blue">break</span>;
<span style="color:blue">case</span> <span style="color:#A31515">"bit"</span>:
attributeValue = sqlDataReader.GetBoolean(ordinal) ? <span style="color:#A31515">"1"</span> : <span style="color:#A31515">"0"</span>;
<span style="color:blue">break</span>;
<span style="color:blue">default</span>:
attributeValue = sqlDataReader.GetValue(ordinal).ToString().Trim();
<span style="color:blue">break</span>;
}
xmlTextWriter.WriteAttributeString(sqlDataReader.GetName(ordinal), attributeValue);
}
}
xmlTextWriter.WriteEndElement();
}
xmlTextWriter.Flush();
memoryStream.Position = 0L;
}
}
<span style="color:green">// send a row of meta-data back to SQL Server</span>
<span style="color:blue">string</span> outputColumnName = <span style="color:#A31515">"XML_"</span> + System.<span style="color:#2B91AF">Guid</span>.NewGuid().ToString().ToUpper(System.Globalization.<span style="color:#2B91AF">CultureInfo</span>.CurrentCulture);
<span style="color:#2B91AF">SqlDataRecord</span> sqlDataRecord = <span style="color:blue">new</span> <span style="color:#2B91AF">SqlDataRecord</span>(<span style="color:blue">new</span> <span style="color:#2B91AF">SqlMetaData</span>(outputColumnName, <span style="color:#2B91AF">SqlDbType</span>.Xml));
<span style="color:#2B91AF">SqlContext</span>.Pipe.SendResultsStart(sqlDataRecord);
<span style="color:green">// now send the XML itself in a single row</span>
<span style="color:#2B91AF">SqlXml</span> sqlXml = <span style="color:blue">new</span> <span style="color:#2B91AF">SqlXml</span>(memoryStream);
sqlDataRecord.SetSqlXml(0, sqlXml);
<span style="color:#2B91AF">SqlContext</span>.Pipe.SendResultsRow(sqlDataRecord);
<span style="color:green">// end the sending of results back to SQL Server</span>
<span style="color:#2B91AF">SqlContext</span>.Pipe.SendResultsEnd();
}
}
}
}
}
</pre>
<p>
4. Build the project.
</p>
<p>
5. Fire up SQL Server Management Studio (or whatever Microsoft have re-named it to by the time you read this).
</p>
<p>
6. Open a new Query window, and select the database in which you wish the CLR stored procedure to be created.
</p>
<p>
7. Enable the CLR for your instance of SQL Server as follows (it doesn't matter if it's already enabled):
</p>
<pre>
<span style="color:blue">EXEC</span> <span style="color:maroon">sp_configure</span> <span style="color:red">'clr enabled'</span><span style="color:gray">,</span> <span style="color:red">'1'</span>
<span style="color:blue">GO</span>
<span style="color:blue">RECONFIGURE</span>
<span style="color:blue">GO</span>
</pre>
<p>
8. Load the assembly into SQL Server:
</p>
<pre>
<span style="color:blue">CREATE</span> <span style="color:blue">ASSEMBLY</span> SqlServerClrAssembly
<span style="color:blue">FROM</span> <span style="color:red">'C:\Users\Ian.Picknell\Documents\Blog\SQL Server\Execute Procedure For XML Auto\SqlServerClr\bin\Debug\SqlServerClr.dll'</span>
<span style="color:blue">WITH</span> <span style="color:blue">PERMISSION_SET</span> <span style="color:gray">=</span> <span style="color:blue">SAFE</span>
<span style="color:blue">GO</span>
</pre>
<p>
9. Provide SQL Server with details of the stored procedure within the assembly (specifically its name and parameters).
</p>
<pre>
<span style="color:blue">CREATE</span> <span style="color:blue">PROCEDURE</span> dbo<span style="color:gray">.</span>ExecuteProcedureForXmlAuto
<span style="color:gray">(</span>
@ProcedureName <span style="color:blue">NVARCHAR</span><span style="color:gray">(</span>128<span style="color:gray">),</span>
@Parameters <span style="color:blue">NVARCHAR</span><span style="color:gray">(</span>4000<span style="color:gray">)</span> <span style="color:gray">=</span> <span style="color:gray">NULL</span>
<span style="color:gray">)</span>
<span style="color:blue">AS</span> <span style="color:blue">EXTERNAL</span> NAME SqlServerClrAssembly<span style="color:gray">.</span>[SqlServerClr.StoredProcedures]<span style="color:gray">.</span>ExecuteProcedureForXmlAuto<span style="color:gray">;</span>
<span style="color:blue">GO</span>
</pre>
<p>
Note that the format of the 'AS' clause in this context is AS EXTERNAL NAME <i>assembly_name</i>.<i>class_name</i>.<i>method_name</i>.
The <i>class_name</i> component must be a valid SQL Server identifier - so you'll almost always have to use delimters here.
</p>
<p>
10. Call your target stored procedure 'through' the ExecuteProcedureForXmlAuto wrapper. For example:
</p>
<pre>
<span style="color:blue">EXEC</span> dbo<span style="color:gray">.</span>ExecuteProcedureForXmlAuto <span style="color:red">'sp_configure'</span>
<span style="color:blue">GO</span>
</pre>
<p>
The above statement produced the following:
</p>
<table style="font-family:MS Sans Serif,Sans-Serif">
<thead>
<tr><td>XML_880017D4-254D-4287-9B9E-496D10CA0C82</td></tr>
</thead>
<tbody>
<tr><td nowrap="nowrap" style="color:blue; text-decoration:underline;"><sp_configure name="allow updates" minimum="0" maximum="1" config_value="0" run_value="0" /><sp_configure name="backup compression default" minimum="0" maximum="1" config_value="0" run_value="0" /><sp_configure name="clr enabled" minimum="0" maximum="1" config_value="1" run_value="1" /><sp_configure name="cross db ownership chaining" minimum="0" maximum="1" config_value="0" run_value="0" /><sp_configure name="default language" minimum="0" maximum="9999" config_value="0" run_value="0" /><sp_configure name="filestream access level" minimum="0" maximum="2" config_value="0" run_value="0" /><sp_configure name="max text repl size (B)" minimum="-1" maximum="2147483647" config_value="65536" run_value="65536" /><sp_configure name="nested triggers" minimum="0" maximum="1" config_value="1" run_value="1" /><sp_configure name="remote access" minimum="0" maximum="1" config_value="1" run_value="1" /><sp_configure name="remote admin connections" minimum="0" maximum="1" config_value="0" run_value="0" /><sp_configure name="remote login timeout (s)" minimum="0" maximum="2147483647" config_value="20" run_value="20" /><sp_configure name="remote proc trans" minimum="0" maximum="1" config_value="0" run_value="0" /><sp_configure name="remote query timeout (s)" minimum="0" maximum="2147483647" config_value="600" run_value="600" /><sp_configure name="server trigger recursion" minimum="0" maximum="1" config_value="1" run_value="1" /><sp_configure name="show advanced options" minimum="0" maximum="1" config_value="0" run_value="0" /><sp_configure name="user options" minimum="0" maximum="32767" config_value="0" run_value="0" /></td></tr></tbody>
</table>
<p>
Clicking the hyperlink then gives you XML which is (to my eye) identical to that produced by a genuine FOR XML AUTO clause:
</p>
<pre>
<span style="color:blue"><</span><span style="color:#A31515">sp_configure</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span style="">"<span style="color:blue">allow updates</span>"<span style="color:blue"> </span><span style="color:red">minimum</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">maximum</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">config_value</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">run_value</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> /></span></span>
<span style="color:blue"><</span><span style="color:#A31515">sp_configure</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span style="">"<span style="color:blue">backup compression default</span>"<span style="color:blue"> </span><span style="color:red">minimum</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">maximum</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">config_value</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">run_value</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> /></span></span>
<span style="color:blue"><</span><span style="color:#A31515">sp_configure</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span style="">"<span style="color:blue">clr enabled</span>"<span style="color:blue"> </span><span style="color:red">minimum</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">maximum</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">config_value</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">run_value</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> /></span></span>
<span style="color:blue"><</span><span style="color:#A31515">sp_configure</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span style="">"<span style="color:blue">cross db ownership chaining</span>"<span style="color:blue"> </span><span style="color:red">minimum</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">maximum</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">config_value</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">run_value</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> /></span></span>
<span style="color:blue"><</span><span style="color:#A31515">sp_configure</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span style="">"<span style="color:blue">default language</span>"<span style="color:blue"> </span><span style="color:red">minimum</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">maximum</span><span style="color:blue">=</span>"<span style="color:blue">9999</span>"<span style="color:blue"> </span><span style="color:red">config_value</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">run_value</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> /></span></span>
<span style="color:blue"><</span><span style="color:#A31515">sp_configure</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span style="">"<span style="color:blue">filestream access level</span>"<span style="color:blue"> </span><span style="color:red">minimum</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">maximum</span><span style="color:blue">=</span>"<span style="color:blue">2</span>"<span style="color:blue"> </span><span style="color:red">config_value</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">run_value</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> /></span></span>
<span style="color:blue"><</span><span style="color:#A31515">sp_configure</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span style="">"<span style="color:blue">max text repl size (B)</span>"<span style="color:blue"> </span><span style="color:red">minimum</span><span style="color:blue">=</span>"<span style="color:blue">-1</span>"<span style="color:blue"> </span><span style="color:red">maximum</span><span style="color:blue">=</span>"<span style="color:blue">2147483647</span>"<span style="color:blue"> </span><span style="color:red">config_value</span><span style="color:blue">=</span>"<span style="color:blue">65536</span>"<span style="color:blue"> </span><span style="color:red">run_value</span><span style="color:blue">=</span>"<span style="color:blue">65536</span>"<span style="color:blue"> /></span></span>
<span style="color:blue"><</span><span style="color:#A31515">sp_configure</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span style="">"<span style="color:blue">nested triggers</span>"<span style="color:blue"> </span><span style="color:red">minimum</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">maximum</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">config_value</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">run_value</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> /></span></span>
<span style="color:blue"><</span><span style="color:#A31515">sp_configure</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span style="">"<span style="color:blue">remote access</span>"<span style="color:blue"> </span><span style="color:red">minimum</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">maximum</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">config_value</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">run_value</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> /></span></span>
<span style="color:blue"><</span><span style="color:#A31515">sp_configure</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span style="">"<span style="color:blue">remote admin connections</span>"<span style="color:blue"> </span><span style="color:red">minimum</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">maximum</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">config_value</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">run_value</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> /></span></span>
<span style="color:blue"><</span><span style="color:#A31515">sp_configure</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span style="">"<span style="color:blue">remote login timeout (s)</span>"<span style="color:blue"> </span><span style="color:red">minimum</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">maximum</span><span style="color:blue">=</span>"<span style="color:blue">2147483647</span>"<span style="color:blue"> </span><span style="color:red">config_value</span><span style="color:blue">=</span>"<span style="color:blue">20</span>"<span style="color:blue"> </span><span style="color:red">run_value</span><span style="color:blue">=</span>"<span style="color:blue">20</span>"<span style="color:blue"> /></span></span>
<span style="color:blue"><</span><span style="color:#A31515">sp_configure</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span style="">"<span style="color:blue">remote proc trans</span>"<span style="color:blue"> </span><span style="color:red">minimum</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">maximum</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">config_value</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">run_value</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> /></span></span>
<span style="color:blue"><</span><span style="color:#A31515">sp_configure</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span style="">"<span style="color:blue">remote query timeout (s)</span>"<span style="color:blue"> </span><span style="color:red">minimum</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">maximum</span><span style="color:blue">=</span>"<span style="color:blue">2147483647</span>"<span style="color:blue"> </span><span style="color:red">config_value</span><span style="color:blue">=</span>"<span style="color:blue">600</span>"<span style="color:blue"> </span><span style="color:red">run_value</span><span style="color:blue">=</span>"<span style="color:blue">600</span>"<span style="color:blue"> /></span></span>
<span style="color:blue"><</span><span style="color:#A31515">sp_configure</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span style="">"<span style="color:blue">server trigger recursion</span>"<span style="color:blue"> </span><span style="color:red">minimum</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">maximum</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">config_value</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">run_value</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> /></span></span>
<span style="color:blue"><</span><span style="color:#A31515">sp_configure</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span style="">"<span style="color:blue">show advanced options</span>"<span style="color:blue"> </span><span style="color:red">minimum</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">maximum</span><span style="color:blue">=</span>"<span style="color:blue">1</span>"<span style="color:blue"> </span><span style="color:red">config_value</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">run_value</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> /></span></span>
<span style="color:blue"><</span><span style="color:#A31515">sp_configure</span><span style="color:blue"> </span><span style="color:red">name</span><span style="color:blue">=</span><span style="">"<span style="color:blue">user options</span>"<span style="color:blue"> </span><span style="color:red">minimum</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">maximum</span><span style="color:blue">=</span>"<span style="color:blue">32767</span>"<span style="color:blue"> </span><span style="color:red">config_value</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> </span><span style="color:red">run_value</span><span style="color:blue">=</span>"<span style="color:blue">0</span>"<span style="color:blue"> /></span></span>
</pre>
<h2>Conclusion</h2>
<p>
There probably aren't a huge number of scenarios where you have an existing stored procedure which produces a rowset, but now
need to get XML from it instead. I originally wrote a version of the above CLR stored procedure for use with BizTalk Server 2006,
which required that data be returned from SQL Server as XML and my brief was that the messaging application I was developing must
be capable of sourcing data from any existing stored procedure. ExecuteProcedureForXmlAuto fit the bill perfectly.
</p>
<h2>See Also</h2>
<ul>
<li><a href="http://ianpicknell.blogspot.com/2010/04/validating-integers-in-sql-server.html">Validating Integers in SQL Server</a></li>
</ul>Ian Picknellhttp://www.blogger.com/profile/11237307149515983183noreply@blogger.com0tag:blogger.com,1999:blog-7929053599129622940.post-76668972331181916592011-03-29T23:08:00.001+01:002011-03-29T23:14:33.593+01:00Implementing IDispatchMessageInspector<h2>Introduction</h2>
<p>
Have you ever needed to change the contract used by WCF service, but were held back by the impact it would have on
existing clients? Sure, you could just ask your clients to update their service references and tweak the code which
calls your service, but that requires that all your clients (of which there might be many) to do a code release on
the same day you release your new server - not ideal.
</p>
<p>
One way to resolve this would be to leave the existing contract alone, but to create a second modified contract exposed
via a new endpoint. That way clients could upgrade to the new contract (and the new endpoint) at their leisure. This will
probably create some redundancy in the server-side code, as you'd have very similar code at both endpoints, although you
could probably refactor any common code such that it was shared. There's also a risk that this approach would lead to
end-point proliferation over time as you make various tweaks to the contract. So, is there an an alternative?
</p>
<p>
One approach which I used recently, and which is the subject of this post, is to <b>intercept the calls which use the old
contract and transform them into a call to the new contract on-the-fly</b>. WCF has an extensibility interface designed just
for this purpose - <b>IDispatchMessageInspector</b>. Note that, despite its name, implementing this interface allows us to both
inspect <i>and modify</i> inbound and outbound messages.
</p>
<p>
To demonstrate this approach I'm going to:
</p>
<ul>
<li>create a WCF Service exposing a particular interface,</li>
<li>create a client which consumes this service,</li>
<li>modify the service in a manner which is not backwards compatible,</li>
<li>show that the client can no longer consume the service,</li>
<li>implement IDispatchMessageInspector in the service to modify calls on the fly, and</li>
<li>show that the client <b>can</b> now consume the service again.</li>
</ul>
<h2>Creating the WCF Service</h2>
<p>
I'm not going to go through this step-by-step. If you're interested in implementing IDispatchMessageInspector to modify
WCF messages on the fly, then I'm sure you'll have created a WCF service or two in your time. So I'll just be providing
source code listing of the various files:
</p>
<ul>
<li>IEmployeeService (the service contract)</li>
<li>EmployeeService (an implementation of that contract)</li>
<li>Employee (a data contract exposed via the service)</li>
<li>Server (contains the console application's entry point and service host provider)</li>
</ul>
<h5>IEmployeeService</h5>
<pre>
<span style="color:blue">using</span> System.ServiceModel;
<span style="color:blue">namespace</span> WcfService
{
[<span style="color:#2B91AF">ServiceContract</span>]
<span style="color:blue">public</span> <span style="color:blue">interface</span> <span style="color:#2B91AF">IEmployeeService</span>
{
[<span style="color:#2B91AF">OperationContract</span>]
<span style="color:blue">void</span> SaveEmployee(<span style="color:#2B91AF">Employee</span> employee);
}
}
</pre>
<h5>EmployeeService</h5>
<pre>
<span style="color:blue">using</span> System;
<span style="color:blue">namespace</span> WcfService
{
<span style="color:blue">public</span> <span style="color:blue">class</span> <span style="color:#2B91AF">EmployeeService</span> : <span style="color:#2B91AF">IEmployeeService</span>
{
<span style="color:blue">public</span> <span style="color:blue">void</span> SaveEmployee(<span style="color:#2B91AF">Employee</span> employee)
{
<span style="color:#2B91AF">Console</span>.WriteLine(<span style="color:#A31515">"Saved {0}"</span>, employee);
}
}
}
</pre>
<h5>Employee</h5>
<pre>
<span style="color:blue">using</span> System;
<span style="color:blue">using</span> System.Runtime.Serialization;
<span style="color:blue">namespace</span> WcfService
{
[<span style="color:#2B91AF">DataContract</span>]
<span style="color:blue">public</span> <span style="color:blue">class</span> <span style="color:#2B91AF">Employee</span>
{
[<span style="color:#2B91AF">DataMember</span>]
<span style="color:blue">public</span> <span style="color:blue">string</span> Name { <span style="color:blue">get</span>; <span style="color:blue">set</span>; }
[<span style="color:#2B91AF">DataMember</span>]
<span style="color:blue">public</span> <span style="color:#2B91AF">DateTime</span> DateOfBirth { <span style="color:blue">get</span>; <span style="color:blue">set</span>; }
[<span style="color:#2B91AF">DataMember</span>]
<span style="color:blue">public</span> <span style="color:blue">char</span> Gender { <span style="color:blue">get</span>; <span style="color:blue">set</span>; }
<span style="color:blue">public</span> <span style="color:blue">override</span> <span style="color:blue">string</span> ToString()
{
<span style="color:blue">return</span> <span style="color:blue">string</span>.Format(<span style="color:#A31515">"{0} (DOB:{1} Gender:{2})"</span>, Name, DateOfBirth.ToShortDateString(), Gender);
}
}
}
</pre>
<h5>Server</h5>
<pre>
<span style="color:blue">using</span> System;
<span style="color:blue">using</span> System.ServiceModel;
<span style="color:blue">using</span> System.ServiceModel.Description;
<span style="color:blue">namespace</span> WcfService
{
<span style="color:blue">class</span> <span style="color:#2B91AF">Server</span>
{
<span style="color:blue">static</span> <span style="color:blue">void</span> Main()
{
<span style="color:green">// create a host and add an end-point to it</span>
<span style="color:#2B91AF">ServiceHost</span> serviceHost = <span style="color:blue">new</span> <span style="color:#2B91AF">ServiceHost</span>(<span style="color:blue">typeof</span>(<span style="color:#2B91AF">EmployeeService</span>));
<span style="color:#2B91AF">ContractDescription</span> contractDescription = <span style="color:#2B91AF">ContractDescription</span>.GetContract(<span style="color:blue">typeof</span>(<span style="color:#2B91AF">IEmployeeService</span>), <span style="color:blue">typeof</span>(<span style="color:#2B91AF">EmployeeService</span>));
<span style="color:#2B91AF">NetTcpBinding</span> netTcpBinding = <span style="color:blue">new</span> <span style="color:#2B91AF">NetTcpBinding</span>();
<span style="color:#2B91AF">EndpointAddress</span> endpointAddress = <span style="color:blue">new</span> <span style="color:#2B91AF">EndpointAddress</span>(<span style="color:#A31515">"net.tcp://localhost:8001/EmployeeService"</span>);
<span style="color:#2B91AF">ServiceEndpoint</span> serviceEndpoint = <span style="color:blue">new</span> <span style="color:#2B91AF">ServiceEndpoint</span>(contractDescription, netTcpBinding, endpointAddress);
serviceHost.Description.Endpoints.Add(serviceEndpoint);
<span style="color:green">// add a meta-data end-point</span>
<span style="color:#2B91AF">ServiceMetadataBehavior</span> serviceMetadataBehavior = <span style="color:blue">new</span> <span style="color:#2B91AF">ServiceMetadataBehavior</span>();
serviceMetadataBehavior.HttpGetEnabled = <span style="color:blue">true</span>;
serviceMetadataBehavior.HttpGetUrl = <span style="color:blue">new</span> <span style="color:#2B91AF">Uri</span>(<span style="color:#A31515">"http://localhost:8002/EmployeeService/mex"</span>);
serviceHost.Description.Behaviors.Add(serviceMetadataBehavior);
<span style="color:green">// open the host</span>
serviceHost.Open();
<span style="color:#2B91AF">Console</span>.WriteLine(<span style="color:#A31515">"Press ENTER to terminate service."</span>);
<span style="color:green">// close the host once the user hits ENTER</span>
<span style="color:#2B91AF">Console</span>.ReadLine();
serviceHost.Close();
}
}
}
</pre>
<p>
Okay, so that the server-side code sorted. As you can see it's all fairly basic stuff: the service merely accepts an
Employee into the SaveEmployee method of its IEmployeeService and writes details of that Employee to the console.
</p>
<h2>Creating the WCF Client</h2>
<p>
The client is just as trivial.<a name='more'></a> It consists of a single Client class (in addition to all the auto-generated Service
Reference stuff and App.config) which creates an instance of the proxy Employee class and asks the service to save it.
</p>
<h5>Client</h5>
<pre>
<span style="color:blue">using</span> System;
<span style="color:blue">using</span> WcfClient.EmployeeServiceProxy;
<span style="color:blue">namespace</span> WcfClient
{
<span style="color:blue">class</span> <span style="color:#2B91AF">Client</span>
{
<span style="color:blue">static</span> <span style="color:blue">void</span> Main()
{
<span style="color:#2B91AF">EmployeeServiceClient</span> employeeServiceClient = <span style="color:blue">new</span> <span style="color:#2B91AF">EmployeeServiceClient</span>();
employeeServiceClient.Open();
<span style="color:#2B91AF">Employee</span> employee = <span style="color:blue">new</span> <span style="color:#2B91AF">Employee</span>
{
Name = <span style="color:#A31515">"Jane Armstrong"</span>,
DateOfBirth = <span style="color:blue">new</span> <span style="color:#2B91AF">DateTime</span>(1969, 7, 12),
Gender = <span style="color:#A31515">'F'</span>
};
<span style="color:blue">try</span>
{
<span style="color:#2B91AF">Console</span>.WriteLine(<span style="color:#A31515">"Saving {0} (DOB:{1} Gender:{2})"</span>, employee.Name, employee.DateOfBirth.ToShortDateString(), employee.Gender);
employeeServiceClient.SaveEmployee(employee);
employeeServiceClient.Close();
}
<span style="color:blue">catch</span> (<span style="color:#2B91AF">Exception</span> ex)
{
<span style="color:#2B91AF">Console</span>.WriteLine(ex);
employeeServiceClient.Abort();
}
<span style="color:#2B91AF">Console</span>.WriteLine(<span style="color:#A31515">"Press ENTER to terminate client."</span>);
<span style="color:#2B91AF">Console</span>.ReadLine();
}
}
}
</pre>
<p>
I generated the EmployeeServiceProxy class and App.config simply by using the Add Service Reference dialog, pointing it at the meta-data
end-point of the service, http://localhost:8002/EmployeeService/mex.
</p>
<h2>Testing the Client and Service</h2>
<p>
Okay, now we're ready to show how this client and service interact. They're both console applications, so you can just
start them both up from the command-line. Let's start the service first:
</p>
<pre class="console">
> WcfService\bin\debug\WcfService.exe
Press ENTER to terminate service.
</pre>
<p>
and then the client:
</p>
<pre class="console">
> WcfClient\bin\Debug\WcfClient.exe
Saving Jane Armstrong (DOB:12/07/1969 Gender:F)
Press ENTER to terminate client.
</pre>
<p>
If you glance back at the service, you'll notice that it now displays:
</p>
<pre class="console">
Saved Jane Armstrong (DOB:12/07/1969 Gender:F)
</pre>
<p>
So, our extremely simple WCF client/service application works fine. <b>Now let's break it.</b>
</p>
<h2>Changing the Contract</h2>
<p>
In an ideal world, WCF contracts would be immutable. But life's not like that. Requirements change. In my scenario I'm
going to imagine that the client/service above underwent a code review and someone said: "You're using a <i>char</i> for
Gender? Hmmm. That allows a client to send a Gender of 'X' or some other invalid value. Perhaps an enumeration might
be better?" The problem is, you've already deployed you client and have dozens of people using it. You really don't want
to have to re-deploy it.
</p>
<p>
Before we look at how implementing IDispatchMessageInspector can solve this issue, let's change the contract (on
the service-side only) and see what happens when a client calls it.
</p>
<p>
We're going to need a Gender enumeration:
</p>
<pre>
<span style="color:blue">namespace</span> WcfService
{
<span style="color:blue">public</span> <span style="color:blue">enum</span> <span style="color:#2B91AF">Gender</span>
{
Male,
Female
}
}
</pre>
<p>
And we're going to need to change Employee to use this enumeration:
</p>
<pre>
<span style="color:blue">using</span> System;
<span style="color:blue">using</span> System.Runtime.Serialization;
<span style="color:blue">namespace</span> WcfService
{
[<span style="color:#2B91AF">DataContract</span>]
<span style="color:blue">public</span> <span style="color:blue">class</span> <span style="color:#2B91AF">Employee</span>
{
...
[<span style="color:#2B91AF">DataMember</span>]
<span style="color:blue">public</span> <span style="color:#2B91AF">Gender</span> Gender { <span style="color:blue">get</span>; <span style="color:blue">set</span>; }
...
}
}
</pre>
<p>
Once we've re-compiled the service, we can fire it up:
</p>
<pre class="console">
> WcfService\bin\Debug\WcfService.exe
Press ENTER to terminate service.
</pre>
<p>
and watch what happens when the client (which is still sending a char Gender) tries to save an employee.
</p>
<pre class="console">
> WcfClient\bin\Debug\WcfClient.exe
Saving Jane Armstrong (DOB:12/07/1969 Gender:F)
System.ServiceModel.FaultException: The formatter threw an exception while tryin
g to deserialize the message: There was an error while trying to deserialize par
ameter http://tempuri.org/:employee. The InnerException message was '<span style="color:Red">Invalid enu
m value '70' cannot be deserialized into type 'WcfService.Gender'</span>. Ensure that t
he necessary enum values are present and are marked with EnumMemberAttribute att
ribute if the type has DataContractAttribute attribute.'. Please see InnerExcep
tion for more details.
</pre>
<p>
Exactly as expected, the client can't pass a char as the service is expecting a member of the Gender enumeration.
(In case you're wondering, the enum value 70 in the message above comes from the ASCII value for the letter 'F'.)
</p>
<h2>Enter IDispatchMessageInspector</h2>
<p>
WCF allows us to both inspect and modify messages after they've been received by the service but, crucially,
<i>before</i> they've been forwarded to an operation. This is achieved by providing a class which implements
IDispatchMessageInspector and adding it as a service behaviour. Adding it as a service behaviour can be achieved
in code or via a configuration file. As my service is code-only so far, I'm going to stick with that approach.
</p>
<p>
A skeleton IDispatchMessageInspector implementation would look like this:
</p>
<pre>
<span style="color:blue">using</span> System;
<span style="color:blue">using</span> System.IO;
<span style="color:blue">using</span> System.ServiceModel;
<span style="color:blue">using</span> System.ServiceModel.Channels;
<span style="color:blue">using</span> System.ServiceModel.Dispatcher;
<span style="color:blue">using</span> System.Xml;
<span style="color:blue">using</span> System.Xml.XPath;
<span style="color:blue">using</span> System.Xml.Xsl;
<span style="color:blue">namespace</span> WcfService
{
<span style="color:blue">public</span> <span style="color:blue">class</span> <span style="color:#2B91AF">MessageInspector</span> : <span style="color:#2B91AF">IDispatchMessageInspector</span>
{
<span style="color:blue">public</span> <span style="color:blue">object</span> AfterReceiveRequest(<span style="color:blue">ref</span> <span style="color:#2B91AF">Message</span> request, <span style="color:#2B91AF">IClientChannel</span> channel, <span style="color:#2B91AF">InstanceContext</span> instanceContext)
{
<span style="color:green">// inspect and/or modify the request</span>
<span style="color:green">// return any object which will be passed to BeforeSendReply to correlate requests and replies</span>
<span style="color:blue">return</span> <span style="color:blue">null</span>;
}
<span style="color:blue">public</span> <span style="color:blue">void</span> BeforeSendReply(<span style="color:blue">ref</span> <span style="color:#2B91AF"> Message</span> reply, <span style="color:blue">object</span> correlationState)
{
<span style="color:green">// inspect and/or modify the reply</span>
}
}
}
</pre>
<p>
</p>
(In case you're wondering about all those redundant <code>using</code> statements - we'll need them later when we provide some real implementations.)
<p>
The object you return in AfterReceivedRequest <i>for a given request</i> will be passed to BeforeSendReply when processing the matching reply. I've used this in the past to optionally transform the
response - i.e. AfterReceiveRequest inspects the request and establishes whether it came from a legacy client (in our example, that would be one which is passing a char Gender) and returns true/false so
BeforeSendReply knows whether it needs to transform the reply such that it can be processed by a legacy client (in our example, the reply hasn't changed, so there's no need to do anything in BeforeSendReply).
</p>
<p>
You'll notice that the request parameter passed to AfterReceiveRequest, and the reply parameter passed to BeforeSendRepy, are passed by reference. This is because the body of a Message object is immutable -
it can't be modified. If you want to modify the message (as we do) you need to create a new Message object with the message you want to use instead of the original and assign <i>that</i> to the request (or reply)
parameter. One other wrinkle is that the body of a Message object can only be read once. So if you read it in AfterReceiveRequest (just to log its contents to a file, for example) then it can't be read again
by the rest of the WCF stack. Fun, eh? We'll ignore this issue for the moment (don't worry, we'll get back to it) and plough on. If you're just doing a straight transformation this isn't a problem anyway: you
read the Message body (once) and create a new Message containing the body you want to be passed to the receiving operation.
</p>
<p>
A Message object essentially represents a SOAP message and, like a SOAP message, contains both headers and a body. In order for us to write some code which transforms the message, we're going to need to
<i>see</i> what the message looks like as it arrives from the client. So we need an <b>IDispatchMessageInspector</b> implementation to inspect the message. We can simply use the template above, and change the
<b>AfterReceiveRequest</b> method to read:
</p>
<pre>
<span style="color:blue">public</span> <span style="color:blue">object</span> AfterReceiveRequest(<span style="color:blue">ref</span> <span style="color:#2B91AF">Message</span> request, <span style="color:#2B91AF">IClientChannel</span> channel, <span style="color:#2B91AF">InstanceContext</span> instanceContext)
{
<span style="color:green">// write the request to the console (only works for small messages)</span>
<span style="color:#2B91AF">Console</span>.WriteLine(request.ToString());
<span style="color:green">// we don't need to pass a correlation state</span>
<span style="color:blue">return</span> <span style="color:blue">null</span>;
}
</pre>
<p>
This code breaks one of the rules I mentioned above whereby a message can only be read once - it seems that ToString is allowed to ready the message, up to a point. Although I've not seen it documented anywhere I
believe that there must be a small internal buffer which is used by the ToString method such that it can output a representation of the string without consuming its contents. For a larger message, the output
produced by ToString would contain elipses (...) to indicate the message body.
</p>
<p>
To cause our MessageInspector to be invoked, we need to create a new class which implememnts <b>IEndpointBehavior</b> and add our MessageInspector to the list of IMessageInspectors when its ApplyDispatchBehavior
method is called:
</p>
<pre>
<span style="color:blue">using</span> System.ServiceModel.Channels;
<span style="color:blue">using</span> System.ServiceModel.Description;
<span style="color:blue">using</span> System.ServiceModel.Dispatcher;
<span style="color:blue">namespace</span> WcfService
{
<span style="color:blue">public</span> <span style="color:blue">class</span> <span style="color:#2B91AF">EndpointBehaviour</span> : <span style="color:#2B91AF">IEndpointBehavior</span>
{
<span style="color:blue">public</span> <span style="color:blue">void</span> AddBindingParameters(<span style="color:#2B91AF">ServiceEndpoint</span> endpoint, <span style="color:#2B91AF">BindingParameterCollection</span> bindingParameters)
{
<span style="color:green">// do nothing</span>
}
<span style="color:blue">public</span> <span style="color:blue">void</span> ApplyClientBehavior(<span style="color:#2B91AF">ServiceEndpoint</span> endpoint, <span style="color:#2B91AF">ClientRuntime</span> clientRuntime)
{
<span style="color:green">// do nothing</span>
}
<span style="color:blue">public</span> <span style="color:blue">void</span> ApplyDispatchBehavior(<span style="color:#2B91AF">ServiceEndpoint</span> endpoint, <span style="color:#2B91AF">EndpointDispatcher</span> endpointDispatcher)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(<span style="color:blue">new</span> <span style="color:#2B91AF">MessageInspector</span>());
}
<span style="color:blue">public</span> <span style="color:blue">void</span> Validate(<span style="color:#2B91AF">ServiceEndpoint</span> endpoint)
{
<span style="color:green">// do nothing</span>
}
}
}
</pre>
<p>
To cause our EndpointBehaviour to be used, we need to add it to the list of end-point behaviours by adding the following line to Server.cs, immediately after the serviceEndpoint has been initialised:
</p>
<pre>
serviceEndpoint.Behaviors.Add(<span style="color:blue">new</span> <span style="color:#2B91AF">EndpointBehaviour</span>());
</pre>
<p>
When we start the WcfService and WcfClient this time, the WcfClient will still fail (we haven't corrected the actual problem yet) but at least we'll see in the WcfService what the SOAP message sent by the
WcfClient was:
</p>
<pre class="console">
Press ENTER to terminate service.
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w
3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">http://tempuri.org/IEmployeeService/SaveEmplo
yee</a:Action>
<a:MessageID>urn:uuid:796c06e4-03b9-4012-899c-935199d5e81e</a:MessageID>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand="1">net.tcp://localhost:8001/EmployeeService</a:To>
</s:Header>
<s:Body>
<SaveEmployee xmlns="http://tempuri.org/">
<employee xmlns:b="http://schemas.datacontract.org/2004/07/WcfService" xml
ns:i="http://www.w3.org/2001/XMLSchema-instance">
<b:DateOfBirth>1969-07-12T00:00:00</b:DateOfBirth>
<b:Gender>70</b:Gender>
<b:Name>Jane Armstrong</b:Name>
</employee>
</SaveEmployee>
</s:Body>
</s:Envelope>
</pre>
<p>
The change we want to make to this Message is trivial: we simply need to change <code><b:Gender>70</b:Gender></code> to <code><b:Gender>Female</b:Gender></code>. Now, we
<i>could</i> use XSLT to do this (which is what I'd typically do if the transformation required were non-trivial), but let's just use an XmlDocument for the modification.
</p>
<p>
So, we'll modify of the <b>AfterReceiveRequest</b> so it reads:
</p>
<pre>
<span style="color:blue">public</span> <span style="color:blue">object</span> AfterReceiveRequest(<span style="color:blue">ref</span> <span style="color:#2B91AF">Message</span> request, <span style="color:#2B91AF">IClientChannel</span> channel, <span style="color:#2B91AF">InstanceContext</span> instanceContext)
{
<span style="color:green">// copy the contents of the original message's body into a MemoryStream</span>
<span style="color:#2B91AF">MemoryStream</span> memoryStream = <span style="color:blue">new</span> <span style="color:#2B91AF">MemoryStream</span>();
<span style="color:#2B91AF">XmlDictionaryWriter</span> xmlDictionaryWriter = <span style="color:#2B91AF">XmlDictionaryWriter</span>.CreateTextWriter(memoryStream);
request.WriteBodyContents(xmlDictionaryWriter);
xmlDictionaryWriter.Flush();
<span style="color:green">// write the contents of the MemoryStream to the Console</span>
memoryStream.Position = 0L;
<span style="color:#2B91AF">Console</span>.WriteLine(<span style="color:blue">new</span> <span style="color:#2B91AF">StreamReader</span>(memoryStream).ReadToEnd());
<span style="color:green">// load the stream into an XmlDocument</span>
memoryStream.Position = 0L;
<span style="color:#2B91AF">XmlDocument</span> xmlDocument = <span style="color:blue">new</span> <span style="color:#2B91AF">XmlDocument</span>();
xmlDocument.Load(memoryStream);
<span style="color:green">// register the namespaces we'll need to xpath to the <gender> node</span>
<span style="color:#2B91AF">XmlNamespaceManager</span> xmlNamespaceManager = <span style="color:blue">new</span> <span style="color:#2B91AF">XmlNamespaceManager</span>(xmlDocument.NameTable);
xmlNamespaceManager.AddNamespace(<span style="color:#A31515">"t"</span>, <span style="color:#A31515">"http://tempuri.org/"</span>);
xmlNamespaceManager.AddNamespace(<span style="color:#A31515">"b"</span>, <span style="color:#A31515">"http://schemas.datacontract.org/2004/07/WcfService"</span>);
<span style="color:green">// modify the <gender> node</span>
<span style="color:#2B91AF">XmlNode</span> genderNode = xmlDocument.SelectSingleNode(<span style="color:#A31515">"/t:SaveEmployee/t:employee/b:Gender"</span>, xmlNamespaceManager);
<span style="color:blue">if</span> (genderNode != <span style="color:blue">null</span>)
{
genderNode.InnerText = genderNode.InnerText == <span style="color:#A31515">"70"</span> ? <span style="color:#A31515">"Female"</span> : <span style="color:#A31515">"Male"</span>;
}
<span style="color:green">// copy the modified XmlDocument back into the MemoryStream</span>
memoryStream.Position = 0L;
xmlDocument.Save(memoryStream);
<span style="color:green">// write the contents of the MemoryStream to the Console</span>
memoryStream.Position = 0L;
<span style="color:#2B91AF">Console</span>.WriteLine(<span style="color:blue">new</span> <span style="color:#2B91AF">StreamReader</span>(memoryStream).ReadToEnd());
<span style="color:green">// create a new Message from the MemoryStream (copying the original's properties and headers)</span>
memoryStream.Position = 0L;
<span style="color:#2B91AF">XmlReader</span> xmlReader = <span style="color:#2B91AF">XmlReader</span>.Create(memoryStream);
<span style="color:#2B91AF">Message</span> modifiedRequest = <span style="color:#2B91AF">Message</span>.CreateMessage(request.Headers.MessageVersion, request.Headers.Action, xmlReader);
modifiedRequest.Properties.CopyProperties(request.Properties);
modifiedRequest.Headers.CopyHeadersFrom(request);
<span style="color:green">// re-assign the request parameter to our new modified request</span>
request = modifiedRequest;
<span style="color:green">// return</span>
<span style="color:blue">return</span> <span style="color:blue">null</span>;
}
</pre>
<p>
If you're like me you'll hate this code. Let's count the number of copies of the message are required: there's obviously the original message (one), then we copy it into a MemoryStream (two), then we copy
that into an XmlDocument (three); once we've modified it, we copy it back into a MemoryStream (four) and finally into a new Message (five). Really? We need to have five copies of the message? In reality
there we only four as we re-used the MemoryStream. Also, the final Message we create is passed XmlReader which wraps the MemoryStream - the Message doesn't have a separate copy of this data, it simply
reads it from the MemoryStream via the XmlReader when necessarily, not at construction time. So there are actually only three copies the message in memory at any one time. But that's still two too many
but my count. In this example the message is clearly tiny, but imagine if it weren't. Those extra copies of the message could really hurt scalability.
</p>
<p>
Before we look at alternative ways to code this, let's just see if it actually works first. So we'll fire up WcfService:
</p>
<pre class="console">
Press ENTER to terminate service.
</pre>
<p>
And then WcfClient:
</p>
<pre class="console">
Saving Jane Armstrong (DOB:12/07/1969 Gender:F)
Press ENTER to terminate client.
</pre>
<p>
Note that the client didn't experience an exception this time. Glancing back at the WcfService we now see:
</p>
<pre class="console">
Press ENTER to terminate service.
<span style="color:#E26B0A"><SaveEmployee xmlns="http://tempuri.org/"><employee xmlns:b="http://schemas.data
contract.org/2004/07/WcfService" xmlns:i="http://www.w3.org/2001/XMLSchema-insta
nce"><b:DateOfBirth>1969-07-12T00:00:00</b:DateOfBirth><span style="font-weight:bold;color:#974706"><b:Gender>70</b:Gender></span><b
:Name>Jane Armstrong</b:Name></employee></SaveEmployee></span>
<span style="color:#76933C"><SaveEmployee xmlns="http://tempuri.org/">
<employee xmlns:b="http://schemas.datacontract.org/2004/07/WcfService" xmlns:i
="http://www.w3.org/2001/XMLSchema-instance">
<b:DateOfBirth>1969-07-12T00:00:00</b:DateOfBirth>
<span style="font-weight:bold;color:#4F6228"><b:Gender>Female</b:Gender></span>
<b:Name>Jane Armstrong</b:Name>
</employee>
</SaveEmployee></span>
Saved Jane Armstrong (DOB:12/07/1969 Gender:Female)
</pre>
<p>
(Yours won't be in colour - I've colour-coded the output to explain what you're seeing). The <span style="color:#E26B0A">orange</span> XML is the original message received by WcfService. You'll note that
Gender is 70 (the ASCII value for 'F'). The <span style="color:#76933C">green</span> XML is once we've translate it; after translation the Gender is Female. You'll also note that the Saved message shows that
the deserialised Employee object contains the correct value for Gender.
</p>
<h2>Alternate Implementations</h2>
<p>
So, are there any better solutions? Well, you might try using the XPathNavigator returned by the MessageBuffer's CreateNavigator() method:
</p>
<pre>
<span style="color:blue">public</span> <span style="color:blue">object</span> AfterReceiveRequest(<span style="color:blue">ref</span> <span style="color:#2B91AF">Message</span> request, <span style="color:#2B91AF">IClientChannel</span> channel, <span style="color:#2B91AF">InstanceContext</span> instanceContext)
{
<span style="color:green">// copy the original request into a buffer which we can read and modify</span>
<span style="color:#2B91AF">MessageBuffer</span> messageBuffer = request.CreateBufferedCopy(<span style="color:#2B91AF">Int32</span>.MaxValue);
<span style="color:green">// do the necessary prep work to navigate over the request</span>
<span style="color:#2B91AF">XPathNavigator</span> xpathNagivator = messageBuffer.CreateNavigator();
<span style="color:#2B91AF">XmlNamespaceManager</span> xmlNamespaceManager = <span style="color:blue">new</span> <span style="color:#2B91AF">XmlNamespaceManager</span>(xpathNagivator.NameTable);
xmlNamespaceManager.AddNamespace(<span style="color:#A31515">"s"</span>, <span style="color:#A31515">"http://www.w3.org/2003/05/soap-envelope"</span>);
xmlNamespaceManager.AddNamespace(<span style="color:#A31515">"t"</span>, <span style="color:#A31515">"http://tempuri.org/"</span>);
xmlNamespaceManager.AddNamespace(<span style="color:#A31515">"b"</span>, <span style="color:#A31515">"http://schemas.datacontract.org/2004/07/WcfService"</span>);
<span style="color:green">// modify the <gender> node</span>
<span style="color:#2B91AF">XPathNavigator</span> genderNode = xpathNagivator.SelectSingleNode(<span style="color:#A31515">"/s:Envelope/s:Body/t:SaveEmployee/t:employee/b:Gender"</span>, xmlNamespaceManager);
<span style="color:blue">if</span> (genderNode != <span style="color:blue">null</span>)
{
genderNode.SetValue(genderNode.Value == <span style="color:#A31515">"70"</span> ? <span style="color:#A31515">"Female"</span> : <span style="color:#A31515">"Male"</span>);
}
<span style="color:green">// re-assign the request parameter to a new Message built from our modified buffer</span>
request = messageBuffer.CreateMessage();
<span style="color:green">// tidy up and return</span>
messageBuffer.Close();
<span style="color:blue">return</span> <span style="color:blue">null</span>;
}
</pre>
<p>
Unfortunately, the XPathNavigator created is read-only (its CanEdit property is set to false) so the call to its <b>SetValue</b> method throws a <b>NotSupportedException</b>.
</p>
<p>
What about an XSLT-based approach? How would that work? Let's start by creating the XSLT itself. We can use the orange XML above as our sample input, and the green XML above as out expected output.
The XSLT is pretty simple, as you'd expect.
</p>
<pre>
<span style="color:blue"><?</span><span style="color:#A31515">xml</span> <span style="color:red">version</span><span style="color:blue">=</span>"<span style="color:blue">1.0</span>" <span style="color:red">encoding</span><span style="color:blue">=</span>"<span style="color:blue">utf-8</span>"<span style="color:blue">?></span>
<span style="color:blue"><</span><span style="color:#2B91AF">xsl:stylesheet</span> <span style="color:red">version</span><span style="color:blue">=</span>"<span style="color:blue">1.0</span>" <span style="color:red">xmlns:xsl</span><span style="color:blue">=</span>"<span style="color:blue">http://www.w3.org/1999/XSL/Transform</span>" <span style="color:red">xmlns:t</span><span style="color:blue">=</span>"<span style="color:blue">http://tempuri.org/</span>" <span style="color:red">xmlns:b</span><span style="color:blue">=</span>"<span style="color:blue">http://schemas.datacontract.org/2004/07/WcfService</span>"<span style="color:blue">></span>
<span style="color:blue"><</span><span style="color:#2B91AF">xsl:output</span> <span style="color:red">method</span><span style="color:blue">=</span>"<span style="color:blue">xml</span>" <span style="color:red">indent</span><span style="color:blue">=</span>"<span style="color:blue">yes</span>"<span style="color:blue">/></span>
<span style="color:blue"><</span>!--<span style="color:green"> transform the Gender node </span><span style="color:blue">--></span>
<span style="color:blue"><</span><span style="color:#2B91AF">xsl:template</span> <span style="color:red">match</span><span style="color:blue">=</span>"<span style="color:blue">t:SaveEmployee/t:employee/b:Gender/text()</span>"<span style="color:blue">></span>
<span style="color:blue"><</span><span style="color:#2B91AF">xsl:choose</span><span style="color:blue">></span>
<span style="color:blue"><</span><span style="color:#2B91AF">xsl:when</span> <span style="color:red">test</span><span style="color:blue">=</span>"<span style="color:blue">.='70'</span>"<span style="color:blue">></span>Female<span style="color:blue"></</span><span style="color:#2B91AF">xsl:when</span><span style="color:blue">></span>
<span style="color:blue"><</span><span style="color:#2B91AF">xsl:otherwise</span><span style="color:blue">></span>Male<span style="color:blue"></</span><span style="color:#2B91AF">xsl:otherwise</span><span style="color:blue">></span>
<span style="color:blue"></</span><span style="color:#2B91AF">xsl:choose</span><span style="color:blue">></span>
<span style="color:blue"></</span><span style="color:#2B91AF">xsl:template</span><span style="color:blue">></span>
<span style="color:blue"><</span>!--<span style="color:green"> simply copy all other nodes as-is </span><span style="color:blue">--></span>
<span style="color:blue"><</span><span style="color:#2B91AF">xsl:template</span> <span style="color:red">match</span><span style="color:blue">=</span>"<span style="color:blue">@* | node()</span>"<span style="color:blue">></span>
<span style="color:blue"><</span><span style="color:#2B91AF">xsl:copy</span><span style="color:blue">></span>
<span style="color:blue"><</span><span style="color:#2B91AF">xsl:apply-templates</span> <span style="color:red">select</span><span style="color:blue">=</span>"<span style="color:blue">@* | node()</span>"<span style="color:blue">/></span>
<span style="color:blue"></</span><span style="color:#2B91AF">xsl:copy</span><span style="color:blue">></span>
<span style="color:blue"></</span><span style="color:#2B91AF">xsl:template</span><span style="color:blue">></span>
<span style="color:blue"></</span><span style="color:#2B91AF">xsl:stylesheet</span><span style="color:blue">></span>
</pre>
<p>
We need to add this file (I called mine TransformRequest.xslt) to the WcfService project and set its Build Action to Embedded Resource. We won't want to load a compile this XSLT every time a request comes in,
so we'll place that logic in the MessageInspector's constructor:
</p>
<pre>
<span style="color:blue">private</span> <span style="color:#2B91AF">XslCompiledTransform</span> _compiledTransform;
<span style="color:blue">public</span> MessageInspector()
{
<span style="color:blue">string</span> transformResourceName = <span style="color:blue">string</span>.Format(<span style="color:#A31515">"{0}.{1}"</span>, GetType().Namespace, TransformFileName);
<span style="color:blue">using</span> (<span style="color:#2B91AF">Stream</span> stream = GetType().Assembly.GetManifestResourceStream(transformResourceName))
{
<span style="color:blue">if</span> (stream == <span style="color:blue">null</span>)
{
<span style="color:blue">throw</span> <span style="color:blue">new</span> <span style="color:#2B91AF">Exception</span>(<span style="color:blue">string</span>.Format(<span style="color:#A31515">"Unable to load the resource named '{0}' - is it embedded within the the '{1}' assembly?"</span>, transformResourceName, GetType().Assembly.FullName));
}
_compiledTransform = <span style="color:blue">new</span> <span style="color:#2B91AF">XslCompiledTransform</span>();
_compiledTransform.Load(<span style="color:#2B91AF">XmlReader</span>.Create(stream));
}
}
</pre>
<p>
Once we have the XSLT in memory, using it within a revised <b>AfterReceiveRequest</b> becomes quite simple.
</p>
<pre>
<span style="color:blue">public</span> <span style="color:blue">object</span> AfterReceiveRequest(<span style="color:blue">ref</span> <span style="color:#2B91AF">Message</span> request, <span style="color:#2B91AF">IClientChannel</span> channel, <span style="color:#2B91AF">InstanceContext</span> instanceContext)
{
<span style="color:green">// transform the original request, via the XSLT, into a new MemoryStream</span>
<span style="color:#2B91AF">MemoryStream</span> memoryStream = <span style="color:blue">new</span> <span style="color:#2B91AF">MemoryStream</span>();
_compiledTransform.Transform(request.GetReaderAtBodyContents(), <span style="color:blue">null</span>, memoryStream);
<span style="color:green">// create a new Message from the MemoryStream (copying the original's properties and headers)</span>
memoryStream.Position = 0L;
<span style="color:#2B91AF">XmlReader</span> xmlReader = <span style="color:#2B91AF">XmlReader</span>.Create(memoryStream);
<span style="color:#2B91AF">Message</span> modifiedRequest = <span style="color:#2B91AF">Message</span>.CreateMessage(request.Headers.MessageVersion, request.Headers.Action, xmlReader);
modifiedRequest.Properties.CopyProperties(request.Properties);
modifiedRequest.Headers.CopyHeadersFrom(request);
<span style="color:green">// re-assign the request parameter to our new modified request and return</span>
request = modifiedRequest;
<span style="color:blue">return</span> <span style="color:blue">null</span>;
}
</pre>
<p>
You note that there are now only two copies of the request message - the original message and the revised messages stored in the MemoryStream. As was noted before, although the Message object we create is
passed an XmlReader which wraps the MemoryStream - the Message doesn't have a separate copy of the data, it simply reads it from the MemoryStream via the XmlReader when necessarily.
</p>
<p>
Although using XSLT might be overkill for this simple requirement, I actually prefer it. It nicely separates the act of performing the transformation (the XSLT) from the plumbing necessary to hook
it up to WCF (the AfterReceiveRequest method).
</p>
<h2>Conclusion</h2>
<p>
IDispatchMessageInspector isn't something you'd design into an appliction at the outset - you should really aim to get your WCF contract fixed before you start allowing clients to connect.
But as a get-out-of-jail-free card when you've no choice but to change the interface, it's invaluable.
</p>
<h2>See Also</h2>
<ul>
<li><a href="http://ianpicknell.blogspot.com/2009/12/systemdata-serialisation-bug-scenario-2.html">System.Data Serialisation Bug - Scenario 2</a></li>
<li><a href="http://ianpicknell.blogspot.com/2010/03/serialising-enumerations-with-wcf.html">Serialising Enumerations with WCF</a></li>
</ul>Ian Picknellhttp://www.blogger.com/profile/11237307149515983183noreply@blogger.com10tag:blogger.com,1999:blog-7929053599129622940.post-70834228770458471942010-11-23T08:00:00.000+00:002010-11-23T08:00:28.358+00:00Detecting Month-Ends in JavaScript<h2>Background</h2>
<p>
I came across the following problem many years ago whilst doing some work for a financial services company.
They had some code (which I don't think was written by me, but my memory is a little hazy on the subject)
which attempted to establish whether the current date was the last date in a month. The code was written in
JavaScript.
</p>
<p>
The normal way of performing such a test is to:
</p>
<ul>
<li>(a) establish what month the current date falls within,</li>
<li>(b) add a single day to the current date,</li>
<li>(c) establish what month the date resulting from (b) falls within, and</li>
<li>(d) compare values (a) and (c) - if they are different then the current date is indeed the last date in a month.</li>
</ul>
<p>
Steps (a) and (c) are easy - JavaScripts's Date object provides a getMonth() method which returns the zero-based
month number (i.e. 0=January, 1=February, etc).
</p>
<p>
Step (b) isn't so easy as JavaScript's Data object does not provide any date arithmetic operations per se. That it,
it does not provide an addDays() method or any equivalent thereof.
</p>
<p>
Well, the value actually stored internally by the Data object is the number of milliseconds between the date/time being
represented and midnight (GMT) on 1st January 1970. And we can both read this value via the getTime() method, and write
this value via the single-parametered constructor respectively. That is, I can obtain the internally stored value of a
Date object called <code>now</code> via <code>ms = now.getTime();</code> and I can create a Date object from
this value via <code>then = new Date(ms);</code>. As we know how many milliseconds there are in a day (1000*60*60*24),
we can add a day's worth of milliseconds to a given date to add one day to it - which is actually what step (b) requires.
</p>
<p>
So, putting it all together we get:
</p><a name='more'></a>
<pre>
<span style="color:blue;">var</span> dateToday = <span style="color:blue">new</span> Date();
<span style="color:blue;">var</span> dateTomorrow = <span style="color:blue">new</span> Date(dateToday.getTime() + (1000 * 60 * 60 * 24));
<span style="color:blue;">if</span> (dateToday.getMonth() == dateTomorrow.getMonth())
alert(<span style="color:maroon">"NOT last day of month"</span>);
<span style="color:blue;">else</span>
alert(<span style="color:maroon">"IS last day of month"</span>);
</pre>
<p>
So, has anyone spotted the floor in this logic? What if I told you the logic would fail (in the UK) on the last day
of October 2004, 2010 and 2021? Yes - you've guess it - it'd to do with British Summer Time (BST) ending and the UK
reverting to Grenwich Mean Time (GMT). At 2:00am on the last Sunday in October the clocks are put back to 1:00am. On
such days there are therefore 25 hours (midnight to midnight). If the last Sunday in October happens to be the last
day of the month (i.e. in 2004, 2010 and 2021) then the above logic will claim that it's <i>not</i> the last day of
the month.
</p>
<p>
Such a bug can clearly lie dormant for many, many years before it's encountered.
</p>
<h2>The Solution</h2>
<p>
The solution is easy. The code shouldn't have used getMonth() to retrieve the current month, as that retrieves the
month according to <i>local</i> time. To ignore the effect of the clocks changing we want to use getUTCMonth() instead.
Problem solved. Or is it?
</p>
<p>
Remember that the Date object stores a GMT-relative value. So when it retrieves the date from the current system clock
(as in <code>date = new Date()</code>) or has its value set via constructor arguments (as in
<code>date = new Date(2010, 9, 31)</code>) it will always convert that to GMT before storing it. So if the current
date/time happened to be midnight (00:00) on 31 October 2010 - which is still within British Summer Time - what's
actually stored is 23:00 on 30 October 2010 - because that is the same date/time expressed in terms of GMT. So when
we add 24 hours to <i>this</i> date we get 23:00 on 31 October 2010. As the month <i>still</i> has't changed, even a
version of the above code which uses getUTCMonth() will report that 31 October 2010 is <i>not</i> the last day of the
month as long as the time component is less that 01:00.
</p>
<p>
So a getUTCMonth-version of the above code will work, except for the one hour between 00:00 and 01:00 on the
31st October if that date is a Sunday. So for the <i>fifteen years</i> between 1st January 2005 and 31st December 2020
there's only a <i>single hour</i> during which it will fail. That asmuses me. It's the sort of really rare bug that
would almost certainly go unnoticed during testing but could well cause problems (depending upon the end-of-month
processing the test was intended to trigger).
</p>
<h2>An Actual Solution</h2>
<p>
There are probably lots of solutions involving the use of GMT (rather than local) time throughout. But the simplest
approach is to realise that we really don't care about the time, but it's the time which is getting us into trouble.
So let's change it.
</p>
<p>
Simply adding the following line into the original code, immediately after <code>dateToday</code> is initialised
fixes things nicely:
</p>
<pre>
dateToday.setHours(12, 0, 0, 0);
</pre>
<p>
Although this might seem a little like cheating, it works well. And that's good enough for me.
</p>Ian Picknellhttp://www.blogger.com/profile/11237307149515983183noreply@blogger.com0tag:blogger.com,1999:blog-7929053599129622940.post-54501777430579337122010-10-28T18:09:00.000+01:002010-11-23T07:58:32.673+00:00Selectively Overriding XML Serialisation Attributes<h2>Introduction</h2>
<p>
As I mentioned in my
<a href="http://ianpicknell.blogspot.com/2010/04/default-values-dont-get-serialised.html">last post</a>,
although you can override XML serialisation attributes by passing an XmlAttributeOverrides instance to an
XmlSerializer, the attributes you provide for a given type and member replace <i>all</i> the existing XML
serialisation attributes - you can't simply tweak one or two and leave the rest intact.
</p>
<p>
If you're thinking that type and members only tend to have one or two serialistion attributes, then take a
look at this set attributes from an auto-generated EquityDerivativeBase class:
</p>
<pre>
[System.Xml.Serialization.XmlIncludeAttribute(<span style="color:blue">typeof</span>(EquityDerivativeShortFormBase))]
[System.Xml.Serialization.XmlIncludeAttribute(<span style="color:blue">typeof</span>(EquityOptionTransactionSupplement))]
[System.Xml.Serialization.XmlIncludeAttribute(<span style="color:blue">typeof</span>(BrokerEquityOption))]
[System.Xml.Serialization.XmlIncludeAttribute(<span style="color:blue">typeof</span>(EquityDerivativeLongFormBase))]
[System.Xml.Serialization.XmlIncludeAttribute(<span style="color:blue">typeof</span>(EquityOption))]
[System.Xml.Serialization.XmlIncludeAttribute(<span style="color:blue">typeof</span>(EquityForward))]
[System.CodeDom.Compiler.GeneratedCodeAttribute(<span style="color:#A31515">"xsd"</span>, <span style="color:#A31515">"4.0.30319.1"</span>)]
[System.<span style="color:#2B91AF">SerializableAttribute</span>()]
[System.Diagnostics.<span style="color:#2B91AF">DebuggerStepThroughAttribute</span>()]
[System.ComponentModel.DesignerCategoryAttribute(<span style="color:#A31515">"code"</span>)]
[System.Xml.Serialization.XmlTypeAttribute(Namespace=<span style="color:#A31515">"http://www.fpml.org/FpML-5/confirmation"</span>)]
</pre>
<p>
Let's suppose I want to use XmlAttributeOverrides to alter the value of the XmlTypeAttribute at run-rime, to place the
element in a different namespace. Well I can. But the XmlAttributeOverrides instance I supply is used to replace <i>all</i>
the existing attributes. So I lose each of the XmlIncludeAttribute attributes which define the classes which use this
class as a base class.
</p>
<h2>Book and Genre classes (with Xml Attributes)</h2>
<p>
To demonstrate how to override the attributes <i>selectively</i> I'm going to use the same Book class as in my
<a href="http://ianpicknell.blogspot.com/2010/04/default-values-dont-get-serialised.html">last post</a>
to demonstrate selectively overriding these attributes. I've added a lot more attributes to the members of the
Book class to demonstrate that they all get retained.
</p>
<pre>
[<span style="color:#2B91AF">XmlType</span>(TypeName=<span style="color:#A31515">"Book"</span>)]
[<span style="color:#2B91AF">XmlRoot</span>(<span style="color:#A31515">"book"</span>, Namespace=<span style="color:#A31515">"http://tempuri.org"</span>)]
<span style="color:blue">public</span> <span style="color:blue">class</span> <span style="color:#2B91AF">Book</span>
{
[<span style="color:#2B91AF">XmlIgnore</span>]
<span style="color:blue">public</span> <span style="color:blue">int</span> InternalId { <span style="color:blue">get</span>; <span style="color:blue">set</span>; }
[<span style="color:#2B91AF">XmlElement</span>(<span style="color:#A31515">"title"</span>)]
<span style="color:blue">public</span> <span style="color:blue">string</span> Title { <span style="color:blue">get</span>; <span style="color:blue">set</span>; }
[<span style="color:#2B91AF">DefaultValue</span>(<span style="color:#A31515">"Anonymous"</span>)]
[<span style="color:#2B91AF">XmlArray</span>(<span style="color:#A31515">"authors"</span>)]
[<span style="color:#2B91AF">XmlArrayItem</span>(<span style="color:#A31515">"author"</span>)]
<span style="color:blue">public</span> <span style="color:blue">string</span>[] Authors { <span style="color:blue">get</span>; <span style="color:blue">set</span>; }
[<span style="color:#2B91AF">XmlElement</span>(<span style="color:#A31515">"isbn13"</span>)]
<span style="color:blue">public</span> <span style="color:blue">string</span> Isbn13 { <span style="color:blue">get</span>; <span style="color:blue">set</span>; }
[<span style="color:#2B91AF">XmlText</span>]
<span style="color:blue">public</span> <span style="color:blue">string</span> Extract { <span style="color:blue">get</span>; <span style="color:blue">set</span>; }
[<span style="color:#2B91AF">XmlAttribute</span>(<span style="color:#A31515">"genre"</span>)]
<span style="color:blue">public</span> <span style="color:#2B91AF">Genre</span> Genre { <span style="color:blue">get</span>; <span style="color:blue">set</span>; }
[<span style="color:#2B91AF">XmlNamespaceDeclarations</span>]
<span style="color:blue">public</span> <span style="color:#2B91AF">XmlSerializerNamespaces</span> XmlNamespaces { <span style="color:blue">get</span>; <span style="color:blue">set</span>; }
[<span style="color:#2B91AF">XmlAnyAttribute</span>]
<span style="color:blue">public</span> <span style="color:#2B91AF">XmlAttribute</span>[] OtherAttributes { <span style="color:blue">get</span>; <span style="color:blue">set</span>; }
[<span style="color:#2B91AF">XmlAnyElement</span>]
<span style="color:blue">public</span> <span style="color:#2B91AF">XmlElement</span>[] OtherElements { <span style="color:blue">get</span>; <span style="color:blue">set</span>; }
<span style="color:blue">public</span> Book()
{
XmlNamespaces = <span style="color:blue">new</span> <span style="color:#2B91AF">XmlSerializerNamespaces</span>();
XmlNamespaces.Add(<span style="color:#A31515">"ns"</span>, <span style="color:#A31515">"http://tempuri.org"</span>);
}
}
<span style="color:blue">public</span> <span style="color:blue">enum</span> <span style="color:#2B91AF">Genre</span>
{
[<span style="color:#2B91AF">XmlEnum</span>(<span style="color:#A31515">"unknown"</span>)]
Unknown,
[<span style="color:#2B91AF">XmlEnum</span>(<span style="color:#A31515">"autobiography"</span>)]
Autobiography,
[<span style="color:#2B91AF">XmlEnum</span>(<span style="color:#A31515">"computing-text"</span>)]
ComputingText,
[<span style="color:#2B91AF">XmlEnum</span>(<span style="color:#A31515">"classic"</span>)]
Classic
}
</pre>
<h2>Solution</h2>
<p>
The solution is to copy all the existing attributes into an XmlAttributeOverrides instance (modifying them as they're
copied), and then apply the XmlAttributeOverrides to the XmlSerializer. That way the XmlAttributeOverrides object retains
all of the original attributes (with the exception of any changes made in transit). Let me show you what I mean:
</p><a name='more'></a>
<pre>
<span style="color:blue;">using</span> System;
<span style="color:blue;">using</span> System.Collections.Generic;
<span style="color:blue;">using</span> System.ComponentModel;
<span style="color:blue;">using</span> System.IO;
<span style="color:blue;">using</span> System.Reflection;
<span style="color:blue;">using</span> System.Xml;
<span style="color:blue;">using</span> System.Xml.Serialization;
<span style="color:blue;">namespace</span> ConsoleApplication
{
<span style="color:blue">public</span> <span style="color:blue">class</span> <span style="color:#2B91AF">Program</span>
{
<span style="color:blue">static</span> <span style="color:blue">void</span> Main(<span style="color:blue">string</span>[] args)
{
<span style="color:green">// create some Books</span>
<span style="color:#2B91AF">Book</span>[] books = <span style="color:blue">new</span> <span style="color:#2B91AF">Book</span>[]
{
<span style="color:blue">new</span> <span style="color:#2B91AF">Book</span> { InternalId=1, Title=<span style="color:#A31515">"The Road Ahead"</span>, Authors=<span style="color:blue">new</span> <span style="color:blue">string</span>[] {<span style="color:#A31515">"Bill Gates"</span>}, Isbn13=<span style="color:#A31515">"978-0670859139"</span>, Genre=<span style="color:#2B91AF">Genre</span>.Autobiography },
<span style="color:blue">new</span> <span style="color:#2B91AF">Book</span> { InternalId=2, Title=<span style="color:#A31515">"Beowulf"</span>, Authors=<span style="color:blue">new</span> <span style="color:blue">string</span>[] {<span style="color:#A31515">"Anonymous"</span>}, Isbn13=<span style="color:#A31515">"978-1588278296"</span>, Extract=<span style="color:#A31515">"That grim spirit was called Grendel,\nFamous waste-wanderer that held the moors\nFen and fastness; the land of the race of monsters\nThe unhappy creature occupied for a while\nAfter the Creator had condemned them."</span>, Genre=<span style="color:#2B91AF">Genre</span>.Classic },
<span style="color:blue">new</span> <span style="color:#2B91AF">Book</span> { InternalId=3, Title=<span style="color:#A31515">"The C Programming Language (2nd Edition)"</span>, Authors=<span style="color:blue">new</span><span style="color:blue">string</span>[] {<span style="color:#A31515">"Brian W Kernighan"</span>,<span style="color:#A31515">"Dennis M Ritchie"</span>}, Isbn13=<span style="color:#A31515">"978-0131103627"</span>, Genre=<span style="color:#2B91AF">Genre</span>.ComputingText },
};
<span style="color:green">// copy the existing attributes into an XmlAttributeOverrides instance (providing an</span>
<span style="color:green">// Action which tweaks the attributes for Book.Isbn13)</span>
<span style="color:#2B91AF">XmlAttributeOverrides</span> xmlAttributeOverrides = GetXmlAttributeOverrides(<span style="color:blue">new</span> <span style="color:#2B91AF">Type</span>[] { <span style="color:blue">typeof</span>(<span style="color:#2B91AF">Book</span>), <span style="color:blue">typeof</span>(<span style="color:#2B91AF">Genre</span>) },
(type, memberName, xmlAttributes) =>
{
<span style="color:blue">if</span> (type == <span style="color:blue">typeof</span>(<span style="color:#2B91AF">Book</span>) && memberName == <span style="color:#A31515">"Isbn13"</span>)
{
<span style="color:green">// remove the sttribute which specifies an element named "isbn13"</span>
xmlAttributes.XmlElements.Clear();
<span style="color:green">// and add an attribute which specifies an attribute named "isbn"</span>
xmlAttributes.XmlAttribute = <span style="color:blue">new</span> <span style="color:#2B91AF">XmlAttributeAttribute</span>(<span style="color:#A31515">"isbn"</span>);
}
});
<span style="color:green">// serialise the books into a MemoryStream (using the overrides)</span>
<span style="color:#2B91AF">XmlSerializer</span> xmlSerializer = <span style="color:blue">new</span> <span style="color:#2B91AF">XmlSerializer</span>(<span style="color:blue">typeof</span>(<span style="color:#2B91AF">Book</span>[]), xmlAttributeOverrides);
<span style="color:#2B91AF">MemoryStream</span> memoryStream = <span style="color:blue">new</span> <span style="color:#2B91AF">MemoryStream</span>();
xmlSerializer.Serialize(memoryStream, books);
<span style="color:green">// write the contents of the MemoryStream to the Console</span>
memoryStream.Position = 0L;
<span style="color:#2B91AF">StreamReader</span> streamReader = <span style="color:blue">new</span> <span style="color:#2B91AF">StreamReader</span>(memoryStream);
<span style="color:#2B91AF">Console</span>.WriteLine(streamReader.ReadToEnd());
<span style="color:green">// wait for user to hit ENTER</span>
<span style="color:#2B91AF">Console</span>.ReadLine();
}
<span style="color:blue">private</span> <span style="color:blue">static</span> <span style="color:#2B91AF">XmlAttributeOverrides</span> GetXmlAttributeOverrides(<span style="color:#2B91AF">Type</span>[] types, <span style="color:#2B91AF">Action</span><<span style="color:#2B91AF">Type</span>, <span style="color:blue">string</span>, <span style="color:#2B91AF">XmlAttributes</span>> tweakAttributesAction)
{
<span style="color:#2B91AF">XmlAttributeOverrides</span> xmlAttributeOverrides = <span style="color:blue">new</span> <span style="color:#2B91AF">XmlAttributeOverrides</span>();
<span style="color:#2B91AF">XmlAttributes</span> xmlAttributes;
<span style="color:blue">foreach</span> (<span style="color:#2B91AF">Type</span> type <span style="color:blue">in</span> types)
{
<span style="color:green">// get the Type's attributes first</span>
xmlAttributes = GetXmlAttributes(type.GetCustomAttributes(<span style="color:blue">false</span>));
xmlAttributeOverrides.Add(type, xmlAttributes);
<span style="color:green">// then iterate over the members, checking those attributes too</span>
<span style="color:blue">if</span> (type.IsEnum)
{
<span style="color:blue">foreach</span> (<span style="color:#2B91AF">FieldInfo</span> fieldInfo <span style="color:blue">in</span> type.GetFields(<span style="color:#2B91AF">BindingFlags</span>.Static | <span style="color:#2B91AF">BindingFlags</span>.Public))
{
xmlAttributes = GetXmlAttributes(fieldInfo.GetCustomAttributes(<span style="color:blue">false</span>));
tweakAttributesAction(type, fieldInfo.Name, xmlAttributes);
xmlAttributeOverrides.Add(type, fieldInfo.Name, xmlAttributes);
}
}
<span style="color:blue">else</span>
{
<span style="color:blue">foreach</span> (<span style="color:#2B91AF">PropertyInfo</span> propertyInfo <span style="color:blue">in</span> type.GetProperties())
{
xmlAttributes = GetXmlAttributes(propertyInfo.GetCustomAttributes(<span style="color:blue">false</span>));
tweakAttributesAction(type, propertyInfo.Name, xmlAttributes);
xmlAttributeOverrides.Add(type, propertyInfo.Name, xmlAttributes);
}
}
}
<span style="color:blue">return</span> xmlAttributeOverrides;
}
<span style="color:blue">private</span> <span style="color:blue">static</span> <span style="color:#2B91AF">XmlAttributes</span> GetXmlAttributes(<span style="color:blue">object</span>[] attributes)
{
<span style="color:#2B91AF">XmlAttributes</span> xmlAttributes =<span style="color:blue">new</span> <span style="color:#2B91AF">XmlAttributes</span>();
<span style="color:#2B91AF">Dictionary</span><<span style="color:#2B91AF">Type</span>, <span style="color:#2B91AF">Action</span><<span style="color:blue">object</span>>> dictionary = <span style="color:blue">new</span> <span style="color:#2B91AF">Dictionary</span><<span style="color:#2B91AF">Type</span>, <span style="color:#2B91AF">Action</span><<span style="color:blue">object</span>>>()
{
{ <span style="color:blue">typeof</span>(<span style="color:#2B91AF">XmlAnyAttributeAttribute</span>), attribute => xmlAttributes.XmlAnyAttribute = attribute <span style="color:blue">as</span> <span style="color:#2B91AF">XmlAnyAttributeAttribute</span> },
{ <span style="color:blue">typeof</span>(<span style="color:#2B91AF">XmlAnyElementAttribute</span>), attribute => xmlAttributes.XmlAnyElements.Add(attribute <span style="color:blue">as</span> <span style="color:#2B91AF">XmlAnyElementAttribute</span>) },
{ <span style="color:blue">typeof</span>(<span style="color:#2B91AF">XmlArrayAttribute</span>), attribute => xmlAttributes.XmlArray = attribute <span style="color:blue">as</span> <span style="color:#2B91AF">XmlArrayAttribute</span> },
{ <span style="color:blue">typeof</span>(<span style="color:#2B91AF">XmlArrayItemAttribute</span>), attribute => xmlAttributes.XmlArrayItems.Add(attribute <span style="color:blue">as</span> <span style="color:#2B91AF">XmlArrayItemAttribute</span>) },
{ <span style="color:blue">typeof</span>(<span style="color:#2B91AF">XmlAttributeAttribute</span>), attribute => xmlAttributes.XmlAttribute = attribute <span style="color:blue">as</span> <span style="color:#2B91AF">XmlAttributeAttribute</span> },
{ <span style="color:blue">typeof</span>(<span style="color:#2B91AF">DefaultValueAttribute</span>), attribute => xmlAttributes.XmlDefaultValue = (attribute <span style="color:blue">as</span> <span style="color:#2B91AF">DefaultValueAttribute</span>).Value },
{ <span style="color:blue">typeof</span>(<span style="color:#2B91AF">XmlElementAttribute</span>), attribute => xmlAttributes.XmlElements.Add(attribute <span style="color:blue">as</span> <span style="color:#2B91AF">XmlElementAttribute</span>) },
{ <span style="color:blue">typeof</span>(<span style="color:#2B91AF">XmlEnumAttribute</span>), attribute => xmlAttributes.XmlEnum = attribute <span style="color:blue">as</span> <span style="color:#2B91AF">XmlEnumAttribute</span> },
{ <span style="color:blue">typeof</span>(<span style="color:#2B91AF">XmlIgnoreAttribute</span>), attribute => xmlAttributes.XmlIgnore = <span style="color:blue">true</span> },
{ <span style="color:blue">typeof</span>(<span style="color:#2B91AF">XmlNamespaceDeclarationsAttribute</span>), attribute => xmlAttributes.Xmlns = <span style="color:blue">true</span> },
{ <span style="color:blue">typeof</span>(<span style="color:#2B91AF">XmlRootAttribute</span>), attribute => xmlAttributes.XmlRoot = attribute <span style="color:blue">as</span> <span style="color:#2B91AF">XmlRootAttribute</span> },
{ <span style="color:blue">typeof</span>(<span style="color:#2B91AF">XmlTextAttribute</span>), attribute => xmlAttributes.XmlText = attribute <span style="color:blue">as</span> <span style="color:#2B91AF">XmlTextAttribute</span> },
{ <span style="color:blue">typeof</span>(<span style="color:#2B91AF">XmlTypeAttribute</span>), attribute => xmlAttributes.XmlType = attribute <span style="color:blue">as</span> <span style="color:#2B91AF">XmlTypeAttribute</span> },
};
<span style="color:blue">foreach</span> (<span style="color:blue">object</span> attribute <span style="color:blue">in</span> attributes)
{
<span style="color:green">// establish what type of attribute we're dealing with</span>
<span style="color:#2B91AF">Type</span> type = attribute.GetType();
<span style="color:green">// if the attribute is one of those supported by XmlAttributes</span>
<span style="color:blue">if</span> (dictionary.ContainsKey(type))
{
<span style="color:green">// lookup the appropriate action and invoke it</span>
dictionary[type](attribute);
}
}
<span style="color:blue">return</span> xmlAttributes;
}
}
</pre>
<p>
You'll notice that I use an Action to tweak the existing attributes prior to copying them into the XmlAttributeOverrides
object, rather than simpy copying them all into the XmlAttributeOverrides object and then tweaking them there. This is
because you can only add attributes to XmlAttributeOverrides - you can't remove them or modify them.
</p>
<p>
The above code produced the following output. Note that the Isbn13 property has been serialised into an <i>attribute</i>
named isbn, contrary to what the attributes applied statically request. Also note that all other attributes have been
honoured.
</p>
<pre class="console">
<?xml version="1.0"?>
<ArrayOfBook xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Book xmlns:ns="http://tempuri.org" isbn="978-0670859139" genre="autobiography">
<ns:title>The Road Ahead</ns:title>
<ns:authors>
<ns:author>Bill Gates</ns:author>
</ns:authors>
</Book>
<Book xmlns:ns="http://tempuri.org" isbn="978-1588278296" genre="classic">
<ns:title>Beowulf</ns:title>
<ns:authors>
<ns:author>Anonymous</ns:author>
</ns:authors>That grim spirit was called Grendel,
Famous waste-wanderer that held the moors
Fen and fastness; the land of the race of monsters
The unhappy creature occupied for a while
After the Creator had condemned them.</Book>
<Book xmlns:ns="http://tempuri.org" isbn="978-0131103627" genre="computing-text">
<ns:title>The C Programming Language (2nd Edition)</ns:title>
<ns:authors>
<ns:author>Brian W Kernighan</ns:author>
<ns:author>Dennis M Ritchie</ns:author>
</ns:authors>
</Book>
</ArrayOfBook>
</pre>
<h2>Summary</h2>
<p>
So, to selectively override XML serialisation attributes, simply create an XmlAttributeOverrides which contains all the
attributes you want to apply - even if you have to copy most of them from the attributes applied statically.
</p>
<h2>See Also</h2>
<ul>
<li><a href="http://ianpicknell.blogspot.com/2010/04/default-values-dont-get-serialised.html">Default Value's Don't Get Serialised</a></li>
</ul>Ian Picknellhttp://www.blogger.com/profile/11237307149515983183noreply@blogger.com0tag:blogger.com,1999:blog-7929053599129622940.post-15586369821297954822010-10-17T13:20:00.000+01:002010-11-23T07:57:19.739+00:00Default Values Don't Get Serialised<h2>The Problem</h2>
<p>
I came across some unusual behaviour during XML Serialisation recently and thought I'd share both
the problem and my solution to it.
</p>
<p>
To demonstrate the behaviour I've created a small console application which serialises two Book objects to the Console.
I've marked the default value of the Author property as "Anonymous", which seems reasonable enough. The second of the two
Book objects specifically has its Author property set to "Anonymous", which it doesn't really need to have - as that's
the default value.
</p>
<p>
Here's the code:
</p>
<pre>
<span style="color:blue;">using</span> System;
<span style="color:blue;">using</span> System.ComponentModel;
<span style="color:blue;">using</span> System.IO;
<span style="color:blue;">using</span> System.Xml.Serialization;
<span style="color:blue;">namespace</span> ConsoleApplication
{
<span style="color:blue">public</span> <span style="color:blue">class</span> <span style="color:#2B91AF">Program</span>
{
<span style="color:blue">static</span> <span style="color:blue">void</span> Main(<span style="color:blue">string</span>[] args)
{
<span style="color:green">// create some Books</span>
<span style="color:#2B91AF">Book</span>[] books = <span style="color:blue">new</span> <span style="color:#2B91AF">Book</span>[]
{
<span style="color:blue">new</span> <span style="color:#2B91AF">Book</span> { Title = <span style="color:#A31515">"The Road Ahead"</span>, Author = <span style="color:#A31515">"Bill Gates"</span>, Isbn13 = <span style="color:#A31515">"978-0670859139"</span> },
<span style="color:blue">new</span> <span style="color:#2B91AF">Book</span> { Title = <span style="color:#A31515">"Beowulf"</span>, Author = <span style="color:#A31515">"Anonymous"</span>, Isbn13 = <span style="color:#A31515">"978-1588278296"</span> },
};
<span style="color:green">// serialise it into a MemoryStream</span>
<span style="color:#2B91AF">XmlSerializer</span> xmlSerializer = <span style="color:blue">new</span> <span style="color:#2B91AF">XmlSerializer</span>(<span style="color:blue">typeof</span>(<span style="color:#2B91AF">Book</span>[]));
<span style="color:#2B91AF">MemoryStream</span> memoryStream = <span style="color:blue">new</span> <span style="color:#2B91AF">MemoryStream</span>();
xmlSerializer.Serialize(memoryStream, books);
<span style="color:green">// write the contents of the MemoryStream to the Console</span>
memoryStream.Position = 0L;
<span style="color:#2B91AF">StreamReader</span> streamReader = <span style="color:blue">new</span> <span style="color:#2B91AF">StreamReader</span>(memoryStream);
<span style="color:#2B91AF">Console</span>.WriteLine(streamReader.ReadToEnd());
<span style="color:green">// wait for user to hit ENTER</span>
<span style="color:#2B91AF">Console</span>.ReadLine();
}
}
<span style="color:blue">public</span> <span style="color:blue">class</span> <span style="color:#2B91AF">Book</span>
{
[<span style="color:#2B91AF">XmlElement</span>(<span style="color:#A31515">"title"</span>)]
<span style="color:blue">public</span> <span style="color:blue">string</span> Title { <span style="color:blue">get</span>; <span style="color:blue">set</span>; }
[<span style="color:#2B91AF">XmlElement</span>(<span style="color:#A31515">"author"</span>)]
[<span style="color:#2B91AF">DefaultValue</span>(<span style="color:#A31515">"Anonymous"</span>)]
<span style="color:blue">public</span> <span style="color:blue">string</span> Author { <span style="color:blue">get</span>; <span style="color:blue">set</span>; }
[<span style="color:#2B91AF">XmlElement</span>(<span style="color:#A31515">"isbn13"</span>)]
<span style="color:blue">public</span> <span style="color:blue">string</span> Isbn13 { <span style="color:blue">get</span>; <span style="color:blue">set</span>; }
}
}
</pre>
<p>
When I run the above, something odd happens. The Author property <b>is not serialised</b> for the second book, as you'll
see below. It turns out that this is by design. The theory is that if the default value has been used, then there's no
need to explicitly output it - a consumer of the data will simply see that the element is missing and infer the default value.
</p>
<pre class='console'>
<?xml version="1.0"?>
<ArrayOfBook xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Book>
<title>The Road Ahead</title>
<author>Bill Gates</author>
<isbn13>978-0670859139</isbn13>
</Book>
<Book>
<title>Beowulf</title>
<isbn13>978-1588278296</isbn13>
</Book>
</ArrayOfBook>
</pre>
<p>
The problem, obviously, is that this approach assumes that consumer is aware that a default value exists. Can you see any
indication from the serialised XML that a default value exists, or that an element has been suppressed because it's value
was equal to the default value? No - that information is hidden from the consumer. We're simply left to hope that the
consumer is aware of this default value.
</p>
<a name='more'></a>
<h2>The Solution</h2>
<p>
Well, the obvious solution is to simply remove the Default attribute from the Author property. But what if you can't? It's
perfectly possible that you're serialising an object to which you do not have the source code. In my case I was serialising
an object where the source code had been auto-generated from an series of XSDs - and was 43,600 lines long, and contained 72
default constraints. If I can avoid editing auto-generated code I always will. So how?
</p>
<p>
Well, although I'd never used it before, there's an overload to the XmlSerializer which allows you to provide a series
of overrides for the serialisation attributes. Unfortunately, it overrides <i>all</i> of the serialisation attributes
for a given type and member - you can't just say "use this Default attribute, but leave all other attributes alone". This
means we need to re-state the XmlElement attribute to specify a name of "author" (otherwise you'll get the default of
"Author"). The call to the XmlSerializer's constructor thus becomes:
</p>
<pre>
<span style="color:#2B91AF">XmlAttributes</span> bookAuthorXmlAttributes = <span style="color:blue">new</span> <span style="color:#2B91AF">XmlAttributes</span>();
bookAuthorXmlAttributes.XmlElements.Add(<span style="color:blue">new</span> <span style="color:#2B91AF">XmlElementAttribute</span>(<span style="color:#A31515">"author"</span>));
<span style="color:#2B91AF">XmlAttributeOverrides</span> xmlAttributeOverrides = <span style="color:blue">new</span> <span style="color:#2B91AF">XmlAttributeOverrides</span>();
xmlAttributeOverrides.Add(<span style="color:blue">typeof</span>(<span style="color:#2B91AF">Book</span>), <span style="color:#A31515">"Author"</span>, bookAuthorXmlAttributes);
<span style="color:#2B91AF">XmlSerializer</span> xmlSerializer = <span style="color:blue">new</span> <span style="color:#2B91AF">XmlSerializer</span>(<span style="color:blue">typeof</span>(<span style="color:#2B91AF">Book</span>[]), xmlAttributeOverrides);
</pre>
<p>
The output this time is:
</p>
<pre class="console">
<?xml version="1.0"?>
<ArrayOfBook xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Book>
<title>The Road Ahead</title>
<author>Bill Gates</author>
<isbn13>978-0670859139</isbn13>
</Book>
<Book>
<title>Beowulf</title>
<author>Anonymous</author>
<isbn13>978-1588278296</isbn13>
</Book>
</ArrayOfBook>
</pre>
<p>
Perfect. But it's a shame we've had to re-state the XmlElement attribute isn't it? It would be better if we could inspect the attributes
which were already present and add all of these in the XmlAttributeOverrides, with the exception of the Default attribute we're trying to
remove. That'll be the subject of my next post.
</p>
<h2>See Also</h2>
<ul>
<li><a href="http://ianpicknell.blogspot.com/2010/05/selectively-overriding-xml.html">Selectively Overriding XML Serialisation Attributes</a></li>
</ul>Ian Picknellhttp://www.blogger.com/profile/11237307149515983183noreply@blogger.com0tag:blogger.com,1999:blog-7929053599129622940.post-17715626190555628982010-04-10T18:57:00.000+01:002010-04-10T18:57:43.666+01:00Validating Integers in SQL Server<h2>Introduction</h2>
<p>
Hypothetical scenario: Data has been loaded into a SQL Server table from a CSV file; each column
in the table has been defined as VARCHAR(255) to prevent the data load job from failing if one or
more values are not of the expected data type. You're job is to copy data from this 'raw' table
into one where the columns are strongly typed (i.e. INT, DATETIME, DECIMAL, etc). The requirement
is to pull through those rows which are entirely valid, but to ignore those rows where any value
in the VARCHAR column cannot be successfully converted to the target data type. Actually,
if rows which are only partially valid could be copied across too, with invalid values being set
to NULL in the target table, then that would be even better.
</p>
<p>
The requirement seems simple enough, but how would you do this? It's actually quite tricky
to get right. So I'm going to make the scenario even simpler - you only have to support
INT data types. Your job is simply to make sure that the supplied VARCHAR can be converted to
an INT before you actual attempt to do so.
</p>
<h2>ISNUMERIC</h2>
<p>
You first port-of-call might be to use ISNUMERIC to establish whether a source value is numeric.
This sounds reasonable enough.
</p>
<pre>
<span style='color:blue'>SELECT</span> <span style='color:fuchsia'>ISNUMERIC</span><span style='color:gray'>(</span><span style='color:red'>'9876543210'</span><span style='color:gray'>)</span>
</pre>
<p>
The above statement returns 1 - the value '9876543210' is numeric. But is it an INT? No - it's
too large. The documentation for ISNUMERIC says that it returns 1 if the supplied character column
can be converted to at least one of the numeric data types. So that's not particularly useful if
we're trying to establish whether a value is a valid INT or not.
</p><a name='more'></a>
<h2>ARITHABORT</h2>
<p>
So you decide simply to convert the value, ignoring arithmetic overflows using SET ARITHABORT OFF. Let's try:
</p>
<pre>
<span style='color:blue'>SET</span> <span style='color:blue'>ARITHABORT</span> <span style='color:blue'>OFF</span>
<span style='color:blue'>SELECT</span> <span style='color:fuchsia'>CONVERT</span><span style='color:gray'>(</span><span style='color:blue'>INT</span><span style='color:gray'>,</span> <span style='color:red'>'9876543210'</span><span style='color:gray'>)</span>
<span style='color:red'>Msg 248, Level 16, State 1, Line 2</span>
<span style='color:red'>The conversion of the varchar value '9876543210' overflowed an int column. Maximum integer value exceeded.</span>
</pre>
<p>
Then you remember that setting ARITHABORT OFF has no effect unless you also set ANSI_WARNINGS OFF.
So you try again:
</p>
<pre>
<span style='color:blue'>SET</span> <span style='color:blue'>ARITHABORT</span> <span style='color:blue'>OFF</span>
<span style='color:blue'>SET</span> <span style='color:blue'>ANSI_WARNINGS</span> <span style='color:blue'>OFF</span>
<span style='color:blue'>SELECT</span> <span style='color:fuchsia'>CONVERT</span><span style='color:gray'>(</span><span style='color:blue'>INT</span><span style='color:gray'>,</span> <span style='color:red'>'9876543210'</span><span style='color:gray'>)</span>
</pre>
<p>
Perfect - this selects the value NULL, which can be easily tested for.
</p>
<h2>User-Defined Function</h2>
<p>
So we wrap this test into a user-defined function so it can be easily re-used.
</p>
<pre>
<span style='color:blue'>CREATE</span> <span style='color:blue'>FUNCTION</span> dbo<span style='color:gray'>.</span>IsINT
<span style='color:gray'>(</span>
@Input <span style='color:blue'>VARCHAR</span><span style='color:gray'>(</span>255<span style='color:gray'>)</span>
<span style='color:gray'>)</span>
<span style='color:blue'>RETURNS</span> <span style='color:blue'>BIT</span>
<span style='color:blue'>AS</span>
<span style='color:blue'>BEGIN</span>
<span style='color:blue'>SET</span> <span style='color:blue'>ARITHABORT</span> <span style='color:blue'>OFF</span>
<span style='color:blue'>SET</span> <span style='color:blue'>ANSI_WARNINGS</span> <span style='color:blue'>OFF</span>
<span style='color:blue'>RETURN</span> <span style='color:blue'>CASE</span> <span style='color:blue'>WHEN</span> <span style='color:fuchsia'>CONVERT</span><span style='color:gray'>(</span><span style='color:blue'>INT</span><span style='color:gray'>,</span> @Input<span style='color:gray'>)</span> <span style='color:gray'>IS</span> <span style='color:gray'>NULL</span> <span style='color:blue'>THEN</span> 0 <span style='color:blue'>ELSE</span> 1 <span style='color:blue'>END</span>
<span style='color:blue'>END</span>
GO
<span style='color:red'>Msg 443, Level 16, State 15, Procedure IsINT, Line 8</span>
<span style='color:red'>Invalid use of side-effecting or time-dependent operator in 'SET OPTION OFF' within a function.</span>
<span style='color:red'>Msg 443, Level 16, State 15, Procedure IsINT, Line 9</span>
<span style='color:red'>Invalid use of side-effecting or time-dependent operator in 'SET OPTION OFF' within a function.</span>
</pre>
<p>
We can't use SET <em>option</em> OFF within a function? That's a shame. Okay, so we'll have to require that the
caller does this. But the least we should do is check that they have.
</p>
<pre>
<span style='color:blue'>CREATE</span> <span style='color:blue'>FUNCTION</span> dbo<span style='color:gray'>.</span>IsINT
<span style='color:gray'>(</span>
@Input <span style='color:blue'>VARCHAR</span><span style='color:gray'>(</span>255<span style='color:gray'>)</span>
<span style='color:gray'>)</span>
<span style='color:blue'>RETURNS</span> <span style='color:blue'>BIT</span>
<span style='color:blue'>AS</span>
<span style='color:blue'>BEGIN</span>
<span style='color:blue'>IF</span> <span style='color:fuchsia'>SESSIONPROPERTY</span><span style='color:gray'>(</span><span style='color:red'>'ARITHABORT'</span><span style='color:gray'>)</span> <span style='color:gray'>=</span> 1
<span style='color:blue'>RAISERROR</span> <span style='color:gray'>(</span>N<span style='color:red'>'This function requires that ARITHABORT be set to OFF before it is called.'</span><span style='color:gray'>,</span> 16<span style='color:gray'>,</span> 1<span style='color:gray'>)</span>
<span style='color:blue'>IF</span> <span style='color:fuchsia'>SESSIONPROPERTY</span><span style='color:gray'>(</span><span style='color:red'>'ANSI_WARNINGS'</span><span style='color:gray'>)</span> <span style='color:gray'>=</span> 1
<span style='color:blue'>RAISERROR</span> <span style='color:gray'>(</span>N<span style='color:red'>'This function requires that ANSI_WARNINGS be set to OFF before it is called.'</span><span style='color:gray'>,</span> 16<span style='color:gray'>,</span> 1<span style='color:gray'>)</span>
<span style='color:blue'>RETURN</span> <span style='color:blue'>CASE</span> <span style='color:blue'>WHEN</span> <span style='color:fuchsia'>CONVERT</span><span style='color:gray'>(</span><span style='color:blue'>INT</span><span style='color:gray'>,</span> @Input<span style='color:gray'>)</span> <span style='color:gray'>IS</span> <span style='color:gray'>NULL</span> <span style='color:blue'>THEN</span> 0 <span style='color:blue'>ELSE</span> 1 <span style='color:blue'>END</span>
<span style='color:blue'>END</span>
<span style='color:red'>Msg 443, Level 16, State 14, Procedure IsINT, Line 9</span>
<span style='color:red'>Invalid use of side-effecting or time-dependent operator in 'RAISERROR' within a function.</span>
<span style='color:red'>Msg 443, Level 16, State 14, Procedure IsINT, Line 12</span>
<span style='color:red'>Invalid use of side-effecting or time-dependent operator in 'RAISERROR' within a function.</span>
</pre>
<p>
Nope. Can't do that either. We'll just have to be content with returning NULL if the options haven't
been set correctly and hoping the caller has read the documentation for our function.
</p>
<pre>
<span style='color:blue'>CREATE</span> <span style='color:blue'>FUNCTION</span> dbo<span style='color:gray'>.</span>IsINT
<span style='color:gray'>(</span>
@Input <span style='color:blue'>VARCHAR</span><span style='color:gray'>(</span>255<span style='color:gray'>)</span>
<span style='color:gray'>)</span>
<span style='color:blue'>RETURNS</span> <span style='color:blue'>BIT</span>
<span style='color:blue'>AS</span>
<span style='color:green'>-- Note to callers: ARITHABORT and ANSI_WARNINGS must be set to OFF</span>
<span style='color:green'>-- before calling; if not, this function will always return NULL.</span>
<span style='color:blue'>BEGIN</span>
<span style='color:blue'>IF</span> <span style='color:fuchsia'>SESSIONPROPERTY</span><span style='color:gray'>(</span><span style='color:red'>'ARITHABORT'</span><span style='color:gray'>)</span> <span style='color:gray'>=</span> 1
<span style='color:blue'>RETURN</span> <span style='color:gray'>NULL</span>
<span style='color:blue'>IF</span> <span style='color:fuchsia'>SESSIONPROPERTY</span><span style='color:gray'>(</span><span style='color:red'>'ANSI_WARNINGS'</span><span style='color:gray'>)</span> <span style='color:gray'>=</span> 1
<span style='color:blue'>RETURN</span> <span style='color:gray'>NULL</span>
<span style='color:blue'>RETURN</span> <span style='color:blue'>CASE</span> <span style='color:blue'>WHEN</span> <span style='color:fuchsia'>CONVERT</span><span style='color:gray'>(</span><span style='color:blue'>INT</span><span style='color:gray'>,</span> @Input<span style='color:gray'>)</span> <span style='color:gray'>IS</span> <span style='color:gray'>NULL</span> <span style='color:blue'>THEN</span> 0 <span style='color:blue'>ELSE</span> 1 <span style='color:blue'>END</span>
<span style='color:blue'>END</span>
</pre>
<p>
Let's try our function with something which is clearly a valid integer:
</p>
<pre>
<span style='color:blue'>SELECT</span> dbo<span style='color:gray'>.</span>IsINT<span style='color:gray'>(</span><span style='color:red'>'12345'</span><span style='color:gray'>)</span>
</pre>
<p>
Ooops - forgot to set ARITHABORT and ANSI_WARNINGS off so it returned NULL.
Good job it warned me. Let's try again.
</p>
<pre>
<span style='color:blue'>SET</span> <span style='color:blue'>ARITHABORT</span> <span style='color:blue'>OFF</span>
<span style='color:blue'>SET</span> <span style='color:blue'>ANSI_WARNINGS</span> <span style='color:blue'>OFF</span>
<span style='color:blue'>SELECT</span> dbo<span style='color:gray'>.</span>IsINT<span style='color:gray'>(</span><span style='color:red'>'12345'</span><span style='color:gray'>)</span>
</pre>
<p>
That worked (it returned 1). Let's throw a few more valid integers at it:
</p>
<pre>
<span style='color:blue'>SELECT</span> dbo<span style='color:gray'>.</span>IsINT<span style='color:gray'>(</span><span style='color:red'>'2147483647'</span><span style='color:gray'>)</span>
<span style='color:blue'>SELECT</span> dbo<span style='color:gray'>.</span>IsINT<span style='color:gray'>(</span><span style='color:red'>'0'</span><span style='color:gray'>)</span>
<span style='color:blue'>SELECT</span> dbo<span style='color:gray'>.</span>IsINT<span style='color:gray'>(</span><span style='color:red'>'-0'</span><span style='color:gray'>)</span>
<span style='color:blue'>SELECT</span> dbo<span style='color:gray'>.</span>IsINT<span style='color:gray'>(</span><span style='color:red'>'-'</span><span style='color:gray'>)</span> <span style='color:green'>-- treated as 0</span>
<span style='color:blue'>SELECT</span> dbo<span style='color:gray'>.</span>IsINT<span style='color:gray'>(</span><span style='color:red'>'-2147483648'</span><span style='color:gray'>)</span>
</pre>
<p>
Yup - they all returned 1. How about a couple which are out-of-range:
</p>
<pre>
<span style='color:blue'>SELECT</span> dbo<span style='color:gray'>.</span>IsINT<span style='color:gray'>(</span><span style='color:red'>'2147483648'</span><span style='color:gray'>)</span>
<span style='color:blue'>SELECT</span> dbo<span style='color:gray'>.</span>IsINT<span style='color:gray'>(</span><span style='color:red'>'-2147483649'</span><span style='color:gray'>)</span>
</pre>
<p>
Both these returned 0. Finally, let's check to see what happens when we pass a non-integer
number or a non-numeric value.
</p>
<pre>
<span style='color:blue'>SELECT</span> dbo<span style='color:gray'>.</span>IsINT<span style='color:gray'>(</span><span style='color:red'>'1.23'</span><span style='color:gray'>)</span>
<span style='color:red'>Msg 245, Level 16, State 1, Line 1</span>
<span style='color:red'>Conversion failed when converting the varchar value '1.23' to data type int.</span>
<span style='color:blue'>SELECT</span> dbo<span style='color:gray'>.</span>IsINT<span style='color:gray'>(</span><span style='color:red'>'N/A'</span><span style='color:gray'>)</span>
<span style='color:red'>Msg 245, Level 16, State 1, Line 1</span>
<span style='color:red'>Conversion failed when converting the varchar value 'N/A' to data type int.</span>
</pre>
<p>
Not so good. It seems that ARITHABORT can only be used to suppress errors of an arithmetic
nature, not all conversion errors.
</p>
<h2>Numeric, but not Integer</h2>
<p>
We can cater for non-numerics by using ISNUMERIC within our function:
</p>
<pre>
<span style='color:blue'>RETURN</span> <span style='color:blue'>CASE</span> <span style='color:blue'>WHEN</span> <span style='color:fuchsia'>ISNUMERIC</span><span style='color:gray'>(</span>@Input<span style='color:gray'>)</span> <span style='color:gray'>=</span> 0 <span style='color:gray'>OR</span> <span style='color:fuchsia'>CONVERT</span><span style='color:gray'>(</span><span style='color:blue'>INT</span><span style='color:gray'>,</span> @Input<span style='color:gray'>)</span> <span style='color:gray'>IS</span> <span style='color:gray'>NULL</span> <span style='color:blue'>THEN</span> 0 <span style='color:blue'>ELSE</span> 1 <span style='color:blue'>END</span>
</pre>
<p>
But how are we going to cope with '1.23' without raising an error? I guess we could convert
the input to a FLOAT first (on the assumption that all genuine numerics will convert to a FLOAT)
and then check to see if there are any digits to the right of the decimal place. Or we could
take a look for a '.' within the input string. We might need to be a little careful as I'm sure
there are some locales which use a character other than '.' as the decimal symbol, although
I'm not sure what support SQL Server has for these. A quick check in Control Panel shows that
the decimal symbol is indeed configurable within Windows itself, with the default in France being ','.
I can't see any support for this within SQL Server though so perhaps that's a red herring.
</p>
<p>
So let's go with the simple option of looking for a '.' within the input string; if we find one
we know the input cannot be an integer. (I'm ignoring the question of whether or not "1.0" should be
considered an integer for the moment.)
</p>
<p>
Our RETURN statement thus becomes:
</p>
<pre>
<span style='color:blue'>RETURN</span> <span style='color:blue'>CASE</span> <span style='color:blue'>WHEN</span> <span style='color:fuchsia'>CHARINDEX</span><span style='color:gray'>(</span><span style='color:red'>'.'</span><span style='color:gray'>,</span> @Input<span style='color:gray'>)</span> <span style='color:gray'>!=</span> 0 <span style='color:gray'>OR</span> <span style='color:fuchsia'>ISNUMERIC</span><span style='color:gray'>(</span>@Input<span style='color:gray'>)</span> <span style='color:gray'>=</span> 0 <span style='color:gray'>OR</span> <span style='color:fuchsia'>CONVERT</span><span style='color:gray'>(</span><span style='color:blue'>INT</span><span style='color:gray'>,</span> @Input<span style='color:gray'>)</span> <span style='color:gray'>IS</span> <span style='color:gray'>NULL</span> <span style='color:blue'>THEN</span> 0 <span style='color:blue'>ELSE</span> 1 <span style='color:blue'>END</span>
</pre>
<p>
Surely we must have tested for everything now: we only even attempt the conversion if the
input is devoid of a '.' and is considered to be numeric, and then we ignore the result
of any arithmetic overflow. Job done.
</p>
<h2>Exponential Notation</h2>
<p>
Hang on a moment. What about integers expressed in exponential form? What happens if we
try our IsINT function against '1E5'. It doesn't contain a '.', is numeric, and shouldn't
cause an overflow. Let's see:
</p>
<pre>
<span style='color:blue'>SELECT</span> dbo<span style='color:gray'>.</span>IsINT<span style='color:gray'>(</span><span style='color:red'>'1E5'</span><span style='color:gray'>)</span>
<span style='color:red'>Msg 245, Level 16, State 1, Line 1</span>
<span style='color:red'>Conversion failed when converting the varchar value '1E5' to data type int.</span>
</pre>
<p>
Now 1E5 is certainly an integer. It's just 100000 expressed in exponential form. But the
requirement was to <cite>"to make sure that the supplied VARCHAR can be converted to
an INT"</cite>. So the fact that 1E5 <em>is</em> an
integer is irrelevant - we only care about whether or not it can be converted to an INT.
It cannot. In fact, this little 'Get Out of Jail Free' card allows us to ignore the issue
of whether or not "1.0" is an integer or not too - CONVERT chokes on it, and that's all that
counts.
</p>
<p>
So, it's looks like we're going to have to handle this as a special case. Using the same
approach we used when checking for a '.' seems the right thing to do. Actually, seeing as we'll
have to check for '.', 'E' and 'e' we might as well use LIKE.
</p>
<h2>Solution</h2>
<p>
The final solution we end up with is therefore:
</p>
<pre>
<span style='color:blue'>CREATE</span> <span style='color:blue'>FUNCTION</span> dbo<span style='color:gray'>.</span>IsINT
<span style='color:gray'>(</span>
@Input <span style='color:blue'>VARCHAR</span><span style='color:gray'>(</span>255<span style='color:gray'>)</span>
<span style='color:gray'>)</span>
<span style='color:blue'>RETURNS</span> <span style='color:blue'>BIT</span>
<span style='color:blue'>AS</span>
<span style='color:green'>-- Note to callers: ARITHABORT and ANSI_WARNINGS must be set to OFF</span>
<span style='color:green'>-- before calling; if not this function will always return NULL.</span>
<span style='color:blue'>BEGIN</span>
<span style='color:blue'>IF</span> <span style='color:fuchsia'>SESSIONPROPERTY</span><span style='color:gray'>(</span><span style='color:red'>'ARITHABORT'</span><span style='color:gray'>)</span> <span style='color:gray'>=</span> 1
<span style='color:blue'>RETURN</span> <span style='color:gray'>NULL</span>
<span style='color:gray'></span>
<span style='color:blue'>IF</span> <span style='color:fuchsia'>SESSIONPROPERTY</span><span style='color:gray'>(</span><span style='color:red'>'ANSI_WARNINGS'</span><span style='color:gray'>)</span> <span style='color:gray'>=</span> 1
<span style='color:blue'>RETURN</span> <span style='color:gray'>NULL</span>
<span style='color:gray'></span>
<span style='color:blue'>RETURN</span> <span style='color:blue'>CASE</span> <span style='color:blue'>WHEN</span> @Input <span style='color:gray'>LIKE</span> <span style='color:red'>'%[.Ee]%'</span> <span style='color:gray'>OR</span> <span style='color:fuchsia'>ISNUMERIC</span><span style='color:gray'>(</span>@Input<span style='color:gray'>)</span> <span style='color:gray'>=</span> 0 <span style='color:gray'>OR</span> <span style='color:fuchsia'>CONVERT</span><span style='color:gray'>(</span><span style='color:blue'>INT</span><span style='color:gray'>,</span> @Input<span style='color:gray'>)</span> <span style='color:gray'>IS</span> <span style='color:gray'>NULL</span> <span style='color:blue'>THEN</span> 0 <span style='color:blue'>ELSE</span> 1 <span style='color:blue'>END</span>
<span style='color:blue'>END</span>
</pre>
<p>
That's quite a lot of effort just to establish whether a VARCHAR can convert into an INT. Imagine how
much more effort it would be if we needed to support DATETIME types too, with all their variations, or
DECIMALs with precision and scale to account for. And even now, I don't know for sure that IsINT won't
thrown an exception one day when someone passes something to it which I haven't allowed for.
</p>
<p>
So, the next time someone suggests you just load data as VARCHARs and do the conversion to a
string type within SQL Server, I hope you'll at least think twice before saying what a good idea
that is.
</p>
<h2>Post Script</h2>
<p>
In case any of you are thinking that the easy option would simply be to wrap the attempted conversion
in a TRY CATCH block, you're out of luck. SQL Server doesn't support the use of exception handling
within a user-defined function. And clearly you don't want to attempt to convert an entire set of
data within a TRY CATCH block as a single conversion failure will drop you into the CATCH block, whilst
we want good data to survive. The only way I can see to use the TRY CATCH approach is to CURSOR
over the set of data being converted. Okay for small sets of data perhaps, but not for anything larger.
</p>Ian Picknellhttp://www.blogger.com/profile/11237307149515983183noreply@blogger.com0tag:blogger.com,1999:blog-7929053599129622940.post-13513544991018417432010-03-31T21:43:00.002+01:002010-03-31T21:43:43.470+01:00Strong Name Storage<h2>Introduction</h2>
<p>
In <a href="http://ianpicknell.blogspot.com/2009/12/adding-strong-name-to-third-party.html">Adding a Strong Name to a Third-Party Assembly</a>
I described how you can add a strong name to a third-party assembly by disassembling that assembly into
IL using ILDASM and then re-assemble it with ILASM, specifying a strong name key.
</p>
<p>
I never really liked that approach. It seemed like using a sledge-hammer to crack a nut: just because we
want to add a few bytes (i.e. the strong name) into an assembly, we have to break the entire assembly
apart and put it back together again?
</p>
<p>
It always struck me as odd that the Strong Name Tool (SN.exe) provided no support for adding a strong
name signature to a assembly, unless one already existed or the assembly had at least been delayed signed
at compilation stage. Is adding a few bytes into the assembly to accommodate the strong name signature
<em>really</em> that hard?
</p>
<p>
So for the last couple of weeks I've been studying both:
</p>
<ul>
<li><a href="http://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/pecoff_v8.docx">Microsoft Portable Executable and Common Object File Format Specification</a> (Word) and</li>
<li><a href="http://download.microsoft.com/download/D/C/1/DC1B219F-3B11-4A05-9DA3-2D0F98B20917/Partition%20II%20Metadata.doc">Common Language Infrastructure (CLI) Partition II: Metadata Definition and Semantics</a> (Word).</li>
</ul>
<p>
These documents describe the physical structure of .NET assemblies. Using the information contained
therein, I've been able to establish just how a strong name is stored within an assembly. It's not
pretty.
</p><a name='more'></a>
<h2>Portable Executable / Common Object File Format</h2>
<p>
A PE/COFF file (which all Windows EXEs/DLLs are) contains a series of headers followed by a series
of sections. Each section must being on a boundary defined by the File Alignment field within one of
the headers, which indicates that each section begins on either a 512-byte or 4096-byte boundary. We
are only really interested in the section named ".text" as it is this section which contains the Strong
Name Signature and the CLI Meta Data.
</p>
<h2>CLR Runtime Header / CLI Header</h2>
<p>
Within one of the headers exists a Data Directory which references various tables and strings within
the image. The 14th entry in the Data Directory (counting from 0) contains the VirtualAddress and Size
of the a header referred to variously as the CLR Runtime Header or CLI Header. I'll use the term
CLI Header for the sake of consistency.
</p>
<p>
The VirtualAddress of the CLI Header is the address of the first byte of the header relative to the
image base when the section is loaded into memory. As we're dealing with the file itself, not an
in-memory image, we need to refer to the virtual address of the containing section and the file offset
of the first byte of the section to establish the file offset of the CLI Header.
</p>
<h2>Strong Name Signature</h2>
<p>
The strong name itself is referenced by two fields within the CLI Header - StrongNameSignatureRVA
and StrongNameSignatureSize. In a weak-named assembly both of these fields are set to 0. In a
strong-named assembly they contain the relative virtual address (i.e. the offset relative to the
start of the containing section) and size (in bytes) of the strong name signature. We <em>could</em>
attempt to derive the strong name ourselves, but there seems little point. All we really need to do is
set each byte in the strong name signature to 0x00 as that will represent a delay-signed assembly.
We can then have the Strong Name Tool (SN.exe) perform the actual signing.
</p>
<p>
Given that each section is padded to a 512-byte (or 4096-byte) boundary anyway, it's possible that
we can simply point StrongNameSignatureRVA to some location within this 'padding' area and make
<em>that</em> the strong name signature. But it's also quite likely that there will be insufficient
free space within the section to accommodate the strong name, so the section itself may need to be
lengthened. Obviously this will mean the sections which follow it will need to be moved, and all the
offsets within the headers will need to be updated to reference their new locations.
</p>
<h2>Public Key</h2>
<p>
Although we may leave the Strong Name Signature itself full of 0x00 bytes, we can't get away without
setting the Public Key. Establishing where to locate the Strong Name Signature above was fairly easy -
the location of the public key is a little more tricky.
</p>
<p>
First, we have to reference the MetaDataRVA and MetaDataSize fields within the CLI Header to establish
the location of the CLI Meta Data within the image. The CLI Meta Data consists of a header plus
five streams of data. One of these streams, #Blob (the Blob Heap), contains the 160-byte public key.
Unfortunately, entries within the Blob Heap are not named - they are referenced by their offset
relative to the start of the stream - so we can't just add a new entry to this stream and be done with
it. The entry which refers to the public key within the Blob Heap in within a separate steam, #~, which
consists of a series of tables with varying structures.
</p>
<p>
The simplest .NET application you can think of will contain the following tables: Module, TypeRef,
TypeDef, MethodDef, MemberRef, CustomAttribute, Assembly and AssemblyRef. The reference to the public
key is stored within the Assembly table. But to even find the Assembly table you need to seek past
all the tables preceding it (the list above is in order, so Assembly is one of the last tables).
</p>
<p>
To establish the location of the Assembly table you need to know which tables precede it (the list
above is a minimum set, there may be many other tables in a given assembly). This list of tables is
readily available. You also need to know how many rows are in each table. This too, is readily available.
Finally, you need to know how wide each row is. Establishing this is not so straight-forward.
For example, the Extends column in the TypeDef table contains an index into the TypeDef, TypeRef,
or TypeSpec tables. This index will occupy 2 or 4 bytes depending upon the number of rows in those
tables. Such an index is referred to in the specification as a coded index "using 2 bytes if the maximum
number of rows of tables t<sub>0</sub>, ...t<sub><em>n</em>-1</sub>, is less than
2<sup>(16 – (log <em>n</em>))</sup>, and using 4 bytes otherwise". In this example <em>n</em> is 3 (as
the index may reference one of 3 separate tables).
</p>
<h2>Method Implementations</h2>
<p>
One other thing we need to handle is the fact that the RVA field in reach row of the MethodDef
table contains the relative virtual address of that method's implementation.
Once we've inserted a zeroed-out strong name signature and a public key and into the file, these method
implementations will now be at different locations so we'll have to update the RVAs too. A similar
issue occurs with the RVA column in the FieldRVA table.
</p>
<h2>Summary</h2>
<p>
Given all the above, it's no surprise that the Strong Name tool opted not to support this scenario.
I don't give up easily, so I'm going to pursue my goal of creating a tool which performs all the magic
necessary to add a strong name where the Strong Name Tool cannot. But I suspect I won't achieve this
any time soon.
</p>
<h2>See Also</h2>
<ul>
<li><a href="http://ianpicknell.blogspot.com/2009/12/adding-strong-name-to-third-party.html">Adding a Strong Name to a Third-Party Assembly</a></li>
<li><a href="http://ianpicknell.blogspot.com/2010/02/tampering-with-strong-named-assembly.html">Tampering with a Strong-Named Assembly</a></li>
<li><a href="http://ianpicknell.blogspot.com/2010/02/faking-strong-name.html">Faking a Strong Name</a></li>
<li><a href="http://ianpicknell.blogspot.com/2010/02/evading-strong-name-integrity-check.html">Evading the Strong Name Integrity Check</a></li>
</ul>Ian Picknellhttp://www.blogger.com/profile/11237307149515983183noreply@blogger.com0tag:blogger.com,1999:blog-7929053599129622940.post-80669583378132485422010-03-12T07:38:00.001+00:002010-03-12T07:40:02.621+00:00Launching a ClickOnce Application<h2>Introduction</h2>
<p>
If you're anything like the me, you won’t be content with knowing that browsing to
http://MyServer/MyVirtualDir/MyApplication.application downloads, installs and launches MyApplication.
You'll want to know <em>how</em> this is achieved. This post goes under the covers of ClickOnce to show
you how it's done. Well, at least how <em>some</em> of it is done.
</p>
<p>
As this post makes quite a lot of references to registry entries, I'll use the standard abbreviations
HKCU and HKCR to represent the HKEY_CURRENT_USER and HKEY_CLASSES_ROOT hives respectively.
</p>
<h2>Retrieving the Deployment Manifest</h2>
<p>
What happens when you browse to http://MyServer/MyVirtualDir/MyApplication.application?
</p>
<p>
The first thing to realise is that no 'magic' is happening behind the scenes. When you send an
HTTP GET request to a web server (Internet Information Services, IIS, for example) the web server
will typically react in one of two ways. It will either establish that your request must be
forwarded to a component on the server for processing (a 'handler' in IIS terminology) or will
establish that the request represents a static file which should simply have its contents returned.
</p>
<p>
IIS stores details of handlers, and the requests which are forwarded to each handler, in its
metabase. Handlers can be defined in terms of a script map (potentially in combination with a
managed handler) or a module mapping. For example:
</p>
<ul>
<li>Requests matching the pattern *.asp are forwarded, via a script map, to the ISAPI module %windir%\system32\inetsrv\asp.dll.</li>
<li>Requests matching the pattern *.aspx are forwarded, via a script map, to the ISAPI module %windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll and to the managed handler System.Web.UI.PageHandlerFactory.</li>
<li>Requests matching the pattern *.shtml are forwarded, via a module mapping, to the pre-registered ServerSideIncludeModule module.</li>
</ul><a name='more'></a>
<p>
You can view the current script maps via IIS Manager or by issuing the following statement at the
command line whilst in your C:\Inetpub\AdminScripts folder:
</p>
<pre class="console">
cscript adsutil.vbs get W3SVC/ScriptMaps
</pre>
<p>
There is no static map, managed handler, or module mapping for an *.application request. Such requests
are picked up by the StaticFileModule module which matches * requests (i.e. requests not processed elsewhere).
</p>
<p>
Once IIS has established that the request represents a static file which will simply have its
contents returned, it looks up the MIME type which it should place in the Content-Type header
of the HTTP response. This, and not the <em>name</em> of the file requested, is to be used by
the browser to establish what type of data is present within the body of the HTTP response.
</p>
<p>
IIS stores a map from file extension to MIME type within its metabase. To see the default MIME
map for your IIS instance use IIS Manager or issue the following statement at the command line whilst
in your C:\Inetpub\AdminScripts folder:
</p>
<pre class="console">
cscript adsutil.vbs enum /MIMEMAP
</pre>
<p>
The MIME map reports than the .application extension maps to a MIME type of application/x-ms-application.
This value is therefore placed in the Content-Type HTTP response header. The HTTP response body simply
contains the unprocessed contents of the .application file itself.
</p>
<p>
Upon receiving the HTTP response, Internet Explorer looks up the file extension which should be
assumed, given the MIME type specified in the header HTTP response. If this seems a little odd
to you, you are not alone. I believe it stems from the fact that within Windows it is file
extensions, not MIME types, which are associated with applications. As the HTTP standard defines
that the MIME type should be used to determine the type of data, Windows has no real choice but
to map the MIME type onto a file extension, to then map from a file extension to an application.
</p>
<p>
Internet Explorer looks up application/x-ms-application first in the user-specific MIME
database at HKCU\Software\Classes\MIME\Database\Content Type and, if it fails to
find an entry there, the machine-specific MIME database at HKCR\MIME\Database\Content Type.
It reads the Extension sub-key and establishes that the MIME type application/x-ms-application
maps to the .application extension. Internet Explorer then saves the file into its Temporary Internet Files
folder (typically located at %USERPROFILE%\AppData\Local\Microsoft\Windows\Temporary Internet Files,
%USERPROFILE%\Local Settings\Temporary Internet Files or %SYSTEMROOT%\Temp\Temporary Internet Files).
Of course, you won't be able to <em>see</em> the file in Windows Explorer as Internet Explorer does its
usual cloak-and-dagger trick of preventing you access to files on your own PC. When I tested this, the
received file was placed within a hidden system folder called KEH38PJB which was within a hidden system
folder called Content.IE5 (I tested on Internet Explorer 8.0) beneath Temporary Internet Files. The file
name will be derived from the requested URL, albeit with the name modified to ensure uniqueness and to
apply the .application file extension. For example, suppose you create an ASPX page called
Default.aspx and have it return an HTTP response with the MIME type "application/x-ms-application"
(as I have done) you'll end up with a file beneath Temporary Internet Files called Default[1].application.
This again re-enforces the fact that it's the MIME type which drives this process, not the file name
extension as stored on the server.
</p>
<h2>Opening the Deployment Manifest</h2>
<p>
Once the deployment manifest has been stored in the Temporary Internet Files folder, Internet
Explorer then attempts to establish how it should handle the file with the (assumed and actual)
.application extension. It checks the user-specific file types at HKCU\Software\Classes and,
if it fails to find an .application sub-key there, checks for machine-specific file types at
HKCR. Via this means it establishes that the .application extension denotes an Application.Manifest
file. It then uses this information to check the user-specific HKCU\Software\Classes\Application.Manifest
and machine-specific HKCR\Application.Manifest keys to establish the CLSID of a library which handles
Application.Manifest files and yields the result {98af66e4-aa41-4226-b80f-0b1a8f34eeb4}. Finally, it
looks up this CLSID in the user-specific HKCU\Software\Classes\CLSID\{98af66e4-aa41-4226-b80f-0b1a8f34eeb4}
and machine-specific HKCR\CLSID\{98af66e4-aa41-4226-b80f-0b1a8f34eeb4} paths to establish that
.application files are handled by C:\WINDOWS\system32\<strong>DFshim.dll</strong>.
</p>
<p>
This is where things start to get a little complicated. I said earlier that no 'magic' was happening
behind the scenes. Well, whilst that was true for the process of retrieving the deployment manifest
it most certainly is not true of the process of actually launching the ClickOnce application once
the deployment manifest has been retrieved and the handler, DFshim.dll, has been identified.
</p>
<p>
DFshim.dll is described in the registry as the 'Manifest mime handler' although its file properties
describe it as the 'Application Deployment Support Library'. It implements the Internet Explorer MIME
handler COM interface. It is a native 32-bit DLL, written in Microsoft Visual C++ 2005, and was installed
into C:\Windows\system32 when the .NET Framework 2.0 was installed.
</p>
<p>
DFshim.dll has a hard-coded reference to <strong>DFdll.dll</strong>, which it locates by checking
the values of HKLM\SOFTWARE\Microsoft\.NETFramework\InstallRoot (typically C:\Windows\Microsoft.NET\Framework\)
and the keys beneath HKLM\SOFTWARE\Microsoft\.NETFramework\Policy\AppPatch (typically v2.0.50727,
even if a later version of the Framework is installed). DFdll.dll too is a native 32-bit DLL written
in Microsoft Visual C++ 2005.
</p>
<p>
DFdll.dll uses COM services exposed by <strong>DFsvc.exe</strong>, which is also located in the
.NET Framework folder. DFsvc.exe is a standard .NET MSIL assembly. DFsvc contains a single
Main method (within the System.Deployment.Application namespace) which simply calls the internal
System.Deployment.Application.DFServiceEntryPoint.Initialize method within System.Deployment.dll. The
Initialize method registers System.Deployment.Application.DeploymentServiceCom with COM via
System.Runtime.InteropServices.RegistrationServices (i.e. it performs the equivalent of
CoRegisterClassObject in COM) using the CLSID {33246f92-d56f-4e34-837a-9a49bfc91df3}. This is the
means by which its services are made available to DFdll.dll.
</p>
<p>
The COM service exposed by System.Deployment.Application.DeploymentServiceCom delegates the
majority of its methods to other non-ComVisible classes within the System.Deployment.Application
namespace. For example, the public ActivateDeployment method calls ActivateDeployment on a new
System.Deployment.Application.ApplicationActivator instance, the public CheckForDeploymentUpdate
method calls CheckForDeploymentUpdate on System.Deployment.Application.SubscriptionStore, etc.
</p>
<p>
It is clear that the vast majority of the work surrounding the actual installation and launch of the
ClickOnce application is undertaken by classes within the System.Deployment namespace, hosted within
DFsvc.exe. DFshim.dll and DFdll.dll appear to primarily be responsible for arbitrating between the
COM-based world of Internet Explorer and the .NET-based world of ClickOnce.
</p>
<h2>Summary</h2>
<p>
Despite appearances, the Web server has very little involvement in the process of launching a
ClickOnce application: it simply serves-up the contents of the deployment manifest along with
the appropriate MIME type. The real fun happens on the client.
</p>Ian Picknellhttp://www.blogger.com/profile/11237307149515983183noreply@blogger.com5tag:blogger.com,1999:blog-7929053599129622940.post-87066015244147973112010-03-02T07:55:00.001+00:002010-03-02T07:58:24.691+00:00Serialising Enumerations with WCF<h2>Introduction</h2>
<p>
There are occasions when the exceptions thrown by Windows Communication Foundation (WCF) are clear
and unambiguous. There are also occassions where they are not. In this post I'll describe a real-world
problem which a colleague encountered recently together which the steps which you might reasonably
perform to diagnose it.
</p>
<p>
The problem manifested itself in a System.ServiceModel.CommunicationException being thrown with
nothing particularly useful in the Exception message itself.
</p>
<pre class="console">
System.ServiceModel.CommunicationException: An error occurred while receiving th
e HTTP response to http://localhost:8731/Design_Time_Addresses/WcfServiceLibrary
/Service1/. This could be due to the service endpoint binding not using the HTTP
protocol. This could also be due to an HTTP request context being aborted by th
e server (possibly due to the service shutting down). See server logs for more d
etails. ---> System.Net.WebException: The underlying connection was closed: An u
nexpected error occurred on a receive. ---> System.IO.IOException: Unable to rea
d data from the transport connection: An existing connection was forcibly closed
by the remote host. ---> System.Net.Sockets.SocketException: An existing connec
tion was forcibly closed by the remote host
</pre>
<p>
Clearly something very bad happened. But the WCF service itself was still up
and running, so one of the theories put forward in the message above <cite>"possibly due to the service
shutting down"</cite> can be quickly dismissed. I'm sure it's obvious, but the above message was
generated by my mock-up of the problem rather than the original problem itself.
</p>
<h2>Mock-up of Original Problem</h2>
<p>
Let me quickly run through the mock-up of the problem which I put together. It consists of three
projects: <strong>WcfServiceLibrary</strong> contains a single WCF service called Service1,
<strong>WcfConsoleHost</strong> provides a host for Service1 in the form of a Console Application,
and <strong>WcfClient</strong> consumes Service1.
</p>
<img style="border-style: none" width="348" height="577" alt="Solution Explorer view of SerialisingEnumerationsWithWCF.sln" src="http://lh6.ggpht.com/_fkuTkJfqL8Q/S4r1vQfGDJI/AAAAAAAAAD4/cXGVTWmwUs8/s800/SerialisingEnumerationsWithWCF_SolutionExplorer.jpg" />
<a name='more'></a>
<p>
WcfServiceLibrary consists of a Service1 class which implements an IService1 interface. Here's the interface:
</p>
<pre>
<span style='color:blue'>using</span> System;
<span style='color:blue'>using</span> System.Runtime.Serialization;
<span style='color:blue'>using</span> System.ServiceModel;
<span style='color:blue'>namespace</span> WcfServiceLibrary
{
<span style='color:blue'>public</span> <span style='color:blue'>enum</span> <span style='color:#2B91AF'>Gender</span>
{
Male = 1,
Female = 2
}
[<span style='color:#2B91AF'>DataContract</span>]
<span style='color:blue'>public</span> <span style='color:blue'>class</span> <span style='color:#2B91AF'>MyCompositeType</span>
{
[<span style='color:#2B91AF'>DataMember</span>]
<span style='color:blue'>public</span> <span style='color:blue'>bool</span> MyBool
{
<span style='color:blue'>get</span>;
<span style='color:blue'>set</span>;
}
[<span style='color:#2B91AF'>DataMember</span>]
<span style='color:blue'>public</span> <span style='color:blue'>string</span> MyString
{
<span style='color:blue'>get</span>;
<span style='color:blue'>set</span>;
}
[<span style='color:#2B91AF'>DataMember</span>]
<span style='color:blue'>public</span> <span style='color:#2B91AF'>Gender</span> MyEnum
{
<span style='color:blue'>get</span>;
<span style='color:blue'>set</span>;
}
}
[<span style='color:#2B91AF'>ServiceContract</span>]
<span style='color:blue'>public</span> <span style='color:blue'>interface</span> <span style='color:#2B91AF'>IService1</span>
{
[<span style='color:#2B91AF'>OperationContract</span>]
<span style='color:#2B91AF'>MyCompositeType</span> GetMyCompositeType(<span style='color:blue'>bool</span> initialised);
}
}
</pre>
<p>
And the class:
</p>
<pre>
<span style='color:blue'>using</span> System;
<span style='color:blue'>namespace</span> WcfServiceLibrary
{
<span style='color:blue'>public</span> <span style='color:blue'>class</span> <span style='color:#2B91AF'>Service1</span> : <span style='color:#2B91AF'>IService1</span>
{
<span style='color:blue'>public</span> <span style='color:#2B91AF'>MyCompositeType</span> GetMyCompositeType(<span style='color:blue'>bool</span> initialised)
{
<span style='color:#2B91AF'>MyCompositeType</span> myCompositeObject = <span style='color:blue'>new</span> <span style='color:#2B91AF'>MyCompositeType</span>();
<span style='color:blue'>if</span> (initialised)
{
myCompositeObject.MyBool = <span style='color:blue'>true</span>;
myCompositeObject.MyString = <span style='color:#A31515'>"Hello World"</span>;
myCompositeObject.MyEnum = <span style='color:#2B91AF'>Gender</span>.Male;
}
<span style='color:blue'>return</span> myCompositeObject;
}
}
}
</pre>
<p>
WcfConsoleHost just contains a Program class which hosts Service1:
</p>
<pre>
<span style='color:blue'>using</span> System;
<span style='color:blue'>using</span> System.ServiceModel;
<span style='color:blue'>namespace</span> WcfConsoleHost
{
<span style='color:blue'>class</span> <span style='color:#2B91AF'>Program</span>
{
<span style='color:blue'>static</span> <span style='color:blue'>void</span> Main(<span style='color:blue'>string</span>[]args)
{
<span style='color:blue'>try</span>
{
<span style='color:#2B91AF'>ServiceHost</span> serviceHost = <span style='color:blue'>new</span> <span style='color:#2B91AF'>ServiceHost</span>(<span style='color:blue'>typeof</span>(WcfServiceLibrary.<span style='color:#2B91AF'>Service1</span>));
serviceHost.Open();
<span style='color:blue'>foreach</span> (System.ServiceModel.Description.<span style='color:#2B91AF'>ServiceEndpoint</span> serviceEndPoint <span style='color:blue'>in</span> serviceHost.Description.Endpoints)
{
<span style='color:#2B91AF'>Console</span>.WriteLine(<span style='color:#A31515'>"Listening at {0}..."</span>, serviceEndPoint.Address.ToString());
}
}
<span style='color:blue'>catch</span> (System.<span style='color:#2B91AF'>Exception</span> ex)
{
System.<span style='color:#2B91AF'>Console</span>.WriteLine(ex.ToString());
}
<span style='color:#2B91AF'>Console</span>.ReadLine();
}
}
}
</pre>
<p>
Finally, we have WcfClient which again just consist of a Program class. Upon execution, WcfClient
simply retrieves an initialised instance of MyCompositeType via WCF and writes its details to the
console.
</p>
<pre>
<span style='color:blue'>using</span> System;
<span style='color:blue'>namespace</span> WcfClient
{
<span style='color:blue'>class</span> <span style='color:#2B91AF'>Program</span>
{
<span style='color:blue'>static</span> <span style='color:blue'>void</span> Main(<span style='color:blue'>string</span>[]args)
{
<span style='color:blue'>try</span>
{
DisplayMyCompositeObject(<span style='color:blue'>true</span>);
}
<span style='color:blue'>catch</span> (System.<span style='color:#2B91AF'>Exception</span> ex)
{
<span style='color:#2B91AF'>Console</span>.WriteLine(ex);
}
<span style='color:#2B91AF'>Console</span>.ReadLine();
}
<span style='color:blue'>static</span> <span style='color:blue'>void</span> DisplayMyCompositeObject(<span style='color:blue'>bool</span> initialised)
{
Service1Reference.<span style='color:#2B91AF'>MyCompositeType</span> myCompositeObject = GetMyCompositeType(initialised);
<span style='color:#2B91AF'>Console</span>.WriteLine(<span style='color:#A31515'>"MyBool={0}"</span>, myCompositeObject.MyBool);
<span style='color:#2B91AF'>Console</span>.WriteLine(<span style='color:#A31515'>"MyString={0}"</span>, myCompositeObject.MyString);
<span style='color:#2B91AF'>Console</span>.WriteLine(<span style='color:#A31515'>"MyString={0}"</span>, myCompositeObject.MyEnum);
<span style='color:#2B91AF'>Console</span>.WriteLine();
}
<span style='color:blue'>static</span> Service1Reference.<span style='color:#2B91AF'>MyCompositeType</span> GetMyCompositeType(<span style='color:blue'>bool</span> initialised)
{
Service1Reference.<span style='color:#2B91AF'>Service1Client</span> service1Client = <span style='color:blue'>null</span>;
Service1Reference.<span style='color:#2B91AF'>MyCompositeType</span> myCompositeObject = <span style='color:blue'>null</span>;
<span style='color:blue'>try</span>
{
service1Client = <span style='color:blue'>new</span> Service1Reference.<span style='color:#2B91AF'>Service1Client</span>();
myCompositeObject = service1Client.GetMyCompositeType(initialised);
service1Client.Close();
}
<span style='color:blue'>catch</span>
{
<span style='color:blue'>if</span>(service1Client != <span style='color:blue'>null</span>)
{
service1Client.Abort();
}
<span style='color:blue'>throw</span>;
}
<span style='color:blue'>return</span> myCompositeObject;
}
}
}
</pre>
<p>
For the sake of completeness, I'll also show you the configuration files I'm using. First, the
server-side configuration file in WcfConsoleHost:
</p>
<pre>
<span style='color:blue'><?</span><span style='color:#A31515'>xml</span><span style='color:blue'> </span><span style='color:red'>version</span><span style='color:blue'>=</span>"<span style='color:blue'>1.0</span>"<span style='color:blue'> </span><span style='color:red'>encoding</span><span style='color:blue'>=</span>"<span style='color:blue'>utf-8</span>"<span style='color:blue'> ?></span>
<span style='color:blue'><</span><span style='color:#A31515'>configuration</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>system.web</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>compilation</span><span style='color:blue'> </span><span style='color:red'>debug</span><span style='color:blue'>=</span>"<span style='color:blue'>true</span>"<span style='color:blue'> /></span>
<span style='color:blue'> </</span><span style='color:#A31515'>system.web</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>system.serviceModel</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>services</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>service</span><span style='color:blue'> </span><span style='color:red'>name</span><span style='color:blue'>=</span>"<span style='color:blue'>WcfServiceLibrary.Service1</span>"<span style='color:blue'> </span><span style='color:red'>behaviorConfiguration</span><span style='color:blue'>=</span>"<span style='color:blue'>WcfServiceLibrary.Service1Behavior</span>"<span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>host</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>baseAddresses</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>add</span><span style='color:blue'> </span><span style='color:red'>baseAddress</span><span style='color:blue'>=</span>"<span style='color:blue'>http://localhost:8731/Design_Time_Addresses/WcfServiceLibrary/Service1/</span>"<span style='color:blue'>/></span>
<span style='color:blue'> </</span><span style='color:#A31515'>baseAddresses</span><span style='color:blue'>></span>
<span style='color:blue'> </</span><span style='color:#A31515'>host</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>endpoint</span><span style='color:blue'> </span><span style='color:red'>address</span><span style='color:blue'>=</span>""<span style='color:blue'> </span><span style='color:red'>binding</span><span style='color:blue'>=</span>"<span style='color:blue'>basicHttpBinding</span>"<span style='color:blue'> </span><span style='color:red'>contract</span><span style='color:blue'>=</span>"<span style='color:blue'>WcfServiceLibrary.IService1</span>"<span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>identity</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>dns</span><span style='color:blue'> </span><span style='color:red'>value</span><span style='color:blue'>=</span>"<span style='color:blue'>localhost</span>"<span style='color:blue'>/></span>
<span style='color:blue'> </</span><span style='color:#A31515'>identity</span><span style='color:blue'>></span>
<span style='color:blue'> </</span><span style='color:#A31515'>endpoint</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>endpoint</span><span style='color:blue'> </span><span style='color:red'>address</span><span style='color:blue'>=</span>"<span style='color:blue'>mex</span>"<span style='color:blue'> </span><span style='color:red'>binding</span><span style='color:blue'>=</span>"<span style='color:blue'>mexHttpBinding</span>"<span style='color:blue'> </span><span style='color:red'>contract</span><span style='color:blue'>=</span>"<span style='color:blue'>IMetadataExchange</span>"<span style='color:blue'>/></span>
<span style='color:blue'> </</span><span style='color:#A31515'>service</span><span style='color:blue'>></span>
<span style='color:blue'> </</span><span style='color:#A31515'>services</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>behaviors</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>serviceBehaviors</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>behavior</span><span style='color:blue'> </span><span style='color:red'>name</span><span style='color:blue'>=</span>"<span style='color:blue'>WcfServiceLibrary.Service1Behavior</span>"<span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>serviceMetadata</span><span style='color:blue'> </span><span style='color:red'>httpGetEnabled</span><span style='color:blue'>=</span>"<span style='color:blue'>true</span>"<span style='color:blue'>/></span>
<span style='color:blue'> <</span><span style='color:#A31515'>serviceDebug</span><span style='color:blue'> </span><span style='color:red'>includeExceptionDetailInFaults</span><span style='color:blue'>=</span>"<span style='color:blue'>true</span>"<span style='color:blue'> /></span>
<span style='color:blue'> </</span><span style='color:#A31515'>behavior</span><span style='color:blue'>></span>
<span style='color:blue'> </</span><span style='color:#A31515'>serviceBehaviors</span><span style='color:blue'>></span>
<span style='color:blue'> </</span><span style='color:#A31515'>behaviors</span><span style='color:blue'>></span>
<span style='color:blue'> </</span><span style='color:#A31515'>system.serviceModel</span><span style='color:blue'>></span>
<span style='color:blue'></</span><span style='color:#A31515'>configuration</span><span style='color:blue'>></span>
</pre>
<p>
And the client-side configuration file in WcfClient:
</p>
<pre>
<span style='color:blue'><?</span><span style='color:#A31515'>xml</span><span style='color:blue'> </span><span style='color:red'>version</span><span style='color:blue'>=</span>"<span style='color:blue'>1.0</span>"<span style='color:blue'> </span><span style='color:red'>encoding</span><span style='color:blue'>=</span>"<span style='color:blue'>utf-8</span>"<span style='color:blue'> ?></span>
<span style='color:blue'><</span><span style='color:#A31515'>configuration</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>system.serviceModel</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>bindings</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>basicHttpBinding</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>binding</span><span style='color:blue'> </span><span style='color:red'>name</span><span style='color:blue'>=</span>"<span style='color:blue'>BasicHttpBinding_IService1</span>"<span style='color:blue'> </span><span style='color:red'>closeTimeout</span><span style='color:blue'>=</span>"<span style='color:blue'>00:01:00</span>"
<span style='color:blue'> </span><span style='color:red'>openTimeout</span><span style='color:blue'>=</span>"<span style='color:blue'>00:01:00</span>"<span style='color:blue'> </span><span style='color:red'>receiveTimeout</span><span style='color:blue'>=</span>"<span style='color:blue'>00:10:00</span>"<span style='color:blue'> </span><span style='color:red'>sendTimeout</span><span style='color:blue'>=</span>"<span style='color:blue'>00:01:00</span>"
<span style='color:blue'> </span><span style='color:red'>allowCookies</span><span style='color:blue'>=</span>"<span style='color:blue'>false</span>"<span style='color:blue'> </span><span style='color:red'>bypassProxyOnLocal</span><span style='color:blue'>=</span>"<span style='color:blue'>false</span>"<span style='color:blue'> </span><span style='color:red'>hostNameComparisonMode</span><span style='color:blue'>=</span>"<span style='color:blue'>StrongWildcard</span>"
<span style='color:blue'> </span><span style='color:red'>maxBufferSize</span><span style='color:blue'>=</span>"<span style='color:blue'>65536</span>"<span style='color:blue'> </span><span style='color:red'>maxBufferPoolSize</span><span style='color:blue'>=</span>"<span style='color:blue'>524288</span>"<span style='color:blue'> </span><span style='color:red'>maxReceivedMessageSize</span><span style='color:blue'>=</span>"<span style='color:blue'>65536</span>"
<span style='color:blue'> </span><span style='color:red'>messageEncoding</span><span style='color:blue'>=</span>"<span style='color:blue'>Text</span>"<span style='color:blue'> </span><span style='color:red'>textEncoding</span><span style='color:blue'>=</span>"<span style='color:blue'>utf-8</span>"<span style='color:blue'> </span><span style='color:red'>transferMode</span><span style='color:blue'>=</span>"<span style='color:blue'>Buffered</span>"
<span style='color:blue'> </span><span style='color:red'>useDefaultWebProxy</span><span style='color:blue'>=</span>"<span style='color:blue'>true</span>"<span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>readerQuotas</span><span style='color:blue'> </span><span style='color:red'>maxDepth</span><span style='color:blue'>=</span>"<span style='color:blue'>32</span>"<span style='color:blue'> </span><span style='color:red'>maxStringContentLength</span><span style='color:blue'>=</span>"<span style='color:blue'>8192</span>"<span style='color:blue'> </span><span style='color:red'>maxArrayLength</span><span style='color:blue'>=</span>"<span style='color:blue'>16384</span>"
<span style='color:blue'> </span><span style='color:red'>maxBytesPerRead</span><span style='color:blue'>=</span>"<span style='color:blue'>4096</span>"<span style='color:blue'> </span><span style='color:red'>maxNameTableCharCount</span><span style='color:blue'>=</span>"<span style='color:blue'>16384</span>"<span style='color:blue'> /></span>
<span style='color:blue'> <</span><span style='color:#A31515'>security</span><span style='color:blue'> </span><span style='color:red'>mode</span><span style='color:blue'>=</span>"<span style='color:blue'>None</span>"<span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>transport</span><span style='color:blue'> </span><span style='color:red'>clientCredentialType</span><span style='color:blue'>=</span>"<span style='color:blue'>None</span>"<span style='color:blue'> </span><span style='color:red'>proxyCredentialType</span><span style='color:blue'>=</span>"<span style='color:blue'>None</span>" <span style='color:red'>realm</span><span style='color:blue'>=</span>""<span style='color:blue'> /></span>
<span style='color:blue'> <</span><span style='color:#A31515'>message</span><span style='color:blue'> </span><span style='color:red'>clientCredentialType</span><span style='color:blue'>=</span>"<span style='color:blue'>UserName</span>"<span style='color:blue'> </span><span style='color:red'>algorithmSuite</span><span style='color:blue'>=</span>"<span style='color:blue'>Default</span>"<span style='color:blue'> /></span>
<span style='color:blue'> </</span><span style='color:#A31515'>security</span><span style='color:blue'>></span>
<span style='color:blue'> </</span><span style='color:#A31515'>binding</span><span style='color:blue'>></span>
<span style='color:blue'> </</span><span style='color:#A31515'>basicHttpBinding</span><span style='color:blue'>></span>
<span style='color:blue'> </</span><span style='color:#A31515'>bindings</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>client</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>endpoint</span><span style='color:blue'> </span><span style='color:red'>address</span><span style='color:blue'>=</span>"<span style='color:blue'>http://localhost:8731/Design_Time_Addresses/WcfServiceLibrary/Service1/</span>"
<span style='color:blue'> </span><span style='color:red'>binding</span><span style='color:blue'>=</span>"<span style='color:blue'>basicHttpBinding</span>"<span style='color:blue'> </span><span style='color:red'>bindingConfiguration</span><span style='color:blue'>=</span>"<span style='color:blue'>BasicHttpBinding_IService1</span>"
<span style='color:blue'> </span><span style='color:red'>contract</span><span style='color:blue'>=</span>"<span style='color:blue'>Service1Reference.IService1</span>"<span style='color:blue'> </span><span style='color:red'>name</span><span style='color:blue'>=</span>"<span style='color:blue'>BasicHttpBinding_IService1</span>"<span style='color:blue'> /></span>
<span style='color:blue'> </</span><span style='color:#A31515'>client</span><span style='color:blue'>></span>
<span style='color:blue'> </</span><span style='color:#A31515'>system.serviceModel</span><span style='color:blue'>></span>
<span style='color:blue'></</span><span style='color:#A31515'>configuration</span><span style='color:blue'>></span>
</pre>
<h2>Testing the Application</h2>
<p>
With all this in place we can start the WcfConsoleHost:
</p>
<pre class="console">
Listening at http://localhost:8731/Design_Time_Addresses/WcfServiceLibrary/Servi
ce1/...
Listening at http://localhost:8731/Design_Time_Addresses/WcfServiceLibrary/Servi
ce1/mex...
</pre>
<p>
Firing up WcfClient the gives us a nice simple:
</p>
<pre class="console">
MyBool=True
MyString=Hello World
MyString=Male
</pre>
<p>
So what's the problem? Well the problem is that if we pass <strong>false</strong> to
WcfClient.Program.DisplayMyCompositeObject (and hence to WcfClient.Program.GetMyCompositeType and hence
to WcfServiceLibrary.Service1.GetMyCompositeType), to ask that an <em>uninitialised</em> instance of
MyCompositeObject be returned, we get the following output:
</p>
<pre class="console">
System.ServiceModel.CommunicationException: An error occurred while receiving th
e HTTP response to http://localhost:8731/Design_Time_Addresses/WcfServiceLibrary
/Service1/. This could be due to the service endpoint binding not using the HTTP
protocol. This could also be due to an HTTP request context being aborted by th
e server (possibly due to the service shutting down). See server logs for more d
etails. ---> System.Net.WebException: The underlying connection was closed: An u
nexpected error occurred on a receive. ---> System.IO.IOException: Unable to rea
d data from the transport connection: An existing connection was forcibly closed
by the remote host. ---> <span style="color:Red">.Net.Sockets.SocketException: An existing connec
tion was forcibly closed by the remote host</span>
at System.Net.Sockets.Socket.Receive(Byte[] buffer, Int32 offset, Int32 size,
SocketFlags socketFlags)
at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 s
ize)
--- End of inner exception stack trace ---
at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 s
ize)
at System.Net.PooledStream.Read(Byte[] buffer, Int32 offset, Int32 size)
at System.Net.Connection.SyncRead(HttpWebRequest request, Boolean userRetriev
edStream, Boolean probeRead)
--- End of inner exception stack trace ---
at System.Net.HttpWebRequest.GetResponse()
at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpCha
nnelRequest.WaitForReply(TimeSpan timeout)
--- End of inner exception stack trace ---
Server stack trace:
at System.ServiceModel.Channels.HttpChannelUtilities.ProcessGetResponseWebExc
eption(WebException webException, HttpWebRequest request, HttpAbortReason abortR
eason)
at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpCha
nnelRequest.WaitForReply(TimeSpan timeout)
at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeS
pan timeout)
at System.ServiceModel.Dispatcher.RequestChannelBinder.Request(Message messag
e, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean on
eway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan tim
eout)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean on
eway, ProxyOperationRuntime operation, Object[] ins, Object[] outs)
at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCall
Message methodCall, ProxyOperationRuntime operation)
at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage req
Msg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgDa
ta, Int32 type)
at WcfClient.Service1Reference.IService1.GetMyCompositeType(Boolean initialis
ed)
at WcfClient.Service1Reference.Service1Client.GetMyCompositeType(Boolean init
ialised) in C:\Users\Ian.Picknell\Documents\Blog\WCF\Serialising Enumerations wi
th WCF\WcfClient\Service References\Service1Reference\Reference.cs:line 140
at WcfClient.Program.GetMyCompositeType(Boolean initialised) in C:\Users\Ian.
Picknell\Documents\Blog\WCF\Serialising Enumerations with WCF\WcfClient\Program.
cs:line 48
at WcfClient.Program.DisplayMyCompositeObject(Boolean initialised) in C:\User
s\Ian.Picknell\Documents\Blog\WCF\Serialising Enumerations with WCF\WcfClient\Pr
ogram.cs:line 23
at WcfClient.Program.Main(String[] args) in C:\Users\Ian.Picknell\Documents\B
log\WCF\Serialising Enumerations with WCF\WcfClient\Program.cs:line 11
</pre>
<p>
I've highlighted what I believe to be the first exception thrown. One thing you may
have noticed in the server-side configuration file earlier in the post was that I'm asking
for exception details to be included within faults, which is generally a good idea during
development. The inner-most exception (according to the client) was <code>
.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host</code>.
</p>
<h2>Diagnosing the Problem</h2>
<p>
Lets enable logging on both client and server to obtain more details. We update <strong>both</strong>
configuration files to add the following within the <code><system.serviceModel></code> element:
</p>
<pre>
<span style='color:blue'><</span><span style='color:#A31515'>diagnostics</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>messageLogging</span><span style='color:blue'> </span><span style='color:red'>logEntireMessage</span><span style='color:blue'>=</span>"<span style='color:blue'>true</span>"<span style='color:blue'> </span><span style='color:red'>logMalformedMessages</span><span style='color:blue'>=</span>"<span style='color:blue'>true</span>"<span style='color:blue'> </span><span style='color:red'>logMessagesAtServiceLevel</span><span style='color:blue'>=</span>"<span style='color:blue'>true</span>"<span style='color:blue'> </span><span style='color:red'>logMessagesAtTransportLevel</span><span style='color:blue'>=</span>"<span style='color:blue'>true</span>"<span style='color:blue'> /></span>
<span style='color:blue'></</span><span style='color:#A31515'>diagnostics</span><span style='color:blue'>></span>
</pre>
<p>
We then direct the messages to an appropriate listener by adding a <code><system.diagnostics></code>
element directly within the <code><configuration></code> element.
</p>
<pre>
<span style='color:blue'><</span><span style='color:#A31515'>system.diagnostics</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>sources</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>source</span><span style='color:blue'> </span><span style='color:red'>name</span><span style='color:blue'>=</span>"<span style='color:blue'>System.ServiceModel</span>"<span style='color:blue'> </span><span style='color:red'>switchValue</span><span style='color:blue'>=</span>"<span style='color:blue'>Information, ActivityTracing</span>"<span style='color:blue'> </span><span style='color:red'>propagateActivity</span><span style='color:blue'>=</span>"<span style='color:blue'>true</span>"<span style='color:blue'> ></span>
<span style='color:blue'> <</span><span style='color:#A31515'>listeners</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>add</span><span style='color:blue'> </span><span style='color:red'>name</span><span style='color:blue'>=</span>"<span style='color:blue'>xml</span>"<span style='color:blue'>/></span>
<span style='color:blue'> </</span><span style='color:#A31515'>listeners</span><span style='color:blue'>></span>
<span style='color:blue'> </</span><span style='color:#A31515'>source</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>source</span><span style='color:blue'> </span><span style='color:red'>name</span><span style='color:blue'>=</span>"<span style='color:blue'>System.ServiceModel.MessageLogging</span>"<span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>listeners</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>add</span><span style='color:blue'> </span><span style='color:red'>name</span><span style='color:blue'>=</span>"<span style='color:blue'>xml</span>"<span style='color:blue'>/></span>
<span style='color:blue'> </</span><span style='color:#A31515'>listeners</span><span style='color:blue'>></span>
<span style='color:blue'> </</span><span style='color:#A31515'>source</span><span style='color:blue'>></span>
<span style='color:blue'> </</span><span style='color:#A31515'>sources</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>sharedListeners</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>add</span><span style='color:blue'> </span><span style='color:red'>initializeData</span><span style='color:blue'>=</span>"<span style='color:blue'>WcfClient.svclog</span>"<span style='color:blue'> </span><span style='color:red'>type</span><span style='color:blue'>=</span>"<span style='color:blue'>System.Diagnostics.XmlWriterTraceListener</span>"<span style='color:blue'> </span><span style='color:red'>name</span><span style='color:blue'>=</span>"<span style='color:blue'>xml</span>"<span style='color:blue'>/></span>
<span style='color:blue'> </</span><span style='color:#A31515'>sharedListeners</span><span style='color:blue'>></span>
<span style='color:blue'></</span><span style='color:#A31515'>system.diagnostics</span><span style='color:blue'>></span>
</pre>
<p>
The only difference between the new entries in the client-side and server-side configuration files
is the name of the file to which diagnostic messages will be written: WcfClient.svclog and
WcfConsoleHost.svclog respectively.
</p>
<p>
Now we can both re-start WcfConsoleHost and WcfClient and re-produce the error. Having done this we need
to check the logs for any additional information. So we fire-up <strong>Microsoft Service Trace Viewer</strong>,
within the <strong>Microsoft Windows SDK</strong> and open the client-side log file, WcfClient.svclog.
</p>
<p>
In the Activity tab on the far left we immediately notice that one of the activities is highlighted in red.
This is a 'Process action' activity for 'http://tempuri.org/IService1/GetMyCompositeType'. Clicking on the
activity populates the panel in the top-right, which has a 'Throwing an exception' message highlighted in red.
Clicking this text populates the panel in the bottom-right with details of the exception. Studying the exception
doesn't really provide any additional information - it simply re-iterates that <code>An existing connection was
forcibly closed by the remote host</code>. Checking the messages and activities immediately prior to the
failure provides no additional insight.
</p>
<p>
Let's check the server-side log file. After all, if the client is saying that the server terminated the
connection then hopefully the server is aware that it did this and knows why. So we open WcfConsoleHost.svclog
with Microsoft Service Trace Viewer.
</p>
<p>
We again notice in the Activity tab on the far left that one of the activities is highlighted in red. This is
a 'Process action' activity for 'http://tempuri.org/IService1/GetMyCompositeType'. Clicking on the activity
populates the panel in the top-right, which has a 'Throwing an exception' message highlighted in red. This all
sounds worrying familiar so far. Clicking on this text populates the panel in the bottom-right with details
of the exception. But the exception is different to that experience by the client. The exception message is:
</p>
<pre>
There was an error while trying to serialize parameter http://tempuri.org/:GetMyCompositeTypeResult. The InnerException message was <span style="color:Red">'Enum value '0' is invalid for type 'WcfServiceLibrary.Gender'</span> and cannot be serialized. Ensure that the necessary enum values are present and are marked with EnumMemberAttribute attribute if the type has DataContractAttribute attribute.'. Please see InnerException for more details.
</pre>
<p>
Of course. Silly me. The Gender enumeration has values 1 and 2 (for Male and Female respectively) but as
it's uninitialised it'll have the value 0 which simply isn't valid. It's a shame that WCF doesn't propagate
the above exception to the client, especially as I've set includeExceptionDetailInFaults to true in my
server-side configuration file. But at least the message is plain and simple once we switch on server-side
logging.
</p>
<p>
Moral of the story? Always ensure that your enumerations support 0, even if you map that to something
generic like Unknown or Undefined. You'll still have to handle these values at some point, but at least
WCF won't thrown exceptions when attempting to pass them across the wire.
</p>
<h2>See Also</h2>
<ul>
<li><a href="http://ianpicknell.blogspot.com/2009/12/systemdata-serialisation-bug-scenario-2.html">System.Data Serialisation Bug - Scenario 2</a></li>
</ul>Ian Picknellhttp://www.blogger.com/profile/11237307149515983183noreply@blogger.com1tag:blogger.com,1999:blog-7929053599129622940.post-8832004172031608542010-02-23T07:50:00.000+00:002010-02-23T07:50:21.788+00:00Faking a Strong Name<h2>Introduction</h2>
<p>
Once upon a time a strong name was just that - strong. If you'd referenced an assembly with a
strong name within your application you could be sure that <em>that's</em> the assembly you were
going to get a run-time. Well, even that was never true. An
<a href="http://msdn.microsoft.com/en-us/library/7wd6ex19.aspx">assembly re-direct</a>
could cause you to be delivered a different <em>version</em> but it would always have to have the
same name and public key token, and hence must have originated from the same source. They key
thing is that no-one could slip your application a fake assembly and pass it off as the real
thing.
</p>
<p>
Well, it turns out that now they can. You simply place your fake assembly in the GAC
folder within which the real version of the assembly would logically reside. The name of
the <em>folder</em> is used to establish its version and public key token. Don't believe me?
Read on...
</p>
<h2>Scenario</h2>
<p>
The scenario is based upon a situation I encountered a few years ago. <strong>ThirdPartySecurity</strong>
contains a class called DirectoryServices which is essentially a wrapper around
System.DirectoryServices - it allows the caller to easily performs some look-ups against Active
Directory ('who are the members of this group', that kind of thing). <strong>ConsoleApplication</strong> has
a reference to ThirdPartySecurity and simply acts as a test harness. <strong>MockDirectoryServices</strong> is
exactly that - a mocked-up implementation of System.DirectoryServices. It is not referenced by
anything.
</p>
<img style="border-style: none" width="282" height="469" alt="Solution Explorer view of FakingAStrongName.sln" src="http://lh3.ggpht.com/_fkuTkJfqL8Q/S3Xp6xFikdI/AAAAAAAAADQ/4oFwczzYUd8/s800/FakingAStrongName_SolutionExplorer.jpg"" />
<p><a name='more'></a>
He's the source code for the third-party DirectoryServices in its entirety. This is the actual
source code of the assembly which was performing this job on a project I worked on. The code is
<em>not</em> mine. I did <em>not</em> write it. I've left the completely alone (despite the urge to
perform some major re-factoring), with the exception that I changed the namespace to protect the
innocent and have changed the catch block in GetUsersInGroup such that it re-throws an exception
rather than logging and swallowing it.
</p>
<pre>
<span style='color:blue'>using</span> System;
<span style='color:blue'>using</span> System.DirectoryServices;
<span style='color:blue'>namespace</span> ThirdPartySecurity
{
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'><summary></span>
<span style='color:gray'>///</span><span style='color:green'> Summary description for ActiveDirectory.</span>
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'></summary></span>
<span style='color:blue'>public</span> <span style='color:blue'>class</span> <span style='color:#2B91AF'>DirectoryServices</span>
{
<span style='color:blue'>public</span> DirectoryServices()
{
}
<span style='color:blue'> #region</span> public methods
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'><summary></span>
<span style='color:gray'>///</span><span style='color:green'> Takes an Active Directory group name and returns an array of user names that</span>
<span style='color:gray'>///</span><span style='color:green'> belong to that single group.</span>
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'></summary></span>
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'><param name="groupName"></param></span>
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'><returns></returns></span>
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'><remarks></span><span style='color:green'>If there are no groups return a null array. If there are no users in the group, return an empty array.</span><span style='color:gray'></remarks></span>
<span style='color:blue'>public</span> <span style='color:blue'>string</span>[] GetUsersInGroup (<span style='color:blue'>string</span> groupName)
{
<span style='color:green'>//If no Users in the group, return an empty array.</span>
<span style='color:blue'>string</span> [] result = <span style='color:blue'>new</span> <span style='color:blue'>string</span>[0];
<span style='color:blue'>try</span>
{
<span style='color:#2B91AF'>DirectorySearcher</span> ds = <span style='color:blue'>new</span> <span style='color:#2B91AF'>DirectorySearcher</span>();
ds.SearchRoot = <span style='color:blue'>new</span> <span style='color:#2B91AF'>DirectoryEntry</span>(); <span style='color:green'>// start searching from local domain</span>
<span style='color:blue'>if</span> (groupName.IndexOf(<span style='color:#A31515'>"\\"</span>) > 0)
groupName = groupName.Substring(groupName.IndexOf(<span style='color:#A31515'>"\\"</span>)+1); <span style='color:green'>//Take out the Domain </span>
ds.Filter = BuildLDAPFilter(<span style='color:#A31515'>"group"</span>,groupName);
<span style='color:#2B91AF'>SearchResultCollection</span> src = ds.FindAll();
<span style='color:#2B91AF'>DirectoryEntry</span> objGroupEntry;
<span style='color:blue'>int</span> i = 0;
<span style='color:blue'>if</span> (src.Count == 1) <span style='color:green'>//Must match to a single group</span>
{
<span style='color:blue'>foreach</span>(<span style='color:#2B91AF'>SearchResult</span> objResult <span style='color:blue'>in</span> src) <span style='color:green'>//for the 1st and only group</span>
{
objGroupEntry = objResult.GetDirectoryEntry();
<span style='color:blue'>string</span>[] members = <span style='color:blue'>new</span> <span style='color:blue'>string</span>[objGroupEntry.Properties[<span style='color:#A31515'>"member"</span>].Count];
<span style='color:blue'>foreach</span>(<span style='color:blue'>object</span> objMember <span style='color:blue'>in</span> objGroupEntry.Properties[<span style='color:#A31515'>"member"</span>])
{
<span style='color:blue'>string</span> memberDetails = objMember.ToString();
<span style='color:green'>//strip out CN= from start of string and get name up to first comma delimiter</span>
members[i] = (memberDetails.Substring(3,memberDetails.IndexOf(<span style='color:#A31515'>","</span>)-3));
i++;
}
result = members;
}
}
<span style='color:blue'>else</span> <span style='color:green'>//If no groups return a Null Array</span>
{
result = <span style='color:blue'>null</span>;
}
src.Dispose();
ds.Dispose();
}
<span style='color:blue'>catch</span> (System.<span style='color:#2B91AF'>Exception</span> ex)
{
<span style='color:green'>// NOTE:the original version of this catch block logged the exception via the</span>
<span style='color:green'>// Enterprise Library and then swallowed it; we'll simply re-throw it</span>
<span style='color:green'>//Framework.EnterpriseLibrary.Log(String.Format("GetUsersInGroup(\"{0}\")failed (Active Directory is probably unavailable); the following exception hasbeen silently ignored.\n\n{1}", groupName, ex),Framework.Severity.Warning);</span>
<span style='color:blue'>throw</span>;
}
<span style='color:blue'>return</span> result;
}
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'><summary></span>
<span style='color:gray'>///</span><span style='color:green'> Takes a user name in an Active Directory store and returns the display name for that user.</span>
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'></summary></span>
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'><param name="userName"></param></span>
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'><returns></returns></span>
<span style='color:blue'>public</span> <span style='color:blue'>string</span> GetDisplayName (<span style='color:blue'>string</span> userName)
{
<span style='color:blue'>return</span> GetPropertyFromUser(userName, <span style='color:#A31515'>"displayname"</span>);
}
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'><summary></span>
<span style='color:gray'>///</span><span style='color:green'> /// Takes a user name in an Active Directory store and returns the email address for that user.</span>
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'></summary></span>
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'><param name="userName"></param></span>
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'><returns></returns></span>
<span style='color:blue'>public</span> <span style='color:blue'>string</span> GetEmailAddress (<span style='color:blue'>string</span> userName)
{
<span style='color:blue'>return</span> GetPropertyFromUser(userName, <span style='color:#A31515'>"mail"</span>);
}
<span style='color:blue'> #endregion</span>
<span style='color:blue'> #region</span> private methods
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'><summary></span>
<span style='color:gray'>///</span><span style='color:green'> Forms a filter string for the search in LDAP Format</span>
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'></summary></span>
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'><param name="objectCategory"></param></span>
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'><param name="filter"></param></span>
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'><returns></returns></span>
<span style='color:blue'>private</span> <span style='color:blue'>string</span> BuildLDAPFilter(<span style='color:blue'>string</span> objectCategory, <span style='color:blue'>string</span> filter)
{
<span style='color:#2B91AF'>String</span> result;
result = <span style='color:#2B91AF'>String</span>.Format(<span style='color:#A31515'>"(&(objectCategory={0})(name={1}))"</span>, objectCategory, filter);
<span style='color:blue'>return</span> result;
}
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'><summary></span>
<span style='color:gray'>///</span><span style='color:green'> /// Takes a user name in an Active Directory store and returns a property specified by the caller for that user.</span>
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'></summary></span>
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'><param name="userName"></span><span style='color:green'>The user to search for the given </span><span style='color:gray'><b></span><span style='color:green'>propertyName</span><span style='color:gray'></b></param></span>
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'><param name="propertyName"></span><span style='color:green'>The property of the </span><span style='color:gray'><b></span><span style='color:green'>userName</span><span style='color:gray'></b></span><span style='color:green'> to be returned.</span><span style='color:gray'></param></span>
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'><returns></returns></span>
<span style='color:gray'>///</span><span style='color:green'> </span><span style='color:gray'><remarks></span><span style='color:green'>If the user is not found returns null, if the property does not exist return empty string</span><span style='color:gray'></remarks></span>
<span style='color:blue'>private</span> <span style='color:blue'>string</span> GetPropertyFromUser(<span style='color:blue'>string</span> userName, <span style='color:blue'>string</span> propertyName)
{
<span style='color:blue'>string</span> result = <span style='color:blue'>null</span>;
<span style='color:#2B91AF'>DirectorySearcher</span> ds = <span style='color:blue'>new</span> <span style='color:#2B91AF'>DirectorySearcher</span>();
ds.SearchRoot = <span style='color:blue'>new</span> <span style='color:#2B91AF'>DirectoryEntry</span>(); <span style='color:green'>// start searching from local domain</span>
ds.Filter = BuildLDAPFilter(<span style='color:#A31515'>"user"</span>,userName);
<span style='color:#2B91AF'>SearchResultCollection</span> src = ds.FindAll();
<span style='color:green'>//TODO: Reject the search if more than 1 user returned</span>
<span style='color:blue'>if</span> (src.Count == 1)
{
result = <span style='color:blue'>string</span>.Empty;<span style='color:green'> //If the property does not exist return an empty string</span>
<span style='color:blue'>foreach</span> (<span style='color:#2B91AF'>SearchResult</span> srcResult <span style='color:blue'>in</span> src)
result = srcResult.Properties[propertyName][0].ToString();
}
<span style='color:blue'>return</span> result;
}
<span style='color:blue'> #endregion</span>
}
}
</pre>
<h2>Problem</h2>
<p>
The problem I had on this project was that I didn't have access to Active Directory when I
was developing off-line on my laptop. Of course, the DirectoryServices class above could be
modified in many ways to work around this: the most obvious way would be to have it implement
an IDirectoryServices interface which could also be implemented by a new class called
MockDirectoryServices, with some entry in the configuration file defining which implementation
would be used at runtime. But that would rely upon having access to the source code, and I'm
trying to show you a technique which will work even if you only have access to the assembly.
(Hence why I called this assembly <em>ThirdParty</em>Security - to help us imagine that the
source code is unavailable).
</p>
<p>
I can demonstrate the problem via the simple ConsoleApplication which makes use of
ThirdPartySecurity to enumerate the members of a specific group.
</p>
<pre>
<span style='color:blue'>using</span> System;
<span style='color:blue'>namespace</span> ConsoleApplication
{
<span style='color:blue'>class</span> <span style='color:#2B91AF'>Program</span>
{
<span style='color:blue'>static</span> <span style='color:blue'>void</span> Main(<span style='color:blue'>string</span>[]args)
{
<span style='color:blue'>try</span>
{
(<span style='color:blue'>new</span> <span style='color:#2B91AF'>Program</span>()).Run();
}
<span style='color:blue'>catch</span> (System.<span style='color:#2B91AF'>Exception</span> ex)
{
<span style='color:#2B91AF'>Console</span>.WriteLine(ex);
}
<span style='color:green'>// ask our AppDomain what DirectoryServices-related assemblies are loaded</span>
<span style='color:#2B91AF'>Console</span>.WriteLine(<span style='color:#A31515'>"\nDirectoryServices-related assemblies loaded into the current AppDomain are:"</span>);
<span style='color:blue'>foreach</span> (System.Reflection.<span style='color:#2B91AF'>Assembly</span> assembly <span style='color:blue'>in</span> <span style='color:#2B91AF'>AppDomain</span>.CurrentDomain.GetAssemblies())
{
<span style='color:blue'>if</span> (assembly.GetName().Name.Contains(<span style='color:#A31515'>"DirectoryServices"</span>))
{
<span style='color:#2B91AF'>Console</span>.WriteLine(<span style='color:#A31515'>"\t{0} (from {1})"</span>, assembly.FullName,assembly.CodeBase);
}
}
<span style='color:#2B91AF'>Console</span>.WriteLine(<span style='color:#A31515'>"\nPress ENTER to exit"</span>);
<span style='color:#2B91AF'>Console</span>.ReadLine();
}
<span style='color:blue'>void</span> Run()
{
<span style='color:green'>// create an instance of the third-party DirectoryServices wrapper</span>
ThirdPartySecurity.<span style='color:#2B91AF'>DirectoryServices</span> directoryServices = <span style='color:blue'>new</span> ThirdPartySecurity.<span style='color:#2B91AF'>DirectoryServices</span>();
<span style='color:green'>// define which group we'll be seeking members of</span>
<span style='color:blue'>string</span> groupName = <span style='color:#A31515'>"MYDOMAIN\\MyGroup"</span>;
<span style='color:#2B91AF'>Console</span>.WriteLine(<span style='color:#A31515'>"The members of the group \"{0}\"are:"</span>, groupName);
<span style='color:green'>// ask the third-party DirectoryServices wrapper for the members of the specified group</span>
<span style='color:blue'>string</span>[] memberNames = directoryServices.GetUsersInGroup(groupName);
<span style='color:green'>// output the results</span>
<span style='color:blue'>foreach</span> (<span style='color:blue'>string</span> memberName <span style='color:blue'>in</span> memberNames)
{
<span style='color:#2B91AF'>Console</span>.WriteLine(<span style='color:#A31515'>"\t{0}"</span>, memberName);
}
}
}
}
</pre>
<p>
Obviously ConsoleApplication has a reference to ThirdPartySecurity and ThirdPartySecurity
has a reference to System.DirectoryServices. The reason I included code to display assemblies
within the AppDomain containing the text 'DirectoryServices' in their name will become clear
later on.
</p>
<p>
If I run this code on my laptop, which is currently disconnected and has no access to Active
Directory, I get the following output. I've highlighted key items within the output.
</p>
<pre class="console">
The members of the group "MYDOMAIN\MyGroup"are:
System.Runtime.InteropServices.COMException (0x8007054B): <span style="color:Red">The specified domain e
ither does not exist or could not be contacted.</span>
at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
at System.DirectoryServices.DirectoryEntry.Bind()
at System.DirectoryServices.DirectoryEntry.get_AdsObject()
at System.DirectoryServices.PropertyValueCollection.PopulateList()
at System.DirectoryServices.PropertyValueCollection..ctor(DirectoryEntry entr
y, String propertyName)
at System.DirectoryServices.PropertyCollection.get_Item(String propertyName)
at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
at System.DirectoryServices.DirectoryEntry.Bind()
at System.DirectoryServices.DirectoryEntry.get_AdsObject()
at System.DirectoryServices.DirectorySearcher.FindAll(Boolean findMoreThanOne
)
at System.DirectoryServices.DirectorySearcher.FindAll()
at ThirdPartySecurity.DirectoryServices.GetUsersInGroup(String groupName) in
C:\Users\Ian.Picknell\Documents\Blog\Strong Names\Faking a Strong Name\ThirdPart
ySecurity\DirectoryServices.cs:line 72
at ConsoleApplication.Program.Run() in C:\Users\Ian.Picknell\Documents\Blog\S
trong Names\Faking a Strong Name\ConsoleApplication\Program.cs:line 42
at ConsoleApplication.Program.Main(String[] args) in C:\Users\Ian.Picknell\Do
cuments\Blog\Strong Names\Faking a Strong Name\ConsoleApplication\Program.cs:lin
e 11
DirectoryServices-related assemblies loaded into the current AppDomain are:
System.DirectoryServices, <span style="color:Red">Version=2.0.0.0</span>, Culture=neutral, <span style="color:Red">PublicKeyTok
en=b03f5f7f11d50a3a</span> (from file:///C:/Windows/assembly/GAC_MSIL/System.DirectoryS
ervices/<span style="color:Red">2.0.0.0__b03f5f7f11d50a3a</span>/System.DirectoryServices.dll)
Press ENTER to exit
</pre>
<h2>Solution</h2>
<p>
The solution is to use a mock implementation of System.DirectoryServices and then
persuade ThirdPartySecurity to use that instead - despite the fact that it holds a reference
to the strong-named <em>real</em> System.DirectoryServices. I highlighted the version number and public
key tokens in the output above because they'll change in a moment.
</p>
<p>
Let's take a look at the mocked-up System.DirectoryServices. You'll note that I've had to
make sure that the methods exist on the same classes that they would in the real
System.DirectoryServices. For example, when ThirdPartySecurity calls Dispose on the real
DirectorySearcher it's actually calling the Dispose method on System.ComponentModel.Component
because that's what the real DirectorySearcher inherits from. So our fake version does too.
By contrast, the real PropertyCollection has no base class - it handles all the collection
logic itself; so our mock version needs to ensure that properties like Count are available
directly on the PropertyCollection class and are not simply inherited from the base Dictionary.
</p>
<pre>
<span style='color:blue'>namespace</span> System.DirectoryServices
{
<span style='color:blue'>public</span> <span style='color:blue'>class</span> <span style='color:#2B91AF'>DirectorySearcher</span> : System.ComponentModel.<span style='color:#2B91AF'>Component</span>
{
<span style='color:blue'>public</span> <span style='color:#2B91AF'>DirectoryEntry</span> SearchRoot { <span style='color:blue'>get</span>; <span style='color:blue'>set</span>; }
<span style='color:blue'>public</span> <span style='color:blue'>string</span> Filter { <span style='color:blue'>get</span>; <span style='color:blue'>set</span>; }
<span style='color:blue'>public</span> <span style='color:#2B91AF'>SearchResultCollection</span> FindAll()
{
<span style='color:#2B91AF'>SearchResultCollection</span> searchResultCollection = <span style='color:blue'>new</span> <span style='color:#2B91AF'>SearchResultCollection</span>();
<span style='color:#2B91AF'>SearchResult</span> searchResult = <span style='color:blue'>new</span> <span style='color:#2B91AF'>SearchResult</span>();
searchResultCollection.Add(searchResult);
<span style='color:blue'>return</span> searchResultCollection;
}
}
<span style='color:blue'>public</span> <span style='color:blue'>class</span> <span style='color:#2B91AF'>DirectoryEntry</span>
{
<span style='color:blue'>private</span> <span style='color:#2B91AF'>PropertyCollection</span> _properties;
<span style='color:blue'>public</span> <span style='color:#2B91AF'>PropertyCollection</span> Properties
{
<span style='color:blue'>get</span> { <span style='color:blue'>return</span> _properties; }
<span style='color:blue'>set</span> { _properties = <span style='color:blue'>value</span>; }
}
}
<span style='color:blue'>public</span> <span style='color:blue'>class</span> <span style='color:#2B91AF'>SearchResult</span>
{
<span style='color:blue'>public</span> <span style='color:#2B91AF'>DirectoryEntry</span> GetDirectoryEntry()
{
<span style='color:#2B91AF'>DirectoryEntry</span> directoryEntry = <span style='color:blue'>new</span> <span style='color:#2B91AF'>DirectoryEntry</span>();
directoryEntry.Properties = <span style='color:blue'>new</span> <span style='color:#2B91AF'>PropertyCollection</span>();
<span style='color:#2B91AF'>PropertyValueCollection</span> members = <span style='color:blue'>new</span> <span style='color:#2B91AF'>PropertyValueCollection</span>();
((System.Collections.<span style='color:#2B91AF'>IList</span>)members).Add(<span style='color:#A31515'>@"CN=MYDOMAIN\ian.picknell,otherstuff"</span>);
((System.Collections.<span style='color:#2B91AF'>IList</span>)members).Add(<span style='color:#A31515'>@"CN=MYDOMAIN\jane.armstrong,otherstuff"</span>);
directoryEntry.Properties[<span style='color:#A31515'>"member"</span>] = members;
<span style='color:blue'>return</span> directoryEntry;
}
}
<span style='color:blue'>public</span> <span style='color:blue'>class</span> <span style='color:#2B91AF'>SearchResultCollection</span> : System.Collections.<span style='color:#2B91AF'>ArrayList</span>, <span style='color:#2B91AF'>IDisposable</span>
{
<span style='color:blue'>public</span> <span style='color:blue'>void</span> Dispose()
{
}
}
<span style='color:blue'>public</span> <span style='color:blue'>class</span> <span style='color:#2B91AF'>PropertyCollection</span> : System.Collections.Generic.<span style='color:#2B91AF'>Dictionary</span><<span style='color:blue'>string</span>, <span style='color:#2B91AF'>PropertyValueCollection</span>>
{
<span style='color:blue'>new</span> <span style='color:blue'>public</span> <span style='color:blue'>int</span> Count
{
<span style='color:blue'>get</span> { <span style='color:blue'>return</span> <span style='color:blue'>base</span>.Count; }
}
<span style='color:blue'>new</span> <span style='color:blue'>public</span> <span style='color:#2B91AF'>PropertyValueCollection <span style='color:blue'>this</span>[<span style='color:blue'>string</span> key]</span>
{
<span style='color:blue'>get</span> { <span style='color:blue'>return</span> <span style='color:blue'>base</span>[key]; }
<span style='color:blue'>set</span> { <span style='color:blue'>base</span>[key] = <span style='color:blue'>value</span>; }
}
}
<span style='color:blue'>public</span> <span style='color:blue'>class</span> <span style='color:#2B91AF'>PropertyValueCollection</span> : System.Collections.<span style='color:#2B91AF'>CollectionBase</span>
{
}
}
</pre>
<p>
We need to make sure this is compiled to an assembly named System.DirectoryServices
via the Assembly Name property on the Application tab of Project Properties. We also
need to make sure that it has a strong name - I just assigned it a new key pair via the
Signing tab of Project Properties.
</p>
<p>
Okay, so how to be persuade ThirdPartySecurity to use this mock implementation of
System.DirectoryServices rather than the real thing?
</p>
<h2>Magic</h2>
<p>
The first thing we need to do is add a configuration file for ConsoleApplication
to redirect requests for System.DirectoryServices from version 2.0.0.0 to some other
version - I'll choose 2.1.0.0.
</p>
<pre>
<span style='color:blue'><?</span><span style='color:#A31515'>xml</span><span style='color:blue'> </span><span style='color:red'>version</span><span style='color:blue'>=</span><span style='font-size:10.0pt;font-family:"Courier New"'>"<span style='color:blue'>1.0</span>"<span style='color:blue'> </span><span style='color:red'>encoding</span><span style='color:blue'>=</span>"<span style='color:blue'>utf-8</span>"<span style='color:blue'> ?></span></span>
<span style='color:blue'><</span><span style='color:#A31515'>configuration</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>runtime</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>assemblyBinding</span><span style='color:blue'> </span><span style='color:red'>xmlns</span><span style='color:blue'>=</span><span style='font-size:10.0pt;font-family:"Courier New"'>"<span style='color:blue'>urn:schemas-microsoft-com:asm.v1</span>"<span style='color:blue'>></span></span>
<span style='color:blue'> <</span><span style='color:#A31515'>dependentAssembly</span><span style='color:blue'>></span>
<span style='color:blue'> <</span><span style='color:#A31515'>assemblyIdentity</span><span style='color:blue'> </span><span style='color:red'>name</span><span style='color:blue'>=</span><span style='font-size:10.0pt;font-family:"Courier New"'>"<span style='color:blue'>System.DirectoryServices</span>"</span>
<span style='color:blue'> </span><span style='color:red'>publicKeyToken</span><span style='color:blue'>=</span><span style='font-size:10.0pt;font-family:"Courier New"'>"<span style='color:blue'>b03f5f7f11d50a3a</span>"</span>
<span style='color:blue'> </span><span style='color:red'>culture</span><span style='color:blue'>=</span><span style='font-size:10.0pt;font-family:"Courier New"'>"<span style='color:blue'>neutral</span>"<span style='color:blue'> /></span></span>
<span style='color:blue'> <</span><span style='color:#A31515'>bindingRedirect</span><span style='color:blue'> </span><span style='color:red'>oldVersion</span><span style='color:blue'>=</span><span style='font-size:10.0pt;font-family:"Courier New"'>"<span style='color:blue'>2.0.0.0</span>"</span>
<span style='color:blue'> </span><span style='color:red'>newVersion</span><span style='color:blue'>=</span><span style='font-size:10.0pt;font-family:"Courier New"'>"<span style='color:blue'>2.1.0.0</span>"<span style='color:blue'> /></span></span>
<span style='color:blue'> </</span><span style='color:#A31515'>dependentAssembly</span><span style='color:blue'>></span>
<span style='color:blue'> </</span><span style='color:#A31515'>assemblyBinding</span><span style='color:blue'>></span>
<span style='color:blue'> </</span><span style='color:#A31515'>runtime</span><span style='color:blue'>></span>
<span style='color:blue'></</span><span style='color:#A31515'>configuration</span><span style='color:blue'>></span>
</pre>
<p>
This just uses a standard assembly re-direction. As I mentioned earlier, such re-directions only
allow the version number to be changed - you can't change the assembly name or public key token.
</p>
<p>
To persuade .NET that our MockDirectoryServices is actually the real System.DirectoryServices
simply create the appropriate folder in the GAC and drop it in there:
</p>
<pre class="console">
> MD %SystemRoot%\assembly\GAC_MSIL\System.DirectoryServices\2.1.0.0__b03f5f7f11d50a3a
> COPY System.DirectoryServices.dll %SystemRoot%\assembly\GAC_MSIL\System.DirectoryServices\2.1.0.0__b03f5f7f11d50a3a
</pre>
<p>
So we're copying <em>our</em> System.DirectoryServices (actually generated from the MockDirectoryServices
project), which has a version number of 1.0.0.0 (because we didn't change it) and some random
public key token and dropping it in the folder which <em>should</em> contain version
2.1.0.0 of System.DirectoryServices, signed with Microsoft's key pair (which has a public key
token of b03f5f7f11d50a3a). In other words, we're lying.
</p>
<p>
When we run ConsoleApplication this time, we get a much more healthy result - even with
no Active Directory in sight. Again, I've highlighted some interesting elements in the output.
</p>
<pre class="console">
The members of the group "MYDOMAIN\MyGroup"are:
MYDOMAIN\ian.picknell
MYDOMAIN\jane.armstrong
DirectoryServices-related assemblies loaded into the current AppDomain are:
System.DirectoryServices, <span style="color:Red">Version=1.0.0.0</span>, Culture=neutral, <span style="color:Red">PublicKeyTok
en=7c6ae5d5059e81d9</span> (from file:///C:/Windows/assembly/GAC_MSIL/System.DirectoryS
ervices/<span style="color:Red">2.1.0.0__b03f5f7f11d50a3a</span>/System.DirectoryServices.dll)
Press ENTER to exit
</pre>
<p>
You'll note that our AppDomain <em>knows</em> that it's dealing with version 1.0.0.0
of an assembly which has a public key token of eeab6744580125d7, which is clearly
<em>not</em> the version ThirdPartySecurity references. But it doesn't seem to care. It
was loaded from a folder within the GAC where version 2.1.0.0 with a public key
token of b03f5f7f11d50a3a was <em>expected</em> to exist, and that seems good enough for it.
</p>
<p>
So, if you ever find yourself needing to provide a mock implementation of an assembly
which has a strong name, now you know how.
</p>
<h2>See Also</h2>
<ul>
<li><a href="http://ianpicknell.blogspot.com/2010/02/tampering-with-strong-named-assembly.html">Tampering with a Strong-Named Assembly</a></li>
<li><a href="http://ianpicknell.blogspot.com/2010/02/where-is-global-assembly-cache.html">Where is the Global Assembly Cache?</a></li>
<li><a href="http://ianpicknell.blogspot.com/2010/02/evading-strong-name-integrity-check.html">Evading the Strong Name Integrity Check</a></li>
</ul>Ian Picknellhttp://www.blogger.com/profile/11237307149515983183noreply@blogger.com0