Search code examples
c#.netwcfdynamics-crm

Updating to .net 4.6.2 from .net 4.5.2 causing Object Reference Exception on ExecuteCore method within xrm sdk


We have a Win 8.1 store app that is developed against the .net 4.5.2 framework, when the computer is updated to .net 4.6.2 we receive an Object Reference is not set to the value of an Object exception.

The application consists of two parts the store app which holds all the UI logic and the Agent which holds all the data layer interaction with the Dynamics CRM Outlook Connector.

I have identified the line it is erroring on base.ExecuteCore(request) when running the first request which is a WhoAmI.

Interestingly the same code works if the agent is started in debug mode or used in a console app. The Agent is started by our UI application using the Application URL, and is confirmed to have started.

I have tried the following:

  • Updating the project to .net 4.6.2
  • Updating the related nuget packages to their latest versions, including the xrm sdk dll.
  • Decompiling the Xrm.SDK dll and inspecting every property used within the method to look for the location of the error, and found all the relevant properties to be set.
  • Bypassing the outlook connector and going direct to the CRM webservice

My thoughts are there .net 4.5.2 and .net 4.6.2 execute code in a different way which causes it to error.

Does anyone have any ideas on how to fix this?

For reference:

Version of CRM: 8.0.1.90

Stack Trace

at Microsoft.Xrm.Sdk.Client.OrganizationServiceContextInitializer.Initialize() at Microsoft.Xrm.Sdk.Client.OrganizationServiceProxy.ExecuteCore(OrganizationRequest request) at CrmProxy.CrmOrganizationProxy.ExecuteCore(OrganizationRequest request) in c:\PGW.Mobility\PGW.Mobility.CrmProxy\CrmContextProvider.cs:line 127

Class:

using System;
using System.Collections.Concurrent;
using System.Diagnostics.Contracts;
using System.Reflection;
using System.ServiceModel.Description;
using System.Threading;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using NLog;
using Mobility.Crm.Core;
using Mobility.CrmProxy.Interfaces;

namespace Mobility.CrmProxy
{
    public class CrmContextProvider : ICrmContextProvider
    {
        private readonly IEntityMapper _mapper;
        private readonly ICrmMetadataHolder _metadataHolder;
        private readonly OrganizationProxyPool _proxyPool;

        public CrmContextProvider(IEntityMapper mapper, ICrmMetadataHolder metadataHolder, OrganizationProxyPool proxyPool, ICrmConnectionConfig crmConnectionConfig)
        {
            _proxyPool = proxyPool;
            _mapper = mapper;
            _metadataHolder = metadataHolder;
        }

        public CrmProxyContext Context()
        {
            Contract.Ensures(Contract.Result<CrmProxyContext>() != null);
            var proxy = _proxyPool.Acquire();
            return new CrmProxyContext(_proxyPool, proxy, _mapper, _metadataHolder);
        }
    }

    public interface IOrganizationServiceReleaser
    {
        void Release(IOrganizationService proxy);
    }

    /// <summary>
    /// Pool of expensive organization proxies to improve overall performance
    /// </summary>
    public class OrganizationProxyPool : IOrganizationServiceReleaser
    {
        public static int OrgProxyInstanceCount = 0;

        private readonly Logger _log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType.FullName);
        private readonly ICrmConnectionConfig _crmConnectionConfig;

        private static readonly ConcurrentBag<IOrganizationService> OrgProxyPool = new ConcurrentBag<IOrganizationService>();

        public OrganizationProxyPool(ICrmConnectionConfig crmConnectionConfig)
        {
             _crmConnectionConfig = crmConnectionConfig;
        }

