Edit WCF message before it is sent to server - c#

We recently had to upgrade our Win 8.1 store app to Win 10. Part of that change was modifying our NetTcpBindings to instead be BasicHttpBindings for file uploads since UWP does not currently support NetTcpBindings. Our issue is that when the client calls the UploadFileMethod on the proxy class, we intercept the message before it is sent to the server so we can apply headers that are used later as follows:
public async Task UploadFileAsync(RemoteFileInfo request)
{
using (new OperationContextScope(this.InnerChannel))
{
string nameSpace = #"http://tempuri.org";
OperationContext.Current.OutgoingMessageHeaders.Add(MessageHeader.CreateHeader("FileName", nameSpace, request.FileName));
OperationContext.Current.OutgoingMessageHeaders.Add(MessageHeader.CreateHeader("Length", nameSpace,
request.Length));
await Channel.UploadFileAsync(request);
}
}
This used to work fine when we were using NetTcpBinding but since we switched to BasicHttpBinding that code is now throwing an exception on the line:
await Channel.UploadFileAsync(request);
With the exception reading:
This message cannot support the operation because it has been written.
After reading up on this exception it appears that we cannot mess with the request object at all before it is sent to the server when using BasicHttpBinding. If that is the case, how can we add OutgoingMessageHeaders to the message using properties of the request itself?
Edit: The proxy class is created as follows:
var imageProxy = new RTMImageServiceProxy(globalContext.Win10UploadBinding,
globalContext.ImageEndpointAddress);
Where Win10UploadBinding is configured as so:
BasicHttpBinding win10BasicBinding = new BasicHttpBinding();
win10BasicBinding.Security.Mode = BasicHttpSecurityMode.None;
win10BasicBinding.TransferMode = TransferMode.Streamed;
win10BasicBinding.SendTimeout = new TimeSpan(0, 0, 2, 0);
win10BasicBinding.MaxReceivedMessageSize = 2147483647;
this.win10UploadBinding = win10BasicBinding;
and globalContext is just a static class I used to store commonly-used variables.

Apparently it turns out that once written cannot be altered so create a copy with adjusted headers. Equivalent issue was brought up here.
Anyway, I encourage you to create custom message inspector: class deriving IClientMessageInspector, as far as client is concerned. It provides separation between method being invoced and headers being adjusted.

Related

How to make GrpcChannel use HTTP/2 on MonoAndroid?

I am trying to make request to my server by MagicOnion protocol (it uses transport from gRPC, but deffrent serialization protocol, message pack instead of protobuf).
An simple test client app, working under net5.0 is executing code like this:
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
var address = $"http://{ServerUrl.Host}:5002";
using var channel = GrpcChannel.ForAddress(address);
var myServiceClient = MagicOnionClient.Create<IMyService>(channel);
var result = await myServiceClient.GetMyData();
...and recieves response succesfully. But if I try to execute the same code on Android app, I am seeing this exception message on server logs:
Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2ConnectionErrorException: HTTP/2 connection error (PROTOCOL_ERROR): Invalid HTTP/2 connection preface.
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2Connection.ParsePreface(ReadOnlySequence`1& buffer, SequencePosition& consumed, SequencePosition& examined)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2Connection.TryReadPrefaceAsync()
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2Connection.ProcessRequestsAsync[TContext](IHttpApplication`1 application)
With logs and traffic dump I can see that the client on .Net 5 uses HTTP/2, but on Android - HTTP/1.1. As I can see, this is the only deifference between requests.
So, how can I make Android (API30, monoandroid10.0) client use HTTP/2?
The resolution is to use another gRPCC implementation lib - Grpc.Core. It provides GrpcChannel class wich is compatible with MagicOnion.
In my case, the library didn't work immediately, throwing the error about libgrpc_csharp_ext loading trouble. To solve this, you also have to add pacakge Grpc.Core.Xamarin.
The usage example:
var channel = new Grpc.Core.Channel(ServerUrl.Host, 5002, Grpc.Core.ChannelCredentials.Insecure);
var myServiceClient = MagicOnionClient.Create<IMyService>(channel);
var result = await myServiceClient.GetMyData();

Choosing between accessing WCF via API Endpoint or via Dll reference

