If you haven’t read all the other parts of this series: Part 1 - Base, Part 2 - Data Query, Part 3 - Post Content, make sure to go read them because this component is the results of all three of them.
What is a Service Data Object (SDO)?
Service Data Objects was created to support the Aptify Web client ability to call stored procedures. You configure which stored procedures can be accessed through the Service Data Objects entity within Aptify. One of the primary examples of using this was to support the ability to have prompted drop down lists populate with values from a stored procedure instead of being manually configured. But it quickly expanded to being used to integrations and membership portals as Aptify clients started using the Soa.
The downfall though, the service was built for Aptify Web. It wasn’t built with the purpose of being used for integrations and membership portals. The response object… is fine. Not how I’d design it, but it works.
eBiz Soa Doesn’t Have SDO’s?
Well I wouldn’t say doesn’t. OTB box it doesn’t include the controller and setup for it. BUT, you totally can add it, it’s pretty easy. Add dlls, add the SDO controller record to the eBiz Soa’s controller configuration record. There you go, SDO’s through eBiz Soa.
We Can Do Better
What if we take the Data Query component and the Post Content component, and sprinkle in the SDO concept :D.
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using PB.Rexies.AptifyBits;
using PB.Rexies.Data;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
namespace SimplifyAptify.AptifyConfigs.Soa.EndpointComponents
{
public class ServiceDataObjectEndpointComponent : PostContentEndpointComponent<Dictionary<string, object>>
{
protected override string RunCore()
{
Logger.LogInformation("ServiceDataObjectEndpointComponent");
// Ensure & Get Post Content Data
EnsureSetPostData();
// Verify SDO Key
if (!PostData.ContainsKey("SDO") || string.IsNullOrWhiteSpace(PostData["SDO"].ToString()))
{
throw new ArgumentException("SDO key is required.");
}
Logger.LogInformation("ServiceDataObjectEndpointComponent - Post Data: {PostData}", JsonConvert.SerializeObject(PostData, Formatting.Indented));
// Get SDO Details
var dataObjectName = string.Empty;
var requiresPrinciple = true;
var lstProps = new Dictionary<string, bool>();
using (var reader = DataAction.ExecuteDataReaderParameterized(
"spEndpointComponent_ServiceDataObjectEndpointComponent_sa",
CommandType.StoredProcedure,
DataAction.GetDataParameter("@SDO", SqlDbType.NVarChar, PostData["SDO"])
))
{
if (reader.Read())
{
dataObjectName = reader.GetNullableString("DataObjectName") ?? string.Empty;
requiresPrinciple = reader.GetNullableBoolean("RequiresPrinciple") ?? true;
}
reader.NextResult();
while (reader.Read())
{
lstProps.Add(reader.GetString("Name"), reader.GetBoolean("IsRequired"));
}
}
Logger.LogInformation("BaseEndpointProcessFlowComponent - DBObject: {DataObjectName} - Requires Principle: {RequiresPrinciple}", dataObjectName, requiresPrinciple);
// Verify SDO Details
if (string.IsNullOrWhiteSpace(dataObjectName))
{
throw new ArgumentException("Invalid SDO.");
}
if (requiresPrinciple)
{
EnsureAuthenticated();
}
if (lstProps.Count > 0)
{
var missing = lstProps.Where(p => p.Value)
.Select(p => p.Key)
.Except(PostData.Keys, StringComparer.OrdinalIgnoreCase);
if (missing.Any())
{
throw new ArgumentException($"Required properties ({string.Join(", ", missing)}) are missing.");
}
}
// Execute Data Query for SDO
var dataQueryComponent = new DataQueryEndpointComponent()
{
DataObjectName = dataObjectName,
AuthenticatedPrincipalRecordId = AuthenticatedPrincipalRecordId
};
foreach (var propVal in PostData.Where(p => !p.Key.Equals("SDO", StringComparison.OrdinalIgnoreCase)))
{
dataQueryComponent.Properties.SetProperty(propVal.Key, propVal.Value);
}
dataQueryComponent.Config(Application);
dataQueryComponent.Run();
Properties.SetProperty(OutputEndpointComponentResponsePropertyName, dataQueryComponent.Properties.GetProperty(OutputEndpointComponentResponsePropertyName));
Properties.SetProperty(EndpointComponentResponsePropertyName, dataQueryComponent.Properties.GetProperty(EndpointComponentResponsePropertyName));
return SuccessResult;
}
}
}
/*
NAME: spEndpointComponent_ServiceDataObjectEndpointComponent_sa
DESC: Gets the details needed for the setup and validation of the Service Data Object Endpoint Component.
TYPE: Stored Procedure
GRANT: GRANT EXECUTE ON spEndpointComponent_ServiceDataObjectEndpointComponent_sa TO EndUsers
*/
CREATE PROCEDURE spEndpointComponent_ServiceDataObjectEndpointComponent_sa (
@SDO NVARCHAR(250),
@ServiceApplicationName NVARCHAR(250) = 'e-Business'
) AS BEGIN
-- GET SDO ID
DECLARE @SDOID INT
SELECT TOP 1 @SDOID = sdo.ID
FROM
ServiceDataObject sdo
INNER JOIN ServiceDataObjectApplication a ON
a.ServiceDataObjectID = sdo.ID
INNER JOIN ServiceApplication sa ON
sa.ID = a.ServiceApplicationID
AND sa.Name = @ServiceApplicationName
INNER JOIN DBObject o ON
o.ID = sdo.DatabaseObjectID
AND o.Type = 'Stored Procedure'
WHERE
sdo.Name = @SDO
-- SDO Details
SELECT
sdo.SQL [DataObjectName],
sdo.EnableSecurity [RequiresPrinciple]
FROM
vwServiceDataObjects sdo
WHERE
sdo.ID = @SDOID
-- SDO Properties
SELECT
p.Name,
p.IsRequired
FROM
ServiceDataObjectParameter p
WHERE
p.ServiceDataObjectID = @SDOID
END
The Dictionary<string, object>
allows us have a request body of flat property and value.
An example call would look like:
{
"SDO": "Data-Query-Examples",
"Example": "MultipleDatasets",
"LimitValue": 222
}
eBiz Soa JSON endpoint
Were not 100% done with the eBiz Soa JSON endpoints. We still need to add our SDO to the eBiz SAO.
SDO Endpoint
{
"endpoints": {
"ServiceDataObject": {
"route": {
"httpMethod": "POST",
"segments": {
"sdo": {
"isLiteral": true,
"type": "string"
}
}
},
"inputEntityDefinition": {
"name": "ServiceDataObjectInputDefinition",
"$ref": "<Path to folder as needed>/utilities.json#/InputFields/PostContent"
},
"outputEntityDefinition": null,
"businessLogic": {
"ServiceDataObjectBusinessLogic": {
"executionType": "processFlow",
"processFlowProperties": {
"processFlowName": "<Name of your process flow that implements your SDO Component>",
"processFlowParameters": {
"AuthenticatedPrincipalRecordId": "@AuthenticatedAttributes.AuthenticatedPrincipalRecordId",
"PostContent": "@Request.PostContent"
}
}
}
},
"options": {
"customOutput": true
},
"security": {
"AllowAnonymous": {
"$ref": "<Path to the OTB Common folder>/security.json#/allowAnonymous"
}
}
}
}
}
Utilities.json
{
"InputFields": {
"PostContent": {
"fields": {
"PostContent": {
"type": "string",
"input": {
"httpMethods": [ "POST" ],
"source": "body"
}
}
}
}
}
}
Setup DB Object and SDO Record
Finally we need an Stored Procedure to execute through the SDO.
#1 - DB Object
/*
NAME: spEndpointComponent_DataQuery_Examples_sa
DESC: Examples of all the Data Query uses.
TYPE: Stored Procedure
GRANT: GRANT EXECUTE ON spEndpointComponent_DataQuery_Examples_sa TO EndUsers
*/
CREATE PROCEDURE spEndpointComponent_DataQuery_Examples_sa (
@AuthenticatedPrincipalRecordId INT,
@Example NVARCHAR(50) = NULL,
@LimitValue NVARCHAR(50) = NULL
) AS BEGIN
SELECT @Example = CASE
WHEN @Example IS NULL OR RTRIM(@Example) = '' THEN 'SingleRecord'
ELSE @Example
END
IF @Example = 'SingleRecord'
BEGIN
SELECT ID, FirstName, LastName
FROM Person
WHERE ID = @AuthenticatedPrincipalRecordId;
END
IF @Example = 'MultipleRecords'
BEGIN
SELECT TOP 10 ID, FirstName, LastName
FROM Person
ORDER BY NEWID()
END
IF @Example = 'SingleRecordTableConfig'
BEGIN
SELECT
tables.TableName,
tables.IsCollection
FROM (
SELECT 'Profile' [TableName], CAST(0 AS BIT) [IsCollection], 0 [Sequence]
) tables
;WITH OtherPersons AS (
SELECT TOP 10 ID, FirstName, LastName, CAST(CASE WHEN ID = @AuthenticatedPrincipalRecordId THEN 1 ELSE 0 END AS BIT) [IsAuthenticatedPrincipalRecord]
FROM Person
WHERE ID != @AuthenticatedPrincipalRecordId
ORDER BY NEWID()
)
SELECT TOP 10 ID, FirstName, LastName, CAST(CASE WHEN ID = @AuthenticatedPrincipalRecordId THEN 1 ELSE 0 END AS BIT) [IsAuthenticatedPrincipalRecord]
FROM Person
WHERE ID = @AuthenticatedPrincipalRecordId
UNION ALL SELECT * FROM OtherPersons
END
IF @Example = 'MultipleRecordsTableConfig'
BEGIN
SELECT
tables.TableName,
tables.IsCollection
FROM (
SELECT 'Persons' [TableName], CAST(1 AS BIT) [IsCollection], 0 [Sequence]
) tables
SELECT TOP 10 ID, FirstName, LastName
FROM Person
ORDER BY NEWID()
END
IF @Example = 'MultipleDatasets'
BEGIN
SELECT TOP 10 ID, FirstName, LastName
FROM Person
ORDER BY NEWID()
SELECT c.Country, c.ISOCode, c.ID
FROM Country c
WHERE @LimitValue IS NULL OR CAST(c.ID AS NVARCHAR(50)) = @LimitValue
SELECT RTRIM(sp.Abbreviation) [Abbr], sp.FullName, sp.CountryID
FROM StateProvince sp
WHERE @LimitValue IS NULL OR CAST(sp.CountryID AS NVARCHAR(50)) = @LimitValue
END
IF @Example = 'MultipleDatasetsTableConfig'
BEGIN
SELECT
tables.TableName,
tables.IsCollection
FROM (
SELECT 'Persons' [TableName], CAST(1 AS BIT) [IsCollection], 0 [Sequence]
UNION SELECT 'Countries' [TableName], CAST(1 AS BIT) [IsCollection], 1 [Sequence]
UNION SELECT 'StateProvinces' [TableName], CAST(1 AS BIT) [IsCollection], 2 [Sequence]
) tables
ORDER BY tables.Sequence
SELECT TOP 10 ID, FirstName, LastName
FROM Person
ORDER BY NEWID()
SELECT c.Country, c.ISOCode, c.ID
FROM Country c
WHERE @LimitValue IS NULL OR CAST(c.ID AS NVARCHAR(50)) = @LimitValue
SELECT RTRIM(sp.Abbreviation) [Abbr], sp.FullName, sp.CountryID
FROM StateProvince sp
WHERE @LimitValue IS NULL OR CAST(sp.CountryID AS NVARCHAR(50)) = @LimitValue
END
IF @Example = 'MultipleDatasetsSingleMultipleTableConfig'
BEGIN
SELECT
tables.TableName,
tables.IsCollection
FROM (
SELECT 'Persons' [TableName], CAST(0 AS BIT) [IsCollection], 0 [Sequence]
UNION SELECT 'Countries' [TableName], CAST(1 AS BIT) [IsCollection], 1 [Sequence]
UNION SELECT 'StateProvinces' [TableName], CAST(1 AS BIT) [IsCollection], 2 [Sequence]
) tables
ORDER BY tables.Sequence
SELECT ID, FirstName, LastName
FROM Person
WHERE ID = @AuthenticatedPrincipalRecordId;
SELECT c.Country, c.ISOCode, c.ID
FROM Country c
WHERE @LimitValue IS NULL OR CAST(c.ID AS NVARCHAR(50)) = @LimitValue
SELECT RTRIM(sp.Abbreviation) [Abbr], sp.FullName, sp.CountryID
FROM StateProvince sp
WHERE @LimitValue IS NULL OR CAST(sp.CountryID AS NVARCHAR(50)) = @LimitValue
END
IF @Example = 'ForJsonPath01'
BEGIN
SELECT (
SELECT
ID,
FirstName,
LastName,
Countries = (
SELECT c.Country, c.ISOCode, c.ID
FROM Country c
WHERE @LimitValue IS NULL OR CAST(c.ID AS NVARCHAR(50)) = @LimitValue
FOR JSON PATH
),
StateProvinces = (
SELECT RTRIM(sp.Abbreviation) [Abbr], sp.FullName, sp.CountryID
FROM StateProvince sp
WHERE @LimitValue IS NULL OR CAST(sp.CountryID AS NVARCHAR(50)) = @LimitValue
FOR JSON PATH
)
FROM
Person
WHERE
ID = @AuthenticatedPrincipalRecordId
FOR JSON PATH,
INCLUDE_NULL_VALUES,
WITHOUT_ARRAY_WRAPPER
) [JsonOutput]
END
IF @Example = 'ForJsonPath02'
BEGIN
SELECT (
SELECT
Profile = JSON_QUERY((
SELECT
ID,
FirstName,
LastName
FROM Person
WHERE ID = @AuthenticatedPrincipalRecordId
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
)),
Countries = (
SELECT c.Country, c.ISOCode, c.ID
FROM Country c
WHERE @LimitValue IS NULL OR CAST(c.ID AS NVARCHAR(50)) = @LimitValue
FOR JSON PATH
),
StateProvinces = (
SELECT RTRIM(sp.Abbreviation) [Abbr], sp.FullName, sp.CountryID
FROM StateProvince sp
WHERE @LimitValue IS NULL OR CAST(sp.CountryID AS NVARCHAR(50)) = @LimitValue
FOR JSON PATH
)
FOR JSON PATH,
INCLUDE_NULL_VALUES,
WITHOUT_ARRAY_WRAPPER
) [JsonOutput]
END
END
#2 - Service Data Object
- Name: Data-Query-Examples
- Enable Security: True
- DBObject: spEndpointComponent_DataQuery_Examples_sa
- Applications:
- e-Business
- Parameters:
- Name: Example
- IsRequired: True
Examples
Uses the Data-Query-Examples SDO from above, here are some examples of using it.
URL: POST /v1/sdo
Single Record
// Post Body
{
"PostContent": "{\"SDO\":\"Data-Query-Examples\",\"Example\":\"SingleRecord\"}"
}
// Response
{
"IsSuccess": true,
"Data": {
"ID": 2545,
"FirstName": "Christopher",
"LastName": "Hunter"
},
"Message": ""
}
Multiple Records
// Post Body
{
"PostContent": "{\"SDO\":\"Data-Query-Examples\",\"Example\":\"MultipleRecords\"}"
}
// Response
{
"IsSuccess": true,
"Data": [
{
"ID": 641,
"FirstName": "James",
"LastName": "Miller"
},
{
"ID": 390,
"FirstName": "John",
"LastName": "Jackson"
},
{
"ID": 1188,
"FirstName": "Brian",
"LastName": "Hill"
},
{
"ID": 2289,
"FirstName": "Querida",
"LastName": "Neal"
},
{
"ID": 1542,
"FirstName": "Alexander",
"LastName": "Jones"
},
{
"ID": 962,
"FirstName": "Dahlia",
"LastName": "Miller"
},
{
"ID": 2257,
"FirstName": "Herbert",
"LastName": "Beck"
},
{
"ID": 1223,
"FirstName": "Mary",
"LastName": "Neal"
},
{
"ID": 1716,
"FirstName": "Alexandra",
"LastName": "Douglas"
},
{
"ID": 1655,
"FirstName": "Oliver",
"LastName": "Baldwin"
}
],
"Message": ""
}
Multiple Datasets
// Post Body
{
"PostContent": "{\"SDO\":\"Data-Query-Examples\",\"Example\":\"MultipleDatasets\",\"LimitValue\":222}"
}
//Response
{
"IsSuccess": true,
"Data": {
"Table": [
{
"ID": 1161,
"FirstName": "Athena",
"LastName": "Baldwin"
},
{
"ID": 257,
"FirstName": "Christina",
"LastName": "Byrd"
},
{
"ID": 608,
"FirstName": "Athena",
"LastName": "Yukon"
},
{
"ID": 1978,
"FirstName": "Habika",
"LastName": "Scott"
},
{
"ID": 2272,
"FirstName": "Yale",
"LastName": "Jackson"
},
{
"ID": 125,
"FirstName": "Abraham",
"LastName": "Yukon"
},
{
"ID": 65,
"FirstName": "Odessa",
"LastName": "Tate"
},
{
"ID": 43,
"FirstName": "Paul",
"LastName": "Holland"
},
{
"ID": 924,
"FirstName": "Rabea",
"LastName": "Hale"
},
{
"ID": 2077,
"FirstName": "Christina",
"LastName": "Beck"
}
],
"Table1": [
{
"Country": "United States",
"ISOCode": "US",
"ID": 222
}
],
"Table2": [
{
"Abbr": "AL",
"FullName": "Alabama",
"CountryID": 222
},
{
"Abbr": "AK",
"FullName": "Alaska",
"CountryID": 222
},
{
"Abbr": "AS",
"FullName": "American Samoa",
"CountryID": 222
},
{
"Abbr": "AZ",
"FullName": "Arizona",
"CountryID": 222
},
...........
{
"Abbr": "WA",
"FullName": "Washington",
"CountryID": 222
},
{
"Abbr": "WV",
"FullName": "West Virginia",
"CountryID": 222
},
{
"Abbr": "WI",
"FullName": "Wisconsin",
"CountryID": 222
},
{
"Abbr": "WY",
"FullName": "Wyoming",
"CountryID": 222
}
]
},
"Message": ""
}
Multiple Datasets w/Table Configuration
// Post Body
{
"PostContent": "{\"SDO\":\"Data-Query-Examples\",\"Example\":\"MultipleDatasetsTableConfig\",\"LimitValue\":222}"
}
// Response
{
"IsSuccess": true,
"Data": {
"Persons": [
{
"ID": 1769,
"FirstName": "Aiesha",
"LastName": "Mann"
},
{
"ID": 2205,
"FirstName": "Gary",
"LastName": "Taylor"
},
{
"ID": 1638,
"FirstName": "Xanthe",
"LastName": "Byrd"
},
{
"ID": 53,
"FirstName": "Baka",
"LastName": "Garcia"
},
{
"ID": 1451,
"FirstName": "Kevin",
"LastName": "Jackson"
},
{
"ID": 1495,
"FirstName": "David",
"LastName": "Estevez"
},
{
"ID": 2471,
"FirstName": "Sebastian",
"LastName": "O'Neal"
},
{
"ID": 2364,
"FirstName": "Noah",
"LastName": "Harris"
},
{
"ID": 2390,
"FirstName": "Paige",
"LastName": "Jones"
},
{
"ID": 51,
"FirstName": "Alexandra",
"LastName": "Hale"
}
],
"Countries": [
{
"Country": "United States",
"ISOCode": "US",
"ID": 222
}
],
"StateProvinces": [
{
"Abbr": "AL",
"FullName": "Alabama",
"CountryID": 222
},
{
"Abbr": "AK",
"FullName": "Alaska",
"CountryID": 222
},
{
"Abbr": "AS",
"FullName": "American Samoa",
"CountryID": 222
},
{
"Abbr": "AZ",
"FullName": "Arizona",
"CountryID": 222
},
...........
{
"Abbr": "WA",
"FullName": "Washington",
"CountryID": 222
},
{
"Abbr": "WV",
"FullName": "West Virginia",
"CountryID": 222
},
{
"Abbr": "WI",
"FullName": "Wisconsin",
"CountryID": 222
},
{
"Abbr": "WY",
"FullName": "Wyoming",
"CountryID": 222
}
]
},
"Message": ""
}
Multiple Datasets Single and Multiple w/Table Configuration
// Post Body
{
"PostContent": "{\"SDO\":\"Data-Query-Examples\",\"Example\":\"MultipleDatasetsSingleMultipleTableConfig\",\"LimitValue\":222}"
}
// Response
{
"IsSuccess": true,
"Data": {
"Persons": {
"ID": 2545,
"FirstName": "Christopher",
"LastName": "Hunter"
},
"Countries": [
{
"Country": "United States",
"ISOCode": "US",
"ID": 222
}
],
"StateProvinces": [
{
"Abbr": "AL",
"FullName": "Alabama",
"CountryID": 222
},
{
"Abbr": "AK",
"FullName": "Alaska",
"CountryID": 222
},
{
"Abbr": "AS",
"FullName": "American Samoa",
"CountryID": 222
},
{
"Abbr": "AZ",
"FullName": "Arizona",
"CountryID": 222
},
...........
{
"Abbr": "WA",
"FullName": "Washington",
"CountryID": 222
},
{
"Abbr": "WV",
"FullName": "West Virginia",
"CountryID": 222
},
{
"Abbr": "WI",
"FullName": "Wisconsin",
"CountryID": 222
},
{
"Abbr": "WY",
"FullName": "Wyoming",
"CountryID": 222
}
]
},
"Message": ""
}
For Json Path - 01
// Post Body
{
"PostContent": "{\"SDO\":\"Data-Query-Examples\",\"Example\":\"ForJsonPath01\",\"LimitValue\":222}"
}
// Response
{
"IsSuccess": true,
"Data": {
"ID": 2545,
"FirstName": "Christopher",
"LastName": "Hunter",
"Countries": [
{
"Country": "Andorra",
"ISOCode": "AD",
"ID": 1
},
{
"Country": "United Arab Emirates",
"ISOCode": "AE",
"ID": 2
},
{
"Country": "Afghanistan",
"ISOCode": "AF",
"ID": 3
},
................
{
"Country": "Canada",
"ISOCode": "CA",
"ID": 36
},
................
{
"Country": "United States",
"ISOCode": "US",
"ID": 222
},
................
{
"Country": "Serbia",
"ISOCode": "RS",
"ID": 247
},
{
"Country": "West Bank",
"ISOCode": "WE",
"ID": 248
}
],
"StateProvinces": [
{
"Abbr": "AB",
"FullName": "Alberta",
"CountryID": 36
},
{
"Abbr": "BC",
"FullName": "British Columbia",
"CountryID": 36
},
{
"Abbr": "MB",
"FullName": "Manitoba",
"CountryID": 36
},
{
"Abbr": "NB",
"FullName": "New Brunswick",
"CountryID": 36
},
{
"Abbr": "NL",
"FullName": "Newfoundland and Labrador",
"CountryID": 36
},
{
"Abbr": "NT",
"FullName": "Northwest Territories",
"CountryID": 36
},
{
"Abbr": "NS",
"FullName": "Nova Scotia",
"CountryID": 36
},
{
"Abbr": "NU",
"FullName": "Nunavut",
"CountryID": 36
},
{
"Abbr": "ON",
"FullName": "Ontario",
"CountryID": 36
},
................
{
"Abbr": "SA",
"FullName": "South Australia",
"CountryID": 14
},
{
"Abbr": "TAS",
"FullName": "Tasmania",
"CountryID": 14
},
{
"Abbr": "VIC",
"FullName": "Victoria",
"CountryID": 14
},
{
"Abbr": "WA",
"FullName": "Western Australia",
"CountryID": 14
}
]
},
"Message": ""
}
For Json Path - 02
// Post Body
{
"PostContent": "{\"SDO\":\"Data-Query-Examples\",\"Example\":\"ForJsonPath02\",\"LimitValue\":222}"
}
// Response
{
"IsSuccess": true,
"Data": {
"Profile": {
"ID": 2545,
"FirstName": "Christopher",
"LastName": "Hunter"
},
"Countries": [
{
"Country": "Canada",
"ISOCode": "CA",
"ID": 36
}
],
"StateProvinces": [
{
"Abbr": "AB",
"FullName": "Alberta",
"CountryID": 36
},
{
"Abbr": "BC",
"FullName": "British Columbia",
"CountryID": 36
},
{
"Abbr": "MB",
"FullName": "Manitoba",
"CountryID": 36
},
{
"Abbr": "NB",
"FullName": "New Brunswick",
"CountryID": 36
},
{
"Abbr": "NL",
"FullName": "Newfoundland and Labrador",
"CountryID": 36
},
{
"Abbr": "NT",
"FullName": "Northwest Territories",
"CountryID": 36
},
................
{
"Abbr": "QC",
"FullName": "Quebec",
"CountryID": 36
},
{
"Abbr": "SK",
"FullName": "Saskatchewan",
"CountryID": 36
},
{
"Abbr": "YT",
"FullName": "Yukon",
"CountryID": 36
}
]
},
"Message": ""
}
Simpler eBiz Soa
That’s it. The combination of these 3 components should make setting up and implementing endpoints simpler and faster for you.
NOTE: ALWAYS remember security and user context. Just because it’s easier to generate your endpoints, it’s no excuse to become a lazy developer.
If you haven’t join yet, make sure to join us on the Aptify Developer Round-Table every other month.
Enjoy.
Aptify Clients
Continue the conversation on the Community Forums.