Friday, July 29, 2011

Does anybody know of good (or any) Microsoft Dynamics Crm 2011 developers book?



Does anybody know of good (or any) Microsoft Dynamics Crm 2011 developers book?
I'm managing just fine till now with the SDK and everything, but still it will be great if there was a book that speaks the humans language and gives real perspective (in appose to Microsoft perspective) on developing for Dynamics CRM.
There's this list http://blogs.msdn.com/b/crm/archive/2011/02/01/microsoft-dynamics-crm-2011-book-club.aspx but (if I'm not mistaken) none of the developers books came out yet.
Please comment when you find one...

Wednesday, July 27, 2011

MSCRM 4.0 n:n lookup field

We had customer, who wanted to have n:n lookup field in MSCRM 4.0.
This is unsupported solution, But Cool :)
That means he wanted to have a lookup in the field that acts like the "To" lookup in email activity but for all types of entities.

Walkthrough:
1) Create n:n relationship between the entities
For holding the actual n:n records.
2) Create n:1 relationship between the entities and place the lookup in the form.
It's just a dummy lookup we won't use it to really old data just do all the UI work for us.
3) Add JavaScript to the onload event:
var nnId = "new_account_contact"; // entity N:N relationship id

RetreiveAssociatedEntities = function(relationshipSchemaName, entity1SchemaName, entity1KeyValue, retreiveAttribute) {
    var fetchXml = "<fetch mapping='logical'>"
    + "  <entity name='" + relationshipSchemaName + "'>"
    + "    <all-attributes />"
    + "    <filter>"
    + "      <condition attribute='" + entity1SchemaName + "id' operator='eq' value ='" + entity1KeyValue + "' />"
    + "    </filter>"
    + "  </entity>"
    + "</fetch>";

    var fetchResults = Fetch(fetchXml);

    var nodeList = fetchResults.selectNodes("resultset/result");

    var returnList = new Array();
    if (nodeList == null || nodeList.length == 0) {
        return returnList;
    } else {
        for (i = 0; i < nodeList.length; i++) {
            var idValue = nodeList[i].selectSingleNode('./' + retreiveAttribute).nodeTypedValue;
            returnList[i] = idValue;
        }
        return returnList;
    }
}
MischiefMayhemSOAP = function(serviceUrl, xmlSoapBody, soapActionHeader, suppressError) {
    var xmlReq = "<?xml version='1.0' encoding='utf-8'?>"
    + "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'"
    + "  xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'"
    + "  xmlns:xsd='http://www.w3.org/2001/XMLSchema'>"
    + GenerateAuthenticationHeader()
    + "  <soap:Body>"
    + xmlSoapBody
    + "  </soap:Body>"
    + "</soap:Envelope>";

    var httpObj = new ActiveXObject("Msxml2.XMLHTTP");

    httpObj.open("POST", serviceUrl, false);

    httpObj.setRequestHeader("SOAPAction", soapActionHeader);
    httpObj.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    httpObj.setRequestHeader("Content-Length", xmlReq.length);

    httpObj.send(xmlReq);

    var resultXml = httpObj.responseXML;

    var errorCount = resultXml.selectNodes("//error").length;
    if (errorCount != 0) {
        var msg = resultXml.selectSingleNode("//description").nodeTypedValue;

        if (typeof (suppressError) == "undefined" || suppressError == null) {
            alert("The following error was encountered: " + msg);
        }

        return null;
    } else {
        return resultXml;
    }
}

Fetch = function(fetchXml) {
    var xmlSoapBody = "<Fetch xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>"
    + "  <fetchXml>"
    + FetchEncode(fetchXml)
    + "  </fetchXml>"
    + "</Fetch>";

    var fetchResponse = MischiefMayhemSOAP("/MSCRMServices/2007/CrmService.asmx", xmlSoapBody, "http://schemas.microsoft.com/crm/2007/WebServices/Fetch");

    if (fetchResponse != null) {
        var fetchResults = new ActiveXObject("Msxml2.DOMDocument");

        fetchResults.async = false;
        fetchResults.resolveExternals = false;
        fetchResults.loadXML(fetchResponse.text);

        return fetchResults;
    } else {
        return null;
    }
}
FetchEncode = function(strInput) //_HtmlEncode
{
    var c;
    var HtmlEncode = '';

    if (strInput == null) {
        return null;
    }
    if (strInput == '') {
        return '';
    }

    for (var cnt = 0; cnt < strInput.length; cnt++) {
        c = strInput.charCodeAt(cnt);

        if (((c > 96) && (c < 123)) ||
  ((c > 64) && (c < 91)) ||
  (c == 32) ||
  ((c > 47) && (c < 58)) ||
  (c == 46) ||
  (c == 44) ||
  (c == 45) ||
  (c == 95)) {
            HtmlEncode = HtmlEncode + String.fromCharCode(c);
        }
        else {
            HtmlEncode = HtmlEncode + '&#' + c + ';';
        }
    }

    return HtmlEncode;
}

