Actions to Take

Applies to: Windows Communication Foundation

Published: June 2011

Author: Greg Cowin

Referenced Image

This topic contains the following sections.

  • Action: Ensure That Principals Are Authenticated and Authorized for Each Operation
  • Action: Use HTTPS/TLS Whenever Possible
  • Action: Take a Countermeasure for Each Threat
  • Action: Measure Service Operation Performance
  • Action: Do Not Return Large Result Sets
  • Action: Specify a Namespace for Each Contract
  • Action: Design Stateless Services
  • Action: Avoid Too Many Round Trips 
  • Action: Establish a Naming Convention for Services
  • Action: Coordinate Service Contract Changes
  • Action: Ensure that Services are Thread Safe
  • Action: Test All Service Operations
  • Action: Add try/catch and Log Unhandled Exceptions for all Externally Exposed Services
  • Conclusion

This section discusses the actions your team should take when they begin a new WCF project. Do the right thing. Make these actions your own.

Action: Ensure That Principals Are Authenticated and Authorized for Each Operation

Description

In order to secure your services, authenticate and authorize each principal. In other words, ensure that principals are who they say they are, and can perform the operations they request. A role or claim determines the principal's privileges and identity.

A principal is an entity that is authenticated by a system. Authentication is a mechanism that validates and confirms the identity of a principal. Principals, in addition to being authenticated, are typically capable of being assigned rights and privileges to use resources. The two most common types of principals are users and computers.

Usually, an authentication service accepts a user name and password and returns a security token. Subsequent service operations use that token as proof that the principal is legitimate. Some older systems do not use tokens, and still pass the user name and password for each operation. Essentially, they authenticate each operation.

Privilege Escalation

Privilege escalation is a malicious act that exploits a design vulnerability in order to gain access to unauthorized resources, and to perform unauthorized actions.

One design flaw that enables privilege escalation is if the web service does not check for the proper role type, or for the appropriate claims. Luckily, most teams avoid this particular mistake in their designs. However, there are more subtle vulnerabilities that are often ignored.

One of these is a design flaw that allows someone to access the private data of other users. The following code is an example of a method that retrieves employee information.

EmployeeResult GetEmployee(string securityToken, int employeeNumber);

The business rule states that users can access their own information but not that of other employees unless they have administrative status. However, if the authorization is not properly checked, then any authenticated user can retrieve another employee's data by guessing the employee number. The following code is an example.

Employee employee = GetEmployee(token, 1)

A more common example of privilege escalation occurs in multi-tenant environments. A poorly designed service operation that retrieves customer data could be used by an authenticated principal to gain access to another customer's information.

Key Tasks

Here are some key tasks to perform in order to ensure that a service is secure.

  • Determine the authentication mechanism or service

  • Determine the authorization mechanism or service.

  • Audit all service operations to ensure that they perform the proper authentications and authorizations.

  • Consider using the Service Protector pattern to make it easier to read, understand, and audit service operations.

The Goals

Service operations should ensure that principals are authenticated and authorized. Each service operation should be easy to understand and simple to read, so that reviewers can verify that it authenticates principals properly, and checks for the proper role types, and claims.

Recommendations

Here are some recommendations to help you perform the tasks.

  • Mechanisms for authentication and authorization should be included in the earliest implementations of the service. This means that you can immediately test that the service is secure, even if you initially have only one principal to represent a calling system, and if you operate in a trusted zone.

  • Consider using the System.Security.Permissions.PrincipalPermission class, although you will probably need to supplement it with application-specific code. For more information, see "PrincipalPermission Class" at https://msdn.microsoft.com/en-us/library/system.security.permissions.principalpermission.aspx.

  • Consider using the Service Protector pattern. This pattern is discussed in the next section.

  • Consider using AD FS 2.0 as the underlying mechanism for authentication and claims management, particularly in greenfield applications. For more information about AD FS 2.0, see "Using Active Directory Federation Services 2.0 in Identity Solutions" at https://msdn.microsoft.com/en-us/magazine/ee335705.aspx.

  • Your team needs clearly defined criteria for what constitutes proper authentication and authorization before they can implement a secure service, and then audit it for compliance.

