Replaceable Attributes in D365 Operations

Back to Blog Index
Share:

Replaceable Attributes in D365 Operations

We will take a scenario and help walk through the process of when and how to use Replaceable Attribute feature in Dynamics 365.

Standard business scenario

We were working for an extension modelling in Dynamics 365 for Operations in standard Bank module. The standard process is to download the “Bank Positive Pay” file information, which will be downloaded as text file.

Standard business scenario

Change Request

The approach was to avoid this manual process and automate the bank Positive Pay process. We bypassed the standard downloading process and instead transferred these positive pay files into Azure BLOB storage automatically. Then from Azure BLOB storage, an external web service will pick these files for automatic payment processing, usually an FTP service which works for most banks.

Change Request

The reasons include the business policies on data security and operational efficiency. The reasons could differ based on business cases, however the focus here is on the programming aspect.

Analysis

If you further examine the arrangement of the code to deliver the file, the standard code downloads the file. (Classes/BankPositivePayExport/sendFileToDestination)


/// <summary>
/// Send file to destination. 
/// </summary>

[Replaceable]
protectedvoid sendFileToDestination()
{
str downloadUrl = DMFDataPopulation::getAzureBlobReadUrl(str2Guid(fileId));

Filename filename = strFmt('%1-%2%3', bankPositivePayTable.PayFormat, 
bankPositivePayTable.PositivePayNum, 
this.getFileExtensionFromURL(downloadUrl));

System.IO.Stream stream = File::UseFileFromURL(downloadUrl);

File::SendFileToUser(stream, filename);
}

As per Chain of Command (or Event Handlers), we have option to only write custom code either before method execution or after method execution. But in this case since standard method was already downloading file. Challenge here was to see how we can block or bypass the standard code.

Since NEXT command is mandatory in calling Chain of Command method, when this NEXT command is called standard code will get executed and file will be downloaded to the local computer.


<Chain of command>
Pre code
	NEXT () <standard code of sending positive pay>Mandatory
Post code
<End of Chain of command>

Resolution

About Replaceable Attributes

With replaceable method, we need not call NEXT command in Chain of Command (CoC) and completely override the logic written by standard Microsoft code. But Microsoft suggests using this NEXT command conditionally.

As part of this blog, lets see how to mitigate the scenario of blocking the standard local file storage and get the file stored in Azure BLOB’s.

Create an extension class for “Classes/BankPostivePayExport”

usingMicrosoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;

[ExtensionOf(classStr(BankPositivePayExport))]
finalclass BankPositivePayExport_Extension
{
}

By replaceable attribute, write code to store in Azure BLOB

This method is actual replaceable method in “Classes/BankPostivePayExport”. Following code explains how we wrote custom logic to download the file and send it for Azure BLOB storage.


/// <summary>
/// Sends file to Blob storage
/// </summary>
protectedvoid sendFileToDestination()
{
#define.Underscore("_")
#define.DOT(".")
#define.XML("XML")
#define.FILENAME("ASCPositivePay")

if (this.parmBankPositivePayNum())
{
BankPositivePayTableascbankPositivePayTable;

ascbankPositivePayTable= BankPositivePayTable::find(this.parmPostitivePayNum());

ASCDownloadURL downloadURL;

ascDownloadURL = DMFDataPopulation::getAzureBlobReadUrl(str2Guid(fileId));

Filename filename = strFmt(’%1%2%3%4%5’, 
#FILENAME,
ascbankPositivePayTable.PositivePayNum,
#Underscore,
System.String::Format(’{0:MM.dd.yy}’, today()),
#DOT + #XML);

System.IO.Stream stream = File::UseFileFromURL(downloadUrl);
CloudBlobContainer blobContainer;

blobContainer = this.connectToBlob("<Name from Azure Key vault>",
"<Key from Azure Key Value>",
"<BLOB Folder name>");               

this.uploadFile(blobContainer, stream, filename);
}
}

Establish connect to Azure BLOB container

Following code establishes the connection with Azure BLOB. As a safety measure directly storage account name, account key and container name is not advisable, these values should be stored in Azure Key Vault and picked based on tokens (Note: Concept of storing and retrieving from key vault is outside scope of this blog, you can refer to Azure Key Vault article on how to store and retrieve storage keys)


/// <summary>
/// Establish connection to blob storage
/// </summary>
/// <param name = "_storageAccountName">Storage account name</param>
/// <param name = "_accountKey">Account key</param>
/// <param name = "_blobContainerName">Blob container name</param>
/// <returns>Blob container</returns>
public CloudBlobContainer connectToBlob(ASCStorageAccount _storageAccountName, 
ASCAccountKey     _accountKey, 
ASCBLOBContainer  _blobContainerName)
{
CloudBlobClient        blobClient;
CloudBlobContainer     blobContainer;
CloudStorageAccount    storageAccount;
ASCConnectionString    connectionString;

connectionString = strfmt(@ASC:ConnectionString,
_storageAccountName,
_accountKey);

storageAccount      = CloudStorageAccount::Parse(connectionString);
blobClient          = storageAccount.CreateCloudBlobClient();
blobContainer       = blobClient.GetContainerReference(_blobContainerName);

return blobContainer;
}

Labels
@ASC:ConnectionString= ”DefaultEndpointsProtocol=https;AccountName=%1;AccountKey=%2;EndpointSuffix=core.windows.net”

Custom method to upload file to Azure BLOB

This is a custom method, which explains how the BLOB container is uploaded to Azure BLOB. This method takes file stream, blob container name and blob file name as input to create files in Azure BLOB.


/// <summary>
/// Uploads the file to the blob
/// </summary>
/// <param name = "_blobContainer">Blob container</param>
/// <param name = "_stream">File stream</param>
/// <param name = "_fileName">File name</param>
publicvoid uploadFile(CloudBlobContainer_blobContainer, 
System.IO.Stream_stream, 
FileName_fileName)
{
_blobContainer.CreateIfNotExistsAsync();
try
{
ttsbegin;
CloudBlockBlob blockblob =blobContainer.GetBlockBlobReference(_fileName);

if(blockblob && !blockblob.Exists(null, null))
{
if(_stream)
{
blockblob.UploadFromStreamAsync(_stream).Wait();
blockBlob.FetchAttributes(null,null,null);
BlobProperties BlobProperties = blockblob.Properties;

if(BlobProperties.Length == _stream.Length)
{
info(strFmt("@ASC:FileUploadedSuccessfully", _fileName));
}
}
}
else
{
info(strFmt("@ASC:FileAlreadyExists", _fileName));
}
ttscommit;
}
catch
{
info("@ASC:ErrorUploadingFile");
}
}

Labels
@ASC:ErrorUploadingFile =“Error while uploading file”
@ASC:FileAlreadyExists =“File already exists”
@ASC:FileUploadedSuccessfully=“File uploaded successfully”

Conclusion

We find this Replaceable Attribute cool and very beneficial in blocking the standard code completely and writing our own custom code. To know more about Do’s and Don’t’s of using replaceable methods, refer to the Microsoft documentation:

https://docs.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/extensibility/extensibility-attributes#replaceable

This blog focuses on the ability to customize a standard behavior. However, we recommend to thoroughly evaluate the need to customize.

Krish Moorthy

Author: Krish Moorthy

Technical Specialist - Dynamics

Krish has strong experience in designing and developing complex customizations, software upgrade and data migration on Microsoft Dynamics 365 / AX system. He has a passion for training and mentoring upcoming software developers.