Home Dashboard Directory Help
Search

Bug with GetQueryStringConverter not being called by WebServiceHost by Sean Hederman


Status: 

Closed
 as Deferred Help for as Deferred


20
0
Sign in
to vote
Type: Bug
ID: 616486
Opened: 10/25/2010 9:17:58 AM
Access Restriction: Public
3
Workaround(s)
view
15
User(s) can reproduce this bug

Description

The WebHttpBehavior.GetQueryStringConverter method is designed so that you can extend the behavior by supplying your own QueryStringConverter. However WebServiceHost completely ignores this behavior and news up QueryStringConverter on it's own, without involving the GetQueryStringConverter method.

It hus, completely bypasses this extensibility making it impossible to change the QueryStringConverter to be changed in any way incompatible with the base class.
Details
Sign in to post a comment.
Posted by mclifton on 12/22/2011 at 3:59 PM
Figured out how to get this to work inside of an ASP.NET WCF web service. Check the workarounds for the solution.
Posted by mclifton on 12/21/2011 at 6:48 PM
Can anyone supply a workaround for a WCF webservice contained inside of an ASP.NET Web Application? I assume it would need to be done in the web.config, but I'm not sure how to accomplish that.
Posted by Microsoft on 6/20/2011 at 1:07 PM
This bug is under consideration to be fixed for the next version of the .NET Framework (the version after .NET 4.0). It will not be fixed for .NET 4.0 or early versions of the framework.

However, there is a general workaround that can be used. A working solution with the workaround is attached. At the top of the comments tab is a "Details" title that include an "Expand" link. In the expanded details is a list of file attachments. The workaround can be found in the QueryStringConverterBug.zip file attached by microsoft.

The workaround involves not using the WebServiceHost. Instead use the base ServiceHost class and explicitly add a WebHttp endpoint using the WebHttpBinding and an endpoint behavior that derives from WebHttpBehavior (you have to derive in order to supply a custom QueryStringConverter).
Posted by Microsoft on 6/20/2011 at 12:28 PM
Please see the attached solution for a general workaround to this issue.

The workaround involves not using the WebServiceHost. Instead use the base ServiceHost class and explicitly add a WebHttp endpoint using the WebHttpBinding and an endpoint behavior that derives from WebHttpBehavior (you have to derive inorder to supply a custom QueryStringConverter).

If you are hosting in IIS, you will need to author a custom ServiceHostFactory to do the same thing that is being done in the console application here.
Posted by Sean Hederman on 6/19/2011 at 12:08 AM
It appears that MS don't bother tracking defects that are marked as fixed. Hopefully this will be in .NET 5.0 when it's released probably sometime in the next couple years. Lesson Learned: never rely on MS to implement web technologies.
Posted by Jimmy Main on 5/26/2011 at 11:25 PM
Hi,

What is happening with this bug.
The second workaround doesn't work, and after implementing a complete QueryStringConverter, and testing it for two days I realise that all the documentation I have been reading is rubbish, and my solution cannot work.

This has resulted in a serious waste of my time.

Please fix this.
Sorry, but this kind of bug is sloppy.

Regards
Craig.
Posted by prenou on 5/24/2011 at 7:09 AM
Hi,
I'm also blocked with this. Solution 1 given by Microsoft is okay but obviously not so pretty, Solution 2 did not work.

@Microsoft: It would really great to get the release date of this bug fix. Could you please at least give us this information so that I can know if it's worth to wait for this or not?

Thanks a lot.
Posted by mr-miles on 5/6/2011 at 4:59 AM
I tried the workaround, it didn't work.

Two things: first of all I think there's a bug - I only got past onOpening if I set WebHttpBinding.ContentTypeMapper = null beforehand.

Having done that, I then get a second exception:

