Hello world….Again!

This blog has been neglected for too long, which is a disgrace really.  So I’m starting it back up again, but a few things have had to change.

  • We’re now on WordPress, rather than BlogEngine.net thanks to these stellar instructions.
  • We’re now a Salesforce.com blog about administration and development
  • I  imported all the old blog posts, for fun, but not the old comments or ratings.
  • I still haven’t settled on a theme or a feel, but by the time you find this little space that will probably be sorted out.l

That’s it – ciao for now.

Matt

Making a Custom Activity Work in Your Project

In the past I have followed an amendment of these instructions in order to get a custom activity to work in a CRM workflow project.  It works well, but there is actually an easier way to go about it.

Add a Class Library project to your solution.  I called mine Dummy.

  • Reference System.WorkFlow.Activities
  • Reference to System.Workflow.ComponentModel
  • Use  System.Workflow.Activities.SequenceActivity
  • Make your class inherit SequenceActivity.

And you’re done!

Deactivating Records Of Custom Entity in CRM

I just spun my wheels a bit too much trying to solve a problem that wasn't really a problem at all.  I've been writing a plugin to set an instance of a custom CRM entity to inactive.  My code is:

EntityId = (<span style="color: #2b91af">Key</span>)de.Properties[<span style="color: #a31515">&quot;NameOfCustomEntity&quot;</span>];
id = EntityId.Value;
<span style="color: #2b91af">SetStateDynamicEntityRequest </span>request = <span style="color: blue">new </span><span style="color: #2b91af">SetStateDynamicEntityRequest</span>();
request.Entity = <span style="color: blue">new </span><span style="color: #2b91af">Moniker</span>(<span style="color: #a31515">&quot;KeyColumnName&quot;</span>, id);
request.State = <span style="color: #a31515">&quot;Inactive&quot;</span>;
request.Status = -1;
<span style="color: #2b91af">SetStateDynamicEntityResponse </span>resp = (<span style="color: #2b91af">SetStateDynamicEntityResponse</span>)service.Execute(request);

When I would refresh the form for the record in question, it was still marked as "Active" afterwords.  I realized, finally, that for some reason the refresh doesn't work.  Closing the form and reopening it will then display the proper value from the database and the user will then be able to see that the record is in fact set to "Inactive".

Dynamically Dependant CRM Picklists

If you ever come across a need to link the options of one picklist list to the selected value of another, you might be terrified about the Javascript you will have to write to achieve this.

Well, the Easter Bunny has a surprise egg for you!

The Microsoft Dynamics CRM Demonstration Tools allows you to connect to your CRM server, visually build the relationships between the two piclists you want, and automatically generate the javascript required to link the picklists.

 

The Microsoft Dynamics CRM 4.0 Demonstration Tools include utilities to:

  • Generate Data
  • Change the Dates and/or Times for Data
  • Create Dependent Picklists
  • Generate E-Mail
  • Set Icons for Entities
  • change the CRM Navigation Pane
  • Replace Strings in CRM

NHibernate Cascade Options Explained

NHibernate offers several cascading options.  Consider the behaviour of each, and choose the best option for your app.

  • delete – If an object is deleted, delete all associated objects.
  • delete-orphans – If an object is deleted, delete all objects associated to it.  Also when an object is no longer associated with another object, delete it.
  • all-delete-orhpans – If an object is saved, deleted, or updated, check associated objects and save, delete, or update them.  Also when an object is no longer associated with another object, delete it.
  • save-update – When an object is saved or updated, save or update any associated objects that are now dirty
  • all – If an object is saved, deleted, or updated, check related objects and save, delete, or update them
  • none – Let’s developers handle cascades by themselves.

Retrieving DynamicEntity in Dynamics CRM 4.0 To Access Custom Properties Of An Entity In Your Workflow

I’ve been working with Dynamics CRM 4.0 for a while now, and last week had to write the most complicated workflow I’ve ever done.  It was pretty challenging logic, and gave me a great opportunity to explore parts of the CRM SDK I had never used up until that point.

In the past I had selected specific instances of some entity based on a guid and had no trouble doing so.  CRM allows for custom properties added to a base entity, and in this particular case I had to retrieve information from some of these custom properties.  However, my standard way of retrieving an object wasn’t including these extended custom properties!