I have been contemplating on a dilemma for hours. I have a Visual Studio Solution that contains a WCF, WebForms, UWP, Xamarin and a SharedLibrary Projects.
I intend to use the WCF project as the backend which talks to the database and process Email and SMS integration and feed the other apps.
OPTION A
Currently, The WCF is hosted on an Azure App Service which makes it accessible via POST, GET, etc from the url which is: https://mywcfprojectlink.azurewebsites.net/service1.svc/GetUsers
With such arrangements, I can perform a POST request to get data from the apps:
string response = string.Empty;
string url = "https://mywcfprojectlink.azurewebsites.net/service1.svc/GetUsers";
try
{
var values = new Dictionary<string, string>
{
{ "data", Encryption.EncryptString(dat.ToString()) } //dat is incoming method param
};
string jsonString = JsonConvert.SerializeObject(values);
var cli = new WebClient();
cli.Headers[HttpRequestHeader.ContentType] = "application/json";
response = cli.UploadString($"{url}", jsonString);
var result = JsonConvert.DeserializeObject<string>(response);
topic.InnerText = Encryption.DecryptString(result.ToString());
}
catch (Exception)
{
return string.Empty;
}
The method above is a simple one as I have other ones where I Deserialize with Models/Classes.
OPTION B
I equally have access to the methods defined in service1 by adding the project reference to my WebForms which surprisingly is also compatible with xamarin but not with UWP. Nevertheless, I am interested in the WebForms scenario. Below is an example method:
using BackEnd;
//Service1 service1 = new Service1();
//var send = service1.GetUsers(dat.ToString()); //dat is incoming method param
//topic.InnerText = send;
Obviously, using the Option B would eliminate the need to encrypt, decrypt, serialize or deserialize the data being sent. However, I have serious performance concerns.
I need to know the better option and if there is yet another alternative (probably an Azure Resource), you can share with me.
If you decide to use https endpoint of the Azure website, option A is secure because of SSL encryption. So you don't have to encrypt/decrypt it by yourself. The only tip is to create a proper authorization mechanism. For example use TransportWithMessageCredential. An example is provided in below article https://www.codeproject.com/Articles/1092557/WCF-Security-and-Authentication-in-Azure-WsHttpBin

Correcty handle Expectation failed (417) and change Expect100Continue in WCF client app