The Service Protector Pattern

The purpose of the Service Protector pattern is to protect a block of service code. The protected code executes only after it checks that the principal has the proper authentication and authorization to perform a given action (this action is expressed as a lambda expression). The Service Protector pattern uses the fluent interface idiom, which helps you to write code that anyone can read and understand. Code that is readily comprehensible is easy to review and audit to ensure that the proper security measures are in place. This is particularly important for service layers, which, by definition, cross security boundaries.

While Eric Evans and Martin Fowler first coined the phrase "fluent interface," the idiom was first used in Smalltalk. In Smalltalk, if a method does not explicitly return an object, then it returns itself rather than returning nothing. A fluent interface is normally implemented by using method chaining, but more is involved. You are meant to use method names that sound like a prose sentence if you read the entire chain of methods. For more information, see "Fluent interface" at http://en.wikipedia.org/wiki/Fluent_interface.

Use the Service Protector pattern any time that your authentication and authorization measurements use role types and claims.

Consider using the Service Protector pattern when you must secure a service operation. This pattern is useful whether you are relying on existing, internal legacy authentication services, or if you are using current and emerging technologies such as ADFS. For more information about ADFS, see "Active Directory Federation Services (AD FS) 2.0 at https://technet.microsoft.com/en-us/library/adfs2(WS.10).aspx.

Structure

The following figure illustrates the structure of the Service Protector pattern.

Figure 2: Structure of the Service Protector pattern.

Referenced Image

Here is an explanation of the diagram.

Participants

These are the participants in the Service Protector pattern.

  • IAuthorized

    The IAuthorized interface represents a strategy for authorization. Implement the IAuthorized interface when a service operation needs to authorize an action.

  • IServiceProtector

    The IServiceProtector interface specifies the interface for a service protector. A service protector provides a fluent interface that ensures that key security requirements are met before a block of code can execute. The IServiceProtector interface specifies methods that can be cascaded, and that are easily readable.

  • RoleType

    The RoleType enum represents the type of role. A role can be ContactOnly, Standard, Administrator, or Service. You can certainly adapt these values to suit the roles in your organization.

  • SampleAuthorizationAction

    The SampleAuthorizationAction class is an example of an authorization action. This example implementation answers true to the IsAuthorizedFor method only if the given object is "I am authorized".

  • SampleServiceProtector

    The SampleServiceProtector class is a sample implementation of the Service Protector pattern. It demonstrates typical uses, and shows where you might integrate such a class into your authentication system. You can use this implemenation to help you to design your own.

Collaborations

The ServiceProtector interacts with each IAuthorizationAction when you add an action with the following method.

IServiceProtector.AndAuthorizedTo(IAuthorizationAction action, object obj);

If an action, which is given in the InOrderToPerform method, returns true for IAuthorization.IsAuthorizedFor, then the processing continues. Otherwise, the given action is not performed. Every security check in the service protector should either throw a SOAP fault, or set a result object, if the action does not execute.

Consequences

Unfortunately, there are different kinds of authentication systems, and many of them implement one-of-a-kind solutions. The identity service will influence the implementation of the Service Protector pattern.

The type of approach that you use to communicate service errors will also affect the implementation. Remember that the two options are to use result objects or to use SOAP faults. Refer to "Decision: How to Communicate Errors" for more information.

Implementation

There are many different implementations of an authentication service. Most of them can use Security Assertion Markup Language (SAML) tokens, which represent claims as an XML string. This is true of the following implementation. Identity is usually unique to an organization. SAML does not specify the implementation of local services, nor how the authorization services are implemented. This allows an implementation to generalize the concept of identity.

