Examples

Examples for webservices configuration.

In the "Samples" subdirectory you can find a Visual Studio 2012 solution containing six sample projects covering how to use CRUD web services (Insert, Query, Update and Delete), Custom Query service and an example illustrating the chunked read feature.

Additionally the solution contains the CookieManagerLib project with classes allowing for passing cookies between different instances of services (WCF equivalent of passing CookieContainer between instances in previous version).

General settings

To use these projects you have to follow the instruction below:

  1. The service generator can be found at the following url:

    http://<your server>/<your path to web services>

    Open the service generator and create a Custom CRUD service, naming it "Companies" and using "Abstract Types".

    Create "Company" and "Person" data types.

    Edit the fields for both data types and select all available fields.

    Edit the links for the Person data type and create a link to Company (Link Id, 0; Type, Parent).

    Create the default methods for the Person and Company data types.

  2. You need to update/recreate service references of each project to point them to Companies service you generated in previous step.
    You can do it by one of the following ways:
    • Deleting existing reference and using Visual Studio wizard via the "Add service reference" context menu where you have to enter URL to svc file of your newly created service.

      Adding a Service Reference

    • Modification of service configuration in app.config file (changes are in green font color)

      <client>
         <endpoint address="http://localhost/ws8.0/admin/Companies.svc"
            binding="basicHttpBinding"
            bindingConfiguration="BasicHttpBinding_ICompaniesWebService"
            contract="CompaniesServiceReference.ICompaniesWebService"
            name="BasicHttpBinding_ICompaniesWebService"/>
      </client>
    • Setting the URL to the entry point of service in code
      var client = new CompaniesServiceClient();
      client.Endpoint.Address = new EndpointAddress("http://localhost/ws8.0/admin/Companies.svc");

You can get the URL of your generated service by clicking the one of the links in the CRUD generator as depictured below.

Retrieving the URL of a generated service

Login and session cookie management

Each CRUD service and CommonServicesWcf has

  • Login
  • Logout
  • IsLoggedIn

methods. But since WCF services using basic http protocol are stateless the next method called after Login returns error message "Session is null. Login missing?".

To have a possibility of sustaining session cookies between subsequent calls into the services we can use two approaches:

  1. Configure the service to use cookie transmission between server and client. In app.config file we need to add the attribute allowCookies="true" attribute in binding section.
    <bindings>
       <basicHttpBinding>
          <binding name="BasicHttpBinding_ICompaniesWebService" allowCookies="true"/>
       </basicHttpBinding>
    </bindings>
  2. Use WCF service’s behavior extension infrastructure to intercept cookies returned by server and pass them between each service client configured to use that extension.
    Note: This mechanism is equivalent of passing CookieContainer instances between Common Services and other services in update.seven webservices.

    An example of the implementation of such behavior extension is located in the CookieManagerLib project and it's usage is illustrated in the Custom Query sample project.

    Note: You have to remove the allowCookies attribute or set it to false otherwise behavior extension won’t be able to intercept cookies.

Insert

This example shows how to create a company record and a person record linked to that company.

The program contains the following distinct parts.

  1. We create an instance of the web service (CompaniesWebServiceClient) which is used to perform a login and create records.
  2. We create a Company object and assign it Company Name, any other data you require can also be added to the appropriate fields. The default Company name is "Test Company".
  3. Using the InsertCompany method of CompaniesWebServiceClient, we insert the Company into the database.
  4. After the insert is completed we check that it was successful and if so we display the Id of the new Company record and continue creating a Person record to link to the Company. If the insert failed we display the error message.
  5. We create a Person object and assign it a First and Last name, any other data you require can also be added to the appropriate fields. The default name is "Test" "Person". Set the link object Company_0 to our Test Company.
  6. Using the InsertPerson method of CompaniesWebServiceClient, we insert the Person into the database.
  7. After the insert is completed we check that it was successful and if so we display the Id of the new Person record. If the insert failed we display the error message.

Query

This example returns all company records matching the defined condition; as a search value we use "Test Company", the company we created in the insert example above.

  1. We create an instance of the web service (CompaniesWebServiceClient) which is used to perform a login and to query for the records.
  2. We create a Sort object; the example’s default sort order is by the "Street" attribute of the returned Companies.
  3. We create a ComparisonCondition object; this defines the query conditions we require. The sample’s default condition is to find any companies called "Test Company". You can customize the condition to suit your database contents.
  4. We create a Company object, for our query and assign our sort and condition objects to the Sort and CustomCondition attributes.
  5. Using the QueryCompany method of CompaniesWebServiceClient, we query the database for Companies that match our condition.
  6. After the query is completed we check that if it was executed successfully and if any matching companies are returned we display their Name and Street. If the query failed we display the error message.