It was pretty easy to solve once I got to understand the DynamicEntity object.  DynamicEntity can be an instance of any of CRM entity and contains all of the custom properties. 

So I wrote my Execute method as follows:

<span style="color: blue">protected override </span>ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
    {
        <span style="color: blue">try
        </span>{
            <span style="color: green">// Get the context service.
            </span>IContextService contextService = (IContextService)executionContext.GetService(<span style="color: blue">typeof</span>(IContextService));
            IWorkflowContext context = contextService.Context;
            <span style="color: green">// Use the context service to create an instance of CrmService.
            </span>ICrmService crmService = context.CreateCrmService(<span style="color: blue">true</span>);
            DynamicEntity CaseEntity = GetCase(crmService, context.PrimaryEntityId);
            <span style="color: green">//check if CaseEntity was null
            </span><span style="color: blue">if </span>(CaseEntity == <span style="color: blue">null</span>)
            {
                <span style="color: green">//handle
            </span>}
            <span style="color: green">//check if CaseEntity has the desired custom property
            </span><span style="color: blue">if </span>(!CaseEntity.Properties.Contains(<span style="color: #a31515">&quot;custom_someproperty&quot;</span>))
            {
                <span style="color: green">//handle
            </span>}
            <span style="color: green">//get the custom property, let's assume it was a lookup
            </span>Lookup MyNewObjectFromTheCustomField = (Lookup)CaseEntity[<span style="color: #a31515">&quot;custom_someproperty&quot;</span>];
            <span style="color: green">//do other stuff
            </span><span style="color: blue">return </span>ActivityExecutionStatus.Closed;
        }
    }


The code of the GetCase method is pretty straightforward.  We will return a DynamicEntity instance that will contain all base and custom properties of the case object we need.

<span style="color: blue">private </span>DynamicEntity GetCase(ICrmService crmService, <span style="color: #2b91af">Guid </span>caseid)
    {
        <span style="color: green">//build up the query
        </span>QueryByAttribute query = <span style="color: blue">new </span>QueryByAttribute();
        query.EntityName = EntityName.incident.ToString();
        query.ColumnSet = <span style="color: blue">new </span>AllColumns();
        query.Attributes = <span style="color: blue">new </span><span style="color: #2b91af">String</span>[] { <span style="color: #a31515">&quot;mycustomproperty&quot; </span>};
        query.Values = <span style="color: blue">new </span><span style="color: #2b91af">Object</span>[] { caseid };
        <span style="color: green">//build up the request
        </span>RetrieveMultipleRequest request = <span style="color: blue">new </span>RetrieveMultipleRequest();
        request.ReturnDynamicEntities = <span style="color: blue">true</span>;
        request.Query = query;
        <span style="color: green">//execute 
        </span>RetrieveMultipleResponse response = (RetrieveMultipleResponse)crmService.Execute(request);
        <span style="color: blue">if </span>(response.BusinessEntityCollection.BusinessEntities.Count == 1)
        {
            DynamicEntity entity = (DynamicEntity)response.BusinessEntityCollection.BusinessEntities[0];
            <span style="color: blue">return </span>entity;
        }
        <span style="color: blue">else
        </span>{
            <span style="color: blue">return null</span>;
        }
    }

Hosting an MVC Application on XP (IIS 5.1)

Getting an MVC 2.0 application running on my XP Pro box under IIS 5.1 was a fairly painless process, contrary to the feelings conveyed in many blog posts I had read on the subject.  Some had written they had to change their routes in their Global.asax file, and had to change links between views in their codefiles.  I don’t know why this was an issue for them, because it certainly wasn’t necessary for me.

However, I did get to exploit a bug in IIS 5.1 to allow MVC url rewriting :)

I personally set up the application as my Default Web Site, but you may choose a virtual directory if you would so rather. 

  1. Right click on Default Web Site and click Properties
  2. Make sure the website or virtual directory is an Application.
  3. Set permissions to Scripts Only
  4. Click Configuration.  Under the Mappings tab, click the Add button
  5. You need to insert the path to the file aspnet_isapi.dll.  This is most likely C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll.
  6. In the Extension field, enter “.*” (excluding quotes). 
  7. Select All Verbs.  Select “Script Engine”.  Make sure ”Check that file exists” is not selected.
  8. Here’s the bug.  “.*” This isn’t a valid extension in IIS 5.1 so the OK button is disabled.  Click in the Extension field, then in the Executable field, and the OK button should be enabled!  Click OK at this point.