My application is a C# Windows service that consumes a WCF service. When the first "Expectation failed (417)" error occurs, I change both ServicePointManager.Expect100Continue and ServicePoint.Expect100Continue to false:
try
{
//ServicePointManager.Expect100Continue = false; // If uncomment all work
var svc = new ServiceClient();
svc.GetData(); // first error
}
catch (ProtocolException pex)
{
if (pex.Message.Contains("(417)"))
{
ServicePointManager.Expect100Continue = false;
var sp = ServicePointManager.FindServicePoint(new Uri(#"http://addr.to.service/service.svc"));
sp.Expect100Continue = false;
var svc = new ServiceClient();
svc.GetData(); // second same error
}
}
However, the second call to the service also fails. But if I set Expect100Continue to false before any connection, communication with the service works correctly.
Is this way correctly to handle Expect100Continue errors? I need the application adapts automatically without user action. What am I forgetting to do this work?
Most of the settings on ServicePointManager are treated as the default values applied on all NEW ServicePoints that are created after that point in the application's life. In the case where you change the setting after seeing the error, you are not actually changing anything on existing ServicePoint instances, including the instance associated with the connection used by WCF in this case.
In your Sample code you are calling ServicePointManager.FindServicePoint to try to find the correct ServicePoint. However, FindServicePoint has several overloads and it is easy to use that API incorrectly. For instance, FindServicePoint will try to take into account things http/https, the host you are connecting to, your proxy configuration, etc. If you are not providing the correct parameters to FindServicePoint, you can easily end up getting the wrong ServicePoint returned to you and your settings will not be applied to the ServicePoint you intended to change.
I would recommend that you use the FindServicePoint overload that takes an IWebProxy object to ensure that you get the right ServicePoint. In most cases, you should be able to pass in WebRequest.DefaultWebProxy as the IWebProxy object.
From the MSDN documentation of ServicePointManager.Expect100Continue,
Changing the value of this property does not affect existing ServicePoint objects. Only new ServicePoint objects created after the change are affected. Therefore changing the value on the existing WCF client will have no effect. You need to create a new WCF client, then call GetData()

How to increase the file size limit in WCF console application

I've having a very simple WCF service (a console application for file upload). I keep getting error (400) bad request. It works when I upload small files (4kb) but failing for 700kb.
From the readings I've done from stack overflow and other, I'll have to increase the MaxReceivedMessageSize. This was implemented using a custom class and overriding the OnOpening method but it still didn't work.
I'm testing with a console application using the webclient
outputBytes = webClient.UploadData(baseUrl + "/uploads/2." + filenameOnly, File.ReadAllBytes(filename));
Also, I'm using the WebServiceHost as in
var uri = new Uri("http://localhost:8000");
var svc = new WebServiceHost(typeof (UploadService), uri);
How do I solve this issue?
PS: Application does not have a config file so I'll looking at how to set this in code. If not and a config file is needed, then what should be the content.
Notes:
I found this link Bad Request Error 400 - WCF Client where it was explained that those properties are only valid for soap based services. He suggested updating the web.config. Since this is a console application, I'm wondering how this can be done'
regards.
You can set the maximumreceivedmessage programatically like this:
var binding = new wsHttpBinding(); // or whatever binding you are using
binding.MaxReceivedMessageSize = Int32.MaxValue;
var wcfClient = new WCFServiceTestClient(binding, strServiceURL);
Hopefully this will be enough to solve your problem, but you might also want to consider chopping up the file into bits (batching) before sending it to the server. Sending very large chunks of data can incurr a huge memory penalty as the client has to serialize the entire thing up front before sending it to the server (and then the same to deserialize it on the server side).
Edit: Based on your additional comment (below), the code on the application hosting the service might look something like this:
WebServiceHost webServiceHost = new WebServiceHost(typeof(UploadService), uri);
WebHttpBinding binding = new WebHttpBinding();
binding.MaxReceivedMessageSize = Int32.MaxValue;
webServiceHost.AddServiceEndpoint(typeof(IUploadService), binding, "WebServiceHost");
webServiceHost.Open();
Did you have a look at the documentation?
It covers both code and configuration.

WCF NetTcpBinding - from Microsoft tutorial - getting timeout

I built a project as descripted in this URL:
http://msdn.microsoft.com/en-us/library/ms734784.aspx
I used the app.config version. But using the code-Version does not change anything (the timeout-error still occurs).
To create the ServiceHost I used the following code:
this.serviceHost = new ServiceHost(typeof(Calculator));
// Open the ServiceHostBase to create listeners and start
// listening for messages.
this.serviceHost.Open();
On the client side I used the following code:
ChannelFactory<ICalculator> factory = new ChannelFactory<ICalculator>("netTcp_ICalculator");
ICalculator communicationChannel = this.factory.CreateChannel();
string test = communicationChannel.GetData(5);
On the last line the program waits one minute, then I get a timeout:
This request operation sent to net.tcp://localhost:8008/Calculator did not
receive a reply within the configured timeout (00:01:00).
The time allotted to this operation may have been a portion
of a longer timeout. This may be because the service is still
processing the operation or because the service was unable to
send a reply message. Please consider increasing the operation
timeout (by casting the channel/proxy to IContextChannel and
setting the OperationTimeout property) and ensure that the service
is able to connect to the client.
The class Calculator and the interface exist. Besides this timeout I get no other error. I set a breakpoint at the GetData method, but the breakpoint was not hit.
I have tried to change the portnumber used for the client from 8008 to 8009, but let the endpoint for the server at 8008. I wanted to test if the client tries to reach the server. Then I get the error that the other side is not answering (EndpointNotFoundException).
When changing the client port back to 8008 I get the Timeout error again.
Is there anything wrong with my code?
How can I ensure that the server can reach the client?
Client and server are in the same test application.
Thank you for your help!
EDIT:
I have now deleted the app.config settings. And tried to build the server and client by using the sourcecode. To build the server was no problem. But building the client is a problem.
There is no way to call:
CalculatorClient cc = new CalculatorClient(myBinding, myEndpointAddress);
The compiler does not know CalculatorClient.
Can I use the following instead?
NetTcpBinding myBinding = new NetTcpBinding();
myBinding.Security.Mode = SecurityMode.None;
// Create the address string, or get it from configuration.
string tcpUri = "net.tcp://localhost:8008/Calculator";
// Create an endpoint address with the address.
EndpointAddress myEndpointAddress = new EndpointAddress(tcpUri);
ChannelFactory<ICalculator> factory = new ChannelFactory<ICalculator>(myBinding, myEndpointAddress);
factory.Open();
ICalculator communicationChannel = this.factory.CreateChannel();
string test = communicationChannel.GetData(5);
I get again an exception at the last line :(
SOLVED:
Ok, the problem is solved. I needed to call the WCF host initialization via an own thread:
hostThread = new Thread(this.createService);
hostThread.Start();
Now everything works fine!
Thanks for all your help!
You are not adding any endpoints to the service.
You did not include the part of the example code that adds the service endpoint:
Uri tcpUri = new Uri("net.tcp://localhost:8008/Calculator");
// Create the ServiceHost.
ServiceHost sh = new ServiceHost(typeof(Calculator), tcpUri);
// Create a binding that uses TCP and set the security mode to none.
NetTcpBinding b = new NetTcpBinding();
b.Security.Mode = SecurityMode.None;
// Add an endpoint to the service.
sh.AddServiceEndpoint(typeof(ICalculator), b, "");
// Open the service and wait for calls.
sh.Open();
Edit: Same goes for your client. You have to specify an endpoint addresses
// Create a channel factory.
NetTcpBinding b = new NetTcpBinding();
b.Security.Mode = SecurityMode.None;
Uri tcpUri = new Uri("net.tcp://localhost:8008/Calculator");
ChannelFactory<ICalculator> myChannelFactory = new ChannelFactory<ICalculator>(b,new EndpointAddress(tcpUri));
// Create a channel.
ICalculator calculator = myChannelFactory.CreateChannel();
Edit2: I can't currently test this code... Will give it a try tomorrow morning.
Are you using Windows 7?
If so, you likely need to run Visual Studio as an Administrator. UAC will not let you create the service endpoint unless you are a running as an administrator.

Categories

Resources