Here is an implementation for IServiceProtector.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DecisionFramework.ServiceProtection
{
    /// <summary>
    /// Specifies the interface for a service protector.
    /// </summary>
    /// <remarks>
    /// A service protector provides a fluent interface 
    /// for ensuring key security requirements in order
    /// to perform a block of code.
    /// </remarks>
    public interface IServiceProtector
    {
        /// <summary>
        /// The mechanisms will need this security token to determine
        /// authentication and authorization. 
        /// </summary>
        string SecurityToken { get; internal set; }

        /// <summary>
        /// Specifies that the service will require a valid given
        /// security token in order to continue processing.
        /// </summary>
        /// <param name="securityToken">The security token to check validity.</param>
        /// <returns>This for fluent interface.</returns>
        IServiceProtector RequiresValidSecurityToken(string securityToken);

        /// <summary>
        /// Specifies that the principal associated with the security token
        /// will need the authorization action for the given optional toObject.
        /// </summary>
        /// <param name="action">The authorization action that performs the check.</param>
        /// <param name="toObject">Action will check access to this, optional, object.</param>
        /// <returns>This for fluent interface.</returns>
        IServiceProtector AndAuthorizedTo (IAuthorizationAction action, object toObject); 
        
        /// <summary>
        /// Specifies that the principal associated with the given security token 
        /// will need to be of the level of the given role type.
        /// </summary>
        /// <param name="roleType">The role type level required.</param>
        /// <returns>This for fluent interface.</returns>
        IServiceProtector AndRequiresRoleType(RoleType roleType);

        /// <summary>
        /// All other statements must be true in order to perform
        /// the given service action (block of code).
        /// </summary>
        /// <param name="action">The service action (block of code).</param>
        void InOrderToPerform(Action action);
    }
}

Sample Code

The following code is an example of the body of a service operation.

            string securityToken = "some security token converted to string";
            IServiceProtector protector = new SampleServiceProtector();

            protector
                .RequiresValidSecurityToken(securityToken)
                .AndRequiresRoleType(RoleType.Administrator)
                .InOrderToPerform(() =>
                {
                    // this code will be executed if and only if all of the 
                    // preceeding checks are met.
                    // perhaps a call to data access layer.

                });
 

The following code is an example of a service operation body that contains an authorization action.

            string securityToken = "some security token converted to string";
            IServiceProtector protector = new SampleServiceProtector();

            protector
                .RequiresValidSecurityToken(securityToken)
                .AndRequiresRoleType(RoleType.Administrator)
                .AndAuthorizedTo(new SampleAuthorizationAction(), null)
                .InOrderToPerform(() =>
                {
                    // this code will be executed if and only if all of the 
                    // preceeding checks are met.
                    // perhaps a call to data access layer.

                });

Action: Use HTTPS/TLS Whenever Possible

Description

Use Transport Layer Security (TLS) whenever possible to secure and protect users' data and privacy. TLS and its predecessor, Secure Sockets Layer (SSL), are cryptographic protocols that provide communications security. TLS is predominantly used with HTTP to form the HTTPS protocol, which can securely carry web traffic. The same types of security risks that exist on the web also exist for web services.

Without TLS, a web service is vulnerable to man-in-the-middle attacks, which intercept and store all cookies, images, and text that is transmitted from one point to another. After the cookies are captured, it is possible for the malicious user to impersonate users on many sites, including such commonly used ones as Facebook, and Gmail.

While developers have known about this vulnerability for years, it has largely been ignored. The public release of Firesheep, an extension to the Firefox web browser that was developed by Eric Butler, brought attention to the problem. The Firesheep extension has a built-in packet sniffer that intercepts unencrypted cookies for websites such as Facebook and Twitter. The cookies are captured as they are transmitted over networks. In the same vein, Facebook recently announced that someone with access to a company's Internet access logs can access users' cookies and impersonate them on Facebook.

Here is a possible scenario. Imagine that you are at a local coffee shop with free Wi-Fi. You sign into Facebook, and then you send a couple of tweets. You don't care if someone might see some posts and pictures that you send over a public Wi-Fi. However, there is a very real problem. Hackers love the fresh smell of cookies. Someone sitting nearby with Firesheep can use it to sniff that traffic, acquire the cookies, and impersonate you on Facebook and Twitter. In fact, almost anyone who can use a mouse can perpetrate this attack.

