Posts Tagged ‘crm 2011’

Need to import complex data into CRM 2013? It may be easier than you think!

While CRM 2013′s Import Wizard can provide a quick-and-dirty way to get data into the system, there’s a limit to what you can do and how much you can control how the wizard responds to missing data. Sometimes you just happen to have a huge table of historical data that you need in CRM, which may or may not be referencing records that already exist in the system, but will definitely need to be referencing something once they’re imported. Or maybe that historical data will need to reference something like a Contact or Account, but you’d like the lookup logic to be more complex than a simple “do any of these fields contain the value from this column?” condition.

The words “custom app” or “one-time use” tend to send up red flags, but that’s no excuse to try to force a square peg into a round hole. While you could always export data from CRM and import it into a database temporarily, allowing you to analyze it and craft a series of Excel files for import, it may actually be more cost-effective to go ahead and write that one-time use custom app. The truth is, as long as you know how to knock the logic out in C# and you have the connection info for both the source and the destination, it’s very easy to do.

MSDN provides a handy guide on how to set up a barebones version of such an app: Walkthrough: Build a console application that connects to Microsoft Dynamics CRM using developer extensions. All that’s left is to add code to pull your source data, logic to perform lookups, and populate CRM Entity objects to your heart’s content!

Tags: , , , , , ,


Getting the Current User Information in a Plugin

I was working with MS CRM 2011 and I had a situation where I needed to get the information about the current user in the middle of plugin execution. When you start digging around the documentation in MS CRM you’ll find that there are two properties that look like they might have the information you need:

  • InitiatingUserId
  • UserId

Here’s an example of the code you’ll see in a plugin:

public void Execute(IServiceProvider serviceProvider)
{
    IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
        IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

    // Which one is correct?
    var initiatingUserId = context.InitiatingUserId;
    var userId = context.UserId;
}

How do you know which one you need? What’s the difference? The difference is that the UserId property may or may not be the same as the InitiatingUserId. If in your plugin step registration “Run in User’s Context” field has the value “Calling User” then the UserId and InitiatingUserId will match. If you specify a user in the “Run in User’s Context” field, then the UserId field will contain the user ID of the person you specify and the InitiatingUserId will be the actual CRM user whose action triggered the plugin.

My bet is that most people are usually looking for the InitiatingUserId, so it probably makes sense to use that property even if you specify “Calling User” for the “Run in User’s Context” field. That way, if the “Run in User’s Context” value ever changes, InitiatingUserId will still have the correct value. If you use “UserID”, it’s possible you’ll get a different value than what you expect.

Hopefully this helps someone figure out which value is the correct one for your situation.

Tags: , , , , ,


CRM 2013 Online and On-Premise: Handy Dev Tools

I’ve always believed that most anything is possible, as long as you have the right tools. The holds true with Dynamics as much as it does with home repair or car maintenance, and having the right tools at your disposal can turn time-consuming dev tasks into trivial ones.

Daren Turner’s roundup of Dynamics CRM 2011 and 2013 useful Tools and Features bears reblogging, as he lists indispensable resources, such as the CRM SDK, tools that can help your everyday web development, like IE’s Developer Tools, and solutions that you can import into Dynamics to significantly speed up your dev time.

I’ve found two of those solutions particularly useful in my own development: the Ribbon Workbench and the OData Query Designer. Sure, you can mess around with the ribbon XML directly, if you like; the various nodes and attributes are well-documented in the Dynamics SDK. However, the Ribbon Workbench provides you with a WYSIWYG interface that can take the pain out of manually customizing the ribbon. As for the OData Query Designer, it’s just a quick and easy way to generate the strings needed to query the CRM web service from JavaScript, and is part of the Dynamics XRM Tools solution, which contains other handy tools as well.

Tags: , ,


CRM 2011 Plug-ins 101: Be Careful Where You Put That Service!

When I first started playing around with CRM 2011 plug-ins, I pretty much dove in headfirst to see what I could do. Sure, I browsed the SDK and modeled my code after the examples, but I ended up running into a nasty issue with one of my embellishments. What I thought was an innocuous method of handling the IOrganizationService proved to be problematic, considering how CRM 2011 plug-ins function.

