Create A Complex Service In D365

Share this:

In a previous article we looked at how to setup Postman to call a existing D365 service. We then reviewed the steps to create a new custom service in Dynamics 365 F&S. However that was a very basic service. Finally in this article we will create a complex service in D365. Do you need to send an array, or list, of data into the system, or perhaps several different objects? In this article I will show you how.

What Are The Steps?

In order to create a complex custom service in D365, we still need the same core components of any service.

  1. We need a new class that is our ‘Request‘ object. The data we send in will be mapped to the variables in this class. Then we will use this class to run our operation.
  2. We need a new calls that is our ‘Response‘ object. In our code we will set the variables in this class to contain whatever information we want returned to the calling program. D365 will then convert these values into JSON data.
  3. We need a Service class. This is the class where we will put the code that will: a) read the data in our Request object, b) run some process, and then c) populate the Response object.
  4. We need a Service object, that points to our Service class. This lets Dynamics 365 know that our class can be called by an outside system.
  5. Lastly, we need a Service Group to put our Service object into.

What is New in Our Complex Custom Service?

Let’s pretend that we have information for a sales order that is coming from an outside system, such as a website, that we want to send into D365. We can use a complex service to do that.

Before we create the other objects of the service, we will create a ‘header’ class to contain the sales order header information. And we will create a ‘line’ class to contain the sales order line information. Then I will show you how your service can use this information

Create The Header Class