The countermeasure to man-in-the-middle-attacks is HTTPS/TLS. The slight overhead it incurs is not a sufficient reason to ignore it. Given the current threat, which Firesheep has made obvious, it is clear that you should use it whenever possible. Be aware that many large sites, such as Facebook, Gmail, Hotmail, and Windows Live, are only now converting to HTTP/TLS. If you use Windows Azure, then always use TLS, even for internal role-to-role communication, because the Windows Azure environment and infrastructure are shared. For more information about the man-in-the-middle attack see http://en.wikipedia.org/wiki/Man-in-the-middle_attack.

Action: Take a Countermeasure for Each Threat

Description

Whenever possible, implement a countermeasure for each security threat. Consider using STRIDE, which is a system developed by Microsoft. It provides a methodology for analyzing six categories of security threats. The categories are:

  • Spoofing of user identity

  • Tampering

  • Repudiation

  • Information disclosure

  • Denial of Service (D.o.S.)

  • Elevation of privilege

Recommendation

See "Improving Web Application Security: Threats and Countermeasures" at https://msdn.microsoft.com/en-us/library/ms994921.aspx for more information.

Even if you think the STRIDE method is more than you need, at least specify the common security threats, and the countermeasures that you plan to take. For critical services such as financial services, consider using multiple countermeasures. If one countermeasure is compromised or misconfigured, the service is still protected. For example, message security can be used in addition to TLS.

Action: Measure Service Operation Performance

Description

Providing good performance is one of the key goals of the WCF decision framework. Knowing where to instrument is important if you are to meet this goal. Along with the service operations, you should also consider instrumenting worker roles, agents, service processes, and other key system boundaries. One thing is certain, you need to instrument your code in order to know if you are meeting performance requirements, even if they are not explicitly stated. Be proactive.

Alternatives

There are two options for performance monitoring. You can either use the Windows Management Instrumentation (WMI) or you can use the Instrumentation pattern.

Use the WMI provider for WCF to track some general-purpose WCF counters. WMI is Microsoft's implementation of the Web-Based Enterprise Management (WBEM) initiative, and the Common Information Model (CIM).

Use the Instrumentation pattern to help you think about and implement instrumentation. The pattern is discussed in the next section.

Recommendation

Even though WMI can measure throughput and many other characteristics, it does not provide an easy and efficient way to capture the execution times for an operation. You can also consider implementing an alternative PublishPolicy that that can record to a log file or to a database.

Using the Instrumentation Pattern

The Instrumentation pattern provides a simple way to track performance for an operation, and to minimize observer effects. The metrics include the shortest time, the average time, the longest time to complete the operation, and the number of times an operation was invoked.

You should consider instrumenting the following components to measure their performance

  • Service operations - Measure the performance of all service operations.

  • Agents - Instrument the process portion of autonomous agents.

  • System boundaries - Measure performance wherever a system boundary is crossed. An example is the data access layer.

Structure

The following diagram illustrates the structure of the Instrumentation pattern.

Figure 3: Structure of the Instrumentation pattern

Referenced Image

Participants

These are the participants in the Instrumentation pattern.

  • PerformanceMetrics

    The PerformanceMetrics class represents the performance metrics for an operation. It keeps track of the name of the operation, the total time, the longest time, shortest time to complete the operation, and the number of times the operation has been invoked. The average time can be computed by dividing the total time by the number of invocations.

  • IPublishPolicy

    The IPublishPolicy interface specifies the publishing policy that is used by the Instrumentation class. If this policy answers true from the method ShouldPublish, then the instrumentation is reported to the corresponding IReportInstrumentation implementation.

  • IReportInstrumentation

    The IReportInstrumentation interface defines how to report the instrumentation results. It specifies the Report(Instrumentation) method. You can implement this interface to publish the instrumentation results to a log file, an event viewer, or a database.

Collaborations

After every measurement, the Instrumentation instance asks IPublishPolicy if it ShouldPublish the results of the instrumentation. When IPublishPolicy.ShouldPublish returns true, IReportInstrument.Report is invoked and the performance metrics are reset. You could easily provide your own implementation of the IPublishPolicy interface that decides whether to publish the performance metrics. You can decide based on time, the number of invocations, or some other criteria. You only need to implement a single method, IPublishPolicy.ShouldPublish. You can decide how to publish the performance information by implementing the IReportInstrumentation with its single method, Report. The Report method could log to a file, an event viewer, or to a database. Publication might only occur once per hour, or in whatever time frame that you define.

