Service Operations (WCF Data Services)

Important

WCF Data Services has been deprecated and will no longer be available for download from the Microsoft Download Center. WCF Data Services supported earlier versions of the Microsoft OData (V1-V3) protocol only and has not been under active development. OData V1-V3 has been superseded by OData V4, which is an industry standard published by OASIS and ratified by ISO. OData V4 is supported through the OData V4 compliant core libraries available at Microsoft.OData.Core. Support documentation is available at OData.Net, and the OData V4 service libraries are available at Microsoft.AspNetCore.OData.

RESTier is the successor to WCF Data Services. RESTier helps you bootstrap a standardized, queryable, HTTP-based REST interface in minutes. Like WCF Data Services before it, Restier provides simple and straightforward ways to shape queries and intercept submissions before and after they hit the database. And like Web API + OData, you still have the flexibility to add your own custom queries and actions with techniques you're already familiar with.

WCF Data Services enables you to define service operations on a data service to expose methods on the server. Like other data service resources, service operations are addressed by URIs. Service operations enable you to expose business logic in a data service, such as to implement validation logic, to apply role-based security, or to expose specialized querying capabilities. Service operations are methods added to the data service class that derives from DataService<T>. Like all other data service resources, you can supply parameters to the service operation method. For example, the following service operation URI (based on the quickstart data service) passes the value London to the city parameter:

http://localhost:12345/Northwind.svc/GetOrdersByCity?city='London'

The definition for this service operation is as follows:

[WebGet]
public IQueryable<Order> GetOrdersByCity(string city)
<WebGet()> _
Public Function GetOrdersByCity(ByVal city As String) As IQueryable(Of Order)

You can use the CurrentDataSource of the DataService<T> to directly access the data source that the data service is using. For more information, see How to: Define a Service Operation.

For information on how to call a service operation from a .NET Framework client application, see Calling Service Operations.

Service Operation Requirements

The following requirements apply when defining service operations on the data service. If a method does not meet these requirements, it will not be exposed as a service operation for the data service.

  • The operation must be a public instance method that is a member of the data service class.

  • The operation method may only accept input parameters. Data sent in the message body cannot be accessed by the data service.

  • If parameters are defined, the type of each parameter must be a primitive type. Any data of a non-primitive type must be serialized and passed into a string parameter.

  • The method must return one of the following:

    • void (Nothing in Visual Basic)

    • IEnumerable<T>

    • IQueryable<T>

    • An entity type in the data model that the data service exposes.

    • A primitive class such as integer or string.

  • In order to support query options such as sorting, paging, and filtering, service operation methods should return IQueryable<T>. Requests to service operations that include query options are rejected for operations that only return IEnumerable<T>.

  • In order to support accessing related entities by using navigation properties, the service operation must return IQueryable<T>.

  • The method must be annotated with the [WebGet] or [WebInvoke] attribute.

    • [WebGet] enables the method to be invoked by using a GET request.

    • [WebInvoke(Method = "POST")] enables the method to be invoked by using a POST request. Other WebInvokeAttribute methods are not supported.

  • A service operation may be annotated with the SingleResultAttribute that specifies that the return value from the method is a single entity rather than a collection of entities. This distinction dictates the resulting serialization of the response and the manner in which additional navigation property traversals are represented in the URI. For example, when using AtomPub serialization, a single resource type instance is represented as an entry element and a set of instances as a feed element.

Addressing Service Operations

You can address service operations by placing the name of the method in the first path segment of a URI. As an example, the following URI accesses a GetOrdersByState operation that returns an IQueryable<T> collection of Orders objects.

http://localhost:12345/Northwind.svc/GetOrdersByState?state='CA'&includeItems=true

When calling a service operation, parameters are supplied as query options. The previous service operation accepts both a string parameter state and a Boolean parameter includeItems that indicates whether to include related Order_Detail objects in the response.

The following are valid return types for a service operation:

Valid Return Types URI Rules
void (Nothing in Visual Basic)

-or-

Entity types

-or-

Primitive types
The URI must be a single path segment that is the name of the service operation. Query options are not allowed.
IEnumerable<T> The URI must be a single path segment that is the name of the service operation. Because the result type is not an IQueryable<T> type, query options are not allowed.
IQueryable<T> Query path segments in addition to the path that is the name of the service operation are allowed. Query options are also allowed.

Additional path segments or query options may be added to the URI depending on the return type of the service operation. For example, the following URI accesses a GetOrdersByCity operation that returns an IQueryable<T> collection of Orders objects, ordered by RequiredDate in descending order, along with the related Order_Details objects:

http://localhost:12345/Northwind.svc/GetOrdersByCity?city='London'&$expand=Order_Details&$orderby=RequiredDate desc

Service Operations Access Control

Service-wide visibility of service operations is controlled by the SetServiceOperationAccessRule method on the IDataServiceConfiguration class in much the same way that entity set visibility is controlled by using the SetEntitySetAccessRule method. For example, the following line of code in the data service definition enables access to the CustomersByCity service operation.

config.SetServiceOperationAccessRule(
    "GetOrdersByCity", ServiceOperationRights.AllRead);
config.SetServiceOperationAccessRule( _
    "GetOrdersByCity", ServiceOperationRights.AllRead)

Note

If a service operation has a return type that has been hidden by restricting access on the underlying entity sets, then the service operation will not be available to client applications.

For more information, see How to: Define a Service Operation.

Raising Exceptions

We recommend that you use the DataServiceException class whenever you raise an exception in the data service execution. This is because the data service runtime knows how to map properties of this exception object correctly to the HTTP response message. When you raise a DataServiceException in a service operation, the returned exception is wrapped in a TargetInvocationException. To return the base DataServiceException without the enclosing TargetInvocationException, you must override the HandleException method in the DataService<T>, extract the DataServiceException from the TargetInvocationException, and return it as the top-level error, as in the following example:

// Override to manage returned exceptions.
protected override void HandleException(HandleExceptionArgs args)
{
    // Handle exceptions raised in service operations.
    if (args.Exception.GetType() ==
        typeof(TargetInvocationException)
        && args.Exception.InnerException != null)
    {
        if (args.Exception.InnerException.GetType()
            == typeof(DataServiceException))
        {

            // Unpack the DataServiceException.
            args.Exception = args.Exception.InnerException as DataServiceException;
        }
        else
        {
            // Return a new DataServiceException as "400: bad request."
            args.Exception =
                new DataServiceException(400,
                    args.Exception.InnerException.Message);
        }
    }
}
' Override to manage returned exceptions.
Protected Overrides Sub HandleException(args As HandleExceptionArgs)
    ' Handle exceptions raised in service operations.
    If args.Exception.GetType() = GetType(TargetInvocationException) _
        AndAlso args.Exception.InnerException IsNot Nothing Then
        If args.Exception.InnerException.GetType() = GetType(DataServiceException) Then
            ' Unpack the DataServiceException.
            args.Exception = _
                TryCast(args.Exception.InnerException, DataServiceException)
        Else
            ' Return a new DataServiceException as "400: bad request."
            args.Exception = _
                New DataServiceException(400, args.Exception.InnerException.Message)
        End If
    End If
End Sub

See also