[InvalidOperationException: Operation 'xxx' in contract 'Izzz' has a query variable named 'yyyy' of type 'System.Nullable`1[System.Boolean]', but type 'System.Nullable`1[System.Boolean]' is not convertible by 'QueryStringConverter'. Variables for UriTemplate query values must have types that can be converted by 'QueryStringConverter'.]
System.ServiceModel.Dispatcher.UriTemplateClientFormatter.Populate(Dictionary`2& pathMapping, Dictionary`2& queryMapping, Int32& totalNumUTVars, UriTemplate& uriTemplate, OperationDescription operationDescription, QueryStringConverter qsc, String contractName) +1530
System.ServiceModel.Dispatcher.UriTemplateDispatchFormatter..ctor(OperationDescription operationDescription, IDispatchMessageFormatter inner, QueryStringConverter qsc, String contractName, Uri baseAddress) +128
System.ServiceModel.Description.WebHttpBehavior.GetRequestDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint) +184
System.ServiceModel.Description.WebHttpBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) +1677
System.ServiceModel.Description.DispatcherBuilder.InitializeServiceHost(ServiceDescription description, ServiceHostBase serviceHost) +4114
System.ServiceModel.ServiceHostBase.InitializeRuntime() +82
System.ServiceModel.ServiceHostBase.OnOpen(TimeSpan timeout) +64
System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout) +789
System.ServiceModel.HostingManager.ActivateService(String normalizedVirtualPath) +287
System.ServiceModel.HostingManager.EnsureServiceAvailable(String normalizedVirtualPath) +1132

[ServiceActivationException: The service '/' cannot be activated due to an exception during compilation. The exception message is: Operation 'xxx' in contract 'Izzz' has a query variable named 'yyy' of type 'System.Nullable`1[System.Boolean]', but type 'System.Nullable`1[System.Boolean]' is not convertible by 'QueryStringConverter'. Variables for UriTemplate query values must have types that can be converted by 'QueryStringConverter'..]
System.Runtime.AsyncResult.End(IAsyncResult result) +890624
System.ServiceModel.Activation.HostedHttpRequestAsyncResult.End(IAsyncResult result) +180062
System.Web.CallHandlerExecutionStep.OnAsyncHandlerCompletion(IAsyncResult ar) +136

I can't believe this still isn't fixed after eight months, and being told where the bug is. It's particularly rubbish as it bypasses the extensibility completely.

Worse still, it's marked as resolved when it clearly isn't.

Posted by KristianKristensen on 4/20/2011 at 9:41 AM
I tried the same; didn't work.
I'm hosting my REST services using Global.asax. The WebHttpBinding doesn't show up in base.Description.Endpoints until after base.OnOpening has been called. Sort of destroys the workaround.

Also there seems to be a bug in the workaround. Shouldn't the DummyContentTypeMapper be removed again AFTER calling base.OnOpening()?
Posted by Scottw512 on 3/2/2011 at 10:46 AM
Some additional info...I can see it execute the line

webHttpBinding.ContentTypeMapper = new DummyContentTypeMapper();

but it never hits the breakpoint I have set on the line

return WebContentFormat.Default;

in DummyContentTypeMapper.GetMessageFormatForContentType
Posted by Scottw512 on 3/2/2011 at 10:43 AM
I tried implementing the second workaround by copying and pasting the code in the post from Microsoft and I'm still having the problem. I set breakpoints in the OnOpening override and can step through the code. It finishes and still throws the error. Any ideas how to troubleshoot this?
Posted by bald75 on 3/1/2011 at 1:32 PM
also, you need the populate the "workarounds" tab, gahhh!
Posted by bald75 on 3/1/2011 at 1:32 PM
why didn't you guys just add support for nullable types to begin with, gahhhh!!!
Posted by Andres Paz1 on 2/1/2011 at 11:37 AM
I'm confused. This is marked as Resolved/Fixed. How do I know which version was this fixed on?
Posted by Sean Hederman on 1/24/2011 at 11:44 PM
Since we all know EXACTLY where the bug is, and EXACTLY what the fix is, can I ask why you are "considering it for a fix in the next release"?
Posted by Microsoft on 1/24/2011 at 4:15 PM
Hi Everyone,

First off, let me ensure everyone that the product team is aware of this bug and is considering it for a fix in the next release. I realize that the fact this connection issue is marked “paused” or “resolved” might suggest that nothing is being done, but that is not the case. Of course, waiting for the next release in which this bug may be fixed is not all that useful if one is blocked by it today. Therefore, with this post I hope to accomplish the following:

(1) Provide a straight forward work-around to the issue with the QueryStringConverter that is easy to understand ands solves some scenarios blocked by this bug.

(2) Explain the bug in more detail so that developers will understand when they will and won't hit the bug and also provide some context in regards to the next item...

