Is there a way to create an async client for a synchronous WCF service without adding a service reference? This is for a .NET 4 client.
A service reference in Visual Studio is nothing else than a code generator that creates a proxy class with corresponding data elements necessary to call your web service. Of course you can hand build a proxy if you really want to go over tedious and boring work.
Maybe start by decompiling System.ServiceModel.ClientBase using .net reflector?
Do some research on ChannelFactory: http://msdn.microsoft.com/en-us/library/system.servicemodel.channelfactory.aspx
Even when implementing my own client by wrapping a ChannelFactory, I am still using the Add Service reference in another project to create the class definitions and move them into the real project. That's a good compromise.
Here's a simple async service interface:
[ServiceContract(Name = "IService")]
public interface IServiceAsync
{
[OperationContract(AsyncPattern = true)]
IAsyncResult BeginGetStuff(string someData, AsyncCallback callback, object state);
IEnumerable<Stuff> EndGetStuff(IAsyncResult result);
}
The .NET contract might look like this:
[ServiceContract]
public interface IService
{
[OperationContract]
IEnumerable<Stuff> GetStuff(string someData);
}
Then in code, assuming you use HTTP, No security and binary message encoding, something like this (Sorry I haven't compiled any of this, just typed it using some of the code I have written for projects):
//Create a binding for the proxy to use
HttpTransportBindingElement httpTransportBindingElement;
httpTransportBindingElement = new HttpTransportBindingElement();
absoluteServiceUri = new Uri(absoluteServiceUri.OriginalString + BinaryEndpointUri, UriKind.Absolute);
}
//Create the message encoding binding element - we'll specify binary encoding
var binaryMessageEncoding = new BinaryMessageEncodingBindingElement();
//Add the binding elements into a Custom Binding
var customBinding = new CustomBinding(binaryMessageEncoding, httpTransportBindingElement);
// Set send timeout
customBinding.SendTimeout = this.SendTimeout;
var factory = new ChannelFactory<IServiceAsync>(customBinding, new EndpointAddress(absoluteServiceUri, new AddressHeader[0]));
var channel = factory.CreateChannel();
channel.BeginGetStuff(Bla, results => { // Do something }, null);
Related
The issue has already discussed here, but it did not addressed it quite the way I am looking for.
I have already created a service reference from a client console app in visual studio, but I want to do it programmatically with the following contraint:
From Microsoft Docs - wcf, it obvious that we have to have the service interface reference available to the client. In my case I do have the reference available, instead I have the address where the service is hosted and this address is a dynamic one.
So I want to define a customized client class that will have its object declared with the host address only. Lets take the following snippet as an example:
public partial class CalculatorServiceClient : System.ServiceModel.ClientBase<ICalculatorService>, ICalculatorService
{
}
As you can see that ICalculatorService is available while defining the class. What to do if the interface to the service is not available while defining the class.
You can connect to the WCF service pragmatically without having to use the generated class methods, but note that this can have issues if the service changes in future
The idea is simple .
Create a service contract that matches your service implementation
[DataContract]
public class SomeDataContarctClass
{
[DataMember]
public string SomeMember{get;set;}
etc....
}
Create the interface
public IServiceInterface
{
[OperationContract]
List<SomeDataContarctClass> GetSomeData();
...etc
}
Now this is where you start to Glue things together,
Then create the service
public IServiceInterface CreateIService()
{
EndpointAddress myEndpoint = new EndpointAddress("SERVICE URL");
BasicHttpBinding binding= new BasicHttpBinding();
defaultBinding.MaxReceivedMessageSize = 2147483647;
defaultBinding.MaxBufferPoolSize = 2147483647;
defaultBinding.MaxBufferSize = 2147483647;
defaultBinding.ReaderQuotas.MaxArrayLength = 2147483647;
defaultBinding.ReaderQuotas.MaxStringContentLength = 2147483647;
ChannelFactory<IUpdaterService> myChannelFactory = new ChannelFactory<IServiceInterface>(binding, myEndpoint);
myChannelFactory.Endpoint.EndpointBehaviors.Add(new ServiceInterceptionBehavior());
// Create a channel.
return myChannelFactory.CreateChannel();
}
Then you can call the service using
var myserviceImp = CreateIService();
var data = myserviceImp.GetSomeData();
I have MVC client that invokes a WCF service. The MVC client needs to pass one custom header in httprequest. The MVC client is also using Unity for DI.
I have already gone through SO POST and others links but they are all suggesting to use message inspector and custom behavior(which might be the correct way) but i'm looking for quick and dirty way because this will be temporary solution.
// Unity type Registration
public static void RegisterTypes(IUnityContainer container)
{
container.RegisterType<IDocumentManagementChannel>(new PerRequestLifetimeManager(),
new InjectionFactory(f=> CreateDocumentManagementChannel()));
}
private static IDocumentManagementChannel CreateDocumentManagementChannel()
{
var factory = new ChannelFactory<IDocumentManagementChannel>("BasicHttpEndPoint");
var channel = factory.CreateChannel();
// How do i add HttpHeaders into channel here?
return channel
}
In the code above How do i add custom header after i create a channel?
1- Below code should send the soap header from MVC
string userName = Thread.CurrentPrincipal.Identity.Name;
MessageHeader<string> header = new MessageHeader<string>(userName);
OperationContext.Current.OutgoingMessageHeaders.Add(
header.GetUntypedHeader("String", "System"));
2- And this code should read it on WCF
string loginName = OperationContext.Current.IncomingMessageHeaders.GetHeader<string>("String", "System");
3- As for the channel, i recommend you create your custom System.ServiceModel.ClientBase as follows:
public abstract class UserClientBase<T> : ClientBase<T> where T : class
{
public UserClientBase()
{
string userName = Thread.CurrentPrincipal.Identity.Name;
MessageHeader<string> header = new MessageHeader<string>(userName);
OperationContext.Current.OutgoingMessageHeaders.Add(
header.GetUntypedHeader("String", "System"));
}
}
4- Create a custom client class that inherits from UserClientBase and use the base channel internally to call your IxxService which is the T here.
I decided to open a new question about this matter, maybe expanding this question, not having found a precise answer about the issue anywhere on the Internet.
I want to use protobuf-net to serialize/deserialize messages exchanged between my WCF client and service. The service is self-hosted in a Windows Service. Both client and service are configured programmatically, using a custom binding very similar to wsHttpBinding. Service reference code is generated using "Add Service Reference" option in Visual Studio. The ORM used on the WCF service is EntityFramework 4 and it's code is generated using EF 4.x POCO Generator. More info about my service configuration can be found in a question I started here (that's where I described that my current serializer is DataContractSerialzizer).
I have only tested protobuf-net with one service operation which returns a list of custom DTOs.
Here is the operation (be advised that I just did a copy-paste of my code to here, there might be some fields named in my domestic language, not English):
public static List<OsobaView> GetListOsobas()
{
Database DB = new Database(); // EF object context
var retValue = DB.Baza.Osoba
.Select(x => new OsobaView
{
ID = x.ID,
Prezime = x.Prezime,
Ime = x.Ime,
Adresa = x.Adresa,
DatumRodjenja = x.DatumRodjenja,
JMBG = x.JMBG
});
return retValue.ToList();
}
Here is the definition of OsobaView class:
[ProtoContract]
public class OsobaView
{
[ProtoMember(1)]
public int ID;
[ProtoMember(2)]
public string Prezime;
[ProtoMember(3)]
public string Ime;
[ProtoMember(4)]
public string Adresa;
[ProtoMember(5)]
public DateTime DatumRodjenja;
[ProtoMember(6)]
public string JMBG;
}
As I am using "Add Service Reference" to generate the reference code, I had to use one of the two work-arounds in order to have my client recognize ProtoContracts and members:
using a shared assembly for DTOs (which is not an ideal solution in my case except for custom DTOs, due to the fact that I pass EF-generated POCOs to the client)
using ProtoPartialMember approach
I used both of them and I used both v1 and v2 of protobuf-net, all solutions yielded similar results which led me to believe my client is not deserializing at all. Read on.
Let's consider cases where I used the ProtoPartialMember approach. At first I used v2. I love the way ProtoOperationBehavior can be used. Here is the service operation to be invoked:
[ProtoBuf.ServiceModel.ProtoBehavior]
public List<OsobaView> GetListOsobas()
{
return OsobaQueries.GetListOsobas();
}
Here is how I replaced DataContractSerializerOperationBehavior with ProtoOperationBehavior for the needed service operation on client side:
OperationDescription op = Service.Proxy.Endpoint.Contract.Operations.Find("GetListOsobas");
if (op != null)
{
DataContractSerializerOperationBehavior dcsBehavior = op.Behaviors.Find<DataContractSerializerOperationBehavior>();
if (dcsBehavior != null)
op.Behaviors.Remove(dcsBehavior);
op.Behaviors.Add(new ProtoBuf.ServiceModel.ProtoOperationBehavior(op));
}
And of course, here is the above mentioned work-around implementation for DTO:
[ProtoPartialMember(1, "ID")]
[ProtoPartialMember(2, "Prezime")]
[ProtoPartialMember(3, "Ime")]
[ProtoPartialMember(4, "Adresa")]
[ProtoPartialMember(5, "DatumRodjenja")]
[ProtoPartialMember(6, "JMBG")]
[ProtoContract]
public partial class OsobaView
{
}
Now when I call this service operation from my client, I get null. But Fiddler disagrees. It clearly says, in response header:
Content-Length: 1301963
Content-Type: application/soap+xml; charset=utf-8
...and in the message body:
<s:Body>
<GetListOsobasResponse xmlns="http://tempuri.org/">
<proto>CkMIpHES .../* REALLY LONG RESPONSE */... IyMDAxOA==</proto>
</GetListOsobasResponse>
</s:Body>
Then I thought, let's try with v1. On the service side, I haven't changed much. I just removed the reference to v2 .DLL and replaced it with a reference to v1 .DLL. On the client side, I had to remove the code to add ProtoOperationBehavior to my service operation behaviors and added the following line instead:
Service.Proxy.Endpoint.Behaviors
.Add(new ProtoBuf.ServiceModel.ProtoEndpointBehavior());
I fired it up, invoked the operation, and this time the result is not null. This time it is a list of blank fields. Again, Fiddler couldn't agree because it again said the same what it said before. The same content length and the same message body.
What's going on here?
P.S. If it's worth anything, here is the WCF configuration:
CustomBinding customBinding = new CustomBinding();
customBinding.CloseTimeout = TimeSpan.FromMinutes(10);
customBinding.OpenTimeout = TimeSpan.FromMinutes(10);
customBinding.ReceiveTimeout = TimeSpan.FromMinutes(10);
customBinding.SendTimeout = TimeSpan.FromMinutes(10);
HttpsTransportBindingElement httpsBindingElement = new HttpsTransportBindingElement();
httpsBindingElement.AllowCookies = false;
httpsBindingElement.BypassProxyOnLocal = false;
httpsBindingElement.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
httpsBindingElement.MaxBufferPoolSize = 20480000;
httpsBindingElement.MaxBufferSize = 20480000;
httpsBindingElement.MaxReceivedMessageSize = 20480000;
httpsBindingElement.RequireClientCertificate = true;
httpsBindingElement.UseDefaultWebProxy = true;
TransportSecurityBindingElement transportSecurityElement = new TransportSecurityBindingElement();
transportSecurityElement.EndpointSupportingTokenParameters.SignedEncrypted.Add(new UserNameSecurityTokenParameters());
transportSecurityElement.EndpointSupportingTokenParameters.SetKeyDerivation(false);
TransactionFlowBindingElement transactionFlowElement = new TransactionFlowBindingElement();
TextMessageEncodingBindingElement textMessageEncoding = new TextMessageEncodingBindingElement();
textMessageEncoding.MaxReadPoolSize = 20480000;
textMessageEncoding.MaxWritePoolSize = 20480000;
textMessageEncoding.ReaderQuotas = XmlDictionaryReaderQuotas.Max;
ReliableSessionBindingElement reliableSessionElement = new ReliableSessionBindingElement();
reliableSessionElement.ReliableMessagingVersion = ReliableMessagingVersion.WSReliableMessagingFebruary2005;
customBinding.Elements.Add(transportSecurityElement);
customBinding.Elements.Add(transactionFlowElement);
customBinding.Elements.Add(textMessageEncoding);
customBinding.Elements.Add(reliableSessionElement);
customBinding.Elements.Add(httpsBindingElement);
EndpointAddress endpoint = new EndpointAddress(new Uri(ServiceAddress));
Service.Proxy = new BazaService.BazaClient(customBinding, endpoint);
Service.Proxy.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindBySubjectName, CertificateSubject);
CustomBehavior behavior = Service.Proxy.Endpoint.Behaviors.Find<CustomBehavior>();
if (behavior == null)
{
Service.Proxy.Endpoint.Behaviors.Add(new CustomBehavior()); // message inspector
}
Service.Proxy.Endpoint.Contract.Behaviors.Add(new CyclicReferencesAwareContractBehavior(true));
Service.Proxy.Endpoint.Behaviors.Add(new ProtoBuf.ServiceModel.ProtoEndpointBehavior());
/* code used for protobuf-net v2
OperationDescription op = Service.Proxy.Endpoint.Contract.Operations.Find("GetListOsobas");
if (op != null)
{
DataContractSerializerOperationBehavior dcsBehavior = op.Behaviors.Find<DataContractSerializerOperationBehavior>();
if (dcsBehavior != null)
op.Behaviors.Remove(dcsBehavior);
op.Behaviors.Add(new ProtoBuf.ServiceModel.ProtoOperationBehavior(op));
} */
Service.Proxy.ClientCredentials.UserName.UserName = LogOn.UserName;
Service.Proxy.ClientCredentials.UserName.Password = LogOn.Password;
Service.Proxy.Open();
EDIT
To provide even more information, I have read what's written there but it didn't help. I have deleted the service reference generated by Visual Studio and created my own, sharing the whole service contract, but nothing has changed.
After concentrating a bit better, I decided to restart the solution from scratch. I created one class library for the EDMX with it's POCOs, one for ServiceContract and DataContracts and one for the actual WCF service implementation. Then I shared those two libraries containing ServiceContract and DataContracts, and POCOs with the WCF client and tried again, which yielded the same results as before. After trying some other operations which didn't use protobuf-net for serialization, turned out they behaved the same as the first one, resulting in empty fields (!).
The thing was that, I screwed my WCF client's .datasource files while refactoring after I decided to use the assembly sharing technique. So this was a typical PEBKAC, it of course works fine when done properly. Great work with protobuf-net, Marc Gravell!
The problem I am having connecting a wcf client application to a host running on a separate machine is documented in a question previously asked:
WCF: Why does passing in a remote endpoint fail?
However, the solution provided here says you need to use a SpnEndpointIdentity with an empty string. Since my code doesn't look anything like the case in the example I have referenced, I need to know what to do with the SpnEndpointIdentity object I have created.
I have a ChannelFactory upon which I call Create channel, passing in an EndpointAddress:
public override void InitialiseChannel()
{
SpnEndpointIdentity spnEndpointIdentity = new SpnEndpointIdentity("");
var address = new EndpointAddress(EndpointName);
Proxy = ChannelFactory.CreateChannel(address);
}
(NB: ChannelFactory is of type IChannelFactory, where T is the service contract interface)
So what do I do with spnEndpointIdentity? I can't pass it to CreateChannel.
Or perhaps I can use it somehow when I create the channel factory:
private ChannelFactory<T> CreateChannelFactory()
{
var binding = new NetTcpBinding
{
ReaderQuotas = { MaxArrayLength = 2147483647 },
MaxReceivedMessageSize = 2147483647
};
SpnEndpointIdentity spnEndpointIdentity = new SpnEndpointIdentity("");
var channelFactory = new ChannelFactory<T>(binding);
return channelFactory;
}
Again, I can't pass it into the constructor, so what do I do with it?
Thanks.
You almiost got it.
What you're missing is that you associate the EndpointIdentity with the EndpointAddress, and then provide that to CreateChannel():
SpnEndpointIdentity spnEndpointIdentity = new SpnEndpointIdentity("");
var address = new EndpointAddress(EndpointName, spnEndpointIdentity);
How can I implement one way WCF operations?
I just tried using IsOneWay attribute as:
[OperationContract(IsOneWay=true)]
void MethodName(string param1, int param2)
Is there any other change I need to make or any specific change in app.config?
FYI, my WCF service implements netTcpBinding, though I think that shouldn't make any difference.
As shown, your code looks ok. There should be no problem with doing one-way calls with netTcpBinding.
If you're interested, chapter 5 in Juval Lowy's awesome Programming WCF Services 2nd Edition contains a good bit of information about one-way services.
From what you've shown, so far though I don't see anything wrong. Please give us some more details.
We had a problem with one-way calls not returning immediately using the NetTcpBinding. This blog post identifies the problem and provides a solution.
http://blogs.msdn.com/b/distributedservices/archive/2009/02/12/client-proxy-close-method-call-does-not-finish-immediately-in-one-way-wcf-calls.aspx
From the article:
Problem: Clients calling a one-way method in WCF Service and then close method on proxy does not return until the call is actually finished or call times out. Ever wonder why this happens?
Cause: When you specify “One-Way” on your interface, the underlying channel operation is still two-way since the one way binding element is not in the channel stack. Thus, the close operation gets blocked until the one way operation completes.
This is by design and the development team is working to change it in future versions of .Net framework.
...
Solution (Work around):
Layer the OneWayBindingElement on top of netTcpBinding as shown in the below code. This way, close call on proxy will return immediately and eventually the one-way call will return in fire and forget fashion.
[ServiceContract]
public interface IService1
{
[OperationContract(IsOneWay = true)]
void SetData(int value);
}
public class Service1 : IService1
{
public void SetData(int value)
{
//Application specific code
}
}
Service Host code:
Form1ServiceHost = new ServiceHost(this, new Uri("net.tcp://localhost:8091/WindowsFormApp/Form1/"), new Uri("http://localhost:8090/WindowsFormApp/Form1/"));
Binding binding = new NetTcpBinding();
BindingElementCollection oldBindingElements = binding.CreateBindingElements();
BindingElementCollection bindingElements = new BindingElementCollection();
bindingElements.Add(new OneWayBindingElement());
foreach (BindingElement bindingElement in oldBindingElements)
{
bindingElements.Add(bindingElement);
}
binding = new CustomBinding(bindingElements);
Form1ServiceHost.AddServiceEndpoint("WCFServiceLibrary.IService1", binding, "");
Form1ServiceHost.Open();
Client Code:
Binding binding = new NetTcpBinding();
BindingElementCollection oldBindingElements = binding.CreateBindingElements();
BindingElementCollection bindingElements = new BindingElementCollection();
bindingElements.Add(new OneWayBindingElement());
foreach (BindingElement bindingElement in oldBindingElements)
{
bindingElements.Add(bindingElement);
}
binding = new CustomBinding(bindingElements);
Service1Client client = new Service1Client(binding, new EndpointAddress("net.tcp://localhost:8091/WindowsFormApp/Form1/"));
client.SetData(10);
Console.WriteLine("set data");
Console.WriteLine("Now closing the channel,Before close, current time is {0}", DateTime.Now.ToString() + " " + DateTime.Now.Millisecond.ToString());
client.Close();
Console.WriteLine("Now closing the channel,After close, current time is {0}", DateTime.Now.ToString() + " " + DateTime.Now.Millisecond.ToString());`