public async override Task RunAsync(AuthorizationData authorizationData)
        {
            try
            {
                Service = new ServiceClient <ICampaignManagementService>(authorizationData);

                // Add a campaign that will later be associated with negative keywords.

                var campaign = new Campaign
                {
                    Name           = "Women's Shoes" + DateTime.UtcNow,
                    Description    = "Red shoes line.",
                    BudgetType     = BudgetLimitType.MonthlyBudgetSpendUntilDepleted,
                    MonthlyBudget  = 1000.00,
                    TimeZone       = "PacificTimeUSCanadaTijuana",
                    DaylightSaving = true
                };

                var campaignIds = await AddCampaignsAsync(authorizationData.AccountId, new[] { campaign });

                // Print the new assigned campaign identifier

                PrintCampaignIdentifiers(campaignIds);

                // You may choose to associate an exclusive set of negative keywords to an individual campaign
                // or ad group. An exclusive set of negative keywords cannot be shared with other campaigns
                // or ad groups. This example only associates negative keywords with a campaign.

                var entityNegativeKeywords = new[]
                {
                    new EntityNegativeKeyword
                    {
                        EntityId         = campaignIds[0],
                        EntityType       = "Campaign",
                        NegativeKeywords = new[]
                        {
                            new NegativeKeyword
                            {
                                MatchType = MatchType.Phrase,
                                Text      = "auto"
                            }
                        }
                    }
                };

                AddNegativeKeywordsToEntitiesResponse addNegativeKeywordsToEntitiesResponse =
                    await AddNegativeKeywordsToEntitiesAsync(entityNegativeKeywords);

                PrintNegativeKeywordIds(addNegativeKeywordsToEntitiesResponse.NegativeKeywordIds);
                PrintNestedPartialErrors(addNegativeKeywordsToEntitiesResponse.NestedPartialErrors);
                if (addNegativeKeywordsToEntitiesResponse.NestedPartialErrors == null ||
                    addNegativeKeywordsToEntitiesResponse.NestedPartialErrors.Count == 0)
                {
                    OutputStatusMessage("Added an exclusive set of negative keywords to the Campaign.\n");
                    PrintNegativeKeywordIds(addNegativeKeywordsToEntitiesResponse.NegativeKeywordIds);
                }
                else
                {
                    PrintNestedPartialErrors(addNegativeKeywordsToEntitiesResponse.NestedPartialErrors);
                }

                GetNegativeKeywordsByEntityIdsResponse getNegativeKeywordsByEntityIdsResponse =
                    await GetNegativeKeywordsByEntityIdsAsync(campaignIds, "Campaign", authorizationData.AccountId);

                PrintEntityNegativeKeywords(getNegativeKeywordsByEntityIdsResponse.EntityNegativeKeywords);
                PrintPartialErrors(getNegativeKeywordsByEntityIdsResponse.PartialErrors);
                if (getNegativeKeywordsByEntityIdsResponse.PartialErrors == null ||
                    getNegativeKeywordsByEntityIdsResponse.PartialErrors.Count == 0)
                {
                    OutputStatusMessage("Retrieved an exclusive set of negative keywords for the Campaign.\n");
                    PrintEntityNegativeKeywords(getNegativeKeywordsByEntityIdsResponse.EntityNegativeKeywords);
                }
                else
                {
                    PrintPartialErrors(getNegativeKeywordsByEntityIdsResponse.PartialErrors);
                }

                // If you attempt to delete a negative keyword without an identifier the operation will
                // succeed but will return partial errors corresponding to the index of the negative keyword
                // that was not deleted.
                var nestedPartialErrors = (BatchErrorCollection[]) await DeleteNegativeKeywordsFromEntitiesAsync(entityNegativeKeywords);

                if (nestedPartialErrors == null || nestedPartialErrors.Length == 0)
                {
                    OutputStatusMessage("Deleted an exclusive set of negative keywords from the Campaign.\n");
                }
                else
                {
                    OutputStatusMessage("Attempt to DeleteNegativeKeywordsFromEntities without NegativeKeyword identifier partially fails by design.");
                    PrintNestedPartialErrors(nestedPartialErrors);
                }

                // Delete the negative keywords with identifiers that were returned above.
                nestedPartialErrors = (BatchErrorCollection[]) await DeleteNegativeKeywordsFromEntitiesAsync(
                    getNegativeKeywordsByEntityIdsResponse.EntityNegativeKeywords);

                if (nestedPartialErrors == null || nestedPartialErrors.Length == 0)
                {
                    OutputStatusMessage("Deleted an exclusive set of negative keywords from the Campaign.\n");
                }
                else
                {
                    PrintNestedPartialErrors(nestedPartialErrors);
                }

                // Negative keywords can also be added and deleted from a shared negative keyword list.
                // The negative keyword list can be shared or associated with multiple campaigns.
                // NegativeKeywordList inherits from SharedList which inherits from SharedEntity.

                var negativeKeywordList = new NegativeKeywordList
                {
                    Name = "My Negative Keyword List" + DateTime.UtcNow,
                    Type = "NegativeKeywordList"
                };

                SharedListItem[] negativeKeywords =
                {
                    new NegativeKeyword
                    {
                        Text      = "car",
                        Type      = "NegativeKeyword",
                        MatchType = MatchType.Exact
                    },
                    new NegativeKeyword
                    {
                        Text      = "car",
                        Type      = "NegativeKeyword",
                        MatchType = MatchType.Phrase
                    }
                };

                // You can create a new list for negative keywords with or without negative keywords.

                var addSharedEntityResponse = await AddSharedEntityAsync(negativeKeywordList, negativeKeywords);

                var    sharedEntityId = addSharedEntityResponse.SharedEntityId;
                long[] listItemIds    = addSharedEntityResponse.ListItemIds.ToArray();

                OutputStatusMessage(String.Format("NegativeKeywordList successfully added to account library and assigned identifer {0}\n", sharedEntityId));

                PrintNegativeKeywordResults(
                    sharedEntityId,
                    negativeKeywords,
                    listItemIds,
                    addSharedEntityResponse.PartialErrors.ToArray());

                OutputStatusMessage("Negative keywords currently in NegativeKeywordList:");
                negativeKeywords = (SharedListItem[]) await GetListItemsBySharedListAsync(new NegativeKeywordList { Id = sharedEntityId });

                if (negativeKeywords == null || negativeKeywords.Length == 0)
                {
                    OutputStatusMessage("None\n");
                }
                else
                {
                    PrintNegativeKeywords(negativeKeywords.Cast <NegativeKeyword>());
                }

                // To update the list of negative keywords, you must either add or remove from the list
                // using the respective AddListItemsToSharedList or DeleteListItemsFromSharedList operations.
                // To remove the negative keywords from the list pass the negative keyword identifers
                // and negative keyword list (SharedEntity) identifer.

                var partialErrors = await DeleteListItemsFromSharedListAsync(listItemIds, new NegativeKeywordList { Id = sharedEntityId });

                if (partialErrors == null || !partialErrors.Any())
                {
                    OutputStatusMessage("Deleted most recently added negative keywords from negative keyword list.\n");
                }
                else
                {
                    PrintPartialErrors(partialErrors);
                }

                OutputStatusMessage("Negative keywords currently in NegativeKeywordList:");
                negativeKeywords = (SharedListItem[]) await GetListItemsBySharedListAsync(new NegativeKeywordList { Id = sharedEntityId });

                if (negativeKeywords == null || negativeKeywords.Length == 0)
                {
                    OutputStatusMessage("None\n");
                }
                else
                {
                    PrintNegativeKeywords(negativeKeywords.Cast <NegativeKeyword>());
                }

                // Whether you created the list with or without negative keywords, more can be added
                // using the AddListItemsToSharedList operation.

                negativeKeywords = new SharedListItem[]
                {
                    new NegativeKeyword
                    {
                        Text      = "auto",
                        Type      = "NegativeKeyword",
                        MatchType = MatchType.Exact
                    },
                    new NegativeKeyword
                    {
                        Text      = "auto",
                        Type      = "NegativeKeyword",
                        MatchType = MatchType.Phrase
                    }
                };

                var addListItemsToSharedListResponse = await AddListItemsToSharedListAsync(
                    negativeKeywords,
                    new NegativeKeywordList { Id = sharedEntityId });

                listItemIds = addListItemsToSharedListResponse.ListItemIds.ToArray();

                PrintNegativeKeywordResults(
                    sharedEntityId,
                    negativeKeywords,
                    listItemIds,
                    addListItemsToSharedListResponse.PartialErrors.ToArray());

                OutputStatusMessage("Negative keywords currently in NegativeKeywordList:");
                negativeKeywords = (SharedListItem[]) await GetListItemsBySharedListAsync(new NegativeKeywordList { Id = sharedEntityId });

                if (negativeKeywords == null || negativeKeywords.Length == 0)
                {
                    OutputStatusMessage("None\n");
                }
                else
                {
                    PrintNegativeKeywords(negativeKeywords.Cast <NegativeKeyword>());
                }

                // You can update the name of the negative keyword list.

                negativeKeywordList = new NegativeKeywordList
                {
                    Id   = sharedEntityId,
                    Name = "My Updated Negative Keyword List",
                    Type = "NegativeKeywordList"
                };

                partialErrors = await UpdateSharedEntitiesAsync(new SharedEntity[] { negativeKeywordList });

                if (partialErrors == null || !partialErrors.Any())
                {
                    OutputStatusMessage(String.Format("Updated Negative Keyword List Name to {0}.\n", negativeKeywordList.Name));
                }
                else
                {
                    PrintPartialErrors(partialErrors);
                }

                // Get and print the negative keyword lists and return the list of identifiers.

                const string sharedEntityType = "NegativeKeywordList";
                var          sharedEntityIds  = await GetAndPrintSharedEntityIdentifiersAsync(sharedEntityType);

                // Negative keywords were added to the negative keyword list above. You can associate the
                // shared list of negative keywords with a campaign with or without negative keywords.
                // Shared negative keyword lists cannot be associated with an ad group. An ad group can only
                // be assigned an exclusive set of negative keywords.

                var associations = new[]
                {
                    new SharedEntityAssociation
                    {
                        EntityId         = campaignIds[0],
                        EntityType       = "Campaign",
                        SharedEntityId   = sharedEntityId,
                        SharedEntityType = "NegativeKeywordList"
                    }
                };

                partialErrors = await SetSharedEntityAssociationsAsync(associations);

                if (partialErrors == null || !partialErrors.Any())
                {
                    OutputStatusMessage(String.Format("Associated CampaignId {0} with Negative Keyword List Id {1}.\n",
                                                      campaignIds[0], sharedEntityId));
                }
                else
                {
                    PrintPartialErrors(partialErrors);
                }

                // Get and print the associations either by Campaign or NegativeKeywordList identifier.
                GetSharedEntityAssociationsByEntityIdsResponse getSharedEntityAssociationsByEntityIdsResponse =
                    await GetSharedEntityAssociationsByEntityIdsAsync(new[] { campaignIds[0] }, "Campaign", "NegativeKeywordList");

                PrintSharedEntityAssociations(getSharedEntityAssociationsByEntityIdsResponse.Associations);
                PrintPartialErrors(getSharedEntityAssociationsByEntityIdsResponse.PartialErrors);

                // Currently the GetSharedEntityAssociationsBySharedEntityIds operation accepts only one shared entity identifier in the list.
                GetSharedEntityAssociationsBySharedEntityIdsResponse getSharedEntityAssociationsBySharedEntityIdsResponse =
                    await GetSharedEntityAssociationsBySharedEntityIdsAsync("Campaign", new[] { sharedEntityIds[sharedEntityIds.Count - 1] }, "NegativeKeywordList");

                PrintSharedEntityAssociations(getSharedEntityAssociationsBySharedEntityIdsResponse.Associations);
                PrintPartialErrors(getSharedEntityAssociationsBySharedEntityIdsResponse.PartialErrors);

                // Explicitly delete the association between the campaign and the negative keyword list.

                partialErrors = await DeleteSharedEntityAssociationsAsync(associations);

                if (partialErrors == null || !partialErrors.Any())
                {
                    OutputStatusMessage("Deleted NegativeKeywordList associations\n");
                }
                else
                {
                    PrintPartialErrors(partialErrors);
                }

                // Delete the campaign and any remaining assocations.

                DeleteCampaigns(authorizationData.AccountId, new[] { campaignIds[0] });
                OutputStatusMessage(String.Format("Deleted CampaignId {0}\n", campaignIds[0]));

                // DeleteCampaigns does not delete the negative keyword list from the account's library.
                // Call the DeleteSharedEntities operation to delete the shared entities.

                partialErrors = await DeleteSharedEntitiesAsync(new SharedEntity[] { new NegativeKeywordList {
                                                                                         Id = sharedEntityId
                                                                                     } });

                if (partialErrors == null || !partialErrors.Any())
                {
                    OutputStatusMessage(String.Format("Deleted Negative Keyword List (SharedEntity) Id {0}\n", sharedEntityId));
                }
                else
                {
                    PrintPartialErrors(partialErrors);
                }
            }
            // Catch authentication exceptions
            catch (OAuthTokenRequestException ex)
            {
                OutputStatusMessage(string.Format("Couldn't get OAuth tokens. Error: {0}. Description: {1}", ex.Details.Error, ex.Details.Description));
            }
            // Catch Campaign Management service exceptions
            catch (FaultException <Microsoft.BingAds.CampaignManagement.AdApiFaultDetail> ex)
            {
                OutputStatusMessage(string.Join("; ", ex.Detail.Errors.Select(error => string.Format("{0}: {1}", error.Code, error.Message))));
            }
            catch (FaultException <Microsoft.BingAds.CampaignManagement.ApiFaultDetail> ex)
            {
                OutputStatusMessage(string.Join("; ", ex.Detail.OperationErrors.Select(error => string.Format("{0}: {1}", error.Code, error.Message))));
                OutputStatusMessage(string.Join("; ", ex.Detail.BatchErrors.Select(error => string.Format("{0}: {1}", error.Code, error.Message))));
            }
            catch (FaultException <Microsoft.BingAds.CampaignManagement.EditorialApiFaultDetail> ex)
            {
                OutputStatusMessage(string.Join("; ", ex.Detail.OperationErrors.Select(error => string.Format("{0}: {1}", error.Code, error.Message))));
                OutputStatusMessage(string.Join("; ", ex.Detail.BatchErrors.Select(error => string.Format("{0}: {1}", error.Code, error.Message))));
            }
            catch (Exception ex)
            {
                OutputStatusMessage(ex.Message);
            }
        }
        /// <summary>
        /// Outputs the list item identifiers, as well as any partial errors
        /// </summary>
        /// <param name="sharedListId"></param>
        /// <param name="sharedListItems"></param>
        /// <param name="sharedListItemIds"></param>
        /// <param name="partialErrors"></param>
        protected void OutputNegativeKeywordsWithPartialErrors(
            long sharedListId,
            SharedListItem[] sharedListItems,
            long[] sharedListItemIds,
            BatchError[] partialErrors)
        {
            if (sharedListItemIds == null)
            {
                return;
            }

            for (var index = 0; index < sharedListItems.Length; index++)
            {
                // Determine if the SharedListItem is a NegativeKeyword.
                if (sharedListItems[index] is NegativeKeyword)
                {
                    // Determine if the corresponding index has a valid identifier
                    if (sharedListItemIds[index] > 0)
                    {
                        OutputStatusMessage(String.Format("NegativeKeyword[{0}] ({1}) successfully added to NegativeKeywordList ({2}) and assigned Negative Keyword Id {3}.",
                                  index,
                                  ((NegativeKeyword)(sharedListItems[index])).Text,
                                  sharedListId,
                                  sharedListItemIds[index]));
                    }
                }
                else
                {
                    OutputStatusMessage("SharedListItem is not a NegativeKeyword.");
                }
            }

            OutputPartialErrors(partialErrors);
        }
