How to do it...

To write a solution to read the Service Bus, follow these steps:

  1. First, create an enum named ConPIInboundActionType. This will form the type of action. Label this Inbound action.
  2. Add an element named ActivateBillOfMaterials.
  3. Create a string Extended Data Type (EDT) named ConPIInboundActionId and extend Num and with a label.
  4. Create a table named ConPIInboundActionTable.
  5. Add the following fields:
Name EDT/Enum
ActionId ConPIInboundActionId
Name Name
ActionType ConPIInboundActionType
ServiceBusQueueName BusinessEventsServiceBusQueueEndpointQueueName
ConnectionString SysConnectionString
  1. Add a field group for Overview (ActionId, Name, or ActionType).
  2. Add a field group for ConnectionDetails (ServiceBusQueueName or ConnectionString).
  3. Complete the table, as per best practices, as a Group table.
  4. Before writing the form, we will write the code to read the Service Bus queue so we can use it as part of validation and testing. Create a class named ConPIServiceBusReader.
  5. At the top of the class, add the following using statements:
using System.IO;
using System.Text;
using Microsoft.ServiceBus;
using Microsoft.ServiceBus.Messaging;
  1. Set up the class variables, and write the constructor code, shown as follows:
private str queueName;
private str connectionString;

protected void new(str _queueName, str _connectionString)
{
queueName = _queueName;
connectionString = _connectionString;
}
public static ConPIServiceBusReader Construct(ConPIInboundActionTable _action)
{
return new ConPIServiceBusReader(_action.ServiceBusQueueName,
_action.ConnectionString);
}
We aren't using the key vault in this case, for simplicity. A more secure method would be to use the build in Key Vault mechanism, which is configured in System Administration | Setup | Key Vault parameters.
  1. Next, we will write a validate method; this simply tries to construct a queue client on the Service Bus to test if a connection can be made. It can be done using the following code:
public boolean Validate()
{
System.Exception exc;
try
{
var receiverFactory = MessagingFactory::
CreateFromConnectionString(connectionString);
var receiver =
receiverFactory.CreateQueueClient(queueName);
}
catch (exc)
{
return checkFailed(exc.Message);
}
return true;
}

The final method is to write the code to read the Service Bus queue, but we have a problem – we need to call <T>BrokeredMessage.GetBody(). This is a generic type, which is not supported in X++. To solve this without resorting to reflection, we need to create a C# library project.

  1. Right-click on the solution in the Solution Explorer, and choose Add | New Project..
  2. Choose Class library and then Visual C#, and name the project ConServiceBus.
  1. Rename the class1.cs to ServiceBusFacade.cs, and click Yes to rename the references.
  2. Right-click on the References node, and choose Add reference.
  3. Click Browse, and search in the C:\AOSService\PackagesLocalDirectory\Bin folder.
This might be K: on Azure-based Development VMs.
  1. Look for the Microsoft.ServiceBus.dll file, select it, and press Add and then OK on the Reference manager dialog.
  2. Open the ServiceBusFacade class.
  3. Alter the class so that it reads as follows:
using Microsoft.ServiceBus.Messaging;
using System.IO;
namespace ConServiceBus
{
public class ServiceBusFacade
{
public static Stream GetBodyStream(BrokeredMessage
_message)
{
return _message.GetBody<Stream>();
}
}
}
  1. Build the C# project.
  2. Back in the ConProductionIntegration X++ project, add a reference to the ConServicebus C# project by right-clicking on the References node, and choose the ConServiceBus project from the Projects tab page.
  3. Open the ConPIServiceBusReader class, and add the following to the top of the class:
using ConServiceBus;
  1. Write the read method shown as follows:
public str Read(boolean _peek = false)
{
if (!this.Validate())
{
return '';
}
str messageBody = '';
var receiverFactory = MessagingFactory::
CreateFromConnectionString(connectionString);
Microsoft.ServiceBus.Messaging.ReceiveMode receiveMode;
receiveMode = Microsoft.ServiceBus.Messaging.ReceiveMode::
ReceiveAndDelete;
if (_peek)
{
receiveMode = Microsoft.ServiceBus.Messaging.ReceiveMode::
PeekLock;
}
var queueClient = receiverFactory.CreateQueueClient(queueName,
receiveMode);
using (var message = queueClient.Receive())
{
if (message != null)
{
System.Exception exc;
try
{
// The correct way, when X++ supports generic types
// System.IO.Stream inputStream =
// message.GetBody<Stream>();

System.IO.Stream inputStream
= ConServiceBus.ServiceBusFacade::
GetBodyStream(message);
using (System.IO.StreamReader reader
= new System.IO.StreamReader(inputStream))
{
messageBody = reader.ReadToEnd();
}
if (_peek)
{
message.Abandon();
}
else
{
message.Complete();
}
}
catch(exc)
{
message.Abandon();
Error(exc.Message);
}
}
queueClient.Close();
}
return messageBody;
}
The code in bold uses reflection to call <T>GetBody(); this is because X++ does not support generic types. The safe way to do this is to use a C# library, and this is covered in the There's more... section. Outside of testing and prototyping scenarios, reflection should be avoided.
  1. The final part is to create a form for the table using the Simple list and Details - List Grid pattern.
  2. Follow the normal pattern, adding a tab page for the connection details.
  3. Write a display method and a form method to test the current record and display the results. Write the following code:
[FormObservable]
Notes resultText;
public display Notes ResultText()
{
return resultText;
}
public void DoTest()
{
ConPIServiceBusReader reader
= ConPIServiceBusReader::
Construct(ConPIInboundActionTable);
resultText = reader.Read(true);
}
  1. Create a Button Group and Button called TestButton on the action pane at the top, and override the clicked event method to call element.DoTest();.
  2. Add a new tab page, using the Fill Text patter, and add a string control using ResultText() as the Data Method property.
  1. The form should look like the following screenshot:

  1. Add the menu item to an extension of OrganizationAdministration, under Setup.
  2. Close and save all documents, build the package, and synchronize the database.