Pessoal,
Seguindo a série de posts sobre o USD, irei escrever neste post sobre as Action Calls!
Para ver os posts anteriores, acesse os links (estão por ordem de publicação do mais recente para o mais antigo):
CRM – Toolbars e Buttons (USD)
CRM Utilidades do USD (Unified Service Desk)
As Action Calls são a forma USD de criamos interações com todos os demais controles, como por exemplo uma ação visual de um hosted control (UII Action), um botão de menu (Toolbar Button), uma ação de navegação (Windows Navigation Rule).
Não devemos confundir as Action Calls com os Events (falaremos sobre Events no próximo post). As Actions representam chamadas de um controle para realizar funcionalidades “out of the box”, ou seja, algo que não seja implementado nativamente pelo USD.
Então, devemos lembrar, quando criamos Actions, estamos apenas assinando métodos, não estamos acionando! Elas são cérebro do USD, implementarão nossas regras de negócio!
Dificilmente será possível criar um exemplo/customização do USD sem o uso de uma Action Call, eu mesmo, em meus posts anteriores, sem ter detalhado o que realmente são as Action Calls fiz uso delas. Assim, neste post, tentarei fazer algo diferente!
1 – Abrir Página CRM através de um Custom Hosted Control
Crei um Custom Hosted Control com apenas alguns controles visuais, após iniciarmos um sessão de atendimento, o operador de call center pode selecionar a opção “Ocorrência” e clicar no botão. Automaticamente uma página do CRM é apresentada! Para fazer isso, criei um chamada de Action Call dentro do Hosted Control!
Primeiramente precisamos criar um Custom Hosted Control, abra o Visual Studio e criei um projeto do tipo “USD Custom Hosted Control”:
Após o projeto ter sido criado, confirme as referências das DLL’s, caso não sejam localizadas procure na pasta de instalação de seu USD ou no SDK do CRM:
Neste exemplo eu inseri alguns controles (textbox, radio e button), apenas para dar um ideia de que podemos criar um WPF novo herdando toda a referência de classes de um WPF para o USD!
Em relação ao C#, criei o evento no clique do botão e inseri a chamada para a Action Call:
Se notarmos bem, não aciono um Action Call propriamente dita, estou na verdade criando uma Action Call em tempo de execução e ela morrerá após ser executada. Nesse caso, não temos o conceito de reaproveitamento… Para solucionar isso, devemos criar um Event, ele agrupa “N” Action Calls e podemos fazer reuso, mas isso é assunto do próximo post! Por hora, ficamos com o conceito de chamar uma “Action” dentro de um Custom Hosted Control!
Bom, os códigos do Custom Hosted Control!
XAML:
<USD:DynamicsBaseHostedControl x:Class="CustomUSDHostedControl.USDControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:USD="clr-namespace:Microsoft.Crm.UnifiedServiceDesk.Dynamics;assembly=Microsoft.Crm.UnifiedServiceDesk.Dynamics" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <ToolBarTray Name="ProgrammableToolbarTray" Grid.Row="0" Focusable="false"/> <!-- this is where any toolbar assigned to this control will go --> <Button Name="button" Click="button_Click" HorizontalAlignment="Left" Margin="89,131,0,0" Grid.Row="1" Content="Ok" VerticalAlignment="Top" Width="52" Height="22" /> <TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="78,47,0,0" Grid.Row="1" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/> <RadioButton x:Name="radioButtonInfo" Content="Informação" GroupName="options" HorizontalAlignment="Left" Margin="78,94,0,0" Grid.Row="1" VerticalAlignment="Top"/> <RadioButton x:Name="radioButtonOcor" Content="Ocorrência" GroupName="options" HorizontalAlignment="Left" Margin="174,94,0,0" Grid.Row="1" VerticalAlignment="Top" /> </Grid> </USD:DynamicsBaseHostedControl>
CS:
// ===================================================================== // This file is part of the Microsoft Dynamics CRM SDK code samples. // // Copyright (C) Microsoft Corporation. All rights reserved. // // This source code is intended only as a supplement to Microsoft // Development Tools and/or on-line documentation. See these other // materials for detailed information regarding Microsoft code samples. // // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // ===================================================================== using System; using System.Globalization; using System.Windows; using Microsoft.Crm.UnifiedServiceDesk.CommonUtility; using Microsoft.Crm.UnifiedServiceDesk.Dynamics; using System.Collections.Generic; using Microsoft.Crm.UnifiedServiceDesk.Dynamics.Utilities; namespace CustomUSDHostedControl { ///<summary> /// Interaction logic for USDControl.xaml /// This is a base control for building Unified Service Desk Aware add-ins /// See USD API documentation for full API Information available via this control. /// </summary> public partial class USDControl : DynamicsBaseHostedControl { #region Vars ///<summary> /// Log writer for USD /// </summary> private TraceLogger LogWriter = null; #endregion ///<summary> /// UII Constructor /// </summary> /// <param name="appID">ID of the application</param> /// <param name="appName">Name of the application</param> /// <param name="initString">Initializing XML for the application</param> public USDControl(Guid appID, string appName, string initString) : base(appID, appName, initString) { InitializeComponent(); // This will create a log writer with the default provider for Unified Service desk LogWriter = new TraceLogger(); #region Enhanced LogProvider Info // This will create a log writer with the same name as your hosted control. // LogWriter = new TraceLogger(traceSourceName:"MyTraceSource"); // If you utilize this feature, you would need to add a section to the system.diagnostics settings area of the UnifiedServiceDesk.exe.config //<source name="MyTraceSource" switchName="MyTraceSwitchName" switchType="System.Diagnostics.SourceSwitch"> // <listeners> // <add name="console" type="System.Diagnostics.DefaultTraceListener"/> // <add name="fileListener"/> // <add name="USDDebugListener" /> // <remove name="Default"/> // </listeners> //</source> // and then in the switches area : //<add name="MyTraceSwitchName" value="Verbose"/> #endregion } ///<summary> /// Raised when the Desktop Ready event is fired. /// </summary> protected override void DesktopReady() { // this will populate any toolbars assigned to this control in config. PopulateToolbars(ProgrammableToolbarTray); base.DesktopReady(); } ///<summary> /// Raised when an action is sent to this control /// </summary> /// <param name="args">args for the action</param> protected override void DoAction(Microsoft.Uii.Csr.RequestActionEventArgs args) { // Log process. LogWriter.Log(string.Format(CultureInfo.CurrentCulture, "{0} -- DoAction called for action: {1}", this.ApplicationName, args.Action), System.Diagnostics.TraceEventType.Information); #region Example process action //// Process Actions. //if (args.Action.Equals("your action name", StringComparison.OrdinalIgnoreCase)) //{ // // Do some work // // Access CRM and fetch a Record // Microsoft.Xrm.Sdk.Messages.RetrieveRequest req = new Microsoft.Xrm.Sdk.Messages.RetrieveRequest(); // req.Target = new Microsoft.Xrm.Sdk.EntityReference( "account" , Guid.Parse("0EF05F4F-0D39-4219-A3F5-07A0A5E46FD5")); // req.ColumnSet = new Microsoft.Xrm.Sdk.Query.ColumnSet("accountid" , "name" ); // Microsoft.Xrm.Sdk.Messages.RetrieveResponse response = (Microsoft.Xrm.Sdk.Messages.RetrieveResponse)this._client.CrmInterface.ExecuteCrmOrganizationRequest(req, "Requesting Account"); // // Example of pulling some data out of the passed in data array // List<KeyValuePair<string, string>> actionDataList = Utility.SplitLines(args.Data, CurrentContext, localSession); // string valueIwant = Utility.GetAndRemoveParameter(actionDataList, "mykey"); // asume there is a myKey=<value> in the data. // // Example of pushing data to USD // string global = Utility.GetAndRemoveParameter(actionDataList, "global"); // Assume there is a global=true/false in the data // bool saveInGlobalSession = false; // if (!String.IsNullOrEmpty(global)) // saveInGlobalSession = bool.Parse(global); // Dictionary<string, CRMApplicationData> myDataToSet = new Dictionary<string, CRMApplicationData>(); // // add a string: // myDataToSet.Add("myNewKey", new CRMApplicationData() { name = "myNewKey", type = "string", value = "TEST" }); // // add a entity lookup: // myDataToSet.Add("myNewKey", new CRMApplicationData() { name = "myAccount", type = "lookup", value = "account,0EF05F4F-0D39-4219-A3F5-07A0A5E46FD5,MyAccount" }); // if (saveInGlobalSession) // { // // add context item to the global session // ((DynamicsCustomerRecord)((AgentDesktopSession)localSessionManager.GlobalSession).Customer.DesktopCustomer).MergeReplacementParameter(this.ApplicationName, myDataToSet, true); // } // else // { // // Add context item to the current session. // ((DynamicsCustomerRecord)((AgentDesktopSession)localSessionManager.ActiveSession).Customer.DesktopCustomer).MergeReplacementParameter(this.ApplicationName, myDataToSet, true); // } //} #endregion base.DoAction(args); } ///<summary> /// Raised when a context change occurs in USD /// </summary> /// <param name="context"></param> public override void NotifyContextChange(Microsoft.Uii.Csr.Context context) { base.NotifyContextChange(context); } #region User Code Area private void button_Click(object sender, RoutedEventArgs e) { // Verify if radio "Ocorrência" is Checked if (radioButtonOcor.IsChecked == true) { // Action var openIncident = new ActionDefinition { Application = "Incident", // Hosted Control Action = "New_CRM_Page", // Action ActionData = "LogicalName=incident" // Data }; // Execute Actions CRMWindowRouter.ExecuteActions(localSessionManager.ActiveSession, new List<ActionDefinition> { openIncident }, string.Empty, new Dictionary<string, string>()); textBox.Text = "Incident Opened!"; } else { textBox.Text = "Button Fired!"; } } #endregion } }
Compile seu projeto e cole a DLL gerada na raiz da pasta de instalação de seu USD, geralmente fica em “C:\Program Files\Microsoft Dynamics CRM USD\USD”.
Por fim, precisamos criar nosso Custom Hosted Control no CRM. Abra o CRM, navegue em Configurações > Unified Service Desk > Hosted Control > Novo:
Precisamos informar os seguintes campos:
- Name – Nome do hosted control
- USD Component Type – USD Hosted Control
- Application is Global – Marcar a caixa de seleção (neste caso, queremos que o hosted control seja inicializado junto com o USD)
- Diplay Panel – MainPanel
- Assembly URI – “CustomUSDHostedControl”, o nome de nossa DLL
- Assembly Type – “CustomUSDHostedControl.USDControl”, o nome de nossa Classe
Pronto! Temos um Custom Hosted Control acionando uma “Action Call”, ou melhor, fazendo uma criação e chamada de Action Call por demanda!
2– Interceptar Action Call em um Custom Hosted Control
Através do Hosted Control A (SmartCustomUSDHostedControl) faço uma chamada que aciona o Hosted Control B (CustomUSDHostedControl) que recebe como parâmetro o valor do TextBox do Hosted Control A, atribui como valor do Textbox do Hosted Control B!
Precisaremos que o item 1 deste post tenha sido executado. Após isto, abra o “CustomHostedControl” e crie uma UII Action:
A única informação que deve inserir é o nome da UII Action! No meu exemplo “ButtonClick”.
Agora vamos ao Visual Studio, abra o Custom Hosted Control “CustomUSDHostedControl”, precisamos fazer uma edição no método “DoAction”, é o responsável por orquestrar as chamadas (Actions) que são feitas ao Hosted Control. Iremos tratar a ação “ButtonClick”, consumir os dados de foram enviados na ação e atribui ao textbox:
Segue o código:
///<summary> /// Raised when an action is sent to this control /// </summary> /// <param name="args">args for the action</param> protected override void DoAction(Microsoft.Uii.Csr.RequestActionEventArgs args) { // Log process. LogWriter.Log(string.Format(CultureInfo.CurrentCulture, "{0} -- DoAction called for action: {1}", this.ApplicationName, args.Action), System.Diagnostics.TraceEventType.Information); #region Example process action //// Process Actions. //if (args.Action.Equals("your action name", StringComparison.OrdinalIgnoreCase)) //{ // // Do some work // // Access CRM and fetch a Record // Microsoft.Xrm.Sdk.Messages.RetrieveRequest req = new Microsoft.Xrm.Sdk.Messages.RetrieveRequest(); // req.Target = new Microsoft.Xrm.Sdk.EntityReference( "account" , Guid.Parse("0EF05F4F-0D39-4219-A3F5-07A0A5E46FD5")); // req.ColumnSet = new Microsoft.Xrm.Sdk.Query.ColumnSet("accountid" , "name" ); // Microsoft.Xrm.Sdk.Messages.RetrieveResponse response = (Microsoft.Xrm.Sdk.Messages.RetrieveResponse)this._client.CrmInterface.ExecuteCrmOrganizationRequest(req, "Requesting Account"); // // Example of pulling some data out of the passed in data array // List<KeyValuePair<string, string>> actionDataList = Utility.SplitLines(args.Data, CurrentContext, localSession); // string valueIwant = Utility.GetAndRemoveParameter(actionDataList, "mykey"); // asume there is a myKey=<value> in the data. // // Example of pushing data to USD // string global = Utility.GetAndRemoveParameter(actionDataList, "global"); // Assume there is a global=true/false in the data // bool saveInGlobalSession = false; // if (!String.IsNullOrEmpty(global)) // saveInGlobalSession = bool.Parse(global); // Dictionary<string, CRMApplicationData> myDataToSet = new Dictionary<string, CRMApplicationData>(); // // add a string: // myDataToSet.Add("myNewKey", new CRMApplicationData() { name = "myNewKey", type = "string", value = "TEST" }); // // add a entity lookup: // myDataToSet.Add("myNewKey", new CRMApplicationData() { name = "myAccount", type = "lookup", value = "account,0EF05F4F-0D39-4219-A3F5-07A0A5E46FD5,MyAccount" }); // if (saveInGlobalSession) // { // // add context item to the global session // ((DynamicsCustomerRecord)((AgentDesktopSession)localSessionManager.GlobalSession).Customer.DesktopCustomer).MergeReplacementParameter(this.ApplicationName, myDataToSet, true); // } // else // { // // Add context item to the current session. // ((DynamicsCustomerRecord)((AgentDesktopSession)localSessionManager.ActiveSession).Customer.DesktopCustomer).MergeReplacementParameter(this.ApplicationName, myDataToSet, true); // } //} #endregion if (args.Action.Equals("ButtonClick", StringComparison.OrdinalIgnoreCase)) { // Get Data Value var dataValue = args.DataObject.ToString(); // Set Data Value in TextBox textBox.Text = dataValue; } base.DoAction(args); }
Agora temos que criar o Custom Hosted Control que irá acionar a Action “ButtonClick”. Para isso precisamos criar um novo projeto do tipo “USD Custom Hosted Control”, chamei o meu de “SmartCustomUSDHostedControl”.
Inseri um textbox e um botão:
No .cs no clique do botão estou fazendo uma chamada para o controle “CustomHostedControl”:
Vamos aos códigos!
XAML:
<USD:DynamicsBaseHostedControl x:Class="SmartCustomUSDHostedControl.USDControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:USD="clr-namespace:Microsoft.Crm.UnifiedServiceDesk.Dynamics;assembly=Microsoft.Crm.UnifiedServiceDesk.Dynamics" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <ToolBarTray Name="ProgrammableToolbarTray" Grid.Row="0" Focusable="false"/> <Button x:Name="button" Content="Call "CustomUSDHostedControl" HorizontalAlignment="Left" Margin="25,103,0,0" Grid.Row="1" VerticalAlignment="Top" Width="235" Click="button_Click"/> <TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="32,26,0,0" Grid.Row="1" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/> <!-- this is where any toolbar assigned to this control will go --> </Grid> </USD:DynamicsBaseHostedControl>
CS:
private void button_Click(object sender, RoutedEventArgs e) { // Action var callCustomHC = new ActionDefinition { Application = "CustomUSDHostedControl", // Hosted Control Action = "ButtonClick", // Action ActionData = textBox.Text // Data }; // Execute Actions CRMWindowRouter.ExecuteActions(localSessionManager.ActiveSession, new List<ActionDefinition> { callCustomHC }, string.Empty, new Dictionary<string, string>()); }
Compile os dois projetos e adicione as DLL’s dentro da pasta do USD!
A última etapa é criar o Custom Hosted Control no CRM:
Informe os seguintes campos:
- Name – Nome do hosted control
- USD Component Type – USD Hosted Control
- Application is Global – Marcar a caixa de seleção (neste caso, queremos que o hosted control seja inicializado junto com o USD)
- Diplay Panel – MainPanel
- Assembly URI – “SmartCustomUSDHostedControl”, o nome de nossa DLL
- Assembly Type – “SmartCustomUSDHostedControl.USDControl”, o nome de nossa Classe
Bom é isso! Abra seu USD e inicie uma sessão com um cliente para verificar o funcionamento!
Outro post extenso, desculpem… Mas era preciso uma grande quantidade de detalhes para exemplificar um pouco mais o uso das Actions.
Para maiores detalhes, consulte os links:
Create an action call for a UII action
[]’s,
Tiago Cardoso
Hi Tiago,
Your tutorial is very helpful. I spent too much time trying to find how to fire an action call from a custom hosted control. And you helped me. But in my case, I want to open a standard hosted control with this action call not as you did (you opened a custom hosted control). And it doesn’t work. I want just to open a web page in this hosted control using a “Navigate” action. It looks easy but it doesn’t work at all! Thank you in advance.
CurtirCurtir
Hi Amine,
Thank you so much to read my post! I really appreciated that!
So, before you “navigate”, you have to “MoveToPanel”. That means: bring my hosted control to screen. Basically, you need something like that:
// Action
var move = new ActionDefinition
{
Application = "Bing", // Hosted Control
Action = "MoveToPanel", // Action
ActionData = "MainPanel" // Data
};
// Action
var navigate = new ActionDefinition
{
Application = "Bing", // Hosted Control
Action = "Navigate", // Action
ActionData = "url=http://www.bing.com" // Data
};
// Execute Actions
CRMWindowRouter.ExecuteActions(localSessionManager.ActiveSession,
new List { move,navigate }, string.Empty, new Dictionary());
Please try that and let me know if it works!
Regards,
Tiago
CurtirCurtir
Thank you for your answer Tiago. What I’ve done is similar to what you’ve said. I used the “navigate” action. I used a custom URL instead because I inserted entity ID and form ID into it. Thank you again for your help.
Best regards.
Amine
CurtirCurtir
Great Amine! Thank you! It’s a pleasure! 😉
CurtirCurtir