I was creating the service like you usually do:

IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();
IOrganizationServiceFactory serviceFactory = 
executionContext.GetExtension<IOrganizationServiceFactory>();
IOrganizationService service = 
serviceFactory.CreateOrganizationService(context.UserId);

Then I made a horrible mistake:

CrmData.Service = service;

At the time, I thought: “It sure is nice and clean, keeping all my data access methods in a separate class! Why don’t I just plop the service into some variable so I don’t have to pass it for each method call?” Little did I know that my choice to not RTFM would’ve told me exactly why I shouldn’t do that.

From MSDN’s Write a Plug-In article:

For improved performance, Microsoft Dynamics CRM caches plug-in instances. The plug-in’s Execute method should be written to be stateless because the constructor is not called for every invocation of the plug-in. Also, multiple system threads could execute the plug-in at the same time. All per invocation state information is stored in the context, so you should not use global variables in plug-ins or attempt to store any data in member variables for use during the next plug-in invocation.

The worst part about this is that it works great, up until you have the plug-in firing more than once, simultaneously. Various errors will be logged, from messages about the service already being in use to errors exclaiming that basic interal CRM functionality, such as RetrieveMultiple, cannot be found.

So, for anyone else out there who has a CRM 2011 plug-in that’s failing in strange and inconsistent ways, you may want to see if you’re keeping your IOrganizationService on a short leash. Sending it away to another class to be used indiscriminately by any ol’ plug-in execution is a surefire way to pad the server error logs!

Tags: , , , , , ,


CRM 2011 Quirk: Field-level Security & Updates

We’ve recently received a request to set up some field-level security in a CRM instance that has already had a ton of customizations developed for it and we ran into an issue that I did not expect to see. When setting up a Field Security Profile with Read permissions, but no Update or Create permissions, one of our Silverlight Web Resources was throwing the dreaded “Need to start a transaction before commit” error:

After doing some digging, it turned out that it wasn’t the “Work Item” custom entity that was the issue; the error was a result of an earlier web service call failing on another entity with a field that had one of the new Field Security Profiles. Only, the field we were updating wasn’t the one with the profile; we just wanted to set new_ProjectCompleteDate and new_InstallCompleteDate, not new_UpdateFromSlx, which is a field that’s not referenced anywhere in the code.

// If the Work Item Type has "Set Install Complete Date" = "Yes", set the Install Complete Date to the Work Item's Complete Date.
if (setInstallComplete != null)
{
  if(setInstallComplete.new_SetInstallCompleteDate.Value == true)
  {
    _retrievedProject.new_InstallCompleteDate = (DateTime)dtpScheduledDate.SelectedDate;

    saveProject = true;
  }
}

var setProjectComplete = (from wi in _retrievedWorkItemDefinitions
                          where wi.new_SetCompleteDate.HasValue
                          orderby wi.new_ProjectPercentComplete descending
                          select wi).FirstOrDefault();

// If the Work Item Type has "Set Complete Date" = "Yes", set the Project Complete Date to the Work Item's Complete Date.
if (setProjectComplete != null)
{
  if(setProjectComplete.new_SetCompleteDate.Value == true)
  {
    _retrievedProject.new_ProjectCompleteDate = (DateTime)dtpScheduledDate.SelectedDate;

    saveProject = true;
  }
}

if (saveProject)
{
  _context.UpdateObject(_retrievedProject);
  _context.BeginSaveChanges(OnSchedulingComplete_Project, _retrievedProject);
}
else
{
  OnSchedulingComplete_Project(null);
}

This was throwing a permissions error, as if we were trying to update every field on the entity’s form. It also happened in a custom plug-in, where we wanted to update a field on the Account, which was another entity with a Field Security Profile. Again, the field being updated was not the field with the profile:

account.new_IntegrationStatus = new OptionSetValue(1);
context.UpdateObject(account);
context.SaveChanges();

