Posted in Dynamics 365, Power Apps Portals

Power Apps Portals – Identify Portal User via Plug-in

If you work with D365/Dataverse for some time, you know how Plug-ins can really extend the platform and open a whole new world of possibilities (if you are new to Dataverse plug-ins, make sure to check out the official documentation: https://docs.microsoft.com/en-us/power-apps/developer/data-platform/write-plug-in).

Microsoft has released a while ago a new PluginExecutionContext class that contains additional details related to Power Apps Portals. This is very useful if you need to identify if the operation is coming from the Portals and to identify the User (Contact) that is performing the action.

This link contains more details of the class: https://docs.microsoft.com/en-us/dotnet/api/microsoft.xrm.sdk.ipluginexecutioncontext2?view=dataverse-sdk-latest

The usage of this is very simple, just initiate a new object for the IPluginExecutionContext2 class and you can get access to the IsPortalsClientCall and PortalsContactId properties:

using Microsoft.Xrm.Sdk;
using System;

namespace OR.Plugins
{
    public class PortalPlugin : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            IPluginExecutionContext2 context = (IPluginExecutionContext2)serviceProvider.GetService(typeof(IPluginExecutionContext2));

            bool isPortalsCall = context.IsPortalsClientCall;

            if (isPortalsCall)
            {
                Guid portalsContactId = context.PortalsContactId;
                if (portalsContactId == Guid.Empty)
                {
                    // anonymous
                }
                else
                {
                    // authenticated
                }
            }
            else
            {
                // not coming from portals
            }
        }
    }
}

In the below example, I am creating a task related to the current record (Account in my case) and setting details in the subject of the task as if this is coming from the Portals or not:

using Microsoft.Xrm.Sdk;
using System;

namespace OR.Plugins
{
    public class PortalPlugin : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            IPluginExecutionContext2 context = (IPluginExecutionContext2)serviceProvider.GetService(typeof(IPluginExecutionContext2));
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService orgService = serviceFactory.CreateOrganizationService(context.UserId);
            string subject = "";

            bool isPortalsCall = context.IsPortalsClientCall;

            if (isPortalsCall)
            {
                Guid portalsContactId = context.PortalsContactId;
                if (portalsContactId == Guid.Empty)
                {
                    subject = "Coming from Portals - Anonymous user";
                }
                else
                {
                    subject = $"Coming from Portals - Authenticated user: {portalsContactId}";
                }
            }
            else
            {
                subject = "Not coming from Portals";
            }

            Entity followup = new Entity("task");

            followup["subject"] = subject;
            followup["scheduledstart"] = DateTime.Now.AddDays(7);
            followup["scheduledend"] = DateTime.Now.AddDays(7);
            followup["regardingobjectid"] = new EntityReference(context.PrimaryEntityName, context.PrimaryEntityId);

            orgService.Create(followup);
        }
    }
}

Then when updating my Account record in three different ways (Model-Driven App / Portals authenticated / Portal unauthenticated), here is the result:

Hope this tip is useful for you and your Power Apps Portals/Dataverse implementation.

Posted in Dynamics 365