Your website should now be able to work!

My reason for setting this up was to launch a beta testing of an application I have written.  I wanted to host it at home to save on costs and ended up using a service called No-IP to provide a Dynamic DNS.  This free service allows users to navigate to a URL that is constantly being updated with my proper IP address.  Some configuration and port forwarding settings later and I was easily able to have my url navigate to my application.

One problem I did have was an inability to log into the website I was testing – My website was swallowing some error and simply displaying “We’re sorry, your request could not be processed”.  Sure, I could have looked at my Log4Net configuration, but what fun would that be?  (Actually, I forgot I was logging.)  I simply attached to the aspnet_wp.exe worker process to debug my application as I was accessing it through the URL I had just set up.  I quickly found out that my database (sqlite in this case) was read only. 

Ultimately what I had to do was give the IUSR_MachineName account permissions to write to the file and folder in  which it resides.

  1. Right click on My Computer and select Manage.
  2. Expand System Tools
  3. Expand Local Users and Groups
  4. Select Users
  5. Find the IUSR_MachineName account.  right click – Properties
  6. Click the Member Of Tab.

I added this account to the Administrators group.  This may be too unrestricted for you, but it wasn’t an issue for me.

And voila!  Navigating to my no-ip.org subdomain url from anywhere in the world will direct the user to the application running on my desktop at home.

Put A Custom Button In The Toolbar Of A CRM Entity

I recently needed to add a button to the toolbar on the details form for the Case entity on Dynamics CRM 4.0.  This was much easier than I had expected.

I will be assuming your workflow has already been created and installed.  First, you need to export your current configuration file. 

  • In CRM, click the Settings tab in the bottom left. 
  • Select Customization – Export Customizations.
  • Select ISV Config.
  • Click Export Selected Customizations.

This will download a zip file containing the current file.  Extract the file, and keep the zip file as a backup in case you break something :)

A typical node for an entity will look like the following:

<span style="color: blue">&lt;</span><span style="color: #a31515">Entity </span><span style="color: red">name</span><span style="color: blue">=</span>"<span style="color: blue">incident</span>"<span style="color: blue">&gt;
&lt;</span><span style="color: #a31515">ToolBar</span><span style="color: blue">&gt;
&lt;</span><span style="color: #a31515">ToolBarSpacer </span><span style="color: blue">/&gt;
&lt;</span><span style="color: #a31515">Button</span><span style="color: blue">&gt;
&lt;</span><span style="color: #a31515">Titles</span><span style="color: blue">&gt;
&lt;</span><span style="color: #a31515">Title</span><span style="color: blue">/&gt;
&lt;/</span><span style="color: #a31515">Titles</span><span style="color: blue">&gt;
&lt;</span><span style="color: #a31515">ToolTips</span><span style="color: blue">&gt;
&lt;</span><span style="color: #a31515">ToolTip</span><span style="color: blue">/&gt;
&lt;/</span><span style="color: #a31515">ToolTips</span><span style="color: blue">&gt;
&lt;/</span><span style="color: #a31515">Button</span><span style="color: blue">&gt;
&lt;/</span><span style="color: #a31515">ToolBar</span><span style="color: blue">&gt;
&lt;/</span><span style="color: #a31515">Entity</span><span style="color: blue">&gt;</span>

Note that “incident” is the data type for Case objects in CRM.

I saw that I already had an entry for incident, so I needed only to add the new <button> subnode.  That gave me the following:

