Joshua Evensen
Joshua Evensen

Reputation: 1566

In a WCF Service, how can I expose a second endpoint with access to only certain methods?

I've got a service that has a whole slew of methods for creating and managing assets for our site. It's also got two methods that need to be accessible externally, lets say "GetConfiguration" and "GetElement". I want to expose the same service at different endpoints using different ServiceContracts. BUT I want those two methods to be available at both endpoints.

Here's the relevant section of my App.Config:

  <service name="Manager.Manager" behaviorConfiguration="Manager.ManagerBehavior">
    <endpoint address="" binding="customBinding" bindingConfiguration="NetHttpBinding" contract="Manager.IManager" />
    <endpoint address="Runtime" binding="customBinding" bindingConfiguration="NetHttpBinding" contract="Manager.IPublicManager" />
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
  </service>

When I load this thing into IIS and try to access it, I get the following error:

 An ExceptionDetail, likely created by IncludeExceptionDetailInFaults=true, whose value is:
 System.InvalidOperationException: An exception was thrown in a call to a WSDL export extension: System.ServiceModel.Description.DataContractSerializerOperationBehavior
 contract: http://tempuri.org/:IPublicManager ----> System.InvalidOperationException: The Manager.IPublicManager.GetConfiguration operation references a message element [http://tempuri.org/:GetConfiguration] that has already been exported from the Manager.IManager.GetConfiguration operation. You can change the name of one of the operations by changing the method name or using the Name property of OperationContractAttribute. Alternatively, you can control the element name in greater detail using the MessageContract programming model.
 at System.ServiceModel.Description.MessageContractExporter.AddElementToSchema(XmlSchemaElement element, String elementNs, XmlSchemaSet schemaSet)
 at System.ServiceModel.Description.MessageContractExporter.ExportWrappedPart(Message message, String elementName, String elementNs, XmlSchemaSet schemaSet, Boolean skipSchemaExport)
 at System.ServiceModel.Description.DataContractSerializerMessageContractExporter.ExportBody(Int32 messageIndex, Object state)
 at System.ServiceModel.Description.MessageContractExporter.ExportMessage(Int32 messageIndex, Object state)
 at System.ServiceModel.Description.MessageContractExporter.ExportMessageContract()
 at System.ServiceModel.Description.WsdlExporter.CallExtension(WsdlContractConversionContext contractContext, IWsdlExportExtension extension)
 --- End of inner ExceptionDetail stack trace ---
 at System.ServiceModel.Description.WsdlExporter.CallExtension(WsdlContractConversionContext contractContext, IWsdlExportExtension extension)
 at System.ServiceModel.Description.WsdlExporter.CallExportContract(WsdlContractConversionContext contractContext)
 at System.ServiceModel.Description.WsdlExporter.ExportContract(ContractDescription contract)
 at System.ServiceModel.Description.WsdlExporter.ExportEndpoint(ServiceEndpoint endpoint, XmlQualifiedName wsdlServiceQName)
 at System.ServiceModel.Description.WsdlExporter.ExportEndpoints(IEnumerable`1 endpoints, XmlQualifiedName wsdlServiceQName)
 at System.ServiceModel.Description.ServiceMetadataBehavior.MetadataExtensionInitializer.GenerateMetadata()
 at System.ServiceModel.Description.ServiceMetadataExtension.EnsureInitialized()
 at System.ServiceModel.Description.ServiceMetadataExtension.HttpGetImpl.InitializationData.InitializeFrom(ServiceMetadataExtension extension)
 at System.ServiceModel.Description.ServiceMetadataExtension.HttpGetImpl.GetInitData()
 at System.ServiceModel.Description.ServiceMetadataExtension.HttpGetImpl.TryHandleDocumentationRequest(Message httpGetRequest, String[] queries, Message& replyMessage)
 at System.ServiceModel.Description.ServiceMetadataExtension.HttpGetImpl.ProcessHttpRequest(Message httpGetRequest)
 at SyncInvokeGet(Object , Object[] , Object[] )
 at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
 at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
 at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
 at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc)
 at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)

So it doesn't like that the Operations have the same name. Any ideas? They both use the same Service class. Maybe I'm just approaching the problem entirely the wrong way?

Upvotes: 4

Views: 1431

Answers (2)

Mahesh Malpani
Mahesh Malpani

Reputation: 1999

1) Create the two interface/ contract with service contract attribute. 2) Implement both contract in service. 3) create endpoint and mention different contract name (interface) though service is same.

For more information please see think link

https://www.safaribooksonline.com/library/view/learning-wcf/9780596101626/ch01s07.html

Upvotes: 0

faester
faester

Reputation: 15086

Although this doesn't exactly solve your problem, I hope it can be of some help. I use webHttpBinding since I am most familiar with that.

The idea here is to split public and private functionality into two separate interfaces:

[ServiceContract]
public interface IPrivate  : IPublic
{
    [WebGet(UriTemplate = "/admin")]
    [OperationContract]
    int PrivateStuff();
}

[ServiceContract]
public interface IPublic
{
    [WebGet(UriTemplate = "/common")]
    [OperationContract]
    int PublicStuff();
}

As you can see implementors of IPrivate must also implement IPublic why the service implements IPrivate to be exposable as both.

The service implementation can now be exposed as both an IPublic and an IPrivate and the endpoints can be exposed beneath the service ("service1.svc"):

    <system.serviceModel>
      <behaviors>
        <serviceBehaviors>
          <behavior>
            <serviceMetadata httpGetEnabled="true"/>
            <serviceDebug includeExceptionDetailInFaults="false"/>
          </behavior>
        </serviceBehaviors>
        <endpointBehaviors>
          <behavior name="rest">
            <webHttp/>
          </behavior>
        </endpointBehaviors>
      </behaviors>
      <services>
        <service name="ManagerService.Service">
          <endpoint address="private" behaviorConfiguration="rest" contract="ManagerService.IPrivate" binding="webHttpBinding"/>
          <endpoint address="public" behaviorConfiguration="rest" contract="ManagerService.IPublic" binding="webHttpBinding" />
          <endpoint address="/private/mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
          <endpoint address="/public/mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
        </service>
      </services>

The service is now exposed in it's public part at http://host/service1.svc/common whereas the private version is exposed at http://host/service1.svc/admin

The meta data exchange does not seem to work correctly, but that is probably due to the binding type.

Upvotes: 1

Related Questions