However, if we set the Read permission to “No” on these profiles, the errors went away; they were no longer considered a field available to update. I don’t know if things were set up this way by Microsoft by design or if this is a bug, but, if you run into that vague “Need to start a transaction before commit” message, you may want to consider reviewing any recent security changes. From where I stand, it doesn’t make sense that the system would require a user to have update permissions for a field to make a change to the record, even when they’re not even interested in touching that field at all. Still, it’s another quirk to consider when planning your security and customizations.

Tags: , , , , ,


CRM 2011: Issue with Opening a New Window from Outlook with ADFS Enabled

With CRM 2011, ribbon customization requests are fairly common, and it doesn’t take much to create a custom button that will generate an entity record of some type, then pop open the finished product. It’s just a matter of editing the RibbonDiffXml in an exported solution’s customizations.xml file, pointing the button to a JavaScript web resource, and handling everything else with OData. Once the record’s created and the guid is returned, we simply build the URL and fire off a window.open().

However, while this works just fine when accessing CRM via the browser, there’s an issue if the user is accessing it via the Outlook client when CRM is using ADFS. When the new record is opened, the user is redirected to the ADFS login page. Even if they’ve logged in for one popup, they’ll be prompted again on subsequent popups. This doesn’t happen when the user clicks the out-of-the-box “New” button, so what’s the deal? The Outlook authentication doesn’t pass over automatically with window.open(), so, when in doubt, use what’s already working!

It’s important to note that the following solution is unsupported by Microsoft. This means that a future update could possibly change things in such a way that this fix is no longer valid, so, just like with any other unsupported change to CRM, make a note to test it after updates are applied and always have a backup plan.

With that out of the way, on to openObj(), a function in ..\Microsoft Dynamics CRM\CRMWeb\_static\_common\scripts:

function openObj(type, id, parameters, urlPrefix, mode, extraParams) {
    try {
        var $v_0 = openObject(type, id, parameters, urlPrefix, mode, extraParams);

        if ($v_0 || !$v_0) return $v_0;

        return !IsNull($v_0)
    } catch ($$e_1_0) {
        return false
    }
}


Not much to it and there’s no documentation available to explain what all the parameters are used for, but that’s okay; we’re only concerned with the first two. “type” refers to the entity’s TypeCode and “id” refers to its guid, both of which we should already have available for the window.open() we were previously using. If you’re not sure of what an entity’s TypeCode is, a quick and easy way to get it is to open up a record and use the “Copy a Link” button:





Which should get you something like the following, where you’ll want to be looking at the “etc” number:

[Record Name]
<http://[ServerName]/[OrgName]/main.aspx?etc=10009&extraqs=formid%3d80cb9582-2560-4dad-a0e0-a1acdcb8d114&id=%7bE1134832-40AB-E111-8E5E-005056BC4F66%7d&pagetype=entityrecord>


Or you can use SQL:

SELECT ObjectTypeCode FROM ENTITYVIEW WHERE Name = [EntityName]



Also make sure you include the global.ashx file in the CommandDefinition RibbonDiffXml for the custom button, like in the following example. “FunctionName” is being passed a dummy value, just because we want to include a reference to the resource, but don’t want the button to directly call any function in it:

<CommandDefinitions>
	<CommandDefinition Id="Custom.sse_customerjob.grid.ScheduleAppointment.Command">
		<EnableRules></EnableRules>
		<DisplayRules></DisplayRules>
		<Actions>
			<JavaScriptFunction FunctionName="General_ValidateGuids" Library="/WebResources/sse_NewAppointmentButton">
				<CrmParameter Value="SelectedControlSelectedItemIds"></CrmParameter>
				<StringParameter Value="Customer Job Site" />
			</JavaScriptFunction>
			<JavaScriptFunction FunctionName="isNaN" Library="/_common/global.ashx"></JavaScriptFunction>
		</Actions>
	</CommandDefinition>
</CommandDefinitions>



Once you have that, it’s as simple as replacing the window.open() with:

openObj(typeCode, recordGuid);


Now the new record’s window should pop up without any trouble, no matter how the user is accessing CRM!

Tags: , , , ,