<span style="color: blue">&lt;</span><span style="color: #a31515">Button </span><span style="color: red">Icon</span><span style="color: blue">=</span>"<span style="color: blue">/_imgs/ico/16_send.gif</span>" <span style="color: red">JavaScript</span><span style="color: blue">=</span>"<span style="color: blue">window.confirm('Are you sure you want email notify the Owner?');</span>" <span style="color: red">ValidForCreate</span><span style="color: blue">=</span>"<span style="color: blue">0</span>"<span style="color: blue">&gt;
&lt;</span><span style="color: #a31515">Titles</span><span style="color: blue">&gt;
&lt;</span><span style="color: #a31515">Title </span><span style="color: red">LCID</span><span style="color: blue">=</span>"<span style="color: blue">1033</span>" <span style="color: red">Text</span><span style="color: blue">=</span>"<span style="color: blue">Email Owner</span>" <span style="color: blue">/&gt;
&lt;/</span><span style="color: #a31515">Titles</span><span style="color: blue">&gt;
&lt;</span><span style="color: #a31515">ToolTips</span><span style="color: blue">&gt;
&lt;</span><span style="color: #a31515">ToolTip </span><span style="color: red">LCID</span><span style="color: blue">=</span>"<span style="color: blue">1033</span>" <span style="color: red">Text</span><span style="color: blue">=</span>"<span style="color: blue">Send an automated email notification to the owner</span>" <span style="color: blue">/&gt;
&lt;/</span><span style="color: #a31515">ToolTips</span><span style="color: blue">&gt;
&lt;/</span><span style="color: #a31515">Button</span><span style="color: blue">&gt;</span>

As you can see, clicking this button will ask a question and will do nothing useful.  But what we want to do is execute a workflow when it is clicked.  To execute a workflow from javascript, CRM uses SOAP requests.  Below is a nicely formatted version of the code I needed in the Javascript property of the button node I just added.

<span style="color: blue">var </span>_return = window.confirm(<span style="color: #a31515">'Are you sure you want to send email to the owner?'</span>);
<span style="color: blue">if</span>(_return)
{
<span style="color: blue">var </span>caseid = crmForm.ObjectId;
<span style="color: blue">var </span>authenticationHeader = GenerateAuthenticationHeader();
<span style="color: green">//Prepare the SOAP message.
</span><span style="color: blue">var </span>xml = <span style="color: #a31515">"&lt;?xml version='1.0' encoding='utf-8'?&gt;"</span>+ 
<span style="color: #a31515">"&lt;soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'"</span>+
<span style="color: #a31515">" xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'"</span>+
<span style="color: #a31515">" xmlns:xsd='http://www.w3.org/2001/XMLSchema'&gt;"</span>+ 
authenticationHeader+ 
<span style="color: #a31515">"&lt;soap:Body&gt;"</span>+ 
<span style="color: #a31515">"&lt;Execute xmlns='http://schemas.microsoft.com/crm/2007/WebServices'&gt;" 
"&lt;Request xsi:type='ExecuteWorkflowRequest'&gt;" </span>+
<span style="color: #a31515">"&lt;EntityId&gt;" </span>+ caseid + <span style="color: #a31515">"&lt;/EntityId&gt;" </span>+
<span style="color: #a31515">"&lt;WorkflowId&gt;79dae421-e144-466c-9cf7-951528a7cb9f&lt;/WorkflowId&gt;" </span>+
<span style="color: #a31515">"&lt;/Request&gt;" </span>+
<span style="color: #a31515">"&lt;/Execute&gt;" </span>+
<span style="color: #a31515">"&lt;/soap:Body&gt;" </span>+ 
<span style="color: #a31515">"&lt;/soap:Envelope&gt;"
</span><span style="color: blue">var </span>xHReq = <span style="color: blue">new </span>ActiveXObject(<span style="color: #a31515">"Msxml2.XMLHTTP"</span>);
xHReq.Open(<span style="color: #a31515">"POST"</span>, <span style="color: #a31515">"/mscrmservices/2007/CrmService.asmx"</span>, <span style="color: blue">false</span>);&amp;#xD;&amp;#xA;xHReq.setRequestHeader(<span style="color: #a31515">"SOAPAction"</span>,<span style="color: #a31515">"http://schemas.microsoft.com/crm/2007/WebServices/Execute"</span>);
xHReq.setRequestHeader(<span style="color: #a31515">"Content-Type"</span>, <span style="color: #a31515">"text/xml; charset=utf-8"</span>);
xHReq.setRequestHeader(<span style="color: #a31515">"Content-Length"</span>, xml.length);
xHReq.send(xml);&amp;#xD;&amp;#xA;window.alert(<span style="color: #a31515">'Email has been sent.'</span>);
}

This javascript figures out the guid id of the entity to perform the workflow against, and has hardcoded the workflow guid id you wish to execute.  Simple enough.  But looking in that file at another example, I noticed the javascript had encoded characters.  I’m not sure if that is necessary, but not wanting to mess with success, I decided to do the same for my new button.

