Tuesday, December 13, 2011

Using tinyMCE in Dynamics CRM 2011

Using tinyMCE in Dynamics CRM 2011

Based on htmlbox - dynamics crm 2011 integretion described in here:

https://community.dynamics.com/product/crm/crmnontechnical/b/crmsoftwareblog/archive/2011/06/27/adding-wysiwyg-editor-to-textarea-in-microsoft-dynamics-crm-part-2-crm-2011.aspx

There are other tinyMCE - Dynamics crm 2011 integrations on the web for exmaple:
http://procentrix.com/Community/BrendenS/post.aspx?ID=13

IMPORTANT: This solution is unsupported.

Dynamics crm TinyMCE

Walkthrough:
  1. Get the minified Jquery from
    http://jquery.com/
  2. Get the tinyMCE
    http://www.tinymce.com/download/download.php
  3. Use the web resource util as described here
    http://blog.customereffective.com/blog/2011/06/using-crm-2011-web-resource-utility.html
  4. Add the jquery and tinymce to the entity form
  5. Create new web resource with the following code and add it to the form
    
    var textAreaName = "new_description";
    
    function onLoad(textarea)
    {
     textAreaName = textarea;
     Xrm.Page.getAttribute(textAreaName).setSubmitMode("always");
     //Xrm.Page.getControl("new_description").setVisible(false);
     //$('#new_description').css("width", "90%").css("height", "10%");
     $('#' + textAreaName + '_d').append('<textarea id="wysiwyg" name="wysiwyg" style="width:90%;height:90%;">' + $("#" + textAreaName).val() + '</textarea>');
     $('#' + textAreaName).hide();
    
     tinyMCE.init({
      mode : "exact",
      elements : "wysiwyg",
      theme : "advanced",
      plugins : "autolink,lists,pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,iespell,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template,inlinepopups,autosave",
      theme_advanced_buttons1 : "newdocument,|,bold,italic,underline,|,justifyleft,justifycenter,justifyright,fontselect,fontsizeselect,formatselect",
      theme_advanced_buttons2 : "cut,copy,paste,|,bullist,numlist,|,outdent,indent,|,undo,redo,|,link,unlink,anchor,image,|,code,preview,|,forecolor,backcolor",
      theme_advanced_buttons3 : "insertdate,inserttime,|,spellchecker,advhr,,removeformat,|,sub,sup,|,charmap,emotions", 
      theme_advanced_toolbar_location : "top",
      theme_advanced_toolbar_align : "left",
      theme_advanced_statusbar_location : "bottom",
      theme_advanced_resizing : true,
      onchange_callback : "myCustomOnChangeHandler"
     });
    }
    
    function myCustomOnChangeHandler(inst) {
      if(inst.isDirty()) {
       inst.save();
      }
      Xrm.Page.getAttribute(textAreaName).setValue($('#wysiwyg').val());
      return true;
    }
    
    function onSave()
    {
     for (var i=0; i<tinymce.editors.length; i++) {
      tinyMCE.execCommand('mceRemoveControl',false, tinymce.editors[i].id); 
     };
     $('#wysiwyg').remove();
    }
    
  6. Add new onload event: function name: onLoad Parameter: "the attribute schema name"
  7. Add new onsave event: function name: onSave 
  8. Save and publish...


You can download an example here (Replaces the entity task!):
unmanaged solution:
managed solution:


Monday, December 12, 2011

MSCRM 2011 performance and mcafee

We had major performance issues with Microsoft Dynamics CRM 2011 on premise.
The main bottleneck for the performance was not the latency or Javascripts but the Mcafee antivirus installed on the clients computers.
More information about the problem and the solution can be found in the following links:
http://support.microsoft.com/kb/924341
http://community.dynamics.com/product/crm/crmtechnical/b/crminthefield/archive/2011/01/24/anti-virus-exclusions-for-microsoft-dynamics-crm.aspx
http://blog.customereffective.com/blog/2008/08/disable-mcafee.html

Monday, November 28, 2011

Dynamics crm 2011 n to n lookup

N:N lookup in Dynamics CRM 2011.

