ServiceChannelFactory.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ WCF / WCF / 3.5.30729.1 / untmp / Orcas / SP / ndp / cdf / src / WCF / ServiceModel / System / ServiceModel / Channels / ServiceChannelFactory.cs / 1 / ServiceChannelFactory.cs

                            //------------------------------------------------------------ 
// Copyright (c) Microsoft Corporation.  All rights reserved.
//-----------------------------------------------------------

namespace System.ServiceModel.Channels 
{
    using System.Collections.Generic; 
    using System.Diagnostics; 
    using System.ServiceModel.Diagnostics;
    using System.ServiceModel.Dispatcher; 
    using System.ServiceModel.Description;
    using System.Collections.ObjectModel;
    using System.ServiceModel;
    using System.ServiceModel.Security; 
    using System.Text;
    using System.Globalization; 
    using System.Security; 
    using System.Runtime.Remoting;
 
    abstract class ServiceChannelFactory : ChannelFactoryBase
    {
        string bindingName;
        List channelsList; 
        ClientRuntime clientRuntime;
        RequestReplyCorrelator requestReplyCorrelator = new RequestReplyCorrelator(); 
        IDefaultCommunicationTimeouts timeouts; 
        MessageVersion messageVersion;
 
        public ServiceChannelFactory(ClientRuntime clientRuntime, Binding binding)
            : base()
        {
            if (clientRuntime == null) 
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("clientRuntime"); 
            } 

            this.bindingName = binding.Name; 
            this.channelsList = new List();
            this.clientRuntime = clientRuntime;
            this.timeouts = new DefaultCommunicationTimeouts(binding);
            this.messageVersion = binding.MessageVersion; 
        }
 
        public ClientRuntime ClientRuntime 
        {
            get 
            {
                this.ThrowIfDisposed();
                return this.clientRuntime;
            } 
        }
 
        internal RequestReplyCorrelator RequestReplyCorrelator 
        {
            get 
            {
                ThrowIfDisposed();
                return this.requestReplyCorrelator;
            } 
        }
 
        protected override TimeSpan DefaultCloseTimeout 
        {
            get { return this.timeouts.CloseTimeout; } 
        }

        protected override TimeSpan DefaultReceiveTimeout
        { 
            get { return this.timeouts.ReceiveTimeout; }
        } 
 
        protected override TimeSpan DefaultOpenTimeout
        { 
            get { return this.timeouts.OpenTimeout; }
        }

        protected override TimeSpan DefaultSendTimeout 
        {
            get { return this.timeouts.SendTimeout; } 
        } 

        public MessageVersion MessageVersion 
        {
            get { return this.messageVersion; }
        }
 
        // special overload for security only
        public static ServiceChannelFactory BuildChannelFactory(ChannelBuilder channelBuilder, ClientRuntime clientRuntime) 
        { 
            if (channelBuilder.CanBuildChannelFactory())
            { 
                return new ServiceChannelFactoryOverDuplex(channelBuilder.BuildChannelFactory(), clientRuntime,
                    channelBuilder.Binding);
            }
            else if (channelBuilder.CanBuildChannelFactory()) 
            {
                return new ServiceChannelFactoryOverDuplexSession(channelBuilder.BuildChannelFactory(), clientRuntime, 
                    channelBuilder.Binding); 
            }
            else 
            {
                return new ServiceChannelFactoryOverRequestSession(channelBuilder.BuildChannelFactory(), clientRuntime,
                    channelBuilder.Binding, false);
            } 
        }
 
        public static ServiceChannelFactory BuildChannelFactory(ServiceEndpoint serviceEndpoint) 
        {
            if (serviceEndpoint == null) 
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("serviceEndpoint");
            }
 
            serviceEndpoint.EnsureInvariants();
            serviceEndpoint.ValidateForClient(); 
 
            ChannelRequirements requirements;
            ContractDescription contractDescription = serviceEndpoint.Contract; 
            ChannelRequirements.ComputeContractRequirements(contractDescription, out requirements);

            BindingParameterCollection parameters;
            ClientRuntime clientRuntime = DispatcherBuilder.BuildProxyBehavior(serviceEndpoint, out parameters); 