CRM 2011: Error when Deleting an Audited Entity with a Plug-in

Normally, auditing in CRM 2011 doesn’t cause any issues. A user can easily delete an audited entity manually, via OData, or a .NET plug-in. However, I’ve recently run into an issue when attempting to delete during the SetStateDynamicEntity step for the Quote.

The requirement was to delete a set of associated Assets on a closed Quote during the “Activate Quote” process for the revised copy of the closed Quote. My first pass at the deletion code was as simple and straightforward as the following:

public static void DeleteAsset(new_asset assetToDelete)
{
   try
   {
      using (var ctx = new OrgContext(_service))
      {
         ctx.Delete(&quot;new_asset&quot;, assetToDelete.Id);
         ctx.SaveChanges();
      }

   }
   catch (Exception ex)
   {
   }
}

I’ve deleted entities in plug-ins before, but this was throwing an error when saving, for some reason I could not determine. The Microsoft.Crm.AuditMonikerMessagesPlugin was throwing a null reference exception, despite the context and entity object being populated:

The Web Service plug-in failed in OrganizationId: 8fa8e23a-2083-e111-9161-005056bc4f66; SdkMessageProcessingStepId: b92673ed-dc92-442b-a6c6-82f2fce14585; EntityName: new_asset; Stage: 25; MessageName: Delete; AssemblyName: Microsoft.Crm.AuditMonikerMessagesPlugin, Microsoft.Crm.Audit, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35; ClassName: Microsoft.Crm.AuditMonikerMessagesPlugin; Exception: Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.Crm.AuditLib.GetAction(IPluginExecutionContext context, Entity entity, Boolean useParentMessageName)
   at Microsoft.Crm.AuditBase.LogAuditData(IPluginExecutionContext context, Guid objectId, Boolean allowEntityOnlyAudit, Entity entity, EntityMetadata entityMetadata)
   at Microsoft.Crm.AuditMonikerMessagesPlugin.Execute(IServiceProvider serviceProvider)
   at Microsoft.Crm.Extensibility.V5PluginProxyStep.ExecuteInternal(PipelineExecutionContext context)
   at Microsoft.Crm.Extensibility.VersionedPluginProxyStepBase.Execute(PipelineExecutionContext context)
.
.

Doing a bit of Googling, the closest article I could find was “Can’t Delete an Email From Microsoft Dynamics CRM 2011″, where the situation wasn’t quite like mine, but I gave his suggestion a shot anyway and disabled auditing on the Asset entity.

To my surprise, that actually worked perfectly. Unfortunately, while the records were being deleted as expected, we needed auditing to be enabled for the entity, so this wasn’t a suitable fix. In the end, I simply hooked up a JavaScript function to the “Activate Quote” ribbon button that used OData to delete the Asset records.

Tags: , ,


CRM 2011: Custom Reports & the rsProcessingAborted Error

I created a custom report for CRM 2011 in BIDS months ago for a client, but was recently notified that it had stopped working after some significant changes were made to their environment. Out-of-the-box reports would display just fine, but the one I wrote displayed a vague “The report cannot be displayed. (rsProcessingAborted)” message and there was nothing useful in the Event Viewer to shed more light on the problem.

After confirming that the data source and credentials my report was using were still valid, I started poking around and discovered that other custom reports, even those created through the CRM 2011 Report Wizard, were failing with the same error. I tried a few solutions that I found online, but the one that fixed it is covered in When CRM 2011 Custom Reports Don’t Work.

Turns out all I needed to do was look up which account was being used for the CRM Application account by checking the CRMAppPool in IIS, then add an SPN for it with “setspn –a HTTP/NETBIOSNAME DOMAINSERVICEACCOUNT”. Such a quick and easy solution for something I was beginning to worry would end up being a real headache to fix! 

Tags: , , ,


Microsoft CRM 2011: Immediately Open a Record That Was Updated Using oData

This is a quick modification to the Microsoft CRM 2011: Update oData Examples article we posted a while ago. In the event that you would prefer to just pop up a record that you’ve updated or created using oData instead of making your user find it on their own, you can use the AJAX “success” attribute to execute whatever code you want when the update/create is complete.