For to 4.0 version: http://dynamicslollipops.blogspot.com/2011/07/mscrm-40-nn-lookup-field.html

Prerequest customization:
1) 1:N relation, and lookup placed on the form.
2) N:N relationship for holding the values.

Walkthrough:
1) Create new javascript webresource with the following code:

var CRM_FORM_TYPE_CREATE = 1;

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;
}

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;
}

/*"new_new_entity1_new_entity2new","new_multynewid", "new_entity2", "new_name"*/
FillMultiLookup = function(relationshipSchemaName, lookupSchemaName, relatedEntitySchemaName, relatedEntityPrimaryAttributeSchemaName) {
    var relatedValues = RetreiveAssociatedEntities(relationshipSchemaName, Xrm.Page.data.entity.getEntityName(), Xrm.Page.data.entity.getId(), relatedEntitySchemaName + "id");
    var value = new Array();
    for (var i = 0; i < relatedValues.length; i++) {
        value[i] = new Object();
        value[i].id = relatedValues[i];
        value[i].name = RetreiveAssociatedEntities(relatedEntitySchemaName, relatedEntitySchemaName, relatedValues[i], relatedEntityPrimaryAttributeSchemaName)[0];
        value[i].typename = relatedEntitySchemaName;
    }
 crmForm.all[lookupSchemaName].DataValue = value;
}

UpdateN2N = function(nnId, relatedEntitySchemaName, relatedEntitySchemaId, lookupSchemaName) {
    var oldValues = RetreiveAssociatedEntities(nnId, Xrm.Page.data.entity.getEntityName(), Xrm.Page.data.entity.getId(), relatedEntitySchemaId);
 var value = crmForm.all[lookupSchemaName].DataValue;
    //if there's records in lookup
    if (value != null) {
        //go over all the related records and remove them if not in the new list (lookup)
        var temp = value;
        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(Xrm.Page.data.entity.getEntityName(), Xrm.Page.data.entity.getId(), relatedEntitySchemaName, 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(Xrm.Page.data.entity.getEntityName(), Xrm.Page.data.entity.getId(), relatedEntitySchemaName, temp[i].id, nnId);
            }
        }
    }
    else if (oldValues != null) {
        for (var i = 0; i < oldValues.length; i++) {
            DisassociateEntities(Xrm.Page.data.entity.getEntityName(), Xrm.Page.data.entity.getId(), relatedEntitySchemaName, oldValues[i], nnId);
        }
    }
}

FilterN2NLookup = function(filterByLookup, filterLookup, entityName, nnName, nnFrom, linkedEntityName, linkedAttributeName) {
    var filter = '';
    var values = filterByLookup.DataValue;
    if (values != null) {
        //filter = '<filters><filter entity="' + entityName + '"><condition attribute="' + attributeName + '" operator="in">';
        //for (var i = 0; i < values.length; i++) {
        //    filter += '<value uiname="' + values[i].name + '">' + values[i].id + '</value>';
        // }
        //filter += '</condition></filter></filters>';

        filter = '<link-entity entity="' + entityName + '" name="' + nnName + '" from="' + nnFrom + '" to="' + nnFrom + '" visible="false" intersect="true"><link-entity name="' + linkedEntityName + '" from="' + linkedAttributeName + '" to="' + linkedAttributeName + '"><filter type="and"><condition attribute="' + linkedAttributeName + '" operator="in">';
        for (var i = 0; i < values.length; i++) {
            filter += '<value uiname="' + values[i].name + '">' + values[i].id + '</value>';
        }
        filter += '</condition></filter></link-entity></link-entity>';

    }
    filterLookup.AddParam('filters', filter);
}

