dynamics-crmmicrosoft-dynamicsdynamics-365adxstudio-portals

Multiselect option set in portal, or viable alternatives


I have an application entity form that has 3 multi select option sets that determine what questions will appear for the rest of the form based on the chose values. I need this to appear on my portal, or a viable alternative.

the multiselect option set does not render in the portal


Solution

  • Dynamics V9.0 introduced Multiselect optionsets, but this new field type is not support for rendering in the portal (..yet).

    There is a well documented workaround (not using the OOB multiselect optionset) approach to creating something that renders as a multiselect optionset in the portal: https://nishantrana.me/2017/03/06/configuring-multiple-choice-field-for-web-form-in-portal-dynamics-365/

    TL:DR is to create a a boolean (two option) field for each option on the entity and use the form meta data to create "Multi Select" fields that will render on the portal.

    In your example you would need to create 3 groups of fields.

    Some considerations for this approach will be:

    1. Number of Options in each optionset
    2. Volatility of the options (how often do they change)
    3. Are the option sets related/filtered

    There are some possible options what would require plugins and custom liquid development, but hopefully this approach will satisfy your requirement.

    EDIT: I have limited experience with the Multiselect optionset and with Liquid, but I attempted to create a Web Template that returns JSON with contacts, where the cf_interests field is a multiselect field.

    The following is not a complete answer

    {% fetchxml feed %}
    <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" count="10" returntotalrecordcount="true" {% if request.params['page'] %} page="{{request.params['page']}}" {% else %} page="1" {% endif %}>
      <entity name="contact" >
        <attribute name="fullname" />
        <attribute name="cf_interests" />
        <filter>
          <condition attribute="statecode" operator="eq" value="0" />
          {% if request.params['id'] %}
          <condition attribute="contactid" operator="eq" value="{{ request.params['id' | xml_escape}}" />
          {% endif %}
        </filter>
      </entity>
    </fetch>
    {% endfetchxml %}{
        {% for contact in feed.results.entities %}
          {
            "fullname": "{{ contact.fullname}}",
            "interest": "{{ contact.cf_interests | join: ", " }}"
            "interests: "{% for interest in contact.cf_interests%}
                        {
                            "interest-{{forloop.index}}": "{{contact.cf_interest[{{forloop.index}}]}}"
                        }{% unless forloop.last %},{% endunless %}
                        {% endfor -%}
          }{% unless forloop.last %},{% endunless %}
      {% endfor -%}
    }
    

    Ideally this would have pulled all contact (or contact by ID if passed as a param) with their name and all the interest they have. This returns results in the XRM Toolbox but nothing on the portal:

    Fetch Used:

    Fetch used

    Fetch Results:

    Fetch Results

    Output of JSON Web Template:

    Output of JSON Web Template

    Regardless, you'd need to use either the web api to update the Multiselect once the user has interacted with the custom multiselect control you would create. I was hoping to use the JSON web template to drive that control.

    There is also a new class you can use in a plugin or thorugh the webapi to retrieve and set multiselect optionsets

    https://learn.microsoft.com/en-us/dynamics365/customer-engagement/developer/multi-select-picklist

    There is a simple sample plugin I created registered on create and update of contact that writes the selected optionset values to a new string:

    using System;
    using System.ServiceModel;
    using System.Text;
    using Microsoft.Xrm.Sdk;
    using Microsoft.Xrm.Sdk.Query;
    
    namespace PortalMultiSelect
    {
        public class ContactPostCreateUpdate : IPlugin
        {
            public void Execute(IServiceProvider serviceProvider)
            {
                // Obtain the tracing service
                ITracingService tracingService =
                (ITracingService)serviceProvider.GetService(typeof(ITracingService));
    
                // Obtain the execution context from the service provider.  
                IPluginExecutionContext context = (IPluginExecutionContext)
                    serviceProvider.GetService(typeof(IPluginExecutionContext));
    
                string className = "ContactPostCreateUpdate";
    
                // The InputParameters collection contains all the data passed in the message request.  
                if (context.InputParameters.Contains("Target") &&
                    context.InputParameters["Target"] is Entity)
                {
                    // Obtain the target entity from the input parameters.  
                    Entity entity = (Entity)context.InputParameters["Target"];
    
                    // Obtain the organization service reference which you will need for  
                    // web service calls.  
                    IOrganizationServiceFactory serviceFactory =
                        (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
                    IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
    
                    tracingService.Trace("Message " + context.MessageName);
                    if (context.MessageName != "Create" && context.MessageName != "Update")
                        return;
                    if (context.Depth > 1)
                        return;
                    tracingService.Trace(string.Format("Primary EntityNname: {0} and Depth is: {1} ", context.PrimaryEntityName, context.Depth));
                    if (context.PrimaryEntityName != "contact")
                        return; //Should only be registerd on Contact Entity
    
                    try
                    {
                        Guid contactid = context.PrimaryEntityId;
                        Entity contact = service.Retrieve("contact", contactid, new ColumnSet(true));
    
                        tracingService.Trace("{0}: Setting up interest collection", className);
    
                        // It will returns the Collection of Values in the OptionSet.
                        OptionSetValueCollection interests = (OptionSetValueCollection)contact.Attributes["cf_interests"];
    
                        string interestValues = string.Empty;
    
                        StringBuilder sb = new StringBuilder();
    
                        tracingService.Trace("{0}: Joining interest values to string", className);
    
    
                        foreach (OptionSetValue interest in interests)
                        {
                            sb.Append(interest.Value).Append(";");
                        }
    
                        interestValues = sb.ToString(0, sb.Length - 1);
    
                        contact["cf_intereststring"] = interestValues;
    
                        service.Update(contact);
    
                    }
    
                    catch (FaultException<OrganizationServiceFault> ex)
                    {
                        throw new InvalidPluginExecutionException(string.Format("An error occurred in {0}. {1}", className, ex));
                    }
    
                    catch (Exception ex)
                    {
                        tracingService.Trace("{0}: {1}",className ,ex.ToString());
                        throw;
                    }
                }
            }
        }
    }
    

    New TL:DR Multiselect optionsets are new and there are lots of limitations.