The XrmToolBox is an awesome collection of tools for CRM. If you haven’t used it before, stop reading this and download it now. Go ahead. I’ll wait.
Welcome back…
To date I’ve created 2 plugins for XrmToolBox, Option Set Converter and Early Bound Generator. While developing them I discovered that there really needed to be a base plugin control to make it simpler to perform CRM calls and perform asynchronous calls. The base class that I ended up with made development a lot easier for me, and could be very helpful for the rest of the community, so I’m going to highlight it now, the PluginUserControlBase.
UpdateConnection
The first improvement the base makes is simply to implement the required IMsCrmToolsPluginUserControl interface. The documentation shows UpdateConnection has to check the string name being being passed in to determine which method to call:
public void UpdateConnection(IOrganizationService newService,
string actionName = "",
object parameter = null)
{
Service = newService;
if (actionName == "WhoAmI")
{
ProcessWhoAmI();
}
}
This is a really painful implementation since each method that you need a connection to CRM, will require an if statement for the method to actually call. It also allows for late bound exceptions if the action name is updated. Here is my implementation:
public virtual void UpdateConnection(IOrganizationService newService, ConnectionDetail detail, string actionName, object parameter)
{
Service = newService;
ConnectionDetail = detail;
OnConnectionUpdated(new ConnectionUpdatedEventArgs(newService, detail));
if (actionName == String.Empty)
{
return;
}
MethodInfo method;
if (parameter == null)
{
method = GetType().GetMethod(actionName);
if (method == null)
{
throw new Exception("Unable to find method " + GetType().Name + "." + actionName);
}
method.Invoke(this, null);
}
else
{
var externalCaller = parameter as ExternalMethodCallerInfo;
if (externalCaller == null)
{
method = GetType().GetMethod(actionName, new[] {parameter.GetType()});
if (method == null)
{
throw new Exception("Unable to find method " + GetType().Name + "." + actionName);
}
method.Invoke(this, new[] {parameter});
}
else
{
externalCaller.ExternalAction();
}
}
}
The Service and ConnectionDetail get set, then an Event is raised for any plugins that need to know when the connection is updated. Then, rather than having an if statement, reflection is used to lookup the correct method to execute. The ExternalMethodCallerInfo check also allows an action to be used, incase a method needs to be called with more than one parameter.
ExecuteMethod
The documentation shows a button clicked event handler having to check if the Service is populated, and then passing in the string name of the method to call:
private void BtnWhoAmIClick(object sender, EventArgs e)
{
if (Service == null)
{
if (OnRequestConnection != null)
{
var args = new RequestConnectionEventArgs {ActionName = "WhoAmI", Control = this};
OnRequestConnection(this, args);
}
}
else
{
ProcessWhoAmI();
}
}
This is also really annoying, having to check each time if the service is populated, and calling it differently depending on it being populated or not. If the plugin has 3 or 4 actions that require the Crm Service, the OnRequestConnection logic will have to be duplicated multiple times. I created an ExecuteMethod method to do all of that automagically, using actions rather than strings:
/// <summary>
/// Checks to make sure that the Plugin has an IOrganizationService Connection, before calling the action.
/// </summary>
/// <param name="action"></param>
public void ExecuteMethod(Action action)
{
if (Service == null)
{
var name = action.GetMethodInfo().Name;
if (name.Contains("__"))
{
throw new ArgumentOutOfRangeException("action",
@"The Action of an Execute Method must not be a lambda. Use the ExecuteAction(action, parameter) Method.");
}
OnRequestConnection(this, new RequestConnectionEventArgs
{
ActionName = action.GetMethodInfo().Name,
Control = this
});
}
else
{
action();
}
}
/// <summary>
/// Checks to make sure that the Plugin has an IOrganizationService Connection, before calling the action.
/// </summary>
/// <param name="action"></param>
/// <param name="parameter"></param>
public void ExecuteMethod<T>(Action<T> action, T parameter)
{
var caller = parameter as ExternalMethodCallerInfo;
if (Service == null)
{
if (caller == null)
{
OnRequestConnection(this, new RequestConnectionEventArgs
{
ActionName = action.GetMethodInfo().Name,
Control = this,
Parameter = parameter
});
}
else
{
OnRequestConnection(this, new RequestConnectionEventArgs
{
ActionName = "Recaller",
Control = this,
Parameter = parameter
});
}
}
else if (caller == null)
{
action(parameter);
}
else
{
caller.ExternalAction.Invoke();
}
}
private void Recaller(ExternalMethodCallerInfo info)
{
info.ExternalAction.Invoke();
}
WorkAsync
The final improvement that I wanted to highlight was WorkAsync methods which eliminate the need to have duplicated code to create a background worker and update statuses:
WorkAsync(
"Message to Display when Starting a CRM Call",
(w, e) => { /* Work To Do Asynchronously */ },
e => { /* Cleanup when work has completed */ });
The end result is a much much more simplified plugin:
public partial class SampleTool : PluginUserControlBase
{
private void BtnWhoAmIClick(object sender, EventArgs e)
{
ExecuteMethod(ProcessWhoAmI);
}
private void ProcessWhoAmI()
{
WorkAsync("Retrieving your user id...",
(e) => // Work To Do Asynchronously
{
var request = new WhoAmIRequest();
var response = (WhoAmIResponse) Service.Execute(request);
e.Result = response.UserId;
},
e => // Cleanup when work has completed
{
MessageBox.Show(string.Format("You are {0}", (Guid)e.Result));
}
);
}
private void BtnCloseClick(object sender, EventArgs e)
{
base.CloseToolPrompt();
}
}
Interested in creating a plugin for the XrmToolBox? Just download the Early Bound Generator and reference the DLab.XrmToolboxCommon dll.
Happy Coding!
No comments:
Post a Comment