/*"new_new_entity1_new_entity2new","new_multynewid", "new_entity2", "new_name"*/
function ConvertN2N(relationshipSchemaName, lookupSchemaName, relatedEntitySchemaName, relatedEntityPrimaryAttributeSchemaName)
{
 document.getElementById(lookupSchemaName).setAttribute("lookupstyle", "multi");
 document.getElementById(lookupSchemaName).setAttribute("_lookupstyle", "multi");

 document.getElementById(lookupSchemaName).onchange = function(){UpdateN2N(relationshipSchemaName, relatedEntitySchemaName, relatedEntitySchemaName + "id", lookupSchemaName)};

 Xrm.Page.getAttribute(lookupSchemaName).setSubmitMode("never");

 if (crmForm.FormType != CRM_FORM_TYPE_CREATE) {
  FillMultiLookup(relationshipSchemaName, lookupSchemaName, relatedEntitySchemaName, relatedEntityPrimaryAttributeSchemaName);
 }
 else {
  Xrm.Page.getControl(lookupSchemaName).setDisabled(true);
 }
}

2) Add it to the form
3) Add to the onload 
   3.1) Function: ConvertN2N
   3.2) Parameters: N:N Relationship name, lookup attribute name, Related Entity Schema Name, Related Entity Primary Attribute Schema Name
(Example: "new_incident_new_problem", "new_problemid", "new_problem", "new_name")



That's it, everything else is inside the code. 
Plug and Play.

Sunday, November 27, 2011

Microsoft Dynamics CRM 2011 Performs better on IE 9

Microsoft Dynamics CRM 2011 Performs better on IE 9.
After few tests we ran on Microsoft Dynamics CRM 2011 vanilla, we have found that IE 9 performance improves by 1-2~ sec per form.

Open Create Account form:
IE 8  - 3~ sec.
IE 9 - 1-1.5 ~ sec.

Open update Account form
IE 8 - 3~ sec.
IE 9 - 1-1.5 ~ sec.

* Average time for 3 tests.

Share your results

Taken form http://xkcd.com/979/

Sunday, November 20, 2011

Microsoft Dynamics CRM 2011 crmForm workaround


There is a problem when checking attributes value, after the user clicks on ribbon button without losing focus of the attribute.

Description:

1) I've register the ribbon button event to something like:
if(Xrm.Page.getAttribute("new_test").getValue() != null)
{
alert("aaaaa");
2) User clicks the ribbon button when the attribute is empty
3) Get's alert
4) User insert text to the attribute and clicks again 
5) Get's alert

When user isn't getting out (clicks out side) of the attribute's textbox the value, gets from Xrm.Page.getAttribute("xxxx").getValue(), remains the old value but if he does (just click out side the textbox) everything works.

After changing the code to the old (CRM 4) javascript syntact:
if(crmForm.all.new_test.DataValue != null)
{
     alert("AAAA");
}

Everything worked great! Even when the focus still on the textbox.

This is a workaround.

Tuesday, November 15, 2011

MSCRM 2011 Set Guid (Any Entity Id) Plugin

MSCRM 2011 Set Guid (Any Entity Id) Plugin

http://dynamicslollipops.blogspot.com/2011/10/mscrm-2011-workflow-assembly-get.html

Because it's impossiable to use workflow assembly in Microsoft Dynamics CRM 2011,
This is the piece of code needed for creating plugin that's sets any entity GUID into any test field you choose.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xrm.Sdk;

namespace MSCRM2011SetGuidPlugin
{
    public class MSCRM2011SetGuidPlugin : IPlugin
    {
        public readonly string rAttributeSchemaName = "new_guid";
        public string m_SecureConfig { get; set; }
        public string m_Config { get; set; }

        public MSCRM2011SetGuidPlugin(string i_Config, string i_SecureConfig)
        {
            m_Config = i_Config;
            m_SecureConfig = i_SecureConfig;
        }


        /// <summary>
        /// A plugin that creates a follow-up task activity when a new account is created.
        /// </summary>
        /// <remarks>Register this plug-in on the Create message, account entity,
        /// and asynchronous mode.
        /// </remarks>
        public void Execute(IServiceProvider serviceProvider)
        {
            //Extract the tracing service for use in debugging sandboxed plug-ins.
            ITracingService tracingService =
                (ITracingService)serviceProvider.GetService(typeof(ITracingService));

            // Obtain the execution context from the service provider.
            IPluginExecutionContext context = (IPluginExecutionContext)
                serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

            tracingService.Trace("Config: " + m_Config);
            string attr = rAttributeSchemaName;
            if (!String.IsNullOrEmpty(m_Config))
            {
                attr = m_Config;
            }
            tracingService.Trace("attr: " + attr);
            tracingService.Trace("id: " + context.PrimaryEntityId);
            Guid id = context.PrimaryEntityId;

            Entity entity = new Entity(context.PrimaryEntityName);
            entity.Id = id;
            entity.Attributes.Add(attr, id.ToString());
            service.Update(entity);
        }
    }

}