Dynamics 365: Web Service plug-in failed – Invalid name character in ‘List`1’ when using Shared Variables

Hi

Recently I had an issue with D365 plug-ins and it took me a lot of investigation and trial & error to figure out the problem.

I will start by explaining my scenario:

  • Dynamics 365 v8.2 on-premise
  • Plug-in registered on the Update message and Pre-Validation execution pipeline
  • Plug-in registered on the Update message and Pre-Operation execution pipeline
  • Also my Pre-Validation plug-in passes two Shared Variables to my Pre-Operation plug-in

If you are not familiar with Shared Variables in plug-ins, they are a great way to pass values through custom code without having to read D365 data again. It is also a great way to avoid infinite loops. Take a look at the documentation for more details: https://docs.microsoft.com/en-us/dynamics365/sales-enterprise/developer/custom-plugin-handling-shared-variable.

When triggering the plug-in via my application, it threw a very weird exception and for some reason I couldn’t debug my plug-in (a serialization error would come up and not log into the profiler). After a while troubleshooting, I found the below error in the Event Viewer in D365 Server:

The Web Service plug-in failed in OrganizationId: 466f6c23-6896-e811-a824-005056a99671; SdkMessageProcessingStepId: 4d51b0c7-d9c4-ea11-b81d-005056a75ee5; EntityName: contact; Stage: 30; MessageName: Update; AssemblyName: Microsoft.Crm.Extensibility.InternalOperationPlugin, Microsoft.Crm.ObjectModel, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35; ClassName: Microsoft.Crm.Extensibility.InternalOperationPlugin; Exception: Unhandled Exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
   at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Web.Services.Protocols.LogicalMethodInfo.Invoke(Object target, Object[] values)
   at Microsoft.Crm.Extensibility.InternalOperationPlugin.Execute(IServiceProvider serviceProvider)
   at Microsoft.Crm.Extensibility.V5PluginProxyStep.ExecuteInternal(PipelineExecutionContext context)
   at Microsoft.Crm.Extensibility.VersionedPluginProxyStepBase.Execute(PipelineExecutionContext context)
Inner Exception: System.ArgumentException: Invalid name character in 'List`1'. The '`' character, hexadecimal value 0x60, cannot be included in a name.
   at System.Xml.XmlWellFormedWriter.CheckNCName(String ncname)
   at System.Xml.XmlWellFormedWriter.WriteQualifiedName(String localName, String ns)
   at System.Runtime.Serialization.XmlWriterDelegator.WriteAttributeQualifiedName(String attrPrefix, XmlDictionaryString attrName, XmlDictionaryString attrNs, XmlDictionaryString name, XmlDictionaryString ns)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteTypeInfo(XmlWriterDelegator writer, XmlDictionaryString dataContractName, XmlDictionaryString dataContractNamespace)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteTypeInfo(XmlWriterDelegator writer, DataContract contract, DataContract declaredContract)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithXsiType(XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle objectTypeHandle, Type objectType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle, Type declaredType)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerializeReference(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
   at WriteKeyValuePairOfstringanyTypeToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , ClassDataContract )
   at System.Runtime.Serialization.ClassDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
   at WriteParameterCollectionToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , CollectionDataContract )
   at System.Runtime.Serialization.CollectionDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerializeReference(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
   at WriteAsyncOperationDataToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , ClassDataContract )
   at System.Runtime.Serialization.ClassDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerializeReference(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
   at WriteAsyncOperationDataToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , ClassDataContract )
   at System.Runtime.Serialization.ClassDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerializeReference(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
   at WriteAsyncOperationDataToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , ClassDataContract )
   at System.Runtime.Serialization.ClassDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerializeReference(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
   at WriteAsyncOperationDataToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , ClassDataContract )
   at System.Runtime.Serialization.ClassDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.DataContractSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
   at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
   at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
   at Microsoft.Crm.Extensibility.AsynchronousStep.SerializeAsyncData(PipelineExecutionContext context)
   at Microsoft.Crm.Extensibility.AsynchronousStep.Execute(PipelineExecutionContext context)
   at Microsoft.Crm.Extensibility.PipelineInstrumentationHelper.Execute(Boolean instrumentationEnabled, String stopwatchName, ExecuteWithInstrumentation action, PipelineExecutionContext context)
   at Microsoft.Crm.Extensibility.Pipeline.Execute(PipelineExecutionContext context)
   at Microsoft.Crm.Extensibility.PipelineInstrumentationHelper.Execute(Boolean instrumentationEnabled, String stopwatchName, ExecuteWithInstrumentation action, PipelineExecutionContext context)
   at Microsoft.Crm.Extensibility.MessageProcessor.Execute(PipelineExecutionContext context)
   at Microsoft.Crm.Extensibility.InternalMessageDispatcher.Execute(PipelineExecutionContext context)
   at Microsoft.Crm.Extensibility.ExtensiblePlatformMessageDispatcher.Execute(PipelineExecutionContext pluginContext)
   at Microsoft.Crm.Extensibility.ExtensiblePlatformMessageDispatcher.UpdateWithInvocationSource(BusinessEntity entity, FilterExpression filter, Int32 invocationSource, ExecutionContext context)
   at Microsoft.Crm.Extensibility.ExtensiblePlatformMessageDispatcher.Update(BusinessEntity entity, FilterExpression filter, ExecutionContext context)
   at Microsoft.Crm.BusinessEntities.BusinessProcessObject.UpdateWithPipelineAndExtensions(IBusinessEntity entity, ExecutionContext context)
   at Microsoft.Crm.BusinessEntities.BusinessProcessObject.Update(IBusinessEntity entity, ExecutionContext context)
.

The above error message doesn’t really help much as it happens in an internal plug-in.

Also this sentence from the error message didn’t make a lot of sense (at first): Invalid name character in ‘List`1’. The ‘`’ character, hexadecimal value 0x60, cannot be included in a name.

In my code I wasn’t referencing anything that could have a “`” or any other special character that could be messing things up. My code in the pre-validation plug-in was running correctly and I was able to debug it, I just couldn’t debug the pre-operation one.

After a while troubleshooting and nearly giving up, a colleague suggested that I play around with the Shared Variables I was passing between the plugins and bang!!! That was the issue…

I had two values passing via Shared Variables:

  • GUID – created in the Pre-Validation step
  • List<Entity> – records retrieved in the Pre-Validation step

When removing the List object from my Shared Variables, my plug-in started working with no issues and I could even debug my Pre-Operation plug-in.

Why does this happen?

The issue happens due to how the C# language names a generic list object. Running the simple code below, the exception starts making sense:

            List<string> myList = new List<string>();

            myList.Add("value 01");
            myList.Add("value 02");
            myList.Add("value 03");

            foreach (var item in myList)
            {
                Console.WriteLine(item);
            }

            Console.WriteLine(myList.ToString());

Internally, D365 is probably trying to convert the list object into a string (myList.ToString()) to be used in the next plug-in, and due to the special character created by C#, it throws an exception that Dynamics 365 can’t handle.

Conclusion

The conclusion for this is very simple, don’t pass a list of objects via Shared Variables in plug-ins. In my case, I decided to change the retrieve to the Pre-Operation plug-in. Another thing you can try is an array of GUIDs or other array types, just not a generic C# list object.