WCF Rest Webservice with stream - wcf

I read the following post with interest as it is an exact replica of the problem I am experiencing (and driving me insane)
"For request in operation UploadFile to be a stream the operation must have a single parameter whose type is Stream." -http://social.msdn.microsoft.com/Forums/en/wcf/thread/80cd26eb-b7a6-4db6-9e6e-ba65b3095267
I have pretty much followed all code/examples I have found and yet still cannot get around this error -
http://blogs.msdn.com/b/carlosfigueira/archive/2008/04/17/wcf-raw-programming-model-receiving-arbitrary-data.aspx
All I would like to achieve is to post an image(jpeg/png) from an android device using the standard filename/stream parameters.More than likely it is something simple that I have misconfigured, misunderstood or left out but I need to have a solution for proof of concept.
public interface IConXServer
{
[OperationContract]
[WebInvoke(UriTemplate = "UploadImage({fileName})", Method="POST")]
void UploadImage(string fileName, Stream imageStream);
}
public class ConXWCFServer : IConXServer
{
public void UploadImage(string fileName, Stream imageStream)
{
//implement image save
}
}
web.config settings
-->
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint name="webHttpEndpoint" helpEnabled="false"/>
</webHttpEndpoint>
</standardEndpoints>
<bindings>
<webHttpBinding>
<binding name="webHttpBinding" transferMode="Streamed"/>
</webHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="webHttpBehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="false"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceThrottling maxConcurrentCalls="2147483647" maxConcurrentSessions="2147483647"/>
</behavior>
</serviceBehaviors>
</behaviors>
Using vs2010 and IIS Express. If I comment out the above method all the others methods work and return data as well as the wsdl query
Regards and thanks in advance
Kern

You mention WSDL, which leads me to believe you're getting the error while trying to browse the metadata endpoint for the service. So, first off, WSDL and REST don't go together, so you shouldn't expect to use it at all for a REST interface. Forget the service metadata concept even exists in the REST world.
Next While it's true the REST's webHttpBinding supports parameters in front of the Stream body parameter, other bindings do not and there must either be a single Stream parameter or a message contract with headers and a stream body.
So, in the end, the problem is not with the REST webHttpBinding at all, I bet it works just fine. If it doesn't I would be absolutely shocked because you're not doing anything that shouldn't work in that department. The problem is that you're expecting the metadata endpoint to generate WSDL for the service contract you've defined and that's just not supported.

I do it this way and it'works.
Add Factory Class to webservice (WcfService2.ServiceFactory)
<%# ServiceHost Language="C#" Debug="true" Service="WcfService2.Service1" CodeBehind="Service1.svc.cs" Factory="WcfService2.ServiceFactory" %>
My Interface:
public interface IService1
{
[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest, Method = "POST",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
UriTemplate = "UploadFile/{fileName}")]
void UploadFile(string fileName, Stream fileContent);
}
My Method:
public void UploadFile(string fileName, Stream fileContent)
{
var pathfile = "\\\\SERVER\\TravelsRequestFiles";
using (var fileStream = new FileStream(string.Concat(pathfile, "\\", fileName), FileMode.Create, FileAccess.Write))
{
fileContent.CopyTo(fileStream);
}
}
my FactoryClass:
public class ServiceFactory : ServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
return new MyServiceHost(serviceType, baseAddresses);
}
class MyServiceHost : ServiceHost
{
public MyServiceHost(Type serviceType, Uri[] baseAddresses)
: base(serviceType, baseAddresses)
{
}
protected override void InitializeRuntime()
{
ServiceEndpoint endpoint = this.Description.Endpoints[0];
endpoint.Behaviors.Add(new EndpointBehaviors());
base.InitializeRuntime();
}
}
}
I added a EndpointBehaviors class, wich it find de operation UploadFile and remove its DataContractSerializerOperationBehavior, and then works!
public class EndpointBehaviors: IEndpointBehavior
{
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
ContractDescription cd = endpoint.Contract;
foreach (DispatchOperation objDispatchOperation in endpointDispatcher.DispatchRuntime.Operations)
{
if (objDispatchOperation.Name.Equals("UploadFile"))
{
OperationDescription myOperationDescription = cd.Operations.Find("UploadFile");
DataContractSerializerOperationBehavior serializerBehavior = myOperationDescription.Behaviors.Find<DataContractSerializerOperationBehavior>();
myOperationDescription.Behaviors.Remove(serializerBehavior);
}
}
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void Validate(ServiceEndpoint endpoint)
{
BindingElementCollection elements = endpoint.Binding.CreateBindingElements();
WebMessageEncodingBindingElement webEncoder = elements.Find<WebMessageEncodingBindingElement>();
if (webEncoder == null)
{
throw new InvalidOperationException("This behavior must be used in an endpoint with the WebHttpBinding (or a custom binding with the WebMessageEncodingBindingElement).");
}
}
}

