Carl’s Geek Notes

August 27, 2008

XmlNamespaceManager and overridden default XML namespace

Filed under: C#/.NET, Computers, Programming, Web Services, XML — Carl @ 12:22 pm

Issue: My XmlDocument.SelectNodes() or SelectSingleNode() wasn’t working correctly with the incoming XML. It was always returning null even when the element was specified.

Problem: If the XML has an overridden default XML namespace (i.e., the root node has an attribute xmlns=”urn:MyNamespace” in it), SelectNode is unable to properly resolve it.

Solution: Use an XmlNamespaceManager to give a prefix to the default namespace, like so:

string MyXML = "<?xml version=\"1.0\"?><MyElement xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"urn:MyNamespace\"><MySubElement>3</MySubElement></MyElement>";

// Create and load the XML document
XmlDocument MyDoc = new XmlDocument();
MyDoc.LoadXml(Response);

// Create a namespace manager with the XML document's name table
XmlNamespaceManager MyNamespaces = new XmlNamespaceManager(MyDoc.NameTable);

// Add namespaces for all prefixed xmlns declarations
MyNamespaces.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
MyNamespaces.AddNamespace("xsd", "http://www.w3.org/2001/XMLSchema");

// Add the default namespace with a custom prefix
MyNamespaces.AddNamespace("MyNS", "urn:MyNamespace");

// Use the namespace manager to select the node
// Make sure that each element in the default namespace is prefixed with your custom prefix
// It is not sufficient to do MyNS:MyElement/MySubElement, both must be qualified!
XmlNode MyNode = MyDoc.SelectSingleNode("MyNS:MyElement/MyNS:MySubElement", MyNamespaces);

September 11, 2007

Error CS0012: Unable to generate a temporary class

Filed under: C#/.NET, Computers, Programming, Web Services, XML — Carl @ 8:56 am

I received an error when serializing a class to XML:

Unable to generate a temporary class (result=1).
error CS0012: The type 'MyType' is defined in an assembly that is not referenced. You must add a reference to assembly 'MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=123412341234'.

Searching the web seemed to indicate that marking fields or properties of the class as non-serialized or adding a reference to the assembly containing the type would correct the problem. The odd thing is, MyType wasn’t part of the class being serialized; there were no fields or properties of MyType within the class!

Fortunately, I knew that it was working before I made some changes this morning. I had added the following:

 
public static implicit operator MyType(MySerializedClass parameterValue) {...}

to the class. When I changed it to an explicit operator, the error went away.  I'm not sure why this behavior is so, but it solved my problem.

August 27, 2007

Changing web service URL without updating the web reference

Filed under: C#/.NET, Computers, Programming, Web Services — Carl @ 7:57 am

We all know the problem.  We work and work at it, and there seems to be no explanation or cause for it.  We develop test cases that all seem to work, and then it never works when we do it for real.  We spend hours upon hours on it, testing, scouring Google for answers, writing and rewriting code, and waking up at odd hours with a completely off-the-wall proposed solution to it.

Then we figure it out, and it’s so blazingly simple that we wonder how we ever consider ourselves geeks.

Here’s mine, for the past couple of weeks.

I had a bizarre Web Services problem.  The service uses a strongly-typed parameter:

DoWebServiceStuff(MyType theParameter)

The web service is published to other interested parties for two-way communication; i.e., they build a component to consume our web service and we build a component to consume theirs.  We were having difficulty debugging a new development effort.  The symptoms of the problem were:

  • The parameter never deserialized at the other end.  Every time it was checked, it was null.
  • The XML string for the parameter deserialized to the object with no problems, if we took the string out of the logs and wrote code to deserialize from it.

Our methodology is that the codebase has a reference set to the instance of the web service that we sent.  To send to another system (regardless of whether it was one we developed or not), we use the same reference and set the URL to be the URL of the end system.  If I built a custom reference to the other system, it would work, but not when I used my reference to send to their URL.

Finally, I figured out the problem.  Their web service was built as DoWebServiceStuff(MyType TheParameter).  Notice the difference?  The name of the parameter was different, therefore its SOAP wrapper was different.  When I created a web reference to their service, .NET abstracts the SOAP process away from me so that it’s handled properly, but when I use my web reference to send it, the parameter name is still different.

