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(whereishrefers 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 asWebServiceURLand should always use thehttpsschema. - baseurl containing something in the line of
- 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
IssuerURLandIssuerBindingrespectively.
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,IssuerURLandIssuerBinding. - Gain performance. A token issued for one
WebServiceURLcan 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
Results
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();
}
}
}
}
}