Sending custom WCF Message to a service - c#

My goal is to record wcf calls to one IIS hosted wcf service and replay them to a different wcf service. So far I have an IDispatchMessageInspector working following this example, it can log an incoming request and the corresponding reply to disk.
How can I read in a message from disk and then send it to the other service? Is there a way for the client to send a low level Message object to the service without going through the normal client proxy object?

I was able to get it to work by simply creating an IRequestChannel, reading the following helped explain how it works
Using the Message Class
WCF Data Transfer Architecture
WCF Messaging Fundamentals
The code to send the message:
private static void TestDispatchingMessage()
{
var reader = XmlDictionaryReader.CreateBinaryReader(new FileStream(#"path\request_6c6fc02f-45a7-4049-9bab-d6f2fff5cb2d.xml", FileMode.Open), XmlDictionaryReaderQuotas.Max);
var message = Message.CreateMessage(reader, int.MaxValue, MessageVersion.Soap11);
message.Headers.To = new System.Uri(#"url");
BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.None)
{
MessageEncoding = WSMessageEncoding.Mtom,
MaxReceivedMessageSize = int.MaxValue,
SendTimeout = new TimeSpan(1, 0, 0),
ReaderQuotas = { MaxStringContentLength = int.MaxValue, MaxArrayLength = int.MaxValue, MaxDepth = int.MaxValue }
};
var cf = new ChannelFactory<IRequestChannel>(binding, new EndpointAddress(#"url"));
foreach (OperationDescription op in cf.Endpoint.Contract.Operations)
{
op.Behaviors.Remove<DataContractSerializerOperationBehavior>();
op.Behaviors.Add(new ProtoBehaviorAttribute());
}
cf.Open();
var channel = cf.CreateChannel();
channel.Open();
var result = channel.Request(message);
Console.WriteLine(result);
channel.Close();
cf.Close();
}
This is what was in the IDispatchMessageInspector class:
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
var callId = Guid.NewGuid();
var action = request.Headers.Action.Substring(request.Headers.Action.LastIndexOf('/'));
var fileName = string.Format(#"path\{0}_{1}.data", action, callId);
try
{
var buffer = request.CreateBufferedCopy(int.MaxValue);
var writeRequest = buffer.CreateMessage();
using (var stream = new FileStream(fileName, FileMode.CreateNew))
{
using (var writer = XmlDictionaryWriter.CreateBinaryWriter(stream))
{
writeRequest.WriteMessage(writer);
writer.Flush();
}
}
request = buffer.CreateMessage();
buffer.Close();
}
catch (Exception ex)
{
Log.ErrorException("Error writing", ex);
}
Log.Info("Call {0}", callId);
return callId;
}

Yes, sending raw messages should be easy if you work at the communication protocol level. Here's one of my old examples.

Related

Sending request to PKI Web service in .NET 6

I am trying to establish connection to external PKI SOAP web service, but not sure how to set BasicHttpBinding security in .NET 6. Constantly getting exception:
*System.ServiceModel.ProtocolException: 'The header 'Security' from the namespace 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' was not understood by the recipient of this message, causing the message to not be processed. This error typically indicates that the sender of this message has enabled a communication protocol that the receiver cannot process. Please ensure that the configuration of the client's binding is consistent with the service's binding. '
*
I am using auto generated class from wsdl, but create my own binding.
BasicHttpBinding:
public BasicHttpBinding GetCustomBinding()
{
BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport)
{
Security =
{
Message =
{
ClientCredentialType = BasicHttpMessageCredentialType.Certificate
},
Transport =
{
ClientCredentialType = HttpClientCredentialType.Certificate
},
Mode = BasicHttpSecurityMode.Transport
},
MaxReceivedMessageSize = MaxMessageSizeBytes
};
return binding;
}
Creating proxy client:
public autoGeneratedClient GetClient(string endpointUrl, string dnsIdentity, string clientCertificatePath, string clientCertificatePassword, string serviceCertificatePath, int timeout = 10)
{
DnsEndpointIdentity endpointIdentity = new DnsEndpointIdentity(dnsIdentity);
EndpointAddress endpointAddress = new EndpointAddress(new Uri(endpointUrl), endpointIdentity);
//CustomBinding for eBox web service with security setup
MyCustomBinding myCustomBinding = new MyCustomBinding();
Binding binding = myCustomBinding.GetCustomBinding();
binding.CloseTimeout = new TimeSpan(0, timeout, 0);
binding.ReceiveTimeout = new TimeSpan(0, timeout, 0);
binding.SendTimeout = new TimeSpan(0, timeout, 0);
binding.OpenTimeout = new TimeSpan(0, timeout, 0);
autoGeneratedClient client = new autoGeneratedClient(binding, endpointAddress);
client.ClientCredentials.ClientCertificate.Certificate = X509CertificateFactory.GetClientCertificate(clientCertificatePath, clientCertificatePassword);
client.ClientCredentials.ServiceCertificate.DefaultCertificate = X509CertificateFactory.GetServiceCertificate(serviceCertificatePath);
client.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
return client;
}

Getting a 400 Bad Request when trying to connect to an ONVIF camera

I have a Xamarin Android app that has a feature for taking snapshots of external cameras. Until now we were using some models that provided us access from HTTP with CGI for this.
However, these models were discontinuated and we are forced to change for models that provide ONVIF protocol.
I created an additional ClassLibrary project in my solution (once it is not possible to add Services References directly in Xamarin Android projects) to handle this function. And in this project I added a Service Reference to ONVIF wsdl (http://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl).
So, I created the following function:
public string GetSnapshotUri(string cameraIPAddress, string username, string password)
{
try
{
var messageElement = new TextMessageEncodingBindingElement()
{
MessageVersion = MessageVersion.CreateVersion(EnvelopeVersion.Soap12, AddressingVersion.None)
};
HttpTransportBindingElement httpBinding = new HttpTransportBindingElement()
{
AuthenticationScheme = AuthenticationSchemes.Basic
};
CustomBinding bind = new CustomBinding(messageElement, httpBinding);
var mediaClient = new MediaClient(bind, new EndpointAddress($"http://{ cameraIPAddress }/onvif/Media"));
mediaClient.ClientCredentials.UserName.UserName = username;
mediaClient.ClientCredentials.UserName.Password = password;
Profile[] profiles = mediaClient.GetProfiles();
string profileToken = profiles[0].token;
MediaUri mediaUri = mediaClient.GetSnapshotUri(profileToken);
return mediaUri.Uri;
}
catch (WebException ex)
{
return ex.Message;
}
catch (Exception ex)
{
return ex.Message;
}
}
But when the function is called and the mediaClient.GetProfiles() method is reached, an error is thrown:
**
System.Net.WebException: 'There was an error on processing web
request: Status code 400(BadRequest): Bad Request'
**
see error message
I've tried to search for any related problem, but everything I've tried didn't work.
Any suggestions?
Link related: ONVIF api capture image in C#
Thanks!
After a long time, I finally had success.
Here is the final solution:
public Byte[] GetSnapshotBytes(string ip, string user, string password)
{
try
{
if (string.IsNullOrEmpty(ip)) return null;
var snapshotUri = string.Format("http://{0}:80/onvifsnapshot/media_service/snapshot?channel=1&subtype=0", ip);
NetworkCredential myCredentials = new NetworkCredential(user, password);
WebRequest myWebRequest = WebRequest.Create(snapshotUri);
myWebRequest.Credentials = myCredentials.GetCredential(new Uri(snapshotUri), "");
using (HttpWebResponse lxResponse = (HttpWebResponse)myWebRequest.GetResponse())
{
using (BinaryReader reader = new BinaryReader(lxResponse.GetResponseStream()))
{
Byte[] lnByte = reader.ReadBytes(1 * 1024 * 1024 * 10);
return lnByte;
}
}
}
catch (Exception)
{
throw;
}
}

Google Document AI - Invalid argument

I am very new in google Document AI, I tried to use this code but still have this response. Do you have any idea what I'm doing wrong?
I have installed from nuget Google.Cloud.DocumentAI.V1
Status(StatusCode="InvalidArgument", Detail="Request contains an invalid argument.", DebugException="Grpc.Core.Internal.CoreErrorDetailException: {"created":"#1643889903.765000000","description":"Error received from peer ipv4:142.250.186.42:443","file":"......\src\core\lib\surface\call.cc","file_line":1067,"grpc_message":"Request contains an invalid argument.","grpc_status":3}")
public async void Start()
{
Environment.SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", #"path-to-json");
try
{
//Generate a document
string pdfFilePath = #"path-to-invoice-pdf";
var bytes = File.ReadAllBytes(pdfFilePath);
ByteString content = ByteString.CopyFrom(bytes);
// Create client
DocumentProcessorServiceClient documentProcessorServiceClient = await DocumentProcessorServiceClient.CreateAsync();
// Initialize request argument(s)
ProcessRequest request = new ProcessRequest
{
ProcessorName = ProcessorName.FromProjectLocationProcessor("ProjectID", "eu", "ProcessorID"),
SkipHumanReview = false,
RawDocument = new RawDocument
{
MimeType = "application/pdf",
Content = content
}
};
// Make the request
ProcessResponse response = await documentProcessorServiceClient.ProcessDocumentAsync(request);
Document docResponse = response.Document;
Console.WriteLine(docResponse.Text);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Quoted from this doc:
Note that if you wish to use DocumentProcessorServiceClient other than in the US, you must specify the endpoint when you construct the client. The endpoint is of the form {location}-documentai.googleapis.com, e.g. eu-documentai.googleapis.com. The simplest way to specify the endpoint is to use DocumentProcessorServiceClientBuilder:
DocumentProcessorServiceClient client = new DocumentProcessorServiceClientBuilder
{
Endpoint = "eu-documentai.googleapis.com"
}.Build();

TcpClient.BeginRead works once

This is my client side code. The package is sent and received only once. In this code, I use two TCP and UDP protocols simultaneously on one port. My UDP protocol works very well but my TCP protocol has a problem and only sends and receives packets once.
Thanks in advance for your guidance.
This is my code:
private void TryClientConnectToServerCallback(IAsyncResult result)
{
m_TcpClient.EndConnect(result);
if (!m_TcpClient.Connected)
{
return;
}
else
{
m_MyStream = m_TcpClient.GetStream();
m_MyStream.BeginRead(m_ReceiveBuffer, 0, 4096 * 2,new AsyncCallback(ReceiveDataCallBackTcp), null);
//m_TcpClient.Client.BeginSend(m_ReceiveBuffer, 0, 4096 * 2, SocketFlags.None, ReceiveDataCallBackUdp, null);
m_UdpClient.BeginReceive(ReceiveDataCallBackUdp, null);
print(m_UdpClient.Client.Connected);
}
}
private void ReceiveDataCallBackTcp(IAsyncResult result)
{
try
{
print("Data Received");
int m_ReadBytes = m_MyStream.EndRead(result);
if (m_ReadBytes <= 0)
{
return; //no take any data from client
}
jsonObject = new JsonObject();
byte[] m_NewByte = new byte[m_ReadBytes];
Buffer.BlockCopy(m_ReceiveBuffer, 0, m_NewByte, 0, m_ReadBytes);
string jsonData = DataConverter.ConvertToString(m_ReceiveBuffer);
SendAndReceiveSizeDataLogger(false, m_NewByte, "TCP");
//Console.WriteLine("Data receive form client {0}", jsonData);
jsonObject = JsonConvert.DeserializeObject<JsonObject>(jsonData);
Debug.Log(jsonObject.FunctionName);
if (m_Events.ContainsKey(jsonObject.FunctionName))
{
for (int i = 0; i < m_Events[jsonObject.FunctionName].Count; i++)
{
m_Events[jsonObject.FunctionName][i](jsonObject);
}
}
m_MyStream.BeginRead(m_ReceiveBuffer, 0, 4096 * 2, new AsyncCallback(ReceiveDataCallBackTcp), null);
//m_TcpClient.Client.BeginSend(m_ReceiveBuffer, 0, 4096 * 2, SocketFlags.None, ReceiveDataCallBackUdp, null);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
this code send data for client
public void SendTCP(JsonObject jsonObject)
{
if (!m_Socket.Connected)
{
Console.WriteLine("Failed to send data to server, you are not connected to the server");
return;
}
//convert jsonObject class to jsonObject for send to server
string jsonData = JsonConvert.SerializeObject(jsonObject);
//Console.WriteLine("Data Serialized " + jsonData);
byte[] data = new byte[4096];
//convert data to byte array for send to server
data = DataConverter.ConvertToByteArray(jsonData);
Console.WriteLine("Sent Method " + m_Socket.Client.RemoteEndPoint.ToString());
m_MyStream.BeginWrite(data, 0, jsonData.Length, SendTCPCallBack, null);
#region Data Property
int dataSendSize = 0;
for (int i = 0; i < data.Length; i++)
{
if (data[i] > 0)
{
dataSendSize++;
}
}
//Console.WriteLine("Data size send to client : " + dataSendSize);
#endregion
}
It's unclear what this code is trying to do, but it does have several problems. It's clear it's trying to asynchronously receive and deserialize data from a UDP or TCP client, but it's doing this in a very complicated way.
The only clear thing is that it's using Json.NET, which can't deserialize objects asynchronously.
Simply reading and deserializing a UPD message needs just a few lines :
var result=await udpClient.ReceiveAsync();
var jsonData=Encoding.UTF8.GetString(result.Buffer);
var jsonObject = JsonConvert.DeserializeObject<JsonObject>(jsonData);
TCP doesn't have messages though, it's a continuous stream of bytes. If a connection is only meant to retrieve a single document, we could just read until the stream closes and deserialize the data :
await tcpClient.ConnectAsync(...);
using var stream=tcpClient.GetStream();
//Uses UTF8 by default
using var sr=new StreamReader(stream);
var serializer = new JsonSerializer();
var jsonObject=serializer.Deserialize<JsonObject>(jsonData);
If the server sends multiple JSON documents, we'd need a way to identify them, and deserialize them one by one. A very common way to send multiple documents is to use unindented, single line documents and separate them with a newline, eg:
{"EventId":123,"Category":"Business","Level":"Information"}
{"EventId":123,"Category":"Business","Level":"Information"}
{"EventId":123,"Category":"Business","Level":"Information"}
In this case we can use a StreamReader to read the strings one line at a time and deserialize them:
await tcpClient.ConnectAsync(...);
using var stream=tcpClient.GetStream();
//Uses UTF8 by default
using var sr=new StreamReader(stream);
while(true)
{
var line=await sr.ReadLineAsync();
var jsonObject=JsonConvert.DeserializeObject<JsonObject>(jsonData);
...
}
Full Async with System.Text.Json
.NET Core 3.1 introduced System.Text.Json which is fully asynchronous. The TCP code can be rewritten to use JsonSerializer.DeserializeAsync :
await tcpClient.ConnectAsync(...);
using var utf8Stream=tcpClient.GetStream();
var jsonObject=await JsonSerializer.DeserializeAsync<JsonObject>(utf8Stream);
System.Text.Json works only with UTF-8, which is after all the de-facto standard encoding for all web applications.
No DeserializeAsync overload accepts a string, because there's no IO involved when the string is already in memory. The UDP or streaming JSON code would look similar to JSON.NET :
await tcpClient.ConnectAsync(...);
using var stream=tcpClient.GetStream();
//Uses UTF8 by default
using var sr=new StreamReader(stream);
while(true)
{
var line=await sr.ReadLineAsync();
var jsonObject=JsonSerializer.Deserialize<JsonObject>(jsonData);
...
}

WCF UDP discovery only works for BasicHttpBinding not for NetTcpBinding

I have a WCF service that has announced two service endpoints. One is for net.tcp and one is for http.
net.tcp://localhost:11110/MyService
http://localhost:11111/MyService
// Make service discoverable
var discoveryBehavior = new ServiceDiscoveryBehavior();
var announceEndpointAddress = ConfigurationManager.AppSettings["AnnouncementEndpoint"];
var netTcpBinding = new NetTcpBinding(SecurityMode.None)
{
ReceiveTimeout = TimeSpan.MaxValue,
SendTimeout = TimeSpan.MaxValue,
OpenTimeout =TimeSpan.MaxValue,
CloseTimeout = TimeSpan.MaxValue
};
discoveryBehavior.AnnouncementEndpoints.Add(
new AnnouncementEndpoint(netTcpBinding,
new EndpointAddress(announceEndpointAddress)
));
_serviceHost.Description.Behaviors.Add(discoveryBehavior);
_serviceHost.AddServiceEndpoint(new UdpDiscoveryEndpoint());
On the client side, I am using WCF Discovery.
class Program
{
static void Main(string[] args)
{
var optionalBindings = new Binding[]
{
new NetTcpBinding(),
new BasicHttpBinding(),
new NetNamedPipeBinding()
};
var executed = false;
foreach (var binding in optionalBindings)
{
var contract = ContractDescription.GetContract(typeof(IMyService));
var endpoint = new DynamicEndpoint(contract, binding);
var factory = new ChannelFactory<IMyService>(endpoint);
var proxy = factory.CreateChannel();
try
{
var result = proxy.GetData(123456);
((ICommunicationObject) proxy).Close();
executed = true;
break;
}
catch (EndpointNotFoundException)
{
// Ignored
}
}
}
}
What confuses me is that it only works with BasicHttpBinding and never works with NetTcpBinding. So if BasicHttpBinding from optionalBindings list, i am not able to call the service although net.tcp endpoint is there.
Can anyone explain the reason for it?

Categories

Resources