If no attribute is in the configuration, then it tries to place the GUID into new_guid.

The complate code project and dll

Friday, November 4, 2011

MSCRM 2011 Debug plugin

I've just read it and wanted to share it.
http://msdn.microsoft.com/en-us/library/hh372952.aspx

It helps debuging plugins even when the Dynamics CRM is ONLINE.
It is great for unit testing.

It is what i was looking for.

Thursday, November 3, 2011

Dynamics CRM 2011 record hyperlink

Dynamics CRM 2011 record hyperlink.

Finally
Microsoft added Insert Hyperlink to send email / email templates in MSCRM 2011 Rollup 5.

The workarounds are finally uneeded anymore.

Walkthrough:
1) Go to send email in workflow
2) Insert text for display
3) insert the Record URLto the url field.
4) Click on OK.



Monday, October 31, 2011

MSCrm 2011 - Workflow Assembly Get Current Entity ID


MSCrm 2011 - Workflow Assembly Get Current Entity ID

This is a workflow assembly that returns the current record ID (Guid) in Dynamics crm 2011.
There is only one drawback, It won't work in Dynamics CRM 2011 ONLINE because workflow assemblies aren't supported there.

Code:

    public class GetEntityId : CodeActivity
    {
        protected override void Execute(CodeActivityContext executionContext)
        {
            //Create the tracing service
            ITracingService tracingService = executionContext.GetExtension<ITracingService>();

            IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();
            EntityId.Set(executionContext, context.PrimaryEntityId.ToString());
        }

        [Output("Entity Id")]
        public OutArgument<string> EntityId { get; set; }
    }


Walkthrough:
1) Copy the .dll from the bin folder to <%CRM folder%>\Server\bin\assembly
2) Use the plugin registration tool to register the workflow assembly
3) Now you should be able to see it in the Workflow window:


4) After you added it to the Workflow you can use it:



Download Link: https://docs.google.com/open?id=0B8k6R6QcCN7INjU2ZjIwMGQtYTBjYy00MmQ1LWI1ZWQtODg0OWViYmQ3YzRl


Dynamics CRM 2011 ONLINE users walkaround:
Create plugin on post create step insert the id to some text field.
The plugin has advantage, as it will work on all environments

Plugin version: http://dynamicslollipops.blogspot.com/2011/11/mscrm-2011-get-guid-plugin.html

Sunday, October 16, 2011

MSCRM 2011 Javascript save and close

In 2011 This script doesn't work:
1. Xrm.Page.data.entity.save();
2. window.close();


CRM 4.0 Code (Worked great):
 crmForm.Save();
 window.close();


The solution in 2011:
Xrm.Page.data.entity.save("saveandclose")

The right why in 4.0
crmForm.SaveAndClose();


and just for closing the window
Xrm.Page.ui.close();

Saturday, October 8, 2011

dynamics crm 2011 online lead capture


It's finally here Dynamics CRM 2011 online lead capture is Available in Israel. After I've read about it, searched for it and failed to find it few months ago, Finally it's there.


It's easy to connect the CRM to the company's web page, something that's makes a lot of sense in Customer Relationship Management system.
More information:
http://www.youtube.com/watch?v=2WNY9DPMY1I
http://www.youtube.com/watch?v=K_qhhZdNgcg
http://www.democrmonline.com/IMWebtoLead/

Sunday, September 25, 2011

How to change the customer / partner portal to authenticate against the MSCRM only.


How to change the customer / partner portal to authenticate against the MSCRM only.


There is a new version for the portals, to see how to convert Customer Portal V2 go to http://dynamicslollipops.blogspot.co.il/2012/10/how-to-change-customer-partner-portal.html