        public IOrganizationService Acquire()
        {
            IOrganizationService proxy;
            if (!OrgProxyPool.TryTake(out proxy))
            {
                var count = Interlocked.Increment(ref OrgProxyInstanceCount);
                _log.Info("Creating new OrganizationServiceProxy (total count: {0})", count);
                var crmProxy = new CrmOrganizationProxy(_crmConnectionConfig);
                // enable types declared in Crm.Core assembly
                //crmProxy.EnableProxyTypes(typeof(CrmContext).Assembly);   //Removed as per null reference exception with .net 4.6.2??
            crmProxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(new ProxyTypesBehavior(Assembly.GetExecutingAssembly()));
                proxy = crmProxy;
            }
            return proxy;
        }

        public void Release(IOrganizationService proxy)
        {
            // ignore failed proxies
            var crmProxy = proxy as CrmOrganizationProxy;
            if (crmProxy != null && !crmProxy.Failed)
                OrgProxyPool.Add(proxy);
        }
    }

    class CrmOrganizationProxy : OrganizationServiceProxy
    {
        private readonly ICrmConnectionConfig _crmConnectionConfig;

        public CrmOrganizationProxy(ICrmConnectionConfig crmConnectionConfig)
            : base(
                new Uri(crmConnectionConfig.CurrentCrmServerUri), null,
                new ClientCredentials
                {
                    Windows = { ClientCredential = crmConnectionConfig.CurrentCredentials }
                },
                null)
        {
            _crmConnectionConfig = crmConnectionConfig;
        }

        public bool Failed { get; private set; }

        protected override void AuthenticateCore()
        {
            if (_crmConnectionConfig.IsOffline)
            {
                // TODO: report bug to XRM team
                // note: this is reflection workaround over bug in Xrm when connecting offline Outlook CRM
                // without this Xrm throws weird "authentication failed!" exception which is wrong because
                // Cassini web server (where offline CRM server hosted) doesn't even support any authentication

                var isAuthPrivateSetter = typeof(ServiceProxy<IOrganizationService>).
                    GetProperty("IsAuthenticated").GetSetMethod(true);
                var proxyBase = (ServiceProxy<IOrganizationService>)this;
                isAuthPrivateSetter.Invoke(proxyBase, new object[] { true });
            }
            else
            {
                base.AuthenticateCore();
            }
        }

        protected override OrganizationResponse ExecuteCore(OrganizationRequest request)
        {
            // Telerik decompiler shows that context ivokes only Execute() method of proxy, 
            // so it is enough to check failures only in ExecuteCore()
            try
            {
                return base.ExecuteCore(request);
            }
            catch (Exception)
            {
                Interlocked.Decrement(ref OrganizationProxyPool.OrgProxyInstanceCount);
                Failed = true;
                throw;
            }
        }
    }
}

UPDATE 17/07/2017

I now have a more detailed error after changing code to below.

Error:

LastCrmError = "Unable to Login to Dynamics CRMOrganizationServiceProxy is null"

Code:

var crmProxy = new CrmServiceClient("Url=http://crm/AdventureWorks;");
crmProxy.GetMyCrmUserId();

After adding file based logging I now get a helpful error I think

The value of OperationContext.Current is not the OperationContext value installed by this OperationContextScope.

Stack Trace : at System.ServiceModel.OperationContextScope.PopContext() at Microsoft.Xrm.Sdk.Client.ServiceContextInitializer1.Dispose(Boolean disposing) at Microsoft.Xrm.Sdk.Client.ServiceContextInitializer1.Dispose() at Microsoft.Xrm.Sdk.Client.DiscoveryServiceProxy.Execute(DiscoveryRequest request) at Microsoft.Xrm.Tooling.Connector.CrmWebSvc.DiscoverOrganizations(Uri discoveryServiceUri, Uri homeRealmUri, ClientCredentials clientCredentials, ClientCredentials deviceCredentials)


Solution

  • This appears to be a bug with .net 4.6.2 specifically. Updating the framework to .net 4.7 resolved this issue.

    I'm 80% sure it is related to this bug: https://connect.microsoft.com/VisualStudio/Feedback/Details/3118586