Consequences of Using the Instrumentation Pattern

The pattern enables you to inspect the Instrumentation instance in the debugger to better understand the service's performance. It also tracks how many times the service operation is invoked. This allows you to easily identify excessive calls to any single operation. The overhead of evaluating a lambda expression and keeping track of the average, shortest, and longest times is minimal.

The Measure Method

The Measure method in the Instrumentation class subtracts the start and end times for the code that is being evaluated. Without the Instrumentation pattern, you would have to write this method each time you instrument a piece of code. The following code is an implementation of the Measure method.

/// <summary>
/// Measure the given operation. The metrics will be identified using
 /// the given operation name. 
/// </summary>
/// <param name="operationName">The name of the operation to measure.</param>
/// <param name="operation">The operation to measure.</param>
public void Measure(string operationName, Operation operation)  
{
long startTicks = DateTime.Now.Ticks;
 operation.Invoke();
long endTicks = DateTime.Now.Ticks;
Record(operationName, endTicks - startTicks);
if (PublishPolicy != null 
             && PublishPolicy.ShouldPublish(this) 
              && ReportInstrumentation != null) 
{
ReportInstrumentation.Report(this);
 Reset();
}
}

Sample Code

The following code is an example service named GeocodeService. It includes instrumentation to measure a lambda expression. The sample report policy publishes the instrumentation once every hour to a log file. This code demonstrates how the Instrumentation pattern makes it easy to capture and report performance information.

 [ServiceContract]
public class GeocodeService
{
private Instrumentation Instrumentation = new Instrumentation
{
Name = "GeocodeService",
PublishPolicy = new LogServiceInstrumentationReportPolicy(),
ReportInstrumentation = new ReportInstrumentationAsLogEvent(),
};
 
[OperationContract]
public GeoResult Geocode(Address address)
{
GeoResult geoResult = new GeoResult();

Instrumentation.Measure("Geocode", () =>
{
            // perform geocode
geoResult.Address = address;
// more processing

});
return geoResult;
}
}

Action: Do Not Return Large Result Sets

Description

In general, service operations should not return large result sets. Large result sets can slow response times, and can even cause timeouts. Just as search engines do not return thousands of results, neither should your services. In addition, including paging as a part of the request makes the service much more user responsive. When you implement paging, it is important to allow the user to specify how many items per page to return (within some upper limit). Limiting the number of results also makes it easier to use the services for mobile applications, where the number of results will need to be much smaller than for a normal web application.

For example, a mobile application might only allow five requests at a time, while a Silverlight application could asks for hundreds, or even thousands. Other applications could request tens of thousands or even millions, but, in general, the fewer requests the better.

Alternatives

Two options for data shaping are the Windows Communication Foundation (WCF) Rich Internet Applications (RIA) Services, and the Data Shaping pattern.

  • WCF RIA Services – WCF RIA services are used with technologies such as Silverlight. WCF RIA Services requires Silverlight 4.0 and Visual Studio 2010 or later.

  • Data Shaping Pattern – The Data Shaping pattern is used for data shaping with request/result interactions. It helps you to filter, page, and sort potentially large data results. The pattern is discussed in the next section.

Recommendation

Unless WCF RIA Services are appropriate for your project, consider the Data Shaping pattern. Many situations require that you shape the data, particularly when the result set is large. Often, an application can become less responsive as the number of results gradually increases, or if a random query produces too many results.

Using the Data Shaping Pattern

Data shaping is about filtering, sorting, and paging result data. You had better shape your data or it will shape you. This pattern provides an approach to data shaping, and is especially useful for WCF services.

Use this pattern any time that there is the possibility of a large result set. This pattern works well with LINQ to SQL, and with the Entity Framework. It can also prevent an application from gradually losing its responsiveness as the size of tables and result sets grow. If you've ever wondered why your application grows slower over time, it is probably because it is returning larger and larger result sets. The following figure illustrates the structure of the Data Shaping pattern.

Figure 4: The structure of the Data Shaping pattern

Referenced Image

