Documentation Center

Best practices using .SVC Web Service in C#

This topic contains a small example on how to optimize the creation for a SVC. web service.

Before you begin

Prerequisites
This working example was created in October 2015 using the following prerequisites:
  • Microsoft .NET FrameWork
  • Microsoft Visual Studio
Web service URL
Make sure you know the full URL to the Content Manager web services which is a combination of the following input parameters:
  • baseurl containing something in the line of https://ish.example.com (where ish refers to an example server dedicated to Content Manager).
  • infosharewswebappname which contains the name of the website for the Content Manager web service (e.g. ISHWS).

This URL (e.g. https://ish.example.com/ISHWS/) is referenced below as WebServiceURL and should always use the https schema.

WS Trust URL and binding type

Make sure you know the full URL to the WS Trust endpoint provided by the integrated Security Token Service. This is can be found from the input parameters issuerwstrustendpointurl and issuerwstrustbindingtype.

These values are referenced below as IssuerURL and IssuerBinding respectively.

About this task

By creating WCF proxies (as per the following basic example) makes the WCF pipeline taking charge of tokens provisioning without your intervention. Also the target URL is extrapolated by the configuration file. This is sufficient to get someone started but this level of automation have drawbacks.

This example describes how to make WCF pipeline take ownership of issuing tokens and then use them. The example also shows how to control the WebServiceURL from within the code.

Once the token is explicitly acquired, then it can be re-purposed to drive the proxy generation of other ISHWS.SVC proxies as long as it is still valid. The benefit is a performance boost.

To issue a token you need to consume the IssuerURL provided by the Security Token Service using the correct IssuerBinding. WCF provides most of the grunt work. The main drivers for the WCF pipeline are IssuerBinding and an artefact known as RequestSecurityToken. It is the code's responsibility to initialize these values.

The extra control provides the following benefits:

  • Dynamically control the execution of the target Content Manager repository by using valid combinations of WebServiceURL, IssuerURL and IssuerBinding.
  • Gain performance. A token issued for one WebServiceURL can be used for all derived .SVC proxies. Getting a token is expensive to repeat for each proxy.
  • In an advanced case you can consume a Content Manager repository through the ISHWS API using different authentication scheme than the one the end user uses.

Procedure

  1. Create a Visual Studio Project, for example, a Console Application. Default namespace is ConsoleApplication1.
  2. Navigate to the project then right click on References section.
  3. Add a web service reference to the Application25 web service:
    1. Select Add Service Reference...
    2. In the Adress text box type the WebServiceURL followed by /Wcf/API25/application.svc
      For example: https://ish.example.com/ISHWS/Wcf/API25/application.svc
    3. Enter in the Namespace, the value Application25ServiceReference
  4. Add a web service reference to the User25 web service:
    1. Select Add Service Reference...
    2. In the Adress text box, enter the WebServiceURL followed by /Wcf/API25/user.svc
      For example: https://ish.example.com/ISHWS/Wcf/API25/user.svc
    3. Enter in the Namespace, the value User25ServiceReference
  5. Add an additional assembly reference to the project to help issue tokens. The name of the assembly is System.IdentityModel
  6. Add extra namespaces in the using section.
    
    using System;
    using System.Collections.Generic;
    using System.IdentityModel.Protocols.WSTrust;
    using System.IdentityModel.Tokens;
    using System.Linq;
    using System.Net;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Security;
    using System.Text;
    using System.Threading.Tasks;
    
  7. Add the following code fragment that can issue tokens.
            /// <summary>
            /// Enumeruation for different authentication types
            /// </summary>
            enum enAuthenticationType
            {
                //Windows based authentication
                WindowsMixed,
                //User name / password based authentication
                UsernameMixed
            }
            /// <summary>
            /// Issues a token
            /// </summary>
            /// <param name="issuerUri">The uri of the sts that issues token. 
            /// <remarks>The endpoint is explicit to the authentication type and depends on the STS</remarks>
            /// </param>
            /// <param name="authenticationType">The authentication type that matches the <paramref name="issuerUri"/></param>
            /// <param name="credential">Used to drive the credentials to authenticate. When <paramref name="authenticationType"/> is Windows then this value is ignored and can be null</param>
            /// <param name="ishWSUri">The uri of the ISHWS.</param>
            /// <returns></returns>
            static GenericXmlSecurityToken IssueToken(Uri issuerUri, enAuthenticationType authenticationType, NetworkCredential credential, Uri ishWSUri)
            {
                var appliesTo = ishWSUri.AbsoluteUri;
                var requestSecurityToken = new RequestSecurityToken
                {
                    RequestType = RequestTypes.Issue,
                    AppliesTo = new EndpointReference(appliesTo),
                    KeyType = System.IdentityModel.Protocols.WSTrust.KeyTypes.Symmetric
                };
    
                WS2007HttpBinding issuerBinding = new WS2007HttpBinding(SecurityMode.TransportWithMessageCredential);
                issuerBinding.Security.Message.EstablishSecurityContext = false;
                issuerBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
    
                switch (authenticationType)
                {
                    case enAuthenticationType.WindowsMixed:
                        issuerBinding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;
                        break;
                    case enAuthenticationType.UsernameMixed:
                        issuerBinding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
                        break;
                }
    
                var issuerEndpointAddress = new EndpointAddress(issuerUri);
                using (var factory = new WSTrustChannelFactory(issuerBinding, issuerEndpointAddress))
                {
                    if (authenticationType == enAuthenticationType.UsernameMixed)
                    {
                        factory.Credentials.UserName.UserName = credential.UserName;
                        factory.Credentials.UserName.Password = credential.Password;
                    }
    
                    factory.TrustVersion = TrustVersion.WSTrust13;
                    factory.Credentials.SupportInteractive = false;
    
                    WSTrustChannel channel = null;
                    try
                    {
                        channel = (WSTrustChannel)factory.CreateChannel();
                        RequestSecurityTokenResponse requestSecurityTokenResponse;
                        return channel.Issue(requestSecurityToken, out requestSecurityTokenResponse) as GenericXmlSecurityToken;
                    }
                    finally
                    {
                        if (channel != null)
                        {
                            channel.Abort();
                        }
                        factory.Abort();
                    }
                }
            }
    
  8. Inside the Main method you should now be able to write something in the lines of:
    
            static void Main(string[] args)
            {
                //Define the target ISHWS uri. This is used from as the AppliesTo parameter
                var ishWSUri = new Uri(@"https://ish.example.com/ISHWS/");
                //Load the https binding from the configuration
                var ishWSBinding = new CustomBinding("CustomBinding_Application1");
                //Define the target API25/Application endpoint
                var application25Uri = new Uri(ishWSUri, "Wcf/API25/Application.svc");
                //Define the target API25/User endpoint
                var user25Uri = new Uri(ishWSUri, "Wcf/API25/User.svc");
    
                //Define the target ws-trust endpoint that issues tokens
                var issuerUri = new Uri(@"https://ish.example.com/ISHSTS/issue/wstrust/mixed/username");
                //Define username/password credentials. 
                var credential = new NetworkCredential("admin", "admin");
                var authenticationType = enAuthenticationType.UsernameMixed;
    
                //For Windows Authentication use the following
                //Define the target ws-trust endpoint that issues tokens
                //var issuerUri = new Uri(@"https://adfs.example.com/adfs/services/trust/13/windowsmixed");
                //NetworkCredential credential = null;
                //var authenticationType = enAuthenticationType.Windows;
    
    
                //Issue a token
                var token = IssueToken(issuerUri, authenticationType, credential, ishWSUri);
    
                //Create a client
                var applicationClient = new Application25ServiceReference.ApplicationClient(ishWSBinding, new EndpointAddress(application25Uri));
                //Create a channel from the client and inject the token.
                var application25Channel = applicationClient.ChannelFactory.CreateChannelWithIssuedToken(token);
    
                var version = application25Channel.GetVersion();
    
                Console.WriteLine(version);
    
                //Reuse a token for another client
                var userClient = new User25ServiceReference.UserClient(ishWSBinding, new EndpointAddress(user25Uri));
                //Create a channel from the client and inject the token.
                var user25Channel = userClient.ChannelFactory.CreateChannelWithIssuedToken(token);
    
                // Create requested metadata xml
                string xmlRequestedMetadata = "<ishfields>" +
                                                  "<ishfield name='USERNAME' level='none'/>" +
                                                  "<ishfield name='FISHUSERDISPLAYNAME' level='none'/>" +
                                                  "<ishfield name='FISHEMAIL' level='none'/>" +
                                                  "<ishfield name='FUSERGROUP' level='none'/>" +
                                                  "<ishfield name='FISHUSERROLES' level='none' ishvaluetype='element'/>" +
                                                  "<ishfield name='FISHEXTERNALID' level='none'/>" +
                                                "</ishfields>";
    
                // Execute the GetMyMetadata call
                string xmlObjectList = user25Channel.GetMyMetadata(xmlRequestedMetadata);
    
                Console.WriteLine(xmlObjectList);
    
                Console.ReadLine();
            }
    
    The above code does the following:
    1. Initialize the URIs for WebServiceURL.
    2. Initialize the URIs, binding and credentials for the IssuerURL.
    3. Call the IssueToken to get a token. Authentication on the Security Token Service happens in this call. Authentication failure should have an explicit exception handler.

      Here we use the WebServiceURL to drive the AppliesTo value. This makes the token easy to re-purpose between different proxies.

    4. Use the token to create a proxy (application25Channel) for the Wcf/API25/Application.svc.
    5. Call the GetVersion of Wcf/API25/Application.svc and output the result to the console.
    6. Use the same token to create a proxy (user25Channel)for the Wcf/API25/User.svc. This is where performance is gained.
    7. Call the GetMyMetadata of Wcf/API25/User.svc and output the result to the console.

Results

The entire file looks like this

using System;
using System.Collections.Generic;
using System.IdentityModel.Protocols.WSTrust;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Security;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    static class Program
    {
        static void Main(string[] args)
        {
            //Define the target ISHWS uri. This is used from as the AppliesTo parameter
            var ishWSUri = new Uri(@"https://ish.example.com/ISHWS/");
            //Load the https binding from the configuration
            var ishWSBinding = new CustomBinding("CustomBinding_Application1");
            //Define the target API25/Application endpoint
            var application25Uri = new Uri(ishWSUri, "Wcf/API25/Application.svc");
            //Define the target API25/User endpoint
            var user25Uri = new Uri(ishWSUri, "Wcf/API25/User.svc");

            //Define the target ws-trust endpoint that issues tokens
            var issuerUri = new Uri(@"https://ish.example.com/ISHSTS/issue/wstrust/mixed/username");
            //Define username/password credentials. 
            var credential = new NetworkCredential("admin", "admin");
            var authenticationType = enAuthenticationType.UsernameMixed;

            //For Windows Authentication use the following
            //Define the target ws-trust endpoint that issues tokens
            //var issuerUri = new Uri(@"https://adfs.example.com/adfs/services/trust/13/windowsmixed");
            //NetworkCredential credential = null;
            //var authenticationType = enAuthenticationType.Windows;


            //Issue a token
            var token = IssueToken(issuerUri, authenticationType, credential, ishWSUri);

            //Create a client
            var applicationClient = new Application25ServiceReference.ApplicationClient(ishWSBinding, new EndpointAddress(application25Uri));
            //Create a channel from the client and inject the token.
            var application25Channel = applicationClient.ChannelFactory.CreateChannelWithIssuedToken(token);

            var version = application25Channel.GetVersion();

            Console.WriteLine(version);

            //Reuse a token for another client
            var userClient = new User25ServiceReference.UserClient(ishWSBinding, new EndpointAddress(user25Uri));
            //Create a channel from the client and inject the token.
            var user25Channel = userClient.ChannelFactory.CreateChannelWithIssuedToken(token);

            // Create requested metadata xml
            string xmlRequestedMetadata = "<ishfields>" +
                                              "<ishfield name='USERNAME' level='none'/>" +
                                              "<ishfield name='FISHUSERDISPLAYNAME' level='none'/>" +
                                              "<ishfield name='FISHEMAIL' level='none'/>" +
                                              "<ishfield name='FUSERGROUP' level='none'/>" +
                                              "<ishfield name='FISHUSERROLES' level='none' ishvaluetype='element'/>" +
                                              "<ishfield name='FISHEXTERNALID' level='none'/>" +
                                            "</ishfields>";

            // Execute the GetMyMetadata call
            string xmlObjectList = user25Channel.GetMyMetadata(xmlRequestedMetadata);

            Console.WriteLine(xmlObjectList);

            Console.ReadLine();
        }

        /// <summary>
        /// Enumeruation for different authentication types
        /// </summary>
        enum enAuthenticationType
        {
            //Windows based authentication
            WindowsMixed,
            //User name / password based authentication
            UsernameMixed
        }
        /// <summary>
        /// Issues a token
        /// </summary>
        /// <param name="issuerUri">The uri of the sts that issues token. 
        /// <remarks>The endpoint is explicit to the authentication type and depends on the STS</remarks>
        /// </param>
        /// <param name="authenticationType">The authentication type that matches the <paramref name="issuerUri"/></param>
        /// <param name="credential">Used to drive the credentials to authenticate. When <paramref name="authenticationType"/> is Windows then this value is ignored and can be null</param>
        /// <param name="ishWSUri">The uri of the ISHWS.</param>
        /// <returns></returns>
        static GenericXmlSecurityToken IssueToken(Uri issuerUri, enAuthenticationType authenticationType, NetworkCredential credential, Uri ishWSUri)
        {
            var appliesTo = ishWSUri.AbsoluteUri;
            var requestSecurityToken = new RequestSecurityToken
            {
                RequestType = RequestTypes.Issue,
                AppliesTo = new EndpointReference(appliesTo),
                KeyType = System.IdentityModel.Protocols.WSTrust.KeyTypes.Symmetric
            };

            WS2007HttpBinding issuerBinding = new WS2007HttpBinding(SecurityMode.TransportWithMessageCredential);
            issuerBinding.Security.Message.EstablishSecurityContext = false;
            issuerBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;

            switch (authenticationType)
            {
                case enAuthenticationType.WindowsMixed:
                    issuerBinding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;
                    break;
                case enAuthenticationType.UsernameMixed:
                    issuerBinding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
                    break;
            }

            var issuerEndpointAddress = new EndpointAddress(issuerUri);
            using (var factory = new WSTrustChannelFactory(issuerBinding, issuerEndpointAddress))
            {
                if (authenticationType == enAuthenticationType.UsernameMixed)
                {
                    factory.Credentials.UserName.UserName = credential.UserName;
                    factory.Credentials.UserName.Password = credential.Password;
                }

                factory.TrustVersion = TrustVersion.WSTrust13;
                factory.Credentials.SupportInteractive = false;

                WSTrustChannel channel = null;
                try
                {
                    channel = (WSTrustChannel)factory.CreateChannel();
                    RequestSecurityTokenResponse requestSecurityTokenResponse;
                    return channel.Issue(requestSecurityToken, out requestSecurityTokenResponse) as GenericXmlSecurityToken;
                }
                finally
                {
                    if (channel != null)
                    {
                        channel.Abort();
                    }
                    factory.Abort();
                }
            }
        }
    }
}