This blog is based on few other blogs:
Active Directory Authentication:
http://www.shanmcarthur.net/crm/developers-corner/customer-portal-modifications-for-demo-vpc-without-internet-connectivity
SqlMembershipProvider: http://intergr8it.net/?p=216
Both are great and doing the work.
Yet I had to create another approach.

As you probably know the portals comes with authentication against LiveID, Even that it's best practice and all, sometimes the portal isn't expost to the internet and simpler authentication is needed.

Basically the authentication options are:

1)      LiveID – Each user has to have account in Microsoft LiveId website in order to use the portal.

2)      Active Directory – Each user has to be active directory user (almost same as every MSCRM user)

3)      SqlMemberhipProvider – Each user will be created in separate sql table.

4)      Form authentication – It's up to you (In code) who to authenticate and who not. This is the one we'll use to authenticate user based only on the data in the MSCRM.

In this Solution register page is droped and the crm administrator is registers the users stright in the crm. Once the user is configured in the crm his good to go.

Walkthrough:


In order to do it you'll need to download the portal and follow the installation steps, avoid all the stuff regarding the LiveID.

After you've imported the solution and the website data we are ready to go.

1)      Open the website in visual studio.

2)      Edit he web.config.

a.       Remove the
<add name="Live" connectionString="Application Id=0000000000000000; Secret=00000000000000000000000000000000"/>

From the connectionstring tag

b.      Remove the
<membership defaultProvider="CrmMembershipProvider">
<providers><add name="CrmMembershipProvider" type="Microsoft.Xrm.Portal.Web.Security.LiveIdMembershipProvider, Microsoft.Xrm.Portal" liveIdConnectionStringName="Live"/></providers></membership>

c.        Replace the Authentication tag to as follow
<authentication mode="Forms">
<forms loginUrl="/login" timeout="525600"     defaultUrl="/"  />

 </authentication>

3)      Edit \MasterPages\Default.master.

a.       Replace

<crm:LiveIdLoginStatus ID="LiveLoginStatus" runat="server" LoginNavigateUrl='<%$ CrmSiteMap: SiteMarker=Login, Eval=Url %>'/>

With

<asp:LoginStatus ID="LoginStatus1" runat="server" />

4)      Edit \Pages\Login.aspx

a.        Replace               
<crm:LiveIdLoginStatus ID="LiveIdLink" runat="server" LoginImageUrl="http://www.passportimages.com/1033/signin.gif"

LogoutImageUrl="http://www.passportimages.com/1033/signout.gif" />

With

 <asp:Login ID="Login1" runat="server" OnAuthenticate="Login1_Authenticate"></asp:Login>

5)       Edit \Pages\Login.aspx.cs

a.        Add:
  private Contact _loginContact;



        protected Contact LoginContact

        {

            get

            {

                return _loginContact ??

                (_loginContact = XrmContext.ContactSet

                .FirstOrDefault(c => c.Adx_username == Login1.UserName && c.Adx_LogonEnabled != null && c.Adx_LogonEnabled.Value));

            }

        }

b.        Replace onload:
protected void Page_Load(object sender, EventArgs e)

        {

            if ((User != null) && User.Identity.IsAuthenticated)

            {

                var redirectUrl = !string.IsNullOrEmpty(Request.QueryString["ReturnUrl"])

               ? Request["ReturnUrl"]

               : !string.IsNullOrEmpty(Request.QueryString["URL"])

                   ? Request["URL"]

                   : "/";



                Response.Redirect(redirectUrl);

            }

        }

c.        Add:
  protected void Login1_Authenticate(object sender, System.Web.UI.WebControls.AuthenticateEventArgs e)

        {

            if (LoginContact == null)

            {

                e.Authenticated = false;

            }

            else

            {

                if (LoginContact.Adx_password == Login1.Password)

                {

                    if (LoginContact.Adx_changepasswordatnextlogon.Value)

                    {

                        var page = ServiceContext.GetPageBySiteMarkerName(Website, "ChangePassword");

                        string redirectURL = ServiceContext.GetUrl(page) + "?UserName=" + Server.UrlEncode(Login1.UserName) + "&Password=" + Server.UrlEncode(Login1.Password);

                        Response.Redirect(redirectURL);

                    }

                    else

                    {

                        LoginContact.Adx_LastSuccessfulLogon = DateTime.Now.Date;



                        XrmContext.UpdateObject(LoginContact);

                        XrmContext.SaveChanges();



                        e.Authenticated = true;

                       // Response.Redirect("/");

                        FormsAuthentication.RedirectFromLoginPage(Login1.UserName, true);

                    }

                }

                else

                {

                    e.Authenticated = false;

                }

            }

        }



