I'm working on an established project that's based on WCF. It uses routing in order to expose a REST-ful API. Recently I had to change the behavior of one of the included services in order to return larger JSON-serialized responses without crashing. The change was trivial to make using a ServiceBehaviorAttribute (as shown when declaring the ServiceController class), but we wanted the solution to be configuration-based, not code-based.
NOTE: that attribute isn't actually present in the code. It's just there to illustrate how it would be used.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Net;
namespace Company.Project.Service.Services.Interfaces
{
[ServiceContract]
public interface IServiceController
{
[OperationContract]
[Description("Gets stuff")]
[WebGet(UriTemplate = "/?stuffIds={stuffIds}", ResponseFormat = WebMessageFormat.Json)]
IEnumerable<Stuff> GetStuff(string stuffIds);
}
}
namespace Company.Project.Service.Services
{
[ServiceBehavior(MaxItemsInObjectGraph = 2147483647)]
public class ServiceController : BaseController, IServiceController
{
public IEnumerable<Stuff> GetStuff(string stuffIds)
{
}
}
}
Here is (most of) the code that sets up routing:
using System;
using System.ServiceModel.Activation;
using System.Web.Routing;
using Company.Project.Service.Services;
namespace Company.Project.Service.Host
{
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
RegisterRoutes();
}
private void RegisterRoutes()
{
RouteTable.Routes.Add(new ServiceRoute("service", new WebServiceHostFactory(), typeof(Services.ServiceController)));
}
}
}
The service configuration is as follows:
<services>
<service name="Company.Project.Service.Services.ServiceControllerZZTop"
behaviorConfiguration="secureBehavior">
<endpoint name="service"
contract="Company.Project.Service.Services.Interfaces.IServiceController"
binding="basicHttpBinding"
bindingConfiguration="AsmxConfiguration"
address="ws"
/>
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="AsmxConfiguration"
receiveTimeout="00:03:00"
sendTimeout="00:03:00"
maxReceivedMessageSize="2147483647"
maxBufferPoolSize="2147483647">
<security mode="None"></security>
<readerQuotas maxDepth="2147483647"
maxStringContentLength="2147483647"
maxArrayLength="2147483647"
maxBytesPerRead="2147483647"
maxNameTableCharCount="2147483647" />
</binding>
<binding name="SecureBasic"
receiveTimeout="00:03:00"
sendTimeout="00:03:00"
maxReceivedMessageSize="2147483647"
maxBufferPoolSize="2147483647">
<security mode="Transport">
<transport clientCredentialType="None" />
</security>
<readerQuotas maxDepth="2147483647"
maxStringContentLength="2147483647"
maxArrayLength="2147483647"
maxBytesPerRead="2147483647"
maxNameTableCharCount="2147483647" />
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="secureBehavior">
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceMetadata httpsGetEnabled="true" />
<dataContractSerializer maxItemsInObjectGraph="2147483647" />
</behavior>
</serviceBehaviors>
</behaviors>
In the service configuration you'll notice that the name doesn't quite match the name of the service controller class. That is intentional -- I'd had "Cheap Sunglasses" in my head all day -- and apparently necessary to make the whole thing work. I'll explain further in a bit. Otherwise, looks normal: service references a behavior that sets the serializer's maxItemsInObjectGraph attribute high enough to fix the original problem, and the
<endpoint/>
within references a basicHttpBinding that sets all of the other necessary properties.
We used to use the
SecureBasic
binding, but went to the
AsmxConfiguration
binding when we started exposing endpoints "non-secure". (HTTP instead of SSL) They're identical except that one has transport-level security.
Running the code as I've shown actually works, and without failing when returning very large data sets. However, if I remove ZZ Top from the service class name in the service configuration, the service responds with a 404 error page.
Server Error in '/v2' Application. The resource cannot be found. Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /service/
Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.0.30319.18034
So, the questions:
- WHY? What inexorable forces have I set in opposition here?
- When writing WCF configuration for a routed service, like this one is, how do I set up the endpoint configuration? Am I doing it right? Removing the address attribute doesn't help. Should it be some other value? Basically: what is the best way to ensure that my WCF configuration is correct when I'm using routing?