Note: If only an object (Company, Person etc.) is specified without any conditions, ALL available records of this Info Area (table) are returned.

This sample has a simple condition; more complex conditions can also be used. See the Conditions section later in this chapter.

Update

In this example we update a company which satisfies the search condition. If more than one record is found, none of the records are updated and an error message, "Parent record not unique" is returned. If no record is found, a new record with the specified fields is created.

  1. We create an instance of the web service (CompaniesWebServiceClient) which is used to perform a login and to update the record.
  2. We create a ComparisonCondition object; this defines the query conditions we require. The example's default condition is to find any companies with a Synonym called "Test Synonym".
  3. We create a Company object, for our update and assign our condition object to the CustomCondition attribute and set the street field to "New Test Street" to update the value.
  4. Using the UpdateCompany method of CompaniesWebServiceClient, we update the Company that matches our condition.
  5. The response indicates if the record that was updated or contains an error message if no record or more than one record was found.

To update multiple records it would be necessary to update every record individually.

Delete

This example deletes all matching company records from database.

  1. We create an instance of the web service (CompaniesWebServiceClient) which is used to perform a login and to delete the records.
  2. We create two instances of ComparisonCondition. We search for a company with a company name equal to "Test Company" and synonym equal to "Test Synonym". Combine the comparison conditions using a LogicalCondition with an And operation.
  3. We create a Company object, for our delete and assign our logical condition object to the CustomCondition attribute.
  4. Using the DeleteCompany method of CompaniesWebServiceClient, we delete Companies that match our condition from the database.
  5. After the deletion is completed we check if it was successful and if any companies have been deleted we display their Record Ids. If the query failed we display an error message.
Note: Note: Use the delete method with caution, since – as the method name implicates – all matching data are deleted.

Custom Query

This example shows how to execute a (web) query via webservices. It is possible to pass parameters to the query.

  1. Please create a query called "CompanyTest". Add the Company Info Area to this query, including some output fields (Company, Synonym, Street and City are used in the example). Add a (parameter) condition for companies by company name and click "Use as Parameter (requires user input)".
  2. In the example we create a query service "CompanyTest" with the service generator create.
  3. We create an instance of CommonServicesWcfClient and perform a login.
  4. We create an instance of the web service (CompanyTestQueryServiceClient) which is used to execute the query. Due to usage of behavior extension intercepting cookies (see app.config file) the session created by Login method of CommonServicesWcfClient is maintained for the query service client.
  5. We create an instance of CompanyTest which represents the query and set the value of our parameter to find the company called “Test Company”.
  6. Using the Query method of CompanyTestQueryServiceClient, we execute the CompanyTest query.
  7. After the query is completed we check if it was executed successfully and if any companies are returned we display their Company, Synonym, Street and City attributes. If the query failed we display the error message.

Chunked Query

A chunked query allows for splitting the results of a query into parts and process them as such. The chunked query is initialized by setting the "Chunked" request option property to true; you can either provide your own unique identifier or retrieve the automatically assigned one from the response. To retrieve any subsequent chunks of the query you need to provide the identifier in the query request options.

Note: If you use the same RequestOptions object that is used for creating the chunked query, for a retrieving more chunks, you need to set the "Chunked" property to false and update the unique identifier if one has been automatically assigned. Otherwise you get an error saying that a Chunked operation with that identifier already exists.

The Chunked Query example shows how to get all company records in chunks of 10 records.

  1. We create an instance of the web service (CompaniesWebServiceClient) which is used to perform a login an read the data.
  2. We create a Company object, for our query.
  3. We create RequestOptions object with Chunked property set to true and ChunkSize property set to 10.
  4. Using the QueryCompany method of CompaniesWebServiceClient we query the database for first chunk of records (pass RequestOptions object as second parameter of QueryCompany method).
  5. After the query is completed we check if was executed successfully. If any matching companies are found we display their Name and Street. If the query failed we display the error message.
  6. Before we can query for next chunk of records we need to set ChunkQid property of RequestOptions to the value of ChunkQid from response object and set Chunked property of RequestOptions object to false.
  7. We continue to query for chunks of records and displaying Name and Street values until query won’t return more data.

Tenant Catalogs

To create business objects with tenant catalogs you may use the following example code as a template:
 // create tenant objects
   Tenant oCountryTenant = new Tenant();
   oCountryTenant.Field = "Country";
   oCountryTenant.TenantNumber = 100;

   Tenant oAreaTenant = new Tenant();
   oAreaTenant.Field = "Area";
   oAreaTenant.TenantNumber = 123;