(3) Provide a second work-around that is less straight forward but actually unblocks more scenarios,

So without further ado, Item (1) The Straight Forward Work-Around:

This thread was originally opened because the developer wanted to support Nullable<T> with the QueryStringConverter. The bug in question prevents this. Here is a workaround getting Nullable<T>-like support. This workaround isn't perfect (having the bug fixed would be preferable, no doubt) but it easy to implement and should unblock anybody that is trying to achieve this scenario.
First, use non-nullable types in the method signature of your service operation:

    [WebGet(UriTemplate = "/test?s={start}&e={end}")]
    public string Test(DateTime start, DateTime end)

Instead of:

        [WebGet(UriTemplate = "/test?s={start}&e={end}")]
    public string Test(DateTime? start, DateTime? end)

Second, create a helper method that will check the original request Uri to determine if the query name/value was actually present or not:

        Nullable<T> GetQueryValueOrNull<T>(string queryName, T queryValue)
        where T : struct
        {
            if (string.IsNullOrWhiteSpace(
        WebOperationContext.Current.IncomingRequest.
        UriTemplateMatch.QueryParameters[queryName]))
            {
                return null;
            }
            
            return queryValue;
        }

Third, in each service operation implementation, call the helper method to get the new Nullable value:

    public string Test(DateTime start, DateTime end)
        {
            DateTime? startOrNull = GetQueryValueOrNull<DateTime>("s", start);
            DateTime? endOrNull = GetQueryValueOrNull<DateTime>("e", end);
            return "Executed";
        }

Now this workaround does introduce extra code in your service operation and binds the query parameter names from the UriTemplate to the imperative code within the service operation. However it will unblock you for the Nullable scenario and the help page for the service will still indicate the query string variables. We’ll look at a second workaround that unblocks more scenarios after we gain some context on the bug.

Moving on to item (2), Explaining the Bug in Detail:

As was pointed out earlier in this connect thread, the bug is in the IsRawContentMapperCompatibleDispatchOperation() method of the WebServiceHost. This is actually not the method that will create the QueryStringConverter that is used by the service during runtime. However, this method will attempt to validate the parameter types of the service operation by ensuring that they are supported by the QueryStringConverter. Unfortunately, a default QueryStringConverter is constructed for this validation; this is a bug, as the code should be creating the custom QueryStringConverter provided. Parameter types in the service operation that would be allowed with the custom QueryStringConverter appear to be invalid in this validation step and so an exception is thrown.

If you wrote a custom QueryStringConverter that did not attempt to convert new types not currently supported by the default QueryStringConverter, but instead simply altered how the default types were converted, you would not hit this exception. Of course, the main scenarios for custom QueryStringConverters is to support conversion of new types, so this fact isn’t all too useful.

But now that we know this exception is thrown from this validation step and is not occurring where the actual runtime QueryStringConverter will be created, this gives us hope for another workaround. If we could simply by-pass this validation step the exception would not be thrown and the correct custom QueryStringConverter would be created for use by the service during runtime. So how can we pass this validation step?

Item (3), A Second Work-Around to Unblock More Scenarios:

It turns out this validation step is only executed if a custom ContentTypeMapper is not set when the WebServiceHost is executing OnOpening. Therefore, if we can provide a dummy ContentTypeMapper prior to calling OnOpening of the WebServiceHost, this validation step will not occur and the service will start up without throwing the exception. After OnOpening has completed, we’ll need to remove our custom ContentTypeMapper so that we’ll see the correct default behavior.

The first step is to create a dummy ContentTypeMapper:

    class DummyContentTypeMapper : WebContentTypeMapper
    {
        public override WebContentFormat
    GetMessageFormatForContentType(string contentType)
        {
            return WebContentFormat.Default;
        }
    }

The second step is to override the OnOpening of WebServiceHost is a derived type so that we can set and remove this dummy ContentTypeMapper:

