Author: archanarai
Date: Tue Nov 14 09:30:07 2017
New Revision: 1815191

URL: http://svn.apache.org/viewvc?rev=1815191&view=rev
Log:
Bound Functions

Modified:
    
olingo/site/trunk/content/doc/odata4/tutorials/action/tutorial_bound_action.mdtext

Modified: 
olingo/site/trunk/content/doc/odata4/tutorials/action/tutorial_bound_action.mdtext
URL: 
http://svn.apache.org/viewvc/olingo/site/trunk/content/doc/odata4/tutorials/action/tutorial_bound_action.mdtext?rev=1815191&r1=1815190&r2=1815191&view=diff
==============================================================================
--- 
olingo/site/trunk/content/doc/odata4/tutorials/action/tutorial_bound_action.mdtext
 (original)
+++ 
olingo/site/trunk/content/doc/odata4/tutorials/action/tutorial_bound_action.mdtext
 Tue Nov 14 09:30:07 2017
@@ -27,7 +27,7 @@ Notice:    Licensed to the Apache Softwa
 
 ## Introduction
 
-In the present tutorial, we’ll implement a bound action.
+In the present tutorial, we’ll implement a bound action and function.
 
 **Note:**
 The final source code can be found in the project [git 
repository](https://git-wip-us.apache.org/repos/asf/olingo-odata4).
@@ -58,13 +58,13 @@ In addition an *Action* can return void
 
 First an *Operation* can be bound or unbound.In this tutorial we will focus on 
bound operation. 
 
-Bound actions support overloading (multiple actions having the same name 
within the same namespace) by binding parameter type. The combination of action 
name and the binding parameter type MUST be unique within a namespace.
+Bound actions and functions support overloading (multiple actions having the 
same name within the same namespace) by binding parameter type. The combination 
of action name and the binding parameter type MUST be unique within a namespace.
 
-An action element MAY specify a Boolean value for the IsBound attribute.
-Actions whose IsBound attribute is false or not specified are considered 
unbound. Unbound actions are invoked through an action import.
-Actions whose IsBound attribute is true are considered bound. Bound actions 
are invoked by appending a segment containing the qualified action name to a 
segment of the appropriate binding parameter type within the resource path. 
Bound actions MUST contain at least one edm:Parameter element, and the first 
parameter is the binding parameter. The binding parameter can be of any type, 
and it MAY be nullable.
+An action or a function element MAY specify a Boolean value for the IsBound 
attribute.
+Actions/Functions whose IsBound attribute is false or not specified are 
considered unbound. Unbound actions/functions are invoked through an action 
import/function import.
+Actions/Functions whose IsBound attribute is true are considered bound. Bound 
actions/functions are invoked by appending a segment containing the qualified 
action name to a segment of the appropriate binding parameter type within the 
resource path. Bound actions/functions MUST contain at least one edm:Parameter 
element, and the first parameter is the binding parameter. The binding 
parameter can be of any type, and it MAY be nullable.
 
-Bound actions that return an entity or a collection of entities MAY specify a 
value for the EntitySetPath attribute if determination of the entity set for 
the return type is contingent on the binding parameter. The value for the 
EntitySetPath attribute consists of a series of segments joined together with 
forward slashes. The first segment of the entity set path MUST be the name of 
the binding parameter. The remaining segments of the entity set path MUST 
represent navigation segments or type casts.
+Bound actions/functions that return an entity or a collection of entities MAY 
specify a value for the EntitySetPath attribute if determination of the entity 
set for the return type is contingent on the binding parameter. The value for 
the EntitySetPath attribute consists of a series of segments joined together 
with forward slashes. The first segment of the entity set path MUST be the name 
of the binding parameter. The remaining segments of the entity set path MUST 
represent navigation segments or type casts.
 
 A navigation segment names the SimpleIdentifier of the navigation property to 
be traversed. A type cast segment names the QualifiedName of the entity type 
that should be returned from the type cast.
 
@@ -90,6 +90,21 @@ To call such a bound action the client i
         "discountCode": "BLACKFRIDAY"
       }
 
