{"id":843,"date":"2011-10-17T16:03:31","date_gmt":"2011-10-17T16:03:31","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/odatateam\/2011\/10\/17\/actions-in-wcf-data-services\/"},"modified":"2011-10-17T16:03:31","modified_gmt":"2011-10-17T16:03:31","slug":"actions-in-wcf-data-services","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/actions-in-wcf-data-services\/","title":{"rendered":"Actions in WCF Data Services"},"content":{"rendered":"<p><em><font size=\"3\">\u201cActions will provide a way to inject behaviors into an otherwise data-centric model without confusing the data aspects of the model, while still staying true to the resource oriented underpinnings of OData.&quot;<\/font><\/em> <\/p>\n<p>The October 2011 CTP of WCF Data Services adds powerful, but incomplete support for <a href=\"https:\/\/www.odata.org\/blog\/2011\/10\/7\/actions-in-odata\">Actions<\/a>. The motivation behind Actions stems from wanting to advertise in an OData entry an invocable \u2018Action\u2019 that has a side-effect on the OData service. <\/p>\n<p>This statement is broad, but deliberately so; Actions have a lot of power. <\/p>\n<h3>Using WCF Data Services to Invoke an Action:<\/h3>\n<p>This release\u2019s WCF Data Services client can invoke Actions that have no parameters with any return type (i.e. void, Feed, Entry, ComplexType, Collection of ComplexType, PrimitiveType or Collection of PrimitiveType. <\/p>\n<p>To invoke Actions you call either Execute(..) for void actions or Execute&lt;T&gt;(..) for everything else. For example:<\/p>\n<p><font face=\"Courier New\">var checkedOut = ctx.Execute&lt;bool&gt;(      <br \/>&#160;&#160; new Uri(\u201c<\/font><a href=\"http:\/\/server\/service.svc\/Movies(6)\/Checkout\"><font face=\"Courier New\">http:\/\/server\/service.svc\/Movies(6)\/Checkout<\/font><\/a><font face=\"Courier New\">\u201d),      <br \/>&#160;&#160; HttpMethod.Post,       <br \/>&#160;&#160; true       <br \/>).Single();<\/font><\/p>\n<p>Here the Execute&lt;T&gt; function takes the Uri of the Action you want to invoke, the HttpMethod to use (which in this case is Post because we are invoking a side-effecting action), and singleResult=true to indicate there is only one result (i.e. it is not a collection). The method returns a QueryOperationResponse&lt;bool&gt;, which implements IEnumerable&lt;bool&gt;, so we call Single() to get the lone boolean that is the result of invoking the action.<\/p>\n<p><font color=\"#a5a5a5\">NOTE: Needing to specify singleResult=true is a temporary CTP only requirement, because in the CTP our deserialization code can\u2019t automatically detect whether the result is a collection or single result.<\/font> <\/p>\n<p>A nice side-effect of this new feature is that you can now call ServiceOperations too, so long as you craft the full Uri (including any parameters) yourself. For example, the code below calls a ServiceOperation called GetMoviesByGenre that takes a single parameter called Genre and returns a Collection (or feed) of Movies using a Get: <\/p>\n<p><font face=\"Courier New\">var movies = ctx.Execute&lt;Movie&gt;(      <br \/>&#160;&#160; new Uri(\u201c<\/font><a href=\"http:\/\/server\/service.svc\/GetMoviesByGenre?genre='Comedy'\"><font face=\"Courier New\">http:\/\/server\/service.svc\/GetMoviesByGenre?genre=\u2019Comedy\u2019<\/font><\/a><font face=\"Courier New\">\u201d),      <br \/>&#160;&#160; HttpMethod.Get,       <br \/>&#160;&#160; true       <br \/>);<\/font> <\/p>\n<p><font face=\"Courier New\">foreach(var movie in movies) {      <br \/>&#160;&#160; \/\/ do something       <br \/>}<\/font> <\/p>\n<h4>Coming Soon\u2026<\/h4>\n<p>By RTM we plan to add full support for parameters, both for actions and service operations.<\/p>\n<p>The current plan is for a new BodyParameter class that could be used to specify Actions parameters like this:<\/p>\n<p><font face=\"Courier New\">var checkedOutForAWeek = ctx.Execute&lt;bool&gt;(      <br \/>&#160;&#160; new Uri(\u201c<\/font><a href=\"http:\/\/server\/service.svc\/Movies(6)\/Checkout\"><font face=\"Courier New\">http:\/\/server\/service.svc\/Movies(6)\/Checkout<\/font><\/a><font face=\"Courier New\">\u201d),      <br \/>&#160;&#160; HttpMethod.Post,       <br \/>&#160;&#160; new BodyParameter(&quot;noOfDays&quot;, 7)       <br \/>).Single();<\/font> <\/p>\n<p>And a new UriParameter class that could be used to specify ServiceOperation parameters too: <\/p>\n<p><font face=\"Courier New\">var movies = ctx.Execute&lt;Movie&gt;(      <br \/>&#160;&#160; new Uri(&quot;<\/font><a href=\"http:\/\/server\/service.svc\/GetMoviesByGenre\"><font face=\"Courier New\">http:\/\/server\/service.svc\/GetMoviesByGenre<\/font><\/a><font face=\"Courier New\">&quot;),      <br \/>&#160;&#160; HttpMethod.Get,       <br \/>&#160;&#160; new UriParameter(&quot;genre&quot;, &quot;Comedy&quot;)       <br \/>);<\/font><\/p>\n<h3>Setting up a WCF Data Service with Actions:<\/h3>\n<p>Unfortunately, creating actions with WCF Data Services in this release is quite tricky because it requires a <a href=\"http:\/\/blogs.msdn.com\/b\/alexj\/archive\/2010\/01\/07\/data-service-providers-getting-started.aspx\">completely Custom Data Service Provider<\/a>, but we are striving to make this easy by RTM. <\/p>\n<p>This CTP\u2019s WCF Data Services Server only supports one parameter (i.e. the binding parameter), again this will change by RTM. <\/p>\n<p>To get started with actions, first create a ServiceAction in your IDataServiceMetadataProvider2 implementation; something like this: <\/p>\n<blockquote>\n<p><font face=\"Courier New\">ServiceAction checkout = new ServiceAction(        <br \/>&#160;&#160; &quot;Checkout&quot;,         <br \/>&#160;&#160; ResourceType.GetPrimitiveResourceType(typeof(bool)),         <br \/>&#160;&#160; null,         <br \/>&#160;&#160; new List&lt;ServiceOperationParameter&gt;{         <br \/>&#160;&#160;&#160;&#160;&#160; new ServiceOperationParameter(&quot;movie&quot;, movieResourceType)         <br \/>&#160;&#160; },         <br \/>&#160;&#160; true         <br \/>);         <br \/>checkout.SetReadOnly();         <br \/><\/font>      <br \/>ServiceAction currently derives from ServiceOperation, so you will need to add any ServiceActions that you create to the collection of ServiceOperations you expose via both IDataServiceMetadataProvider.ServiceOperations and IDataServiceMetadataProvider.TryResolveServiceOperation(..). Also because Data Services are locked down by default you will need to configure your service to expose your actions using SetServiceOperationAccessRule(..).<\/p>\n<\/blockquote>\n<p>IDataServiceMetadataProvider2 also adds a new method to find actions possibly bound to a ResourceType instance, (i.e. to an individual Movie). This is so that when the WCF Data Service is serializing Entities it doesn\u2019t need to walk over all the metadata to find Actions that might bind to a particular entity. Here is a na\u00efve implementation, where _sop is a list of all ServiceOperations: <\/p>\n<p><font face=\"Courier New\">public IEnumerable&lt;ServiceOperation&gt; GetServiceOperationsByResourceType(ResourceType resourceType)      <br \/>{       <br \/>&#160;&#160; return _sops.OfType&lt;ServiceAction&gt;()       <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; .Where(a =&gt; a.Parameters.Count &gt; 0 &amp;&amp; a.Parameters.First().ParameterType == resourceType);       <br \/>}<\/font>     <\/p>\n<p>Next implement IDataServiceQueryProvider2.IsServiceOperationAdvertisable(..) to tell Data Services whether an Action should be advertised on a particular entity: <\/p>\n<p><font face=\"Courier New\">public bool IsServiceOperationAdvertisable(      <br \/>&#160;&#160; object resourceInstance,       <br \/>&#160;&#160; ServiceOperation serviceOperation,       <br \/>&#160;&#160; ref Microsoft.Data.OData.ODataOperation operationToSerialize       <br \/>){       <br \/>&#160;&#160; Movie m = resourceInstance as Movie;       <br \/>&#160;&#160; if (m == null) return false;       <br \/>&#160;&#160; var checkedOut = GetIsCheckedOut(m, HttpContext.Current.User);<\/font> <\/p>\n<p><font face=\"Courier New\">&#160;&#160; if (serviceOperation.Name == &quot;Checkout&quot; &amp;&amp; !checkedOut) return true;      <br \/>&#160;&#160; else if (serviceOperation.Name == &quot;Checkin&quot; &amp;&amp; checkedOut) return true;       <br \/>&#160;&#160; else return false;       <br \/>}<\/font> <\/p>\n<p>Here resourceInstance is the instance that is being serialized to the client, serviceOperation is the ServiceAction that the server is considering advertising, and operationToSerialize is an OData-structure representing the action information that\u2019ll be serialized if you return true (note you can change properties on this class if for example you want to override the title or target of the Action in the payload). <\/p>\n<p>As you can see, this code knows that only Movies have actions, and that it has only two actions; Checkin and Checkout. It calls an implementation-specific method to work out whether the current user has the current movie checked out and then uses this information to decide whether to advertise the Action. <\/p>\n<p>Next you need to implement IDataServiceUpdateProvider2.InvokeAction(..) so that when a client invokes the Action you actually do something: <\/p>\n<p><font face=\"Courier New\">public object InvokeServiceAction(object dataService, ServiceAction action, object[] parameters)      <br \/>{       <br \/>&#160;&#160; if (action.Name == &quot;Checkin&quot;)       <br \/>&#160;&#160; {       <br \/>&#160;&#160;&#160;&#160;&#160; Movie m = (parameters[0] as IQueryable&lt;Movie&gt;).SingleOrDefault();       <br \/>&#160;&#160;&#160;&#160;&#160; return Checkin(m);       <br \/>&#160;&#160; }       <br \/>&#160;&#160; else if (action.Name == &quot;Checkout&quot;)       <br \/>&#160;&#160; {       <br \/>&#160;&#160;&#160;&#160;&#160; Movie m = (parameters[0] as IQueryable&lt;Movie&gt;).SingleOrDefault();       <br \/>&#160;&#160;&#160;&#160;&#160; return Checkout(m);       <br \/>&#160;&#160; }       <br \/>&#160;&#160; else       <br \/>&#160;&#160;&#160;&#160;&#160; throw new NotSupportedException();       <br \/>}<\/font> <\/p>\n<p>As you can see, this code figures out which action is being invoked and then gets the binding parameter from the parameters collection. The binding parameter will be an unexecuted query (it is unexecuted because this gives a provider the opportunity to invoke an action without actually retrieving the parameter from the datasource, if indeed that is possible), so we extract the Movie by casting parameters[0] to IQueryable&lt;Movie&gt; and calling SingleOrDefault, and then we call the appropriate code for the action directly. <\/p>\n<p>And we are done\u2026 <\/p>\n<p><font color=\"#a5a5a5\"><strong>WARNING:<\/strong> This code will need to change by RTM so that Actions actually get invoked during IDataServiceUpdateProvider.SaveChanges(..). This will involve creating delegates and returning something that isn\u2019t the actual results, but rather something from which you can get the results later. See <\/font><a href=\"http:\/\/blogs.msdn.com\/b\/alexj\/archive\/2010\/02\/11\/creating-a-data-service-provider-part-7-update.aspx\"><font color=\"#a5a5a5\">this post on implementing IDataServiceUpdateProvider<\/font><\/a><font color=\"#a5a5a5\"> for more context if you are interested.<\/font> <\/p>\n<h3>Conclusion:<\/h3>\n<p>As you can see, Actions is a work in progress, and many things are likely to change. Even though it is a lot of work to implement actions with the CTP (mainly because you have to implement IDataServiceMetadataProvider2, IDataServiceQueryProvider2 and IDataServiceUpdateProvider2 from scratch), it\u2019s worth trying because Actions opens up the world of behaviors to OData.<\/p>\n<p>Come RTM we expect the whole experience to be a lot better. <\/p>\n<p>Let me know if you have any questions.    <\/p>\n<p><a href=\"https:\/\/twitter.com\/adjames\">Alex James<\/a><strong><\/strong>     <br \/>Program Manager     <br \/>OData Team<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u201cActions will provide a way to inject behaviors into an otherwise data-centric model without confusing the data aspects of the model, while still staying true to the resource oriented underpinnings of OData.&quot; The October 2011 CTP of WCF Data Services adds powerful, but incomplete support for Actions. The motivation behind Actions stems from wanting to [&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":[],"class_list":["post-843","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-odata"],"acf":[],"blog_post_summary":"<p>\u201cActions will provide a way to inject behaviors into an otherwise data-centric model without confusing the data aspects of the model, while still staying true to the resource oriented underpinnings of OData.&quot; The October 2011 CTP of WCF Data Services adds powerful, but incomplete support for Actions. The motivation behind Actions stems from wanting to [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/843","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=843"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/843\/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=843"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=843"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=843"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}