Compile, Debug and Publish to the IIS.

Friday, September 16, 2011

MSCRM 2011 Ribbon dynamically display the save button.


Microsoft Dynamics CRM 2011 Ribbon is great once you get to know how to work with it. 
At the beginning it looked too complicated, there are too many tags to write and it is difficult to understand how to place the button in the right place. 
The good news are after understanding the why it works, it all makes sense.
They are very flexible and you can do all most everything in a supported why.

This blog shows how to remove and show Ribbon save buttons based on some logic.

In MSCRM 4 it can be done using unsupported code
Something like: 

var toolBarButtons = document.all.mnuBar1.rows[0].cells[0].childNodes[0].childNodes;
and then iterate over all the buttons till the save button found and removed / showed when needed.


In MSCRM 2011 there is supported and much more elegant solution:

Generally speaking you should know these steps before starting (Copied from: http://msdn.microsoft.com/en-us/library/gg334532.aspx):
1.     Prerequisites: This section describes the specific requirements and resources you need.
2.     Export: Create and export a solution containing just the solution components you need. For more information about creating and exporting a solution see the application Help and Export the Ribbon.
3.     Identify the IDs for existing items: Examine default ribbon definitions. The walkthrough provides the XML you need, but it is important that you understand the relationships to existing data in the default ribbon definitions.
4.     Create the script Web resource: Walkthroughs that add controls to the ribbon require a JScript Web resource to perform an action. For more information about creating JScript Web resources, see the application Help and JScript Libraries for Microsoft Dynamics CRM.
5.     Edit the customizations.xml file: This section leads you through a series of cumulative steps editing nodes within the <RibbonDiffXml> (RibbonDiffXml)element. The final step shows you the entire modified RibbonDiffXml element in the customizations.xml file.
6.     Import and publish the solution: Compress the modified solution files and import those files to update your solution. For more information about compressing and importing a solution, see the application Help and Import the Ribbon.
7.     Test and verify that the requirements are met: In this step you confirm that the requirements specified in the prerequisites section are met.


We'll twist it a little bit to meet our requirement.

1) Replace the CommandDefinition that descripes the save button with new one. We'll copy it from incidentribbon.xml and place it in the exported customization.xml.

      <CommandDefinition Id="Mscrm.SavePrimary">
        <EnableRules>
          <EnableRule Id="Mscrm.AvailableOnForm" />
          <EnableRule Id="Mscrm.CanSavePrimary" />
          <EnableRule Id="Mscrm.ReadPrimaryPermission" />
          <EnableRule Id="Mscrm.Form.incident.MainTab.CustomRule.EnableRule" />
        </EnableRules>
        <DisplayRules>
          <DisplayRule Id="Mscrm.CanSavePrimaryEntityType" />
        </DisplayRules>
        <Actions>
          <JavaScriptFunction FunctionName="Mscrm.RibbonActions.saveForm" Library="/_static/_common/scripts/RibbonActions.js">
            <CrmParameter Value="PrimaryControl" />
          </JavaScriptFunction>
        </Actions>
      </CommandDefinition>

2) add new Enable Rule which indicates should it be shown or not
<EnableRule Id="Mscrm.Form.incident.MainTab.CustomRule.EnableRule">
     <CustomRule FuncationName="EnableButtons" Library="$Webresource:new_incident_library" />
</EnableRule>

3) Now in your code you'll be able to decide would the save button be shown or not by defining the logic in the function EnableButtons. Whenever you want to change the display make sure the function returns true / false as expected and refresh the ribbon by using: 
Xrm.Page.ui.refreshRibbon()

The thing to remember here is that you can change the current setting of almost everything in the ribbon by redefining them.