protected override void OnOpening()
{
    if (base.Description != null)
    {
     foreach (var endpoint in base.Description.Endpoints)
     {
        if (endpoint.Binding != null)
        {
         // Add the derived WebHttpBehavior where the custom    
         // QueryStringConverter is set and then...

         WebHttpBinding webHttpBinding = endpoint.Binding as WebHttpBinding;
         if (webHttpBinding != null)
         {
            webHttpBinding.ContentTypeMapper = new DummyContentTypeMapper();
         }
        }
     }
    }

    base.OnOpening();

    if (base.Description != null)
    {
     foreach (var endpoint in base.Description.Endpoints)
     {
        if (endpoint.Binding != null)
        {
         WebHttpBinding webHttpBinding = endpoint.Binding as WebHttpBinding;
         if (webHttpBinding != null)
         {
            webHttpBinding.ContentTypeMapper = new DummyContentTypeMapper();
         }
        }
     }
    }
}

Thanks,
~Randall


Posted by Markus Springweiler on 1/19/2011 at 4:24 AM
Microsoft get your thumbs out of your ass!

Why is this bug tagged as "paused"? This bug is breaking any documentation which mentions QueryStringConverter in any way.
Posted by JeffN825 on 1/17/2011 at 9:35 AM
Microsoft:

When will this be fixed? This is a show stopper and a pretty blatant bug in your WebServiceHost code:


private static bool IsRawContentMapperCompatibleDispatchOperation(OperationDescription operation, ref int numStreamOperations)
{
    UriTemplateDispatchFormatter throwAway = new UriTemplateDispatchFormatter(operation, null,


/* YOUR BUG IS RIGHT HERE */
new QueryStringConverter(),


operation.DeclaringContract.Name, new Uri("http://localhost"));
    int num = throwAway.pathMapping.Count + throwAway.queryMapping.Count;
    bool isRequestCompatible = false;
.............
}




Posted by Andres Paz1 on 1/14/2011 at 10:52 AM
Is there a workaround for this?
How can we provide a Custom querystringconverter when using REST?
Posted by alekat on 12/16/2010 at 3:17 AM
Ah pity: the patch was for UriTemplateProcessor, so I think that this is still broken?
Posted by alekat on 12/15/2010 at 9:19 AM
woot! just recieved a really quick update and fix from Glen, going to try it now.
;-p

Alan
Posted by alekat on 12/15/2010 at 2:34 AM
I've been reviewing WCF (rest) and OpenRasta (have to choose between the two) with regards to some important projects I'm working on. So far I have pressure from Management to use a Microsoft technology, so how quickly this issue is resolved will determine which technology I will be recommending going forward. (Decision time is within the next 3 weeks. )

The fact that this issue has been marked as *resolved* is ringing alarm bells for me. I know that as a big company Microsoft has to decide where to allocate resources and uses voting so that critical issues will bubble to the surface and unfortunately that means that this issue will probably never get resolved as long as it's only got 7 vote points.

The purpose of a REST framework is to provide extension points where additional behaviour can be wired in as needed. The fact that this issue is "deffered" tells me this is not a high priority to Microsoft and is an indicator that if I'm looking for an extensible architecture, then this is probably not the right choice for me?

I'd love to be wrong here.

I'm just saying!

Alan
Posted by Microsoft on 11/15/2010 at 4:10 PM
We think this is a bug.

Daniel Roth
Program Manager
Posted by DGDev on 11/14/2010 at 9:43 AM
This is a much needed fix. I'd like to give my REST services the ability to have querystring string parameters which support comma-delimited values.
http://api.domain.com/Service/Operation/?Filter=val1,val2,va4&etc...

Was this a bug or intended change?
Posted by Microsoft on 11/5/2010 at 2:51 PM
Thank you for reporting this issue. We will consider fixing this issue in a future release.

Daniel Roth
Program Manager
Posted by Sean Hederman on 10/26/2010 at 4:45 AM
That would be because I used the "Steps to Reproduce" section to indicate exactly where in your code the bug was and how to fix it rather than exact steps. I have attached a sample project, as requested.
Posted by Microsoft on 10/26/2010 at 12:52 AM
Thank you for reporting this issue. In order to research the issue reported, we must first reproduce in our labs. Unfortunately, we are unable to reproduce the issue with the steps you provided.

Could you please provide us with a sample project zip?

It would be greatly appreciated if you could provide us this information as quickly as possible.

