Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WCF: (MTOM) is there any way to change the scheme used in xop:Content reference uris generated by WCF?

WCF uses http://tempuri/1/number for Content-ID uri references when handling streamed MTOM requests.

Is there any way how to force WCF to use a different Content-ID references for the xop:Include?

Background of the problem:

I am building a .NET client for MTOM enabled jax ws java web service that handles streamed large data uploads. I have hand crafted the service and data contacts (the WSDL generated contracts were not correct and did not allow streaming).

The problem is that the web service (jax ws) does not receive the request body containing the data.

It receives the data that is transferred in headers.

We have built a java client for the ws - this one works.

I have captured and compared the HTTP traffic when issuing requests from java and wcf, and the only difference is in how Content-ID reference is generated when posting the multipart data:

  • WCF uses http://tempuri/1/... Content-ID references which yield in encoded value, like href="cid:http%3A%2F%2Ftempuri.org%2F1%2F634019957020047928"

  • Java client uses "email-style" uris, like href="cid:[email protected]"

These yield in the following xop-includes (Data is the only element in the soap body) (XOP includes specification)


//WCF:
<Data>
   <xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="cid:http%3A%2F%2Ftempuri.org%2F1%2F634019957020047928" />
</Data>

//JAVA:
<Data>
    <xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="cid:[email protected]"/>
</Data>

later on, in the multipart data, the content is referred to by unencoded Content-ID:

--uuid:7e166bb7-042f-4ba3-b6ef-98fbbc21244b+id=1
Content-ID: <http://tempuri.org/1/634019957020047928>
Content-Transfer-Encoding: binary
Content-Type: application/octet-stream

I guess there may be a bug in the jax web service framework and it is not recognizing WCF-generated+urlencoded Content-ID uri references.

Is there any way how to force WCF to use a different Content-ID references for the xop:Include?


EDIT: I have found the XmlMtomWriter which has the GenerateUriForMimePart method, this is used to generate Content-IDs.

public static string GenerateUriForMimePart(int index)
{
    return string.Format(CultureInfo.InvariantCulture, 
"http://tempuri.org/{0}/{1}", new object[] { index, DateTime.Now.Ticks });
}

It does not seem that the ID generation is in any way overridable.

A similar issue is described here, the answer provided does not help: http://social.msdn.microsoft.com/Forums/en/wcf/thread/f90affbd-f431-4602-a81d-cc66c049e351

like image 669
Marek Avatar asked Feb 17 '10 11:02

Marek


2 Answers

Asnwering to myself after long investigation: Not possible without reimplementing the whole XmlMtomWriter and other related layers and concerns in WCF - almost everything involved in the mtom implementation is internal.

like image 125
Marek Avatar answered Sep 22 '22 14:09

Marek


I know it is an old question. But I'm faced the same problem two days ago.

I found a way which works BUT it is a VERY VERY dirty hack (I know that. I thought about not publishing it here but perhaps it would help somebody.) Hopefully you will not blame me for that.

The ContentId is formatted with the use of CultureInfo.InvariantCulture. I didn't find an official way for replacing it with a custom CultureInfo. But with the help of reflection I got it running. The following implementation is only for .Net 4.0.

public class NoTempUriInvariantCultureInfo : CultureInfo, ICustomFormatter
{
   private static CultureInfo originalCulture;
   private static object originalCultureLock;
   private static int enableCounter;

   private NoTempUriInvariantCultureInfo(CultureInfo invariantCulture)
      : base(invariantCulture.Name)
   {
      originalCulture = invariantCulture;
   }

   public static void Enable()
   {
      if(originalCultureLock == null)
         originalCultureLock = new object();

      lock (originalCultureLock)
      {
         if (enableCounter == 0)
         {
            var mInvCultField = typeof (CultureInfo).GetField("s_InvariantCultureInfo", BindingFlags.NonPublic | BindingFlags.Static);
            mInvCultField.SetValue(null, new NoTempUriInvariantCultureInfo(CultureInfo.InvariantCulture));
         }
         enableCounter++;
      }
   }

   public static void Disable()
   {
      lock (originalCulture)
      {
         if (enableCounter == 0)
            return;

         enableCounter--;
         if (enableCounter == 0)
         {
            var mInvCultField = typeof (CultureInfo).GetField("s_InvariantCultureInfo", BindingFlags.NonPublic | BindingFlags.Static);
            mInvCultField.SetValue(null, NoTempUriInvariantCultureInfo.originalCulture);
         }
      }
   }

   public override object GetFormat(Type formatType)
   {
      var result = originalCulture.GetFormat(formatType);
      return result ?? this;
   }

   public string Format(string format, object arg, IFormatProvider formatProvider)
   {
      if (format == null)
         return System.Text.RegularExpressions.Regex.Replace(arg.ToString().Replace("http%3A%2F%2Ftempuri.org%2F1%2F", ""), "http[:][/][/]tempuri[.]org[/][0-9]+[/]*", "");
      return String.Format("{0:" + format + "}", arg);
   }
}

I enable my own "InvariantCulture" only before a WCF call.

NoTempUriInvariantCultureInfo.Enable();
try
{
    // make your call
}
finally
{
    NoTempUriInvariantCultureInfo.Disable();
}

CultureInfo.InvariantCulture is a global state object. Enabling my own InvariantCulture affects every other thread. Again, it is a dirty hack. But it works.

like image 28
Michael Avatar answered Sep 22 '22 14:09

Michael