/* ========================================================================
* Copyright (c) 2005-2013 The OPC Foundation, Inc. All rights reserved.
*
* OPC Reciprocal Community License ("RCL") Version 1.00
*
* Unless explicitly acquired and licensed from Licensor under another
* license, the contents of this file are subject to the Reciprocal
* Community License ("RCL") Version 1.00, or subsequent versions
* as allowed by the RCL, and You may not copy or use this file in either
* source code or executable form, except in compliance with the terms and
* conditions of the RCL.
*
* All software distributed under the RCL is provided strictly on an
* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* AND LICENSOR HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
* LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RCL for specific
* language governing rights and limitations under the RCL.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/RCL/1.00/
* ======================================================================*/
using System;
using System.Collections.Generic;
using System.Xml;
using System.ServiceModel;
using System.Runtime.Serialization;
using Opc.Ua.Bindings;
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel.Security;
using System.Net;
namespace Opc.Ua
{
///
/// A base class for WCF channel objects used access UA interfaces
///
public abstract class WcfChannelBase : IChannelBase, ITransportChannel
{
#region Constructors
///
/// Initializes the object with the specified binding and endpoint address.
///
public WcfChannelBase()
{
m_messageContext = null;
m_settings = null;
m_wcfBypassChannel = null;
}
#endregion
#region IDisposable Members
///
/// Frees any unmanaged resources.
///
public void Dispose()
{
Dispose(true);
}
///
/// An overrideable version of the Dispose.
///
protected virtual void Dispose(bool disposing)
{
// nothing to do.
}
#endregion
#region IChannelBase Members
///
/// Returns true if the channel uses the UA Binary encoding.
///
public bool UseBinaryEncoding
{
get
{
if (m_settings != null && m_settings.Configuration != null)
{
return m_settings.Configuration.UseBinaryEncoding;
}
return false;
}
}
///
/// Gets the binary encoding support.
///
public BinaryEncodingSupport BinaryEncodingSupport
{
get
{
if (m_settings != null && m_settings.Configuration != null)
{
if (m_settings != null && m_settings.Configuration.UseBinaryEncoding)
{
return BinaryEncodingSupport.Required;
}
return BinaryEncodingSupport.None;
}
return BinaryEncodingSupport.Optional;
}
}
///
/// Opens the channel with the server.
///
public void OpenChannel()
{
ICommunicationObject channel = m_channel as ICommunicationObject;
if (channel != null && channel.State == CommunicationState.Closed)
{
channel.Open();
}
}
///
/// Closes the channel with the server.
///
public void CloseChannel()
{
ICommunicationObject channel = m_channel as ICommunicationObject;
if (channel != null && channel.State == CommunicationState.Opened)
{
channel.Abort();
}
}
///
/// Schedules an outgoing request.
///
/// The request.
public void ScheduleOutgoingRequest(IChannelOutgoingRequest request)
{
#if MANAGE_CHANNEL_THREADS
System.Threading.Thread thread = new System.Threading.Thread(OnSendRequest);
thread.Start(request);
#else
throw new NotImplementedException();
#endif
}
#endregion
#region ITransportChannel Members
///
/// A masking indicating which features are implemented.
///
public TransportChannelFeatures SupportedFeatures
{
get
{
if (m_wcfBypassChannel != null)
{
return m_wcfBypassChannel.SupportedFeatures;
}
return TransportChannelFeatures.Reconnect | TransportChannelFeatures.BeginSendRequest | TransportChannelFeatures.BeginClose;
}
}
///
/// Gets the description for the endpoint used by the channel.
///
public EndpointDescription EndpointDescription
{
get
{
if (m_wcfBypassChannel != null)
{
return m_wcfBypassChannel.EndpointDescription;
}
if (m_settings != null)
{
return m_settings.Description;
}
return null;
}
}
///
/// Gets the configuration for the channel.
///
public EndpointConfiguration EndpointConfiguration
{
get
{
if (m_wcfBypassChannel != null)
{
return m_wcfBypassChannel.EndpointConfiguration;
}
if (m_settings != null)
{
return m_settings.Configuration;
}
return null;
}
}
///
/// Gets the context used when serializing messages exchanged via the channel.
///
public ServiceMessageContext MessageContext
{
get
{
if (m_wcfBypassChannel != null)
{
return m_wcfBypassChannel.MessageContext;
}
return m_messageContext;
}
}
///
/// Gets or sets the default timeout for requests send via the channel.
///
public int OperationTimeout
{
get
{
if (m_wcfBypassChannel != null)
{
return m_wcfBypassChannel.OperationTimeout;
}
return m_operationTimeout;
}
set
{
if (m_wcfBypassChannel != null)
{
m_wcfBypassChannel.OperationTimeout = value;
return;
}
m_operationTimeout = value;
}
}
///
/// Initializes a secure channel with the endpoint identified by the URL.
///
/// The URL for the endpoint.
/// The settings to use when creating the channel.
/// Thrown if any communication error occurs.
public void Initialize(
Uri url,
TransportChannelSettings settings)
{
if (m_wcfBypassChannel != null)
{
m_wcfBypassChannel.Initialize(url, settings);
return;
}
throw new NotSupportedException("WCF channels must be configured when they are constructed.");
}
///
/// Opens a secure channel with the endpoint identified by the URL.
///
public void Open()
{
if (m_wcfBypassChannel != null)
{
m_wcfBypassChannel.Open();
return;
}
}
///
/// Begins an asynchronous operation to open a secure channel with the endpoint identified by the URL.
///
public IAsyncResult BeginOpen(AsyncCallback callback, object callbackData)
{
if (m_wcfBypassChannel != null)
{
return m_wcfBypassChannel.BeginOpen(callback, callbackData);
}
throw new NotSupportedException("WCF channels must be configured when they are constructed.");
}
///
/// Completes an asynchronous operation to open a communication object.
///
public void EndOpen(IAsyncResult result)
{
if (m_wcfBypassChannel != null)
{
m_wcfBypassChannel.EndOpen(result);
return;
}
throw new NotSupportedException("WCF channels must be configured when they are constructed.");
}
///
/// Closes any existing secure channel and opens a new one.
///
/// Thrown if any communication error occurs.
///
/// Calling this method will cause outstanding requests over the current secure channel to fail.
///
public abstract void Reconnect();
///
/// Begins an asynchronous operation to close the existing secure channel and open a new one.
///
public IAsyncResult BeginReconnect(AsyncCallback callback, object callbackData)
{
if (m_wcfBypassChannel != null)
{
return m_wcfBypassChannel.BeginReconnect(callback, callbackData);
}
throw new NotSupportedException("WCF channels cannot be reconnected.");
}
///
/// Completes an asynchronous operation to close the existing secure channel and open a new one.
///
public void EndReconnect(IAsyncResult result)
{
if (m_wcfBypassChannel != null)
{
m_wcfBypassChannel.EndReconnect(result);
return;
}
throw new NotSupportedException("WCF channels cannot be reconnected.");
}
///
/// Closes any existing secure channel.
///
public void Close()
{
if (m_wcfBypassChannel != null)
{
m_wcfBypassChannel.Close();
return;
}
CloseChannel();
}
///
/// Begins an asynchronous operation to close the secure channel.
///
public IAsyncResult BeginClose(AsyncCallback callback, object callbackData)
{
if (m_wcfBypassChannel != null)
{
return m_wcfBypassChannel.BeginClose(callback, callbackData);
}
AsyncResultBase result = new AsyncResultBase(callback, callbackData, 0);
result.OperationCompleted();
return result;
}
///
/// Completes an asynchronous operation to close a communication object.
///
public void EndClose(IAsyncResult result)
{
if (m_wcfBypassChannel != null)
{
m_wcfBypassChannel.EndClose(result);
return;
}
AsyncResultBase.WaitForComplete(result);
CloseChannel();
}
///
/// Sends a request over the secure channel.
///
public IServiceResponse SendRequest(IServiceRequest request)
{
if (m_wcfBypassChannel != null)
{
return m_wcfBypassChannel.SendRequest(request);
}
byte[] requestMessage = BinaryEncoder.EncodeMessage(request, m_messageContext);
InvokeServiceResponseMessage responseMessage = InvokeService(new InvokeServiceMessage(requestMessage));
return (IServiceResponse)BinaryDecoder.DecodeMessage(responseMessage.InvokeServiceResponse, null, m_messageContext);
}
///
/// Begins an asynchronous operation to send a request over the secure channel.
///
public IAsyncResult BeginSendRequest(IServiceRequest request, AsyncCallback callback, object callbackData)
{
if (m_wcfBypassChannel != null)
{
return m_wcfBypassChannel.BeginSendRequest(request, callback, callbackData);
}
#if MANAGE_CHANNEL_THREADS
SendRequestAsyncResult asyncResult = new SendRequestAsyncResult(this, callback, callbackData, 0);
asyncResult.BeginSendRequest(SendRequest, request);
return asyncResult;
#else
byte[] requestMessage = BinaryEncoder.EncodeMessage(request, m_messageContext);
return BeginInvokeService(new InvokeServiceMessage(requestMessage), callback, callbackData);
#endif
}
///
/// Completes an asynchronous operation to send a request over the secure channel.
///
public IServiceResponse EndSendRequest(IAsyncResult result)
{
if (m_wcfBypassChannel != null)
{
return m_wcfBypassChannel.EndSendRequest(result);
}
#if MANAGE_CHANNEL_THREADS
return SendRequestAsyncResult.WaitForComplete(result);
#else
InvokeServiceResponseMessage responseMessage = EndInvokeService(result);
return (IServiceResponse)BinaryDecoder.DecodeMessage(responseMessage.InvokeServiceResponse, null, m_messageContext);
#endif
}
///
/// The client side implementation of the InvokeService service contract.
///
public abstract InvokeServiceResponseMessage InvokeService(InvokeServiceMessage request);
///
/// The client side implementation of the BeginInvokeService service contract.
///
public abstract IAsyncResult BeginInvokeService(InvokeServiceMessage request, AsyncCallback callback, object asyncState);
///
/// The client side implementation of the EndInvokeService service contract.
///
public abstract InvokeServiceResponseMessage EndInvokeService(IAsyncResult result);
#endregion
#if MANAGE_CHANNEL_THREADS
#region SendRequestAsyncResult Class
///
/// An AsyncResult object when handling an asynchronous request.
///
protected class SendRequestAsyncResult : AsyncResultBase, IChannelOutgoingRequest
{
#region Constructors
///
/// Initializes a new instance of the class.
///
/// The channel being used.
/// The callback to use when the operation completes.
/// The callback data.
/// The timeout in milliseconds
public SendRequestAsyncResult(
IChannelBase channel,
AsyncCallback callback,
object callbackData,
int timeout)
:
base(callback, callbackData, timeout)
{
m_channel = channel;
}
#endregion
#region IChannelOutgoingRequest Members
///
/// Gets the request.
///
/// The request.
public IServiceRequest Request
{
get { return m_request; }
}
///
/// Gets the handler used to send the request.
///
/// The send request handler.
public ChannelSendRequestEventHandler Handler
{
get { return m_handler; }
}
///
/// Used to call the default synchronous handler.
///
///
/// This method may block the current thread so the caller must not call in the
/// thread that calls IServerBase.ScheduleIncomingRequest().
/// This method always traps any exceptions and reports them to the client as a fault.
///
public void CallSynchronously()
{
OnSendRequest(null);
}
///
/// Used to indicate that the asynchronous operation has completed.
///
/// The response. May be null if an error is provided.
///
public void OperationCompleted(IServiceResponse response, ServiceResult error)
{
// save response and/or error.
m_error = null;
m_response = response;
if (ServiceResult.IsBad(error))
{
m_error = new ServiceResultException(error);
m_response = null;
}
// operation completed.
OperationCompleted();
}
#endregion
#region Public Members
///
/// Begins processing an incoming request.
///
/// The method which sends the request.
/// The request.
/// The result object that is used to call the EndSendRequest method.
public IAsyncResult BeginSendRequest(
ChannelSendRequestEventHandler handler,
IServiceRequest request)
{
m_handler = handler;
m_request = request;
try
{
// queue request.
m_channel.ScheduleOutgoingRequest(this);
}
catch (Exception e)
{
m_error = e;
m_response = null;
// operation completed.
OperationCompleted();
}
return this;
}
///
/// Checks for a valid IAsyncResult object and waits for the operation to complete.
///
/// The IAsyncResult object for the operation.
/// The response.
public static new IServiceResponse WaitForComplete(IAsyncResult ar)
{
SendRequestAsyncResult result = ar as SendRequestAsyncResult;
if (result == null)
{
throw new ArgumentException("End called with an invalid IAsyncResult object.", "ar");
}
if (result.m_response == null && result.m_error == null)
{
if (!result.WaitForComplete())
{
throw new TimeoutException();
}
}
if (result.m_error != null)
{
throw new ServiceResultException(result.m_error, StatusCodes.BadInternalError);
}
return result.m_response;
}
///
/// Checks for a valid IAsyncResult object and returns the original request object.
///
/// The IAsyncResult object for the operation.
/// The request object if available; otherwise null.
public static IServiceRequest GetRequest(IAsyncResult ar)
{
SendRequestAsyncResult result = ar as SendRequestAsyncResult;
if (result != null)
{
return result.m_request;
}
return null;
}
#endregion
#region Private Members
///
/// Processes the request.
///
private void OnSendRequest(object state)
{
try
{
// call the service.
m_response = m_handler(m_request);
}
catch (Exception e)
{
// save any error.
m_error = e;
m_response = null;
}
// report completion.
OperationCompleted();
}
#endregion
#region Private Fields
private IChannelBase m_channel;
private ChannelSendRequestEventHandler m_handler;
private IServiceRequest m_request;
private IServiceResponse m_response;
private Exception m_error;
#endregion
}
#endregion
///
/// Processes the request.
///
/// IChannelOutgoingRequest object passed to the ScheduleOutgoingRequest method.
protected virtual void OnSendRequest(object state)
{
try
{
IChannelOutgoingRequest request = (IChannelOutgoingRequest)state;
request.CallSynchronously();
}
catch (Exception e)
{
Utils.Trace(e, "Unexpected error sending outgoing request.");
}
}
#endif
#region Protected Methods
///
/// Creates a new UA-binary transport channel if requested. Null otherwise.
///
/// The application configuration.
/// The description for the endpoint.
/// The configuration to use with the endpoint.
/// The client certificate.
/// The message context to use when serializing the messages.
///
public static ITransportChannel CreateUaBinaryChannel(
ApplicationConfiguration configuration,
EndpointDescription description,
EndpointConfiguration endpointConfiguration,
X509Certificate2 clientCertificate,
ServiceMessageContext messageContext)
{
// check if the server if configured to use the ANSI C stack.
bool useUaTcp = description.EndpointUrl.StartsWith(Utils.UriSchemeOpcTcp);
bool useHttps = description.EndpointUrl.StartsWith(Utils.UriSchemeHttps);
#if !SILVERLIGHT
bool useAnsiCStack = false;
#else
useHttps = description.EndpointUrl.StartsWith(Utils.UriSchemeHttp);
#endif
switch (description.TransportProfileUri)
{
case Profiles.UaTcpTransport:
{
useUaTcp = true;
#if !SILVERLIGHT
if (configuration != null)
{
useAnsiCStack = configuration.UseNativeStack;
}
#endif
break;
}
case Profiles.HttpsXmlTransport:
case Profiles.HttpsBinaryTransport:
case Profiles.HttpsXmlOrBinaryTransport:
{
useHttps = true;
break;
}
}
#if !SILVERLIGHT
// check for a WCF channel.
if (!useUaTcp && !useHttps)
{
// binary channels only need the base class.
if (endpointConfiguration.UseBinaryEncoding)
{
Uri endpointUrl = new Uri(description.EndpointUrl);
BindingFactory bindingFactory = BindingFactory.Create(configuration, messageContext);
Binding binding = bindingFactory.Create(endpointUrl.Scheme, description, endpointConfiguration);
WcfChannelBase wcfChannel = new WcfChannelBase();
// create regular binding.
if (configuration != null)
{
wcfChannel.Initialize(
configuration,
description,
endpointConfiguration,
binding,
clientCertificate,
null);
}
// create no-security discovery binding.
else
{
wcfChannel.Initialize(
description,
endpointConfiguration,
binding,
null);
}
return wcfChannel;
}
return null;
}
#endif
// initialize the channel which will be created with the server.
ITransportChannel channel = null;
// create a UA-TCP channel.
TransportChannelSettings settings = new TransportChannelSettings();
settings.Description = description;
settings.Configuration = endpointConfiguration;
settings.ClientCertificate = clientCertificate;
if (description.ServerCertificate != null && description.ServerCertificate.Length > 0)
{
settings.ServerCertificate = Utils.ParseCertificateBlob(description.ServerCertificate);
}
#if !SILVERLIGHT
if (configuration != null)
{
settings.CertificateValidator = configuration.CertificateValidator.GetChannelValidator();
}
#endif
settings.NamespaceUris = messageContext.NamespaceUris;
settings.Factory = messageContext.Factory;
if (useUaTcp)
{
#if !SILVERLIGHT
Type type = null;
if (useAnsiCStack)
{
type = Type.GetType("Opc.Ua.NativeStack.NativeStackChannel,Opc.Ua.NativeStackWrapper");
}
if (useAnsiCStack && type != null)
{
channel = (ITransportChannel)Activator.CreateInstance(type);
}
else
{
channel = new Opc.Ua.Bindings.TcpTransportChannel();
}
#endif
}
else if (useHttps)
{
channel = new Opc.Ua.Bindings.HttpsTransportChannel();
}
channel.Initialize(new Uri(description.EndpointUrl), settings);
channel.Open();
return channel;
}
///
/// Creates a new UA-binary transport channel if requested. Null otherwise.
///
/// The configuration.
/// The description.
/// The endpoint configuration.
/// The client certificates.
/// The message context.
///
/*public static ITransportChannel CreateUaBinaryChannel(
ApplicationConfiguration configuration,
EndpointDescription description,
EndpointConfiguration endpointConfiguration,
X509Certificate2Collection clientCertificates,
ServiceMessageContext messageContext)
{
// check if the server if configured to use the ANSI C stack.
bool useUaTcp = description.EndpointUrl.StartsWith(Utils.UriSchemeOpcTcp);
bool useHttps = description.EndpointUrl.StartsWith(Utils.UriSchemeHttps);
#if !SILVERLIGHT
bool useAnsiCStack = false;
#else
useHttps = description.EndpointUrl.StartsWith(Utils.UriSchemeHttp);
#endif
switch (description.TransportProfileUri)
{
case Profiles.UaTcpTransport:
{
useUaTcp = true;
#if !SILVERLIGHT
if (configuration != null)
{
useAnsiCStack = configuration.UseNativeStack;
}
#endif
break;
}
case Profiles.HttpsXmlTransport:
case Profiles.HttpsBinaryTransport:
case Profiles.HttpsXmlOrBinaryTransport:
{
useHttps = true;
break;
}
}
#if !SILVERLIGHT
// check for a WCF channel.
if (!useUaTcp && !useHttps)
{
// binary channels only need the base class.
if (endpointConfiguration.UseBinaryEncoding)
{
Uri endpointUrl = new Uri(description.EndpointUrl);
BindingFactory bindingFactory = BindingFactory.Create(configuration, messageContext);
Binding binding = bindingFactory.Create(endpointUrl.Scheme, description, endpointConfiguration);
WcfChannelBase wcfChannel = new WcfChannelBase();
// create regular binding.
if (configuration != null)
{
wcfChannel.Initialize(
configuration,
description,
endpointConfiguration,
binding,
clientCertificates,
null);
}
// create no-security discovery binding.
else
{
wcfChannel.Initialize(
description,
endpointConfiguration,
binding,
null);
}
return wcfChannel;
}
return null;
}
#endif
// initialize the channel which will be created with the server.
ITransportChannel channel = null;
// create a UA-TCP channel.
TransportChannelSettings settings = new TransportChannelSettings();
settings.Description = description;
settings.Configuration = endpointConfiguration;
if (clientCertificates != null && clientCertificates.Count > 0)
{
settings.ClientCertificate = clientCertificates[0];
//settings.ClientCertificateChain = clientCertificates;
}
if (description.ServerCertificate != null && description.ServerCertificate.Length > 0)
{
settings.ServerCertificate = Utils.ParseCertificateBlob(description.ServerCertificate);
}
#if !SILVERLIGHT
if (configuration != null)
{
settings.CertificateValidator = configuration.CertificateValidator.GetChannelValidator();
}
#endif
settings.NamespaceUris = messageContext.NamespaceUris;
settings.Factory = messageContext.Factory;
if (useUaTcp)
{
#if !SILVERLIGHT
Type type = null;
if (useAnsiCStack)
{
type = Type.GetType("Opc.Ua.NativeStack.NativeStackChannel,Opc.Ua.NativeStackWrapper");
}
if (useAnsiCStack && type != null)
{
channel = (ITransportChannel)Activator.CreateInstance(type);
}
else
{
channel = new Opc.Ua.Bindings.TcpTransportChannel();
}
#endif
}
else if (useHttps)
{
channel = new Opc.Ua.Bindings.HttpsTransportChannel();
}
channel.Initialize(new Uri(description.EndpointUrl), settings);
channel.Open();
return channel;
}
*/
///
/// Handles the Opened event of the InnerChannel control.
///
/// The source of the event.
/// The instance containing the event data.
internal void InnerChannel_Opened(object sender, EventArgs e)
{
#if !SILVERLIGHT
Uri endpointUrl = this.m_channelFactory.Endpoint.Address.Uri;
if (endpointUrl.Scheme == Utils.UriSchemeHttp || endpointUrl.Scheme == Utils.UriSchemeHttps)
{
ServicePoint sp = System.Net.ServicePointManager.FindServicePoint(endpointUrl);
sp.ConnectionLimit = 1000;
}
X509Certificate2 clientCertificate = null;
X509Certificate2 serverCertificate = null;
ClientCredentials credentials = m_channelFactory.Endpoint.Behaviors.Find();
if (credentials != null)
{
clientCertificate = credentials.ClientCertificate.Certificate;
serverCertificate = credentials.ServiceCertificate.DefaultCertificate;
}
Opc.Ua.Security.Audit.SecureChannelCreated(
g_ImplementationString,
m_channelFactory.Endpoint.Address.Uri.ToString(),
null,
EndpointDescription,
clientCertificate,
serverCertificate,
BinaryEncodingSupport.Optional);
#endif
}
///
/// Converts a FaultException into a ServiceResultException.
///
public ServiceResultException HandleSoapFault(System.ServiceModel.FaultException exception)
{
if (exception == null || exception.Detail == null || exception.Detail.ResponseHeader == null)
{
return ServiceResultException.Create(StatusCodes.BadUnexpectedError, exception, "SOAP fault did not contain any details.");
}
ResponseHeader header = exception.Detail.ResponseHeader;
return new ServiceResultException(new ServiceResult(
header.ServiceResult,
header.ServiceDiagnostics,
header.StringTable));
}
#endregion
#region Private Fields
internal TransportChannelSettings m_settings;
internal ServiceMessageContext m_messageContext;
internal ITransportChannel m_wcfBypassChannel;
internal int m_operationTimeout;
internal ChannelFactory m_channelFactory;
internal IChannelBase m_channel;
internal const string g_ImplementationString = "Opc.Ua.ChannelBase WCF Client " + AssemblyVersionInfo.CurrentVersion;
#endregion
}
///
/// A base class for WCF channel objects used access UA interfaces
///
public class WcfChannelBase : WcfChannelBase where TChannel : class, IChannelBase
{
#region Constructors
///
/// Initializes the object with the specified binding and endpoint address.
///
public WcfChannelBase()
{
}
#if !SILVERLIGHT
///
/// Initializes the channel without security.
///
/// The endpoint.
/// The endpoint configuration.
/// The binding.
/// Name of the configuration in the app.config file.
public void Initialize(
EndpointDescription endpoint,
EndpointConfiguration endpointConfiguration,
Binding binding,
string configurationName)
{
if (endpoint == null) throw new ArgumentNullException("endpoint");
if (endpointConfiguration == null) throw new ArgumentNullException("endpointConfiguration");
if (binding == null) throw new ArgumentNullException("binding");
Uri endpointUrl = new Uri(endpoint.EndpointUrl);
// create the configuration.
if (endpointConfiguration == null)
{
endpointConfiguration = EndpointConfiguration.Create();
}
ServiceMessageContext messageContext = null;
// extract the message context from the binding.
BaseBinding baseBinding = binding as BaseBinding;
if (baseBinding != null)
{
messageContext = baseBinding.MessageContext;
}
// create a new message context.
else
{
messageContext = new ServiceMessageContext();
messageContext.MaxArrayLength = endpointConfiguration.MaxArrayLength;
messageContext.MaxByteStringLength = endpointConfiguration.MaxByteStringLength;
messageContext.MaxMessageSize = endpointConfiguration.MaxMessageSize;
messageContext.MaxStringLength = endpointConfiguration.MaxStringLength;
messageContext.NamespaceUris = new NamespaceTable();
messageContext.ServerUris = new StringTable();
messageContext.Factory = EncodeableFactory.GlobalFactory;
}
// a work around designed to preserve the behavoir of the object when the WCF UA-TCP implementation was supported.
if (endpointUrl.Scheme == Utils.UriSchemeOpcTcp)
{
m_wcfBypassChannel = CreateUaBinaryChannel(
null,
endpoint,
endpointConfiguration,
(X509Certificate2)null,
messageContext);
return;
}
// update the channel timeout.
TimeSpan timeout = new TimeSpan(endpointConfiguration.OperationTimeout * TimeSpan.TicksPerMillisecond);
binding.OpenTimeout = timeout;
binding.CloseTimeout = timeout;
binding.SendTimeout = timeout;
binding.ReceiveTimeout = timeout;
// create the address.
EndpointAddress address = new EndpointAddress(endpointUrl);
// create the factory.
ChannelFactory channelFactory = null;
if (binding == null)
{
channelFactory = new System.ServiceModel.ChannelFactory(configurationName, address);
}
else
{
channelFactory = new System.ServiceModel.ChannelFactory(binding, address);
}
// check if XML encoding is used.
if (!endpointConfiguration.UseBinaryEncoding)
{
ServiceMessageContextMessageInspector inspector = new ServiceMessageContextMessageInspector(binding);
channelFactory.Endpoint.Behaviors.Add(inspector);
// update the max items in graph (set to an low value by default).
foreach (OperationDescription operation in channelFactory.Endpoint.Contract.Operations)
{
operation.Behaviors.Find().MaxItemsInObjectGraph = Int32.MaxValue;
}
}
// create the settings.
TransportChannelSettings settings = new TransportChannelSettings();
settings.Description = endpoint;
settings.Configuration = endpointConfiguration;
settings.ClientCertificate = null;
settings.ServerCertificate = null;
settings.CertificateValidator = null;
settings.NamespaceUris = messageContext.NamespaceUris;
settings.Factory = messageContext.Factory;
// save the parameters.
m_settings = settings;
m_messageContext = messageContext;
m_operationTimeout = endpointConfiguration.OperationTimeout;
m_channelFactory = channelFactory;
// create the channel.
base.m_channel = m_channel = channelFactory.CreateChannel();
ICommunicationObject communicationObject = m_channel as ICommunicationObject;
if (communicationObject != null)
{
communicationObject.Opened += new EventHandler(InnerChannel_Opened);
}
}
///
/// Initializes the channel with security.
///
/// The application configuration.
/// The description.
/// The endpoint configuration.
/// The binding.
/// The client certificate.
/// Name of the configuration in the app.config file.
public void Initialize(
ApplicationConfiguration configuration,
EndpointDescription description,
EndpointConfiguration endpointConfiguration,
Binding binding,
X509Certificate2 clientCertificate,
string configurationName)
{
if (configuration == null) throw new ArgumentNullException("configuration");
if (description == null) throw new ArgumentNullException("description");
if (binding == null) throw new ArgumentNullException("binding");
// validate the url.
Uri uri = new Uri(description.EndpointUrl);
// create the configuration.
if (endpointConfiguration == null)
{
endpointConfiguration = EndpointConfiguration.Create(configuration);
}
ServiceMessageContext messageContext = null;
// extract the message context from the binding.
BaseBinding baseBinding = binding as BaseBinding;
if (baseBinding != null)
{
messageContext = baseBinding.MessageContext;
}
// create a new message context.
else
{
messageContext = configuration.CreateMessageContext();
messageContext.MaxArrayLength = endpointConfiguration.MaxArrayLength;
messageContext.MaxByteStringLength = endpointConfiguration.MaxByteStringLength;
messageContext.MaxMessageSize = endpointConfiguration.MaxMessageSize;
messageContext.MaxStringLength = endpointConfiguration.MaxStringLength;
messageContext.NamespaceUris = new NamespaceTable();
messageContext.ServerUris = new StringTable();
messageContext.Factory = EncodeableFactory.GlobalFactory;
}
// a work around designed to preserve the behavoir of the object when the WCF UA-TCP implementation was supported.
if (uri.Scheme == Utils.UriSchemeOpcTcp)
{
m_wcfBypassChannel = CreateUaBinaryChannel(
configuration,
description,
endpointConfiguration,
clientCertificate,
messageContext);
return;
}
TimeSpan timeout = new TimeSpan(endpointConfiguration.OperationTimeout * TimeSpan.TicksPerMillisecond);
binding.OpenTimeout = timeout;
binding.CloseTimeout = timeout;
binding.SendTimeout = timeout;
binding.ReceiveTimeout = timeout;
X509Certificate2 serverCertificate = null;
EndpointIdentity identity = null;
if (description.SecurityPolicyUri != SecurityPolicies.None)
{
// create the DNS identity for the server from the certificate.
if (description.ServerCertificate == null)
{
throw ServiceResultException.Create( StatusCodes.BadCertificateInvalid, "EndpointDescription for {0} does not have a ServerCertificate specified.", description.EndpointUrl );
}
serverCertificate = CertificateFactory.Create( description.ServerCertificate, true );
identity = EndpointIdentity.CreateX509CertificateIdentity( serverCertificate );
}
// create the adderss.
EndpointAddress address = new EndpointAddress(uri, identity);
// create the factory.
ChannelFactory channelFactory = null;
if (binding == null)
{
channelFactory = new System.ServiceModel.ChannelFactory(configurationName, address);
}
else
{
channelFactory = new System.ServiceModel.ChannelFactory(binding, address);
}
// check if XML encoding is used.
if (!endpointConfiguration.UseBinaryEncoding)
{
// add behavoir required to provide the MessageContext to the WCF encoders.
ServiceMessageContextMessageInspector inspector = new ServiceMessageContextMessageInspector(binding);
channelFactory.Endpoint.Behaviors.Add(inspector);
// update the max items in graph (set to an low value by default).
foreach (OperationDescription operation in channelFactory.Endpoint.Contract.Operations)
{
operation.Behaviors.Find().MaxItemsInObjectGraph = Int32.MaxValue;
}
}
// add the service certificate into the behaviors.
if (serverCertificate != null)
{
ClientCredentials credentials = (ClientCredentials)channelFactory.Endpoint.Behaviors[typeof(ClientCredentials)];
credentials.ServiceCertificate.DefaultCertificate = serverCertificate;
credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.Custom;
credentials.ServiceCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck;
credentials.ServiceCertificate.Authentication.TrustedStoreLocation = StoreLocation.LocalMachine;
credentials.ServiceCertificate.Authentication.CustomCertificateValidator = configuration.CertificateValidator.GetChannelValidator();
if (clientCertificate != null)
{
credentials.ClientCertificate.Certificate = clientCertificate;
}
}
// set the protection level on the contract.
if (description != null)
{
if (description.SecurityMode == MessageSecurityMode.Sign)
{
channelFactory.Endpoint.Contract.ProtectionLevel = System.Net.Security.ProtectionLevel.Sign;
}
}
// create the settings.
TransportChannelSettings settings = new TransportChannelSettings();
settings.Description = description;
settings.Configuration = endpointConfiguration;
settings.ClientCertificate = clientCertificate;
settings.ServerCertificate = serverCertificate;
settings.CertificateValidator = configuration.CertificateValidator.GetChannelValidator();
settings.NamespaceUris = messageContext.NamespaceUris;
settings.Factory = messageContext.Factory;
// save the parameters.
m_settings = settings;
m_messageContext = messageContext;
m_operationTimeout = endpointConfiguration.OperationTimeout;
m_channelFactory = channelFactory;
// create the channel.
base.m_channel = m_channel = channelFactory.CreateChannel();
ICommunicationObject communicationObject = m_channel as ICommunicationObject;
if (communicationObject != null)
{
communicationObject.Opened += new EventHandler(InnerChannel_Opened);
}
}
///
/// Initializes the channel with security.
///
/// The configuration.
/// The description.
/// The endpoint configuration.
/// The binding.
/// The client certificates.
/// Name of the configuration.
/*public void Initialize(
ApplicationConfiguration configuration,
EndpointDescription description,
EndpointConfiguration endpointConfiguration,
Binding binding,
X509Certificate2Collection clientCertificates,
string configurationName)
{
if (configuration == null) throw new ArgumentNullException("configuration");
if (description == null) throw new ArgumentNullException("description");
if (binding == null) throw new ArgumentNullException("binding");
// validate the url.
Uri uri = new Uri(description.EndpointUrl);
// create the configuration.
if (endpointConfiguration == null)
{
endpointConfiguration = EndpointConfiguration.Create(configuration);
}
ServiceMessageContext messageContext = null;
// extract the message context from the binding.
BaseBinding baseBinding = binding as BaseBinding;
if (baseBinding != null)
{
messageContext = baseBinding.MessageContext;
}
// create a new message context.
else
{
messageContext = configuration.CreateMessageContext();
messageContext.MaxArrayLength = endpointConfiguration.MaxArrayLength;
messageContext.MaxByteStringLength = endpointConfiguration.MaxByteStringLength;
messageContext.MaxMessageSize = endpointConfiguration.MaxMessageSize;
messageContext.MaxStringLength = endpointConfiguration.MaxStringLength;
messageContext.NamespaceUris = new NamespaceTable();
messageContext.ServerUris = new StringTable();
messageContext.Factory = EncodeableFactory.GlobalFactory;
}
// a work around designed to preserve the behavoir of the object when the WCF UA-TCP implementation was supported.
if (uri.Scheme == Utils.UriSchemeOpcTcp)
{
m_wcfBypassChannel = CreateUaBinaryChannel(
configuration,
description,
endpointConfiguration,
clientCertificates,
messageContext);
return;
}
TimeSpan timeout = new TimeSpan(endpointConfiguration.OperationTimeout * TimeSpan.TicksPerMillisecond);
binding.OpenTimeout = timeout;
binding.CloseTimeout = timeout;
binding.SendTimeout = timeout;
binding.ReceiveTimeout = timeout;
X509Certificate2 serverCertificate = null;
EndpointIdentity identity = null;
if (description.SecurityPolicyUri != SecurityPolicies.None)
{
// create the DNS identity for the server from the certificate.
if (description.ServerCertificate == null)
{
throw ServiceResultException.Create(StatusCodes.BadCertificateInvalid, "EndpointDescription for {0} does not have a ServerCertificate specified.", description.EndpointUrl);
}
serverCertificate = CertificateFactory.Create(description.ServerCertificate, true);
identity = EndpointIdentity.CreateX509CertificateIdentity(serverCertificate);
}
// create the adderss.
EndpointAddress address = new EndpointAddress(uri, identity);
// create the factory.
ChannelFactory channelFactory = null;
if (binding == null)
{
channelFactory = new System.ServiceModel.ChannelFactory(configurationName, address);
}
else
{
channelFactory = new System.ServiceModel.ChannelFactory(binding, address);
}
// check if XML encoding is used.
if (!endpointConfiguration.UseBinaryEncoding)
{
// add behavoir required to provide the MessageContext to the WCF encoders.
ServiceMessageContextMessageInspector inspector = new ServiceMessageContextMessageInspector(binding);
channelFactory.Endpoint.Behaviors.Add(inspector);
// update the max items in graph (set to an low value by default).
foreach (OperationDescription operation in channelFactory.Endpoint.Contract.Operations)
{
operation.Behaviors.Find().MaxItemsInObjectGraph = Int32.MaxValue;
}
}
// add the service certificate into the behaviors.
if (serverCertificate != null)
{
ClientCredentials credentials = (ClientCredentials)channelFactory.Endpoint.Behaviors[typeof(ClientCredentials)];
credentials.ServiceCertificate.DefaultCertificate = serverCertificate;
credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.Custom;
credentials.ServiceCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck;
credentials.ServiceCertificate.Authentication.TrustedStoreLocation = StoreLocation.LocalMachine;
credentials.ServiceCertificate.Authentication.CustomCertificateValidator = configuration.CertificateValidator.GetChannelValidator();
if (clientCertificates != null && clientCertificates.Count > 0 && clientCertificates[0] != null)
{
credentials.ClientCertificate.Certificate = clientCertificates[0];
}
}
// set the protection level on the contract.
if (description != null)
{
if (description.SecurityMode == MessageSecurityMode.Sign)
{
channelFactory.Endpoint.Contract.ProtectionLevel = System.Net.Security.ProtectionLevel.Sign;
}
}
// create the settings.
TransportChannelSettings settings = new TransportChannelSettings();
settings.Description = description;
settings.Configuration = endpointConfiguration;
if (clientCertificates != null && clientCertificates.Count > 0)
{
settings.ClientCertificate = clientCertificates[0];
//settings.ClientCertificateChain = clientCertificates;
}
settings.ServerCertificate = serverCertificate;
settings.CertificateValidator = configuration.CertificateValidator.GetChannelValidator();
settings.NamespaceUris = messageContext.NamespaceUris;
settings.Factory = messageContext.Factory;
// save the parameters.
m_settings = settings;
m_messageContext = messageContext;
m_operationTimeout = endpointConfiguration.OperationTimeout;
m_channelFactory = channelFactory;
// create the channel.
base.m_channel = m_channel = channelFactory.CreateChannel();
ICommunicationObject communicationObject = m_channel as ICommunicationObject;
if (communicationObject != null)
{
communicationObject.Opened += new EventHandler(InnerChannel_Opened);
}
}
*/
#endif
#endregion
#region IDisposable Members
///
/// An overrideable version of the Dispose.
///
protected override void Dispose(bool disposing)
{
if (disposing)
{
try
{
var communicationObject = m_channel as ICommunicationObject;
if (communicationObject != null)
{
communicationObject.Opened -= new EventHandler(InnerChannel_Opened);
communicationObject.Close();
}
}
catch (Exception)
{
// ignore errors.
}
Utils.SilentDispose(m_channel);
m_channel = null;
Utils.SilentDispose(m_channelFactory);
m_channelFactory = null;
}
base.Dispose(disposing);
}
#endregion
#region IChannelBase Members
///
/// The client side implementation of the InvokeService service contract.
///
public override InvokeServiceResponseMessage InvokeService(InvokeServiceMessage request)
{
IAsyncResult result = null;
lock (this.Channel)
{
result = this.Channel.BeginInvokeService(request, null, null);
}
return this.Channel.EndInvokeService(result);
}
///
/// The client side implementation of the BeginInvokeService service contract.
///
public override IAsyncResult BeginInvokeService(InvokeServiceMessage request, AsyncCallback callback, object asyncState)
{
WcfChannelAsyncResult asyncResult = new WcfChannelAsyncResult(m_channel, callback, asyncState);
lock (asyncResult.Lock)
{
asyncResult.InnerResult = asyncResult.Channel.BeginInvokeService(request, asyncResult.OnOperationCompleted, null);
}
return asyncResult;
}
///
/// The client side implementation of the EndInvokeService service contract.
///
public override InvokeServiceResponseMessage EndInvokeService(IAsyncResult result)
{
WcfChannelAsyncResult asyncResult = WcfChannelAsyncResult.WaitForComplete(result);
return asyncResult.Channel.EndInvokeService(asyncResult.InnerResult);
}
#endregion
#region ITransportChannel Members
///
/// Closes any existing secure channel and opens a new one.
///
public override void Reconnect()
{
if (m_wcfBypassChannel != null)
{
m_wcfBypassChannel.Reconnect();
return;
}
Utils.Trace("RECONNECT: Reconnecting to {0}.", m_settings.Description.EndpointUrl);
// grap the existing channel.
TChannel channel = m_channel;
ChannelFactory channelFactory = m_channelFactory as ChannelFactory;
// create the new channel.
base.m_channel = m_channel = channelFactory.CreateChannel();
ICommunicationObject communicationObject = null;
if (channel != null)
{
try
{
communicationObject = channel as ICommunicationObject;
if (communicationObject != null)
{
communicationObject.Opened -= new EventHandler(InnerChannel_Opened);
communicationObject.Close();
}
}
catch (Exception)
{
// ignore errors.
}
Utils.SilentDispose(channel);
}
// register callback with new channel.
communicationObject = m_channel as ICommunicationObject;
if (communicationObject != null)
{
communicationObject.Opened += new EventHandler(InnerChannel_Opened);
}
}
#endregion
#region WcfChannelAsyncResult Class
///
/// An async result object that wraps the WCF channel.
///
protected class WcfChannelAsyncResult : AsyncResultBase
{
///
/// Initializes a new instance of the class.
///
/// The channel.
/// The callback.
/// The callback data.
public WcfChannelAsyncResult(
TChannel channel,
AsyncCallback callback,
object callbackData)
:
base(callback, callbackData, 0)
{
m_channel = channel;
}
///
/// Gets the wrapped channel.
///
/// The wrapped channel.
public TChannel Channel
{
get { return m_channel; }
}
///
/// Called when asynchronous operation completes.
///
/// The asynchronous result object.
public void OnOperationCompleted(IAsyncResult ar)
{
try
{
// check if the begin operation has had a chance to complete.
lock (Lock)
{
if (InnerResult == null)
{
InnerResult = ar;
}
}
// signal that the operation is complete.
OperationCompleted();
}
catch (Exception e)
{
Utils.Trace(e, "Unexpected exception invoking WcfChannelAsyncResult callback function.");
}
}
///
/// Checks for a valid IAsyncResult object and waits for the operation to complete.
///
/// The IAsyncResult object for the operation.
/// The oject that
public static new WcfChannelAsyncResult WaitForComplete(IAsyncResult ar)
{
WcfChannelAsyncResult asyncResult = ar as WcfChannelAsyncResult;
if (asyncResult == null)
{
throw new ArgumentException("End called with an invalid IAsyncResult object.", "ar");
}
if (!asyncResult.WaitForComplete())
{
throw new ServiceResultException(StatusCodes.BadTimeout);
}
return asyncResult;
}
private TChannel m_channel;
}
#endregion
#region Protected Methods
///
/// Gets the inner channel.
///
/// The channel.
protected TChannel Channel
{
get { return m_channel; }
}
#endregion
#region Private Fields
private new TChannel m_channel;
#endregion
}
}