Participants

These are the participants in the Data Shaping pattern.

  • ShapingRequest

    The ShapingRequest class represents a data-shaping request. By default, it provides two elements of data shaping, which are paging and sorting. Filtering is the third element of data shaping, and this capability is supplied in a subclass.

  • ShapingResult

    The ShapingResult class represents the result of a shaping request. It also contains the original request object.

Collaborations

The ShapingResult class also contains the ShapingRequest class, which can be used for further requests, such as the next or previous page of results.

Consequences

Although a request object adds some additional complexity, it is necessary when large result sets are possible. Even if the size of the result is not a problem initially, you will probably need to retrofit calls that do not support data shaping as the size of the tables grow. The following diagram illustrates an implementation of the Data Shaping pattern.

Figure 5: An example implementation of the Data Shaping pattern.

Referenced Image

This implementation provides a complete data-shaping example that includes paging, sorting, and filtering. The paging is accomplished in the base class, with the ShapingRequest.PerPage and ShapingRequest.Page properties. The sorting is controlled by the ShapingRequest.SortByProperty. The filtering, in this example, is specified by the PeopleRequest.MatchByFirstName and PeopleRequest.MatchByLastName properties. If the filtering properties are null then there is no filtering; otherwise, all the filtering properties are taken into account.

Sample Code

The following code is for the PeopleRequest class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace DecisionFramework.RequestResultExample
{
    [DataContract]
    public class PeopleRequest : ShapingRequest
    {
        [DataMember]
        public string MatchByLastName { get; set; }

        [DataMember]
        public string MatchByFirstName { get; set; }
    }
}

The following code is for the PeopleResult class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace DecisionFramework.RequestResultExample
{
    [DataContract]
    public class PeopleResult : ShapingResult
    {
        [DataMember]
        public List<Person> Results { get; set; }
    }
}

Action: Specify a Namespace for Each Contract

Description

You must specify a namespace for each contract, or many clients may not be able to generate references to the services. It is easy to specify a namespace by using the ServiceContract attribute. The following code is an example.

[ServiceContract(Namespace = "http://mycompany.com/2011/05")]
public class AuthenticationService
{ … }

Consider adding the year and the month to your namespace, along with your company name, to help with service versioning.

Action: Design Stateless Services

Description

A stateless service is a service where each message or request is treated as an independent transaction, without any dependency on previous messages. Communications should be pairs of requests and responses that are independent of each other.

Stateless services not only simplify interactions, but they also make it easier to deploy scalable services that can easily failover to a different server. Stateless services also minimize resource consumption by deferring the management of state information. Even when you have to defer state to another system such as a database, make sure that your services are stateless.

Action: Avoid Too Many Round Trips 

Description

Just as with database access, avoid too many round trips to web services. Making more than ten web service calls for each page or view affects the user experience. Of course, responsiveness is related to the size of each payload, but generally, even many small payloads can take too much time.

The number of times a service operation is used might surprise you. Add some type of instrumentation so that you will know how often a service is invoked. See the section "Using the Instrumentation Pattern" in this article for an instrumentation strategy. Examine the three most often used services to see if there is a more efficient way of calling them. A possible solution is to add a convenience operation that combines or reduces the number of calls for at least some situations.

Action: Establish a Naming Convention for Services

Description

Establish a naming convention for your services. Generally, you should follow the normal .NET naming conventions. In addition to these, consider the following suggestions:

  • Be sure to avoid namespace collisions with the application layer. Generally, the service layer is a thin wrapper around existing application layer entities. Be careful that the service class does not have the same name as the application layer. Duplicate names are not only confusing, but they require fully qualified namespaces for every call.

If you use a two-way binding, consider adding "Callback" to the name of the callback contract. An example of such a name is ISomeContractCallback. The callback provides a way for the service to call service operations on the client.

Prefix an "I" to the service interface contract name, just as you do for other interfaces. An example of such a name is IMyServiceContract.

The following code is an example of how to name a service contract.

[ServiceContract (Namespace="http://mycompany.com/2011/05")]
public interface IMyContract
{
   …
}

Action: Coordinate Service Contract Changes