The following is what I ended up with:   

<span style="color: blue">&lt;</span><span style="color: #a31515">Button </span><span style="color: red">Icon</span><span style="color: blue">=</span>"<span style="color: blue">/_imgs/ico/16_send.gif</span>" <span style="color: red">JavaScript</span><span style="color: blue">=</span>"<span style="color: red">&amp;#xD;&amp;#xA;</span><span style="color: blue">var _return = window.confirm('Are you sure you want email notify the Owner?');</span><span style="color: red">&amp;#xD;&amp;#xA;</span><span style="color: blue">if(_return)</span><span style="color: red">&amp;#xD;&amp;#xA;</span><span style="color: blue">{</span><span style="color: red">&amp;#xD;&amp;#xA;</span><span style="color: blue">var caseid = crmForm.ObjectId;</span><span style="color: red">&amp;#xD;&amp;#xA;</span><span style="color: blue">var authenticationHeader = GenerateAuthenticationHeader();</span><span style="color: red">&amp;#xD;&amp;#xA;</span><span style="color: blue">//Prepare the SOAP message.</span><span style="color: red">&amp;#xD;&amp;#xA;</span><span style="color: blue">var xml = </span><span style="color: red">&amp;quot;&amp;lt;</span><span style="color: blue">?xml version='1.0' encoding='utf-8'?</span><span style="color: red">&amp;gt;&amp;quot;</span><span style="color: blue">+ </span><span style="color: red">&amp;#xD;&amp;#xA;&amp;quot;&amp;lt;</span><span style="color: blue">soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'</span><span style="color: red">&amp;quot;</span><span style="color: blue">+</span><span style="color: red">&amp;#xD;&amp;#xA;&amp;quot; </span><span style="color: blue">xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'</span><span style="color: red">&amp;quot;</span><span style="color: blue">+</span><span style="color: red">&amp;#xD;&amp;#xA;&amp;quot; </span><span style="color: blue">xmlns:xsd='http://www.w3.org/2001/XMLSchema'</span><span style="color: red">&amp;gt;&amp;quot;</span><span style="color: blue">+ </span><span style="color: red">&amp;#xD;&amp;#xA;</span><span style="color: blue">authenticationHeader+ </span><span style="color: red">&amp;#xD;&amp;#xA;&amp;quot;&amp;lt;</span><span style="color: blue">soap:Body</span><span style="color: red">&amp;gt;&amp;quot;</span><span style="color: blue">+ </span><span style="color: red">&amp;#xD;&amp;#xA;&amp;quot;&amp;lt;</span><span style="color: blue">Execute xmlns='http://schemas.microsoft.com/crm/2007/WebServices'</span><span style="color: red">&amp;gt;&amp;quot; </span><span style="color: blue">+</span><span style="color: red">&amp;#xD;&amp;#xA;&amp;quot;&amp;lt;</span><span style="color: blue">Request xsi:type='ExecuteWorkflowRequest'</span><span style="color: red">&amp;gt;&amp;quot; </span><span style="color: blue">+</span><span style="color: red">&amp;#xD;&amp;#xA;&amp;quot;&amp;lt;</span><span style="color: blue">EntityId</span><span style="color: red">&amp;gt;&amp;quot; </span><span style="color: blue">+ caseid + </span><span style="color: red">&amp;quot;&amp;lt;</span><span style="color: blue">/EntityId</span><span style="color: red">&amp;gt;&amp;quot; </span><span style="color: blue">+</span><span style="color: red">&amp;#xD;&amp;#xA;&amp;quot;&amp;lt;</span><span style="color: blue">WorkflowId</span><span style="color: red">&amp;gt;</span><span style="color: blue">E0AE00CD-F794-47F3-9103-C315D8049549</span><span style="color: red">&amp;lt;</span><span style="color: blue">/WorkflowId</span><span style="color: red">&amp;gt;&amp;quot; </span><span style="color: blue">+</span><span style="color: red">&amp;#xD;&amp;#xA;&amp;quot;&amp;lt;</span><span style="color: blue">/Request</span><span style="color: red">&amp;gt;&amp;quot; </span><span style="color: blue">+</span><span style="color: red">&amp;#xD;&amp;#xA;&amp;quot;&amp;lt;</span><span style="color: blue">/Execute</span><span style="color: red">&amp;gt;&amp;quot; </span><span style="color: blue">+</span><span style="color: red">&amp;#xD;&amp;#xA;&amp;quot;&amp;lt;</span><span style="color: blue">/soap:Body</span><span style="color: red">&amp;gt;&amp;quot; </span><span style="color: blue">+ </span><span style="color: red">&amp;#xD;&amp;#xA;&amp;quot;&amp;lt;</span><span style="color: blue">/soap:Envelope</span><span style="color: red">&amp;gt;&amp;quot;&amp;#xD;&amp;#xA;</span><span style="color: blue">var xHReq = new ActiveXObject(</span><span style="color: red">&amp;quot;</span><span style="color: blue">Msxml2.XMLHTTP</span><span style="color: red">&amp;quot;</span><span style="color: blue">);</span><span style="color: red">&amp;#xD;&amp;#xA;</span><span style="color: blue">xHReq.Open(</span><span style="color: red">&amp;quot;</span><span style="color: blue">POST</span><span style="color: red">&amp;quot;</span><span style="color: blue">, </span><span style="color: red">&amp;quot;</span><span style="color: blue">/mscrmservices/2007/CrmService.asmx</span><span style="color: red">&amp;quot;</span><span style="color: blue">, false);</span><span style="color: red">&amp;#xD;&amp;#xA;</span><span style="color: blue">xHReq.setRequestHeader(</span><span style="color: red">&amp;quot;</span><span style="color: blue">SOAPAction</span><span style="color: red">&amp;quot;</span><span style="color: blue">,</span><span style="color: red">&amp;quot;</span><span style="color: blue">http://schemas.microsoft.com/crm/2007/WebServices/Execute</span><span style="color: red">&amp;quot;</span><span style="color: blue">);</span><span style="color: red">&amp;#xD;&amp;#xA;</span><span style="color: blue">xHReq.setRequestHeader(</span><span style="color: red">&amp;quot;</span><span style="color: blue">Content-Type</span><span style="color: red">&amp;quot;</span><span style="color: blue">, </span><span style="color: red">&amp;quot;</span><span style="color: blue">text/xml; charset=utf-8</span><span style="color: red">&amp;quot;</span><span style="color: blue">);</span><span style="color: red">&amp;#xD;&amp;#xA;</span><span style="color: blue">xHReq.setRequestHeader(</span><span style="color: red">&amp;quot;</span><span style="color: blue">Content-Length</span><span style="color: red">&amp;quot;</span><span style="color: blue">, xml.length);</span><span style="color: red">&amp;#xD;&amp;#xA;</span><span style="color: blue">xHReq.send(xml);</span><span style="color: red">&amp;#xD;&amp;#xA;</span><span style="color: blue">window.alert('Email has been sent.');</span><span style="color: red">&amp;#xD;&amp;#xA;</span><span style="color: blue">}</span><span style="color: red">&amp;#xD;&amp;#xA;</span>" <span style="color: red">ValidForCreate</span><span style="color: blue">=</span>"<span style="color: blue">0</span>"<span style="color: blue">&gt;
&lt;</span><span style="color: #a31515">Titles</span><span style="color: blue">&gt;
&lt;</span><span style="color: #a31515">Title </span><span style="color: red">LCID</span><span style="color: blue">=</span>"<span style="color: blue">1033</span>" <span style="color: red">Text</span><span style="color: blue">=</span>"<span style="color: blue">Email Owner</span>" <span style="color: blue">/&gt;
&lt;/</span><span style="color: #a31515">Titles</span><span style="color: blue">&gt;
&lt;</span><span style="color: #a31515">ToolTips</span><span style="color: blue">&gt;
&lt;</span><span style="color: #a31515">ToolTip </span><span style="color: red">LCID</span><span style="color: blue">=</span>"<span style="color: blue">1033</span>" <span style="color: red">Text</span><span style="color: blue">=</span>"<span style="color: blue">Send an automated email notification to the owner</span>" <span style="color: blue">/&gt;
&lt;/</span><span style="color: #a31515">ToolTips</span><span style="color: blue">&gt;
&lt;/</span><span style="color: #a31515">Button</span><span style="color: blue">&gt;</span>