In programming, we’re so used to thinking that the name of the parameter only matters locally, and focused on what is contained within that parameter.  So, if you’re ever seeing a problem where a web service parameter won’t deserialize, check your parameter spelling and casing between systems.

May 29, 2007

WSE Authentication Error when using a proxy or web farm

Filed under: C#/.NET, Computers, Programming, Web Services, WSE — Carl @ 8:55 am

When going into a production environment, I discovered that WSE was throwing the following exception:

System.Web.Services.Protocols.SoapException: SOAP-Fault code:http://schemas.xmlsoap.org/ws/2004/08/addressing:DestinationUnreachable Message: Microsoft.Web.Services3.Addressing.AddressingFault: Destination Unreachable ---> System.Exception: WSE846: The <wsa:To> header must match the actor URI value of the web service. The actor URI value can be explicitly specified using SoapActorAttribute on the ASMX class. In the absence of the attribute, the actor URI is assumed to be equal to the HTTP Request Url of the incoming message. The <To> header received contained http://MySite/MyService.asmx while the receiver is expecting http://MySite/MyService.asmx?StuffAddedByProxyToQueryString. --- End of inner exception stack trace ---

The problem was that the firewall functions as a proxy server and adds some stuff to the query string of the request, making them not match.  The packets were signed by the client with the base URL and the server received the URL that had been munged by the proxy server.

The solution was very simple, I added the following attribute:

[Microsoft.Web.Services3.Messaging.SoapActor("*")]

to my service and it disabled that check.  According to Zach Bonham, the same result should be expected when using a load-balanced web farm.  He also observes “but it seems like I’ve lost some of the functionality of why I chose WSE in the first place,” and I definitely agree with that.

May 16, 2007

Error using svcutil.exe to generate proxy classes from WCF service

Filed under: C#/.NET, Computers, Programming, WCF, Web Services — Carl @ 2:17 pm

While using svcutil.exe to generate proxy classes for a WCF client application, I got the following error:

C:\>svcutil.exe http://localhost:2338/MyService/Service.svc?wsdl
Microsoft (R) Service Model Metadata Tool
[Microsoft (R) Windows (R) Communication Foundation, Version 3.0.4506.30]
Copyright (c) Microsoft Corporation. All rights reserved.
Attempting to download metadata from 'http://localhost:2338/MyService/Service.svc?wsdl' using WS-Metadata Exchange or DISCO.
Error: Cannot import wsdl:portType
Detail: An exception was thrown while running a WSDL import extension: System.ServiceModel.Description.XmlSerializerMessageContractImporter
Error: Object reference not set to an instance of an object.
XPath to Error Source: //wsdl:definitions[@targetNamespace='http://my.services']/wsdl:portType[@name='IMyService']
Error: Cannot import wsdl:binding
Detail: There was an error importing a wsdl:portType that the wsdl:binding is dependent on.
XPath to wsdl:portType: //wsdl:definitions[@targetNamespace='http://my.services']/wsdl:portType[@name='IMyService']
XPath to Error Source: //wsdl:definitions[@targetNamespace='http://my.services']/wsdl:binding[@name='WSHttpBinding_IMyService']
Error: Cannot import wsdl:port
Detail: There was an error importing a wsdl:binding that the wsdl:port is dependent on.
XPath to wsdl:binding: //wsdl:definitions[@targetNamespace='http://my.services']/wsdl:binding[@name='WSHttpBinding_IMyService]
XPath to Error Source: //wsdl:definitions[@targetNamespace='http://my.services']/wsdl:service[@name='MyService']/wsdl:port[@name='WSHttpBinding_IMyService']

Generating files...
C:\MyService.cs

