Merge pull request #4453 from lambdageek/bug-49721
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel / ServiceHostBase.cs
index a132663fe82c0228736dcfcb45f6d0d09430662d..1a84cecc5842e18f6e3e6df25253cbea370641fd 100644 (file)
@@ -126,7 +126,7 @@ namespace System.ServiceModel
                        get { return description; }
                }
 
-               protected IDictionary<string,ContractDescription> ImplementedContracts {
+               protected internal IDictionary<string,ContractDescription> ImplementedContracts {
                        get { return contracts; }
                }
 
@@ -189,20 +189,41 @@ namespace System.ServiceModel
                        string implementedContract, Binding binding,
                        Uri address)
                {
-                       return AddServiceEndpoint (implementedContract, binding, address, address);
+                       return AddServiceEndpoint (implementedContract, binding, address, null);
                }
 
                public ServiceEndpoint AddServiceEndpoint (
                        string implementedContract, Binding binding,
                        Uri address, Uri listenUri)
                {
-                       EndpointAddress ea = BuildEndpointAddress (address, binding);
+                       EndpointAddress ea = new EndpointAddress (BuildAbsoluteUri (address, binding));
                        ContractDescription cd = GetContract (implementedContract, binding.Namespace == "http://schemas.microsoft.com/ws/2005/02/mex/bindings");
                        if (cd == null)
                                throw new InvalidOperationException (String.Format ("Contract '{0}' was not found in the implemented contracts in this service host.", implementedContract));
                        return AddServiceEndpointCore (cd, binding, ea, listenUri);
                }
 
+               public virtual void AddServiceEndpoint (ServiceEndpoint endpoint)
+               {
+                       if (endpoint == null)
+                               throw new ArgumentNullException ("endpoint");
+
+                       ThrowIfDisposedOrImmutable ();
+
+                       if (endpoint.Address == null)
+                               throw new ArgumentException ("Address on the argument endpoint is null");
+                       if (endpoint.Contract == null)
+                               throw new ArgumentException ("Contract on the argument endpoint is null");
+                       if (endpoint.Binding == null)
+                               throw new ArgumentException ("Binding on the argument endpoint is null");
+
+                       if (!ImplementedContracts.Values.Any (cd => cd.ContractType == endpoint.Contract.ContractType) &&
+                           endpoint.Binding.Namespace != "http://schemas.microsoft.com/ws/2005/02/mex/bindings") // special case
+                               throw new InvalidOperationException (String.Format ("Contract '{0}' is not implemented in this service '{1}'", endpoint.Contract.Name, Description.Name));
+
+                       Description.Endpoints.Add (endpoint);
+               }
+
                Type PopulateType (string typeName)
                {
                        Type type = Type.GetType (typeName);
@@ -258,7 +279,7 @@ namespace System.ServiceModel
                        return null;
                }
 
-               internal EndpointAddress BuildEndpointAddress (Uri address, Binding binding)
+               internal Uri BuildAbsoluteUri (Uri address, Binding binding)
                {
                        if (!address.IsAbsoluteUri) {
                                // Find a Base address with matching scheme,
@@ -272,17 +293,21 @@ namespace System.ServiceModel
                                        baseaddr = new Uri (baseaddr.AbsoluteUri + "/");
                                address = new Uri (baseaddr, address);
                        }
-                       return new EndpointAddress (address);
+                       return address;
                }
 
                internal ServiceEndpoint AddServiceEndpointCore (
                        ContractDescription cd, Binding binding, EndpointAddress address, Uri listenUri)
                {
+                       if (listenUri != null)
+                               listenUri = BuildAbsoluteUri (listenUri, binding);
+
                        foreach (ServiceEndpoint e in Description.Endpoints)
                                if (e.Contract == cd && e.Binding == binding && e.Address == address && e.ListenUri.Equals (listenUri))
                                        return e;
                        ServiceEndpoint se = new ServiceEndpoint (cd, binding, address);
-                       se.ListenUri = listenUri.IsAbsoluteUri ? listenUri : new Uri (address.Uri, listenUri);
+                       // FIXME: should we reject relative ListenUri?
+                       se.ListenUri = listenUri ?? address.Uri;
                        Description.Endpoints.Add (se);
                        return se;
                }
@@ -293,41 +318,11 @@ namespace System.ServiceModel
                                throw new InvalidOperationException ("ApplyConfiguration requires that the Description property be initialized. Either provide a valid ServiceDescription in the CreateDescription method or override the ApplyConfiguration method to provide an alternative implementation");
 
                        ServiceElement service = GetServiceElement ();