FillMultiLookup = function() {
    debugger;
    var contacts = RetreiveAssociatedEntities(nnId, "account", crmForm.ObjectId, "contactid");
    var value = new Array();
    for (var i = 0; i < contacts.length; i++) {
        value[i] = new Object();
        value[i].id = contacts[i];
        value[i].name = RetreiveAssociatedEntities("contact", "contact", contacts[i], "fullname")[0];
        value[i].typename = "contact";
    }
    crmForm.all.new_contactn2nid.DataValue = value;
    return value;
}

AssociateEntities = function(moniker1name, moniker1id, moniker2name, moniker2id, RelationshipName) {
    var authenticationHeader = GenerateAuthenticationHeader();
    // Prepare the SOAP message.
    var xml = "<?xml version='1.0' encoding='utf-8'?>";
    xml += "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance\' xmlns:xsd='http://www.w3.org/2001/XMLSchema\'>";
    xml += authenticationHeader;
    xml += "<soap:Body><Execute xmlns='http://schemas.microsoft.com/crm/2007/WebServices'><Request xsi:type='AssociateEntitiesRequest'>";
    xml += "<Moniker1><Id xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>" + moniker1id + "</Id>";
    xml += "<Name xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>" + moniker1name + "</Name></Moniker1>";
    xml += "<Moniker2><Id xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>" + moniker2id + "</Id>";
    xml += "<Name xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>" + moniker2name + "</Name></Moniker2>";
    xml += "<RelationshipName>" + RelationshipName + "</RelationshipName>";
    xml += "</Request></Execute></soap:Body></soap:Envelope>";

    // Prepare the xmlHttpObject and send the request.
    var xHReq = new ActiveXObject("Msxml2.XMLHTTP");
    xHReq.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
    xHReq.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/Execute");
    xHReq.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    xHReq.setRequestHeader("Content-Length", xml.length);
    xHReq.send(xml);

    // Capture the result.
    var resultXml = xHReq.responseXML;

    // Check for errors.
    var errorCount = resultXml.selectNodes('//error').length;

    if (errorCount != 0) {
        var msg = resultXml.selectSingleNode('//description').nodeTypedValue;
        alert(msg);
    }
}

DisassociateEntities = function(moniker1name, moniker1id, moniker2name, moniker2id, RelationshipName) {
    var authenticationHeader = GenerateAuthenticationHeader();
    // Prepare the SOAP message.
    var xml = "<?xml version='1.0' encoding='utf-8'?>";
    xml += "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance\' xmlns:xsd='http://www.w3.org/2001/XMLSchema\'>";
    xml += authenticationHeader;
    xml += "<soap:Body><Execute xmlns='http://schemas.microsoft.com/crm/2007/WebServices'><Request xsi:type='DisassociateEntitiesRequest'>";
    xml += "<Moniker1><Id xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>" + moniker1id + "</Id>";
    xml += "<Name xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>" + moniker1name + "</Name></Moniker1>";
    xml += "<Moniker2><Id xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>" + moniker2id + "</Id>";
    xml += "<Name xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>" + moniker2name + "</Name></Moniker2>";
    xml += "<RelationshipName>" + RelationshipName + "</RelationshipName>";
    xml += "</Request></Execute></soap:Body></soap:Envelope>";

    // Prepare the xmlHttpObject and send the request.
    var xHReq = new ActiveXObject("Msxml2.XMLHTTP");
    xHReq.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
    xHReq.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/Execute");
    xHReq.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    xHReq.setRequestHeader("Content-Length", xml.length);
    xHReq.send(xml);

    // Capture the result.
    var resultXml = xHReq.responseXML;

    // Check for errors.
    var errorCount = resultXml.selectNodes('//error').length;

    if (errorCount != 0) {
        var msg = resultXml.selectSingleNode('//description').nodeTypedValue;
        alert(msg);
    }
}

//returns the item location in array if not found -1
GetIndexFromArray = function(item, recordArr) {
    for (var i = 0; i < recordArr.length; i++) {
        if (recordArr[i] != null && recordArr[i].id == item) {
            return i;
        }
    }
    return -1;
}