For instance, in the following “updateContact” function from the previous article, I’ve added code to build a URL, then open a window:

function updateContact(id, contactObject) {

  //Parse the entity object into JSON
  var jsonEntity = window.JSON.stringify(contactObject);

  //Asynchronous AJAX function to Update a CRM record using OData
 $.ajax({
    type: &quot;POST&quot;,
    contentType: &quot;application/json; charset=utf-8&quot;,
    datatype: &quot;json&quot;,
    data: jsonEntity,
    url: Xrm.Page.context.getServerUrl() + &quot;/XRMServices/2011/OrganizationData.svc/ContactSet(guid'&quot; + id + &quot;')&quot;,
    beforeSend: function (XMLHttpRequest) {
      //Specifying this header ensures that the results will be returned as JSON.             
      XMLHttpRequest.setRequestHeader(&quot;Accept&quot;, &quot;application/json&quot;);

      //Specify the HTTP method MERGE to update just the changes you are submitting.             
      XMLHttpRequest.setRequestHeader(&quot;X-HTTP-Method&quot;, &quot;MERGE&quot;);
    },
    success: function (data, textStatus, XmlHttpRequest) {
      if(Xrm.Page.getAttribute('new_opencontactonsave').getValue() == true) {
        var linkUrl = Xrm.Page.context.getServerUrl() + &quot;/main.aspx?etc=2&amp;extraqs=formid%3d894cc46a-b0cb-4ab0-8bf6-200544e46a2d&amp;id=%7b&quot; + id + &quot;%7d&amp;pagetype=entityrecord&quot;;

        var windowWidth = screen.width * .5;
        var windowLeft = windowWidth * .5;

        var windowHeight = screen.height * .75;
        var windowTop = (screen.height - windowHeight) * .5;

        var options = &quot;left=&quot; + windowLeft + &quot;,width=&quot; + windowWidth + &quot;,top=&quot; + windowTop + &quot;,height=&quot; + windowHeight;

        window.open(linkUrl,&quot;_blank&quot;,options);
      }
    }
  });
}

In this case, we’re using the GUID for the Contact we just updated when building the URL, but, if we inserted a Contact record, we could use the result data to find the GUID of the record with something like “data.d.ContactId”. The quickest way to get the values for the other querystring variables, “etc” and “extraqs”, is to open a record of whatever entity you’re interested in, click “Copy a Link”, then pull the values from there. All that’s left is to set the window options that you want and open it up!

Contact Header

The updated CRM 2011 solution file can be downloaded here!

Tags: , , , , ,


Microsoft CRM 2011 oData ExamplesEjemplos de oData en Microsoft CRM 2011

Microsoft CRM has added support for oData in the 2011 release. oData, according to the oData web site “…is a Web protocol for querying and updating data that provides a way to unlock your data and free it from silos that exist in applications today.” What it means is that using an XMLHttpRequest object, you can send a query string to a web service with syntax that is very SQL like and get data back from the web service. It goes beyond that in that it also allows you to update and insert as well. MS CRM did not implement the full oData specification, but it still offers a quite a bit of functionality.

My goal here is to give you two different oData calls to populate some data on a CRM form. I will assume that you have a basic knowledge of CRM, adding JavaScript to forms in 2011 and creating an XMLHttpRequest. Also, I am using jQuery in the form to parse the json that gets returned.

Here’s the scenario that I’m going to implement. On the account main page/detail screen, you need to put data from the contact. I know, I know, contacts appear in a grid, but the customer really wants to see the info for the support contact (a custom attribute I created on the contact) and the most recently created contact at the top of the account screen like this:

Account Screen

Be sure to read the comments in the Javascript – I have a lot of explanations about the oData query in the comments. If you don’t read those, you’ll miss important stuff. Also, remember, this is just a quick intro. Be sure to read the oData URI Conventions to learn more about some of the various query options, too!