Related

Declaratively configure WCF behavior extension on programmatically constructed endpoint

I have a WCF behavior extension that I would like to add to a WCF client. However, the client is constructed programmatically. The endpoint address may vary, but I know the type. I could add the behavior programmatically or in the config file (preferred), but I need to pass some configuration in the config file only.
I don't want this in Common behaviors (machine.config).
I can add the behavior programmatically
endpoint.Behaviors.Add(new MyCustomBehavior())
But I'd rather do it in config, so I can configure the extension there as well.
Is it possible to declaratively add and configure an endpoint behavior extension to a programmatically constructed endpoint knowing only the the type or interface while leaving the client endpoint to be constructed programmatically?
<system.serviceModel>
<client>
<!-- Created programmatically -->
</client>
<extensions>
<behaviorExtensions>
<add name="MyCustomBehavior" type="namespace.CustomBehaviors", MyAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="MyCustomBehavior">
<MyCustomBehavior MyImportantBehaviorParam1="foo" />
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
Of course I could put the config in another section, and have my behavior read it there, but I'd rather use the WCF facilities if possible.
To do that you need to create a behavior configuration extension for your endpoint. For more information on how to do that, check https://learn.microsoft.com/en-us/archive/blogs/carlosfigueira/wcf-extensibility-behavior-configuration-extensions.
Update: I see your issue now. There's no direct way to add to an endpoint created via code a behavior declared in configuration. You can still do it, though, but you'll need to use some reflection to access the CreateBehavior method of the behavior configuration extension (the method is protected) to actually create the endpoint behavior to add it to the endpoint created via code. The code below shows how this can be done.
public class StackOverflow_10232385
{
public class MyCustomBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
Console.WriteLine("In {0}.{1}", this.GetType().Name, MethodBase.GetCurrentMethod().Name);
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
Console.WriteLine("In {0}.{1}", this.GetType().Name, MethodBase.GetCurrentMethod().Name);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
Console.WriteLine("In {0}.{1}", this.GetType().Name, MethodBase.GetCurrentMethod().Name);
}
public void Validate(ServiceEndpoint endpoint)
{
Console.WriteLine("In {0}.{1}", this.GetType().Name, MethodBase.GetCurrentMethod().Name);
}
}
public class MyCustomBehaviorExtension : BehaviorExtensionElement
{
public override Type BehaviorType
{
get { return typeof(MyCustomBehavior); }
}
protected override object CreateBehavior()
{
return new MyCustomBehavior();
}
}
[ServiceContract]
public interface ITest
{
[OperationContract]
string Echo(string text);
}
public class Service : ITest
{
public string Echo(string text)
{
return text;
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "");
var configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
ServiceModelSectionGroup smsg = configuration.GetSectionGroup("system.serviceModel") as ServiceModelSectionGroup;
EndpointBehaviorElement endpointBehaviorElement = smsg.Behaviors.EndpointBehaviors["MyCustomBehavior_10232385"];
foreach (BehaviorExtensionElement behaviorElement in endpointBehaviorElement)
{
MethodInfo createBehaviorMethod = behaviorElement.GetType().GetMethod("CreateBehavior", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, null, Type.EmptyTypes, null);
IEndpointBehavior behavior = createBehaviorMethod.Invoke(behaviorElement, new object[0]) as IEndpointBehavior;
endpoint.Behaviors.Add(behavior);
}
host.Open();
Console.WriteLine("Host opened");
ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new BasicHttpBinding(), new EndpointAddress(baseAddress));
ITest proxy = factory.CreateChannel();
Console.WriteLine(proxy.Echo("Hello"));
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
And the configuration for this code:
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="myCustomBehavior_10232385" type="QuickCode1.StackOverflow_10232385+MyCustomBehaviorExtension, QuickCode1"/>
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="MyCustomBehavior_10232385">
<myCustomBehavior_10232385/>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>