UpdateN2N = function() {
    debugger;
    var oldValues = RetreiveAssociatedEntities(nnId, "account", crmForm.ObjectId, "contactid");
    //if there's records in lookup
    if (crmForm.all.new_contactn2nid.DataValue != null) {
        //go over all the related records and remove them if not in the new list (lookup)
        var temp = crmForm.all.new_contactn2nid.DataValue;
        for (var i = 0; i < oldValues.length; i++) {
            //if not in the new list disassociate them
            var index = GetIndexFromArray(oldValues[i], temp);
            if (index == -1) {
                DisassociateEntities("account", crmForm.ObjectId, "contact", oldValues[i], nnId);
            }
            else { // if in the list remove them from the list
                temp[index] = null;
            }
        } //ends for
        //go over all the remaining records and associate them
        for (var i = 0; i < temp.length; i++) {
            if (temp[i] != null) {
                AssociateEntities("account", crmForm.ObjectId, "contact", temp[i].id, nnId);
            }
        }
    }
    else if (oldValues != null) {
        for (var i = 0; i < oldValues.length; i++) {
            DisassociateEntities("account", crmForm.ObjectId, "contact", oldValues[i], nnId);
        }
    }
}

document.getElementById("new_contactn2nid").setAttribute("lookupstyle", "multi");
FillMultiLookup();
4)Add JavaScript to the onchange event:
UpdateN2N();
5) Add JavaScript to the onsave event:
crmForm.all.new_contactn2nid.DefaultValue = crmForm.all.new_contactn2nid.DataValue;
crmForm.all.new_contactn2nid.DataValue = crmForm.all.new_contactn2nid.DefaultValue;
crmForm.all.new_contactn2nid.DataValue = null;
Description: First of all we made the lookup multi by changing lookstyle attribute.
document.getElementById("new_contactn2nid").setAttribute("lookupstyle","multi");
Next we filled the lookup with the n:n relationship values.
UpdateN2N();
After the user changes the values in the lookup we update the real n:n relationship The last obstacle was to prevent the form from updating the lookup with the multi records in it, otherwise it will throw an error and won't update. This is achieved by setting the default value of the lookup to current value, setting it back the default to the current and then removing everything from it.
crmForm.all.new_contactn2nid.DefaultValue = crmForm.all.new_contactn2nid.DataValue;
crmForm.all.new_contactn2nid.DataValue = crmForm.all.new_contactn2nid.DefaultValue;
crmForm.all.new_contactn2nid.DataValue = null;
Thanks to http://crmentropy.blogspot.com/2010/09/nn-relationship-utility-code-javascript.html For the AssociateEntities, DisassociateEntities, RetreiveAssociatedEntities  functions helping dealing with the Many-to-Many relationship.

Thursday, July 21, 2011

MSCRM 2011 early binding plugin

Checked on MSCRM 2011 on premise and online.

*** I think it's important to understand what is going behind the scenes, here's easier option for creating plugin with early binding. It's done using Dynamics CRM 2011 developer toolkit.
http://dynamicslollipops.blogspot.com/2012/02/mscrm-2011-using-early-binding-in.html

When i've tried to follow the sdk:

"Walkthrough: Build a Plug-in That Connects to Microsoft Dynamics CRM 2011 Using Developer Extensions"
I've ended up looking the web for a way to register plugin which refernced Microsoft.Xrm.Client.dll,
The plugin registration tool kept given me errors because it couldn't find Microsoft.Xrm.Client.dll
Could not load file or assembly 'Microsoft.Xrm.Client, Version=5.0.9688.1154, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. The system cannot find the file specified.

All the solution in the web suggested to use ILMerge for merging the plugin assembly with the 


Microsoft.Xrm.Client.dll and many other not intuitive solutions.
WTF?!?! There most be easier why to enjoy the early binding with out all this mess.

It's possible, just follow these steps:

1) create the xrm class using CrmSvcUtil
Example:
CrmSvcUtil.exe /out:Xrm.cs /url:http://crm2011:5555/Basic/XRMServices/2011/Organization.svc /username:yyyyy /password:xxxxx /namespace:Xrm/serviceContextName:XrmServiceContext 
2) Add the created file to the project
3) Add reference to the project
microsoft.xrm.sdk.dll
microsoft.crm.sdk.proxy.dll
4) inherit IPlugin

This is it.

this is how you get the context:
IPluginExecutionContext context = (IPluginExecutionContext)
serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory serviceFactory =
                    (IOrganizationServiceFactory)serviceProvider.GetService(
                typeof(IOrganizationServiceFactory));
                IOrganizationService service =
                serviceFactory.CreateOrganizationService(context.UserId);

                XrmServiceContext xrm = new XrmServiceContext(service);