            Binding binding = serviceEndpoint.Binding; 
            Type[] requiredChannels = ChannelRequirements.ComputeRequiredChannels(ref requirements); 

            CustomBinding customBinding = new CustomBinding(binding); 
            BindingContext context = new BindingContext(customBinding, parameters);

            InternalDuplexBindingElement internalDuplexBindingElement = null;
            InternalDuplexBindingElement.AddDuplexFactorySupport(context, ref internalDuplexBindingElement); 

            customBinding = new CustomBinding(context.RemainingBindingElements); 
            customBinding.CopyTimeouts(serviceEndpoint.Binding); 

            foreach (Type type in requiredChannels) 
            {
                if (type == typeof(IOutputChannel) && customBinding.CanBuildChannelFactory(parameters))
                {
                    return new ServiceChannelFactoryOverOutput(customBinding.BuildChannelFactory(parameters), clientRuntime, binding); 
                }
 
                if (type == typeof(IRequestChannel) && customBinding.CanBuildChannelFactory(parameters)) 
                {
                    return new ServiceChannelFactoryOverRequest(customBinding.BuildChannelFactory(parameters), clientRuntime, binding); 
                }

                if (type == typeof(IDuplexChannel) && customBinding.CanBuildChannelFactory(parameters))
                { 
                    if (requirements.usesReply &&
                        binding.CreateBindingElements().Find().ManualAddressing) 
                    { 
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                            SR.GetString(SR.CantCreateChannelWithManualAddressing))); 
                    }