+Similarly there can be a bound function GetOrders which is bound to the 
Customer entity having 1 parameter
+
+Such a function can be expressed in the metadata document as follows
+
+    ::::xml
+    <Function Name="GetOrders" isBound=”true”>
+     <Parameter Name="Customers" Type="SampleEntities.Customer" 
Nullable="false"/>
+     <Parameter Name="discountCode" Type="Edm.String" Nullable="false"/>
+     <ReturnType Type="Collection(SampleEntities.Orders)"/>
+    </Function>
+
+To call such a bound function the client issues a GET request to a URL 
identifying the function. In this simple case such a call could look like this:
+
+      GET 
http://host/service/Customers('ALFKI')/SampleEntities.GetOrders(discountCode='BLACKFRIDAY')
+     
 
 ## Preparation
 
@@ -101,7 +116,7 @@ Afterwards do a Deploy and run: it shoul
 
 ## Implementation
 
-We use the given data model you are familiar with. To keep things simple we 
implement one bound action.
+We use the given data model you are familiar with. To keep things simple we 
implement one bound action and one bound function.
 
 **Bound Action that returns a collection of entities: DiscountProducts**    
 This action takes bound parameter “*ParamCategory*” and an additional 
parameter “*Amount*”. The action updates the price of all products related 
to categories by applying the discount amount.
@@ -130,6 +145,32 @@ After finishing the implementation the d
 
 While actions are called by using HTTP Method POST is nessesary to introduce 
new processor interfaces for actions. So there exists a bunch of interfaces, 
for each return type strictly one.
 
+
+**Bound Function that returns a collection of entities: 
GetDiscountedProducts**    
+This function takes bound parameter “*ParamCategory*” and an additional 
parameter “*Amount*”. The function lists all the products related to 
categories which are eligible for the discount amount.
+
+After finishing the implementation the definition of the action should be like 
this:
+
+    ::::xml
+    <Function Name="GetDiscountedProducts" IsBound="true">
+      <Parameter Name="ParamCategory" Type="Collection(OData.Demo.Category)"/>
+      <Parameter Name="Amount" Type="Edm.Int32"/>
+      <ReturnType Type="Collection(OData.Demo.Product)"/>
+    </Function>
+
+**Bound Function that returns an entity: GetDiscountedProduct**
+
+This function takes bound parameter “*ParamCategory*” and an additional 
parameter “*Amount*”. The function lists one specific product related to a 
category which is eligible for the discount amount.
+After finishing the implementation the definition of the action should be like 
this:
+
+    :::xml
+    <Function Name="GetDiscountedProduct" IsBound="true">
+       <Parameter Name="ParamCategory" Type=”OData.Demo.Category"/>
+       <Parameter Name="Amount" Type="Edm.Int32"/>
+       <ReturnType Type=" OData.Demo.Product"/>
+    </Function>
+
+
 **Steps**    
 
   * Extend the Metadata model
@@ -144,21 +185,28 @@ Create the following constants in the De
     //Bound Action
     public static final String ACTION_PROVIDE_DISCOUNT = "DiscountProducts";
     public static final FullQualifiedName ACTION_PROVIDE_DISCOUNT_FQN = new 
FullQualifiedName(NAMESPACE, ACTION_PROVIDE_DISCOUNT);
-      
-    //Bound Action
+
     public static final String ACTION_PROVIDE_DISCOUNT_FOR_PRODUCT = 
"DiscountProduct";
     public static final FullQualifiedName 
ACTION_PROVIDE_DISCOUNT_FOR_PRODUCT_FQN = new FullQualifiedName(NAMESPACE, 
ACTION_PROVIDE_DISCOUNT_FOR_PRODUCT);
     
-    //Action Parameters
+    //Bound Function
+    public static final String FUNCTION_PROVIDE_DISCOUNT = 
"GetDiscountedProducts";
+    public static final FullQualifiedName FUNCTION_PROVIDE_DISCOUNT_FQN = new 
FullQualifiedName(NAMESPACE, FUNCTION_PROVIDE_DISCOUNT);
+      
+    public static final String FUNCTION_PROVIDE_DISCOUNT_FOR_PRODUCT = 
"GetDiscountedProduct";
+    public static final FullQualifiedName 
FUNCTION_PROVIDE_DISCOUNT_FOR_PRODUCT_FQN = new FullQualifiedName(NAMESPACE, 
FUNCTION_PROVIDE_DISCOUNT_FOR_PRODUCT);
+    
+    //Parameters
     public static final String PARAMETER_AMOUNT = "Amount";
     
-    //Bound Action Binding Parameter
+    //Binding Parameter
     public static final String PARAMETER_CATEGORY = "ParamCategory";
 
 
 The way to announce the operations is very similar to announcing EntityTypes. 