WCF - Streaming file upload over http

I am trying to build a WCF service that will allow my WPF desktop clients to upload files to a server.
I adapted a code sample from The Code Project (WCF Streaming: Upload/Download Files Over HTTP) and I've looked at several SO posts as well, but can't seem to get this working.
When I execute the code, it fails with a null reference exception at the point that the server tries to read the stream that has been passed through the interface.
At this point, I am rather lost and don't know how to fix this up. Any suggestions are appreciated.
Code samples follow:
CustomerDocumentModel is the data element that I pass through the WCF interface with the stream to read the client side file:
[DataContract]
[KnownType(typeof(System.IO.FileStream))]
public class CustomerDocumentModel : IDisposable
{
public CustomerDocumentModel()
{
}
public CustomerDocumentModel(string documentName, string path)
{
DocumentName = documentName;
Path = path;
}
[DataMember]
public string DocumentName;
[DataMember]
public string Path;
[DataMember]
public System.IO.Stream FileByteStream;
public void Dispose()
{
if (FileByteStream != null)
{
FileByteStream.Close();
FileByteStream = null;
}
}
}
IBillingService is the interface definition for my WCF service:
[ServiceContract]
public interface IBillingService
{
// other methods redacted...
[OperationContract]
void UploadCustomerDocument(CustomerDocumentModel model);
}
The class BillingService implements the WCF service:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class BillingService : IBillingService
{
// Other methods redacted ...
public void UploadCustomerDocument(CustomerDocumentModel model)
{
string path = HttpContext.Current.Server.MapPath(
String.Format("/Documents/{1}",
model.DocumentName));
using (FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
{
const int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int size = 0;
try
{
// The following Read() fails with a NullReferenceException
while ((size = model.FileByteStream.Read(buffer, 0, bufferSize)) > 0)
{
stream.Write(buffer, 0, size);
}
}
catch
{
throw;
}
finally
{
stream.Close();
model.FileByteStream.Close();
}
}
}
}
A few relevant bits from the web.config on my WCF web server:
<system.web>
<compilation debug="true" targetFramework="4.0" />
<httpRuntime maxRequestLength="2097151" useFullyQualifiedRedirectUrl="true" executionTimeout="360"/>
</system.web>
<system.serviceModel>
<serviceHostingEnvironment
aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true" />
<bindings>
<basicHttpBinding>
<binding name="userHttps" transferMode="Streamed" maxReceivedMessageSize="2147483647" maxBufferSize="2147483647">
<readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
<security mode="None" />
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="">
<dataContractSerializer maxItemsInObjectGraph="2147483646"/>
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
The client is a WPF/MVVM app that creates a CustomerDocumentModel model, uses an OpenFileDialog to Open() the file stream and then passes the model to the UploadCustomerDocument method on WCF Service.
If I am missing any relevant details, please ask.
I know this rather very late reply for your question and I'm sure you must have resolved your problem as well. This could be helpful to someone else :-)
Use Messagecontract over Datacontract and only one MessageBodyMember with datatype Stream and rest all parameter are MessageHeader.
Here is the example:
[MessageContract]
public class CustomerDocumentModel : IDisposable
{
public CustomerDocumentModel(string documentName, string path)
{
DocumentName = documentName;
Path = path;
}
[MessageHeader]
public string DocumentName{get;set;}
[MessageHeader]
public string Path{get;set;}
[MessageBodyMember]
public System.IO.Stream FileByteStream{get;set;}
public void Dispose()
{
if (FileByteStream != null)
{
FileByteStream.Close();
FileByteStream = null;
}
}
}
Note: Make sure your in your configuration transfer mode is StreamedResponse, also you may want to change the MessageEncoding to MTOM for better performance.
public void UploadCustomerDocument(CustomerDocumentModel model)
{
var filename = //your file name and path;
using (var fs = new FileStream(filename, FileMode.Create))
{
model.FileByteStream.CopyTo(fs);
}
}
Your data type is what is making the streaming fail. This is documented on MSDN here: http://msdn.microsoft.com/en-us/library/ms731913.aspx
The relevant passage is:
Restrictions on Streamed Transfers
Using the streamed transfer mode causes the run time to enforce
additional restrictions.
Operations that occur across a streamed transport can have a contract
with at most one input or output parameter. That parameter corresponds
to the entire body of the message and must be a Message, a derived
type of Stream, or an IXmlSerializable implementation. Having a return
value for an operation is equivalent to having an output parameter.
Some WCF features, such as reliable messaging, transactions, and SOAP
message-level security, rely on buffering messages for transmissions.
Using these features may reduce or eliminate the performance benefits
gained by using streaming. To secure a streamed transport, use
transport-level security only or use transport-level security plus
authentication-only message security.
SOAP headers are always buffered, even when the transfer mode is set
to streamed. The headers for a message must not exceed the size of the
MaxBufferSize transport quota. For more information about this
setting, see Transport Quotas.

WCF service behavior extension throwing null reference exception

So, im trying to write routing service. The idea is that, every time someone calls routing service, endpoint is randomly selected by WCF behavior extension. I used slightly modified example from MSDN called DynamicReconfiguration to achieve that. Part of my web.config looks like this
<behaviors>
<serviceBehaviors>
<behavior>
<behavior name="behaviorWithUpdate">
<updateBehavior />
</behavior>
</serviceBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="updateBehavior" type="RouterService.UpdateBehavior, RouterService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
<services>
and implementation of behavior and behavior extension
public class UpdateBehavior : BehaviorExtensionElement, IServiceBehavior
{
void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
var rulesUpdateExtension = new RulesUpdateExtension();
serviceHostBase.Extensions.Add(rulesUpdateExtension);
}
void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
class RulesUpdateExtension : IExtension<ServiceHostBase>
{
ServiceHostBase _owner;
List<EndpointAddress> _endpoints = new List<EndpointAddress>
{
new EndpointAddress("http://localhost:19338/Service1.svc"),
new EndpointAddress("http://localhost:20464/Service2.svc")
};
void IExtension<ServiceHostBase>.Attach(ServiceHostBase owner)
{
this._owner = owner;
UpdateRules(DateTime.Now.Second % 2 == 0 ? _endpoints[0] : _endpoints[1]);
}
void IExtension<ServiceHostBase>.Detach(ServiceHostBase owner)
{
}
void UpdateRules(EndpointAddress endpoint)
{
var rc = new RoutingConfiguration();
var serviceEndpoint = new ServiceEndpoint(
ContractDescription.GetContract(typeof(IService1)),
new BasicHttpBinding(),
endpoint);
rc.FilterTable.Add(new MatchAllMessageFilter(), new List<ServiceEndpoint> { serviceEndpoint });
this._owner.Extensions.Find<RoutingExtension>().ApplyConfiguration(rc);
}
}
public override Type BehaviorType
{
get { return typeof(UpdateBehavior); }
}
protected override object CreateBehavior()
{
return new UpdateBehavior();
}
}
Problem is that last line of UpdateRules method is throwing NullReferenceException. It cant find this extension even though i attach it in behavior. In example from MSDN, routing service is hosted in console application and here im trying to host it on IIS. Im missing something here...
In the sample RoutingService project, the code in the routing.cs programmatically injects the RoutingExtension into the RoutingService. This is normally done in the config file using the behaviors>serviceBehaviors>behavior>routing element to set up the filter table to use. However, since a WCF service can only be assigned a single service behavior through the config file, the sample RoutingService project dynamically adds the RoutingExtension in the service host initialization.
To do the same thing in IIS, you need to create a custom service host factory to perform the same function of the routing.cs code in the sample project. Look at this MSDN article for how to create a custom host. It also shows how you would configure it with IIS.