After saving this file (remember – keep your original as a backup!), you are ready to import the modified config into CRM:

  • Click Settings.
  • Click Customization.
  • Click Import Customizations
  • Click browse and select your file.
  • Click upload.

CRM is able to automatically determine which file you have uploaded.  It should say ISV Config.  Highlight it and click Import Selected Customizations.

Test your newly added button.  Should there be a problem, re-import the original unmodified config and go back to the drawing board with the javascript you wrote.

If this ends up helping someone, I will be happy to write more CRM posts in the future.  Please let me know by commenting or rating this post.

Enabling Uploads in FCKeditor in an ASP.NET MVC project

I just spent as little as 10 minutes enabling uploads in FCKeditor.  I was quite surprised how easy it was.

NOTE – this post assumes you have already set up FCKeditor on your page.

First, you need to add a reference to FredCK.FCKeditorV2.dll in your project.

Second, find the file named fckconfig.js and find the following lines:

<span style="color: blue">var </span>_FileBrowserLanguage = <span style="color: #a31515">'php'</span>; <span style="color: green">
</span><span style="color: blue">var </span>_QuickUploadLanguage = <span style="color: #a31515">'php'</span>; 

Change ‘php’ to aspx.  This will determine the upload connector used.

Now open the connector file at FCKeditor\editor\filemanager\connectors\aspx\config.ascx.  Set the UserFilesPath variable to a relative folder from your website root – I use “/Content/files/”.  Make sure you then create this folder.

