{"id":793,"date":"2012-04-11T13:17:00","date_gmt":"2012-04-11T13:17:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/odatateam\/2012\/04\/11\/actions-in-wcf-data-services-part-2-how-idataserviceactionprovider-works\/"},"modified":"2012-04-11T13:17:00","modified_gmt":"2012-04-11T13:17:00","slug":"actions-in-wcf-data-services-part-2-how-idataserviceactionprovider-works","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/actions-in-wcf-data-services-part-2-how-idataserviceactionprovider-works\/","title":{"rendered":"Actions in WCF Data Services \u2013 Part 2: How IDataServiceActionProvider works"},"content":{"rendered":"<p>In this post we will explorer the IDataServiceActionProvider interface, which must be implemented to add Actions to a WCF Data Service.<\/p>\n<p>However if you are simply creating an OData Service and you can find an implementation of IDataServiceActionProvider that works for you (I&rsquo;ll post sample code with Part 3) then you can probably skip this post.<\/p>\n<p>Now before we continue, to understand this post fully you&rsquo;ll need to be familiar with Custom Data Service Providers and a good place to start is <a href=\"https:\/\/blogs.msdn.com\/b\/alexj\/archive\/tags\/dsp\/\">here<\/a>.<\/p>\n<h2>IDataServiceActionProvider<\/h2>\n<p>Okay so lets take a look at the actions interface:<\/p>\n<p><span style=\"font-family: Consolas\">public interface IDataServiceActionProvider <br \/>{ <br \/>&nbsp;&nbsp;&nbsp; bool AdvertiseServiceAction( <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DataServiceOperationContext operationContext, <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ServiceAction serviceAction, <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; object resourceInstance, <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bool resourceInstanceInFeed, <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ref ODataAction actionToSerialize); <\/p>\n<p>&nbsp;&nbsp;&nbsp; IDataServiceInvokable CreateInvokable( <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DataServiceOperationContext operationContext, <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ServiceAction serviceAction, <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; object[] parameterTokens); <br \/>&nbsp; <br \/>&nbsp;&nbsp; IEnumerable&lt;ServiceAction&gt; GetServiceActions( <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DataServiceOperationContext operationContext); <br \/>&nbsp; <br \/>&nbsp;&nbsp; IEnumerable&lt;ServiceAction&gt; GetServiceActionsByBindingParameterType( <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DataServiceOperationContext operationContext, <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ResourceType bindingParameterType); <br \/>&nbsp; <br \/>&nbsp;&nbsp; bool TryResolveServiceAction( <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DataServiceOperationContext operationContext, <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; string serviceActionName, <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out ServiceAction serviceAction); <br \/>}<\/span> <\/p>\n<p>You&rsquo;ll notice that every method either takes or returns a ServiceAction. ServiceAction is the metadata representation of an Action, that includes information like the Action name, its parameters, its ReturnType etc.<\/p>\n<p>When you implement IDataServiceActionProvider you are augmenting the metadata for your service which is defined by your services implementation of <a href=\"http:\/\/blogs.msdn.com\/b\/alexj\/archive\/2010\/01\/08\/creating-a-data-service-provider-part-3-metadata.aspx\">IDataServiceMetadataProvider<\/a> with Actions and handling dispatch to those actions as appropriate.<\/p>\n<p>We added this new interface rather than creating a new version of IDataServiceMetadataProvider because we didn&rsquo;t have time to add an Action implementation for the built-in Entity Framework and Reflection provider, but we still wanted you be able to add actions when using those providers. This separation of concerns allows you to use the built-in providers and layer in support for Actions on the side.<\/p>\n<p>However one problem remains: to create a new Action you will need access to the ResourceTypes in the your service, so you can create Action parameters and specify Action ReturnTypes. Previously you couldn&rsquo;t get at the ResourceTypes unless you created a completely custom provider. So to give you access to the ResourceTypes we added an implementation of IServiceProvider to the DataServiceOperationContext class which is passed to every one of the above methods.<\/p>\n<p>Now anywhere you have one of these operationContexts you can get the current implementation of IDataServiceMetadataProvider (and thus the ResourceTypes) like this:<\/p>\n<p>var metadata = operationContext.GetService(typeof(IDataServiceMetadataProvider)) as IDataServiceMetadataProvider;<\/p>\n<h2>Exposing ServiceActions<\/h2>\n<p>There are 3 methods that the Data Services Server uses to learn about actions:<\/p>\n<ul>\n<li>GetServiceActions(DataServiceOperationContext) &ndash; returns every ServiceAction in the service, and is used when we need all the metadata, i.e. if someone goes to $metadata<\/li>\n<li>GetServiceActionsByBindingParameterType(DataServiceOperationContext,ResourceType) &ndash; returns every ServiceAction that can be bound to the ResourceType specified. This is used when we are returning an entity and we want to include information about Actions that can be invoked against that entity. The contract here is you should only include Actions that take the specified ResourceType exactly (i.e. no derived types) as the binding parameter to the action. We will call this method once for each ResourceType we encounter during serialization.<\/li>\n<li>TryResolveServiceAction(DataServiceOperationContext,serviceActionName,out serviceAction) &ndash; return true if a ServiceAction with the specified name is found.<\/li>\n<\/ul>\n<p>Now you could clearly implement both GetServiceActionsByBindingParameterType(..) and TryResolveServiceAction(..) by calling GetServiceActions(..), but Data Services tries to avoid loading all the metadata at once wherever possible, so you get the opportunity to provide more efficient targeted implementations.<\/p>\n<p>Basically 99% of the time Data Services doesn&rsquo;t need every ServiceAction, so it won&rsquo;t ask for all of them most of the time.<\/p>\n<p>To expose an Action you simply create a ServiceAction and return it from these methods as appropriate. For example to create a ServiceAction that corresponds to this C# signature (where Movie is an entity):<\/p>\n<p><span style=\"font-family: Consolas\">void Rate(Movie movie, int rating)<\/span><\/p>\n<p>You would do something like this:<\/p>\n<p><span style=\"font-family: Consolas\">ServiceAction movieRateAction = new ServiceAction( <br \/>&nbsp;&nbsp; &ldquo;Rate&rdquo;, <span style=\"color: #333333\"><span style=\"color: #9bbb59\">\/\/ name of the action<\/span> <br \/><\/span>&nbsp;&nbsp; null, <span style=\"color: #9bbb59\">\/\/ no return type i.e. void<\/span> <br \/>&nbsp;&nbsp; null, <span style=\"color: #9bbb59\">\/\/ no return type means we don&rsquo;t need to know the ResourceSet so use null.<\/span> <br \/>&nbsp;&nbsp; OperationParameterBindingKind.Always, <span style=\"color: #9bbb59\">\/\/ i.e this action is always bound to an Movie entities <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ other options are Never and Always.<\/span> <br \/>&nbsp;&nbsp; new [] { <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; new ServiceActionParameter(&ldquo;movie&rdquo;, movieResourceType),&nbsp; <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; new ServiceActionParameter(&ldquo;rating&rdquo;, ResourceType.GetPrimitiveType(typeof(int))) <br \/>&nbsp;&nbsp; } <br \/>);<\/span><\/p>\n<p>As you can see nothing too tricky here.<\/p>\n<h2>Advertizing ServiceActions<\/h2>\n<p>If you looked at the <a href=\"http:\/\/blogs.msdn.com\/b\/astoriateam\/archive\/2012\/04\/10\/actions-in-wcf-data-services-part-1-service-author-code.aspx\">first post<\/a> you&rsquo;ll remember that some Actions are available only in certain states. This is configured when you create your the ServiceAction, something like this:<\/p>\n<p><span style=\"font-family: Consolas\">ServiceAction checkoutMovieAction = new ServiceAction( <br \/>&nbsp;&nbsp; &ldquo;Checkout&rdquo;, <span style=\"color: #9bbb59\">\/\/ name of the action<\/span> <br \/>&nbsp;&nbsp; ResourceType.GetPrimitiveType(typeof(bool)), <span style=\"color: #9bbb59\">\/\/ Edm.Boolean is the returnType<\/span> <br \/>&nbsp;&nbsp; null, <span style=\"color: #9bbb59\">\/\/ the returnType is a bool, so it doesn&rsquo;t have a ResourceSet<\/span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br \/>&nbsp;&nbsp; OperationParameterBindingKind.Sometimes, <span style=\"color: #9bbb59\">\/\/ You can&rsquo;t always checkout a movie<\/span> <br \/>&nbsp;&nbsp; new [] { new ServiceActionParameter(&ldquo;Movie&rdquo;, movieResourceType) } <br \/>);<\/span><\/p>\n<p>Notice in this example the OperationParameterBindingKind is set to Sometimes which means the Checkout Action is not available for every Movie. So when DataServices returns a Movie it will check with the ActionProvider to see if the Action is currently available. Which it does by calling:<\/p>\n<p><span style=\"font-family: Consolas\">bool AdvertiseServiceAction( <br \/>&nbsp;&nbsp; DataServiceOperationContext operationContext,&nbsp; <br \/>&nbsp;&nbsp; ServiceAction serviceAction, <span style=\"color: #9bbb59\">\/\/ the action that the server knows MAY be bound.<\/span> <br \/>&nbsp;&nbsp; object resourceInstance, <span style=\"color: #9bbb59\">\/\/ the entity which MAY allow the action to be bound. <br \/><\/span>&nbsp;&nbsp; bool resourceInstanceInFeed, <span style=\"color: #9bbb59\">\/\/ whether the server is serializing a single entity or a feed (expect multiple calls). <br \/><\/span>&nbsp;&nbsp; ref ODataAction actionToSerialize); <span style=\"color: #9bbb59\">\/\/ modifying this parameter allows you to do customize things like the URL <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ the client will POST to to invoke the action.<\/span><\/span><\/p>\n<p>For example you might check if the current user (i.e. HttpContext.Current.User) has a Movie checked out already, to decide whether they can Checkout that Movie or not.<\/p>\n<p>The resourceInstanceInFeed parameter needs a special mention. Sometimes working out whether an Action is available is time or resource intensive, for example if you have to do a separate database query. Generally this isn&rsquo;t a problem if you are returning just one Entity, but if you are returning a whole feed of entities it is clearly undesirable. The OData protocol says that in situations like this you should err by exposing actions that aren&rsquo;t actually available (and fail later if they are invoked). WCF Data Services doesn&rsquo;t know if it is expensive to establish action availability, so to help you decide whether to do the check it lets you know whether you are in a feed or not. This way your Action provider can just return true, it if knows it is costly to calculate and it is in a feed.<\/p>\n<h2>Invoking ServiceActions<\/h2>\n<p>When an action is invoked, your implementation of this is called:<\/p>\n<p><span style=\"font-family: Consolas\">IDataServiceInvokable CreateInvokable(&nbsp; <br \/>&nbsp;&nbsp; DataServiceOperationContext operationContext,&nbsp; <br \/>&nbsp;&nbsp; ServiceAction serviceAction, <br \/>&nbsp;&nbsp; object[] parameterTokens);<\/span><\/p>\n<p>Notice you don&rsquo;t actually invoke the action immediately instead you return an implementation of IDataServiceInvokable, which looks like this:<\/p>\n<p><span style=\"font-family: Consolas\">public interface IDataServiceInvokable <br \/>{ <br \/>&nbsp;&nbsp;&nbsp; object GetResult(); <br \/>&nbsp;&nbsp;&nbsp; void Invoke(); <br \/>}<\/span> <\/p>\n<p>As you can see this is a simple interface, but why do we delay calling the action?<\/p>\n<p>Well actions generally have side-effects so they need to work in conjunction with the UpdateProvider (or IDataServiceUpdateProvider2), to actually save those changes to disk. To support Actions you need an Update Provider like the built-in Entity Framework provider that implements the new IDataServiceUpdateProvider2 interface:<\/p>\n<p><span style=\"font-family: Consolas\">public interface IDataServiceUpdateProvider2 : IDataServiceUpdateProvider, IUpdatable <br \/>{ <br \/>&nbsp;&nbsp;&nbsp; void ScheduleInvokable(IDataServiceInvokable invokable); <br \/>}<\/span><\/p>\n<p>This allows WCF Data Services to schedule arbitrary work to happen during IDataServiceUpdateProvider.SaveChanges(..), which allows update providers and action providers to be written independently. Which is great because if you are using the Entity Framework you really don&rsquo;t want to have to write an update provider from scratch.<\/p>\n<p>Now when you implement IDataServiceInvokable you are responsible for 3 things:<\/p>\n<ol>\n<li>Capturing and potentially marshaling the parameters.<\/li>\n<li>Dispatching the parameters to the code that actually implements the Action when Invoke() is called.<\/li>\n<li>Storing any results from Invoke() so they can be retrieved using GetResult() <\/li>\n<\/ol>\n<p>The parameters themselves are passed as tokens. This is because it is possible to write a Data Service Provider that works with tokens that represent resources, if this is the case you may need to convert these token into actual resources before dispatching to the actual action. What is required depends 100% on the rest of the provider code, so it is impossible to say exactly what you need to do here. However in part 3 well explore doing this for the Entity Framework.<\/p>\n<p>If the first parameter to the action is a binding parameter (i.e. an EntityType or a Collection of EntityTypes) it will be passed as an un-enumerated IQueryable. Most of the time this isn&rsquo;t too interesting but it does mean you can do nifty tricks like write an action that doesn&rsquo;t actually retrieve the entities from the database if appropriate.<\/p>\n<h2>Summary<\/h2>\n<p>This post walked you through the design of IDataServiceActionProvider and the expectations for people implementing this interface. While this is quite a tricky interface to implement, it is low level code and hopefully you will be able to find an existing implementation that works for you. Indeed in Part 3 we will share and walk through an sample implementation for the Entity Framework designed to deliver the Service Author Experience we introduced in <a href=\"http:\/\/blogs.msdn.com\/b\/astoriateam\/archive\/2012\/04\/10\/actions-in-wcf-data-services-part-1-service-author-code.aspx\">Part 1<\/a>.<\/p>\n<p>If you have any questions let me know.<\/p>\n<p><a href=\"https:\/\/twitter.com\/#!\/adjames\"><strong>Alex James<\/strong><\/a> <br \/>Senior Program Manager, Microsoft<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this post we will explorer the IDataServiceActionProvider interface, which must be implemented to add Actions to a WCF Data Service. However if you are simply creating an OData Service and you can find an implementation of IDataServiceActionProvider that works for you (I&rsquo;ll post sample code with Part 3) then you can probably skip this [&hellip;]<\/p>\n","protected":false},"author":512,"featured_media":3253,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[5,52],"class_list":["post-793","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-odata","tag-actions","tag-odata-v3"],"acf":[],"blog_post_summary":"<p>In this post we will explorer the IDataServiceActionProvider interface, which must be implemented to add Actions to a WCF Data Service. However if you are simply creating an OData Service and you can find an implementation of IDataServiceActionProvider that works for you (I&rsquo;ll post sample code with Part 3) then you can probably skip this [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/793","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/users\/512"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/comments?post=793"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/793\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media\/3253"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media?parent=793"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=793"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=793"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}