connect 2022 banner tablet connect 2022 banner mobile

Learn to Use a Label Creator Add-in Extension in Dynamics 365 for Finance & Operations

By Mark Nelson | April 2, 2018

Creating labels has always been considered a chore by developers, and the task hasn’t really gotten any easier with Dynamics 365. Wouldn’t it be nice if you could type your value into the property and let Visual Studio move it to a label file? Luckily, we can. I'll show you how to use the label creator Add-in extension in Dynamics 365 for Finance & Operations.

Start by creating a new Dynamics 365 Developer Tools Add-in project.

Create new Dynamics 365 Developer Tools Addin project

This will create a new class library project that contains two source files. The first, DesignerContextMenuAddIn.cs, contains boilerplate code for creating a designer add-in. The second, MainMenuAddIn.cs, contains a menu add-in.

Label Creator Addin Extension

The first thing we will need to do is to add a few references to our project:

  • C:\Program Files (x86)\Common Files\Microsoft Shared\MSEnv\PublicAssemblies\
    • EnvDTE.dll
    • EnvDTE80.dll
  • C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\Extensions\5hutepyf.xp2\
    (Your folder may vary slightly)

    • Microsoft.Dynamics.Framework.Tools.LabelEditor.dll
    • Microsoft.Dynamics.Framework.Tools.ProjectSystem.dll
    • Microsoft.Dynamics.Framework.Tools.ProjectSupport.dll
  • C:\AOSService\PackagesLocalDirectory\bin\
    • Microsoft.Dynamics.AX.Metadata.dll
    • Microsoft.Dynamics.Framework.Tools.AutomationObjects.dll

Our designer’s context menu is controlled by the DesignerMenuExportMetadata attribute on the class. You can see in the screenshot that by default our add-in will appear in both the Form and Table designers.

The OnClick method is called when the user clicks on our menu. We will modify this method to convert the text contained in the Label property to an actual label. To do this, create regular expression to distinguish between a label identifier and a normal string.

private static Regex newLabelMatcher = 
    new Regex("\\A(?<AtSign>\\@)(?<LabelFileId>[a-zA-Z]\\w*):(?<LabelId>[a-zA-Z]\\w*)", 
    RegexOptions.Compiled | RegexOptions.CultureInvariant);
 
private static Regex legacyLabelMatcher = 
    new Regex("\\A(?<LabelId>(?<AtSign>\\@)(?<LabelFileId>[a-zA-Z][a-zA-Z][a-zA-Z])\\d+)", 
    RegexOptions.Compiled | RegexOptions.CultureInvariant);

These expressions will be used by the following methods.
private Boolean IsValidLabelId(String labelId)
{
    bool result = false;
 
    if (String.IsNullOrEmpty(labelId) == false)
    {
        result = DesignerContextMenuAddIn.IsLegacyLabelId(labelId);
        if (result == false)
        {
            result = (DesignerContextMenuAddIn.newLabelMatcher.Matches(labelId).Count > 0);
        }
    }
 
    return result;
}
 
private static Boolean IsLegacyLabelId(String labelId)
{
    bool result = false;
 
    if (String.IsNullOrEmpty(labelId) == false)
    {
        result = (DesignerContextMenuAddIn.legacyLabelMatcher.Matches(labelId).Count > 0);
    }
 
    return result;
}

Handle the click event in the OnClick method.  Here we will test the selected object, get the object’s model and label file, and create the label.
public override void OnClick(AddinDesignerEventArgs e)
{
    ModelInfoCollection modelInfoCollection = null;
 
    IMetaModelService metaModelService = null;
    // Get the metamodel provider
    IMetaModelProviders metaModelProvider = ServiceLocator.GetService(typeof(IMetaModelProviders)) as IMetaModelProviders;
 
    if (metaModelProvider != null)
    {
        // Get the metamodel service
        metaModelService = metaModelProvider.CurrentMetaModelService;
    }
 
    try
    {
        // Is the selected element a table?
        if (e.SelectedElement is ITable)
        {
            ITable table = e.SelectedElement as ITable;
 
            modelInfoCollection = metaModelService.GetTableModelInfo(table.Name);
            AxLabelFile labelFile = this.GetLabelFile(metaModelProvider, metaModelService, modelInfoCollection);
 
            this.createLabel(table, labelFile);
        }
    }
    catch (Exception ex)
    {
        CoreUtility.HandleExceptionWithErrorMessage(ex);
    }
}

