The CRM Grid – A Microsoft CRM Blog

Greg Owens’ rose-tinted MS-CRM mumblings

Disabled fields incorrectly saving to the CRM database

Earlier this week, I encountered what seems to be a bug in MS CRM 4.0 which I had not noticed before. This occurred with Update Rollup 13 and Update Rollup 14 (and possibly others…?) and has been replicated on at least one other unrelated environment. (Edit: but not replicable on all environments…)

The problem manifests itself with fields marked on the CRM Form Designer as “readonly” being incorrectly saved back to the database, during the create stage. Strangely on affected machines I was unable to replicate this problem when updating records (which behaved as expected).

According to the CRM 4.0 SDK, readonly/disabled fields are not submitted to the server during the save process. After all, that’s what the ForceSubmit method is for (my emphasis):

All enabled fields will be submitted after they have been modified either through script or by user input.

Contrary to this, on creation of a record, data in read-only fields is currently being submitted and saved to the database. I haven’t identified why this occurs on some deployments and not on others. Details of the main environment that I have encountered this on are listed at the end of this posting. To test your environment, try following these steps:

  • Create a new entity, “new_test”
  • Create a new string attribute, “”new_editablestring”
  • Create a new string attribute, “”new_readonlystring”
  • Place both fields onto the form
  • Mark “new_readonlystring” as Read-Only
  • Enable the “onchange” event for “new_editablestring”
  • Add the following script to the onchange event handler
    crmForm.all.new_readonlystring.DataValue = crmForm.all.new_editablestring.DataValue;
  • Save & publish the form
  • Create a new_test record
  • Enter a value in the Editable String field, e.g. “Creation String”
  • Note that the ReadOnly string value is correctly changed on screen
  • Hit save & close
  • Open the record again, note that the “new_readonlystring” now contains a value “Creation String”
  • Enter a new value in the Editable String field, e.g. “Edited String”
  • Note that the ReadOnly string value is correctly changed on screen to “Edited String”
  • Hit save & close
  • Open the record again, note that the “new_readonlystring” still contains a value “Creation String” as it was correctly NOT updated

After some testing, it became clear that no permutation of programatically setting the field to Disabled, disabled, readOnly, ReadOnly or readonly made any difference. The only answer, it seems, is to prevent read-only attributes from hitting the database via a plugin. This is simple enough – the Create message accepts a Target input parameter which is a DynamicEntity. We can simply remove the unwanted properties from the DynamicEntity before the Target gets commited. This is difficult to manage though – every time you make a field read only on your form, you would need to recompile your code, or update your plug-in configuration strings (depending on how you chose to handle this). There is however a better solution – retrieve the form definition from the CRM platform, identify which fields should be read-only and omit them dynamically from the input target. And here is how this is done:

using System;
using System.Xml;
using Vitalogy.XRM.Common;
using Vitalogy.XRM.Plugins.BaseClass;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;

namespace Vitalogy.XRM.Plugins
{
public class AllEntitiesCreate : VitalogyBasePluginClass, IPlugin
{
///
/// Identifies read-only fields from the form and ensures that they
/// are not saved to the database (this shouldn't happen anyway
/// but there appears to be a bug in the CRM platform)
///
/// <param name="context" />
public void Execute(IPluginExecutionContext context)
{
if (!PluginShouldRun(context))
{
DebugLog.LogMessage(string.Format("Plug-In \"{0}\" is not designed to run in this scenario", GetType().Name));
return;
}

DebugLog.LogStart();
try
{
var entity = (DynamicEntity)context.InputParameters.Properties[ParameterName.Target];

// Filter out read-only attributes from the create message
FilterReadOnlyAttributes(context, entity);

}
catch (System.Web.Services.Protocols.SoapException ex)
{
DebugLog.LogError(string.Format("A SOAP error occurred in the {0} plug-in.\n\n{1}", GetType(), ex.Detail), ex);
throw new InvalidPluginExecutionException(
String.Format("A SOAP error occurred in the {0} plug-in.", GetType()), ex);
}
catch (Exception ex)
{
DebugLog.LogError(string.Format("A general exception occurred in the {0} plug-in.", GetType()), ex);
throw new InvalidPluginExecutionException(
String.Format("A general exception occurred in the {0} plug-in.", GetType()), ex);
}
finally
{
DebugLog.LogFinish();
}
}

private static void FilterReadOnlyAttributes(IPluginExecutionContext context, DynamicEntity entity)
{
using (var crmService = context.CreateCrmService(true))
{
DebugLog.LogMessage("Plug-in to strip out read-only fields during record creation");
// get formxml
DebugLog.LogMessage(string.Format("Getting formXml for entity {0}", entity.Name));
// RetrieveFormXmlRequest is unsupported since it is for "internal use
// only" but the alternative involves getting the entity type code
// from metadataservice then issuing a request for OrganisationUI
// for the given entity. This too is deprecated due to not being
// supported in CRM 2011 - but what are we to do, eh?
var formXmlRequest = new RetrieveFormXmlRequest {EntityName = entity.Name};
var formXmlResponse = (RetrieveFormXmlResponse) crmService.Execute(formXmlRequest);
var formXml = formXmlResponse.FormXml;
DebugLog.LogMessage(string.Format("Retrieved formXml: {0}", formXml));

// xpath to identify read-only fields
var formXmlDocument = new XmlDocument();
formXmlDocument.LoadXml(formXml);
var disabledAttributes = formXmlDocument.SelectNodes("//control[@disabled='true']/@id");

// pluck them out of the create message
if (disabledAttributes == null)
{
DebugLog.LogMessage("No read-only attributes found");
return;
}

DebugLog.LogMessage(string.Format("Retrieved {0} read-only attributes", disabledAttributes.Count));
foreach (XmlNode disabledAttribute in disabledAttributes)
{
DebugLog.LogMessage(string.Format("{0} is read-only", disabledAttribute.Value));
if (entity.Properties.Contains(disabledAttribute.Value))
{
DebugLog.LogMessage("and has been removed from the Target input parameter");
entity.Properties.Remove(disabledAttribute.Value);
}
}
context.InputParameters[ParameterName.Target] = entity;
}
}

private static bool PluginShouldRun(IPluginExecutionContext context)
{
// only run if in the pre-Create stage
if (context.Stage == MessageProcessingStage.BeforeMainOperationOutsideTransaction)
{
if (context.InputParameters.Properties.Contains(ParameterName.Target) &&
(context.InputParameters.Properties[ParameterName.Target] is DynamicEntity))
{
return context.MessageName == MessageName.Create;
}
return false;
}
return false;
}

}
}

