{"id":103,"date":"2014-12-08T01:32:00","date_gmt":"2014-12-08T01:32:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/odatateam\/2014\/12\/08\/tutorial-sample-functions-actions-in-web-api-v2-2-for-odata-v4-0-type-scenario\/"},"modified":"2024-01-24T16:54:54","modified_gmt":"2024-01-24T23:54:54","slug":"tutorial-sample-functions-actions-in-web-api-v2-2-for-odata-v4-0-type-scenario","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/tutorial-sample-functions-actions-in-web-api-v2-2-for-odata-v4-0-type-scenario\/","title":{"rendered":"[Tutorial &amp; sample] Functions &amp; Actions in Web API V2.2 for OData V4.0 \u2013 Type Scenario"},"content":{"rendered":"<h2>Introduction<\/h2>\n<p>Functions and actions are two of the most important parts in OData. They are always very useful to define special\/customized server-side behaviors to process the data in OData services.<\/p>\n<p>From <a href=\"https:\/\/docs.oasis-open.org\/odata\/odata\/v4.0\/odata-v4.0-part1-protocol.html\"><em>OData V4 spec<\/em><\/a><em>, <\/em>functions and actions both are operations and can be either <em>bound <\/em>to a type or unbound.\u00a0However, there&#8217;s\u00a0a little bit difference between them:<\/p>\n<ul type=\"square\">\n<li><a href=\"http:\/\/docs.oasis-open.org\/odata\/odata\/v4.0\/errata01\/os\/complete\/part1-protocol\/odata-v4.0-errata01-os-part1-protocol-complete.html#_Functions_1\"><em>Functions <\/em><\/a>are operations that do not have side effects, may support further composition and must have return type.<\/li>\n<li><a href=\"http:\/\/docs.oasis-open.org\/odata\/odata\/v4.0\/errata01\/os\/complete\/part1-protocol\/odata-v4.0-errata01-os-part1-protocol-complete.html#_Actions_1\"><em>Actions <\/em><\/a>are operations that allow side effects, such as data modification, and cannot be further composed in order to avoid non-deterministic behavior.<\/li>\n<\/ul>\n<p><a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.AspNet.OData\/\">In Web API V2.2 for OData V4.0<\/a>, functions and actions are supported, for both bound and unbound. This blog is intended to use detail examples to illustrate how to <em>configure\/route\/invoke<\/em> functions and actions, for both bound and un-bound, in Web API V2.2 for OData with strong CRL types. For basic tutorial of function and action, you can refer to <a href=\"https:\/\/www.asp.net\/web-api\/overview\/odata-support-in-aspnet-web-api\/odata-v4\/odata-actions-and-functions\">Actions and Functions in OData v4 using ASP.NET Web API 2.2<\/a>.<\/p>\n<p>Ok, let\u2019s get started.<\/p>\n<h2>Construct the model<\/h2>\n<p>For simplicity, we start by creating a simple model which can be used to simulate a simple 2D drawing system.<\/p>\n<h2>Define CLR classes<\/h2>\n<p>First of all, define the\u00a0CLR class <strong>Graph, Shape, Circle, Rectangle, Triangle, Point <\/strong>and enum type <strong>ShapeType<\/strong>. The detailed properties and relationships between each items are shown in the following picture:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2014\/12\/8206.fa_a.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2014\/12\/8206.fa_a.png\" alt=\"\" border=\"0\" \/><\/a><\/p>\n<p>Where:<\/p>\n<ul type=\"square\">\n<li><strong>Graph <\/strong>serves as an entity type, which has a navigation property named \u201c<em>Shapes<\/em>\u201d.<\/li>\n<li><strong>Shape <\/strong>serves as an abstract entity type,<\/li>\n<li><strong>Circle<\/strong>, <strong>Rectangle<\/strong> and <strong>Triangle<\/strong> are subclasses derived from <strong>Shape<\/strong>.<\/li>\n<li><strong>Point <\/strong>serves as a complex type.<\/li>\n<li><strong>ShapeType <\/strong>serves as an enum type.<\/li>\n<\/ul>\n<h2>Build Edm Model<\/h2>\n<p>Based on CLR classes and enum type, Edm Model has to be built explicitly on the conventional model builder. The codes are as follows:<\/p>\n<p><script type=\"text\/javascript\" src=\"https:\/\/gist.github.com\/xuzhg\/0448a180dba8f4658e95.js\"><\/script><\/p>\n<p>Where:<\/p>\n<ul type=\"square\">\n<li><strong><em>Graphs<\/em><\/strong> and <strong><em>Shapes <\/em><\/strong>are two entity sets.<\/li>\n<li><strong><em>BuildFunctions(builder)<\/em><\/strong>: is a private method in which Functions will be built. We will add more functions in this method later.<\/li>\n<li><strong><em>BuildActions(builder):<\/em><\/strong> is a private method in which Actions will be built. We will add more actions in this method later.<\/li>\n<\/ul>\n<h2>Create OData Controller<\/h2>\n<p>At the beginning, we create a simple controller that just have one method, and use in-memory lists to store entities.<\/p>\n<p><script type=\"text\/javascript\" src=\"https:\/\/gist.github.com\/xuzhg\/b4011c27c18ac5c47a72.js\"><\/script><\/p>\n<p>Where, <strong>MapContext<\/strong> plays the DB Role, in which has two lists (<strong>Graphs<\/strong> and <strong>Shapes<\/strong>):<\/p>\n<p><script type=\"text\/javascript\" src=\"https:\/\/gist.github.com\/xuzhg\/48b418c7e90508840d6b.js\"><\/script><\/p>\n<p>Just for test, we create 4 graphs and 10 shapes in <strong><em>BuildMap<\/em><\/strong>() function (detail codes can be found in attached file.)<\/p>\n<h2>Functions in Web API<\/h2>\n<p>As mentioned, functions have the following main features:<\/p>\n<ul type=\"square\">\n<li>Do not have side effects<\/li>\n<li>Can be composed<\/li>\n<li>Must have return type<\/li>\n<\/ul>\n<p>Besides, Functions can be either <em>bound<\/em> to a type or <em>unbound<\/em>.<\/p>\n<h2>Bound functions<\/h2>\n<p>Basically,\u00a0there are two different kinds of bound functions:<\/p>\n<ol>\n<li>Bound to entity type.<\/li>\n<li>Bound to collection of entity type.<\/li>\n<\/ol>\n<p>Bound function can be overloaded, but for all bound overload functions with same binding parameter type, must have same return type. Let\u2019s see how to configure\/route\/invoke the bound functions:<\/p>\n<h3>Configure bound functions<\/h3>\n<p>Add the following bound functions in the <strong><em>BuildFunctions(builder)<\/em><\/strong>. The first four are functions bound to collection of entity type with overload, while the other two are functions bound to entity type.<\/p>\n<p><script type=\"text\/javascript\" src=\"https:\/\/gist.github.com\/xuzhg\/6e80e3af1ef9bcbd8d90.js\"><\/script><\/p>\n<p><strong>Note<\/strong>:<\/p>\n<p>The parameter string value of <em><strong>Returns{Collection|Entity}ViaEntitySetPath<\/strong><\/em>() should follow up the\u00a0template as:<\/p>\n<p><span style=\"color: #ff0000; background-color: #ffffff;\"><strong><em>&#8220;bindingParameter\/{NavigationPropertyName}\/{NavigationPropertyName}\/\u2026&#8221;.<\/em><\/strong><\/span><\/p>\n<h3>Route bound functions<\/h3>\n<p>In the <strong>GraphsController<\/strong>, add the following methods for bound function routing:<\/p>\n<p><script type=\"text\/javascript\" src=\"https:\/\/gist.github.com\/xuzhg\/eb36cc6afcdd5de9b427.js\"><\/script><\/p>\n<p><strong>Note<\/strong>:<\/p>\n<ol>\n<li>Both convention and attribute routing can be used in bound function.<\/li>\n<li>In convention routing, if the method name starts with \u201cGet\u201d, the [HttpGet] can be omitted.<\/li>\n<li>In attribute routing, it does not matter to which controller your method belongs and what method name you create. Please refer to the method \u201c<strong>PointInGraph<\/strong>\u201d.<\/li>\n<\/ol>\n<h3>Invoke bound functions<\/h3>\n<p>To invoke a bound function, the client issues a <span style=\"color: #993366;\"><strong>GET<\/strong><\/span> request with <span style=\"text-decoration: underline;\"><strong>namespace-qualified function name<\/strong><\/span> to a URL that identifies a resource whose type is the same as, or is derived from, the type of the binding parameter of the function. (Please refer to <a href=\"https:\/\/www.asp.net\/web-api\/overview\/odata-support-in-aspnet-web-api\/odata-v4\/odata-actions-and-functions\"><span style=\"text-decoration: underline;\">here<\/span><\/a> to resolve the problem when using the default IIS).<\/p>\n<p>Let\u2019s issue two requests as examples:<\/p>\n<p><strong>GET<\/strong> <em>~\/odata\/Graphs\/Default.GetShapeCount()<\/em><\/p>\n<p><strong>GET<\/strong> <em>~\/odata\/Graphs\/Default.GetShapeCount(shapeType=FunctionActionBlog.ShapeType&#8217;Circle&#8217;)<\/em><\/p>\n<p>Then, the responses are as below respectively:<\/p>\n<p><script type=\"text\/javascript\" src=\"https:\/\/gist.github.com\/xuzhg\/e3c7bc8745e11256f150.js\"><\/script><\/p>\n<p>Below are other examples to invoke the bound functions:<\/p>\n<ul type=\"square\">\n<li><strong>GET<\/strong> <em>~\/odata\/Graphs\/Default.GetShapeCount()<\/em><\/li>\n<li><strong>GET<\/strong> <em>~\/odata\/Graphs\/Default.GetShapeCount(shapeType=FunctionActionBlog.ShapeType&#8217;Circle&#8217;)<\/em><\/li>\n<li><strong>GET<\/strong> <em>~\/odata\/Graphs\/Default.LeastAreaShape()<\/em><\/li>\n<li><strong>GET<\/strong> <em>~\/odata\/Graphs\/Default.LeastAreaShape(shapeType=FunctionActionBlog.ShapeType&#8217;Rectangle&#8217;)<\/em><\/li>\n<li><strong>GET<\/strong> <em>~\/odata\/Graphs(2)\/Default.IsPointInGraph(ptX=215,ptY=115)<\/em><\/li>\n<li><strong>GET<\/strong> <em>~\/odata\/Graphs(2)\/Default.ShapeAreaLt(area=1000.0)<\/em><\/li>\n<li><strong>GET<\/strong> <em>~\/odata\/Graphs(2)\/Default.ShapeAreaLt(area=1000.0)\/$count<\/em><\/li>\n<\/ul>\n<h2>Unbound functions<\/h2>\n<p>Unbound functions don\u2019t bound to any type and they are called as static operations. All unbound function\u00a0overloads <strong>MUST<\/strong> have same return type.<\/p>\n<h3>Configure unbound functions<\/h3>\n<p>In Web API, unbound functions are configured on the model builder directly. Let\u2019s add the following two unbound functions in the <strong><em>BuildFunctions(builder)<\/em><\/strong>. The first one is an unbound function which returns collection of primitive types and the other one is an unbound function which returns from entity set.<\/p>\n<p><script type=\"text\/javascript\" src=\"https:\/\/gist.github.com\/xuzhg\/b3280cdb877c4096e56b.js\"><\/script><\/p>\n<p><strong>Note<\/strong>:<\/p>\n<p>For unbound function, just use the entity set name as the argument to call <strong><em>ReturnsFromEntitySet&lt;T&gt;(entitySetName);<\/em><\/strong><\/p>\n<h3>Route unbound functions<\/h3>\n<p>Only attribute routing can be used for unbound function. Let&#8217;s add the following methods for unbound functions routing in the <strong>GraphsController<\/strong> (or any other controllers):<\/p>\n<p><script type=\"text\/javascript\" src=\"https:\/\/gist.github.com\/xuzhg\/e68d760f22fa1749427d.js\"><\/script><\/p>\n<h3>Invoke unbound functions<\/h3>\n<p>Unbound functions must be called through function import from the service root. So, to invoke a function through a function import, the client issues a <span style=\"color: #993366;\"><strong>GET <\/strong><\/span>request to the service root, followed by <span style=\"text-decoration: underline;\"><strong>the name of the function import<\/strong><\/span>.<\/p>\n<p>Let\u2019s issue a request as example:<\/p>\n<p><strong>GET <\/strong><em>~\/odata\/MapBoundary<\/em><\/p>\n<p>Then, the response is as below:<\/p>\n<p><script type=\"text\/javascript\" src=\"https:\/\/gist.github.com\/xuzhg\/b766dacf17c15f8a10c2.js\"><\/script><\/p>\n<h2>Actions in Web API<\/h2>\n<p>As mentioned, Actions have the following main features:<\/p>\n<ul type=\"square\">\n<li>Be allowed to have side effects<\/li>\n<li>Can not be composed<\/li>\n<li>May have return type<\/li>\n<\/ul>\n<p>Besides, same as functions, actions can be either be <em>bound<\/em> to a type or <em>unbound<\/em>.<\/p>\n<h2>Bound actions<\/h2>\n<p>Same as bound functions, there are two different kinds of bound actions:<\/p>\n<ol>\n<li>Bound to entity type.<\/li>\n<li>Bound to collection of entity type.<\/li>\n<\/ol>\n<p>However, the overload of bound actions are different. Bound actions can be overloaded, but overload must happen by different binding parameter. For one binding parameter, there can be only one bound action.<\/p>\n<h3>Configure bound actions<\/h3>\n<p>Let\u2019s add the following bound actions in the <strong><em>BuildActions(builder)<\/em><\/strong>. The first two are actions bound to collection of entity type, the other two are actions bound to entity type.<\/p>\n<p><script type=\"text\/javascript\" src=\"https:\/\/gist.github.com\/xuzhg\/ea6158f02d53fb2778ed.js\"><\/script><\/p>\n<h3>Route bound actions<\/h3>\n<p>In the <strong>GraphsController<\/strong>, add the following methods for bound action routing:<\/p>\n<p><script type=\"text\/javascript\" src=\"https:\/\/gist.github.com\/xuzhg\/d7d410c07e3a35157989.js\"><\/script><\/p>\n<p><strong>Note:<\/strong><\/p>\n<ol>\n<li>Each method in the controller has \u201c<em><strong>ODataActionParameters<\/strong><\/em>\u201d as parameter. It is a dictionary.<\/li>\n<li>Convention and attribute routing can be used in bound action.<\/li>\n<li>In attribute routing, it does not matter to which controller your method belongs and what method name you create. Refer to the method \u201c<strong>ChangeCircleRadius<\/strong>\u201d.<\/li>\n<\/ol>\n<h3>Invoke bound actions<\/h3>\n<p>To invoke a bound action, the client can issue a <span style=\"color: #993366;\"><strong>POST <\/strong><\/span>request with <span style=\"text-decoration: underline;\"><strong>namespace-qualified action name<\/strong><\/span> to a URL that identifies a resource whose type is same as, or is derived from the type of the binding parameter of the action. (Please refer to <a href=\"https:\/\/www.asp.net\/web-api\/overview\/odata-support-in-aspnet-web-api\/odata-v4\/odata-actions-and-functions\"><span style=\"text-decoration: underline;\">here<\/span><\/a> to resolve the problem when using the default IIS).<\/p>\n<p>Let&#8217;s issue a requests as example:<\/p>\n<p><script type=\"text\/javascript\" src=\"https:\/\/gist.github.com\/xuzhg\/6fff49e990dadfae19a6.js\"><\/script><\/p>\n<p>Here\u2019s the debug information:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2014\/12\/7585.fa_c.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2014\/12\/7585.fa_c.png\" alt=\"\" border=\"0\" \/><\/a><\/p>\n<h2>Unbound actions<\/h2>\n<p>Unbound actions don\u2019t bound to any type and they are called as static operations same as unbound functions. However, unbound actions do not allow overloading.<\/p>\n<h3>Configure unbound actions<\/h3>\n<p>Unbound actions are configured, same as unbound function, directly in the model builder. Let\u2019s add the following unbound actions in the <strong><em>BuildActions(builder)<\/em><\/strong>.<\/p>\n<p><script type=\"text\/javascript\" src=\"https:\/\/gist.github.com\/xuzhg\/54cd4ea761dbdeec2204.js\"><\/script><\/p>\n<h3>Route unbound actions<\/h3>\n<p>Only attribute routing can be used for unbound action. In the <strong>GraphsController <\/strong>or any other controllers, add the following methods for bound actions routing:<\/p>\n<p><script type=\"text\/javascript\" src=\"https:\/\/gist.github.com\/xuzhg\/48ae33873077f6b41cb7.js\"><\/script><\/p>\n<h3>Invoke unbound actions<\/h3>\n<p>To invoke an action through an action import, the client issues a <strong><span style=\"color: #993366;\">POST<\/span><\/strong> request to the service root, followed by<span style=\"text-decoration: underline;\"><strong> the name of the action import<\/strong><\/span>.<\/p>\n<p>For example, Let\u2019s issue a request as:<\/p>\n<p><script type=\"text\/javascript\" src=\"https:\/\/gist.github.com\/xuzhg\/d3c679701b54c3e4a1fd.js\"><\/script><\/p>\n<p>Here\u2019s the debug information:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2014\/12\/1727.fa_d.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2014\/12\/1727.fa_d.png\" alt=\"\" border=\"0\" \/><\/a><\/p>\n<h2>Conclusion<\/h2>\n<p>This blog uses the detailed examples to illustrate how to configure\/route\/invoke bound and unbound functions and actions in Web API 2.2 for OData V4. From these examples, we can see that the functions and actions shipped with Web API provides a very easy and useful way for customers to add their special and customized server-side behaviors to their OData service. Besides, we do believe that, along with the entity type, complex type and the collection\u00a0be supported as function parameter in the next release of Web API OData, it will surely expand the usage of Web API OData in the\u00a0future.\nPlease download the attached file to find the\u00a0source\u00a0codes mentioned in this blog.Thanks.<\/p>\n<p><a href=\"https:\/\/msdnshared.blob.core.windows.net\/media\/MSDNBlogsFS\/prod.evol.blogs.msdn.com\/CommunityServer.Components.PostAttachments\/00\/10\/57\/90\/18\/FunctionActionBlog.zip\">FunctionActionBlog.zip<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Functions and actions are two of the most important parts in OData. They are always very useful to define special\/customized server-side behaviors to process the data in OData services. From OData V4 spec, functions and actions both are operations and can be either bound to a type or unbound.\u00a0However, there&#8217;s\u00a0a little bit difference between [&hellip;]<\/p>\n","protected":false},"author":514,"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-103","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-odata"],"acf":[],"blog_post_summary":"<p>Introduction Functions and actions are two of the most important parts in OData. They are always very useful to define special\/customized server-side behaviors to process the data in OData services. From OData V4 spec, functions and actions both are operations and can be either bound to a type or unbound.\u00a0However, there&#8217;s\u00a0a little bit difference between [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/103","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\/514"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/comments?post=103"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/103\/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=103"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=103"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=103"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}