Example #3
0
        public async override Task RunAsync(AuthorizationData authorizationData)
        {
            try
            {
                ApiEnvironment environment = ((OAuthDesktopMobileAuthCodeGrant)authorizationData.Authentication).Environment;

                CampaignManagementExampleHelper CampaignManagementExampleHelper =
                    new CampaignManagementExampleHelper(this.OutputStatusMessage);
                CampaignManagementExampleHelper.CampaignManagementService =
                    new ServiceClient <ICampaignManagementService>(authorizationData, environment);

                // Add a campaign that will later be associated with negative keywords.

                var campaigns = new[] {
                    new Campaign
                    {
                        Name        = "Women's Shoes " + DateTime.UtcNow,
                        Description = "Red shoes line.",

                        // You must choose to set either the shared  budget ID or daily amount.
                        // You can set one or the other, but you may not set both.
                        BudgetId      = null,
                        DailyBudget   = 50,
                        BudgetType    = BudgetLimitType.DailyBudgetStandard,
                        BiddingScheme = new EnhancedCpcBiddingScheme(),

                        TimeZone = "PacificTimeUSCanadaTijuana",
                    }
                };

                AddCampaignsResponse addCampaignsResponse = await CampaignManagementExampleHelper.AddCampaignsAsync(authorizationData.AccountId, campaigns);

                long?[]      campaignIds    = addCampaignsResponse.CampaignIds.ToArray();
                BatchError[] campaignErrors = addCampaignsResponse.PartialErrors.ToArray();
                CampaignManagementExampleHelper.OutputArrayOfLong(campaignIds);
                CampaignManagementExampleHelper.OutputArrayOfBatchError(campaignErrors);
                long campaignId = (long)campaignIds[0];

                // You may choose to associate an exclusive set of negative keywords to an individual campaign
                // or ad group. An exclusive set of negative keywords cannot be shared with other campaigns
                // or ad groups. This example only associates negative keywords with a campaign.

                var entityNegativeKeywords = new[]
                {
                    new EntityNegativeKeyword
                    {
                        EntityId         = campaignId,
                        EntityType       = "Campaign",
                        NegativeKeywords = new[]
                        {
                            new NegativeKeyword
                            {
                                MatchType = MatchType.Phrase,
                                Text      = "auto"
                            },
                            new NegativeKeyword
                            {
                                MatchType = MatchType.Phrase,
                                Text      = "auto"
                            },
                        }
                    }
                };

                AddNegativeKeywordsToEntitiesResponse addNegativeKeywordsToEntitiesResponse =
                    await CampaignManagementExampleHelper.AddNegativeKeywordsToEntitiesAsync(entityNegativeKeywords);

                CampaignManagementExampleHelper.OutputArrayOfIdCollection(addNegativeKeywordsToEntitiesResponse.NegativeKeywordIds);
                CampaignManagementExampleHelper.OutputArrayOfBatchErrorCollection(addNegativeKeywordsToEntitiesResponse.NestedPartialErrors);
                if (addNegativeKeywordsToEntitiesResponse.NestedPartialErrors == null ||
                    addNegativeKeywordsToEntitiesResponse.NestedPartialErrors.Count == 0)
                {
                    OutputStatusMessage("Added an exclusive set of negative keywords to the Campaign.\n");
                    CampaignManagementExampleHelper.OutputArrayOfIdCollection(addNegativeKeywordsToEntitiesResponse.NegativeKeywordIds);
                }
                else
                {
                    CampaignManagementExampleHelper.OutputArrayOfBatchErrorCollection(addNegativeKeywordsToEntitiesResponse.NestedPartialErrors);
                }

                GetNegativeKeywordsByEntityIdsResponse getNegativeKeywordsByEntityIdsResponse =
                    await CampaignManagementExampleHelper.GetNegativeKeywordsByEntityIdsAsync(new[] { campaignId }, "Campaign", authorizationData.AccountId);

                CampaignManagementExampleHelper.OutputArrayOfEntityNegativeKeyword(getNegativeKeywordsByEntityIdsResponse.EntityNegativeKeywords);
                CampaignManagementExampleHelper.OutputArrayOfBatchError(getNegativeKeywordsByEntityIdsResponse.PartialErrors);
                if (getNegativeKeywordsByEntityIdsResponse.PartialErrors == null ||
                    getNegativeKeywordsByEntityIdsResponse.PartialErrors.Count == 0)
                {
                    OutputStatusMessage("Retrieved an exclusive set of negative keywords for the Campaign.\n");
                    CampaignManagementExampleHelper.OutputArrayOfEntityNegativeKeyword(getNegativeKeywordsByEntityIdsResponse.EntityNegativeKeywords);
                }
                else
                {
                    CampaignManagementExampleHelper.OutputArrayOfBatchError(getNegativeKeywordsByEntityIdsResponse.PartialErrors);
                }

                // If you attempt to delete a negative keyword without an identifier the operation will
                // succeed but will return partial errors corresponding to the index of the negative keyword
                // that was not deleted.
                var nestedPartialErrors = (BatchErrorCollection[])(await CampaignManagementExampleHelper.DeleteNegativeKeywordsFromEntitiesAsync(entityNegativeKeywords)).NestedPartialErrors;
                if (nestedPartialErrors == null || nestedPartialErrors.Length == 0)
                {
                    OutputStatusMessage("Deleted an exclusive set of negative keywords from the Campaign.\n");
                }
                else
                {
                    OutputStatusMessage("Attempt to DeleteNegativeKeywordsFromEntities without NegativeKeyword identifier partially fails by design.");
                    CampaignManagementExampleHelper.OutputArrayOfBatchErrorCollection(nestedPartialErrors);
                }

                // Delete the negative keywords with identifiers that were returned above.
                nestedPartialErrors = (BatchErrorCollection[])(await CampaignManagementExampleHelper.DeleteNegativeKeywordsFromEntitiesAsync(
                                                                   getNegativeKeywordsByEntityIdsResponse.EntityNegativeKeywords)).NestedPartialErrors;
                if (nestedPartialErrors == null || nestedPartialErrors.Length == 0)
                {
                    OutputStatusMessage("Deleted an exclusive set of negative keywords from the Campaign.\n");
                }
                else
                {
                    CampaignManagementExampleHelper.OutputArrayOfBatchErrorCollection(nestedPartialErrors);
                }

                // Negative keywords can also be added and deleted from a shared negative keyword list.
                // The negative keyword list can be shared or associated with multiple campaigns.
                // NegativeKeywordList inherits from SharedList which inherits from SharedEntity.

                var negativeKeywordList = new NegativeKeywordList
                {
                    Name = "My Negative Keyword List" + DateTime.UtcNow,
                    Type = "NegativeKeywordList"
                };

                SharedListItem[] negativeKeywords =
                {
                    new NegativeKeyword
                    {
                        Text      = "car",
                        Type      = "NegativeKeyword",
                        MatchType = MatchType.Exact
                    },
                    new NegativeKeyword
                    {
                        Text      = "car",
                        Type      = "NegativeKeyword",
                        MatchType = MatchType.Phrase
                    }
                };

                // You can create a new list for negative keywords with or without negative keywords.

                var addSharedEntityResponse = await CampaignManagementExampleHelper.AddSharedEntityAsync(negativeKeywordList, negativeKeywords);

                var    sharedEntityId = addSharedEntityResponse.SharedEntityId;
                long[] listItemIds    = addSharedEntityResponse.ListItemIds.ToArray();

                OutputStatusMessage(string.Format("NegativeKeywordList successfully added to account library and assigned identifer {0}\n", sharedEntityId));

                negativeKeywordList.Id = addSharedEntityResponse.SharedEntityId;
                CampaignManagementExampleHelper.OutputSharedEntity(negativeKeywordList);
                CampaignManagementExampleHelper.OutputArrayOfSharedListItem(negativeKeywords.ToList());
                CampaignManagementExampleHelper.OutputArrayOfBatchError(addSharedEntityResponse.PartialErrors);

                OutputStatusMessage("Negative keywords currently in NegativeKeywordList:");
                negativeKeywords = (SharedListItem[])(await CampaignManagementExampleHelper.GetListItemsBySharedListAsync(new NegativeKeywordList {
                    Id = sharedEntityId
                })).ListItems;
                if (negativeKeywords == null || negativeKeywords.Length == 0)
                {
                    OutputStatusMessage("None\n");
                }
                else
                {
                    CampaignManagementExampleHelper.OutputArrayOfNegativeKeyword(negativeKeywords.Cast <NegativeKeyword>().ToList());
                }

                // To update the list of negative keywords, you must either add or remove from the list
                // using the respective AddListItemsToSharedList or DeleteListItemsFromSharedList operations.
                // To remove the negative keywords from the list pass the negative keyword identifers
                // and negative keyword list (SharedEntity) identifer.

                var partialErrors = (await CampaignManagementExampleHelper.DeleteListItemsFromSharedListAsync(listItemIds, new NegativeKeywordList {
                    Id = sharedEntityId
                }))?.PartialErrors;
                if (partialErrors == null || !partialErrors.Any())
                {
                    OutputStatusMessage("Deleted most recently added negative keywords from negative keyword list.\n");
                }
                else
                {
                    CampaignManagementExampleHelper.OutputArrayOfBatchError(partialErrors);
                }

                OutputStatusMessage("Negative keywords currently in NegativeKeywordList:");
                negativeKeywords = (SharedListItem[])(await CampaignManagementExampleHelper.GetListItemsBySharedListAsync(new NegativeKeywordList {
                    Id = sharedEntityId
                })).ListItems;
                if (negativeKeywords == null || negativeKeywords.Length == 0)
                {
                    OutputStatusMessage("None\n");
                }
                else
                {
                    CampaignManagementExampleHelper.OutputArrayOfNegativeKeyword(negativeKeywords.Cast <NegativeKeyword>().ToList());
                }

                // Whether you created the list with or without negative keywords, more can be added
                // using the AddListItemsToSharedList operation.

                negativeKeywords = new SharedListItem[]
                {
                    new NegativeKeyword
                    {
                        Text      = "auto",
                        Type      = "NegativeKeyword",
                        MatchType = MatchType.Exact
                    },
                    new NegativeKeyword
                    {
                        Text      = "auto",
                        Type      = "NegativeKeyword",
                        MatchType = MatchType.Phrase
                    }
                };

                var addListItemsToSharedListResponse = await CampaignManagementExampleHelper.AddListItemsToSharedListAsync(
                    negativeKeywords,
                    new NegativeKeywordList { Id = sharedEntityId });

                listItemIds = addListItemsToSharedListResponse.ListItemIds.ToArray();

                CampaignManagementExampleHelper.OutputArrayOfSharedListItem(negativeKeywords.ToList());
                CampaignManagementExampleHelper.OutputArrayOfBatchError(addListItemsToSharedListResponse.PartialErrors);

                OutputStatusMessage("Negative keywords currently in NegativeKeywordList:");
                negativeKeywords = (SharedListItem[])(await CampaignManagementExampleHelper.GetListItemsBySharedListAsync(new NegativeKeywordList {
                    Id = sharedEntityId
                })).ListItems;
                if (negativeKeywords == null || negativeKeywords.Length == 0)
                {
                    OutputStatusMessage("None\n");
                }
                else
                {
                    CampaignManagementExampleHelper.OutputArrayOfNegativeKeyword(negativeKeywords.Cast <NegativeKeyword>().ToList());
                }

                // You can update the name of the negative keyword list.

                negativeKeywordList = new NegativeKeywordList
                {
                    Id   = sharedEntityId,
                    Name = "My Updated Negative Keyword List",
                    Type = "NegativeKeywordList"
                };

                partialErrors = (await CampaignManagementExampleHelper.UpdateSharedEntitiesAsync(new SharedEntity[] { negativeKeywordList })).PartialErrors;
                if (partialErrors == null || !partialErrors.Any())
                {
                    OutputStatusMessage(string.Format("Updated Negative Keyword List Name to {0}.\n", negativeKeywordList.Name));
                }
                else
                {
                    CampaignManagementExampleHelper.OutputArrayOfBatchError(partialErrors);
                }

                // Get and output the negative keyword lists and store the list of identifiers.

                const string sharedEntityType = "NegativeKeywordList";
                var          sharedEntities   = (await CampaignManagementExampleHelper.GetSharedEntitiesByAccountIdAsync(sharedEntityType)).SharedEntities;
                CampaignManagementExampleHelper.OutputArrayOfSharedEntity(sharedEntities);
                var sharedEntityIds = new long[sharedEntities.Count];
                for (int index = 0; index < sharedEntities.Count; index++)
                {
                    if (sharedEntities[index].Id != null)
                    {
                        sharedEntityIds[index] = (long)sharedEntities[index].Id;
                    }
                }

                // Negative keywords were added to the negative keyword list above. You can associate the
                // shared list of negative keywords with a campaign with or without negative keywords.
                // Shared negative keyword lists cannot be associated with an ad group. An ad group can only
                // be assigned an exclusive set of negative keywords.

                var associations = new[]
                {
                    new SharedEntityAssociation
                    {
                        EntityId         = campaignId,
                        EntityType       = "Campaign",
                        SharedEntityId   = sharedEntityId,
                        SharedEntityType = "NegativeKeywordList"
                    }
                };

                partialErrors = (await CampaignManagementExampleHelper.SetSharedEntityAssociationsAsync(associations)).PartialErrors;
                if (partialErrors == null || !partialErrors.Any())
                {
                    OutputStatusMessage(string.Format("Associated CampaignId {0} with Negative Keyword List Id {1}.\n",
                                                      campaignId, sharedEntityId));
                }
                else
                {
                    CampaignManagementExampleHelper.OutputArrayOfBatchError(partialErrors);
                }

                // Get and output the associations either by Campaign or NegativeKeywordList identifier.
                GetSharedEntityAssociationsByEntityIdsResponse getSharedEntityAssociationsByEntityIdsResponse =
                    await CampaignManagementExampleHelper.GetSharedEntityAssociationsByEntityIdsAsync(new[] { campaignId }, "Campaign", "NegativeKeywordList");

                CampaignManagementExampleHelper.OutputArrayOfSharedEntityAssociation(getSharedEntityAssociationsByEntityIdsResponse.Associations);
                CampaignManagementExampleHelper.OutputArrayOfBatchError(getSharedEntityAssociationsByEntityIdsResponse.PartialErrors);

                // Currently the GetSharedEntityAssociationsBySharedEntityIds operation accepts only one shared entity identifier in the list.
                GetSharedEntityAssociationsBySharedEntityIdsResponse getSharedEntityAssociationsBySharedEntityIdsResponse =
                    await CampaignManagementExampleHelper.GetSharedEntityAssociationsBySharedEntityIdsAsync("Campaign", new[] { sharedEntityIds[sharedEntityIds.Length - 1] }, "NegativeKeywordList");

                CampaignManagementExampleHelper.OutputArrayOfSharedEntityAssociation(getSharedEntityAssociationsBySharedEntityIdsResponse.Associations);
                CampaignManagementExampleHelper.OutputArrayOfBatchError(getSharedEntityAssociationsBySharedEntityIdsResponse.PartialErrors);

                // Explicitly delete the association between the campaign and the negative keyword list.

                partialErrors = (await CampaignManagementExampleHelper.DeleteSharedEntityAssociationsAsync(associations)).PartialErrors;
                if (partialErrors == null || !partialErrors.Any())
                {
                    OutputStatusMessage("Deleted NegativeKeywordList associations\n");
                }
                else
                {
                    CampaignManagementExampleHelper.OutputArrayOfBatchError(partialErrors);
                }

                // Delete the campaign and any remaining assocations.

                await CampaignManagementExampleHelper.DeleteCampaignsAsync(authorizationData.AccountId, new[] { campaignId });

                OutputStatusMessage(string.Format("Deleted Campaign Id {0}\n", campaignId));

                // DeleteCampaigns does not delete the negative keyword list from the account's library.
                // Call the DeleteSharedEntities operation to delete the shared entities.

                partialErrors = (await CampaignManagementExampleHelper.DeleteSharedEntitiesAsync(new SharedEntity[] { new NegativeKeywordList {
                                                                                                                          Id = sharedEntityId
                                                                                                                      } }))?.PartialErrors;
                if (partialErrors == null || !partialErrors.Any())
                {
                    OutputStatusMessage(string.Format("Deleted Negative Keyword List (SharedEntity) Id {0}\n", sharedEntityId));
                }
                else
                {
                    CampaignManagementExampleHelper.OutputArrayOfBatchError(partialErrors);
                }
            }
            // Catch authentication exceptions
            catch (OAuthTokenRequestException ex)
            {
                OutputStatusMessage(string.Format("Couldn't get OAuth tokens. Error: {0}. Description: {1}", ex.Details.Error, ex.Details.Description));
            }
            // Catch Campaign Management service exceptions
            catch (FaultException <Microsoft.BingAds.V12.CampaignManagement.AdApiFaultDetail> ex)
            {
                OutputStatusMessage(string.Join("; ", ex.Detail.Errors.Select(error => string.Format("{0}: {1}", error.Code, error.Message))));
            }
            catch (FaultException <Microsoft.BingAds.V12.CampaignManagement.ApiFaultDetail> ex)
            {
                OutputStatusMessage(string.Join("; ", ex.Detail.OperationErrors.Select(error => string.Format("{0}: {1}", error.Code, error.Message))));
                OutputStatusMessage(string.Join("; ", ex.Detail.BatchErrors.Select(error => string.Format("{0}: {1}", error.Code, error.Message))));
            }
            catch (FaultException <Microsoft.BingAds.V12.CampaignManagement.EditorialApiFaultDetail> ex)
            {
                OutputStatusMessage(string.Join("; ", ex.Detail.OperationErrors.Select(error => string.Format("{0}: {1}", error.Code, error.Message))));
                OutputStatusMessage(string.Join("; ", ex.Detail.BatchErrors.Select(error => string.Format("{0}: {1}", error.Code, error.Message))));
            }
            catch (Exception ex)
            {
                OutputStatusMessage(ex.Message);
            }
        }
        public async override Task RunAsync(AuthorizationData authorizationData)
        {
            try
            {
                Service = new ServiceClient<ICampaignManagementService>(authorizationData);

                // Add a campaign that will later be associated with negative keywords. 

                var campaigns = new[]{
                    new Campaign
                    {
                        Name = "Women's Shoes" + DateTime.UtcNow,
                        Description = "Red shoes line.",
                        BudgetType = BudgetLimitType.MonthlyBudgetSpendUntilDepleted,
                        MonthlyBudget = 1000.00,
                        TimeZone = "PacificTimeUSCanadaTijuana",
                        DaylightSaving = true,
                    }
                };

                AddCampaignsResponse addCampaignsResponse = await AddCampaignsAsync(authorizationData.AccountId, campaigns);
                long?[] campaignIds = addCampaignsResponse.CampaignIds.ToArray();
                BatchError[] campaignErrors = addCampaignsResponse.PartialErrors.ToArray();
                long campaignId = (long)campaignIds[0];

                // You may choose to associate an exclusive set of negative keywords to an individual campaign 
                // or ad group. An exclusive set of negative keywords cannot be shared with other campaigns 
                // or ad groups. This example only associates negative keywords with a campaign.

                var entityNegativeKeywords = new[]
                    {
                        new EntityNegativeKeyword
                            {
                                EntityId = campaignId,
                                EntityType = "Campaign",
                                NegativeKeywords = new[]
                                    {
                                        new NegativeKeyword
                                        {
                                            MatchType = MatchType.Phrase,
                                            Text = "auto"
                                        },
                                        new NegativeKeyword
                                        {
                                            MatchType = MatchType.Phrase,
                                            Text = "auto"
                                        },
                                    }
                            }
                    };

                AddNegativeKeywordsToEntitiesResponse addNegativeKeywordsToEntitiesResponse =
                    await AddNegativeKeywordsToEntitiesAsync(entityNegativeKeywords);
                OutputNegativeKeywordIds(addNegativeKeywordsToEntitiesResponse.NegativeKeywordIds);
                OutputNestedPartialErrors(addNegativeKeywordsToEntitiesResponse.NestedPartialErrors);
                if (addNegativeKeywordsToEntitiesResponse.NestedPartialErrors == null
                    || addNegativeKeywordsToEntitiesResponse.NestedPartialErrors.Count == 0)
                {
                    OutputStatusMessage("Added an exclusive set of negative keywords to the Campaign.\n");
                    OutputNegativeKeywordIds(addNegativeKeywordsToEntitiesResponse.NegativeKeywordIds);
                }
                else
                {
                    OutputNestedPartialErrors(addNegativeKeywordsToEntitiesResponse.NestedPartialErrors);
                }

                GetNegativeKeywordsByEntityIdsResponse getNegativeKeywordsByEntityIdsResponse =
                    await GetNegativeKeywordsByEntityIdsAsync(new[] { campaignId }, "Campaign", authorizationData.AccountId);
                OutputEntityNegativeKeywords(getNegativeKeywordsByEntityIdsResponse.EntityNegativeKeywords);
                OutputPartialErrors(getNegativeKeywordsByEntityIdsResponse.PartialErrors);
                if (getNegativeKeywordsByEntityIdsResponse.PartialErrors == null
                    || getNegativeKeywordsByEntityIdsResponse.PartialErrors.Count == 0)
                {
                    OutputStatusMessage("Retrieved an exclusive set of negative keywords for the Campaign.\n");
                    OutputEntityNegativeKeywords(getNegativeKeywordsByEntityIdsResponse.EntityNegativeKeywords);
                }
                else
                {
                    OutputPartialErrors(getNegativeKeywordsByEntityIdsResponse.PartialErrors);
                }

                // If you attempt to delete a negative keyword without an identifier the operation will
                // succeed but will return partial errors corresponding to the index of the negative keyword
                // that was not deleted. 
                var nestedPartialErrors = (BatchErrorCollection[])await DeleteNegativeKeywordsFromEntitiesAsync(entityNegativeKeywords);
                if (nestedPartialErrors == null || nestedPartialErrors.Length == 0)
                {
                    OutputStatusMessage("Deleted an exclusive set of negative keywords from the Campaign.\n");
                }
                else
                {
                    OutputStatusMessage("Attempt to DeleteNegativeKeywordsFromEntities without NegativeKeyword identifier partially fails by design.");
                    OutputNestedPartialErrors(nestedPartialErrors);
                }

                // Delete the negative keywords with identifiers that were returned above.
                nestedPartialErrors = (BatchErrorCollection[])await DeleteNegativeKeywordsFromEntitiesAsync(
                    getNegativeKeywordsByEntityIdsResponse.EntityNegativeKeywords);
                if (nestedPartialErrors == null || nestedPartialErrors.Length == 0)
                {
                    OutputStatusMessage("Deleted an exclusive set of negative keywords from the Campaign.\n");
                }
                else
                {
                    OutputNestedPartialErrors(nestedPartialErrors);
                }

                // Negative keywords can also be added and deleted from a shared negative keyword list. 
                // The negative keyword list can be shared or associated with multiple campaigns.
                // NegativeKeywordList inherits from SharedList which inherits from SharedEntity.

                var negativeKeywordList = new NegativeKeywordList
                {
                    Name = "My Negative Keyword List" + DateTime.UtcNow,
                    Type = "NegativeKeywordList"
                };

                SharedListItem[] negativeKeywords = 
                    {
                        new NegativeKeyword
                            {
                                Text = "car",
                                Type = "NegativeKeyword",
                                MatchType = MatchType.Exact
                            },
                        new NegativeKeyword
                            {
                                Text = "car",
                                Type = "NegativeKeyword",
                                MatchType = MatchType.Phrase
                            }
                    };

                // You can create a new list for negative keywords with or without negative keywords.

                var addSharedEntityResponse = await AddSharedEntityAsync(negativeKeywordList, negativeKeywords);
                var sharedEntityId = addSharedEntityResponse.SharedEntityId;
                long[] listItemIds = addSharedEntityResponse.ListItemIds.ToArray();

                OutputStatusMessage(String.Format("NegativeKeywordList successfully added to account library and assigned identifer {0}\n", sharedEntityId));

                OutputNegativeKeywordsWithPartialErrors(
                    sharedEntityId,
                    negativeKeywords,
                    listItemIds,
                    addSharedEntityResponse.PartialErrors.ToArray());

                OutputStatusMessage("Negative keywords currently in NegativeKeywordList:");
                negativeKeywords = (SharedListItem[])await GetListItemsBySharedListAsync(new NegativeKeywordList { Id = sharedEntityId });
                if (negativeKeywords == null || negativeKeywords.Length == 0)
                {
                    OutputStatusMessage("None\n");
                }
                else
                {
                    OutputNegativeKeywords(negativeKeywords.Cast<NegativeKeyword>());
                }

                // To update the list of negative keywords, you must either add or remove from the list
                // using the respective AddListItemsToSharedList or DeleteListItemsFromSharedList operations.
                // To remove the negative keywords from the list pass the negative keyword identifers
                // and negative keyword list (SharedEntity) identifer.

                var partialErrors = await DeleteListItemsFromSharedListAsync(listItemIds, new NegativeKeywordList { Id = sharedEntityId });
                if (partialErrors == null || !partialErrors.Any())
                {
                    OutputStatusMessage("Deleted most recently added negative keywords from negative keyword list.\n");

                }
                else
                {
                    OutputPartialErrors(partialErrors);
                }

                OutputStatusMessage("Negative keywords currently in NegativeKeywordList:");
                negativeKeywords = (SharedListItem[])await GetListItemsBySharedListAsync(new NegativeKeywordList { Id = sharedEntityId });
                if (negativeKeywords == null || negativeKeywords.Length == 0)
                {
                    OutputStatusMessage("None\n");
                }
                else
                {
                    OutputNegativeKeywords(negativeKeywords.Cast<NegativeKeyword>());
                }

                // Whether you created the list with or without negative keywords, more can be added 
                // using the AddListItemsToSharedList operation.

                negativeKeywords = new SharedListItem[]
                    {
                        new NegativeKeyword
                            {
                                Text = "auto",
                                Type = "NegativeKeyword",
                                MatchType = MatchType.Exact
                            },
                        new NegativeKeyword
                            {
                                Text = "auto",
                                Type = "NegativeKeyword",
                                MatchType = MatchType.Phrase
                            }
                    };

                var addListItemsToSharedListResponse = await AddListItemsToSharedListAsync(
                    negativeKeywords,
                    new NegativeKeywordList { Id = sharedEntityId });
                listItemIds = addListItemsToSharedListResponse.ListItemIds.ToArray();

                OutputNegativeKeywordsWithPartialErrors(
                    sharedEntityId,
                    negativeKeywords,
                    listItemIds,
                    addListItemsToSharedListResponse.PartialErrors.ToArray());

                OutputStatusMessage("Negative keywords currently in NegativeKeywordList:");
                negativeKeywords = (SharedListItem[])await GetListItemsBySharedListAsync(new NegativeKeywordList { Id = sharedEntityId });
                if (negativeKeywords == null || negativeKeywords.Length == 0)
                {
                    OutputStatusMessage("None\n");
                }
                else
                {
                    OutputNegativeKeywords(negativeKeywords.Cast<NegativeKeyword>());
                }

                // You can update the name of the negative keyword list. 

                negativeKeywordList = new NegativeKeywordList
                {
                    Id = sharedEntityId,
                    Name = "My Updated Negative Keyword List",
                    Type = "NegativeKeywordList"
                };

                partialErrors = await UpdateSharedEntitiesAsync(new SharedEntity[] { negativeKeywordList });
                if (partialErrors == null || !partialErrors.Any())
                {
                    OutputStatusMessage(String.Format("Updated Negative Keyword List Name to {0}.\n", negativeKeywordList.Name));
                }
                else
                {
                    OutputPartialErrors(partialErrors);
                }

                // Get and output the negative keyword lists and store the list of identifiers.

                const string sharedEntityType = "NegativeKeywordList";
                var sharedEntities = await GetSharedEntitiesByAccountIdAsync(sharedEntityType);
                OutputSharedEntityIdentifiersAsync(sharedEntities);
                var sharedEntityIds = new long[sharedEntities.Count];
                for (int index = 0; index < sharedEntities.Count; index++)
                {
                    if (sharedEntities[index].Id != null)
                    {
                        sharedEntityIds[index] = (long)sharedEntities[index].Id;
                    }
                }

                // Negative keywords were added to the negative keyword list above. You can associate the 
                // shared list of negative keywords with a campaign with or without negative keywords. 
                // Shared negative keyword lists cannot be associated with an ad group. An ad group can only 
                // be assigned an exclusive set of negative keywords. 

                var associations = new[]
                    {
                        new SharedEntityAssociation
                            {
                                EntityId = campaignId,
                                EntityType = "Campaign",
                                SharedEntityId = sharedEntityId,
                                SharedEntityType = "NegativeKeywordList" 
                            }
                    };

                partialErrors = await SetSharedEntityAssociationsAsync(associations);
                if (partialErrors == null || !partialErrors.Any())
                {
                    OutputStatusMessage(String.Format("Associated CampaignId {0} with Negative Keyword List Id {1}.\n",
                        campaignId, sharedEntityId));
                }
                else
                {
                    OutputPartialErrors(partialErrors);
                }

                // Get and output the associations either by Campaign or NegativeKeywordList identifier.
                GetSharedEntityAssociationsByEntityIdsResponse getSharedEntityAssociationsByEntityIdsResponse =
                    await GetSharedEntityAssociationsByEntityIdsAsync(new[] { campaignId }, "Campaign", "NegativeKeywordList");
                OutputSharedEntityAssociations(getSharedEntityAssociationsByEntityIdsResponse.Associations);
                OutputPartialErrors(getSharedEntityAssociationsByEntityIdsResponse.PartialErrors);

                // Currently the GetSharedEntityAssociationsBySharedEntityIds operation accepts only one shared entity identifier in the list.
                GetSharedEntityAssociationsBySharedEntityIdsResponse getSharedEntityAssociationsBySharedEntityIdsResponse =
                    await GetSharedEntityAssociationsBySharedEntityIdsAsync("Campaign", new[] { sharedEntityIds[sharedEntityIds.Length - 1] }, "NegativeKeywordList");
                OutputSharedEntityAssociations(getSharedEntityAssociationsBySharedEntityIdsResponse.Associations);
                OutputPartialErrors(getSharedEntityAssociationsBySharedEntityIdsResponse.PartialErrors);

                // Explicitly delete the association between the campaign and the negative keyword list.

                partialErrors = await DeleteSharedEntityAssociationsAsync(associations);
                if (partialErrors == null || !partialErrors.Any())
                {
                    OutputStatusMessage("Deleted NegativeKeywordList associations\n");
                }
                else
                {
                    OutputPartialErrors(partialErrors);
                }

                // Delete the campaign and any remaining assocations. 

                DeleteCampaigns(authorizationData.AccountId, new[] { campaignId });
                OutputStatusMessage(String.Format("Deleted CampaignId {0}\n", campaignId));

                // DeleteCampaigns does not delete the negative keyword list from the account's library. 
                // Call the DeleteSharedEntities operation to delete the shared entities.

                partialErrors = await DeleteSharedEntitiesAsync(new SharedEntity[] { new NegativeKeywordList { Id = sharedEntityId } });
                if (partialErrors == null || !partialErrors.Any())
                {
                    OutputStatusMessage(String.Format("Deleted Negative Keyword List (SharedEntity) Id {0}\n", sharedEntityId));
                }
                else
                {
                    OutputPartialErrors(partialErrors);
                }
            }
            // Catch authentication exceptions
            catch (OAuthTokenRequestException ex)
            {
                OutputStatusMessage(string.Format("Couldn't get OAuth tokens. Error: {0}. Description: {1}", ex.Details.Error, ex.Details.Description));
            }
            // Catch Campaign Management service exceptions
            catch (FaultException<Microsoft.BingAds.V10.CampaignManagement.AdApiFaultDetail> ex)
            {
                OutputStatusMessage(string.Join("; ", ex.Detail.Errors.Select(error => string.Format("{0}: {1}", error.Code, error.Message))));
            }
            catch (FaultException<Microsoft.BingAds.V10.CampaignManagement.ApiFaultDetail> ex)
            {
                OutputStatusMessage(string.Join("; ", ex.Detail.OperationErrors.Select(error => string.Format("{0}: {1}", error.Code, error.Message))));
                OutputStatusMessage(string.Join("; ", ex.Detail.BatchErrors.Select(error => string.Format("{0}: {1}", error.Code, error.Message))));
            }
            catch (FaultException<Microsoft.BingAds.V10.CampaignManagement.EditorialApiFaultDetail> ex)
            {
                OutputStatusMessage(string.Join("; ", ex.Detail.OperationErrors.Select(error => string.Format("{0}: {1}", error.Code, error.Message))));
                OutputStatusMessage(string.Join("; ", ex.Detail.BatchErrors.Select(error => string.Format("{0}: {1}", error.Code, error.Message))));
            }
            catch (Exception ex)
            {
                OutputStatusMessage(ex.Message);
            }
        }