This post is the last in a series on Actions in WCF Data Services. The series was started with an example experience for defining actions (Part 1) and how IDataServiceActionProvider works (Part 2). In this post we’ll go ahead and walk through a sample action provider implementation that delivers the experience outlined in part 1 for the Entity Framework.
If you want to walk through the code while reading this you’ll find the code here: http://efactionprovider.codeplex.com.
Implementation Strategy
The center of the implementation is the EntityFrameworkActionProvider class that derives from ActionProvider. The EntityFrameworkActionProvider is specific to EF, whereas the ActionProvider provides a generic starting point for an implementation of IDataServiceActionProvider that enables the experience outlined in Part 1.
There is quite a bit going on in the sample code, so I can’t walk through all of it in a single blog post, instead I’ll focus on the most interesting bits of the code:
Finding Actions
Data Service providers can be completely dynamic, producing a completely different model for each request. However with the built-in Entity Framework provider the model is static, basically because the EF model is static, also the actions are static too, because they are defined in code using methods with attributes on them. This all means one thing – our implementation can do a single reflection pass to establish what actions are in the model and cache that for all requests.
So every time the EntityFrameActionProvider is constructed, it first checks a cache of actions defined on our data source, which in our case is a class derived from EF’s DBContext. If a cache look up it successful great, if not it uses the ActionFactory class to go an construct ServiceActions for every action declared on the DBContext.
The algorithm for the ActionFactory is relatively simple:
- It is given the Type of the class that defines all the Actions. For us that is the T passed to DataService<T>, which is a class derived from EF’s DBContext.
- It them looks for methods with one of these attributes [Action], [NonBindableAction] or [OccasionallyBindableAction], all of which represent different types of actions.
- For each method it finds it then uses the IDataServiceMetadataProvider it was with constructed with to convert the parameters and return types into ResourceTypes.
- At this point it can construct a ServiceAction for each.
Parameter Marshaling
When an action is actually invoked we need to convert any parameters from WCF Data Services internal representation into objects that the end users methods can actually handle. It is likely that marshaling will be quite different for different underlying providers (EF, Reflection, Custom etc), so the ActionProvider uses an interface IParameterMarshaller, whenever it needs to convert parameters. The EF’s parameter marshaled looks like this:
public class EntityFrameworkParameterMarshaller: IParameterMarshaller
{
static MethodInfo CastMethodGeneric = typeof(Enumerable).GetMethod(“Cast”);
static MethodInfo ToListMethodGeneric = typeof(Enumerable).GetMethod(“ToList”);
public object[] Marshall(DataServiceOperationContext operationContext, ServiceAction action, object[] parameters)
{
var pvalues = action.Parameters.Zip(parameters, (parameter, parameterValue) => new { Parameter = parameter, Value = parameterValue });
var marshalled = pvalues.Select(pvalue => GetMarshalledParameter(operationContext, pvalue.Parameter, pvalue.Value)).ToArray();
return marshalled;
}
private object GetMarshalledParameter(DataServiceOperationContext operationContext, ServiceActionParameter serviceActionParameter, object value)
{
var parameterKind = serviceActionParameter.ParameterType.ResourceTypeKind;
// Need to Marshall MultiValues i.e. Collection(Primitive) & Collection(ComplexType)
if (parameterKind == ResourceTypeKind.EntityType)
{
// This entity is UNATTACHED. But people are likely to want to edit this…
IDataServiceUpdateProvider2 updateProvider = operationContext.GetService(typeof(IDataServiceUpdateProvider2)) as IDataServiceUpdateProvider2;
value = updateProvider.GetResource(value as IQueryable, serviceActionParameter.ParameterType.InstanceType.FullName);
value = updateProvider.ResolveResource(value); // This will attach the entity.
}
else if (parameterKind == ResourceTypeKind.EntityCollection)
{
// WCF Data Services constructs an IQueryable that is NoTracking…
// but that means people can’t just edit the entities they pull from the Query.
var query = value as ObjectQuery;
query.MergeOption = MergeOption.AppendOnly;
}
else if (parameterKind == ResourceTypeKind.Collection)
{
// need to coerce into a List<> for dispatch
var enumerable = value as IEnumerable;
// the <T> in List<T> is the Instance type of the ItemType
var elementType = (serviceActionParameter.ParameterType as CollectionResourceType).ItemType.InstanceType;
// call IEnumerable.Cast<T>();
var castMethod = CastMethodGeneric.MakeGenericMethod(elementType);
object marshalledValue = castMethod.Invoke(null, new[] { enumerable });
// call IEnumerable<T>.ToList();
var toListMethod = ToListMethodGeneric.MakeGenericMethod(elementType);
value = toListMethod.Invoke(null, new[] { marshalledValue });
}
return value;
}
}
This is probably the hardest part of the whole sample because it involves understanding what is necessary to make the parameters you pass to the service authors action methods updatable (remembers Actions generally have side-effects).
For example when Data Services see’s something like this: POST http://server/service/Movies(1)/Checkout
It builds a query to represent the Movie parameter to the Checkout action, however when Data Services is building queries, it doesn’t need the Entity Framework to track the results – because all it is doing is serializing the entities and then discarding them. However in this example, we need to take the query and actually retrieve the object in such as way that it is tracked by EF, so that if it gets modified inside the action EF will notice and push changes back to the Database during SaveChanges.
Delaying invocation
As discussed in Part 2, we need to delay actual invocation of the action until SaveChanges(), to do this we return an implementation of an interface called IDataServiceInvokable:
public class ActionInvokable : IDataServiceInvokable
{
ServiceAction _serviceAction;
Action _action;
bool _hasRun = false;
object _result;
public ActionInvokable(DataServiceOperationContext operationContext, ServiceAction serviceAction, object site, object[] parameters, IParameterMarshaller marshaller)
{
_serviceAction = serviceAction;
ActionInfo info = serviceAction.CustomState as ActionInfo;
var marshalled = marshaller.Marshall(operationContext,serviceAction,parameters);
info.AssertAvailable(site,marshalled[0], true);
_action = () => CaptureResult(info.ActionMethod.Invoke(site, marshalled));
}
public void CaptureResult(object o)
{
if (_hasRun) throw new Exception(“Invoke not available. This invokable has already been Invoked.”);
_hasRun = true;
_result = o;
}
public object GetResult()
{
if (!_hasRun) throw new Exception(“Results not available. This invokable hasn’t been Invoked.”);
return _result;
}
public void Invoke()
{
try
{
_action();
}
catch {
throw new DataServiceException(
500,
string.Format(“Exception executing action {0}”, _serviceAction.Name)
);
}
}
}
As you can see this does a couple of things, it creates an Action (this time a CLR one – just to confuse the situation i.e. a delegate that returns void), that actually calls the method on your DBContext via reflection passing the marshaled parameters. It also has a few guards, one to insure that Invoke() is only called once, and another to make sure GetResult() is only called after Invoke().
Actions that are bound *sometimes*
Part 1 and 2 introduce the concept of occasionally bindable actions, but basically an action might not always be available in all states, for example you might not always be able to checkout a movie (perhaps you already have it checked out).
The ActionInfo class has an IsAvailable(…) method which is used by the ActionProvider whenever WCF Data Services need to know if an Action should be advertised or not. The implementation of this will call the method specified in the [OccasionallyBindableAction] attribute. The code is complicated because it supports always return true without actually checking if the actual availability method is too expensive to call repeatedly. This is indicated using the [SkipCheckForFeeds] attribute.
Getting the Sample Code
The sample is on codeplex made up of two projects:
- ActionProviderImplementation – the actual implementation of the Entity Framework action provider.
- MoviesWebsite – a sample MVC3 website that exposes a simple OData Service
Summary
For most of you downloading the samples and using them to create a OData Service with Actions over the Entity Framework is all you’ll need.
However if you are actually considering tweaking the example or building your own from scratch hopefully you now have enough context!
Happy coding
Alex James
Senior Program Manager, Microsoft.
0 comments