                    return new ServiceChannelFactoryOverDuplex(customBinding.BuildChannelFactory(parameters), clientRuntime, binding);
                } 

                if (type == typeof(IOutputSessionChannel) && customBinding.CanBuildChannelFactory(parameters)) 
                { 
                    return new ServiceChannelFactoryOverOutputSession(customBinding.BuildChannelFactory(parameters), clientRuntime, binding, false);
                } 

                if (type == typeof(IRequestSessionChannel) && customBinding.CanBuildChannelFactory(parameters))
                {
                    return new ServiceChannelFactoryOverRequestSession(customBinding.BuildChannelFactory(parameters), clientRuntime, binding, false); 
                }
 
                if (type == typeof(IDuplexSessionChannel) && customBinding.CanBuildChannelFactory(parameters)) 
                {
                    if (requirements.usesReply && 
                        binding.CreateBindingElements().Find().ManualAddressing)
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                            SR.GetString(SR.CantCreateChannelWithManualAddressing))); 
                    }
 
                    return new ServiceChannelFactoryOverDuplexSession(customBinding.BuildChannelFactory(parameters), clientRuntime, binding); 
                }
            } 

            foreach (Type type in requiredChannels)
            {
                // For SessionMode.Allowed or SessionMode.NotAllowed we will accept session-ful variants as well 
                if (type == typeof(IOutputChannel) && customBinding.CanBuildChannelFactory(parameters))
                { 
                    return new ServiceChannelFactoryOverOutputSession(customBinding.BuildChannelFactory(parameters), clientRuntime, binding, true); 
                }
 
                if (type == typeof(IRequestChannel) && customBinding.CanBuildChannelFactory(parameters))
                {
                    return new ServiceChannelFactoryOverRequestSession(customBinding.BuildChannelFactory(parameters), clientRuntime, binding, true);
                } 

                // and for SessionMode.Required, it is possible that the InstanceContextProvider is handling the session management, so 
                // accept datagram variants if that is the case 
                if (type == typeof(IRequestSessionChannel) && customBinding.CanBuildChannelFactory(parameters)
                    && customBinding.GetProperty(parameters) != null) 
                {
                    return new ServiceChannelFactoryOverRequest(customBinding.BuildChannelFactory(parameters), clientRuntime, binding);
                }
            } 

            // we put a lot of work into creating a good error message, as this is a common case 
            Dictionary supportedChannels = new Dictionary(); 
            if (customBinding.CanBuildChannelFactory(parameters))
            { 
                supportedChannels.Add(typeof(IOutputChannel), 0);
            }
            if (customBinding.CanBuildChannelFactory(parameters))
            { 
                supportedChannels.Add(typeof(IRequestChannel), 0);
            } 
            if (customBinding.CanBuildChannelFactory(parameters)) 
            {
                supportedChannels.Add(typeof(IDuplexChannel), 0); 
            }
            if (customBinding.CanBuildChannelFactory(parameters))
            {
                supportedChannels.Add(typeof(IOutputSessionChannel), 0); 
            }
            if (customBinding.CanBuildChannelFactory(parameters)) 
            { 
                supportedChannels.Add(typeof(IRequestSessionChannel), 0);
            } 
            if (customBinding.CanBuildChannelFactory(parameters))
            {
                supportedChannels.Add(typeof(IDuplexSessionChannel), 0);
            } 

            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(ChannelRequirements.CantCreateChannelException( 
                supportedChannels.Keys, requiredChannels, binding.Name)); 
        }
 
        protected override void OnAbort()
        {
            IChannel channel = null;
 
            lock (ThisLock)
            { 
                channel = (channelsList.Count > 0) ? channelsList[channelsList.Count - 1] : null; 
            }
 
            while (channel != null)
            {
                channel.Abort();
 
                lock (ThisLock)
                { 
                    channelsList.Remove(channel); 
                    channel = (channelsList.Count > 0) ? channelsList[channelsList.Count - 1] : null;
                } 
            }
        }

        protected override void OnClose(TimeSpan timeout) 
        {
            TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); 
            while (true) 
            {
                int count; 
                IChannel channel;
                lock (ThisLock)
                {
                    count = channelsList.Count; 
                    if (count == 0)
                        return; 
                    channel = channelsList[0]; 
                }
                channel.Close(timeoutHelper.RemainingTime()); 
            }
        }

        protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state) 
        {
            List objectList; 
            lock (ThisLock) 
            {
                objectList = new List(); 
                for (int index = 0; index < channelsList.Count; index++)
                    objectList.Add(channelsList[index]);
            }
            return new CloseCollectionAsyncResult(timeout, callback, state, objectList); 
        }
 
        protected override void OnEndClose(IAsyncResult result) 
        {
            CloseCollectionAsyncResult.End(result); 
        }

        protected override void OnOpened()
        { 
            base.OnOpened();
            this.clientRuntime.LockDownProperties(); 
        } 

        public void ChannelCreated(IChannel channel) 
        {
            if (DiagnosticUtility.ShouldTraceVerbose)
            {
                DiagnosticUtility.DiagnosticTrace.TraceEvent(TraceEventType.Verbose, TraceCode.ChannelCreated, 
                    SR.GetString(SR.TraceCodeChannelCreated, DiagnosticTrace.CreateSourceString(channel)),
                    null, null, this); 
            } 
            lock (ThisLock)
            { 
                ThrowIfDisposed();
                channelsList.Add(channel);
            }
        } 

        public void ChannelDisposed(IChannel channel) 
        { 
            if (DiagnosticUtility.ShouldTraceVerbose)
            { 
                DiagnosticUtility.DiagnosticTrace.TraceEvent(TraceEventType.Verbose, TraceCode.ChannelDisposed,
                    SR.GetString(SR.TraceCodeChannelDisposed, DiagnosticTrace.CreateSourceString(channel)),
                    null, null, this);
            } 
            lock (ThisLock)
            { 
                channelsList.Remove(channel); 
            }
        } 

        public virtual ServiceChannel CreateServiceChannel(EndpointAddress address, Uri via)
        {
            IChannelBinder binder = this.CreateInnerChannelBinder(address, via); 
            ServiceChannel serviceChannel = new ServiceChannel(this, binder);
 
            if (binder is DuplexChannelBinder) 
            {
                DuplexChannelBinder duplexChannelBinder = binder as DuplexChannelBinder; 
                duplexChannelBinder.ChannelHandler = new ChannelHandler(this.messageVersion, binder, serviceChannel);
                duplexChannelBinder.DefaultCloseTimeout = this.DefaultCloseTimeout;
                duplexChannelBinder.DefaultSendTimeout = this.DefaultSendTimeout;
                duplexChannelBinder.IdentityVerifier = this.clientRuntime.IdentityVerifier; 
            }
 
            return serviceChannel; 
        }
 
        public TChannel CreateChannel(EndpointAddress address)
        {
            return this.CreateChannel(address, null);
        } 

        public TChannel CreateChannel(EndpointAddress address, Uri via) 
        { 
            if(!this.CanCreateChannel())
            { 
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                    SR.GetString(SR.CouldnTCreateChannelForChannelType2, this.bindingName, typeof(TChannel).Name)));
            }
 
            return (TChannel)this.CreateChannel(typeof(TChannel), address, via);
        } 
 
        public abstract bool CanCreateChannel();
 
        public object CreateChannel(Type channelType, EndpointAddress address)
        {
            return this.CreateChannel(channelType, address, null);
        } 

        public object CreateChannel(Type channelType, EndpointAddress address, Uri via) 
        { 
            if (via == null)
            { 
                via = this.ClientRuntime.Via;

                if (via == null)
                    via = address.Uri; 
            }
 
            ServiceChannel serviceChannel = this.CreateServiceChannel(address, via); 

            serviceChannel.Proxy = CreateProxy(channelType, channelType, MessageDirection.Input, serviceChannel); 

            serviceChannel.ClientRuntime.GetRuntime().InitializeChannel((IClientChannel)serviceChannel.Proxy);
            OperationContext current = OperationContext.Current;
            if ((current != null) && (current.InstanceContext != null)) 
            {
                current.InstanceContext.WmiChannels.Add((IChannel)serviceChannel.Proxy); 
                serviceChannel.WmiInstanceContext = current.InstanceContext; 
            }
 
            return serviceChannel.Proxy;
        }

        ///  
        /// Critical - constructs a ServiceChannelProxy, which is Critical
        /// Safe - returns the TP, but does not return the RealProxy -- caller can't get from TP to RP without an elevation 
        ///  
        [SecurityCritical, SecurityTreatAsSafe]
        internal static object CreateProxy(Type interfaceType, Type proxiedType, MessageDirection direction, ServiceChannel serviceChannel) 
        {
            if (!proxiedType.IsInterface)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString("SFxChannelFactoryTypeMustBeInterface"))); 
            }
            ServiceChannelProxy proxy = new ServiceChannelProxy(interfaceType, proxiedType, direction, serviceChannel); 
            return proxy.GetTransparentProxy(); 
        }
 
        /// 
        /// Critical - calls LinkDemand method RemotingServices.GetRealProxy and access critical class ServiceChannelProxy
        /// Safe - gets the ServiceChannel (which is not critical) and discards the RealProxy
        ///  
        [SecurityCritical, SecurityTreatAsSafe]
        internal static ServiceChannel GetServiceChannel(object transparentProxy) 
        { 
            ServiceChannelProxy proxy = RemotingServices.GetRealProxy(transparentProxy) as ServiceChannelProxy;
 
            if (proxy != null)
                return proxy.GetServiceChannel();
            else
                return null; 
        }
 
        protected abstract IChannelBinder CreateInnerChannelBinder(EndpointAddress address, Uri via); 

        abstract class TypedServiceChannelFactory : ServiceChannelFactory 
            where TChannel : class, IChannel
        {
            IChannelFactory innerChannelFactory;
 
            protected TypedServiceChannelFactory(IChannelFactory innerChannelFactory,
                ClientRuntime clientRuntime, Binding binding) 
                : base(clientRuntime, binding) 
            {
                this.innerChannelFactory = innerChannelFactory; 
            }

            protected IChannelFactory InnerChannelFactory
            { 
                get { return this.innerChannelFactory; }
            } 
 
            protected override void OnAbort()
            { 
                base.OnAbort();
                this.innerChannelFactory.Abort();
            }
 
            protected override void OnOpen(TimeSpan timeout)
            { 
                this.innerChannelFactory.Open(timeout); 
            }
 
            protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
            {
                return this.innerChannelFactory.BeginOpen(timeout, callback, state);
            } 

            protected override void OnEndOpen(IAsyncResult result) 
            { 
                this.innerChannelFactory.EndOpen(result);
            } 

            protected override void OnClose(TimeSpan timeout)
            {
                TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); 
                base.OnClose(timeoutHelper.RemainingTime());
                this.innerChannelFactory.Close(timeoutHelper.RemainingTime()); 
            } 

            protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state) 
            {
                return new ChainedAsyncResult(timeout, callback, state, base.OnBeginClose, base.OnEndClose,
                    this.innerChannelFactory.BeginClose, this.innerChannelFactory.EndClose);
            } 

            protected override void OnEndClose(IAsyncResult result) 
            { 
                ChainedAsyncResult.End(result);
            } 

            public override T GetProperty()
            {
                if (typeof(T) == typeof(TypedServiceChannelFactory)) 
                {
                    return (T)(object)this; 
                } 

                T baseProperty = base.GetProperty(); 
                if (baseProperty != null)
                {
                    return baseProperty;
                } 

                return this.innerChannelFactory.GetProperty(); 
            } 
        }
 
        class ServiceChannelFactoryOverOutput : TypedServiceChannelFactory
        {
            public ServiceChannelFactoryOverOutput(IChannelFactory innerChannelFactory, ClientRuntime clientRuntime, Binding binding)
                : base(innerChannelFactory, clientRuntime, binding) 
            {
            } 
 
            protected override IChannelBinder CreateInnerChannelBinder(EndpointAddress to, Uri via)
            { 
                return new OutputChannelBinder(this.InnerChannelFactory.CreateChannel(to, via));
            }

            public override bool CanCreateChannel() 
            {
                return (typeof(TChannel) == typeof(IOutputChannel) 
                    || typeof(TChannel) == typeof(IRequestChannel)); 
            }
        } 

        class ServiceChannelFactoryOverDuplex : TypedServiceChannelFactory
        {
            public ServiceChannelFactoryOverDuplex(IChannelFactory innerChannelFactory, ClientRuntime clientRuntime, Binding binding) 
                : base(innerChannelFactory, clientRuntime, binding)
            { 
            } 

            protected override IChannelBinder CreateInnerChannelBinder(EndpointAddress to, Uri via) 
            {
                return new DuplexChannelBinder(this.InnerChannelFactory.CreateChannel(to, via), this.RequestReplyCorrelator);
            }
 
            public override bool CanCreateChannel()
            { 
                return (typeof(TChannel) == typeof(IOutputChannel) 
                    || typeof(TChannel) == typeof(IRequestChannel)
                    || typeof(TChannel) == typeof(IDuplexChannel)); 
            }
        }

        class ServiceChannelFactoryOverRequest : TypedServiceChannelFactory 
        {
            public ServiceChannelFactoryOverRequest(IChannelFactory innerChannelFactory, ClientRuntime clientRuntime, Binding binding) 
                : base(innerChannelFactory, clientRuntime, binding) 
            {
            } 

            protected override IChannelBinder CreateInnerChannelBinder(EndpointAddress to, Uri via)
            {
                return new RequestChannelBinder(this.InnerChannelFactory.CreateChannel(to, via)); 
            }
 
            public override bool CanCreateChannel() 
            {
                return (typeof(TChannel) == typeof(IOutputChannel) 
                    || typeof(TChannel) == typeof(IRequestChannel));
            }
        }
 
        class ServiceChannelFactoryOverOutputSession : TypedServiceChannelFactory
        { 
            bool datagramAdapter; 
            public ServiceChannelFactoryOverOutputSession(IChannelFactory innerChannelFactory, ClientRuntime clientRuntime, Binding binding, bool datagramAdapter)
                : base(innerChannelFactory, clientRuntime, binding) 
            {
                this.datagramAdapter = datagramAdapter;
            }
 
            protected override IChannelBinder CreateInnerChannelBinder(EndpointAddress to, Uri via)
            { 
                IOutputChannel channel; 

                if (this.datagramAdapter) 
                {
                    channel = DatagramAdapter.GetOutputChannel(
                        delegate() { return this.InnerChannelFactory.CreateChannel(to, via); },
                        timeouts); 
                }
                else 
                { 
                    channel = this.InnerChannelFactory.CreateChannel(to, via);
                } 

                return new OutputChannelBinder(channel);
            }
 
            public override bool CanCreateChannel()
            { 
                return (typeof(TChannel) == typeof(IOutputChannel) 
                    || typeof(TChannel) == typeof(IOutputSessionChannel)
                    || typeof(TChannel) == typeof(IRequestChannel) 
                    || typeof(TChannel) == typeof(IRequestSessionChannel));
            }
        }
 
        class ServiceChannelFactoryOverDuplexSession : TypedServiceChannelFactory
        { 
            public ServiceChannelFactoryOverDuplexSession(IChannelFactory innerChannelFactory, ClientRuntime clientRuntime, Binding binding) 
                : base(innerChannelFactory, clientRuntime, binding)
            { 
            }

            protected override IChannelBinder CreateInnerChannelBinder(EndpointAddress to, Uri via)
            { 
                return new DuplexChannelBinder(this.InnerChannelFactory.CreateChannel(to, via), this.RequestReplyCorrelator);
            } 
 
            public override bool CanCreateChannel()
            { 
                return (typeof(TChannel) == typeof(IOutputChannel)
                    || typeof(TChannel) == typeof(IRequestChannel)
                    || typeof(TChannel) == typeof(IDuplexChannel)
                    || typeof(TChannel) == typeof(IOutputSessionChannel) 
                    || typeof(TChannel) == typeof(IRequestSessionChannel)
                    || typeof(TChannel) == typeof(IDuplexSessionChannel)); 
            } 
        }
 
        class ServiceChannelFactoryOverRequestSession : TypedServiceChannelFactory
        {
            bool datagramAdapter = false;
 
            public ServiceChannelFactoryOverRequestSession(IChannelFactory innerChannelFactory, ClientRuntime clientRuntime, Binding binding, bool datagramAdapter)
                : base(innerChannelFactory, clientRuntime, binding) 
            { 
                this.datagramAdapter = datagramAdapter;
            } 

            protected override IChannelBinder CreateInnerChannelBinder(EndpointAddress to, Uri via)
            {
                IRequestChannel channel; 

                if (this.datagramAdapter) 
                { 
                    channel = DatagramAdapter.GetRequestChannel(
                        delegate() { return this.InnerChannelFactory.CreateChannel(to, via); }, 
                        this.timeouts);
                }
                else
                { 
                    channel = this.InnerChannelFactory.CreateChannel(to, via);
                } 
 
                return new RequestChannelBinder(channel);
            } 

            public override bool CanCreateChannel()
            {
                return (typeof(TChannel) == typeof(IOutputChannel) 
                    || typeof(TChannel) == typeof(IOutputSessionChannel)
                    || typeof(TChannel) == typeof(IRequestChannel) 
                    || typeof(TChannel) == typeof(IRequestSessionChannel)); 
            }
        } 

        class DefaultCommunicationTimeouts : IDefaultCommunicationTimeouts
        {
            TimeSpan closeTimeout; 
            TimeSpan openTimeout;
            TimeSpan receiveTimeout; 
            TimeSpan sendTimeout; 

            public DefaultCommunicationTimeouts(IDefaultCommunicationTimeouts timeouts) 
            {
                this.closeTimeout = timeouts.CloseTimeout;
                this.openTimeout = timeouts.OpenTimeout;
                this.receiveTimeout = timeouts.ReceiveTimeout; 
                this.sendTimeout = timeouts.SendTimeout;
            } 
 
            public TimeSpan CloseTimeout
            { 
                get { return this.closeTimeout; }
            }

            public TimeSpan OpenTimeout 
            {
                get { return this.openTimeout; } 
            } 

            public TimeSpan ReceiveTimeout 
            {
                get { return this.receiveTimeout; }
            }
 
            public TimeSpan SendTimeout
            { 
                get { return this.sendTimeout; } 
            }
        } 
    }
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK