How to create Action step by step and Call action in MS Dynamics CRM?
How many ways to call action in MS Dynamics CRM?
What the benefits of action instead of Workflow/plugin?
Actions
Actions are a type of process in Microsoft Dynamics 365. You can invoke actions, including custom actions, directly from a workflow or dialog, without writing code! More information: Invoke custom actions from a workflow or dialog
Actions can also be invoked by running custom code that uses the Microsoft Dynamics 365 Web services.
You can call actions:
It can be called from both client & server side, enabling the Single Point of Approach (Implement once, consume anywhere), for ex:- From code that executes within a plug-in, custom workflow and any C# code.
From a command that is placed in the application and executes the operation using JavaScript code.
Can receive input parameters and return output parameters in a straight forward manner, similar to an Organization level Web Service
From an integration with another system that uses the Microsoft Dynamics 365 web services.
From a custom client application that uses the Microsoft Dynamics 365 web services.
Why use actions?
Actions open a range of possibilities for composing business logic. Before Actions, the primary way to implement business processes was limited to plug-ins or custom workflow activities. With Actions, you can perform operations, such as Create, Update, Delete, Assign, or Perform Action. Internally, an action creates a custom Dynamics 365 message. With Actions you can create a custom message (for example: submitquote , leadtoax etc. Once an action is defined and activated, a developer can use that message like any of the other messages provided by the Microsoft Dynamics 365 platform.
Suppose you have button on Quote form which sends information from CRM to another platform (for ex another platform is AX).
Create and activate a Custom Action (Settings > Process)
Now you can call this Action(ofs_submitquotetoax) from JavaScript on some event (OnLoad, OnSave,etc). In this example I am calling the action from SUBMIT QUOTE button on Quote form which sending quote information to other system (AX).
// Call this below method from Button click event or from any event on the form
// For Alert.showLoding method you can see another Alert.js library
// For Process.callAction method you can see another Process.js library
// You can download Alert.js & Process.js from this Path: https://drive.google.com/drive/folders/0B2CUbevE8v9YMkZlMEhUZ3NJc1U
function submitquote() {
var actionName = "ofs_submitquoteax";
var entityName = Xrm.Page.data.entity.getEntityName();
var entityId = Xrm.Page.data.entity.getId();
Alert.showLoading("Submitting...", 400, 150);
var inputParams = [
{
key: "EntityRef", type: Process.Type.EntityReference,
value: new Process.EntityReference(entityName, entityId)
}
];
// call process callection method
Process.callAction(actionName, inputParams, cloneSuccessCallback, errorCallback);
}
function cloneSuccessCallback() {
Alert.hide();
Alert.show("Action Success", "", null, "SUCCESS");
Alert.hide();
var entityName = Xrm.Page.data.entity.getEntityName();
var entityId = Xrm.Page.data.entity.getId();
Xrm.Utility.openEntityForm(entityName, entityId);
//Xrm.Page.data.refresh();
}
function errorCallback(error, trace) {
alert(error);
alert(alert(error));
}
e, for this event you can register & trigger a Plugin and receive the input-parameter (in our case we sending input-parameter key as EntityRef in which we are sending entityName and entityId.
Parameters: Action Unique Name, Input Parameters (array), Success Callback (function), Error Callback (function), CRM Base URL (not required on forms/views)
Each Input Parameter object should contain key, value, and type. Types are defined by the Process.Type enum. EntityReference values should be an object containing id and entityType.
The Success Callback function should accept one argument which is an array of output parameters, each containing key, and value.
You can write plugin in below way and access input parameter
protected override void ExecuteCrmPlugin(LocalPluginContext localContext)
{ // Register the plugin in PreValidation stage as this plugin will trigger from Javascript (Action)
if (localContext == null)
{
throw new InvalidPluginExecutionException("localContext");
}
IPluginExecutionContext context = localContext.PluginExecutionContext;
if (context.Depth > 1) { return; }
IOrganizationService service = localContext.OrganizationService;
ITracingService trace = localContext.TracingService;
Entity quoteEntity = null;
EntityReference qEntity = null;
if (context.InputParameters.Contains("EntityRef") && context.InputParameters["EntityRef"] is EntityReference)
{
//if (context.PrimaryEntityName.ToLower() != "quote") { return; }
qEntity = context.InputParameters["EntityRef"] as EntityReference;
if (qEntity.LogicalName.ToLower().Equals("quote"))
{
try
{
quoteEntity = service.Retrieve("quote", qEntity.Id, new ColumnSet("ofs_parentaccountid", "quotenumber", "revisionnumber", "ofs_well"));
// Execute Your logic
}
catch (Exception ex)
{
trace.Trace(string.Format("Exception Quote_Create_AXIntegration Plugin: {0}", new[] { ex.ToString() }));
}
}
}
else { return; }
}
Register your plugin and then register the step in below way, you can notice your custom message name in below screen-shot "ofs_submitquoteax";
Ref:https://community.dynamics.com/crm/b/mylifemicrosoftdynamiccrm/archive/2017/04/17/microsoft-dynamics-crm-actions
Related
I have a question regarding a small issue that I'm having. I've created a widget that will live on the Service Portal to allow an admin to Accept or Reject requests.
The data for the widget is pulling from the Approvals (approval_approver) table. Under my GlideRecord, I have a query that checks for the state as requested. (Ex. addQuery('state', 'requested'))
To narrow down the search, I tried entering addQuery('sys_id', current.sys_id). When I use this query, my script breaks and I get an error on the Service Portal end.
Here's a sample of the GlideRecord script I've written to Accept.
[//Accept Request
if(input && input.action=="acceptApproval") {
var inRec1 = new GlideRecord('sysapproval_approver');
inRec1.addQuery('state', 'requested');
//inRec1.get('sys_id', current.sys_id);
inRec1.query();
if(inRec1.next()) {
inRec1.setValue('state', 'Approved');
inRec1.setValue('approver', gs.getUserID());
gs.addInfoMessage("Accept Approval Processed");
inRec1.update();
}
}][1]
I've research the web, tried using $sp.getParameter() as a work-around and no change.
I would really appreciate any help or insight on what I can do different to get script to work and filter the right records.
If I understand your question correctly, you are asking how to get the sysId of the sysapproval_approver record from the client-side in a widget.
Unless you have defined current elsewhere in your server script, current is undefined. Secondly, $sp.getParameter() is used to retrieve URL parameters. So unless you've included the sysId as a URL parameter, that will not get you what you are looking for.
One pattern that I've used is to pass an object to the client after the initial query that gets the list of requests.
When you're ready to send input to the server from the client, you can add relevant information to the input object. See the simplified example below. For the sake of brevity, the code below does not include error handling.
// Client-side function
approveRequest = function(sysId) {
$scope.server.get({
action: "requestApproval",
sysId: sysId
})
.then(function(response) {
console.log("Request approved");
});
};
// Server-side
var requestGr = new GlideRecord();
requestGr.addQuery("SOME_QUERY");
requestGr.query(); // Retrieve initial list of requests to display in the template
data.requests = []; // Add array of requests to data object to be passed to the client via the controller
while(requestsGr.next()) {
data.requests.push({
"number": requestsGr.getValue("number");
"state" : requestsGr.getValue("state");
"sysId" : requestsGr.getValue("sys_id");
});
}
if(input && input.action=="acceptApproval") {
var sysapprovalGr = new GlideRecord('sysapproval_approver');
if(sysapprovalGr.get(input.sysId)) {
sysapprovalGr.setValue('state', 'Approved');
sysapprovalGr.setValue('approver', gs.getUserID());
sysapprovalGr.update();
gs.addInfoMessage("Accept Approval Processed");
}
...
I am using ASP.NET Boilerplate with Code-First Entity Framework and MVC 5.
and in order to send a notification I am using the following code
public async Task SendNotification(Guid auditId, int auditNo, int? tenantId, long userId)
{
var notificationData = new LocalizableMessageNotificationData(
new LocalizableString(
"NotificationName",
EODAConsts.LocalizationSourceName
)
);
notificationData["auditNo"] = auditNo;
notificationData["auditId"] = auditId;
await _notificationPublisher.PublishAsync(NotificationName, notificationData, severity: NotificationSeverity.Error, userIds: new[] { new UserIdentifier(tenantId, userId) });
}
we know that sending the notification means adding it to AbpTenantNotifications and AbpUserNotifications ,but after sending it what is the way to retrieve inserted notification id in AbpTenantNotifications ,because PublishAsync method doesn't return any value
i mean what is the unique key in table AbpTenantNotifications which insures selecting specific one notification that is inserted after calling PublishAsync method
NotificationInfo only persist in the table for a short time only.
When you calls PublishAsync, NotifcationInfo is created immediately (see here).
Subsequently, it is consumed by NotificationDistributor.DistributeAsync and deleted right after converting NotificationInfo into TenantNotification & UserNotification (see here)
If you want to capture the TenantNotification when it is created, you can try with entity event handler (see here)
We are using Azure b2c to handle our logins on our .net core MVC site.
We would like to use the optional state parameter to hold onto some data/a value between the initial request to the site (this value would likely be in a querystring param) which is then sent off to b2c to login, and the successfully logged in return back to the site.
OpenIDConnect allow the setting of this state value in the request, and will pass it back with the token response.
It appears that setting the value is relatively simple; in the OnRedirectToIdentityProvider event in the OpenIdConnectOptions like so:
public Task OnRedirectToIdentityProvider(RedirectContext context){
...
context.ProtocolMessage.SetParameter("state", "mystatevalue");
...
}
however, I cannot see how to get this value back again when the user is returned.
I can see that the OnTicketReceived event is hit, and this has a TicketReceivedContext which has a Form property with a state value in it, however this is still encrypted.
Where would i be able to get the un-encrypted value back from?
I have had a look at the Azure docs for b2c but I cannot find an example on this.
thanks
Managed to get this working by using the OnTokenValidated event.
This is able to get the unencrypted parameter as below.
...//first set up the new event
options.Events = new OpenIdConnectEvents()
{
...
OnTokenValidated = OnTokenValidated
};
...
private Task OnTokenValidated(TokenValidatedContext tokenValidatedContext)
{
var stateValue = tokenValidatedContext.ProtocolMessage.GetParameter("state");
if (stateValue != null)
{
//do something with that value..
}
return Task.CompletedTask;
}
I was wondering if we can write plugins that get executed for messages like "publish" and "publish all" in Dynamics CRM (any version). if so can you share any sample references for the same or code snippets.
This is a plugin that works for Publish and PublishAll messages and it will log the event using an entity that I created for this purpose (you can change to do whatever you want).
When the event is Publish, the plugin uses the ParameterXml parameter (MSDN) to log which components are being published. In the case of the PublishAll message, this parameter is not present so there's no detail (which makes sense because you're publishing all).
public class PublishPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
if (context.MessageName != "Publish" && context.MessageName != "PublishAll")
return;
string parameterXml = string.Empty;
if (context.MessageName == "Publish")
{
if (context.InputParameters.Contains("ParameterXml"))
{
parameterXml = (string)context.InputParameters["ParameterXml"];
}
}
CreatePublishAuditRecord(service, context.MessageName, context.InitiatingUserId, parameterXml);
}
private void CreatePublishAuditRecord(IOrganizationService service, string messageName, Guid userId, string parameterXml)
{
Entity auditRecord = new Entity("fjo_publishaudit");
auditRecord["fjo_message"] = messageName;
auditRecord["fjo_publishbyid"] = new EntityReference("systemuser", userId);
auditRecord["fjo_publishon"] = DateTime.Now;
auditRecord["fjo_parameterxml"] = parameterXml;
service.Create(auditRecord);
}
}
This is how it looks in CRM:
You can download the plugin project and CRM solution from my GitHub.
See here for a list of valid Dynamics CRM messages. Publish and PublishAll are both listed. They're also valid in all version of CRM from 2011 onward.
https://msdn.microsoft.com/en-us/library/gg328576.aspx
Just register your plugin like any other but use Publish or PublishAll for the message and leave the Entity as blank.
In the case of Publish, it seems by looking at the documentation that you can't narrow down which entity is being published. You'll have to take a look at what the Input Parameters give you to see if you can work out which entity you're dealing with, if you need it.
My application uses BreezeJS, ASP.NET Web API and EF.
I'm trying to save an object using breeze, as follows:
var saveOptions = this.manager.saveOptions.using({ resourceName: "SaveLocationSettings", tag: clientId, allowConcurrentSaves: true });
var obj = self.manager.saveChanges(null, saveOptions).then(saveSucceeded, saveFailed);
I'm using a custom save method on the server side, which returns a SaveResult object. However, on the client side, the entity manager still maintains the modified state.
My controller on the Web API is a BreezeController.
According to the breeze documentation, if your custom method has the signature similar to the Breeze SaveChanges() method, it should work similar to SaveChanges() method. However, if I use the breeze SaveChanges(), the entity state gets updated properly. But my custom endpoint save does not update the entity state, although the data is saved in the database.
UPDATE:
After some investigation, I figured that this happens only with one entity type that goes to this particular save endpoint. Say, I have a 'location' object, with a collection of 'availability' associated with it, as follows:
Class Location {
public Location() {
this.Availabilities = new HashSet<Availability>();
}
}
Now from the client side, if I only change some property of the Location object, it handles the hasChanges property correctly. But if I change the Availability only or Availability along with another property of the location, then the hasChanges is not updated properly on client side.
This is my server side code that's called from the WebAPI controller:
public SaveResult SaveLocation(Location l, List<MaxAvailability> maxAvailability, int changedBy)
{
// Create a SaveResult object
// we need to return a SaveResult object for breeze
var keyMappings = new List<KeyMapping>();
var entities = new List<object> {l, maxAvailability};
var saveResult = new SaveResult() { Entities = entities, KeyMappings = keyMappings, Errors = null };
try
{
if (l.LocationId == -1)
{
// add new location
l.LocationId = this.AddNewLocationWithItsAssociatedData(l, maxAvailability, changedBy);
}
else
{
// do changes to the existing location
this.UpdateExistingLocationWithItsAssociatedData(l, maxAvailability, changedBy);
}
}
catch (DbEntityValidationException ex)
{
// Log the error and add the errors list to SaveResult.
// Retrieve the error messages as a list of strings.
saveResult.Errors = this.GetErrors(ex);
}
return saveResult;
}
I think I figured out the answer. It was due to some bad practice in my code. When modifying the availability of an existing location, instead of updating the existing record, I was deleting the existing record and adding a new one. This was causing the client side availability object and the database object to have two different states. Once it was resolved, the hasChanges() state was behaving as expected.