I’ve made the solution available for download, but if you want to create the customizations manually, the steps are below.

  1. Create a solution
  2. On the account entity, create the following custom attributes:
    1. new_MostRecentEmail
    2. new_MostRecentFullName
    3. new_MostRecentPhone
    4. new_MostRecentTitle
    5. new_SupportEmail
    6. new_SupportFullName
    7. new_SupportPhone
    8. new_SupportTitle
  3. On the contact entity create the following custom attribute:
    1. new_IsSupportContact
  4. Add two script web resources
    1. Create one and add jQuery (you can download jQuery here) for the code
    2. Create a second resource and add the javascript code further down
  5. Modify the account form to look like the picture above using the fields created above. Make the added fields read-only.
  6. On the account form, add both script resources.
  7. On the form properties, for the form OnLoad event, specify the “onLoad” function in the second script resource you created.
  8. Create a test account and a test contact. Designate one or more contacts as “Is Support Contact”.
  9. Publish your customizations and try it out.
var xp = Xrm.Page;
 
function onLoad(context) {
    var accountId = xp.data.entity.getId();
 
    /*  This is the heart of using oData. This is where the query is defined.
        It has very SQL-like syntax although instead of a "where" clause it
        uses "filter". Also, the comparison operators or different. 'eq' instead
        of '=', 'gt' instead of '>", etc. 
 
        I'm using the oData select keyword to select the four columns I'm interested in.
        Next, I'm using the "top" construct to return a single record.
        After the top is the "orderby" which sorts on "CreatedOn" descending
        so the most recent created record is first. Between the top 1 filter and
        the sort of ModifiedOn descending, the record we want is returned.
 
        Also note that the ParentCustomerId is a complex type, so you have to
        specify 'ParentCustomerId/Id' to navigate from the ParentCustomerId
        to it's child property 'Id' to get to the actual value.*/
        var mostRecentQuery = "/XRMServices/2011/organizationData.svc/ContactSet?$select=FullName,JobTitle,EMailAddress1,Telephone1&$top=1&$orderby=CreatedOn desc&$filter=ParentCustomerId/Id eq guid'" + accountId + "'";
    getContact(mostRecentQuery, "MostRecent");
 
    var supportContactQuery = "/XRMServices/2011/organizationData.svc/ContactSet?$select=FullName,JobTitle,EMailAddress1,Telephone1&$filter=new_IsSupportContact eq true";
    getContact(supportContactQuery, "SupportContact");
}
 
function getContact(oDataQuery, queryName) {
    try {
            var serverUrl;
             /* This is the heart and soul of the whole thing - the actual oData call. */
            serverUrl = Xrm.Page.context.getServerUrl() + oDataQuery;
            var request = new XMLHttpRequest();
            request.open("GET", serverUrl, true);
            request.setRequestHeader("Accept", "application/json");
            request.setRequestHeader("Content-Type", "application/json; charset=utf-8");
            /* Next, we specify the function that we want called when the XMLHttpRequest state
               changes. This will get called on each state change. */
            request.onreadystatechange = function () {
                requestComplete(request, queryName);
            }
            request.send();
    } catch (e) {
        // You probably want to do something other than an alert here.
        alert(e.Description);
    }
}
 
function requestComplete(request, queryName) {
    /*  A request.readyState of 4 means the request is complete. If
        it's complete and the status is 200 (OK), then we can assign the results
        of the call to our controls on the form.*/
    if (request.readyState == 4 && request.status == 200) {
        // debugger;
        var json = $.parseJSON(request.responseText);
        if ( (json != undefined) && (json.d != undefined) && (json.d.results != undefined) && (json.d.results[0] != null) ) {
            json = json.d.results[0];
            // Note that the way things are set up, it's possible to have more
            // than one "Support Contact" returned but I'm hard coding the results
            // to return the first contact only.
            if (queryName == "MostRecent") {
                xp.getAttribute("new_mostrecentfullname").setValue(json.FullName);
                xp.getAttribute("new_mostrecentphone").setValue(json.Telephone1);
                xp.getAttribute("new_mostrecentemail").setValue(json.EMailAddress1);
                xp.getAttribute("new_mostrecenttitle").setValue(json.JobTitle);
                // As an aside (the field below doesn't exist), when placing values
                // into a numeric field, you will need to use the javascript
                // function parseFloat to prevent CRM type errors.
                //xp.getAttribute("new_numericrank").setValue(parseFloat(json.new_NumericRank));
            } else if (queryName == "SupportContact") {
                xp.getAttribute("new_supportfullname").setValue(json.FullName);
                xp.getAttribute("new_supportphone").setValue(json.Telephone1);
                xp.getAttribute("new_supportemail").setValue(json.EMailAddress1);
                xp.getAttribute("new_supporttitle").setValue(json.JobTitle);
            }
        }
    }
}