We have to override some methods. Those methods provide the definition of the 
Edm elements. We need methods for:
 
   - Actions
+  - Functions
 
 The code is simple and straight forward. We need to create a list of 
parameters of which the first parameter should be the binding parameter, then 
create a return type. At the end all parts are fit together and get returned as 
new CsdlAction Object.
 
@@ -217,6 +265,63 @@ The code is simple and straight forward.
         return null;
       }
 
+Similarly, for functions we need to create a list of parameters of which the 
first parameter should be the binding parameter, then create a return type. At 
the end all parts are fit together and get returned as new CsdlFunction Object.
+
+    ::::java
+        @Override
+        public List<CsdlFunction> getFunctions(final FullQualifiedName 
functionName) {
+       // It is allowed to overload functions, so we have to provide a list of 
Functions for each function name
+        final List<CsdlFunction> functions = new ArrayList<CsdlFunction>();
+        
+        if (functionName.equals(FUNCTION_PROVIDE_DISCOUNT_FQN)) {
+          // Create parameters
+          final List<CsdlParameter> parameters = new 
ArrayList<CsdlParameter>();
+          CsdlParameter parameter = new CsdlParameter();
+          parameter.setName(PARAMETER_CATEGORY);
+          parameter.setType(ET_CATEGORY_FQN);
+          parameter.setCollection(true);
+          parameters.add(parameter);
+          parameter = new CsdlParameter();
+          parameter.setName(PARAMETER_AMOUNT);
+          parameter.setType(EdmPrimitiveTypeKind.Int32.getFullQualifiedName());
+          parameters.add(parameter);
+          
+          // Create the Csdl Function
+          final CsdlFunction function = new CsdlFunction();
+          function.setName(FUNCTION_PROVIDE_DISCOUNT_FQN.getName());
+          function.setBound(true);
+          function.setParameters(parameters);
+          function.setReturnType(new 
CsdlReturnType().setType(ET_PRODUCT_FQN).setCollection(true));
+          functions.add(function);
+          
+          return functions;
+        } else if 
(functionName.equals(FUNCTION_PROVIDE_DISCOUNT_FOR_PRODUCT_FQN)) {
+          // Create parameters
+          final List<CsdlParameter> parameters = new 
ArrayList<CsdlParameter>();
+          CsdlParameter parameter = new CsdlParameter();
+          parameter.setName(PARAMETER_CATEGORY);
+          parameter.setType(ET_CATEGORY_FQN);
+          parameter.setCollection(false);
+          parameters.add(parameter);
+          parameter = new CsdlParameter();
+          parameter.setName(PARAMETER_AMOUNT);
+          parameter.setType(EdmPrimitiveTypeKind.Int32.getFullQualifiedName());
+          parameters.add(parameter);
+          
+          // Create the Csdl Function
+          final CsdlFunction function= new CsdlFunction();
+          function.setName(ACTION_PROVIDE_DISCOUNT_FOR_PRODUCT_FQN.getName());
+          function.setBound(true);
+          function.setParameters(parameters);
+          function.setReturnType(new 
CsdlReturnType().setType(ET_PRODUCT_FQN).setCollection(false));
+          functions.add(function);
+          
+          return functions;
+        }
+        
+        return null;
+      }
+
 Finally we have to announce these operations to the schema. Add the following 
lines to the method getSchemas():
 
     ::::java
@@ -226,6 +331,12 @@ Finally we have to announce these operat
     actions.addAll(getActions(ACTION_PROVIDE_DISCOUNT_FOR_PRODUCT_FQN));
     schema.setActions(actions);
 
+    // add functions
+    List<CsdlFunction> functions = new ArrayList<CsdlFunction>();
+    functions.addAll(getFunctions(FUNCTION_PROVIDE_DISCOUNT_FQN));
+    functions.addAll(getFunctions(FUNCTION_PROVIDE_DISCOUNT_FOR_PRODUCT_FQN));
+    schema.setFunctions(functions);
+
 ### Extend the data store
 
 We need two methods in the data store to read the action DiscountProducts and 
DiscountProduct. The first method returns a collection of entites and second 
method returns a single entity.
@@ -290,6 +401,211 @@ In the second method, we are returning a
     }
     
 
