As part of my 2011 upgrade from 4.0, I was able to setup a new 'clean' server this past week, and discovered something very strange that is a bit disconcerting. All of my 4.0 Plugins seemed to convert to 2011 OK, but for some reason if you try to manage/change them in the new 2011 Plugin Registration tool, you get a lot of errors.
As part of my 2011 upgrade project, I am re-writing all of my plugins that work with data, but I was leaving all the ones that created custom workflow items alone. However, one of my earlier plugins had some auto-numbering code for entities that needed a number, and some custom workflow items. So what I planned to do was to just un-register the steps that did the auto-numbering, and leave the other stuff alone.
When I went into the 2011 plugin registration tool, clicked the plugin and steps, clicked 'unregister'...and got a bunch of error messages.
Some research (i.e. Google searches) revealed that is was having trouble because it was compiled under 4.0, but the server was a new, clean 2011 server that had never had 4.0 on it. SO the solution was to register some of the 4.0 assemblies in the GAC on the new server.
So you need to get these assemblies from a 4.0 server and register them on the 2011 server.
Note: After registering them, I also needed to reboot the server.
gacutil /if System.workflow.activities.dll
gacutil /if Microsoft.crm.sdk.dll
gacutil /if Microsoft.crm.sdktypeproxy.dll
This seems to work, but really bothers me that I had to do this. It was not truly a complete upgrade from 4.0. Granted, if I didn't have any plugins, this wouldn't be an issue, but I doubt there are too many deployments that don't have at least one.
Tuesday, March 6, 2012
Monday, February 6, 2012
2011 Plug-in Development and Debug Workflow Tips
Just started a major part of a 4.0 to 2011 conversion and began to re-work my Plug-ins. I did this for multiple reasons, but the main one is that I wrote a lot of 4.0 plugins on the fly while I was learning how to write them. Needless to say, a lot of my code was not very efficient and needed some re factoring. Lots of code duplication across multiple projects.
The first thing I did was to install the CRM VS2010 developer extensions and use them to create my Plugin project from scratch, along with adding a CRMPackage project to deploy the plugin.
I really like being able to browse the entity in CRM Explorer, right-click and select to "Create Plug-In", select the type, etc. It creates all the basics for you. Sweet.
The first time you have all your Plug-ins and steps/messages (Post Create, Post Update, etc.) defined, you can then go to the CrmPackage project and deploy the Plugin. It sets everything up for you, so you don't have to go into the Plugin registration tool and manually add the plug-in, steps, images, etc.
However, I also saw that if you are just working in the code making minor changes, that this 'deploy' step can take some time, slowing down your development.
So I wanted to see if I could just update the Assembly DLL directly, since my steps and target images weren't changing. I was just adding new functional code or fixing bugs.
This can be done, but it's tricky. The first couple of times I tried it, the Plugin Registration Tool would lock up tight requiring me to kill it in Task Manager.
Part of the problem is that in order to copy the .PDB debug symbols to the \server\bin\assembly directory, you have to issue an IISRESET in the build process.
I do this as a post-build event command line, followed by a copy of the .PDB files to the directory. I also copy the DLL to a local directory that I can point the Plugin Registration Tool at.
The first thing I did was to install the CRM VS2010 developer extensions and use them to create my Plugin project from scratch, along with adding a CRMPackage project to deploy the plugin.
I really like being able to browse the entity in CRM Explorer, right-click and select to "Create Plug-In", select the type, etc. It creates all the basics for you. Sweet.
The first time you have all your Plug-ins and steps/messages (Post Create, Post Update, etc.) defined, you can then go to the CrmPackage project and deploy the Plugin. It sets everything up for you, so you don't have to go into the Plugin registration tool and manually add the plug-in, steps, images, etc.
However, I also saw that if you are just working in the code making minor changes, that this 'deploy' step can take some time, slowing down your development.
So I wanted to see if I could just update the Assembly DLL directly, since my steps and target images weren't changing. I was just adding new functional code or fixing bugs.
This can be done, but it's tricky. The first couple of times I tried it, the Plugin Registration Tool would lock up tight requiring me to kill it in Task Manager.
Part of the problem is that in order to copy the .PDB debug symbols to the \server\bin\assembly directory, you have to issue an IISRESET in the build process.
I do this as a post-build event command line, followed by a copy of the .PDB files to the directory. I also copy the DLL to a local directory that I can point the Plugin Registration Tool at.
The side effect of this is that the Plugin Registration tool loses its mind, and apparently a cached connection to the server. If you just jump over to it and try to update the assembly with the Update Button, it will lock up.
The solution...Refresh first.
By refreshing, it takes an extra few seconds as it reconnects to IIS, but that's what you need to do to keep it working. After that you can click Update, point it to your re-built DLL file, and you're all set.
In 4.0 i used to be able to just Update the Assembly directly, and that process would take the extra few seconds while it reconnected to IIS, but that now causes a lockup. No big deal, but was pretty annoying the first few times I did it.
After the Refresh, you can now switch back to VS2010, select Debug, Attach to Process, and select the W3WP process.
Hope this helps if you are seeing lockups with the Plugin Registration Tool.
Wednesday, January 25, 2012
Update a Closed / Completed Task
Sometimes there may be an instance where you need to update some fields on a task activity through the SDK, but there is an initial problem. If you just try to run UpdateObject on the record, it will tell you that you can't update a task that is closed.
What you have to do is temporarily re-open the task, make the change, then close it again. This will reflect in your audit log, and any plugin's on the SetState message will fire, so use accordingly.
In my Method, I am clearing a custom field called InternalQueueId that is a lookup to another entity.
int RecordCount = 1;
int TaskClosedState, TaskClosedStatus;
List<SPS.Task> Tasks = (from t in xrmConnection.TaskSet
where t.StatusCode == 5
&& t.custom_internalqueueid != null
orderby t.ActivityId
select t).Take(MaxRecordsToProcess).ToList();
if (Tasks.Count > 0)
{
foreach (var t in Tasks)
{
TaskClosedState = t.StateCode.Value;
TaskClosedStatus = t.StatusCode.Value;
RecordCount += 1;
// Reopen the task
SetStateRequest ssr = new SetStateRequest();
ssr.EntityMoniker = t.ToEntityReference();
ssr.State = new OptionSetValue(0);
ssr.Status = new OptionSetValue(2);
SetStateResponse resp1 = (SetStateResponse)xrmConnection.Execute(ssr);
// Update the field and set it back to null
if (t.custom_internalqueueid != null)
{
SPS.Task tUpdate = (Task)xrmConnection.Retrieve(Task.EntityLogicalName, t.Id, new ColumnSet("activityid", "custom_internalqueueid"));
tUpdate.custom_internalqueueid = null;
UpdateRequest ur = new UpdateRequest()
{
Target = tUpdate,
};
xrmConnection.Execute(ur);
}
// Re-Close the task
SetStateRequest Closed = new SetStateRequest();
Closed.EntityMoniker = new EntityReference(t.LogicalName, t.Id);
Closed.State = new OptionSetValue(TaskClosedState);
Closed.Status = new OptionSetValue(TaskClosedStatus);
SetStateResponse resp2 = (SetStateResponse)xrmConnection.Execute(Closed);
xrmConnection.SaveChanges();
}
RecordCount -= 1;
xrmConnection.SaveChanges();
}
Kudos to David Jennaway @ http://mscrmuk.blogspot.com/ for pointing me in the right direction to solve this.
What you have to do is temporarily re-open the task, make the change, then close it again. This will reflect in your audit log, and any plugin's on the SetState message will fire, so use accordingly.
In my Method, I am clearing a custom field called InternalQueueId that is a lookup to another entity.
int RecordCount = 1;
int TaskClosedState, TaskClosedStatus;
List<SPS.Task> Tasks = (from t in xrmConnection.TaskSet
where t.StatusCode == 5
&& t.custom_internalqueueid != null
orderby t.ActivityId
select t).Take(MaxRecordsToProcess).ToList();
if (Tasks.Count > 0)
{
foreach (var t in Tasks)
{
TaskClosedState = t.StateCode.Value;
TaskClosedStatus = t.StatusCode.Value;
RecordCount += 1;
// Reopen the task
SetStateRequest ssr = new SetStateRequest();
ssr.EntityMoniker = t.ToEntityReference();
ssr.State = new OptionSetValue(0);
ssr.Status = new OptionSetValue(2);
SetStateResponse resp1 = (SetStateResponse)xrmConnection.Execute(ssr);
// Update the field and set it back to null
if (t.custom_internalqueueid != null)
{
SPS.Task tUpdate = (Task)xrmConnection.Retrieve(Task.EntityLogicalName, t.Id, new ColumnSet("activityid", "custom_internalqueueid"));
tUpdate.custom_internalqueueid = null;
UpdateRequest ur = new UpdateRequest()
{
Target = tUpdate,
};
xrmConnection.Execute(ur);
}
// Re-Close the task
SetStateRequest Closed = new SetStateRequest();
Closed.EntityMoniker = new EntityReference(t.LogicalName, t.Id);
Closed.State = new OptionSetValue(TaskClosedState);
Closed.Status = new OptionSetValue(TaskClosedStatus);
SetStateResponse resp2 = (SetStateResponse)xrmConnection.Execute(Closed);
xrmConnection.SaveChanges();
}
RecordCount -= 1;
xrmConnection.SaveChanges();
}
Tuesday, January 24, 2012
LINQ Restrictions with CRM 2011 xRM Provider
Apparently there are different rules with the LINQ provider in the CRM 2011 SDK from the CRM 4.0 LINQ provider, and I ran into one today.
I have a process were I am deleting old email activities that meet a specific criteria in the subject line. I had a LINQ query in CRM 4.0 console app that worked just fine:
var Emails = (
from e1 in xrmConnection.EmailSet
join i in xrmConnection.IncidentSet on e1.RegardingObjectId.Id equals i.IncidentId
where e1.CreatedOn.Value <= dt
&& e1.Subject.ToString().StartsWith("XYZ:", true, null)
where i.IncidentStageCode.Value == 200999 && i.Custom_ResolvedOn.Value <= dt
select e1).Take(MaxRecordsToProcess).ToList();
When I ran this same LINQ query in CRM 2011, it didn't work and threw this error:
Invalid 'where' condition. An entity member is invoking an invalid property or method.
After a search, I found this forum post that explained the error and after trial and error I found my specific problem. The key point is this forum post is this limitation statement:
A limitation of the CRM LINQ provider is that:
1.The left hand side of a predicate (where clause) MUST be an entity attribute
2.The right hand side of a predicate MUST be a literal value or variable
At first, I didn't see it because I was putting them in the right order...the problem was the the string check didn't actually compare it to 'true'.
I have a process were I am deleting old email activities that meet a specific criteria in the subject line. I had a LINQ query in CRM 4.0 console app that worked just fine:
var Emails = (
from e1 in xrmConnection.EmailSet
join i in xrmConnection.IncidentSet on e1.RegardingObjectId.Id equals i.IncidentId
where e1.CreatedOn.Value <= dt
&& e1.Subject.ToString().StartsWith("XYZ:", true, null)
where i.IncidentStageCode.Value == 200999 && i.Custom_ResolvedOn.Value <= dt
select e1).Take(MaxRecordsToProcess).ToList();
When I ran this same LINQ query in CRM 2011, it didn't work and threw this error:
Invalid 'where' condition. An entity member is invoking an invalid property or method.
After a search, I found this forum post that explained the error and after trial and error I found my specific problem. The key point is this forum post is this limitation statement:
A limitation of the CRM LINQ provider is that:
1.The left hand side of a predicate (where clause) MUST be an entity attribute
2.The right hand side of a predicate MUST be a literal value or variable
At first, I didn't see it because I was putting them in the right order...the problem was the the string check didn't actually compare it to 'true'.
var Emails = (
from e1 in xrmConnection.EmailSet
join i in xrmConnection.IncidentSet on e1.RegardingObjectId.Id equals i.IncidentId
where e1.CreatedOn.Value <= dt
&& e1.Subject.ToString().StartsWith( "XYZ:" , true, null) == true
where i.IncidentStageCode.Value == 200999 && i.Custom_ResolvedOn.Value <= dt
select e1).Take(MaxRecordsToProcess).ToList();
Adding that '== true' to the string comparison solved it.
Weird.
from e1 in xrmConnection.EmailSet
join i in xrmConnection.IncidentSet on e1.RegardingObjectId.Id equals i.IncidentId
where e1.CreatedOn.Value <= dt
&& e1.Subject.ToString().StartsWith( "XYZ:" , true, null) == true
where i.IncidentStageCode.Value == 200999 && i.Custom_ResolvedOn.Value <= dt
select e1).Take(MaxRecordsToProcess).ToList();
Adding that '== true' to the string comparison solved it.
Weird.
Tuesday, January 17, 2012
CRM 2011 Switching Forms
One of the custom entities we have in our CRM deployment is used to track System information. As a result, we have to store LOTS of different data points about each customers installation (Usernames, ip addresses, etc).
Since we have multiple business units, all using the same Dynamics CRM deployment, we have different needs for each unit, but all stored in the same System Entity. In CRM 4.0 we solved this by storing the data for individual systems on tabs, then show/hide tabs when the form loads. This worked fine until we ran into the limitation of 8 tabs per form. Ugh.
In CRM 2011 we can now have multiple forms for the same entity, so I wanted to take advantage of this and have the Form Load even determine which form to load for the appropriate system type.
First, I created a new custom entity for System Type, then added a field on that entity for the name of the form to load. Now when the System form loads, it queries the system type for the form name to use.
The heart of this routine is the call to:
Xrm.Page.ui.formSelector.items.get(sSystemMainFormId).navigate()
The trick is getting the GUIDs for all the forms. For that, I built an array and then loop through it.
Hopefully you will find this useful.
function setCorrectSystemForm() {
var sAlertMessage = '';
var arrForms = new Array();
var iFormCounter = 0;
var sFormId = '';
var oCurrentFormItem = Xrm.Page.ui.formSelector.getCurrentItem();
var sCurrentForm = oCurrentFormItem.getLabel();
var sCurrentFormId = oCurrentFormItem.getId();
var sSystemMainFormId = '';
// Load all the forms into an array
Xrm.Page.ui.formSelector.items.forEach(
function (item, index) {
var itemLabel = item.getLabel();
var itemId = item.getId();
arrForms[iFormCounter] = new Object();
arrForms[iFormCounter].name = item.getLabel();
arrForms[iFormCounter].id = item.getId();
sAlertMessage += " \u2219 " + itemLabel + " :: " + itemId + "\n";
iFormCounter++;
});
iFormCounter--;
sAlertMessage += "\n\n iFormCounter = " + iFormCounter.toString();
// Query for the form name required for this system
var sFormType = getSystemTypeFormName();
var sFormTypeId = '';
// Match that name with an ID from our Array
for (var i = 0; i <= iFormCounter; i++) {
if (arrForms[i].name == sFormType) {
sAlertMessage += '\n This System uses Form :: ' + arrForms[i].name + ' \n id :: ' + arrForms[i].id.toString();
sFormTypeId = arrForms[i].id;
break;
}
}
// Get the default Main form ID in case we haven't assigned a System Type for this record.
for (var i = 0; i <= iFormCounter; i++) {
if (arrForms[i].name == 'System-Main') {
sAlertMessage += '\n\n System-Main Form id :: ' + arrForms[i].id.toString();
sSystemMainFormId = arrForms[i].id;
break;
}
}
sAlertMessage += '\n ';
sAlertMessage += '\n sFormType :: ' + sFormType;
sAlertMessage += '\n sFormTypeId :: ' + sFormTypeId;
sAlertMessage += '\n ';
sAlertMessage += '\n sCurrentForm :: ' + sCurrentForm;
sAlertMessage += '\n sCurrentFormId :: ' + sCurrentFormId;
// Compare the form type required to the current form and navigate as needed.
if (sFormTypeId != '' && sFormTypeId != sCurrentFormId) {
// Comment this out to get full debug information
sAlertMessage = '';
sAlertMessage += '\n Incorrect Form for this System \'Type\'.';
sAlertMessage += '\n Loading Form ' + sFormType + ' ...';
alert(sAlertMessage);
// Navigate to the correct form
Xrm.Page.ui.formSelector.items.get(sFormTypeId).navigate();
} else {
if (sFormTypeId == '' && sCurrentFormId != sSystemMainFormId) {
sAlertMessage = '';
sAlertMessage += '\n System \'Type\' Not Selected!\n'
sAlertMessage += '\n Select a \'System Type\' to load correct form for this system...';
sAlertMessage += '\n Defaulting to System-Main form.';
alert(sAlertMessage);
// Navigate to the default form
Xrm.Page.ui.formSelector.items.get(sSystemMainFormId).navigate();
}
} // end else if no system type selected
};
Since we have multiple business units, all using the same Dynamics CRM deployment, we have different needs for each unit, but all stored in the same System Entity. In CRM 4.0 we solved this by storing the data for individual systems on tabs, then show/hide tabs when the form loads. This worked fine until we ran into the limitation of 8 tabs per form. Ugh.
In CRM 2011 we can now have multiple forms for the same entity, so I wanted to take advantage of this and have the Form Load even determine which form to load for the appropriate system type.
First, I created a new custom entity for System Type, then added a field on that entity for the name of the form to load. Now when the System form loads, it queries the system type for the form name to use.
The heart of this routine is the call to:
Xrm.Page.ui.formSelector.items.get(sSystemMainFormId).navigate()
The trick is getting the GUIDs for all the forms. For that, I built an array and then loop through it.
Hopefully you will find this useful.
function setCorrectSystemForm() {
var sAlertMessage = '';
var arrForms = new Array();
var iFormCounter = 0;
var sFormId = '';
var oCurrentFormItem = Xrm.Page.ui.formSelector.getCurrentItem();
var sCurrentForm = oCurrentFormItem.getLabel();
var sCurrentFormId = oCurrentFormItem.getId();
var sSystemMainFormId = '';
// Load all the forms into an array
Xrm.Page.ui.formSelector.items.forEach(
function (item, index) {
var itemLabel = item.getLabel();
var itemId = item.getId();
arrForms[iFormCounter] = new Object();
arrForms[iFormCounter].name = item.getLabel();
arrForms[iFormCounter].id = item.getId();
sAlertMessage += " \u2219 " + itemLabel + " :: " + itemId + "\n";
iFormCounter++;
});
iFormCounter--;
sAlertMessage += "\n\n iFormCounter = " + iFormCounter.toString();
// Query for the form name required for this system
var sFormType = getSystemTypeFormName();
var sFormTypeId = '';
// Match that name with an ID from our Array
for (var i = 0; i <= iFormCounter; i++) {
if (arrForms[i].name == sFormType) {
sAlertMessage += '\n This System uses Form :: ' + arrForms[i].name + ' \n id :: ' + arrForms[i].id.toString();
sFormTypeId = arrForms[i].id;
break;
}
}
// Get the default Main form ID in case we haven't assigned a System Type for this record.
for (var i = 0; i <= iFormCounter; i++) {
if (arrForms[i].name == 'System-Main') {
sAlertMessage += '\n\n System-Main Form id :: ' + arrForms[i].id.toString();
sSystemMainFormId = arrForms[i].id;
break;
}
}
sAlertMessage += '\n ';
sAlertMessage += '\n sFormType :: ' + sFormType;
sAlertMessage += '\n sFormTypeId :: ' + sFormTypeId;
sAlertMessage += '\n ';
sAlertMessage += '\n sCurrentForm :: ' + sCurrentForm;
sAlertMessage += '\n sCurrentFormId :: ' + sCurrentFormId;
// Compare the form type required to the current form and navigate as needed.
if (sFormTypeId != '' && sFormTypeId != sCurrentFormId) {
// Comment this out to get full debug information
sAlertMessage = '';
sAlertMessage += '\n Incorrect Form for this System \'Type\'.';
sAlertMessage += '\n Loading Form ' + sFormType + ' ...';
alert(sAlertMessage);
// Navigate to the correct form
Xrm.Page.ui.formSelector.items.get(sFormTypeId).navigate();
} else {
if (sFormTypeId == '' && sCurrentFormId != sSystemMainFormId) {
sAlertMessage = '';
sAlertMessage += '\n System \'Type\' Not Selected!\n'
sAlertMessage += '\n Select a \'System Type\' to load correct form for this system...';
sAlertMessage += '\n Defaulting to System-Main form.';
alert(sAlertMessage);
// Navigate to the default form
Xrm.Page.ui.formSelector.items.get(sSystemMainFormId).navigate();
}
} // end else if no system type selected
};
Thursday, January 12, 2012
CRM 2011 - Update Rollup 6 released
Available Here: http://www.microsoft.com/download/en/details.aspx?id=28712
Good news is that there are only 2 hot fixes that need to be manually applied.
Slow Performance with RetrieveMultiple: http://support.microsoft.com/kb/2535245
POA Table getting too large: http://support.microsoft.com/kb/2664150
Good news is that there are only 2 hot fixes that need to be manually applied.
Slow Performance with RetrieveMultiple: http://support.microsoft.com/kb/2535245
POA Table getting too large: http://support.microsoft.com/kb/2664150
Wednesday, January 11, 2012
JavaScript query with FetchXML
One of this things I really like about working with JavaScript in
CRM 2011 is that you can now have shared libraries of re-usable code for each
form AND you can build FetchXML and send it through to the server for queries.
We have a LOT of customizations on our Case forms which require
several behind the scenes queries to get data to show on the form as the user
is interacting with it. One example is that when the user selects a Contact
record, there are a couple of fields on the form where we show the phone
number(s) to call them, as well as their email. It's just for reference, but it
helps the user experience.
The good news with CRM 2011
is that you don’t really have to know all the FetchXML codes and write it
yourself. You can build an advanced find
query and then export the FetchXML results to a text file.
Here's a good example of a shared function I use on several forms:
function GetContactData(sContactId) {
var _oService;
var _sOrgName = Xrm.Page.context.getOrgUniqueName();
var _sServerUrl = GetServerURL();
var _sOrgName = Xrm.Page.context.getOrgUniqueName();
var _sServerUrl = GetServerURL();
var sFetch = "<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>" +
"<entity name='contact'>" +
"<attribute name='fullname' />" +
"<attribute name='telephone1' />" +
"<attribute name='emailaddress1' />" +
"<attribute name='mobilephone' />" +
"<attribute name='parentcustomerid' />" +
"<attribute name='contactid' />" +
"<attribute name='address1_line1' />" +
"<attribute name='address1_line2' />" +
"<attribute name='address1_city' />" +
"<attribute name='address1_stateorprovince' />" +
"<attribute name='address1_postalcode' />" +
"<attribute name='address1_country' />" +
"<order attribute='lastname' descending='false' />" +
"<filter type='and'>" +
"<condition attribute='statecode' operator='eq' value='0' />" +
"<condition attribute='contactid' operator='eq' uitype='contact' value='" + sContactId + "' />" +
"</filter></entity></fetch>";
"<entity name='contact'>" +
"<attribute name='fullname' />" +
"<attribute name='telephone1' />" +
"<attribute name='emailaddress1' />" +
"<attribute name='mobilephone' />" +
"<attribute name='parentcustomerid' />" +
"<attribute name='contactid' />" +
"<attribute name='address1_line1' />" +
"<attribute name='address1_line2' />" +
"<attribute name='address1_city' />" +
"<attribute name='address1_stateorprovince' />" +
"<attribute name='address1_postalcode' />" +
"<attribute name='address1_country' />" +
"<order attribute='lastname' descending='false' />" +
"<filter type='and'>" +
"<condition attribute='statecode' operator='eq' value='0' />" +
"<condition attribute='contactid' operator='eq' uitype='contact' value='" + sContactId + "' />" +
"</filter></entity></fetch>";
_oService = new FetchUtil(_sOrgName, _sServerUrl);
// sync query
var results = _oService.Fetch(sFetch);
var results = _oService.Fetch(sFetch);
// Use for Async query
// _oService.Fetch(sFetch, myCallBack);
// _oService.Fetch(sFetch, myCallBack);
if (results != null && results.length > 0) { return results; }
else { return ''; }
else { return ''; }
};
In
order to use this on the case/incident form, here is an example to alert the
user with the contact data returned:
function AlertContactData() {
if (Xrm.Page.getAttribute('responsiblecontactid').getValue() != null) {
var results = GetContactData(Xrm.Page.getAttribute('responsiblecontactid').getValue()[0].id);
if (results != '' && results.length > 0) {
alert("Email address: " + results[0].attributes["emailaddress1"];
alert("Telephone1: " + results[0].attributes["telephone1"];
alert("Mobilephone: " + results[0].attributes["mobilephone"];
}
else { Alert(“Contact Not Found! “);}
}
};
if (Xrm.Page.getAttribute('responsiblecontactid').getValue() != null) {
var results = GetContactData(Xrm.Page.getAttribute('responsiblecontactid').getValue()[0].id);
if (results != '' && results.length > 0) {
alert("Email address: " + results[0].attributes["emailaddress1"];
alert("Telephone1: " + results[0].attributes["telephone1"];
alert("Mobilephone: " + results[0].attributes["mobilephone"];
}
else { Alert(“Contact Not Found! “);}
}
};
So
there are a couple of things needed to make this work, namely the FetchUtil
code. As much as I would like to have brilliantly
come up with this on my own… alas I am not that talented. But I am talented enough to copy good code
when I see it. This particular code was
lifted from another blogger at:
var XMLHTTPREADY = 4;
this.org = sOrg;
this.server = sServer;
if (typeof (ORG_UNIQUE_NAME) != "undefined") {
this.org = ORG_UNIQUE_NAME;
}
}
if (sServer == null) {
this.server = window.location.protocol + "//" + window.location.host;
}
};
FetchUtil.prototype._ExecuteRequest = function (sXml, sMessage, fInternalCallback, fUserCallback) {
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", this.server + "/XRMServices/2011/Organization.svc/web", (fUserCallback != null));
xmlhttp.setRequestHeader("Accept", "application/xml, text/xml, */*");
xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", this.server + "/XRMServices/2011/Organization.svc/web", (fUserCallback != null));
xmlhttp.setRequestHeader("Accept", "application/xml, text/xml, */*");
xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
if (fUserCallback != null) {
//asynchronous: register callback function, then send the request.
var crmServiceObject = this;
xmlhttp.onreadystatechange = function () {
fInternalCallback.call(crmServiceObject, xmlhttp, fUserCallback)
};
xmlhttp.send(sXml);
} else {
//synchronous: send request, then call the callback function directly
xmlhttp.send(sXml);
return fInternalCallback.call(this, xmlhttp, null);
}
};
//asynchronous: register callback function, then send the request.
var crmServiceObject = this;
xmlhttp.onreadystatechange = function () {
fInternalCallback.call(crmServiceObject, xmlhttp, fUserCallback)
};
xmlhttp.send(sXml);
} else {
//synchronous: send request, then call the callback function directly
xmlhttp.send(sXml);
return fInternalCallback.call(this, xmlhttp, null);
}
};
FetchUtil.prototype._HandleErrors = function (xmlhttp) {
/// <summary>(private) Handles xmlhttp errors</summary>
if (xmlhttp.status != XMLHTTPSUCCESS) {
var sError = "Error: " + xmlhttp.responseText + " " + xmlhttp.statusText;
alert(sError);
return true;
} else {
return false;
}
};
/// <summary>(private) Handles xmlhttp errors</summary>
if (xmlhttp.status != XMLHTTPSUCCESS) {
var sError = "Error: " + xmlhttp.responseText + " " + xmlhttp.statusText;
alert(sError);
return true;
} else {
return false;
}
};
FetchUtil.prototype.Fetch = function (sFetchXml, fCallback) {
/// <summary>Execute a FetchXml request. (result is the response XML)</summary>
/// <param name="sFetchXml">fetchxml string</param>
/// <param name="fCallback" optional="true" type="function">(Optional) Async callback function if specified. If left null, function is synchronous </param>
/// <summary>Execute a FetchXml request. (result is the response XML)</summary>
/// <param name="sFetchXml">fetchxml string</param>
/// <param name="fCallback" optional="true" type="function">(Optional) Async callback function if specified. If left null, function is synchronous </param>
var request = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
request += "<s:Body>";
request += '<Execute xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services">' + '<request i:type="b:RetrieveMultipleRequest" ' + ' xmlns:b="http://schemas.microsoft.com/xrm/2011/Contracts" ' + ' xmlns:i="http://www.w3.org/2001/XMLSchema-instance">' + '<b:Parameters xmlns:c="http://schemas.datacontract.org/2004/07/System.Collections.Generic">' + '<b:KeyValuePairOfstringanyType>' + '<c:key>Query</c:key>' + '<c:value i:type="b:FetchExpression">' + '<b:Query>';
request += CrmEncodeDecode.CrmXmlEncode(sFetchXml);
request += '</b:Query>' + '</c:value>' + '</b:KeyValuePairOfstringanyType>' + '</b:Parameters>' + '<b:RequestId i:nil="true"/>' + '<b:RequestName>RetrieveMultiple</b:RequestName>' + '</request>' + '</Execute>';
request += '</s:Body></s:Envelope>';
request += "<s:Body>";
request += '<Execute xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services">' + '<request i:type="b:RetrieveMultipleRequest" ' + ' xmlns:b="http://schemas.microsoft.com/xrm/2011/Contracts" ' + ' xmlns:i="http://www.w3.org/2001/XMLSchema-instance">' + '<b:Parameters xmlns:c="http://schemas.datacontract.org/2004/07/System.Collections.Generic">' + '<b:KeyValuePairOfstringanyType>' + '<c:key>Query</c:key>' + '<c:value i:type="b:FetchExpression">' + '<b:Query>';
request += CrmEncodeDecode.CrmXmlEncode(sFetchXml);
request += '</b:Query>' + '</c:value>' + '</b:KeyValuePairOfstringanyType>' + '</b:Parameters>' + '<b:RequestId i:nil="true"/>' + '<b:RequestName>RetrieveMultiple</b:RequestName>' + '</request>' + '</Execute>';
request += '</s:Body></s:Envelope>';
return this._ExecuteRequest(request, "Fetch", this._FetchCallback, fCallback);
};
};
FetchUtil.prototype._FetchCallback = function (xmlhttp, callback) {
///<summary>(private) Fetch message callback.</summary>
//xmlhttp must be completed
if (xmlhttp.readyState != XMLHTTPREADY) {
return;
}
///<summary>(private) Fetch message callback.</summary>
//xmlhttp must be completed
if (xmlhttp.readyState != XMLHTTPREADY) {
return;
}
//check for server errors
if (this._HandleErrors(xmlhttp)) {
return;
}
if (this._HandleErrors(xmlhttp)) {
return;
}
var sFetchResult = xmlhttp.responseXML.selectSingleNode("//a:Entities").xml;
var resultDoc = new ActiveXObject("Microsoft.XMLDOM");
resultDoc.async = false;
resultDoc.loadXML(sFetchResult);
resultDoc.async = false;
resultDoc.loadXML(sFetchResult);
//parse result xml into array of jsDynamicEntity objects
var results = new Array(resultDoc.firstChild.childNodes.length);
for (var i = 0; i < resultDoc.firstChild.childNodes.length; i++) {
var oResultNode = resultDoc.firstChild.childNodes[i];
var jDE = new jsDynamicEntity();
var obj = new Object();
var results = new Array(resultDoc.firstChild.childNodes.length);
for (var i = 0; i < resultDoc.firstChild.childNodes.length; i++) {
var oResultNode = resultDoc.firstChild.childNodes[i];
var jDE = new jsDynamicEntity();
var obj = new Object();
for (var j = 0; j < oResultNode.childNodes.length; j++) {
switch (oResultNode.childNodes[j].baseName) {
case "Attributes":
var attr = oResultNode.childNodes[j];
switch (oResultNode.childNodes[j].baseName) {
case "Attributes":
var attr = oResultNode.childNodes[j];
for (var k = 0; k < attr.childNodes.length; k++) {
// Establish the Key for the Attribute
var sKey = attr.childNodes[k].firstChild.text;
var sType = '';
var sKey = attr.childNodes[k].firstChild.text;
var sType = '';
// Determine the Type of Attribute value we should expect
for (var l = 0; l < attr.childNodes[k].childNodes[1].attributes.length; l++) {
if (attr.childNodes[k].childNodes[1].attributes[l].baseName == 'type') {
sType = attr.childNodes[k].childNodes[1].attributes[l].text;
}
}
for (var l = 0; l < attr.childNodes[k].childNodes[1].attributes.length; l++) {
if (attr.childNodes[k].childNodes[1].attributes[l].baseName == 'type') {
sType = attr.childNodes[k].childNodes[1].attributes[l].text;
}
}
switch (sType) {
case "a:OptionSetValue":
var entOSV = new jsOptionSetValue();
entOSV.type = sType;
entOSV.value = attr.childNodes[k].childNodes[1].text;
obj[sKey] = entOSV;
break;
case "a:OptionSetValue":
var entOSV = new jsOptionSetValue();
entOSV.type = sType;
entOSV.value = attr.childNodes[k].childNodes[1].text;
obj[sKey] = entOSV;
break;
case "a:EntityReference":
var entRef = new jsEntityReference();
entRef.type = sType;
entRef.guid = attr.childNodes[k].childNodes[1].childNodes[0].text;
entRef.logicalName = attr.childNodes[k].childNodes[1].childNodes[1].text;
entRef.name = attr.childNodes[k].childNodes[1].childNodes[2].text;
obj[sKey] = entRef;
break;
var entRef = new jsEntityReference();
entRef.type = sType;
entRef.guid = attr.childNodes[k].childNodes[1].childNodes[0].text;
entRef.logicalName = attr.childNodes[k].childNodes[1].childNodes[1].text;
entRef.name = attr.childNodes[k].childNodes[1].childNodes[2].text;
obj[sKey] = entRef;
break;
default:
var entCV = new jsCrmValue();
entCV.type = sType;
entCV.value = attr.childNodes[k].childNodes[1].text;
obj[sKey] = entCV;
break;
}
var entCV = new jsCrmValue();
entCV.type = sType;
entCV.value = attr.childNodes[k].childNodes[1].text;
obj[sKey] = entCV;
break;
}
}
jDE.attributes = obj;
break;
break;
case "Id":
jDE.guid = oResultNode.childNodes[j].text;
break;
jDE.guid = oResultNode.childNodes[j].text;
break;
case "LogicalName":
jDE.logicalName = oResultNode.childNodes[j].text;
break;
jDE.logicalName = oResultNode.childNodes[j].text;
break;
case "FormattedValues":
var foVal = oResultNode.childNodes[j];
var foVal = oResultNode.childNodes[j];
for (var m = 0; m < foVal.childNodes.length; m++) {
// Establish the Key, we are going to fill in the formatted value of the already found attribute
var sKey2 = foVal.childNodes[m].firstChild.text;
jDE.attributes[sKey2].formattedValue = foVal.childNodes[m].childNodes[1].text;
}
break;
}
// Establish the Key, we are going to fill in the formatted value of the already found attribute
var sKey2 = foVal.childNodes[m].firstChild.text;
jDE.attributes[sKey2].formattedValue = foVal.childNodes[m].childNodes[1].text;
}
break;
}
}
results[i] = jDE;
}
}
//return entities
if (callback != null) callback(results);
else return results;
if (callback != null) callback(results);
else return results;
};
function jsDynamicEntity(gID, sLogicalName) {
this.guid = gID;
this.logicalName = sLogicalName;
this.attributes = new Object();
};
function jsCrmValue(sType, sValue) {
this.type = sType;
this.value = sValue;
};
this.type = sType;
this.value = sValue;
};
function jsEntityReference(gID, sLogicalName, sName) {
this.guid = gID;
this.logicalName = sLogicalName;
this.name = sName;
this.type = 'EntityReference';
};
this.guid = gID;
this.logicalName = sLogicalName;
this.name = sName;
this.type = 'EntityReference';
};
function jsOptionSetValue(iValue, sFormattedValue) {
this.value = iValue;
this.formattedValue = sFormattedValue;
this.type = 'OptionSetValue';
};
this.value = iValue;
this.formattedValue = sFormattedValue;
this.type = 'OptionSetValue';
};
Subscribe to:
Posts (Atom)