P.S. – I noticed right before I posted that the “Is Support Contact Query” doesn’t filter by account, so the same contact will appear regardless of what account you’re looking at. Consider it an exercise for the reader to add that filter. :)

Microsoft CRM ha añadido soporte para oData en la nueva version de 2011. oData, de acuerdo con el sitio web oData “… es un protocolo de Internet para consultar y actualizar los datos que proporciona una forma de desbloquear sus datos y liberarla de los silos que existen en las aplicaciones de hoy.” Lo que significa es que el uso de un objeto XMLHttpRequest, que se puede enviar por medio de una cadena a un servicio web con la sintaxis que es muy similar a SQL y obtener datos desde el servicio web. Pero también le permite cambiar e insertar así. MS CRM no aplicó la especificación completa oData, pero aún así ofrece mucha de su funcionalidad.

Mi objetivo aquí es darle dos llamadas diferentes de oData para rellenar algunos datos en un formulario de CRM. Voy a suponer que usted tiene un conocimiento básico de CRM, añadiendo JavaScript a los formularios en 2011 y la creación de un XMLHttpRequest. Además, estoy usando jQuery en la forma para analizar el json que se devuelve.

Este es el escenario que voy a poner en práctica. En la página principal / detalle la pantalla de la cuenta, tienes que poner los datos del contacto. Lo sé, lo sé, los contactos aparecen en una cuadrícula, pero el cliente realmente quiere ver la información por el contacto de apoyo (un atributo personalizado que he creado en el contacto) y el contacto de más recien creado en la parte superior de la pantalla de la cuenta así:

Account Screen

Asegúrese de leer los comentarios en el Javascript – Tengo un montón de explicaciones sobre la consulta oData en los comentarios. Si usted no los lee, va a perder cosas importantes. Además, recuerde, esto sólo es una introducción rápida. Asegúrese de leer las Convenciones de URI de oData para aprender más sobre algunas de las opciones de búsquedas diversas, también!

He hecho la solución disponible para bajar, pero si usted desea crear las personalizaciones manualmente, los pasos son los siguientes.

  • Cree una solución
  • En la entidad “cuenta”, cree los atributos personalizados siguientes:
    1. new_MostRecentEmail
    2. new_MostRecentFullname
    3. new_MostRecentPhone
    4. new_MostRecentTitle
    5. new_SupportEmail
    6. new_SupportFullName
    7. new_SupportPhone
    8. new_SupportTitle
  • En la entidad “contacto”, cree el atributo personalizado siguiente:
    1. new_IsSupportContact
  • Agregue dos recursos de la web script
    1. Cree uno y incluye una referencia a jQuery (se puede bajar jQuery aquí) en el código
    2. Cree un segundo recurso y incluye el código JavaScript más abajo
  • Modifique el formulario “cuenta” para que parezca como la imagen arriba, utilizando los campos creados anteriormente. Que los campos añadido de sólo lectura.
  • En el formulario de cuenta, agregar los recursos de script.
  • En las propiedades del formulario, para el evento OnLoad forma, especifique el “onLoad” en función de los recursos de segundoscript que ha creado.
  • Crear una cuenta de prueba y un contacto de prueba. Designar uno o varios contactos como “¿Contacto de soporte”.
  • Publicar las personalizaciones y probarlo.

var xp = Xrm.Page;
 