+We also create methods for GetDiscountedProducts and GetDiscountedProduct 
functions. 
+
+    ::::java
+        public EntityCollection getBoundFunctionEntityCollection(EdmFunction 
function, Integer amount) {
+        EntityCollection collection = new EntityCollection();
+        if ("GetDiscountedProducts".equals(function.getName())) {
+          for (Entity entity : categoryList) {
+            if(amount >= entity.getProperty("amount")){
+              Entity en = getRelatedEntity(entity, (EdmEntityType) 
function.getReturnType().getType());
+              collection.getEntities().add(en);
+            }
+          }
+        }
+        return collection;
+      }
+    
+      public Entity getBoundFunctionEntity(EdmAction function, Integer amount,
+          List<UriParameter> keyParams) throws ODataApplicationException {
+        if ("GetDiscountedProduct".equals(function.getName())) {
+          for (Entity entity : categoryList) {
+            if(amount== entity.getProperty("amount")){
+              return getRelatedEntity(entity, (EdmEntityType) 
function.getReturnType().getType(), keyParams);
+            }
+          }
+        }
+        return null;
+      }
+### Extend the entity collection and the entity processor to handle functions
+
+We start with the entity collection processor DemoEntityCollectionProcessor. 
To keep things simple, the first steps is to distinguish between entity 
collections and function imports. A cleverer implementation can handle both 
cases in one method to avoid duplicated code.
+
+The recent implementation of the readEntityCollection() has been moved to 
readEntityCollectionInternal()
+
+::::java
+public void readEntityCollection(ODataRequest request, ODataResponse response, 
UriInfo uriInfo, ContentType responseFormat) throws ODataApplicationException, 
SerializerException {
+
+  final UriResource firstResourceSegment = 
uriInfo.getUriResourceParts().get(0);
+
+  if(firstResourceSegment instanceof UriResourceEntitySet) {
+    readEntityCollectionInternal(request, response, uriInfo, responseFormat);
+  } else if(firstResourceSegment instanceof UriResourceFunction) {
+    readFunctionImportCollection(request, response, uriInfo, responseFormat);
+  } else {
+    throw new ODataApplicationException("Not implemented",
+      HttpStatusCode.NOT&#95;IMPLEMENTED.getStatusCode(),
+    Locale.ENGLISH);
+  }
+}
+Like by reading entity collections, the first step is to analyze the URI and 
then fetch the data (of the function).
+
+    ::::java
+     private void readEntityCollectionInternal(final ODataRequest request, 
final ODataResponse response,
+     final UriInfo uriInfo, final ContentType responseFormat) throws 
ODataApplicationException, SerializerException {
+
+     EdmEntitySet responseEdmEntitySet = null; // we'll need this to build the 
ContextURL
+     EntityCollection responseEntityCollection = null; // we'll need this to 
set the response body
+
+     // 1st retrieve the requested EntitySet from the uriInfo (representation 
of the parsed URI)
+     List<UriResource> resourceParts = uriInfo.getUriResourceParts();
+     int segmentCount = resourceParts.size();
+
+     UriResource uriResource = resourceParts.get(0); // in our example, the 
first segment is the EntitySet
+     if (!(uriResource instanceof UriResourceEntitySet)) {
+       throw new ODataApplicationException("Only EntitySet is supported",
+           HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT);
+     }
+
+     UriResourceEntitySet uriResourceEntitySet = (UriResourceEntitySet) 
uriResource;
+     EdmEntitySet startEdmEntitySet = uriResourceEntitySet.getEntitySet();
+
+      if (segmentCount == 1) { 
+      // This is a normal query fetch the entity from backend and return 
entityset
+      }
+
+      else if (segmentCount == 2) { // in case of function or navigation
+
+        UriResource lastSegment = resourceParts.get(1); // in our example we 
don't support more complex URIs
+        if (lastSegment instanceof UriResourceFunction) {// For bound function
+        UriResourceFunction uriResourceFunction = (UriResourceFunction) 
lastSegment;
+        // 2nd: fetch the data from backend
+        // first fetch the target entity type 
+        String targetEntityType = 
uriResourceFunction.getFunction().getReturnType().getType().getName();
+        // contextURL displays the last segment
+        for(EdmEntitySet entitySet : 
serviceMetadata.getEdm().getEntityContainer().getEntitySets()){
+          if(targetEntityType.equals(entitySet.getEntityType().getName())){
+            responseEdmEntitySet = entitySet;
+            break;
+          }
+        }
+        
+        // error handling for null entities
+        if (targetEntityType == null || responseEdmEntitySet == null) {
+          throw new ODataApplicationException("Entity not found.",
+              HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ROOT);
+        }
+        Integer amount = 
Integer.parseInt(uriResourceFunction.getParameters().get(0).getText())
+        // then fetch the entity collection for the target type
+        responseEntityCollection = 
storage.getBoundFunctionEntityCollection(function, amount);
+      }
+  }
+
+Then the result has to be serialized. The only difference to entity sets is 
the way how the EdmEntityType is determined.
+
+     ::::java
+      // 3rd: create and configure a serializer
+    ContextURL contextUrl = 
ContextURL.with().entitySet(responseEdmEntitySet).build();
+    final String id = request.getRawBaseUri() + "/" + 
responseEdmEntitySet.getName();
+    EntityCollectionSerializerOptions opts = 
EntityCollectionSerializerOptions.with()
+        .contextURL(contextUrl).id(id).build();
+    EdmEntityType edmEntityType = responseEdmEntitySet.getEntityType();
+
+    ODataSerializer serializer = odata.createSerializer(responseFormat);
+    SerializerResult serializerResult = 
serializer.entityCollection(serviceMetadata, edmEntityType,
+        responseEntityCollection, opts);
+
+    // 4th: configure the response object: set the body, headers and status 
code
+    response.setContent(serializerResult.getContent());
+    response.setStatusCode(HttpStatusCode.OK.getStatusCode());
+    response.setHeader(HttpHeader.CONTENT_TYPE, 
responseFormat.toContentTypeString());
+    }
+
+Next we will implement the processor to read a single entity. The 
implementation is quite similar to the implementation of the collection 
processor.
+
+    ::::java
+    public void readEntity(ODataRequest request, ODataResponse response, 
UriInfo uriInfo, ContentType responseFormat)
+      throws ODataApplicationException, SerializerException {
+
+      UriResource uriResource = uriInfo.getUriResourceParts().get(0);
+
+      if(uriResource instanceof UriResourceEntitySet) {
+        readEntityInternal(request, response, uriInfo, responseFormat);
+      } else if(uriResource instanceof UriResourceFunction) {
+        readFunctionImportInternal(request, response, uriInfo, responseFormat);
+      } else {
+        throw new ODataApplicationException("Only EntitySet is supported",
+          HttpStatusCode.NOT&#95;IMPLEMENTED.getStatusCode(), Locale.ENGLISH);
+      }
+    }
+
+     private void readEntityInternal(final ODataRequest request, final 
ODataResponse response,
+      final UriInfo uriInfo, final ContentType responseFormat) throws 
ODataApplicationException, SerializerException {
+
+       EdmEntityType responseEdmEntityType = null; // we'll need this to build 
the ContextURL
+       Entity responseEntity = null; // required for serialization of the 
response body
+       EdmEntitySet responseEdmEntitySet = null; // we need this for building 
the contextUrl
+
+       // 1st step: retrieve the requested Entity: can be "normal" read 
operation, or navigation (to-one)
+       List<UriResource> resourceParts = uriInfo.getUriResourceParts();
+       int segmentCount = resourceParts.size();
+    
+       UriResource uriResource = resourceParts.get(0); // in our example, the 
first segment is the EntitySet
+       UriResourceEntitySet uriResourceEntitySet = (UriResourceEntitySet) 
uriResource;
+       EdmEntitySet startEdmEntitySet = uriResourceEntitySet.getEntitySet();
+
+      if (segmentCount == 1) { 
+      // This is a normal read call fetch the entity from backend and return 
entity
+      } else if (segmentCount == 2) { // Bound Function or navigation
+        UriResource segment = resourceParts.get(1);
+        if (segment instanceof UriResourceFunction) {
+          UriResourceFunction uriResourceFunction = (UriResourceFunction) 
segment;
+
+          // 2nd: fetch the data from backend.
+          // first fetch the target entity type 
+          String targetEntityType = 
uriResourceFunction.getFunction().getReturnType().getType().getName();
+       
+          // contextURL displays the last segment
+          for(EdmEntitySet entitySet : 
serviceMetadata.getEdm().getEntityContainer().getEntitySets()){
+            if(targetEntityType.equals(entitySet.getEntityType().getName())){
+              responseEdmEntityType = entitySet.getEntityType();
+              responseEdmEntitySet = entitySet;
+              break;
+            }
+          }
+        
+          // error handling for null entities
+          if (targetEntityType == null || responseEdmEntitySet == null) {
+            throw new ODataApplicationException("Entity not found.",
+                HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ROOT);
+          }
+
+        Integer amount = 
Integer.parseInt(uriResourceFunction.getParameters().get(0).getText())
+        // then fetch the entity collection for the target type
+        responseEntity = storage.getBoundFunctionEntity(function, amount);
+      }
+    }
+
+    if (responseEntity == null) {
+      // this is the case for e.g. DemoService.svc/Categories(4) or 
DemoService.svc/Categories(3)/Products(999)
+      throw new ODataApplicationException("Nothing found.", 
HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ROOT);
+    }
+
+    // 3. serialize
+    ContextURL contextUrl = 
ContextURL.with().entitySet(responseEdmEntitySet).suffix(Suffix.ENTITY).build();
+    EntitySerializerOptions opts = 
EntitySerializerOptions.with().contextURL(contextUrl).build();
+
+    ODataSerializer serializer = odata.createSerializer(responseFormat);
+    SerializerResult serializerResult = serializer.entity(serviceMetadata,
+        responseEdmEntityType, responseEntity, opts);
+
+    // 4. configure the response object
+    response.setContent(serializerResult.getContent());
+    response.setStatusCode(HttpStatusCode.OK.getStatusCode());
+    response.setHeader(HttpHeader.CONTENT_TYPE, 
responseFormat.toContentTypeString());
+    }
+
 ### Implement an action processor
 
 Create a new class `DemoActionProcessor` make them implement the interface 
interface 'ActionEntityCollectionProcessor' and 'ActionEntityProcessor'.
@@ -340,7 +656,7 @@ Execute the action and set the response
         UriResourceEntitySet boundEntitySet = (UriResourceEntitySet) 
resourcePaths.get(0);
         if (resourcePaths.size() > 1) {
        // Check if there is a navigation segment added after the bound 
parameter
-          if (resourcePaths.get(1) instanceof UriResourceNavigation) {
+          if (resourcePaths.get(1) instanceof UriResourceAction) {
            action = ((UriResourceAction) resourcePaths.get(2))
                 .getAction();
             throw new ODataApplicationException("Action " + action.getName() + 
" is not yet implemented.",
@@ -426,8 +742,8 @@ Execute the action and set the response
         UriResourceEntitySet boundEntity = (UriResourceEntitySet) 
resourcePaths.get(0);
         if (resourcePaths.size() > 1) {
        // Checks if there is a navigation segment added after the binding 
parameter
-          if (resourcePaths.get(1) instanceof UriResourceNavigation) {
-            action = ((UriResourceAction) resourcePaths.get(2))
+          if (resourcePaths.get(1) instanceof UriResourceAction) {
+            action = ((UriResourceAction) resourcePaths.get(1))
                 .getAction();
             throw new ODataApplicationException("Action " + action.getName() + 
" is not yet implemented.",
             HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ENGLISH);
@@ -491,6 +807,13 @@ Execute the action and set the response
 
 After building and deploying your service to your server, you can try the 
following requests:
 
+**Functions (Called via GET)**     
+
+  * 
[http://localhost:8080/DemoService-Action/DemoService.svc/Categories/OData.Demo.GetDiscountedProducts(Amount=50)](http://localhost:8080/DemoService-Action/DemoService.svc/Categories/OData.Demo.GetDiscountedProducts(Amount=50))
+
+
+  * 
[http://localhost:8080/DemoService-Action/DemoService.svc/Categories(0)/OData.Demo.GetDiscountedProduct(Amount=50)](http://localhost:8080/DemoService-Action/DemoService.svc/Categories(0)/OData.Demo.GetDiscountedProduct(Amount=50))
+
 **Actions (Called via POST)**     
 *Note:* Set the Content-Type header to: `Content-Type: application/json`
 
@@ -508,9 +831,6 @@ After building and deploying your servic
     { "Amount": 50 }
 
 
-To verify that the service has been reseted, you can request the collection of 
products
-
-* 
[http://localhost:8080/DemoService-Action/DemoService.svc/Products](http://localhost:8080/DemoService-Action/DemoService.svc/Products)
 # Links
 
 ### Tutorials


Reply via email to