private static bool InstallFunctionsAndActionsSolution(CDSWebApiService svc) { if (!IsSolutionInstalled(svc)) { //Install the solution //Locate the custom solution zip file, which should have been copied over to the build //output directory. string solutionPath = Directory.GetCurrentDirectory() + "\\SolutionFiles\\" + customSolutionFilename; if (!File.Exists(solutionPath)) { Console.WriteLine($"Solution file not found at {solutionPath}."); return(false); } //Read the solution package into memory byte[] packageBytes = File.ReadAllBytes(solutionPath); Guid ImportJobId = Guid.NewGuid(); //Import the solution package. JObject importParams = new JObject { ["CustomizationFile"] = packageBytes, ["OverwriteUnmanagedCustomizations"] = false, ["PublishWorkflows"] = false, ["ImportJobId"] = ImportJobId }; svc.Post("ImportSolution", importParams); if (IsSolutionInstalled(svc)) { Console.WriteLine("Solution containing custom actions was installed."); } } else { Console.WriteLine("Solution containing custom actions was already installed."); } return(true); }
public static void Run(CDSWebApiService svc) { Console.WriteLine("\n--Starting Functions And Actions--"); //Create records required for this sample CreateRequiredRecords(svc); #region Call an unbound function with no parameters. //Retrieve the current user's full name from the WhoAmI function: // https://docs.microsoft.com/dynamics365/customer-engagement/web-api/whoami // Which returns a WhoAmIResponse ComplexType // https://docs.microsoft.com/dynamics365/customer-engagement/web-api/whoamiresponse // Which contains a UserId property that can be used to retrieve information about the current user Console.WriteLine("Unbound function: WhoAmI"); var whoAmIresp = svc.Get("WhoAmI"); //Then retrieve the full name for that unique ID. var myUserId = whoAmIresp["UserId"]; var user = svc.Get($"systemusers({myUserId})?$select=fullname"); Console.WriteLine($"\tCurrent user has full name: '{user["fullname"]}'."); #endregion Call an unbound function with no parameters. #region Call an unbound function that requires parameters. //Retrieve the time zone code for the specified time zone, using the GetTimeZoneCodeByLocalizedName //function: https://docs.microsoft.com/dynamics365/customer-engagement/web-api/gettimezonecodebylocalizedname, // which returns a GetTimeZoneCodeByLocalizedNameResponse ComplexType // https://docs.microsoft.com/dynamics365/customer-engagement/web-api/gettimezonecodebylocalizednameresponse string timeZoneName = "Pacific Standard Time"; int localeID = 1033; Console.WriteLine("Unbound function with parameters: GetTimeZoneCodeByLocalizedName"); var LocalizedNameResponse = svc.Get($"GetTimeZoneCodeByLocalizedName" + $"(LocalizedStandardName=@p1,LocaleId=@p2)" + $"?@p1='{timeZoneName}'&@p2={localeID}"); Console.WriteLine($"\tThe time zone '{timeZoneName}' has the code '{LocalizedNameResponse["TimeZoneCode"]}'."); #endregion Call an unbound function that requires parameters. #region Call a bound function. //Retrieve the total time, in minutes, spent on all tasks associated with an incident. //Uses the CalculateTotalTimeIncident function: //https://docs.microsoft.com/dynamics365/customer-engagement/web-api/calculatetotaltimeincident, //which returns a CalculateTotalTimeIncidentResponse complex type: //https://docs.microsoft.com/en-us/dynamics365/customer-engagement/web-api/calculatetotaltimeincidentresponse. Console.WriteLine("Bound function: CalculateTotalTimeIncident"); var cttir = svc.Get($"{incident1Uri}/Microsoft.Dynamics.CRM.CalculateTotalTimeIncident()"); Console.WriteLine($"\tThe total duration of tasks associated with the " + $"incident is {cttir["TotalTime"]} minutes."); #endregion Call a bound function. #region Call an unbound action that requires parameters. //Close an opportunity and mark it as won. Uses the WinOpportunity action: //https://docs.microsoft.com/en-us/dynamics365/customer-engagement/web-api/winopportunity, //which takes a int32 status code and an opportunityclose entity type: //https://docs.microsoft.com/en-us/dynamics365/customer-engagement/web-api/opportunityclose. Console.WriteLine("UnBound function: WinOpportunity"); var winOpportParams = new JObject( new JProperty("Status", 3), new JProperty("OpportunityClose", new JObject( new JProperty("subject", "Won Opportunity"), new JProperty("*****@*****.**", opportunity1Uri) ))); svc.Post("WinOpportunity", winOpportParams); Console.WriteLine("\tOpportunity won."); #endregion Call an unbound action that requires parameters. #region Call a bound action that requires parameters. //Add a new letter tracking activity to the current user's queue. Uses the AddToQueue //action: https://docs.microsoft.com/en-us/dynamics365/customer-engagement/web-api/addtoqueue, which is bound to the queue //entity type: https://docs.microsoft.com/en-us/powerapps/developer/common-data-service/reference/entities/queue, and returns a //AddToQueueResponse complex type: https://docs.microsoft.com/en-us/dynamics365/customer-engagement/web-api/addtoqueueresponse. JObject letter = new JObject { ["description"] = "Example letter" }; var letterUri = svc.PostCreate("letters", letter); var letterActivityId = (svc.Get($"{letterUri}/activityid"))["value"]; var queueRef = svc.Get($"systemusers({myUserId})/queueid/$ref"); var myQueueUri = new Uri(queueRef["@odata.id"].ToString()); JObject addToQueueParams = new JObject( new JProperty("Target", new JObject( new JProperty("activityid", letterActivityId), new JProperty("@odata.type", "Microsoft.Dynamics.CRM.letter") ))); Console.WriteLine("Bound action: AddToQueue"); var queueResponse = svc.Post($"{myQueueUri}/Microsoft.Dynamics.CRM.AddToQueue", addToQueueParams); var queueItemId = queueResponse["QueueItemId"].ToString(); Console.WriteLine($"\tQueueItemId returned from AddToQueue action: {queueItemId}"); #endregion Call a bound action that requires parameters. if (InstallFunctionsAndActionsSolution(svc)) { #region Call a bound custom action that requires parameters. //Add a note to a specified contact. Uses the custom action sample_AddNoteToContact, which //is bound to the contact to annotate, and takes a single param, the note to add. It also //returns the URI to the new annotation. Console.WriteLine("Custom action: sample_AddNoteToContact"); var note1 = new JObject { ["NoteTitle"] = "Note Title", ["NoteText"] = "The text content of the note." }; var noteRef = svc.Post($"{contact1Uri}/Microsoft.Dynamics.CRM.sample_AddNoteToContact", note1); //Retrieve the created note and the related contact full name; var noteAndContact = svc.Get($"{svc.BaseAddress}annotations({noteRef["annotationid"]})" + $"?$select=subject,notetext&$expand=objectid_contact($select=fullname)"); Console.WriteLine($"\tA note with the subject '{noteAndContact["subject"]}' \n" + $"\tand the text '{noteAndContact["notetext"]}' was created and associated with \n" + $"\tthe contact '{noteAndContact["objectid_contact"]["fullname"]}'"); #endregion Call a bound custom action that requires parameters. #region Call an unbound custom action that requires parameters. //Create a customer of the specified type, using the custom action sample_CreateCustomer, //which takes two parameters: the type of customer ('account' or 'contact') and the name of //the new customer. var customerParam = new JObject { ["CustomerType"] = "account", ["AccountName"] = "New account customer (sample)" }; Console.WriteLine("Custom action: sample_CreateCustomer"); svc.Post("sample_CreateCustomer", customerParam); Console.WriteLine($"\tThe account '{customerParam["AccountName"]}' was created."); //Try to call the same custom action with invalid parameters, here the same name is //not valid for a contact. (ContactFirstname and ContactLastName parameters are //required when CustomerType is contact.) var invalidCustomerParam = new JObject { ["CustomerType"] = "contact", ["AccountName"] = "New account customer (sample)" }; try { Console.WriteLine("Custom action: sample_CreateCustomer with invalid parameters:"); svc.Post("sample_CreateCustomer", invalidCustomerParam); } catch (CDSWebApiException ex) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine($"Expected Error:\t{ex.Message}\n" + $"\tStatusCode: {ex.StatusCode}\n" + $"\tReasonPhrase: {ex.ReasonPhrase}\n" + $"\tErrorCode: {ex.ErrorCode}"); Console.ResetColor(); } #endregion Call an unbound custom action that requires parameters. Console.WriteLine("Uninstalling solution containing custom actions."); UnInstallFunctionsAndActionsSolution(svc); } //Delete records used by this sample Console.WriteLine("Deleting records created for this sample."); DeleteRequiredRecords(svc); Console.WriteLine("\n--Functions And Actions Completed--"); }
public static void Run(CDSWebApiService svc, bool deleteCreatedRecords) { Console.WriteLine("--Starting Basic Operations--"); #region Section 1: Basic Create and Update operations Console.WriteLine("--Section 1 started--"); //Create a contact var contact1 = new JObject { { "firstname", "Rafel" }, { "lastname", "Shillo" } }; Uri contact1Uri = svc.PostCreate("contacts", contact1); Console.WriteLine($"Contact '{contact1["firstname"]} " + $"{contact1["lastname"]}' created."); entityUris.Add(contact1Uri); //To delete later Console.WriteLine($"Contact URI: {contact1Uri}"); //Update a contact JObject contact1Add = new JObject { { "annualincome", 80000 }, { "jobtitle", "Junior Developer" } }; svc.Patch(contact1Uri, contact1Add); Console.WriteLine( $"Contact '{contact1["firstname"]} {contact1["lastname"]}' " + $"updated with jobtitle and annual income"); //Retrieve a contact var retrievedcontact1 = svc.Get(contact1Uri.ToString() + "?$select=fullname,annualincome,jobtitle,description"); Console.WriteLine($"Contact '{retrievedcontact1["fullname"]}' retrieved: \n" + $"\tAnnual income: {retrievedcontact1["annualincome"]}\n" + $"\tJob title: {retrievedcontact1["jobtitle"]} \n" + //description is initialized empty. $"\tDescription: {retrievedcontact1["description"]}."); //Modify specific properties and then update entity instance. JObject contact1Update = new JObject { { "jobtitle", "Senior Developer" }, { "annualincome", 95000 }, { "description", "Assignment to-be-determined" } }; svc.Patch(contact1Uri, contact1Update); Console.WriteLine($"Contact '{retrievedcontact1["fullname"]}' updated:\n" + $"\tJob title: {contact1Update["jobtitle"]}\n" + $"\tAnnual income: {contact1Update["annualincome"]}\n" + $"\tDescription: {contact1Update["description"]}\n"); // Change just one property string telephone1 = "555-0105"; svc.Put(contact1Uri, "telephone1", telephone1); Console.WriteLine($"Contact '{retrievedcontact1["fullname"]}' " + $"phone number updated."); //Now retrieve just the single property. var telephone1Value = svc.Get($"{contact1Uri}/telephone1"); Console.WriteLine($"Contact's telephone # is: {telephone1Value["value"]}."); #endregion Section 1: Basic Create and Update operations #region Section 2: Create record associated to another /// <summary> /// Demonstrates creation of entity instance and simultaneous association to another, /// existing entity. /// </summary> /// Console.WriteLine("\n--Section 2 started--"); //Create a new account and associate with existing contact in one operation. var account1 = new JObject { { "name", "Contoso Ltd" }, { "telephone1", "555-5555" }, { "*****@*****.**", contact1Uri } }; var account1Uri = svc.PostCreate("accounts", account1); entityUris.Add(account1Uri); //To delete later Console.WriteLine($"Account '{account1["name"]}' created."); Console.WriteLine($"Account URI: {account1Uri}"); //Retrieve account name and primary contact info JObject retrievedAccount1 = svc.Get($"{account1Uri}?$select=name," + $"&$expand=primarycontactid($select=fullname,jobtitle,annualincome)") as JObject; Console.WriteLine($"Account '{retrievedAccount1["name"]}' has primary contact " + $"'{retrievedAccount1["primarycontactid"]["fullname"]}':"); Console.WriteLine($"\tJob title: {retrievedAccount1["primarycontactid"]["jobtitle"]} \n" + $"\tAnnual income: {retrievedAccount1["primarycontactid"]["annualincome"]}"); #endregion Section 2: Create record associated to another #region Section 3: Create related entities /// <summary> /// Demonstrates creation of entity instance and related entities in a single operation. /// </summary> /// Console.WriteLine("\n--Section 3 started--"); //Create the following entries in one operation: an account, its // associated primary contact, and open tasks for that contact. These // entity types have the following relationships: // Accounts // |---[Primary] Contact (N-to-1) // |---Tasks (1-to-N) //Build the Account object inside-out, starting with most nested type(s) JArray tasks = new JArray(); JObject task1 = new JObject { { "subject", "Sign invoice" }, { "description", "Invoice #12321" }, { "scheduledend", DateTimeOffset.Parse("4/19/2019") } }; tasks.Add(task1); JObject task2 = new JObject { { "subject", "Setup new display" }, { "description", "Theme is - Spring is in the air" }, { "scheduledstart", DateTimeOffset.Parse("4/20/2019") } }; tasks.Add(task2); JObject task3 = new JObject { { "subject", "Conduct training" }, { "description", "Train team on making our new blended coffee" }, { "scheduledstart", DateTimeOffset.Parse("6/1/2019") } }; tasks.Add(task3); JObject contact2 = new JObject { { "firstname", "Susie" }, { "lastname", "Curtis" }, { "jobtitle", "Coffee Master" }, { "annualincome", 48000 }, //Add related tasks using corresponding navigation property { "Contact_Tasks", tasks } }; JObject account2 = new JObject { { "name", "Fourth Coffee" }, //Add related contacts using corresponding navigation property { "primarycontactid", contact2 } }; //Create the account and related records Uri account2Uri = svc.PostCreate("accounts", account2); Console.WriteLine($"Account '{account2["name"]} created."); entityUris.Add(account2Uri); //To delete later Console.WriteLine($"Contact URI: {account2Uri}"); //Retrieve account, primary contact info, and assigned tasks for contact. //CDS only supports querying-by-expansion one level deep, so first query // account-primary contact. var retrievedAccount2 = svc.Get($"{account2Uri}?$select=name," + $"&$expand=primarycontactid($select=fullname,jobtitle,annualincome)"); Console.WriteLine($"Account '{retrievedAccount2["name"]}' " + $"has primary contact '{retrievedAccount2["primarycontactid"]["fullname"]}':"); Console.WriteLine($"\tJob title: {retrievedAccount2["primarycontactid"]["jobtitle"]} \n" + $"\tAnnual income: {retrievedAccount2["primarycontactid"]["annualincome"]}"); //Next retrieve same contact and its assigned tasks. //Don't have a saved URI for contact 'Susie Curtis', so create one // from base address and entity ID. Uri contact2Uri = new Uri($"{svc.BaseAddress}contacts({retrievedAccount2["primarycontactid"]["contactid"]})"); //Retrieve the contact var retrievedcontact2 = svc.Get($"{contact2Uri}?$select=fullname," + $"&$expand=Contact_Tasks($select=subject,description,scheduledstart,scheduledend)"); Console.WriteLine($"Contact '{retrievedcontact2["fullname"]}' has the following assigned tasks:"); foreach (JToken tk in retrievedcontact2["Contact_Tasks"]) { Console.WriteLine( $"Subject: {tk["subject"]}, \n" + $"\tDescription: {tk["description"]}\n" + $"\tStart: {tk["scheduledstart"].Value<DateTime>().ToString("d")}\n" + $"\tEnd: {tk["scheduledend"].Value<DateTime>().ToString("d")}\n"); } #endregion Section 3: Create related entities #region Section 4: Associate and Disassociate entities /// <summary> /// Demonstrates associating and disassociating of existing entity instances. /// </summary> Console.WriteLine("\n--Section 4 started--"); //Add 'Rafel Shillo' to the contact list of 'Fourth Coffee', // a 1-to-N relationship. JObject rel1 = new JObject { { "@odata.id", contact1Uri } }; //relationship object for msg content Uri navUri1 = new Uri($"{account2Uri}/contact_customer_accounts/$ref"); //Create relationship svc.Post(navUri1.ToString(), rel1); Console.WriteLine($"Contact '{retrievedcontact1["fullname"]}' " + $"associated to account '{account2["name"]}'."); //Retrieve and output all contacts for account 'Fourth Coffee'. var retrievedContactList1 = svc.Get($"{account2Uri}/contact_customer_accounts?" + $"$select=fullname,jobtitle"); Console.WriteLine($"Contact list for account '{retrievedAccount2["name"]}':"); foreach (JToken ct in retrievedContactList1["value"]) { Console.WriteLine($"\tName: {ct["fullname"]}, Job title: {ct["jobtitle"]}"); } //Dissociate the contact from the account. For a collection-valued // navigation property, must append URI of referenced entity. Uri dis1Uri = new Uri($"{navUri1}?$id={contact1Uri}"); //Equivalently, could have dissociated from the other end of the // relationship, using the single-valued navigation ref, located in // the contact 'Peter Cambel'. This dissociation URI has a simpler form: // [Org URI]/api/data/v9.1/contacts([contactid#])/parentcustomerid_account/$ref svc.Delete(dis1Uri); //'Rafel Shillo' was removed from the the contact list of 'Fourth Coffee' //Associate an opportunity to a competitor, an N-to-N relationship. //First, create the required entity instances. JObject comp1 = new JObject { { "name", "Adventure Works" }, { "strengths", "Strong promoter of private tours for multi-day outdoor adventures" } }; Uri comp1Uri = svc.PostCreate("competitors", comp1); entityUris.Add(comp1Uri); //To delete later JObject oppor1 = new JObject { ["name"] = "River rafting adventure", ["description"] = "Sales team on a river-rafting offsite and team building" }; Uri oppor1Uri = svc.PostCreate("opportunities", oppor1); entityUris.Add(oppor1Uri); //To delete later //Associate opportunity to competitor via opportunitycompetitors_association. // navigation property. JObject rel2 = new JObject { { "@odata.id", comp1Uri } }; Uri navUri2 = new Uri($"{oppor1Uri}/opportunitycompetitors_association/$ref"); svc.Post(navUri2.ToString(), rel2); Console.WriteLine($"Opportunity '{oppor1["name"]}' associated with competitor '{comp1["name"]}'."); //Retrieve all opportunities for competitor 'Adventure Works'. var retrievedOpporList1 = svc.Get($"{comp1Uri}?$select=name,&$expand=opportunitycompetitors_association($select=name,description)"); Console.WriteLine($"Competitor '{retrievedOpporList1["name"]}' has the following opportunities:"); foreach (JToken op in retrievedOpporList1["opportunitycompetitors_association"]) { Console.WriteLine($"\tName: {op["name"]}, \n" + $"\tDescription: {op["description"]}"); } //Dissociate opportunity from competitor. svc.Delete(new Uri($"{navUri2}?$id={comp1Uri}")); // 'River rafting adventure' opportunity disassociated with 'Adventure Works' competitor #endregion Section 4: Associate and Disassociate entities #region Section 5: Delete sample entities Console.WriteLine("\n--Section 5 started--"); //Delete all the created sample entities. Note that explicit deletion is not required // for contact tasks because these are automatically cascade-deleted with owner. if (!deleteCreatedRecords) { Console.Write("\nDo you want these entity records deleted? (y/n) [y]: "); String answer = Console.ReadLine(); answer = answer.Trim(); if (!(answer.StartsWith("y") || answer.StartsWith("Y") || answer == string.Empty)) { entityUris.Clear(); } else { Console.WriteLine("\nDeleting created records."); } } else { Console.WriteLine("\nDeleting created records."); } foreach (Uri entityUrl in entityUris) { svc.Delete(entityUrl); } #endregion Section 5: Delete sample entities Console.WriteLine("--Basic Operations Completed--"); }