In your Visual Studio project, create a new class by right clicking on your project, and selecting ‘Add item’. Then select ‘class’ from the list, and selecting ok. In my case I am going to create a very simple class with just a few variables. Remember to include the [DataContractAttribute] attribute above your class. And use the [DataMember(“<name of your field>”] attribute on each parm method. I named my class ‘rsmTutOrderHeaderClass’, and it looks like this:

[DataContractAttribute]
public class rsmTutOrderHeaderClass
{
    private str messageId;
    private str salesOrderNumber;
    private str customerAccountNumber;

    [DataMember("MessageId")]
    public str parmMessageId(str _value = messageId)
    {
        if (!prmIsDefault(_value))
        {
            messageId = _value;
        }

        return messageId;
    }

    [DataMember("SalesOrderNumber")]
    public str parmSalesOrderNumber(str _value = salesOrderNumber)
    {
        if (!prmIsDefault(_value))
        {
            salesOrderNumber = _value;
        }

        return salesOrderNumber;
    }

    [DataMember("CustomerAccountNumber")]
    public str parmCustomerAccountNumber(str _value = customerAccountNumber)
    {
        if (!prmIsDefault(_value))
        {
            customerAccountNumber = _value;
        }

        return customerAccountNumber;
    }
}

Create The Line Class

Repeat the same steps to create another class. I named mine ‘rsmTutOrderLineClass’. It looks like this:

[DataContractAttribute]
public class rsmTutOrderLineClass
{
    private str messageId;
    private real salesQuantity;
    private str itemNumber;
    private real discountPercentage;
    private str dlvTerm;
    private real discount;
    private real unitPrice;
    private real lineAmount;
    private date requestedReceiptDate;
    private str lineNumberExternal;
    private str dlvMode;

    [DataMember("SalesQuantity")]
    public real parmSalesQuantity(real _value = salesQuantity)
    {
        if (!prmIsDefault(_value))
        {
            salesQuantity = _value;
        }
        return salesQuantity;
    }

    [DataMember("ItemNumber")]
    public str parmItemNumber(str _value = itemNumber)
    {
        if (!prmIsDefault(_value))
        {
            itemNumber = _value;
        }
        return itemNumber;
    }

    [DataMember("DiscountPercentage")]
    public real parmDiscountPercentage(real _value = discountPercentage)
    {
        if (!prmIsDefault(_value))
        {
            discountPercentage = _value;
        }
        return discountPercentage;
    }

    [DataMember("DlvTerm")]
    public str parmDlvTerm(str _value = dlvTerm)
    {
        if (!prmIsDefault(_value))
        {
            dlvTerm = _value;
        }
        return dlvTerm;
    }

    [DataMember("Discount")]
    public real parmDiscount(real _value = discount)
    {
        if (!prmIsDefault(_value))
        {
            discount = _value;
        }
        return discount;
    }

    [DataMember("UnitPrice")]
    public real parmUnitPrice(real _value = unitPrice)
    {
        if (!prmIsDefault(_value))
        {
            unitPrice = _value;
        }
        return unitPrice;
    }

    [DataMember("LineAmount")]
    public real parmLineAmount(real _value = lineAmount)
    {
        if (!prmIsDefault(_value))
        {
            lineAmount = _value;
        }
        return lineAmount;
    }

    [DataMember("MessageId")]
    public str parmMessageId(str _value = messageId)
    {
        if (!prmIsDefault(_value))
        {
            messageId = _value;
        }
        return messageId;
    }

    [DataMember("RequestedReceiptDate")]
    public date parmRequestedReceiptDate(date _value = requestedReceiptDate)
    {
        if (!prmIsDefault(_value))
        {
            requestedReceiptDate = _value;
        }
        return requestedReceiptDate;
    }

    [DataMember("LineNumberExternal")]
    public str parmLineNumberExternal(str _value = lineNumberExternal)
    {
        if (!prmIsDefault(_value))
        {
            lineNumberExternal = _value;
        }
        return lineNumberExternal;
    }

    [DataMember("DlvMode")]
    public str parmDlvMode(str _value = dlvMode)
    {
        if (!prmIsDefault(_value))
        {
            dlvMode = _value;
        }
        return dlvMode;
    }
}

Create the Request Class

Now we are ready to create the rest of the objects needed for the service. When you create a custom service in D365, the Request class needs to have a variable for each piece of data you are sending into the system.

First, create a new class in Visual Studio. I named my ‘rsmTutRequest’.

Next, add the Attribute [DataContractAttribute] just above the class declaration.

Add a variable. I named mine ‘dataAreaId’.

Create a parameter method for that variable, and put the attribute [DataMember(“<The name of your Data>“)] above the method.

Your code should look like this:

[DataContractAttribute]
class rsmTutRequest
{
    private str dataAreaId;

    [DataMember("DataAreaId")]
    public str parmDataAreaId(str _value = dataAreaId)
    {
        if (!prmIsDefault(_value))
        {
            dataAreaId = _value;
        }

        return dataAreaId;
    }
}

Add The Header To The Request Class

Next, in this more complex example, add a parm method that will take in an entire class object. In our example, the service is taking in an entire sales order. We need to send in information for the sales order header, and information for the sales order lines. Let’s start with the header. Add the following code to the class, where the name of the class object is rsmTutOrderHeaderClass:

[DataMember("rsmTutOrderHeaderClass")]
    public rsmTutOrderHeaderClass parmrsmTutOrderHeaderClass(rsmTutOrderHeaderClass _rsmTutOrderHeaderClass = rsmTutOrderHeaderClass)
    {
        if (!prmIsDefault(_rsmTutOrderHeaderClass))
        {
            rsmTutOrderHeaderClass = _rsmTutOrderHeaderClass;
        }

        return rsmTutOrderHeaderClass;
    }

Note: In the value inside the [DataMember(“<property name>”)] does not need to match the name of the class. It should be whatever property name you will use in sending in the data. This could be a JSON or XML tag name.

Add The Lines To The Request Class

Next, in this more complex example add a parm method that will take in a List of class objects. In this example, these are the sales order lines. This is the key part of this article. Your code should look like this:

[DataMemberAttribute("rsmTutOrderLineClass"),
    DataCollection(Types::Class, classStr(rsmTutOrderLineClass)),
    AifCollectionTypeAttribute('_rsmTutOrderLineClassArray', Types::Class, classStr(rsmTutOrderLineClass)),
    AifCollectionTypeAttribute('return', Types::Class, classStr(rsmTutOrderLineClass))]
    public List parmrsmTutOrderLineClassArray(List _rsmTutOrderLineClassArray = rsmTutOrderLineClassArray)
    {
        if (!prmIsDefault(_rsmTutOrderLineClassArray))
        {
            rsmTutOrderLineClassArray = _rsmTutOrderLineClassArray;
        }

        return rsmTutOrderLineClassArray;
    }

The attribute above the parameter in this case is different to allow for an array or list of data to be sent in. Let’s breakdown and explain each line.

  • [DataMemberAttribute(“rsmTutOrderLineClass”), – This lets the system know that when the data is sent in, it will use a JSON or XML tag named ‘rsmTutOrderLineClass’. This can be set to whatever your data tag is and does not need to match the name of your class.
  • DataCollection(Types::Class, classStr(rsmTutOrderLineClass)), – This specifies the name of the class that the incoming data should be mapped to. In our case, the data we will send it will match the parm methods we have created in the rsmTutOrderLineClass class.
  • AifCollectionTypeAttribute(‘_rsmTutOrderLineClassArray’, Types::Class, classStr(rsmTutOrderLineClass)), – This lets the system know that the name of our parameter in our Service class method will be named _rsmTutoOrderLineClassArray. (You can name the variable whatever you would like.) And that it will be a list of objects.
  • AifCollectionTypeAttribute(‘return’, Types::Class, classStr(rsmTutOrderLineClass))] -This tells the system that parm method will return a list of objects of type rsmTutOrderLineClass

Create The Response Class

The Response class does not change from our system example. The class needs to have a variable for each piece of information we would like to send back to the calling program.

First, create a new class in Visual Studio. I named mine ‘rsmTutReponse’.

Add the attribute [DataContractAttribute] just above the class definition.

Add variables that will contain information you want to return to the calling program. I created variables ‘success, ‘errorMessage’, and ‘debugMessage’.

Finally, create a parameter method for each of your variables. Put the attribute [DataMember(“<The name of your data>“)] above each method definition. The value you put as <The name of your data> will be the name of the JSON tag that is generated.

Your code should now look like this:

[DataContractAttribute]
class rsmTutResponse
{
    private boolean     success;
    private str         errorMessage;
    private str         debugMessage;

    [DataMember("ErrorMessage")]
    public str parmErrorMessage(str _value = errorMessage)
    {
        if (!prmIsDefault(_value))
        {
            errorMessage = _value;
        }
        return errorMessage;
    }

    [DataMember("Success")]
    public Boolean parmSuccess(Boolean _value = success)
    {
        if (!prmIsDefault(_value))
        {
            success = _value;
        }
        return success;
    }

    [DataMember("DebugMessage")]
    public str parmDebugMessage(str _value = debugMessage)
    {
        if (!prmIsDefault(_value))
        {
            debugMessage = _value;
        }
        return debugMessage;
    }
}

Create A Complex Service Class

Now that we have finished the Request and Response class, we will look at what is new when we create a complex Service in D365. The Service class is the most interesting class when you create a custom service in D365. It is responsible for reading values from the Request object, running some process, then setting values on the Response object.

First, create a new class in Visual Studio. I named mine ‘rsmTutService’.

Create a method in the class, named whatever you would like. I named mine ‘create’.

The method needs to take as a parameter which has a type of your Request object. And it needs to return a value which has a type of your Response object. This is my example method definition:

public rsmTutResponse create(rsmTutRequest _request)

Inside the method, I have written code to read the header object from the request object, using the parm method, and pass it into a method named ‘createHeader’. This method is responsible for reading values from the header class object. In my case storing the values into a staging table.

Next, I have written a ListIterator to loop through the array or list of line objects that were sent in as part of the request. For each object, I call the method ‘createLine’. This method is responsible for reading values from the line class object. In my case storing the values into a second staging table. The full code of my service is below:

public class rsmTutService
{
    public rsmTutResponse create(rsmTutRequest _request)
    {
        List lines;
        ListIterator   literator;
        rsmTutOrderLineClass rsmTutOrderLineClass;
        int i;

        var response = new rsmTutResponse();
        changecompany(_request.parmDataAreaId())
        {
            try
            {
                this.createHeader(_request.parmrsmTutOrderHeaderClass());

                //Create lines
                lines = _request.parmrsmTutOrderLineClassArray();
                if (lines)
                {
                    literator = new ListIterator(lines);
                    while (literator.more())
                    {
                        rsmTutOrderLineClass = literator.value();
                        this.createLine(_request.parmrsmTutOrderHeaderClass(), rsmTutOrderLineClass);
                        literator.next();
                    }
                }

          response.parmDebugMessage(_request.parmrsmTutOrderHeaderClass().parmMessageId());
                response.parmSuccess(true);
            }
            catch (Exception::CLRError)
            {
                System.Exception interopException = CLRInterop::getLastException();

                response.parmSuccess(false);
                response.parmErrorMessage(interopException.ToString());
            }
            return response;
        }
    }

    public void createHeader(rsmTutOrderHeaderClass _rsmTutOrderHeaderClass)
    {
        rsmTutOrderHeader rsmTutOrderHeader;
        rsmTutOrderHeader lastrsmTutOrderHeader;

        rsmTutOrderHeader.MessageId = _rsmTutOrderHeaderClass.parmMessageId();
        
        select firstonly lastrsmTutOrderHeader
            where lastrsmTutOrderHeader.MessageId == rsmTutOrderHeader.MessageId;

        if (lastrsmTutOrderHeader)
        {
            // throw error about duplicate order header
            throw Error(strFmt("A message with message ID %1 already exists"));
        }
        else
        {
            rsmTutOrderHeader.Status = rsmCommerceOrderStatus::ToBeProcessed;
        
            rsmTutOrderHeader.CustomerAccountNumber = _rsmTutOrderHeaderClass.parmCustomerAccountNumber();
            rsmTutOrderHeader.SalesOrderNumber = _rsmTutOrderHeaderClass.parmSalesOrderNumber();
            rsmTutOrderHeader.insert();
        }

    }

    public void createLine(rsmTutOrderHeaderClass _rsmTutOrderHeaderClass, rsmTutOrderLineClass _rsmTutOrderLineClass)
    {
        rsmTutOrderLine rsmTutOrderLine;

        rsmTutOrderLine.MessageId = _rsmTutOrderHeaderClass.parmMessageId();
        rsmTutOrderLine.LineNumberExternal = _rsmTutOrderLineClass.parmLineNumberExternal();
        rsmTutOrderLine.ItemNumber = _rsmTutOrderLineClass.parmItemNumber();
        rsmTutOrderLine.SalesQuantity = _rsmTutOrderLineClass.parmSalesQuantity();
        rsmTutOrderLine.UnitPrice = _rsmTutOrderLineClass.parmUnitPrice();
        rsmTutOrderLine.Discount = _rsmTutOrderLineClass.parmDiscount();
        rsmTutOrderLine.LineAmount = _rsmTutOrderLineClass.parmLineAmount();
        rsmTutOrderLine.DiscountPercentage = _rsmTutOrderLineClass.parmDiscountPercentage();
        rsmTutOrderLine.RequestedReceiptDate = _rsmTutOrderLineClass.parmRequestedReceiptDate();
        rsmTutOrderLine.DlvMode = _rsmTutOrderLineClass.parmDlvMode();
        rsmTutOrderLine.DlvTerm = _rsmTutOrderLineClass.parmDlvTerm();
        rsmTutOrderLine.insert();
    }
}

Create The Service Object

The Service object, exposes our Service class and allows it to be called by an outside system with the proper authentication. In Visual Studio, right click on your project, and select ‘Add New Item’. Then select ‘Service’.

Create the service

Give the item a name. I named mine ‘rsmTutServiceSimple’

Open the object. Right click on it and select ‘Properties’

Set Service properties

In the Properties window, set the ‘Class’ property with the name of the Service Class you created earlier. Mine is named ‘rsmTutService’. (The same as the service object). Also, set the ‘External Name’ property to the same value. You can change this to be whatever you want. But it is simpler if you leave it the same.

Service Properties

Now, Right click on the ‘Service Operations’ node, and select ‘New Service Operation’.

New Service Operation

Select the new node that is created, and in the Properties window set the ‘Method’ property to the name of the method in your Service class. I named mine ‘create’. Also set the ‘Name’ property to be the same. (You could change this to be something different. But it is simpler if you set it to be the same as the method name).

Service Operation

Create The Service Group

The Service Group is an object that can contain one or more services and is a way of organizing similar operations together.

In Visual Studio right click on the project and select Add>New Item. Select the ‘Service Group’ item. Then give your object a name. I named mine ‘rsmTutServiceGroup’.

Service Group

Open the new Service Group object, and right click on the main node. Select ‘New Service’ from the drop down.

Select the node that is created, and view the properties window. Set the ‘Name’ and the ‘Service’ properties to the name of your Service object. In my case, the name of my Service object is ‘rsmTutServiceSimple’.

Service Group Service

Call The Service From Postman

You just learned how to create a complex Service in D365, now you need to call it. You can view the previous article for detailed steps on how to use Postman to call the service. In this case the URL to call the service will be:

https://<D365 URL>/api/services/rsmTutServiceGroup/rsmTutService/create

Let’s break down this URL into it’s parts.

  • https:// – This specifies we are making a secure call.
  • <D365 URL> – This should be replaced by the URL used to access your D365 environment. For example: https://usnconeboxax1aos.cloud.onebox.dynamics.com
  • api/services – This text tells D365 that we are calling a service.
  • rsmTutServiceGroup – This is the name of the service group we are calling.
  • rsmTutService – This is the name of the service we are calling.
  • create – This is the name of the method we are calling on the Service class.

In this example the Postman body is more complex. The header information as well as an array of line information needs to be sent in. Note: In JSON, an array of data is wrapped in square brackets like this [ ] My example looks like this.

{
	"_request" :
	{
		"DataAreaId": "USRT",
		"rsmTutOrderHeader" :
		{
			"MessageId": "1234",
			"SalesOrderNumber" : "SO1234",
			"CustomerAccountNumber" :"C000137"
		},
		"rsmTutOrderLineClass" :
		[
			{
				"LineNumberExternal": "1",
				"ItemNumber": "MyItemId",
				"SalesQuantity": 1,
				"DiscountPercentage": 10,
				"Discount": 0.1,
				"UnitPrice": 6,
				"RequestedReceiptDate": "12/15/2021"
			},
			{
				"LineNumberExternal": "2",
				"ItemNumber": "MySecondItemId",
				"SalesQuantity": 4,
				"DiscountPercentage": 10,
				"Discount": 0.1,
				"UnitPrice": 7,
				"RequestedReceiptDate": "12/15/2020"
			},
			{
				"LineNumberExternal": "3",
				"ItemNumber": "MyThirdItemId",
				"SalesQuantity": 6,
				"DiscountPercentage": 10,
				"Discount": 0.1,
				"UnitPrice": 8,
				"RequestedReceiptDate": "12/15/2020"
			},
			{
				"LineNumberExternal": "4",
				"ItemNumber": "MyFourthItemId",
				"SalesQuantity": 1,
				"DiscountPercentage": 10,
				"Discount": 0.1,
				"UnitPrice": 9,
				"RequestedReceiptDate": "12/15/2020"
			}
        ]
	}
}

Conclusion

You have now performed the actions to create a complex service in D365 as well as call it! Awesome job! Now any outside system can call code inside of D365. As well as send in complex information. This can be used by Microsoft Power Apps, Power Automate, Azure Logic Apps, other externals systems, and many more. Enjoy!

Peter Ramer
Peter Ramer is a part of the Managed Application Services team at RSM working on Microsoft Dynamics 365. He focuses on the Retail and Commerce industries. When he is not solving problems and finding ways to accelerate his clients' business, he enjoys time with his three kids and amazing wife.

Share this:

28 thoughts on “Create A Complex Service In D365

Add yours

  1. Hello Peter,
    This is a great article which I used to develop something similar.

    I just had a question.
    Currently your service exchanges data in JSON format. How can I make it to use XML format?
    Will it work if In DataMember attribute I replace “SalesOrderNo” with “”?

    Please respond.

    1. Hi Sohan. I am glad you have found the article helpful. To my knowledge I believe you have to use JSON format. Unfortunately, the base code that reads it in does not expect or have code to read XML code. You could send an xml string inside a single JSON property. Then once that long string was inside D365 you could parse the XML string using either some of the XML parsing classes that are in D365. Or you can use .NET code to deserialize the XML. It just unfortunately does not happen automatically like it does with JSON, I believe.
      It will work to replace the DataMember property in the X++ class with whatever property name you use in your JSON. This is nice so that your JSON property names can be different than the class properties in X++.

  2. Hello Peter,
    This article has been of great help to me, One question though, is it possible to Debug custom service? I am calling a custom service from Postman everything is fine but records are not created in D365, is there anyway to debug the same, when calling same service within D365 it is working fine.
    BR
    Amit

    1. Hi Amit. Yes, you can debug a custom service that you call from Postman. Attach visual studio to the iisexpress or w3wp process. And make sure the debug symbols load. (Make sure you get filled in red circles). Lastly, you need to make sure you are calling the right environment from postman, with everything else setup. But otherwise debugging a call from Postman is just the same as debugging any other code.
      Perhaps make sure you can call the systestservice class and debug it. If you can’t debug that class then perhaps Postman does not have everything setup correctly and it isn’t actually calling the code.

  3. Hi Peter,

    You are describing the case when the JSON structure is flat. But what if the JSON looks like

    “_request” :
    {
    “DataAreaId”: “USRT”,
    “rsmTutOrderHeader” :
    {
    “MessageId”: “1234”,
    “SalesOrderNumber” : “SO1234”,
    “CustomerAccountNumber” :”C000137″
    “rsmTutOrderHeaderDetails” :
    {
    “Notes”: “Some notes attachments”
    }
    },
    “rsmTutOrderLineClass” :
    [
    {
    “LineNumberExternal”: “1”,
    “ItemNumber”: “MyItemId”,
    “SalesQuantity”: 1,
    “DiscountPercentage”: 10,
    “Discount”: 0.1,
    “UnitPrice”: 6,
    “RequestedReceiptDate”: “12/15/2021”
    “rsmTutOrderLineDetailsClass”:
    {
    “LineNotes”: “Some line notes”
    }
    }
    ]
    }
    }

    Will your code example work and how it should be modified in this case?

  4. Hi Peter,
    I want to send data for multiple legal entity for single sales order ? is it possible?

    Like: In your example Message ID 1234 and Sales order: SO1234 (need to insert for many legal entity)

    Example:
    “_request” :
    {
    “DataAreaId”: “USRT”,
    “rsmTutOrderHeader” :
    {
    “MessageId”: “1234”,
    “SalesOrderNumber” : “SO1234”,
    “CustomerAccountNumber” :”C000137″
    “rsmTutOrderHeaderDetails” :
    {
    “Notes”: “Some notes attachments”
    }
    },
    “rsmTutOrderLineClass” :
    [
    {
    “LineNumberExternal”: “1”,
    “ItemNumber”: “MyItemId”,
    “SalesQuantity”: 1,
    “DiscountPercentage”: 10,
    “Discount”: 0.1,
    “UnitPrice”: 6,
    “RequestedReceiptDate”: “12/15/2021”
    “rsmTutOrderLineDetailsClass”:
    {
    “LineNotes”: “Some line notes”
    }
    },

    “_request” :
    {
    “DataAreaId”: “USMF”,
    “rsmTutOrderHeader” :
    {
    “MessageId”: “1234”,
    “SalesOrderNumber” : “SO1234”,
    “CustomerAccountNumber” :”C000137″
    “rsmTutOrderHeaderDetails” :
    {
    “Notes”: “Some notes attachments”
    }
    },
    “rsmTutOrderLineClass” :
    [
    {
    “LineNumberExternal”: “1”,
    “ItemNumber”: “MyItemId”,
    “SalesQuantity”: 1,
    “DiscountPercentage”: 10,
    “Discount”: 0.1,
    “UnitPrice”: 6,
    “RequestedReceiptDate”: “12/15/2021”
    “rsmTutOrderLineDetailsClass”:
    {
    “LineNotes”: “Some line notes”
    }
    }
    ]
    }
    }
    ]
    }
    }

    1. Yes. If you look at the service class I wrote, it uses the DataAreaId (the company or legal entity name) passed in. And the code ‘changeCompany’ changes the context of what legal entity the code is running in.
      public rsmTutResponse create(rsmTutRequest _request)
      {
      List lines;
      ListIterator literator;
      rsmTutOrderLineClass rsmTutOrderLineClass;
      int i;

      var response = new rsmTutResponse();
      changecompany(_request.parmDataAreaId())
      {
      try
      {
      this.createHeader(_request.parmrsmTutOrderHeaderClass());

      //Create lines
      lines = _request.parmrsmTutOrderLineClassArray();
      if (lines)
      {
      literator = new ListIterator(lines);
      while (literator.more())
      {
      rsmTutOrderLineClass = literator.value();
      this.createLine(_request.parmrsmTutOrderHeaderClass(), rsmTutOrderLineClass);
      literator.next();
      }
      }

      response.parmDebugMessage(_request.parmrsmTutOrderHeaderClass().parmMessageId());
      response.parmSuccess(true);
      }
      catch (Exception::CLRError)
      {
      System.Exception interopException = CLRInterop::getLastException();

      response.parmSuccess(false);
      response.parmErrorMessage(interopException.ToString());
      }
      return response;
      }
      }

  5. Hi Peter,

    I am taking 100 records from postman in that 50 are related to one company and 20 are related to one company and 30 are related to one company

    How can i add in postman?

    1. I think I need more information to answer. If you have postman send in all 100 records into D365, and you have a column that stores the company, you can use the x++ code “changeCompany(‘‘) { }” and put code between the parenthesis that you want to run for that company. So if you put a loop around that statement, that loops through each record that should do what you want. The system will process that record in the context of the company.
      If you are trying to ‘read’ data and return it back out, you can do the same thing. You can run code to read data from a particular company and add it to your response string. Hope that helps.

  6. Hi Peter, thank you for the excellent article. A related topic that I would love to see a post on is how to document your custom APIs to share with developers who will build clients that make use of these services. From some searching, I haven’t seen a great option outside of manually creating an OpenAPI/swagger specification in json. I would love to see some good practices around this topic described.

  7. Hello Peter!

    Can you provide some insight on how to handle a 2-dimensional array. In my example, I am handling multiple POs in the same call, each with their own lines. In my service class, I am struggling to drill into the PurchaseLines array once I have selected the first PurchaseOrders value. I believe it has something to do with the paramaterization method? Any insight would be appreciated. Thanks

    Input:

    {
    “_request”: {
    “PurchaseOrders”: [
    {
    “PurchId”: “72500”,
    “ERPNumber”: “444204”,
    “InvoiceDate”: “2023-08-29”,
    “PurchaseLines”: [
    {
    “ItemNumber”: “B255”,
    “Quantity”: “1”,
    “UnitPrice”: “38.38”,
    “NetAmount”: “38.38”
    },
    {
    “ItemNumber”: “B21”,
    “Quantity”: “1”,
    “UnitPrice”: “188.10”,
    “NetAmount”: “188.10”
    }
    ]
    }
    ]
    }
    }

  8. I figured out how to get the data! I had to realize that the main difference between our inputs was that I only had 1 top level Array. Your Header and Lines were independent and you had two objects in your top level. I simply added a “PurchaseLines” list data member to the Header contract. This let me select my Lines array as a Json object, which I was able to deserialize using the Lines class we made in your example! Thanks!

  9. Hi Zach!

    Can you provide some insight on how to handle a 2-dimensional array. In my example, I am handling multiple SOs in the same call, each with their own lines. I am struggling to create the classes for multiple so’s.
    can you share the code of each class to handle multiple orders? Since Iam new to custom services any help could be appreciated.

    1. Hi,
      In the above example, you have a ‘request header’, that contains a single ‘order header’ class. And that class contains an ‘array’ of sales lines.
      If you want to be able to pass in multiple sales orders, you can add attributes in the ‘request’ class to make the ‘order header’ class be an array.

      So just copy and past these attributes from the ‘sales lines’, but replace rsmTutOrderLineClass with rsmTutOrderHeaderClass.
      [DataMemberAttribute(“rsmTutOrderLineClass”),
      DataCollection(Types::Class, classStr(rsmTutOrderLineClass)),
      AifCollectionTypeAttribute(‘_rsmTutOrderLineClassArray’, Types::Class, classStr(rsmTutOrderLineClass)),
      AifCollectionTypeAttribute(‘return’, Types::Class, classStr(rsmTutOrderLineClass))]
      public List parmrsmTutOrderLineClassArray(List _rsmTutOrderLineClassArray = rsmTutOrderLineClassArray)

  10. Hi Peter!

    Thanks for the response. I have tried like that only but getting issue in postman, for reference Iam sharing the code which I have written in the classes. plz help me to resolve this and send data without errors. or else it would be appreciated if you do the blog on this.

    [DataContractAttribute]
    internal final class EOEMSalesOrderRequest
    {
    private str dataAreaId;
    List eOEMSalesOrderLineClassArray;
    List eOEMSalesOrderHeaderClassArray;

    [DataMember(“DataAreaId”)]
    public str parmDataAreaId(str _value = dataAreaId)
    {
    if (!prmIsDefault(_value))
    {
    dataAreaId = _value;
    }

    return dataAreaId;
    }

    [DataMemberAttribute(“eOEMSalesOrderHeaderClass”),
    DataCollection(Types::Class, classStr(EOEMSalesOrderHeaderClass)),
    AifCollectionTypeAttribute(‘_eOEMSalesOrderHeaderClassArray’, Types::Class, classStr(EOEMSalesOrderHeaderClass)),
    AifCollectionTypeAttribute(‘return’, Types::Class, classStr(EOEMSalesOrderHeaderClass))]
    public List parmeOEMSalesOrderHeaderClass(List _eOEMSalesOrderHeaderClassArray = eOEMSalesOrderHeaderClassArray)
    {
    if (!prmIsDefault(_eOEMSalesOrderHeaderClassArray))
    {
    eOEMSalesOrderHeaderClassArray = _eOEMSalesOrderHeaderClassArray;
    }

    return eOEMSalesOrderHeaderClassArray;
    }

    [DataMemberAttribute(“eOEMSalesOrderLineClass”),
    DataCollection(Types::Class, classStr(EOEMSalesOrderLineClass)),
    AifCollectionTypeAttribute(‘_eOEMSalesOrderLineClassArray’, Types::Class, classStr(EOEMSalesOrderLineClass)),
    AifCollectionTypeAttribute(‘return’, Types::Class, classStr(EOEMSalesOrderLineClass))]
    public List parmeOEMSalesOrderLineClass(List _eOEMSalesOrderLineClassArray = eOEMSalesOrderLineClassArray)
    {
    if (!prmIsDefault(_eOEMSalesOrderLineClassArray))
    {
    eOEMSalesOrderLineClassArray = _eOEMSalesOrderLineClassArray;
    }

    return eOEMSalesOrderLineClassArray;
    }

    }

    class2:

    [DataContractAttribute]
    internal final class EOEMSalesOrderHeaderClass
    {
    str customerRef;
    str inventSiteId;
    str customerAccountNumber;
    str inventLocationId;
    str customerReq;
    str licencePlate;
    str vendorAccount;
    str EOEMItemApplicable;

    [DataMember(“customerRef”)]
    public str parmcustomerRef(str _value = customerRef)
    {
    if (!prmIsDefault(_value))
    {
    customerRef = _value;
    }

    return customerRef;
    }

    [DataMember(“CustomerAccountNumber”)]
    public str parmCustomerAccountNumber(str _value = customerAccountNumber)
    {
    if (!prmIsDefault(_value))
    {
    customerAccountNumber = _value;
    }

    return customerAccountNumber;
    }

    [DataMember(“customerReq”)]
    public str parmcustomerReq(str _value = customerReq)
    {
    if (!prmIsDefault(_value))
    {
    customerReq = _value;
    }

    return customerReq;
    }

    [DataMember(“licencePlate”)]
    public str parmlicencePlate(str _value = licencePlate)
    {
    if (!prmIsDefault(_value))
    {
    licencePlate = _value;
    }

    return licencePlate;
    }

    [DataMember(“inventLocationId”)]
    public str parminventLocationId(str _value = inventLocationId)
    {
    if (!prmIsDefault(_value))
    {
    inventLocationId = _value;
    }

    return inventLocationId;
    }

    [DataMember(“inventSiteId”)]
    public str parminventSiteId(str _value = inventSiteId)
    {
    if (!prmIsDefault(_value))
    {
    inventSiteId = _value;
    }

    return inventSiteId;
    }

    [DataMember(“vendorAccount”)]
    public str parmvendorAccount(str _value = vendorAccount)
    {
    if (!prmIsDefault(_value))
    {
    vendorAccount = _value;
    }

    return vendorAccount;
    }

    //EOEMItemApplicable
    [DataMember(“EOEMItemApplicable”)]
    public str parmeOEMItemApplicable(str _value = eOEMItemApplicable)
    {
    if (!prmIsDefault(_value))
    {
    eOEMItemApplicable = _value;
    }

    return eOEMItemApplicable;
    }

    }

    class3:
    [DataContractAttribute]
    internal final class EOEMSalesOrderLineClass
    {

    private real salesQuantity;
    private str itemNumber;
    private real unitPrice;
    private real lineAmount;
    private date requestedReceiptDate;
    private int lineNumberExternal;
    private str dlvMode;
    private real purchPrice;
    private real purchQuantity;

    [DataMember(“SalesQuantity”)]
    public real parmSalesQuantity(real _value = salesQuantity)
    {
    if (!prmIsDefault(_value))
    {
    salesQuantity = _value;
    }
    return salesQuantity;
    }

    [DataMember(“ItemNumber”)]
    public str parmItemNumber(str _value = itemNumber)
    {
    if (!prmIsDefault(_value))
    {
    itemNumber = _value;
    }
    return itemNumber;
    }

    [DataMember(“UnitPrice”)]
    public real parmUnitPrice(real _value = unitPrice)
    {
    if (!prmIsDefault(_value))
    {
    unitPrice = _value;
    }
    return unitPrice;
    }

    [DataMember(“LineNumberExternal”)]
    public int parmLineNumberExternal(int _value = lineNumberExternal)
    {
    if (!prmIsDefault(_value))
    {
    lineNumberExternal = _value;
    }
    return lineNumberExternal;
    }

    [DataMember(“PurchasePrice”)]
    public real parmPurchPrice(real _value = purchPrice)
    {
    if (!prmIsDefault(_value))
    {
    purchPrice = _value;
    }
    return purchPrice;
    }

    [DataMember(“PurchaseQuantity”)]
    public real parmpurchaseQuantity(real _value = purchQuantity)
    {
    if (!prmIsDefault(_value))
    {
    purchQuantity = _value;
    }
    return purchQuantity;
    }

    }

    service class:

    internal final class EOEMSalesOrderService
    {

    public EOEMSalesOrderResponse create(EOEMSalesOrderRequest _request)
    {
    SalesLine salesLine;
    str eOEMItemApplicable;
    List lines;
    List header;
    ListIterator literator;
    List purchaselines;
    EOEMSalesOrderLineClass eOEMSalesOrderLineClass;
    EOEMSalesOrderHeaderClass eOEMSalesOrderHeaderClass;
    ListEnumerator salesLineListEnumerator = _request.parmeOEMSalesOrderLineClass().getEnumerator();
    ListEnumerator salesTableListEnumerator = _request.parmeOEMSalesOrderHeaderClass().getEnumerator();

    var response = new EOEMSalesOrderResponse();
    changecompany(_request.parmDataAreaId())
    {
    try
    {
    header = _request.parmeOEMSalesOrderHeaderClass();
    if (header)
    {
    while(salesTableListEnumerator.moveNext())
    {
    eOEMSalesOrderHeaderClass = salesTableListEnumerator.current();
    if(eOEMSalesOrderHeaderClass)
    {
    //this.createHeader(eOEMSalesOrderHeaderClass);

    //header
    SalesFormLetter salesFormLetter;
    SalesTable salesTable;
    SalesTable lastSalesTable;
    SalesId salesId;
    NumberSeq numberSeq;
    str result;
    ttsbegin;
    numberSeq = NumberSeq::newGetNumFromId(SalesParameters::numRefSalesId().NumberSequenceId);
    salesId = numberSeq.num();
    salesTable.SalesId = salesId;
    salesTable.initValue();
    salesTable.CustAccount =eOEMSalesOrderHeaderClass.parmCustomerAccountNumber();
    salesTable.initFromCustTable();
    //salesTable.LanguageId = “en-us”;
    salesTable.PwCLineOfBusinessId = ‘eOEM’;
    salesTable.PurchOrderFormNum = eOEMSalesOrderHeaderClass.parmcustomerReq();
    salesTable.CustomerRef = eOEMSalesOrderHeaderClass.parmcustomerRef();
    salesTable.InventSiteId = eOEMSalesOrderHeaderClass.parminventSiteId();
    salesTable.InventLocationId = eOEMSalesOrderHeaderClass.parminventLocationId();
    salesTable.PwCLicensePlate = eOEMSalesOrderHeaderClass.parmlicencePlate();
    salesTable.ShippingDateRequested = 12\10\2023;

    if(salesTable.validateWrite())
    {
    salesTable.insert();
    }
    else
    {
    throw Exception::Error;
    }

    lines = _request.parmeOEMSalesOrderLineClass();
    if (lines)
    {

    while(salesLineListEnumerator.moveNext())
    {
    eOEMSalesOrderLineClass = salesLineListEnumerator.current();
    if(eOEMSalesOrderLineClass)
    {
    //this.createLines(eOEMSalesOrderHeaderClass, eOEMSalesOrderLineClass);
    salesLine.clear();
    salesLine.initValue();
    salesLine.initFromSalesTable(salestable);
    salesLine.ItemId = eOEMSalesOrderLineClass.parmItemNumber();
    salesLine.QtyOrdered = eOEMSalesOrderLineClass.parmSalesQuantity();
    salesLine.SalesQty = eOEMSalesOrderLineClass.parmSalesQuantity();
    salesLine.SalesPrice = eOEMSalesOrderLineClass.parmUnitPrice();
    salesLine.CustomerLineNum = eOEMSalesOrderLineClass.parmLineNumberExternal();
    if(salesLine.validateWrite())
    {
    salesLine.createLine(true, true, true, true, true, true);
    }
    else
    {
    throw Exception::Error;
    }
    }
    }
    }
    salesFormLetter= SalesFormLetter::construct(DocumentStatus::Confirmation);
    salesFormLetter.update(salesTable);

    response.parmDebugMessage(strfmt(” Sales order ‘%1’ is created successfully with status %2″, salesId,salesTable.SalesStatus));
    response.parmSuccess(true);
    }

    }

    }

    }
    catch (Exception::CLRError)
    {
    System.Exception interopException = CLRInterop::getLastException();

    response.parmSuccess(false);
    response.parmErrorMessage(interopException.ToString());
    }
    return response;
    }

    }
    }

    sending postman data like this:

    {
    “_request” :
    {
    “DataAreaId”: “BSFR”,
    “eOEMSalesOrderHeaderClass” :[
    {
    “customerRef”: “adefrcvg5”,
    “CustomerAccountNumber” :”CFR100000″,
    “customerReq”: “adefrcvg5”,
    “licencePlate”:”1234566″,
    “inventLocationId”: “POSFR00001”,
    “inventSiteId”:”VFR000002″,
    “EOEMItemApplicable”: “Both”,
    “eOEMSalesOrderLineClass” :
    [
    {
    “LineNumberExternal”: “1”,
    “ItemNumber”: “MK-20”,
    “SalesQuantity”: 1,
    “UnitPrice”: 20

    },
    {
    “LineNumberExternal”: “2”,
    “ItemNumber”: “MK-12”,
    “SalesQuantity”: 4,
    “UnitPrice”: 7

    },
    {
    “LineNumberExternal”: “3”,
    “ItemNumber”: “MK-20”,
    “SalesQuantity”: 3,
    “UnitPrice”: 80

    }

    ]
    }
    ]

    }
    }

    error in postman
    {
    “Message”: “An exception occured when invoking the operation – Object reference not set to an instance of an object.”,
    “ExceptionType”: “NullReferenceException”,
    “ActivityId”: “a1ecd5cd-4701-0005-25fe-16be7e2eda01”
    }

    1. Hi Lisa, I am facing the same error, I was wondering if you managed to get a solution to this and assist me. Thanks.

  11. Hi This article is pretty good.
    Just want to see if you have solution for this. If your JSON has 2 top level like this. How would you change your rsmTutRequest class for this
    {
    “event”: {
    “method”: “POST”,
    “path”: “/”
    },
    “Context”: {
    “Filed1”: “POST”,
    “Filed2”: “/”
    }
    }

    1. Basically you need to have your json inside of a single parent json. So to say it another way, the top level can’t be an array of objects. You would wrap the json you have shown with something like:
      request
      {
      Your json here.
      }

      Then have a class that maps to all of the inner properties, or classes to your json structure.
      My recommendation is to always start simple. And slowly add one piece at a time until you get the whole structure to map correctly.

  12. Hi Peter, nice post! but i reveice an error from postman: ‘an exception occured when deserializing the request – exeption occured when parsion and deserializing parameter ‘_request’ – ‘error while deserializing contract class’

    I followed all your steps and tried to debugg but I didn’t find the problem. Can you help me?

    Thanks!

    1. You need to make sure the properties in your class exactly match the name and format of the JSON you pass in. I recommended starting with a very basic example, and then add one property add a time, ensuring each time that it works. They will help you find which property doesn’t match, and you can look closer at that one to see if you see the problem.

Leave a Reply

Your email address will not be published. Required fields are marked *

Proudly powered by WordPress | Theme: Baskerville 2 by Anders Noren.

Up ↑