WCF Constructor with parameter / Custom Behavior Created In Code

I've a must to create wcf service with parameter.
I'm following this http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/8f18aed8-8e34-48ea-b8be-6c29ac3b4f41
First this is that I don't know how can I set this custom behavior "MyServiceBehavior" in my Web.config in ASP.NET MVC app that will host it.
As far as I know behaviors must be declared in section in wcf.config.
How can I add reference there to my behavior class from service assembly?
An second thing is that in the following example they have created local host (they use
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
to host in console application), but how I can add headers
OperationContext.Current.OutgoingMessageHeaders.Add ...
used to initialize constructor when I use in my WPF client application service reference and it will already create instance of web service "client" class
PBSDataCacheSyncContractClient client = new PBSDataCacheSyncContractClient();
is't it too late? Or when I have my own custom behavior can I do something like this:
PBSDataCacheSyncContractClient client = new PBSDataCacheSyncContractClient(my var for service constructor) ?
Regards,
Daniel Skowroński
EDIT: 31-05-2010
#manunt
I've improved my second question.
For answer to my first question, I've managed to create custom extenstion but I can't register it.
My scenario:
I have definitions for my web service in WCF library (interface, contract, implementation of IInstanceProvider, BehaviorExtensionElement)
then I reference it to another project ASP.NET application
inside ASP.NET application I have WCF service file and it is pointed to my class from WCF library
all my configuration is declared in web.config
In my WCF library I have:
namespace PBS.SyncService
{
using System;
using System.Data;
using System.Collections.ObjectModel;
using System.ServiceModel;
using Microsoft.Synchronization.Data;
using System.ServiceModel.Activation;
using Microsoft.Synchronization.Data.Server;
using System.Data.SqlClient;
using System.Collections.Generic;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Configuration;
[XmlSerializerFormat()]
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public partial class PBSDataCacheSyncService : object, IPBSDataCacheSyncContract
{
private PBSDataCacheServerSyncProvider _serverSyncProvider;
public PBSDataCacheSyncService()
{
this._serverSyncProvider = new PBSDataCacheServerSyncProvider();
}
public PBSDataCacheSyncService(long doctorId)
{
this._serverSyncProvider = new PBSDataCacheServerSyncProvider();
this._serverSyncProvider.DoctorId = doctorId;
this._serverSyncProvider.InitializeCustomSyncProvider();
}
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public virtual SyncContext ApplyChanges(Microsoft.Synchronization.Data.SyncGroupMetadata groupMetadata, DataSet dataSet, Microsoft.Synchronization.Data.SyncSession syncSession)
{
return this._serverSyncProvider.ApplyChanges(groupMetadata, dataSet, syncSession);
}
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public virtual SyncContext GetChanges(Microsoft.Synchronization.Data.SyncGroupMetadata groupMetadata, Microsoft.Synchronization.Data.SyncSession syncSession)
{
return this._serverSyncProvider.GetChanges(groupMetadata, syncSession);
}
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public virtual SyncSchema GetSchema(Collection<string> tableNames, Microsoft.Synchronization.Data.SyncSession syncSession)
{
return this._serverSyncProvider.GetSchema(tableNames, syncSession);
}
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public virtual SyncServerInfo GetServerInfo(Microsoft.Synchronization.Data.SyncSession syncSession)
{
return this._serverSyncProvider.GetServerInfo(syncSession);
}
public bool InitializeCustomSyncProvider(long doctorId)
{
this._serverSyncProvider.DoctorId = doctorId;
return this._serverSyncProvider.InitializeCustomSyncProvider();
}
}
[XmlSerializerFormat()]
[ServiceContractAttribute()]
public interface IPBSDataCacheSyncContract
{
[OperationContract()]
SyncContext ApplyChanges(Microsoft.Synchronization.Data.SyncGroupMetadata groupMetadata, DataSet dataSet, Microsoft.Synchronization.Data.SyncSession syncSession);
[OperationContract()]
SyncContext GetChanges(Microsoft.Synchronization.Data.SyncGroupMetadata groupMetadata, Microsoft.Synchronization.Data.SyncSession syncSession);
[OperationContract()]
SyncSchema GetSchema(Collection<string> tableNames, Microsoft.Synchronization.Data.SyncSession syncSession);
[OperationContract()]
SyncServerInfo GetServerInfo(Microsoft.Synchronization.Data.SyncSession syncSession);
[OperationContract()]
bool InitializeCustomSyncProvider(long doctorId);
[OperationContract()]
string[] GetSyncAdapterInfo();
}
public class PBSDataCacheSyncProvider : IInstanceProvider
{
public object GetInstance(InstanceContext instanceContext, Message message)
{
string doctorId = message.Headers.GetHeader<string>("DoctorId", "http://***/SyncService.svc");
if (doctorId != null)
{
return new PBSDataCacheSyncService(Convert.ToInt64(doctorId));
}
else
{
return new PBSDataCacheSyncService();
}
}
public object GetInstance(InstanceContext instanceContext)
{
return new PBSDataCacheSyncService();
}
public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
}
}
public class PBSDataCacheSyncBehavior : BehaviorExtensionElement, IServiceBehavior
{
PBSDataCacheSyncProvider pbsProvider = new PBSDataCacheSyncProvider();
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { }
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers)
{
foreach (EndpointDispatcher ed in cd.Endpoints)
{
ed.DispatchRuntime.InstanceProvider = this.pbsProvider;
}
}
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { }
public override Type BehaviorType
{
get { return typeof(PBSDataCacheSyncBehavior); }
}
protected override object CreateBehavior()
{
return new PBSDataCacheSyncBehavior();
}
}
}
My WCF Service file has name: SyncService.svc and in my makrup I have:
<%# ServiceHost Language="C#" Debug="true" Service="PBS.SyncService.PBSDataCacheSyncService" CodeBehind="PBS.SyncService.PBSDataCache.Server.SyncContract.cs" %>
My web.config:
<service name="PBS.Web.SyncService" behaviorConfiguration="behPBSDataCacheSyncBehavior">
<host>
<baseAddresses>
<add baseAddress="http://***/SyncService.svc" />
</baseAddresses>
</host>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<endpoint address="" binding="basicHttpBinding" contract="PBS.SyncService.IPBSDataCacheSyncContract" />
</service>
<serviceBehaviors>
<behavior name="behPBSDataCacheSyncBehavior">
<PBSDataCacheSyncBehavior /> <!-- this element is being ignored -->
</behavior>
</serviceBehaviors>
<extensions>
<behaviorExtensions>
<add name="PBSDataCacheSyncBehavior" type="PBS.SyncService.PBSDataCacheSyncBehavior, PBS.SyncService,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
Can you tell me what I'm missing in this point?
Why parser ignores my custom extension declaration?
I have following error:
Configuration Error
Description: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately.
Parser Error Message: An error occurred creating the configuration section handler for system.serviceModel/behaviors: Extension element 'PBSDataCacheSyncBehavior' cannot be added to this element. Verify that the extension is registered in the extension collection at system.serviceModel/extensions/behaviorExtensions.
Parameter name: element
EDIT: 01-06-2010
Problem with parser resolved by typing all the declaration in one single line.
I still don't know how to add header when I have service reference.
In my WPF application I have only client instance witch implements my IPBSDataCacheSyncContract autogenerated by Service Reference.
And when I initialize it it only has constructors:
public PBSDataCacheSyncContractClient() {
}
public PBSDataCacheSyncContractClient(string endpointConfigurationName) :
base(endpointConfigurationName) {
}
public PBSDataCacheSyncContractClient(string endpointConfigurationName, string remoteAddress) :
base(endpointConfigurationName, remoteAddress) {
}
public PBSDataCacheSyncContractClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
base(endpointConfigurationName, remoteAddress) {
}
public PBSDataCacheSyncContractClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress) {
}
Where I can add headers?
"As for the second question - you should define message contract with needed headers in it and provide header values for each message separately." Could you be more specific?
EDIT: 02-06-2010
I've encountered other issue.
When I have now my configuration httpGetEnabled is ignored... :
<serviceBehaviors>
<behavior name="behPBSDataCacheSyncBehavior">
<PBSDataCacheSyncBehavior />
<serviceMetadata httpGetEnabled="true" /><!-- ignored -->
<serviceDebug includeExceptionDetailInFaults="true" /><!-- ignored -->
</behavior>
</serviceBehaviors>
How can I fix it?
EDIT: 02-06-2010
OK I've figured workaround. Still it is weird but it works!
My problem was with web.config. And none name behavior entry entry is recognized by my service and not any other... So I simply added no name behavior to collection.
<serviceBehaviors>
<behavior name="">
<PBSDataCacheSyncBehavior />
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
<behavior name="behPBSDataCacheSyncBehavior">
<PBSDataCacheSyncBehavior />
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
And I add header in my code this way:
int doctorId = 2;
Sync.PBSDataCacheSyncContractClient client = new Sync.PBSDataCacheSyncContractClient();
new OperationContextScope (client.InnerChannel);
OperationContext.Current.OutgoingMessageHeaders.Add(
MessageHeader.CreateHeader("DoctorId", "http://***/SyncService.svc", doctorId));
I've changed topic to be more useful.
HTH
Regards,
Daniel Skowroński
I know what the problem is with the behavior not being found and why you need the hack with the behavior with no name.
If you look at this line in your svc markup file:
<%# ServiceHost Language="C#" Debug="true" Service="PBS.SyncService.PBSDataCacheSyncService" CodeBehind="PBS.SyncService.PBSDataCache.Server.SyncContract.cs" %>
and this line in your web.Config:
<service name="PBS.Web.SyncService" behaviorConfiguration="behPBSDataCacheSyncBehavior">
You will notice that the name specified in the service tag is different from the Service class specified in the Service attribute in the markup file.
I think it should be something like this:
instead of
<service name="PBS.Web.SyncService" behaviorConfiguration="behPBSDataCacheSyncBehavior">
this
<service name="PBS.SyncService.PBSDataCacheSyncService" behaviorConfiguration="behPBSDataCacheSyncBehavior">
These two values I think have to the same not sure but in my case the two values were different and I had to do the blank service name hack. But by setting both values the same, it worked. It found the behavior without needing the blank one and I was able to access my wsdl.
Answer for the first question you can find here.
Regarding error you are getting - do not split definition of your extension into two lines, because xml parser cannot handle that.
A sample how to define custom headers without specifying message contract:
var client = new Service1Client();
new OperationContextScope(client.InnerChannel);
MessageHeader<string> typedHeader = new MessageHeader<string>("headercontent");
MessageHeader header = typedHeader.GetUntypedHeader("myheader", "myns");
OperationContext.Current.OutgoingMessageHeaders.Add(header);

