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.
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.
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.
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:
This blog focuses on the ability to customize a standard behavior. However, we recommend to thoroughly evaluate the need to customize.