private AxLabelFile GetLabelFile(IMetaModelProviders metaModelProviders, IMetaModelService metaModelService, ModelInfoCollection modelInfoCollection)
{
    // Choose the first model in the collection
    ModelInfo modelInfo = ((System.Collections.ObjectModel.Collection<ModelInfo>)modelInfoCollection)[0];
 
    // Construct a ModelLoadInfo
    ModelLoadInfo modelLoadInfo = new ModelLoadInfo
    {
        Id = modelInfo.Id,
        Layer = modelInfo.Layer,
    };
 
    // Get the list of label files from that model
    IList<String> labelFileNames = metaModelProviders.CurrentMetadataProvider.LabelFiles.ListObjectsForModel(modelInfo.Name);
 
    // Choose the first
    // What happens if there is no label file?
    AxLabelFile labelFile = metaModelService.GetLabelFile(labelFileNames[0], modelLoadInfo);
 
    return labelFile;
}
 
private void createLabel(ITable table, AxLabelFile labelFile)
{
    if (this.IsValidLabelId(table.Label) == false)
    {
        table.Label = this.FindOrCreateLabel(table.Label, table.Name, "Label", labelFile);
    }
 
    if (this.IsValidLabelId(table.DeveloperDocumentation) == false)
    {
        table.DeveloperDocumentation = this.FindOrCreateLabel(table.DeveloperDocumentation, table.Name, "DeveloperDocumentation", labelFile);
    }
}
 
private String FindOrCreateLabel(String labelText, String elementName, String propertyName, AxLabelFile labelFile)
{
    string newLabelId = String.Empty;
 
    // Don't bother if the string is empty
    if (String.IsNullOrEmpty(labelText) == false)
    {
        // Construct a label id that will be unique
        string labelId = $"{elementName}{propertyName}";
 
        // Get the label factor
        LabelControllerFactory factory = new LabelControllerFactory();
 
        // Get the label edit controller
        LabelEditorController labelController = factory.GetOrCreateLabelController(labelFile, DesignerContextMenuAddIn.Context);
 
        // Make sure the label doesn't exist.
        // What will you do if it does?
        if (labelController.Exists(labelId) == false)
        {
            labelController.Insert(labelId, labelText, String.Empty);
            labelController.Save();
 
            // Construct a label reference to go into the label property
            newLabelId = $"@{labelFile.LabelFileId}:{labelId}";
        }
    }
 
    return newLabelId;
}

Lastly, open the resource file and change the DesignerAddinCaption. This value will appear in the context menu item.

DesignerAddinCaption

To test your add-in, press F5 and Visual Studio will build your project and copy the dll to the appropriate folder. On my computer, the add-in folder is at C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\Extensions\5hutepyf.xp2\AddinExtensions\.

Create a Dynamics 365 Unified Operations project.

Unified Operations project

Add a new table to the project.

Add a new table to the project

Add a label file.

Add a label file

Open your table in the designer and type a value into the Label property.

Type a value into the Label property

Right-click the table node in the designer and move your mouse to the Add-ins menu.

Addins menu

Click the Create labels option and your add-in will create a label resource and update the Label property.

Label property

Label property

Start the Conversation

It’s our mission to help clients win. We’d love to talk to you about the right business solutions to help you achieve your goals.

Subscribe To Our Blog

Sign up to get periodic updates on the latest posts.

Thank you for subscribing!

X