-
-                       //TODO: Should we call here LoadServiceElement ?
-                       if (service != null) {
-                               
-                               //base addresses
-                               HostElement host = service.Host;
-                               foreach (BaseAddressElement baseAddress in host.BaseAddresses) {
-                                       AddBaseAddress (new Uri (baseAddress.BaseAddress));
-                               }
-
-                               // behaviors
-                               // TODO: use EvaluationContext of ServiceElement.
-                               ServiceBehaviorElement behavior = ConfigUtil.BehaviorsSection.ServiceBehaviors [service.BehaviorConfiguration];
-                               if (behavior != null) {
-                                       foreach (var bxe in behavior) {
-                                               IServiceBehavior b = (IServiceBehavior) bxe.CreateBehavior ();
-                                               Description.Behaviors.Add (b);
-                                       }
-                               }
-
-                               // services
-                               foreach (ServiceEndpointElement endpoint in service.Endpoints) {
-                                       ServiceEndpoint se = AddServiceEndpoint (
-                                               endpoint.Contract,
-                                               ConfigUtil.CreateBinding (endpoint.Binding, endpoint.BindingConfiguration),
-                                               endpoint.Address.ToString ());
-                                       // endpoint behaviors
-                                       EndpointBehaviorElement epbehavior = ConfigUtil.BehaviorsSection.EndpointBehaviors [endpoint.BehaviorConfiguration];
-                                       if (epbehavior != null)
-                                               foreach (var bxe in epbehavior) {
-                                                       IEndpointBehavior b = (IEndpointBehavior) bxe.CreateBehavior ();
-                                                       se.Behaviors.Add (b);
-                                       }
-                               }
-                       }
+                       
+                       if (service != null)
+                               LoadConfigurationSection (service);
+                       // simplified configuration
+                       AddServiceBehaviors (String.Empty, false);
                        // TODO: consider commonBehaviors here
 
                        // ensure ServiceAuthorizationBehavior
@@ -345,6 +340,71 @@ namespace System.ServiceModel
                        }
                }
 