Example for the plugin:
public class SLAPlugin : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            IPluginExecutionContext context = (IPluginExecutionContext)
serviceProvider.GetService(typeof(IPluginExecutionContext));
            Entity entity;
            // Check if the input parameters property bag contains a target
            // of the create operation and that target is of type Entity.
            if (context.InputParameters.Contains("Target") &&
            context.InputParameters["Target"] is Entity)
            {
                // Obtain the target business entity from the input parameters.
                entity = (Entity)context.InputParameters["Target"];

                // Verify that the entity represents a contact.
                if (entity.LogicalName != Incident.EntityLogicalName) { return; }
            }
            else
            {
                return;
            }

            try
            {
                IOrganizationServiceFactory serviceFactory =
                    (IOrganizationServiceFactory)serviceProvider.GetService(
                typeof(IOrganizationServiceFactory));
                IOrganizationService service =
                serviceFactory.CreateOrganizationService(context.UserId);

                XrmServiceContext xrm = new XrmServiceContext(service);
                
                // Extract the tracing service.
                ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
                if (tracingService == null){
                    throw new InvalidPluginExecutionException("Failed to retrieve the tracing service.");
                }

                if(!context.PostEntityImages.Contains("MyImage")){
                    throw new InvalidPluginExecutionException("Failed to retrieve the Post Image.");
                }

                Entity postEntity = context.PostEntityImages["MyImage"];
                Incident caseTemp = postEntity.ToEntity<Incident>();

                if (caseTemp != null)
                {
                    //only if missing active conditionset start looking for one
                    if (caseTemp.new_ActiveCoditionSetId == null)
                    {
                        tracingService.Trace("Start looking for conditionset");
                        MSCRM2011SLAProcessWorker a = new MSCRM2011SLAProcessWorker();
                        a.GetConditionSetAndUpdateCase(xrm, tracingService,caseTemp);
                    }
                }
            }
            catch (FaultException<OrganizationServiceFault> ex)
            {
                throw new InvalidPluginExecutionException(
                "An error occurred in the plug-in.", ex);
            }
        }
    }
Updating newly created entity that is not in the context
//create update case
Incident caseToUpdate = new Incident
{
 IncidentId = activeCase.IncidentId
};
//update the update case
xrm.Attach(caseToUpdate);
xrm.UpdateObject(caseToUpdate);
xrm.SaveChanges();
    
Hope it helps...

Sunday, July 17, 2011

Export Import user saved views Microsoft Dynamics Crm 2011

Export Import user saved views Microsoft Dynamics Crm 2011.
Because user can't "move" his saved views from one environment to another, I've create Silverlight application that does it.

The application can be downloaded from here:
http://exportimportuserview.codeplex.com/releases

Prerequisites:
     1) The "SLExportImportUserViews" solution installed on both environments.

Instructions:
     1) Go to the first environment.
     2) Open the HTML Web resource and preview the page.
     3) Export your saved views to xml file.
     4) Go to the other environment.
     5) Open the Silverlight application.
     6) Open the HTML Web Resource and preview the page.
     7) Go to the Import Tab.
     8) Click on ImportXML.
     9) Choose the xml file.
    10)  Click on Import all to crm.

Draw backs:
    1) Because it's odate I can't retrieve other users saved views and I can't assign the saved views to another user from the Silverlight application.
        *Can be fixed by using the wcf service instaed of the Odata or after importing the views assign them from the advanced find window
    2) All the saved views in the grid Exported to the xml file.
    3) All the saved views in the grid Imported to the CRM.
        *Can easily be fixed by exporting and importing only selected items.

The application can be downloaded from here:
http://exportimportuserview.codeplex.com/releases

Tuesday, July 5, 2011

MSCRM 2011 Mapping Opportunity Product to quote detail

This article is based on a blog about changing the opportunity product mapping to quote product in MSCRM 4.0
http://crmconsultancy.wordpress.com/tailoring-the-product-selection-for-opportunities-and-quotes-in-dynamics-crm/productcataloguemapping-mscrm-fields-from-opportunity-product-to-quote-product/

When i had to change the mapping in MSCRM 2011 Online I had a problem getting the mapping id because MSSQL is not reachable in ONLINE version.
The easiest way to get the mapping Guid was using the OData service.
1)* Go to the link SERVER URL/XRMServices/2011/OrganizationData.svc/EntityMapSet()

2) Find the Guid and saved it somewhere.
3) Go to any other relationship in QuoteDetail in your crm (If you can't see the url bar click ctrl+n)
4) Open the Mapping and get by using the developer tool it's url.
Example: mappings/mappingList.aspx?mappingId=%7b5019CFAA-7CA1-E011-8EE6-18A905732A15%7d
5) Replace the Guid from the last part with the mappingId
6) Open the new link in the browser
The link should look like
SERVER URL/tools/systemcustomization/relationships/mappings/mappingList.aspx?mappingId=%7b5019CFAA-7CA1-E011-8EE6-18A905732A15%7d


*It is also possible getting the Guid using ODataTool solution that is also very useful for other purposes.


Hope it helps..