A few things to note:

  • Vitalogy.XRM.Common and Vitalogy.XRM.Plugins.BaseClass are simply used to implement the log4net logging framework (and hence define DebugLog).
  • This implementation is very simplistic so isn’t exactly efficient – the next step would be to cache the list of read-only fields so that it is not being read at every Create
  • This code can only deal with fields defined as “ReadOnly” in the form definition. If you have client-side onLoad script which disables fields onLoad this code will not identify those fields (though if you’ve coded consistently, you might be able to examine your client-side code within the formXml to find these cases…).

My environment

I encountered the aforementioned issue in the following conditions:

  • Combined Application and Database server
  • Microsoft Dynamics CRM 4.0 – Update Rollup 14
  • Microsoft Windows Server 2008 R2 Standard
  • Microsoft SQL Server 2008 SP1 (64 Bit)
  • Various clients, including:
    • Internet Explorer 7 (7.0.5730.13) from client
    • Internet Explorer 8 (8.0.7600.16385) on server
  • When signed in as a CRM Administrator or non-administrative user
Advertisements

7 Comments»

  Andriy Butenko wrote @

Have you tried to mark changed disabled fields using code:
crmForm.all..ForceSubmit = true;

I work with CRM more then 2 years and I have no issues with it…

  Greg Owens wrote @

Andriy – perhaps you’ve misunderstood the problem? crmForm.all.fieldName.ForceSubmit = true; would do exactly the opposite of what I want to do.

At the moment, behaviour on create forms only is like I’d already used your code, except that I haven’t 🙂

The situation is odd because I can replicate it on some deployments but not on all (for example my current local Virtual Machine does not exhibit this behaviour).

Your post has made me wonder what will happen if I try adding crmForm.all.fieldName.ForceSubmit = false; – I will try it and report back!

  Greg Owens wrote @

That didn’t work either…

  Chris Condron wrote @

I’m a little confused.

In order to display readonly values in subsibquent form loads (excepting custom JS), or have them accessable to workflows or plugins, those vaules would need to be in the database.

Therefore they would need to be saved on create to set the vaules in the db, but not on updates because they were set on create.

In the odd case that you never want the vaules in the DB, ( and therefore never accessable to plugins, workflows et. al.) drop the attributes and just add them as custom labels on the form with javascript.

-Chris

  Greg Owens wrote @

Hi Chris and thanks for taking the time to comment.

You are absolutely right about the need to (usually) have data saved back to the database. I guess I was unclear in my original post. In my scenario, I didn’t want the data to be written to the database. Admittedly this is a fringe case. I was simply using JS to pull data into a readonly form field at runtime and expecting it to be discarded on form save. This expectation seemed justified since if I try the same thing on an update form, then the data is not propagated to the server. To do so on a icreate form struck me as inconsistent (not to mention the fact that I have replicated this perceived bug on some systems and cannot on others).

Your suggestion to avoid the use of attributes in my scenario and simply inject labels using DHTML/JavaScript would certainly work but is of course unsupported.

I guess in summary, I’m doing something unusual (though not unsupported) and in doing so I have discovered an inconsistency in some environments. I’m most interested to know if anyone else can replicate the problem!

  Ben wrote @

060 // RetrieveFormXmlRequest is unsupported since it is for “internal use
061 // only” but the alternative involves getting the entity type code
062 // from metadataservice then issuing a request for OrganisationUI
063 // for the given entity. This too is deprecated due to not being
064 // supported in CRM 2011 – but what are we to do, eh?

Had the same problem. Tried your way, it returned null for everything. Fortunately after an inordinate amount of searching I stumbled on this:
http://msdn.microsoft.com/en-us/library/gg334527.aspx

FormXml is stored in the SystemForm entity. You can programmatically retrieve and update the FormXml using this entity.

Hope that helps someone!

  Greg Owens wrote @

Thanks Ben – I suspect that the reason it didn’t work for you is that my original issue (and solution) was in CRM v4 and you are in CRM 2011. Thanks for providing a supported solution for CRM 2011 users.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: