To write a solution to read the Service Bus, follow these steps:
- First, create an enum named ConPIInboundActionType. This will form the type of action. Label this Inbound action.
- Add an element named ActivateBillOfMaterials.
- Create a string Extended Data Type (EDT) named ConPIInboundActionId and extend Num and with a label.
- Create a table named ConPIInboundActionTable.
- Add the following fields:
Name | EDT/Enum |
ActionId | ConPIInboundActionId |
Name | Name |
ActionType | ConPIInboundActionType |
ServiceBusQueueName | BusinessEventsServiceBusQueueEndpointQueueName |
ConnectionString | SysConnectionString |
- Add a field group for Overview (ActionId, Name, or ActionType).
- Add a field group for ConnectionDetails (ServiceBusQueueName or ConnectionString).
- Complete the table, as per best practices, as a Group table.
- 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.
- At the top of the class, add the following using statements:
using System.IO;
using System.Text;
using Microsoft.ServiceBus;
using Microsoft.ServiceBus.Messaging;
- 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.
- 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.
- Right-click on the solution in the Solution Explorer, and choose Add | New Project..
- Choose Class library and then Visual C#, and name the project ConServiceBus.
- Rename the class1.cs to ServiceBusFacade.cs, and click Yes to rename the references.
- Right-click on the References node, and choose Add reference.
- Click Browse, and search in the C:\AOSService\PackagesLocalDirectory\Bin folder.
This might be K: on Azure-based Development VMs.
- Look for the Microsoft.ServiceBus.dll file, select it, and press Add and then OK on the Reference manager dialog.
- Open the ServiceBusFacade class.
- 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>();
}
}
}
- Build the C# project.
- 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.
- Open the ConPIServiceBusReader class, and add the following to the top of the class:
using ConServiceBus;
- 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.
- The final part is to create a form for the table using the Simple list and Details - List Grid pattern.
- Follow the normal pattern, adding a tab page for the connection details.
- 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);
}
- 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();.
- Add a new tab page, using the Fill Text patter, and add a string control using ResultText() as the Data Method property.
- The form should look like the following screenshot:
- Add the menu item to an extension of OrganizationAdministration, under Setup.
- Close and save all documents, build the package, and synchronize the database.