locked
Changing namespace attributes in WSDL RRS feed

  • Question

  • Hi,

    I know that to set the various namespace attributes in the generated WSDL you have to set ServiceContract, ServiceBehavior, DataContract and BindingNamespace all over the place.

    And yes wouldnt it have been so much nicer if there was a centralised place to set these rather than using 3 different attributes and config, but i cant find one as of yet.

    The problem is that the setting of these namespaces using attributes means its the developer who has to make the decision to put it in the code and get it correct everytime. So what i am trying to do is write a service behavior that will at least replace the default tempuri.org on services where the developer has not explicitly set it. I current have a class using the IServiceBehvaior and i go through the service endpoint and change the namespace properties for the service, contract, and binding, but when i look at the wsdl it would appear that the Action elements are still using the temp namespace.

    Can anyone tell me if this is a bug or is there somewhere else i need to change as well, or can this not be done in a behvaiour etc etc

    Thanks

    Dave 

    Thursday, February 8, 2007 1:05 PM

Answers

  • The reason the Actions are unchanged is due to the 'timing' that things get created.  When you add an endpoint of type 'IMyContract', ServiceModel will reflect over the interface and attribute to create a ContractDescription.  If you don't specify values for Actions on operations, defaults are created (based on values of namespace, contract name, operation name, etc).  At this point in time, the Namespace still has the default value, so the generated Action values include that.

    Later your behavior comes along and pokes some new Namespace values into parts of the description, but doesn't update the Action values.  If you want to update the Actions, you can: from a ServiceEndpoint, got to .Contract.Operations[ n ].Action to find the values in description to inspect/modify.

    Using an IServiceBehavior to do this is a little dangerous, in that it's possible that some other behavior that runs before yours may have already taken a 'snapshot' of the description... if you want to be more robust, you can create a subclass of ServiceHost and do this description-massaging in host.OnOpening().

    Thursday, February 8, 2007 5:37 PM
  • Sorry, I didn't realize the MessageDescription OM was so hard to work with.

    Here is a full code sample that I think does what you want.  Note that the namespace changes project into the wsdl, so a svcutil-generated client should 'just work'.

    using System;
    using System.ServiceModel;
    using System.ServiceModel.Description;
    using System.ServiceModel.Dispatcher;
    using System.ServiceModel.Channels;

    [ServiceContract]
    public interface IMyContract
    {
        [OperationContract]
        string echo(string s);
    }

    public class MyService : IMyContract
    {
        public string echo(string s) { return s; }
    }

    public class MyServiceHost : ServiceHost
    {
        public MyServiceHost(Type t, params Uri[] baseAddresses) : base(t, baseAddresses) { }
        protected override void OnOpening()
        {
            this.Description.Namespace = MyNs;
            foreach (ServiceEndpoint e in this.Description.Endpoints)
            {
                ModifyDescriptionNamespaces(e);
            }
            base.OnOpening();
        }
        static readonly string MyNs = "http://myns/";
        public static void ModifyDescriptionNamespaces(ServiceEndpoint e)
        {
            e.Binding.Namespace = MyNs;
            ContractDescription cd = e.Contract;
            cd.Namespace = MyNs;
            foreach (OperationDescription od in cd.Operations)
            {
                ModifyAction(od.Messages, 0);
                if (od.Messages.Count > 1)
                {
                    ModifyAction(od.Messages, 1);
                }
            }
        }
        static void ModifyAction(MessageDescriptionCollection mdc, int index)
        {
            MessageDescription md = mdc[index];
            string originalAction = md.Action;
            string newAction = originalAction;
            if (originalAction.StartsWith("http://tempuri.org/"))
            {
                newAction = MyNs + originalAction.Substring(19);
            }
            MessageDescription newMd = new MessageDescription(newAction, md.Direction);
            foreach (MessageHeaderDescription mhd in md.Headers)
            {
                newMd.Headers.Add(mhd);
            }
            foreach (MessagePropertyDescription mpd in md.Properties)
            {
                newMd.Properties.Add(mpd);
            }
            newMd.MessageType = md.MessageType;
            newMd.ProtectionLevel = md.ProtectionLevel;
            foreach (MessagePartDescription mpd in md.Body.Parts)
            {
                newMd.Body.Parts.Add(mpd);
            }
            newMd.Body.ReturnValue = md.Body.ReturnValue;
            newMd.Body.WrapperName = md.Body.WrapperName;
            newMd.Body.WrapperNamespace = MyNs;
            mdc[index] = newMd;
        }
    }

    public class Repro
    {
        public static void Main()
        {
            Uri address = new Uri("http://localhost:8004/Service");
            Binding binding = new BasicHttpBinding();

            MyServiceHost service = new MyServiceHost(typeof(MyService), address);
            service.AddServiceEndpoint(typeof(IMyContract), binding, address);
            ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
            smb.HttpGetEnabled = true;
            service.Description.Behaviors.Add(smb);
            service.Open();

            Console.WriteLine("Service open at {0} - press a key to continue", address);
            Console.ReadKey();
            // go view the wsdl, it has the right namespaces

            ChannelFactory<IMyContract> cf = new ChannelFactory<IMyContract>(binding, new EndpointAddress(address));
            MyServiceHost.ModifyDescriptionNamespaces(cf.Endpoint);
            IMyContract proxy = cf.CreateChannel();
            Console.WriteLine(proxy.echo("Hello World"));
            ((IClientChannel)proxy).Close();

            service.Close();
            Console.WriteLine("Done, press a key");
            Console.ReadKey();
        }
    }

    Friday, February 9, 2007 6:42 PM