Thank you,
Posted by Microsoft on 10/25/2010 at 9:21 AM
Thank you for your feedback, we are currently reviewing the issue you have submitted. If this issue is urgent, please contact support directly(http://support.microsoft.com)
Sign in to post a workaround.
Posted by mclifton on 12/22/2011 at 3:58 PM
This is a workaround for a WCF service hosted inside of an ASP.NET Web Application.

At the top of your service markup add this attribute:

Factory="System.ServiceModel.Activation.ServiceHostFactory"

For example, it may look like:

<%@ ServiceHost Language="C#" Debug="true" Service="Namespace.Services.MyService" CodeBehind="MyService.svc.cs" Factory="System.ServiceModel.Activation.ServiceHostFactory" %>

In your codebehind add:

    public class NullableTypeWebHttpBehaviorExtension : BehaviorExtensionElement
    {
        public override Type BehaviorType
        {
            get { return typeof(NullableTypeWebHttpBehavior); }
        }

        protected override object CreateBehavior()
        {
            return new NullableTypeWebHttpBehavior();
        }
    }

    public class NullableTypeWebHttpBehavior : WebHttpBehavior
    {
        protected override System.ServiceModel.Dispatcher.QueryStringConverter GetQueryStringConverter(OperationDescription operationDescription)
        {
            return new JsonQueryStringConverter();
        }
    }

Configure your web.config to look like:

<system.serviceModel>
<extensions>
<behaviorExtensions>
    <add name="nullableTypeBehavior" type="Namespace.Services.NullableTypeBehaviorBehaviorExtension, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"></serviceHostingEnvironment>
<services>
<service name="MyWcfService">
    <endpoint address="" behaviorConfiguration="MyBehaviorConfig"
     binding="webHttpBinding" contract="Namespace.Services.MyService" />
</service>
</services>
<behaviors>
<serviceBehaviors>
    <behavior name="web">
     <serviceMetadata httpGetEnabled="true"/>
     <serviceDebug includeExceptionDetailInFaults="true"/>
    </behavior>
</serviceBehaviors>
<endpointBehaviors>
    <behavior name="MyBehaviorConfig">
     <nullableTypeBehavior />
    </behavior>
</endpointBehaviors>
</behaviors>
</client>

And that should be it. It should now use the JsonQueryStringConverter (or whatever custom converter you want) instead of the default.
Posted by CarlosFigueira on 5/27/2011 at 10:53 AM
This bug is specific to the WebServiceHost. If you use a "normal" ServiceHost it works (I've used it a few times).

The main difference is that you'll need to set up the web endpoint yourself, instead of having the host do it "automatically" for you. I'm not sure about the exact characteristics of the endpoint added by the WebServiceHost, but it uses a WebHttpBinding and a WebHttpBehavior (and for this scenario instead of this behavior you'd add a behavior which inherits from that). IIRC it also sets a content type mapper if you have Stream operations.

There's an example of a working code on the MSDN forums at http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/2341c11e-92b3-4da4-aba5-858054f46c80. It's a self-hosted service, but it can be used in webhost as well.
Posted by Jimmy Main on 5/27/2011 at 2:19 AM
Hi,

I have a workaround.
It only caters for situations in which you are using a query parameter rather than a UriTemplate string section.

/myendpoint/{myparam} does not work
/myendpoint?myparam={myparam} does work /myendpoint/myparam='A','B','C','D','E' works with the following signiature.

[WebGet(UriTemplate = "/myendpoint?myparam={myparam}")]
public static returnType MyWebMethod(Strings myparam) { ...


    [Serializable]
    [TypeConverter(typeof(TArrayStringConverter<Strings, string>))]
    public class Strings : List<string> {}

    [Serializable]
    [TypeConverter(typeof(TArrayStringConverter<Integers, int>))]
    public class Integers : List<int> { }

    public class TArrayStringConverter<T, E> : TypeConverter where T : new()
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            if (sourceType == typeof(string))
                return true;

            return base.CanConvertFrom(context, sourceType);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            var list = new T();
            if (null == value)
                return list;

            string values = Convert.ToString(value);
            var query = from string pm in values.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                        where !string.IsNullOrWhiteSpace(pm)
                        select (E) Convert.ChangeType(pm.Trim(), typeof(E));

            (list as List<E>).AddRange(query);
            return list;
        }

    }

File Name Submitted By Submitted On File Size  
WebServiceHostBug.zip 10/26/2010 9 KB
WebServiceHostBug.zip 10/26/2010 9 KB
QueryStringConverterBug.zip 6/20/2011 5 KB