I am trying to make a plugin for CRM 2011 that will update some values in a contact entity when it has been disabled.
When a contact gets disabled: I want it to change 3 radio buttons to "Nei" (No in Norwegian). This will disable the access to my self-service portal i have for my customers. You can see a picture of my contact entity with the radio buttons in it here. I want to force all those radio buttons to "Nei" when contact is disabled.
I am a complete beginner with CRM plugin development, and a fairly new user of C#. So please keep it as simple as possible.
I have been reading through the manuals for weeks and i cant seem to get anywhere. (Well, Microsoft is not known for their well written manuals).
You need to register your plugin to both SetState and SetStateDynamicEntity messages, with Pre-Operation as execution stage. Take this code as an example:
using System;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
namespace TestPlugin
{
public class UpdateBoolFields : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
try
{
if (context.InputParameters.Contains("EntityMoniker") &&
context.InputParameters["EntityMoniker"] is EntityReference)
{
EntityReference targetEntity = (EntityReference)context.InputParameters["EntityMoniker"];
OptionSetValue state = (OptionSetValue)context.InputParameters["State"];
if (state.Value == 1)// I'm not sure is 1 for deactivate
{
IOrganizationService service = factory.CreateOrganizationService(context.UserId);
Entity contact = service.Retrieve(targetEntity.LogicalName, targetEntity.Id, new ColumnSet(true));
contact["field1"] = false;
contact["field2"] = false;
contact["field3"] = false;
service.Update(contact);
}
}
}
catch (Exception e)
{
throw new InvalidPluginExecutionException(e.Message);
}
}
}
}
Related
I am using Dynamics CRM 2016 Sandbox Org. I am getting this error when setting statecode and status code values in pre-operation event pipeline. "Changing state attributes is not allowed in create stage 20 plugins."
I cannot update my plugin to be on post operation due to some business requirements. Its working fine in CRM 2015,2013.
To set the state and status on Pre-Create event you can use the code below. I've tested this on SalesOrder entity on CRM 2015 in the Pre-Validation stage. It sets the sales order status directly to pending when a new order is created.
You must make sure you are setting a correct combination of state and status values.
Int32 statusVal = 2;
Int32 stateVal = 0;
if (((Entity)context.InputParameters["Target"]).Contains("statuscode"))
{
((Entity)context.InputParameters["Target"])["statuscode"] = new OptionSetValue(statusVal);
}
else {
((Entity)context.InputParameters["Target"]).Attributes.Add("statuscode", new OptionSetValue(statusVal));
}
if (((Entity)context.InputParameters["Target"]).Contains("statecode"))
{
((Entity)context.InputParameters["Target"])["statecode"] = new OptionSetValue(stateVal);
}
else
{
((Entity)context.InputParameters["Target"]).Attributes.Add("statecode", new OptionSetValue(stateVal));
}
...
If you would like to use Post Create event where you have the entity id you can try using SetStateRequest.
Make sure you have a valid combination of state and status values.
public static void SetStatusOfObject(IOrganizationService service, string entityName, Guid entityId, int state, int status)
{
SetStateRequest updateStatus = new SetStateRequest();
updateStatus.EntityMoniker = new EntityReference(entityName, entityId);
updateStatus.State = new OptionSetValue(state);
updateStatus.Status = new OptionSetValue(status);
service.Execute(updateStatus);
}
currently I´m writing on a outlook plugin for syncing goolge contacts with outlook but I have to cover some special case:
When a contact gets deleted on google side, my application detects the missing contact and creates a new contact based on the contact info from the outlook one.
Is there a way to get an event or history from google that tells me a user deleted this contact(s)?
Edit 1:
Here is my code how I´m accessing the contacts (what is working FINE):
public GoogleAccessor()
{
var parameters = new OAuth2Parameters()
{
ClientId = CLIENTID,
ClientSecret = CLIENTSECRET,
RedirectUri = REDIRECTURI,
Scope = SCOPES
};
string url = OAuthUtil.CreateOAuth2AuthorizationUrl(parameters);
//An own webbrowser for processing the access tokens
IAuthorizationCodeProvider authcodeProvider = new Presentation.BrowserAuthorizationCodeProvider(new Presentation.BrowserAuthentificatorVM());
parameters.AccessCode = authcodeProvider.GetAuthorizationCode(url);
if(parameters.AccessCode == null)
throw new GoogleOAuthException("AccesCode returned 'null' and failed!");
OAuthUtil.GetAccessToken(parameters);
this._contactsRequest = new ContactsRequest(new RequestSettings(APPLICATIONNAME, parameters) {AutoPaging = true});
}
public IList<IContact> GetAllMappedContacts()
{
Feed<Google.Contacts.Contact> f = _contactsRequest.GetContacts();
this._feedUri = new Uri(f.AtomFeed.Feed);
var photoList = new List<PhotoObject>();
foreach (var entry in f.Entries)
{
var photoObject = GetContactPhoto(entry);
if(photoObject != null)
photoList.Add(photoObject);
}
_googleMapper = new GoogleMapper(f.Entries);
return _googleMapper.MapToLocalContacts();;
}
The thing about syncing in general is that syncing is normally meant to work in one direction.
Source Data -> Data Flow -> Received Data.
In this instance, Outlook is your source data and Google is your received data. All information needs to come from your source. Since this is an Outlook add-in you are creating my suggestion would be to add a button to your add-in ribbon. You can call the button whatever ever you like (maybe "dontSyncButton"), but it's purpose is going to be Categorization of your contact.
Make it so that that when a contact is selected and then the button is clicked, the contact is given a special categorization (perhaps "Dont Sync").
Now give some logic to your code that executes the sync, and have that logic decide whether to sync the contact. Also, give some logic to tell the program to delete the contact out of Google for you if the contacts contains the special category. Semi-Pseudo Code below:
if(contact.Categories.ToString() == "Dont Sync")
{
//Don't Sync Contact
If(googleContact.Exists())
{
//Delete contact from Google if it exist
googleContact.Delete();
}
}
else
{
//Sync Contact
}
It would be nice if Outlook had many modifiable properties that weren't visible to users, but since it does not this is really one of the best options I can think of. I do this to sync contacts from a shared Outlook folder to personal ones and it has worked well so far.
Hope this helps!
I'm experimenting with the new WinRT Appointments API in Windows 8.1, based on a sample provided on the MSDN website of Microsoft: http://code.msdn.microsoft.com/windowsapps/Appointments-API-sample-2b55c76e
It works great and I can add appointments without a hassle, but there's always a confirmation by the user involved when using the method ShowAddAppointmentAsync from the Windows.ApplicationModel.Appointments.AppointmentManager namespace, which shows the Appointments provider Add Appointment UI.
I'm looking for a solution to add a larger collection of appointments in the default Windows 8 calendar, WITHOUT the confirmation for each individual appointment in the collection. Is there a way to get around this and bulk insert appointments? Maybe the Windows Live SDK?
Its true that the API prompts the user before saving, but there is a provision to achieve this.
var appointment = new Windows.ApplicationModel.Appointments.Appointment();
appointment.details = "This is a dummy appointment";
appointment.reminder = 15000;
appointment.subject = "TEST APPPOINTMENT";
var x = new Windows.ApplicationModel.Appointments.AppointmentManager.requestStoreAsync(Windows.ApplicationModel.Appointments.AppointmentStoreAccessType.appCalendarsReadWrite).done(function (apppointmentStore) {
apppointmentStore.createAppointmentCalendarAsync("TEST CALENDAR").done(function (calendar) {
calendar.saveAppointmentAsync(appointment);
});
})
Here you're an example to do it using C#
private AppointmentCalendar currentAppCalendar;
private AsyncLazy<AppointmentStore> lazyAppointmentStore = new AsyncLazy<AppointmentStore>(async () =>
{
var appStore = await AppointmentManager.RequestStoreAsync(AppointmentStoreAccessType.AppCalendarsReadWrite);
return appStore;
});
private AppointmentStore AppStore { get { return lazyAppointmentStore.Value.Result; } }
public AppointmentService()
{
}
public async Task CreateCalendar()
{
IReadOnlyList<AppointmentCalendar> appCalendars =
await AppStore.FindAppointmentCalendarsAsync(FindAppointmentCalendarsOptions.IncludeHidden);
AppointmentCalendar appCalendar = null;
// Apps can create multiple calendars. Here app creates only one.
if (appCalendars.Count == 0)
{
appCalendar = await AppStore.CreateAppointmentCalendarAsync(Constants.CalendarName);
}
else
{
appCalendar = appCalendars[0];
}
appCalendar.OtherAppReadAccess = AppointmentCalendarOtherAppReadAccess.Full;
appCalendar.OtherAppWriteAccess = AppointmentCalendarOtherAppWriteAccess.SystemOnly;
// This app will show the details for the appointment. Use System to let the system show the details.
appCalendar.SummaryCardView = AppointmentSummaryCardView.App;
await appCalendar.SaveAsync();
currentAppCalendar = appCalendar;
}
public async Task<bool> CreateNewAppointment(Data.Schemas.Task task)
{
if (null == task)
throw new ArgumentNullException("task");
Appointment newAppointment = new Appointment();
this.SaveAppointmentData(task, newAppointment);
try
{
// Show system calendar to the user to be edited
string appointmentId = await AppointmentManager.ShowAddAppointmentAsync(newAppointment, Windows.Foundation.Rect.Empty);
return ! string.IsNullOrWhiteSpace(appointmentId);
// Just save the appointment
// await currentAppCalendar.SaveAppointmentAsync(newAppointment);
// return true;
}
catch
{
return false;
}
}
Check my post, to know more about AsyncLazy.
I hope this help you.
Regards.
Juanlu
This is not possible, by using the WinRT appointments API.
A user interaction is always required. It was a design decision by MS that some actions require user interaction and this is one of it.
As stated by #Ken Tucker, you can use the windows live api to create appointments but this requires the user of your app to sing in to windows live and grat it the required permissions.
I am what you call a "n00b" in CRM plugin development. I am trying to write a plugin for Microsoft's Dynamics CRM 2011 that will create a new activity entity when you create a new contact. I want this activity entity to be associated with the contact entity.
This is my current code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xrm.Sdk;
namespace ITPH_CRM_Deactivate_Account_SSP_Disable
{
public class SSPDisable_Plugin: IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// 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);
if (context.InputParameters.Contains("Target") && context.InputParameters["target"] is Entity)
{
Entity entity = context.InputParameters["Target"] as Entity;
if (entity.LogicalName != "account")
{
return;
}
Entity followup = new Entity();
followup.LogicalName = "activitypointer";
followup.Attributes = new AttributeCollection();
followup.Attributes.Add("subject", "Created via Plugin.");
followup.Attributes.Add("description", "This is generated by the magic of C# ...");
followup.Attributes.Add("scheduledstart", DateTime.Now.AddDays(3));
followup.Attributes.Add("actualend", DateTime.Now.AddDays(5));
if (context.OutputParameters.Contains("id"))
{
Guid regardingobjectid = new Guid(context.OutputParameters["id"].ToString());
string regardingobjectidType = "account";
followup["regardingobjectid"] = new EntityReference(regardingobjectidType, regardingobjectid);
}
service.Create(followup);
}
}
}
But when i try to run this code: I get an error when i try to create a new contact in the CRM environment. The error is: "The given key was not present in the dictionary" (Link *1). The error pops up right as i try to save the new contact.
Link *1: http://puu.sh/4SXrW.png
(Translated bold text: "Error on business process")
Microsoft Dynamics CRM uses the term activity to describe several types of interactions. The types of activities are:
Phone Call, Task, E-mail, Letter, Fax and Appointment.
ActivityPointer (Activity) Entity
To make your code working Replace the following line:
Entity followup = new Entity();
with
Entity followup = new Entity("task");
And remove the following line:
followup.LogicalName = "activitypointer";
Also please read my comment and Guido Preite's commend above. You need to modify your code to make it working with contact.
Edited
Make sure ContactId does exist in CRM before referencing it to Activity.
This can often happen if you are explicity adding an attribute value to the target entity in you plugin where it already has been added.
Rather than entity.Attributes.Add(...)
use entity["attributename"] = ...
In CRM when emails arrive and have the tracking token in them they automatically set the regarding field to be the incident (or whatever they relate to)
Unfortunately the Record wall isn't updated with this info so even if you are following the case nothing alerts you to the new activity.
I want to write a plugin on email or incident (or both) that updates the record wall and creates a task to follow up on that email with in 3 days.
I'm looking at the SDK and I can't see what the appropriate event in the pipe line would be to work out when an email is/has its regarding field set on arrival in the CRM.
The CRM email creation life-cycle is not well described in the documentation. [shakes fist]
Extra things that are bothering me
I can't seem to include a reference to get a strongly typed Email, Post or Case (driving me crazy)
Testing this is really hard (harder than it should be)
EDIT Here is my current code
namespace Assembly.Plugins
{
using System;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
/// <summary>
/// PostEmailDeliverIncoming Plugin.
/// </summary>
public class PostEmailDeliverIncoming : Plugin
{
/// <summary>
/// Initializes a new instance of the <see cref="PostEmailDeliverIncoming"/> class.
/// </summary>
public PostEmailDeliverIncoming()
: base(typeof(PostEmailDeliverIncoming))
{
RegisteredEvents.Add(new Tuple<int, string, string, Action<LocalPluginContext>>(40, "DeliverIncoming", "email", ExecutePostEmailDeliverIncoming));
// Note : you can register for more events here if this plugin is not specific to an individual entity and message combination.
// You may also need to update your RegisterFile.crmregister plug-in registration file to reflect any change.
}
protected void ExecutePostEmailDeliverIncoming(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
//Extract the tracing service for use in debugging sandboxed plug-ins.
ITracingService tracingService = localContext.TracingService;
// Obtain the execution context from the service provider.
IPluginExecutionContext context = localContext.PluginExecutionContext;
// Obtain the organization service reference.
var service = localContext.OrganizationService;
// The InputParameters collection contains all the data passed in the message request.
if (!context.InputParameters.Contains("Target") || !(context.InputParameters["Target"] is Entity))
return;
// Obtain the target entity from the input parmameters.
var target = (Entity)context.InputParameters["Target"];
// Verify that the target entity represents an account.
// If not, this plug-in was not registered correctly.
if (target.LogicalName != "email")
return;
if((string)target["direction"] != "Incoming")
return;
if (target["regardingobjectid"] == null)
return;
try
{
// if its not a case I don't care
var incident = service.Retrieve("incident", (Guid)target["regardingobjectid"], new ColumnSet(true));
if (incident == null)
return;
var post = new Entity("post");
post["regardingobjectid"] = target["regardingobjectid"];
post["source"]=new OptionSetValue(0);
post["text"] = String.Format("a new email has arrived.");
// Create the task in Microsoft Dynamics CRM.
tracingService.Trace("FollowupPlugin: Creating the post.");
service.Create(post);
// Create a task activity to follow up with the account customer in 7 days.
var followup = new Entity("task");
followup["subject"] = "Follow up incoming email.";
followup["description"] = "An email arrived that was assigned to a case please follow it up.";
followup["scheduledstart"] = DateTime.Now.AddDays(3);
followup["scheduledend"] = DateTime.Now.AddDays(3);
followup["category"] = context.PrimaryEntityName;
// Refer to the email in the task activity.
if (context.OutputParameters.Contains("id"))
{
var regardingobjectid = new Guid(context.OutputParameters["id"].ToString());
followup["regardingobjectid"] = new EntityReference("email", regardingobjectid);
}
// Create the task in Microsoft Dynamics CRM.
tracingService.Trace("FollowupPlugin: Creating the task activity.");
service.Create(followup);
}
catch (FaultException<OrganizationServiceFault> ex)
{
throw new InvalidPluginExecutionException("An error occurred in the FollupupPlugin plug-in.", ex);
}
catch (Exception ex)
{
tracingService.Trace("FollowupPlugin: {0}", ex.ToString());
throw;
}
}
}
}
I've just been fighting with this exact same issue and came across this post. I thought I'd post the solution for you (if you still need it) and anyone else who comes across the issue in the future.
Here's the solution I arrived at:
- Using the Plugin Registration Tool register a New Image on the appropriate step( Stage = "40", MessageName = "DeliverIncoming")
- Set the New Image to be a Post Image
- In your plugin fetch the Post Image's entity ID:
Guid emailID = context.PostEntityImages["PostImage"].Id;
Entity emailFromRetrieve = localContext.OrganizationService.Retrieve(
"email",
emailID,
new Microsoft.Xrm.Sdk.Query.ColumnSet(true));
Email email = emailFromRetrieve.ToEntity<Email>();
if (email.RegardingObjectId == null)
{
return;
}
var regardingObject = email.RegardingObjectId;
Hope this helps!
I'm actually working on a very similar plugin at the moment. Mine creates a custom entity upon arrival of an email addressed to a certain email address. It also associates the incoming email with that new record via the Regarding field. I've added a Pre-Operation step on Create of Email and it works great, including incoming email from the router.
What I'm not sure of is when CRM fills in the Regarding field. You might look at Post-Operation and see if it is set there?
One interesting caveat regarding the Regarding field (haha!): Unlike single lookup fields, the Regarding object's name is actually stored in the ActivityPointer table, so when you update the Regarding field, be sure to set the Name on the EntityReference. If you don't, the Regarding lookup will still have a clickable icon but there won't be any text. I do it like this:
email.RegardingObjectId = [yourentity].ToEntityReference();
email.RegardingObjectId.Name = email.Subject;
Hope that helps!
I ended up doing this in a workflow on the email entity
Steps
Create new workflow, I called it 'incoming email workflow'
Scope is Organisation
Choose Email as the entity and check 'Record field changes'
Add a step that checks Regarding (Case):Case Contains Data
if true:
Add a step that creates a Post
Edit the properties in the Post
Text : This case has had {Direction(E-mail)} email activity from {From(E-mail)}
Source : Auto Post
Regarding : {Regarding(E-mail)}
Add a step that creates a Task
Edit the properties in the Task
Subject : Follow up {Subject(E-mail)}
Regarding : {Regarding(E-mail)}
Try to use the following code:
if ((bool)entity["directioncode"] == false)
Instead of your code:
if((string)target["direction"] != "Incoming")