All replies

  • Dave,

    I don't think that's going to work. While that would certainly ensure you have a WSDL with the desired namespaces, the serialization code (DataContractSerializer) on the server side wouldn't work correctly, since it requires those attributes to serialize/deserialize request and response messages using the right namespaces.

    Since the namespaces the client knows about (from the modified WSDL) won't match what the server knows, you're gonna have problems.
    Thursday, February 8, 2007 3:57 PM
  • ok if we discount changing data contracts and message contract would that will work ?
    Thursday, February 8, 2007 4:26 PM
  • The reason the Actions are unchanged is due to the 'timing' that things get created.  When you add an endpoint of type 'IMyContract', ServiceModel will reflect over the interface and attribute to create a ContractDescription.  If you don't specify values for Actions on operations, defaults are created (based on values of namespace, contract name, operation name, etc).  At this point in time, the Namespace still has the default value, so the generated Action values include that.

    Later your behavior comes along and pokes some new Namespace values into parts of the description, but doesn't update the Action values.  If you want to update the Actions, you can: from a ServiceEndpoint, got to .Contract.Operations[ n ].Action to find the values in description to inspect/modify.

    Using an IServiceBehavior to do this is a little dangerous, in that it's possible that some other behavior that runs before yours may have already taken a 'snapshot' of the description... if you want to be more robust, you can create a subclass of ServiceHost and do this description-massaging in host.OnOpening().

    Thursday, February 8, 2007 5:37 PM
  • Brian,

    Thanks for that I will look into create my own ServiceHost class and poke around in the OnOpening method. I did look actions on the contract but it would seem that the namespace property on them read only, using reflector the set operation is set to internal :(

    It would have been great if the ServiceContract, Behavior etc attributes hadnt  been sealed as I could have done it there instead which would have a lot elegant solution to the problem.

    Thursday, February 8, 2007 8:29 PM
  • Brian,

    After much digging about it would seem this cannot be done, lookin at the code in ServiceBase it is the CreateDescription method called from the constructor that kicks off the internal process to iterate through all the contracts and operations etc. Whilst it is possible to override the CreateDescription unless there is a way to change the attributes on a type rather than an object there is no way things can be altered during the process.

    I have managed to change everything apart from the action property which is readonly :(

    As already metioned whilst I could do all of this automatically on the server I would still need to build a way to ensure the client uses the same namespaces (we are putting service interface contracts into a shared assembly) so I may take a different tract by creating my own ServiceHost that looks to see if the tempuri is present in the description and then stops the service from starting and informs the developer or the need to specifiy the namespaces.

    Friday, February 9, 2007 10:11 AM
  • Sorry, I didn't realize the MessageDescription OM was so hard to work with.

    Here is a full code sample that I think does what you want.  Note that the namespace changes project into the wsdl, so a svcutil-generated client should 'just work'.

    using System;
    using System.ServiceModel;
    using System.ServiceModel.Description;
    using System.ServiceModel.Dispatcher;
    using System.ServiceModel.Channels;

    [ServiceContract]
    public interface IMyContract
    {
        [OperationContract]
        string echo(string s);
    }

    public class MyService : IMyContract
    {
        public string echo(string s) { return s; }
    }

    public class MyServiceHost : ServiceHost
    {
        public MyServiceHost(Type t, params Uri[] baseAddresses) : base(t, baseAddresses) { }
        protected override void OnOpening()
        {
            this.Description.Namespace = MyNs;
            foreach (ServiceEndpoint e in this.Description.Endpoints)
            {
                ModifyDescriptionNamespaces(e);
            }
            base.OnOpening();
        }
        static readonly string MyNs = "http://myns/";
        public static void ModifyDescriptionNamespaces(ServiceEndpoint e)
        {
            e.Binding.Namespace = MyNs;
            ContractDescription cd = e.Contract;
            cd.Namespace = MyNs;
            foreach (OperationDescription od in cd.Operations)
            {
                ModifyAction(od.Messages, 0);
                if (od.Messages.Count > 1)
                {
                    ModifyAction(od.Messages, 1);
                }
            }
        }
        static void ModifyAction(MessageDescriptionCollection mdc, int index)
        {
            MessageDescription md = mdc[index];
            string originalAction = md.Action;
            string newAction = originalAction;
            if (originalAction.StartsWith("http://tempuri.org/"))
            {
                newAction = MyNs + originalAction.Substring(19);
            }
            MessageDescription newMd = new MessageDescription(newAction, md.Direction);
            foreach (MessageHeaderDescription mhd in md.Headers)
            {
                newMd.Headers.Add(mhd);
            }
            foreach (MessagePropertyDescription mpd in md.Properties)
            {
                newMd.Properties.Add(mpd);
            }
            newMd.MessageType = md.MessageType;
            newMd.ProtectionLevel = md.ProtectionLevel;
            foreach (MessagePartDescription mpd in md.Body.Parts)
            {
                newMd.Body.Parts.Add(mpd);
            }
            newMd.Body.ReturnValue = md.Body.ReturnValue;
            newMd.Body.WrapperName = md.Body.WrapperName;
            newMd.Body.WrapperNamespace = MyNs;
            mdc[index] = newMd;
        }
    }

    public class Repro
    {
        public static void Main()
        {
            Uri address = new Uri("http://localhost:8004/Service");
            Binding binding = new BasicHttpBinding();

            MyServiceHost service = new MyServiceHost(typeof(MyService), address);
            service.AddServiceEndpoint(typeof(IMyContract), binding, address);
            ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
            smb.HttpGetEnabled = true;
            service.Description.Behaviors.Add(smb);
            service.Open();

            Console.WriteLine("Service open at {0} - press a key to continue", address);
            Console.ReadKey();
            // go view the wsdl, it has the right namespaces

            ChannelFactory<IMyContract> cf = new ChannelFactory<IMyContract>(binding, new EndpointAddress(address));
            MyServiceHost.ModifyDescriptionNamespaces(cf.Endpoint);
            IMyContract proxy = cf.CreateChannel();
            Console.WriteLine(proxy.echo("Hello World"));
            ((IClientChannel)proxy).Close();

            service.Close();
            Console.WriteLine("Done, press a key");
            Console.ReadKey();
        }
    }

    Friday, February 9, 2007 6:42 PM