function onLoad(context) {
    var accountId = xp.data.entity.getId();
 
    /*  This is the heart of using oData. This is where the query is defined.
        It has very SQL-like syntax although instead of a "where" clause it
        uses "filter". Also, the comparison operators or different. 'eq' instead
        of '=', 'gt' instead of '>", etc. 
 
        I'm using the oData select keyword to select the four columns I'm interested in.
        Next, I'm using the "top" construct to return a single record.
        After the top is the "orderby" which sorts on "CreatedOn" descending
        so the most recent created record is first. Between the top 1 filter and
        the sort of ModifiedOn descending, the record we want is returned.
 
        Also note that the ParentCustomerId is a complex type, so you have to
        specify 'ParentCustomerId/Id' to navigate from the ParentCustomerId
        to it's child property 'Id' to get to the actual value.*/
        var mostRecentQuery = "/XRMServices/2011/organizationData.svc/ContactSet?$select=FullName,JobTitle,EMailAddress1,Telephone1&$top=1&$orderby=CreatedOn desc&$filter=ParentCustomerId/Id eq guid'" + accountId + "'";
    getContact(mostRecentQuery, "MostRecent");
 
    var supportContactQuery = "/XRMServices/2011/organizationData.svc/ContactSet?$select=FullName,JobTitle,EMailAddress1,Telephone1&$filter=new_IsSupportContact eq true";
    getContact(supportContactQuery, "SupportContact");
}
 
function getContact(oDataQuery, queryName) {
    try {
            var serverUrl;
             /* This is the heart and soul of the whole thing - the actual oData call. */
            serverUrl = Xrm.Page.context.getServerUrl() + oDataQuery;
            var request = new XMLHttpRequest();
            request.open("GET", serverUrl, true);
            request.setRequestHeader("Accept", "application/json");
            request.setRequestHeader("Content-Type", "application/json; charset=utf-8");
            /* Next, we specify the function that we want called when the XMLHttpRequest state
               changes. This will get called on each state change. */
            request.onreadystatechange = function () {
                requestComplete(request, queryName);
            }
            request.send();
    } catch (e) {
        // You probably want to do something other than an alert here.
        alert(e.Description);
    }
}
 
function requestComplete(request, queryName) {
    /*  A request.readyState of 4 means the request is complete. If
        it's complete and the status is 200 (OK), then we can assign the results
        of the call to our controls on the form.*/
    if (request.readyState == 4 && request.status == 200) {
        // debugger;
        var json = $.parseJSON(request.responseText);
        if ( (json != undefined) && (json.d != undefined) && (json.d.results != undefined) && (json.d.results[0] != null) ) {
            json = json.d.results[0];
            // Note that the way things are set up, it's possible to have more
            // than one "Support Contact" returned but I'm hard coding the results
            // to return the first contact only.
            if (queryName == "MostRecent") {
                xp.getAttribute("new_mostrecentfullname").setValue(json.FullName);
                xp.getAttribute("new_mostrecentphone").setValue(json.Telephone1);
                xp.getAttribute("new_mostrecentemail").setValue(json.EMailAddress1);
                xp.getAttribute("new_mostrecenttitle").setValue(json.JobTitle);
                // As an aside (the field below doesn't exist), when placing values
                // into a numeric field, you will need to use the javascript
                // function parseFloat to prevent CRM type errors.
                //xp.getAttribute("new_numericrank").setValue(parseFloat(json.new_NumericRank));
            } else if (queryName == "SupportContact") {
                xp.getAttribute("new_supportfullname").setValue(json.FullName);
                xp.getAttribute("new_supportphone").setValue(json.Telephone1);
                xp.getAttribute("new_supportemail").setValue(json.EMailAddress1);
                xp.getAttribute("new_supporttitle").setValue(json.JobTitle);
            }
        }
    }
}

PD - Me di cuenta justo antes de que he publicado que la “consulta es Comuníquese con soporte” no se filtra por cuenta, por lo que el mismo contacto aparecerá independientemente de lo que cuenta en la que estamos viendo. Consideran que es un ejercicio para el lector agregar el filtro.

Tags: , , ,