Then find this method:

<span style="color: blue">private bool </span>CheckAuthentication()
{
<span style="color: green">// WARNING : DO NOT simply return "true". By doing so, you are allowing
// "anyone" to upload and list the files in your server. You must implement
// some kind of session validation here. Even something very simple as...
//
//        return ( Session[ "IsAuthorized" ] != null &amp;&amp; (bool)Session[ "IsAuthorized" ] == true );
//
// ... where Session[ "IsAuthorized" ] is set to "true" as soon as the
// user logs in your system.
</span><span style="color: blue">return false</span>;
}

You will want to edit this method so that returns true when your user is validated.  Like the comment says, simply returning true will allow all users to upload content, which is not only likely undesirable, but also comes fraught with security risks.

That is all you will need to do in order to allow uploads from within FCKeditor to your webserver.  However, I am also a fan of deleting all of the files relating to languages I am not using to help clean the folder structure somewhat.

BONUS:

There are also other fun settings.  You can download additional skins for FCKeditor here.  Inside fckconfig.js you can set the skin for your toolbars to use:

FCKConfig.SkinPath = FCKConfig.BasePath + <span style="color: #a31515">'skins/office2003/'</span>;

You can also disable tabs on the various dialogs such as the Image or Link Dialog:

FCKConfig.ImageDlgHideAdvanced = <span style="color: blue">true</span>;
FCKConfig.LinkDlgHideAdvanced = <span style="color: blue">true</span>;
FCKConfig.LinkUpload = <span style="color: blue">false</span>;
FCKConfig.FlashDlgHideAdvanced = <span style="color: blue">true</span>;
FCKConfig.FlashUpload = <span style="color: blue">false</span>;

As you may know, FCKeditor has been discontinued and has been replaced by CKeditor, a completely rewritten new library meant to address the same need with additional functionality.  I will post about this when I get a chance to play with it.

Injecting HTML into FCKeditor (Working in Firefox)

Some time ago I wrote about injecting HTML into an instance of FCKeditor.  I had it working in Internet Explorer but not in Firefox.  It turns out that the problem was with my syntax.  Oops…

Here’s what the OnComplete event should look like:

<span style="color: blue">function </span>FCKeditor_OnComplete(editorInstance) 
{
    <span style="color: blue">var </span>oEditor = FCKeditorAPI.GetInstance(editorInstance.Name);
    <span style="color: blue">var </span>content = parent.document.getElementById(<span style="color: #a31515">"ContentBody"</span>).value;
    <span style="color: green">// Replace curly quotes with straight quotes
    </span><span style="color: blue">var </span>EditedContent = content.replace(/\u201C/g, <span style="color: #a31515">'"'</span>);
    oEditor.InsertHtml(EditedContent);
    content = <span style="color: blue">null</span>;
}