Description

Coordinate all service contract changes with the service consumers. During development, make sure that you coordinate and test in your integration, test, or Quality Assurance (QA) environment. When changes occur when the application is in the production environment, it may be necessary to provide a new version, and redeploy the service.

If you are fortunate enough to have control of all of a service's clients, then it is easier to coordinate changes. You can deploy new consumers of the service at the same time that you deploy the new services. However, if you have customers that you do not control, then you will need to coordinate with them and inform them of the changes. It is also probable that you will need a versioning strategy when you do not control all of the clients. Service-oriented architectures require a way to schedule and communicate changes. For related information, see the section "How to Avoid Breaking Existing Consumers" earlier in this article.

Action: Ensure that Services are Thread Safe

Description

Code is thread safe if it can be used in a multi-threaded environment. It is very probable that, of the many clients that visit a website, some of them will call its services concurrently. These service operations should be thread safe. Even if you configure your service to be thread safe from a client invocation perspective, you still must ensure that the code within the service operations is also thread safe.

Other than the usual considerations for thread safety, WCF services have two additional issues. They are:

  • WCF Instance Management

  • WCF Concurrency Management

WCF Instance Management is a set of behaviors that determine which service instances handle which requests. The three WCF Instance Management modes are:

  • Per Session – This is the default behavior. There is one instance per client session.

  • Per Call – There is one instance per service request.

  • Singleton – There is one instance for all service requests.

The following code is an example of how to specify the per-call mode.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall]
public class SomeService : ISomeContract, IDisposable
{    ...
}

A benefit of the per-call mode is that it can increase your application's performance. Although it creates one instance for each call, it can increase throughput. It also fits within the SOA approach because it encourages the development of stateless and self-contained services.

WCF Concurrency Management controls threading within a service instance. There are three modes:

  • Single – Only one thread processes messages.

  • Multiple –Multiple threads process messages concurrently. If you use this mode, make sure that your implementation is thread safe.

  • Reentrant –Messages are sequenced one message at a time, but accept re-entrant operation calls.

The following code is an example of how to specify single concurrency.

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Single]
public class SomeService : ISomeContract
{    ...
}

Although configuring WCF Instance Management and Concurrency Management can be simple, understanding them is a bit more difficult. For more information on thread-safe code, and synchronization concepts, see "Thread Synchronization" at https://msdn.microsoft.com/en-us/magazine/cc163929.aspx. For more details on WCF Concurrency Management and Instance Management, see "Sessions, Instancing, and Concurrency" at https://msdn.microsoft.com/en-us/library/ms731193.aspx.

Action: Test All Service Operations

Description

Be sure to test all service operations. It is essential that your integration points are thoroughly tested. Publishing a set of services for someone else to consume that obviously never worked in the first place is embarrassing, to say the least.

Alternatives

Here are some possible ways to test web services, although this list is not exhaustive.

  • Generate clients and write unit tests. This is one of the best options, but it requires multiple processes in your test bed. There should be one for the service test, and one for the client test.

  • Write test cases for the actual class that implements the service.

  • Initially, use Wcftestclient.exe. However, it will not work for many scenarios, such as when you must pass complex objects as parameters. It is a part of Visual Studio 2010.

  • Use Microsoft Service Trace Viewer to help view, search, filter, and analyze traces that are generated by one or multiple sources. It is a part of Visual Studio 2010.

Recommendation

For testing, there are many good approaches. Ideally, you should use a test automation framework such as MSTest, or NUnit.

Action: Add try/catch and Log Unhandled Exceptions for all Externally Exposed Services

Description

Service operations are often the cause of system failures. Catch, communicate, and log your errors just as you do with agents, service operations, and other system boundaries.

Conclusion

When starting any kind of project, you need to make good decisions, and help your team do the right thing, especially if you are responsible for the service's design. If you are an architect, try to develop your own decision framework. Consider the right actions and, with your team, weigh the alternatives.

The source code in this article is available on CodePlex http://decisionframework.codeplex.com. The code includes an Apache 2.0 license.

Previous article: Decisions to Make

Continue on to the next article: WCF Rest vs. WCF SOAP