// the tenant objects are put in an array
   Tenant[] oTenants = new Tenant[] { oCountryTenant, oAreaTenant };

 // create an object assigning the tenants array
 // to the CustomTenants property
   Company oCompany = new Company();
   oCompany.CompanyField = "Test Company";
   oCompany.Synonym = "Test Synonym";
   oCompany.Country = "Austria";
   oCompany.Area = "Test Area";
   oCompany.CustomTenants = oTenants;

Conditions

CRM.webservices support two types of conditions

  • Condition and
  • ConcreteConditions.

Condition is an abstract base class type for ComparisonCondition and LogicalCondition, which implements the functionality to define complex conditions.

ConcreteConditions offer the same functionality but without the use of abstract classes.

Some program languages do not support the use of abstract types. You can therefore use this option to omit abstract types in your custom CRUD services.

The following example illustrates to implement conditions using both condition types. The example condition is to find Person objects with a First Name “test” and Last Name “person”.

The main difference between the conditions is ComparisonCondition and LogicalCondition have an Operator property, whereas ConcreteConditions has ComparsionOperator and LogicalOperator properties, only on these must be set to indicate what kind of condition the ConcreteConditions represents.

Note: The description for services with simple conditions is provided in a future revision of this document.

Using Condition

ComparisonCondition oCondition1 = new ComparisonCondition();
oCondition1.TableName = "Person";
oCondition1.FieldName = "FirstName";
oCondition1.Value = "test";
oCondition1.Operator = ComparisonOperator.Equal;

ComparisonCondition oCondition2 = new ComparisonCondition();
oCondition2.TableName = "Person";
oCondition2.FieldName = "LastName";
oCondition2.Value = "person";
oCondition2.Operator = ComparisonOperator.Equal;

LogicalCondition oConditions = new LogicalCondition();
oConditions.Operator = LogicalOperator.And;
oConditions.Conditions = new Condition[] { oCondition1, oCondition2 };

Person oPerson = new Person();
oPerson.CustomConditions = oConditions;

Response oResponse = companiesService.QueryPerson(oPerson, null);

Using ConcreteConditions

ConcreteConditions oConcreteCondition1 = new ConcreteConditions();
               oConcreteCondition1.TableName = "Person";
               oConcreteCondition1.FieldName = "FirstName";
               oConcreteCondition1.Value = "test";
               oConcreteCondition1.ComparisonOperator = ComparisonOperator.Equal;
               
               ConcreteConditions oConcreteCondition2 = new ConcreteConditions();
               oConcreteCondition2.TableName = "Person";
               oConcreteCondition2.FieldName = "LastName";
               oConcreteCondition2.Value = "person";
               oConcreteCondition2.ComparisonOperator = ComparisonOperator.Equal;
               
               ConcreteConditions oConcreteConditions = new ConcreteConditions();
               oConcreteConditions.LogicalOperator = LogicalOperator.And;
               oConcreteConditions.Conditions = new ConcreteConditions[] { 
               oConcreteCondition1, oConcreteCondition2 };
               
               Person oPerson = new Person();
               oPerson.ConcreteConditions = oConcreteConditions;
               
               Response oResponse = companiesService.QueryPerson(oPerson, null);

Parent Catalog condition

To create a condition to find a record by a catalog and its parent catalog use the value2 attribute of the comparison condition to specify the parent catalog value.

The following example code can be used as a template:
 // condition with a catalog and it's parent catalog
 ComparisonCondition oComparisonCondition = new ComparisonCondition();
 oComparisonCondition.Operator = ComparisonOperator.Equal;
 oComparisonCondition.TableName = "Interests";
 oComparisonCondition.FieldName = "Interest";
 oComparisonCondition.Value = "catalog value";
 oComparisonCondition.Value2 = "parent catalog value";

External Key Catalogs

Allows catalog values are specified by external key (as opposed to their textual value). This can be set at request level and/or for individual conditions.

The following example shows how to use external catalog keys at the request level. All catalog values must be specified as external keys and all catalog values returned are their external key values.

The 2nd condition (oCondition2) in the example illustrates how to override that behavior for a single condition.

// create request options 
 RequestOptions oRequestOptions = new RequestOptions();
 oRequestOptions.CatExKeys = true;
 oRequestOptions.VisibleFields = "my catalog field";

 // create conditions
 ComparisonCondition oCondition1 = new ComparisonCondition();
 oCondition1.TableName = "Company";
 oCondition1.FieldName = "my catalog field";
 oCondition1.Value = "external_key_1";
 oCondition1.Operator = ComparisonOperator.Equal;

 ComparisonCondition oCondition2 = new ComparisonCondition();
 oCondition2.TableName = "Company";
 oCondition2.FieldName = "my catalog field";
 oCondition2.Value = "my catalog text value";
 oCondition2.Operator = ComparisonOperator.Equal;
 oCondition2.ExtKey = false;

 LogicalCondition oConditions = new LogicalCondition();
 oConditions.Operator = LogicalOperator.Or;
 oConditions.Conditions = new Condition[] { oCondition1, oCondition2 };

 // create an object to query
 Company oCompany = new Company();
 oCompany.CustomConditions = oConditions;

 // create an object to query
 Response oResponse = companiesService.QueryCompany(oCompany, oRequestOptions);