+               void AddServiceBehaviors (string configurationName, bool throwIfNotFound)
+               {
+                       if (configurationName == null)
+                               return;
+                       ServiceBehaviorElement behavior = ConfigUtil.BehaviorsSection.ServiceBehaviors [configurationName];
+                       if (behavior == null) {
+                               if (throwIfNotFound)
+                                       throw new ArgumentException (String.Format ("Service behavior configuration '{0}' was not found", configurationName));
+                               return;
+                       }
+
+                       KeyedByTypeCollection<IServiceBehavior> behaviors = Description.Behaviors;
+                       foreach (var bxe in behavior) {
+                               IServiceBehavior b = (IServiceBehavior) bxe.CreateBehavior ();
+                               if (behaviors.Contains (b.GetType ()))
+                                       continue;
+                               behaviors.Add (b);
+                       }
+               }
+               
+               void ApplyServiceElement (ServiceElement service)
+               {
+                       //base addresses
+                       HostElement host = service.Host;
+                       foreach (BaseAddressElement baseAddress in host.BaseAddresses) {
+                               AddBaseAddress (new Uri (baseAddress.BaseAddress));
+                       }
+
+                       // behaviors
+                       AddServiceBehaviors (service.BehaviorConfiguration, true);
+
+                       // endpoints
+                       foreach (ServiceEndpointElement endpoint in service.Endpoints) {
+                               ServiceEndpoint se;
+
+                               var binding = String.IsNullOrEmpty (endpoint.Binding) ? null : ConfigUtil.CreateBinding (endpoint.Binding, endpoint.BindingConfiguration);
+
+                               if (!String.IsNullOrEmpty (endpoint.Kind)) {
+                                       var contract = String.IsNullOrEmpty (endpoint.Contract) ? null : GetContract (endpoint.Contract, false);
+                                       se = ConfigUtil.ConfigureStandardEndpoint (contract, endpoint);
+                                       if (se.Binding == null)
+                                               se.Binding = binding;
+                                       if (se.Address == null && se.Binding != null) // standard endpoint might have empty address
+                                               se.Address = new EndpointAddress (CreateUri (se.Binding.Scheme, endpoint.Address));
+                                       if (se.Binding == null && se.Address != null) // look for protocol mapping
+                                               se.Binding = ConfigUtil.GetBindingByProtocolMapping (se.Address.Uri);
+
+                                       AddServiceEndpoint (se);
+                               }
+                               else {
+                                       if (binding == null && endpoint.Address != null) // look for protocol mapping
+                                               binding = ConfigUtil.GetBindingByProtocolMapping (endpoint.Address);
+                                       se = AddServiceEndpoint (endpoint.Contract, binding, endpoint.Address);
+                               }
+
+                               // endpoint behaviors
+                               EndpointBehaviorElement epbehavior = ConfigUtil.BehaviorsSection.EndpointBehaviors [endpoint.BehaviorConfiguration];
+                               if (epbehavior != null)
+                                       foreach (var bxe in epbehavior) {
+                                               IEndpointBehavior b = (IEndpointBehavior) bxe.CreateBehavior ();
+                                               se.Behaviors.Add (b);
+                               }
+                       }
+               }
+
                private ServiceElement GetServiceElement() {
                        Type serviceType = Description.ServiceType;
                        if (serviceType == null)
@@ -379,7 +439,6 @@ namespace System.ServiceModel
                        Description.Endpoints.CopyTo (endPoints, 0);
                        var builder = new DispatcherBuilder (this);
                        foreach (ServiceEndpoint se in endPoints) {
-
                                var commonParams = new BindingParameterCollection ();
                                foreach (IServiceBehavior b in Description.Behaviors)
                                        b.AddBindingParameters (Description, this, Description.Endpoints, commonParams);
@@ -415,19 +474,32 @@ namespace System.ServiceModel
                        foreach (ServiceEndpoint endPoint in Description.Endpoints)
                                endPoint.Validate ();
 
-                       if (Description.Endpoints.FirstOrDefault (e => e.Contract != mex_contract) == null)
-                               throw new InvalidOperationException ("The ServiceHost must have at least one application endpoint (that does not include metadata exchange contract) defined by either configuration, behaviors or call to AddServiceEndpoint methods.");
+                       // In 4.0, it seems that if there is no configured ServiceEndpoint, infer them from the service type.
+                       if (Description.Endpoints.Count == 0) {
+                               foreach (Type iface in Description.ServiceType.GetInterfaces ())
+                                       if (iface.GetCustomAttributes (typeof (ServiceContractAttribute), true).Length > 0)
+                                               foreach (var baddr in BaseAddresses) {
+                                                       if (!baddr.IsAbsoluteUri)
+                                                               continue;
+                                                       var binding = ConfigUtil.GetBindingByProtocolMapping (baddr);
+                                                       if (binding == null)
+                                                               continue;
+                                                       AddServiceEndpoint (iface.FullName, binding, baddr);
+                                               }
+                       }
+
+                       if (Description.Endpoints.FirstOrDefault (e => e.Contract != mex_contract && !e.IsSystemEndpoint) == null)
+                               throw new InvalidOperationException ("The ServiceHost must have at least one application endpoint (that does not include metadata exchange endpoint) defined by either configuration, behaviors or call to AddServiceEndpoint methods.");
                }
 
-               [MonoTODO]
-               protected void LoadConfigurationSection (ServiceElement element)
+               protected void LoadConfigurationSection (ServiceElement serviceSection)
                {
-                       ServicesSection services = ConfigUtil.ServicesSection;
+                       ApplyServiceElement (serviceSection);
                }
 
-               [MonoTODO]
                protected override sealed void OnAbort ()
                {
+                       OnCloseOrAbort (TimeSpan.Zero);
                }
 
                Action<TimeSpan> close_delegate;
@@ -450,6 +522,11 @@ namespace System.ServiceModel
                }
 
                protected override void OnClose (TimeSpan timeout)
+               {
+                       OnCloseOrAbort (timeout);
+               }
+               
+               void OnCloseOrAbort (TimeSpan timeout)
                {
                        DateTime start = DateTime.Now;
                        ReleasePerformanceCounters ();
@@ -479,7 +556,8 @@ namespace System.ServiceModel
                                        var cd2 = ChannelDispatchers [j];
                                        if (cd1.IsMex || cd2.IsMex)
                                                continue;
-                                       if (cd1.Listener.Uri.Equals (cd2.Listener.Uri))
+                                       // surprisingly, some ChannelDispatcherBase implementations have null Listener property.
+                                       if (cd1.Listener != null && cd2.Listener != null && cd1.Listener.Uri.Equals (cd2.Listener.Uri))
                                                throw new InvalidOperationException ("Two or more service endpoints with different Binding instance are bound to the same listen URI.");
                                }
                        }
@@ -594,6 +672,8 @@ namespace System.ServiceModel
                        foreach (IEndpointBehavior b in endPoint.Behaviors)
                                b.ApplyDispatchBehavior (endPoint, ed);
                        foreach (OperationDescription operation in endPoint.Contract.Operations) {
+                               if (operation.InCallbackContract)
+                                       continue; // irrelevant
                                foreach (IOperationBehavior b in operation.Behaviors)
                                        b.ApplyDispatchBehavior (operation, ed.DispatchRuntime.Operations [operation.Name]);
                        }