I found this posting (See Hao Xu's post towards the bottom) suggesting that it is the difference between XmlSerializer and XmlFormatter.  Once I called svcutil.exe with the /serializer:XmlSerializer parameter, it generated my classes with no trouble.

March 21, 2007

More WSE Issues: Timestamp Validation and Clock Skew

Filed under: C#/.NET, Computers, Programming, Web Services, Windows Administration, WSE — Carl @ 7:34 am

My newly-deployed WSE 3.0-enabled site wasn’t working properly.  Invoking the service caused the following exception:

"WSE910: An error happened during the processing of a response message, and you can find the error in the inner exception.  You can also find the response message in the Response property."

Checking the InnerException, the real cause was this:

"WSE066: Timestamp is expired. This indicates a stale message but may also be caused by lack of synchronization between sender and receiver clocks. Make sure the clocks are synchronized or use the timeToleranceInSeconds element in the microsoft.web.services3 configuration section to adjust tolerance for lack of clock synchronization."

Step 1 was to make sure that the clock on the server was properly synchronized with an accurate NTP server.  I followed the steps in KB816042 to configure Windows Time service to use an external time source.  When that confirmed that the server time was accurate, I suspected that it had something to do with time zones, since the consumers of the service are in a different time zone.  Turning on WSE tracing let me look at the timestamp:

<wsu:Timestamp wsu:Id="Timestamp-4bd14e9e-37fa-4658-b1cb-43256498cb60">
  <wsu:Created>2007-03-20T17:34:31Z</wsu:Created>
  <wsu:Expires>2007-03-20T17:49:31Z</wsu:Expires>
</wsu:Timestamp>

confirming that it uses UTC for the timestamp.  Some more checking led me to an MSDN article on WS-I BSP Interoperability Guidance (Web Services-Interoperability Basic Security Profile, FYI) that discusses message age and clock skew.  I found these two statements:

“The <defaultTtlInSeconds> element specifies the number of seconds after creation that every outgoing SOAP message is valid. The default value is 5 minutes, but the WS-I SCM architecture document recommends that this is set to 900 seconds (15 minutes).”

“The <timeToleranceInSeconds> setting corresponds to the acceptable time difference (clock skew) between the sender and the recipient of a message. By default, it is configured to 300 seconds or 5 minutes. However, you can change this value in the service’s Web.config file if you require a different value. The WS-I SCM Architecture document recommends that this is set to 900 seconds (15 minutes).”

Setting the defaultTtlInSeconds to 900 on both server and client did the trick!  You can add these elements manually in web.config to the configuration/microsoft.web.services3/security section:

<defaultTtlInSeconds value="900" />
<timeToleranceInSeconds value="900" />

March 16, 2007

A simple WSE 3.0 web service to authorize by username and password

Filed under: C#/.NET, Computers, Programming, Web Services, WSE — Carl @ 8:01 am

I’ve been developing a web service that authorizes based on the Web Services Enhancements (WSE) 3.0 specification.  It’s been a fairly cumbersome exercise, mainly due to poor documentation on the what and why of the process, so I’m writing down the process that I went through to create it.

  1. Install Microsoft’s WSE 3.0 and add references to Microsoft.Web.Services3 to your projects.  (I’ve included the using directives in the full code, but not in the listings below, so download the samples and look through them if you’re interested in which namespaces to include.)
  2. Develop your custom username/password validation routine by deriving it from UsernameTokenManager and overriding the AuthenticateToken method.  The generic one that I did was this:
    public class SampleUsernameTokenManager : UsernameTokenManager
    {
      protected override string AuthenticateToken(UsernameToken token)
      {
        bool Valid = (("MyUsername" == token.Username) && ("MyPassword" == token.Password));
        if (Valid)
        {
          return token.Password;
        }
        else
        {
          throw new UnauthorizedAccessException("User validation failed");
        }
      }
    }
    You would, of course, use whatever routine you needed to determine the validity of the UsernameToken.  The major consideration here is that you have to return the password if the credentials are valid.
  3. Change your web.config file by doing the following:
    • Make sure that configuration/configSections contains the following:
       <section name="microsoft.web.services3" type="Microsoft.Web.Services3.Configuration.WebServicesConfiguration, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    • Make sure that configuration/webServices contains the following:
       <soapExtensionImporterTypes>
        <add type="Microsoft.Web.Services3.Description.WseExtensionImporter, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      </soapExtensionImporterTypes>
      <soapServerProtocolFactory type="Microsoft.Web.Services3.WseProtocolFactory, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    • Make sure that configuration/microsoft.web.services3 contains the following:
       <security>
        <securityTokenManager>
          <add type="SampleWSEService.SampleUsernameTokenManager, SampleWSEService" namespace="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" localName="UsernameToken" />
        </securityTokenManager>
      </security>
      <policy fileName="wse3policyCache.config"/>
      The securityTokenManager element is what binds the specific security method to the class that determines the validity of the token.  The type attribute needs to refer to the fully-qualified name of the class that derives from UsernameTokenManager in the first part and have the name of the DLL containing it in the second.  The important thing to note here is that the namespace and localName attributes must be exactly as shown above!
  4. Change your wse3policyCache.config file to look like this (note that this is the minimum policy file for my example; you may want to combine other authorization methods in this file as well):<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy">
      <extensions>
        <extension name="usernameOverTransportSecurity" type="Microsoft.Web.Services3.Design.UsernameOverTransportAssertion, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
        <extension name="requireActionHeader" type="Microsoft.Web.Services3.Design.RequireActionHeaderAssertion, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
        </extensions>
        <policy name="SamplePolicy">
        <authorization>
          <allow user="*"/>
        </authorization>
        <usernameOverTransportSecurity />
        <requireActionHeader />
      </policy>
    </policies>

    Some important considerations here are:

    • Remember the name that you give the policy; you’ll use that to bind your web service to this policy.
    • The authorization section includes a default deny.  We add an allow for all users (“*”) rather than for all authenticated users (“?”) because we’re sending text usernames; if we were doing Windows-based authentication we’d restrict it to authenticated users only.
    • Alternately, you could list all valid usernames in the authorization section like this:
       <allow user="Fred"/>
      <allow user="Dolores"/>
    • Basically the authorization section allows listed users to be processed by the appropriate security token manager; it doesn’t imply that they are allowed access–only that they are allowed to reach the access mechanism you’ve defined.
  5. Bind the policy to your class that derives from WebService with the policy attribute right before the class declaration, like this:
    [Policy("SamplePolicy")]
    public class Sample : System.Web.Services.WebService
  6. To enable the client to use this, add a web reference to your web service and use the following code:
    // Make sure to connect to the Wse proxy for the service
    MySampleWSEService.SampleWse MyWebService = new MySampleWSEService.SampleWse();// Create the username token
    string Username = "MyUsername";
    string Password = "MyPassword";
    UsernameToken MyToken = new UsernameToken(Username, Password, PasswordOption.SendPlainText);
    MyWebService.SetClientCredential(MyToken);// Create the policy and specify username over transport authentication
    Policy MyPolicy = new Policy();
    MyPolicy.Assertions.Add(new UsernameOverTransportAssertion());// Apply the policy to the exchange
    MyWebService.SetPolicy(MyPolicy);
    string Response = MyWebService.HelloWorld();

You can download my sample solution that includes code for both the web service and the client.

March 15, 2007

WSE 3.0 Hell: “SOAP header security was not understood.”

Filed under: C#/.NET, Computers, Programming, Web Services, WSE — Carl @ 10:29 am

It’s been an interesting couple of days with WSE 3.0.  I continually got the error message:

System.Web.Services.Protocols.SoapHeaderException: SOAP header Security was not understood.
   at System.Web.Services.Protocols.SoapHeaderHandling.SetHeaderMembers(SoapHeaderCollection headers, Object target, SoapHeaderMapping[] mappings, SoapHeaderDirection direction, Boolean client)
  at System.Web.Services.Protocols.SoapServerProtocol.CreateServerInstance()
   at System.Web.Services.Protocols.WebServiceHandler.Invoke()
   at System.Web.Services.Protocols.WebServiceHandler.CoreProcessRequest()" string

when trying to use a username and password in a SOAP username token.  For some reason, the WSE 3.0 configuration manager did not accurately change the web.config file to reflect the soapServerProtocolFactory for WSE 3.0.  I added the following in the configuration/webServices of the web.config file: 

<soapExtensionImporterTypes>
  <add type="Microsoft.Web.Services3.Description.WseExtensionImporter, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</soapExtensionImporterTypes>
<soapServerProtocolFactory type="Microsoft.Web.Services3.WseProtocolFactory, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />

and it worked perfectly.  It looks like the WSE 3.0 configuration tool was adding this to the app.config file instead of the web.config file (and it never adds the soapServerProtocolFactory element to the app.config anyway).

Theme: Silver is the New Black. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.