The following sample shows how to use external catalog keys at the condition level.
 // create condition
 ComparisonCondition oCondition = new ComparisonCondition();
 oCondition.TableName = "Company";
 oCondition.FieldName = "my catalog field";
 oCondition.Value = "external_key_1";
 oCondition.Operator = ComparisonOperator.Equal;
 oCondition2.ExtKey = true;

Request Options

Chunked

Reading in chunks allows for more efficient retrieving of (possible) huge data sets and navigating in these data sets. First only the record-IDs are read (up to maxrecords). Subsequent queries return the next "page" (determined by ChunkSize) of records by referencing the original query via a query-ID (ChunkQid).
const long English = 1;
 // Login
 var companiesService = new CompaniesWebServiceClient();
 companiesService.Login("TestUser", "TestPassword", English);
 Company company = new Company();
 RequestOptions oRequestOptions = new RequestOptions();
 oRequestOptions.Chunked = true;
 oRequestOptions.ChunkSize = 10;
 // get the first chunk of results
 Response oResponse = companiesService.QueryCompany(company, oRequestOptions);
 Console.WriteLine("Succeeded: " + !oResponse.Error);
 // check the results, to see if the query has completed
 if (!oResponse.Error)
 {
 // output the first chunk
 int nCount = 0;
 const string outputFormat = "Company: {0}; Street: {1};";
 foreach (var companyInResponse in oResponse.Objects.Cast<Company>())
 {
 Console.WriteLine(outputFormat, 
companyInResponse.CompanyField, companyInResponse.Street);
 }
 // **** important ****
 // update the request options for getting the next chunks
 // set returned qid and switch off Chunked attribute
 oRequestOptions.ChunkQid = oResponse.ChunkQid;
 oRequestOptions.Chunked = false;
 bool bContinue = true;
 // continue getting chunks while they are available
 while (bContinue)
 {
 // get the next chunk of results
 oResponse = companiesService.QueryCompany(company, oRequestOptions);
 Console.WriteLine("Succeeded: " + !oResponse.Error);
 // check the results, to see if the query has completed
 if (!oResponse.Error)
 {
 nCount = 0;
 foreach (var companyInResponse in oResponse.Objects.Cast<Company>())
 {
 Console.WriteLine(outputFormat, 
companyInResponse.CompanyField, companyInResponse.Street);
 }
 }
 else
 {
 // if the query ends or there is an error output terminate the loop
 Console.WriteLine("ErrorText: " + oResponse.ErrorText);
 Console.WriteLine("Description: " + oResponse.Description);
 bContinue = false;
 }
 }
 }
 else
 {
 // if there is an error with the initial query output the reason
 Console.WriteLine("ErrorText: " + oResponse.ErrorText);
 Console.WriteLine("Description: " + oResponse.Description);
 }

Chunk size

Defines the maximum number of records to be read in one chunk.

For further details see chapter Chunked above.

ChunkQid

A unique ID for a "chunked read" operation, The ChunkQid (query id) can be used to read subsequent chunks of data.

For further details see chapter Chunked above.

CreateNewCatalogValue

An indicator of whether unknown catalog values should be automatically created. It is recommended that new catalog values is not created using this flag due to risk of unwanted duplicates. It works only in the catalog base language.

Matchup

An indicator of whether the matchup is to be done for records during an insert.

MaxRecords

The maximum number of records to be read in a query. The default value is 9999.

RecordId

A 64-bit record id specified in hexadecimal notation and prefixed with an 'x' character.

SkipRecords

Sets the number of records to be skipped, before returning records from a query.

The following example shows how to skip 10 records.

// create request options 
 RequestOptions oRequestOptions = new RequestOptions();
 oRequestOptions.SkipRecords = 10;
 // create an object to query
 Company oCompany = new Company();
 // create an object to query
 Response oResponse = companiesService.QueryCompany(oCompany, oRequestOptions);
Note: A more performant way for paging through large result sets is using Chunked Queries (see chapter Chunked above).

Visible Fields

An indicator, which fields should be retrieved by the layer beyond web services.

RESTSample

This sample illustrates how to implement a client for RESTful service endpoints. The example loads the request templates (e.g. queryrequest.xml, insertrequest.xml, json_queryrequest.txt etc.) and sends it to the service.

Note: The example is intended to work with a CRUD Service called "Companies", which contains Company and Person objects.