When and where to set a custom IOperationInvoker?

I'm trying to extend WCF so that I can have a RESTful web service, in which, for each operation, I perform a verification of the HTTP Authorization header, whose value I use to call a Login() method.
After the login is done, I wish to invoke the operation's corresponding method checking if a security exception is thrown, in which case I'll reply with a custom "access denied" message" using the appropriate HTTP Status Code.
With this in mind, I thought implementing a IEndpointBehavior that applies an implementaion of IOperationInvoker to each operation (setting the DispatchOperation.Invoker property) would be a good idea.
I decided to implement an IOperationInvoker using the Decorator design pattern. My implementation would need another IOperationInvoker in it's constructor to which the method invocations would be delegated.
This is my IOperationInvokerImplementation:
public class BookSmarTkOperationInvoker : IOperationInvoker{
private readonly IOperationInvoker invoker;
public BookSmarTkOperationInvoker(IOperationInvoker decoratee)
{
this.invoker = decoratee;
}
public object[] AllocateInputs()
{
return this.invoker.AllocateInputs();
}
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
BeforeOperation(); // Where there's code to perform the login using WebOperationContext.Current
object o = null;
try
{
o = this.invoker.Invoke(instance, inputs, out outputs);
}
catch (Exception exception)
{
outputs = null;
return AfterFailedOperation(exception); // Return a custom access denied response
}
return o;
}
public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
{
throw new Exception("The operation invoker is not asynchronous.");
}
public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
{
throw new Exception("The operation invoker is not asynchronous.");
}
public bool IsSynchronous
{
get
{
return false;
}
}
}
I decided to implement an IEndpointBehavior by extending the behavior I already needed (WebHttpBehavior) this way I only use one beavior. Here's the code I wrote:
public class BookSmarTkEndpointBehavior : WebHttpBehavior
{
public override void Validate(ServiceEndpoint endpoint)
{
base.Validate(endpoint);
}
public override void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
base.AddBindingParameters(endpoint, bindingParameters);
}
public override void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
base.ApplyDispatchBehavior(endpoint, endpointDispatcher);
foreach (DispatchOperation operation in endpointDispatcher.DispatchRuntime.Operations)
{
IOperationInvoker defaultInvoker = operation.Invoker;
IOperationInvoker decoratorInvoker = new BookSmarTkOperationInvoker(defaultInvoker);
operation.Invoker = decoratorInvoker;
Console.Write("Before: " + ((object)defaultInvoker ?? "null"));
Console.WriteLine(" After: " + operation.Invoker);
}
}
public override void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
base.ApplyClientBehavior(endpoint, clientRuntime);
throw new Exception("The BookSmarTkEndointBehavior cannot be used in client endpoints.");
}
}
Now here's the problem:
Only the constructor is being invoked in the IOperationInvoker, none of the other methods are.
The decoratee IOperationInvoker (the one that's passed in the decorator's constructor) is null.
I'm guessing that maybe some other code from some other behavior is setting another IOperationInvoker in the OperationDispatcher.Invoker setting afterwards. Thus, overriding mine. This would clearly explain my situation.
What is happening and what should I do?
My service is self-hosted.
In case you need to see it, here is the configuration I have in the app.config file under system.serviceModel.
<services>
<service name="BookSmarTk.Web.Service.BookSmarTkService">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8080/service"/>
</baseAddresses>
</host>
<endpoint
address=""
behaviorConfiguration="BookSmaTkEndpointBehavior"
binding="webHttpBinding"
bindingConfiguration="BookSmarTkBinding"
contract="BookSmarTk.Web.Service.BookSmarTkService">
</endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name ="BookSmartkServiceBehavior">
<serviceDebug httpHelpPageEnabled="true" httpHelpPageUrl="/help.htm" includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="BookSmaTkEndpointBehavior">
<!--<webHttp/>-->
<bookSmarTkEndpointBehavior />
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<webHttpBinding>
<binding name="BookSmarTkBinding">
</binding>
</webHttpBinding>
</bindings>
<extensions>
<behaviorExtensions>
<add name="bookSmarTkEndpointBehavior" type="BookSmarTk.Web.Service.BookSmarTkEndpointBehaviorElement, BookSmarTk.Web.Service, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
I you read this far I am deeply grateful towards you. Really, Thank You!
Instead of setting invokers at ApplyDispatchBehavior() method, you have to make an IOperationBehavior implementor:
public class MyOperationBehavior: IOperationBehavior
{
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
dispatchOperation.Invoker = new BookSmarTkOperationInvoker(dispatchOperation.Invoker);
}
public void Validate(OperationDescription operationDescription)
{
}
}
and then at ApplyDispatchBehavior() you should set that behavior:
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
foreach (var operation in endpoint.Contract.Operations) {
if (operation.Behaviors.Contains(typeof(MyOperationBehavior)))
continue;
operation.Behaviors.Add(new MyOperationBehavior());
}
}
I am building something similar (I think - don't have the time to look through all your code), but have gone about it in a different way.
To achieve this I am using the following:
An IMessageInspector to read the incoming HTTP request message headers (in this case extracting a session Id from a cookie and retrieving a session object from a cache).
A combination of an IPrincipal and an IAuthorizationPolicy to implement my own custom authorization code (WCF will automatically invoke my code for requests to web service methods which have the attribute '[PrincipalPermission(SecurityAction.Demand, Role="somerole")]' set).
An IErrorHandler which catches any uncaught exceptions from the web service methods (including a permission denied exception thrown if authorization fails -- i.e. the IsRole method you implement in the IPrincipal returns false). If you catch the security denied exception you can then use WebOperationContext.Current to set the custom HTTP error codes for the response message.
A custom behavior (an IContractBehavior - but you can also use an EndPoint or Service behavior or whatever you want) which creates all the above at runtime and attaches them to the appropriate endpoints.
I know this is very old, but for me Alexey's answer
worked. However, only when the ApplyDispatchBehaviour method calls the base method. Like this:
public override void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
base.ApplyDispatchBehavior(endpoint, endpointDispatcher);
foreach (var operation in endpoint.Contract.Operations)
{
if (operation.Behaviors.Contains(typeof(AccessControlOperationBehaviour)))
continue;
operation.Behaviors.Add(new AccessControlOperationBehaviour());
}
}