演练:C 中的必应广告 API Web 应用程序#
此示例 C# Web 应用程序通过提供的凭据提示用户同意,然后获取经过身份验证的用户可以访问的帐户。
必须首先注册应用程序,并记下客户端 ID (已注册的应用程序 ID) 、客户端机密 (注册的密码) 和重定向 URI。 有关注册应用程序和授权代码授予流的详细信息,请参阅 使用 OAuth 进行身份验证。
还需要生产 开发人员令牌。 可以按如下所述分步创建示例,也可以从 GitHub 下载更多示例。
提示
此示例引用 在 Azure 中创建 ASP.NET Framework Web 应用中的步骤。 有关将 Web 应用部署到 Azure 的更多详细信息,请参阅 Azure 文档。
代码演练
打开 Visual Studio Community 2017 开发环境。 如果已安装 Visual Studio,请单击“工具>获取工具和功能”,在 Visual Studio 中添加 ASP.NET 和 Web 开发和Azure 开发工作负载。
通过选择“文件>>新建项目”创建新项目。 在“新建项目”窗口中,选择下拉列表中的“.NET Framework 4.7.1”,然后选择“Visual C# > Web > ASP.NET Web Application (.NET Framework) ”。 将项目命名为 BingAdsWebApp ,然后单击“ 确定”。
可以将任何类型的 ASP.NET Web 应用部署到 Azure。 对于本快速入门,请选择 MVC 模板,确保身份验证设置为 “无身份验证”,然后单击“ 确定”。
通过 NuGet 为 BingAdsWebApp 安装 SDK。 有关依赖项的详细信息,请参阅 安装 SDK。 单击“ 工具” ->“NuGet 包管理器 ”-“>包管理器控制台”。 在提示符下,键入以下命令以一次安装一个包:
Install-Package Microsoft.BingAds.SDK
、Install-Package System.ServiceModel.Primitives -Version 4.4.1
、Install-Package System.ServiceModel.Http -Version 4.4.1
和Install-Package System.Configuration.ConfigurationManager -Version 4.4.1
。打开 Web.config 文件,并将其内容替换为以下代码块。 编辑 BingAdsEnvironment 以从沙盒移动到生产环境,并根据需要设置生产 开发人员令牌 。 必须使用注册应用程序时预配的相应应用程序 ID、应用程序机密和重定向 URL 值编辑 ClientId、ClientSecret 和 RedirectUri。
注意
如果打算在上线之前在 localhost 上部署,请确保同时注册本地 SSL URL 和端口,例如 https://localhost:44383/. 在这种情况下,还必须在 BingAdsWebApp 项目属性窗口中将 “SSL 启用 ”设置为 True ,然后可以从同一属性窗口复制 localhost 端口。
<?xml version="1.0" encoding="utf-8"?>
<!--
For more information on how to configure your ASP.NET application, please visit
https://go.microsoft.com/fwlink/?LinkId=301880
-->
<configuration>
<configSections>
<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<section name="BingAdsWebApp.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</sectionGroup>
</configSections>
<appSettings>
<!-- To use the production environment, set this value to "Production". -->
<add key="BingAdsEnvironment" value="Sandbox"/>
<add key="webpages:Version" value="3.0.0.0" />
<add key="webpages:Enabled" value="false" />
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
<system.web>
<compilation debug="true" targetFramework="4.7.1" />
<httpRuntime targetFramework="4.7.1" />
<httpModules>
<add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" />
</httpModules>
</system.web>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" culture="neutral" publicKeyToken="30ad4fe6b2a6aeed" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<modules>
<remove name="ApplicationInsightsWebTracking" />
<add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" preCondition="managedHandler" />
</modules>
</system.webServer>
<system.codedom>
<compilers>
<compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.7.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:default /nowarn:1659;1699;1701" />
<compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.7.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:default /nowarn:41008 /define:_MYTYPE=\"Web\" /optionInfer+" />
</compilers>
</system.codedom>
<applicationSettings>
<BingAdsWebApp.Properties.Settings>
<setting name="RefreshToken" serializeAs="String">
<value />
</setting>
<setting name="ClientId" serializeAs="String">
<value>ClientIdGoesHere</value>
</setting>
<setting name="ClientSecret" serializeAs="String">
<value>ClientSecretGoesHere</value>
</setting>
<setting name="RedirectionUri" serializeAs="String">
<value>RedirectionUriGoesHere</value>
</setting>
<setting name="DeveloperToken" serializeAs="String">
<value>DeveloperTokenGoesHere</value>
</setting>
</BingAdsWebApp.Properties.Settings>
</applicationSettings>
</configuration>
创建设置文件。 在 BingAdsWebApp 的项目视图中,右键单击“ 属性 ”,然后单击“ 打开”。 单击 “设置”,然后单击文本“ 项目不包含默认设置文件”。单击此处创建一个。 将自动添加来自 Web.config 的新值。
在 BingAdsWebApp 项目的 Views ->Home 文件夹中,打开 Index.cshtml 文件,并将其内容替换为以下代码块。 这定义了一个网页视图,该视图显示将在下面进一步写入的服务调用的结果。
@{ ViewBag.Title = "Index"; } <h2>Index</h2> <h4>Accounts</h4> <p>@Html.Raw(@ViewBag.Accounts)</p> <p><b style="color: red">@ViewBag.Errors</b></p>
在 BingAdsWebApp 项目的 Controllers 文件夹中,打开 HomeController.cs 文件,并将其内容替换为以下代码块。 这将定义服务调用,该调用将确定哪些结果显示在上面定义的视图中。
using System; using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Net.Http; using System.ServiceModel; using System.Threading.Tasks; using System.Web.Mvc; using BingAdsWebApp.Properties; using Microsoft.BingAds; using Microsoft.BingAds.V13.CustomerManagement; namespace BingAdsWebApp.Controllers { public class HomeController : Controller { private static AuthorizationData _authorizationData; private static ServiceClient<ICustomerManagementService> _customerManagementService; private static string ClientState = "ClientStateGoesHere"; private static string _output = ""; /// <summary> /// Controls the contents displayed at Index.cshtml. /// </summary> public async Task<ActionResult> Index() { try { // If there is already an authenticated Microsoft account during this HTTP session, // go ahead and call Bing Ads API service operations. if (Session["auth"] != null) { return await SetAuthorizationDataAsync((OAuthWebAuthCodeGrant)Session["auth"]); } // Prepare the OAuth object for use with the authorization code grant flow. var apiEnvironment = ConfigurationManager.AppSettings["BingAdsEnvironment"] == ApiEnvironment.Sandbox.ToString() ? ApiEnvironment.Sandbox : ApiEnvironment.Production; var oAuthWebAuthCodeGrant = new OAuthWebAuthCodeGrant( Settings.Default["ClientId"].ToString(), Settings.Default["ClientSecret"].ToString(), new Uri(Settings.Default["RedirectionUri"].ToString()), apiEnvironment); // It is recommended that you specify a non guessable 'state' request parameter to help prevent // cross site request forgery (CSRF). oAuthWebAuthCodeGrant.State = ClientState; // When calling Bing Ads API service operations with ServiceClient or BulkServiceManager, each will refresh your access token // automatically if they detect the AuthenticationTokenExpired (109) error code. // As a best practice you should always use the most recent provided refresh token. // Save the refresh token whenever new OAuth tokens are received by subscribing to the NewOAuthTokensReceived event handler. oAuthWebAuthCodeGrant.NewOAuthTokensReceived += (sender, args) => SaveRefreshToken(args.NewRefreshToken); // If a refresh token is already present, use it to request new access and refresh tokens. if (RefreshTokenExists()) { await oAuthWebAuthCodeGrant.RequestAccessAndRefreshTokensAsync(GetRefreshToken()); // Save the authentication object in a session for future requests. Session["auth"] = oAuthWebAuthCodeGrant; return await SetAuthorizationDataAsync((OAuthWebAuthCodeGrant)Session["auth"]); } // If the current HTTP request is a callback from the Microsoft Account authorization server, // use the current request url containing authorization code to request new access and refresh tokens if (Request["code"] != null) { if (oAuthWebAuthCodeGrant.State != ClientState) throw new HttpRequestException("The OAuth response state does not match the client request state."); await oAuthWebAuthCodeGrant.RequestAccessAndRefreshTokensAsync(Request.Url); // Save the authentication object in a session for future requests. Session["auth"] = oAuthWebAuthCodeGrant; return await SetAuthorizationDataAsync((OAuthWebAuthCodeGrant)Session["auth"]); } SaveRefreshToken(oAuthWebAuthCodeGrant.OAuthTokens?.RefreshToken); // If there is no refresh token saved and no callback from the authorization server, // then connect to the authorization server and request user consent. return Redirect(oAuthWebAuthCodeGrant.GetAuthorizationEndpoint().ToString()); } // Catch authentication exceptions catch (OAuthTokenRequestException ex) { ViewBag.Errors = (string.Format("Couldn't get OAuth tokens. Error: {0}. Description: {1}", ex.Details.Error, ex.Details.Description)); return View(); } // Catch Customer Management service exceptions catch (FaultException<Microsoft.BingAds.V13.CustomerManagement.AdApiFaultDetail> ex) { ViewBag.Errors = (string.Join("; ", ex.Detail.Errors.Select( error => string.Format("{0}: {1}", error.Code, error.Message)))); return View(); } catch (FaultException<Microsoft.BingAds.V13.CustomerManagement.ApiFault> ex) { ViewBag.Errors = (string.Join("; ", ex.Detail.OperationErrors.Select( error => string.Format("{0}: {1}", error.Code, error.Message)))); return View(); } catch (Exception ex) { ViewBag.Errors = ex.Message; return View(); } } /// <summary> /// Adds a campaign to an account of the current authenticated user. /// </summary> private async Task<ActionResult> SetAuthorizationDataAsync(Authentication authentication) { _authorizationData = new AuthorizationData { Authentication = authentication, DeveloperToken = Settings.Default["DeveloperToken"].ToString() }; _customerManagementService = new ServiceClient<ICustomerManagementService>(_authorizationData); var getUserRequest = new GetUserRequest { UserId = null }; var getUserResponse = (await _customerManagementService.CallAsync((s, r) => s.GetUserAsync(r), getUserRequest)); var user = getUserResponse.User; var predicate = new Predicate { Field = "UserId", Operator = PredicateOperator.Equals, Value = user.Id.ToString() }; var paging = new Paging { Index = 0, Size = 10 }; var searchAccountsRequest = new SearchAccountsRequest { Ordering = null, PageInfo = paging, Predicates = new[] { predicate } }; var searchAccountsResponse = (await _customerManagementService.CallAsync((s, r) => s.SearchAccountsAsync(r), searchAccountsRequest)); var accounts = searchAccountsResponse.Accounts.ToArray(); if (accounts.Length <= 0) return View(); _authorizationData.AccountId = (long)accounts[0].Id; _authorizationData.CustomerId = (int)accounts[0].ParentCustomerId; OutputArrayOfAdvertiserAccount(accounts); ViewBag.Accounts = _output; _output = null; return View(); } /// <summary> /// Saves the refresh token /// </summary> /// <param name="refreshToken">The refresh token to save</param> private static void SaveRefreshToken(string refreshToken) { Settings.Default["RefreshToken"] = refreshToken; Settings.Default.Save(); } /// <summary> /// Deletes the contents in the refresh token file /// </summary> private static void DeleteRefreshToken() { Settings.Default["RefreshToken"] = ""; Settings.Default.Save(); } /// <summary> /// Determines whether the global refresh token exists. /// </summary> /// <returns>Returns true if the global refresh token exists.</returns> private bool RefreshTokenExists() { return Settings.Default["RefreshToken"] != null && Settings.Default["RefreshToken"].ToString().Length > 0; } /// <summary> /// Gets the global refresh token. /// </summary> /// <returns>The global refresh token.</returns> private string GetRefreshToken() { return Settings.Default["RefreshToken"].ToString(); } #region OutputHelpers /** * You can extend the app with example output helpers at: * https://github.com/BingAds/BingAds-dotNet-SDK/tree/main/examples/BingAdsExamples/BingAdsExamplesLibrary/v13 * * AdInsightExampleHelper.cs * BulkExampleHelper.cs * CampaignManagementExampleHelper.cs * CustomerBillingExampleHelper.cs * CustomerManagementExampleHelper.cs * ReportingExampleHelper.cs **/ private static void OutputArrayOfAdvertiserAccount(IList<AdvertiserAccount> dataObjects) { if (null != dataObjects) { foreach (var dataObject in dataObjects) { OutputAdvertiserAccount(dataObject); OutputStatusMessage("\n"); } } } private static void OutputAdvertiserAccount(AdvertiserAccount dataObject) { if (null != dataObject) { OutputStatusMessage(string.Format("BillToCustomerId: {0}", dataObject.BillToCustomerId)); OutputStatusMessage(string.Format("CurrencyCode: {0}", dataObject.CurrencyCode)); OutputStatusMessage(string.Format("AccountFinancialStatus: {0}", dataObject.AccountFinancialStatus)); OutputStatusMessage(string.Format("Id: {0}", dataObject.Id)); OutputStatusMessage(string.Format("Language: {0}", dataObject.Language)); OutputStatusMessage(string.Format("LastModifiedByUserId: {0}", dataObject.LastModifiedByUserId)); OutputStatusMessage(string.Format("LastModifiedTime: {0}", dataObject.LastModifiedTime)); OutputStatusMessage(string.Format("Name: {0}", dataObject.Name)); OutputStatusMessage(string.Format("Number: {0}", dataObject.Number)); OutputStatusMessage(string.Format("ParentCustomerId: {0}", dataObject.ParentCustomerId)); OutputStatusMessage(string.Format("PaymentMethodId: {0}", dataObject.PaymentMethodId)); OutputStatusMessage(string.Format("PaymentMethodType: {0}", dataObject.PaymentMethodType)); OutputStatusMessage(string.Format("PrimaryUserId: {0}", dataObject.PrimaryUserId)); OutputStatusMessage(string.Format("AccountLifeCycleStatus: {0}", dataObject.AccountLifeCycleStatus)); OutputStatusMessage(string.Format("TimeStamp: {0}", dataObject.TimeStamp)); OutputStatusMessage(string.Format("TimeZone: {0}", dataObject.TimeZone)); OutputStatusMessage(string.Format("PauseReason: {0}", dataObject.PauseReason)); OutputArrayOfKeyValuePairOfstringstring(dataObject.ForwardCompatibilityMap); OutputArrayOfCustomerInfo(dataObject.LinkedAgencies); OutputStatusMessage(string.Format("SalesHouseCustomerId: {0}", dataObject.SalesHouseCustomerId)); OutputArrayOfKeyValuePairOfstringstring(dataObject.TaxInformation); OutputStatusMessage(string.Format("BackUpPaymentInstrumentId: {0}", dataObject.BackUpPaymentInstrumentId)); OutputStatusMessage(string.Format("BillingThresholdAmount: {0}", dataObject.BillingThresholdAmount)); OutputAddress(dataObject.BusinessAddress); OutputStatusMessage(string.Format("AutoTagType: {0}", dataObject.AutoTagType)); OutputStatusMessage(string.Format("SoldToPaymentInstrumentId: {0}", dataObject.SoldToPaymentInstrumentId)); } } private static void OutputAddress(Address dataObject) { if (null != dataObject) { OutputStatusMessage(string.Format("City: {0}", dataObject.City)); OutputStatusMessage(string.Format("CountryCode: {0}", dataObject.CountryCode)); OutputStatusMessage(string.Format("Id: {0}", dataObject.Id)); OutputStatusMessage(string.Format("Line1: {0}", dataObject.Line1)); OutputStatusMessage(string.Format("Line2: {0}", dataObject.Line2)); OutputStatusMessage(string.Format("Line3: {0}", dataObject.Line3)); OutputStatusMessage(string.Format("Line4: {0}", dataObject.Line4)); OutputStatusMessage(string.Format("PostalCode: {0}", dataObject.PostalCode)); OutputStatusMessage(string.Format("StateOrProvince: {0}", dataObject.StateOrProvince)); OutputStatusMessage(string.Format("TimeStamp: {0}", dataObject.TimeStamp)); OutputStatusMessage(string.Format("BusinessName: {0}", dataObject.BusinessName)); } } private static void OutputArrayOfKeyValuePairOfstringstring(IList<KeyValuePair<string, string>> dataObjects) { if (null != dataObjects) { foreach (var dataObject in dataObjects) { OutputKeyValuePairOfstringstring(dataObject); } } } private static void OutputKeyValuePairOfstringstring(KeyValuePair<string, string> dataObject) { if (null != dataObject.Key) { OutputStatusMessage(string.Format("key: {0}", dataObject.Key)); OutputStatusMessage(string.Format("value: {0}", dataObject.Value)); } } private static void OutputCustomerInfo(CustomerInfo dataObject) { if (null != dataObject) { OutputStatusMessage(string.Format("Id: {0}", dataObject.Id)); OutputStatusMessage(string.Format("Name: {0}", dataObject.Name)); } } private static void OutputArrayOfCustomerInfo(IList<CustomerInfo> dataObjects) { if (null != dataObjects) { foreach (var dataObject in dataObjects) { OutputCustomerInfo(dataObject); OutputStatusMessage("\n"); } } } private static void OutputStatusMessage(String msg) { _output += (msg + "<br/>"); } #endregion OutputHelpers } }
若要首先在本地计算机上部署 Web 应用,必须在 BingAdsWebApp 项目属性窗口中将 “SSL 已启用” 设置为 True 。 如果尚未这样做,则已注册的应用程序重定向 URL 必须包括 SSL URL 和端口,例如 https://localhost:44383/. 从菜单中,选择“ 调试”“开始” > (不调试) 以在本地运行 Web 应用。
若要使用 Azure 应用服务实时部署 Web 应用,请参阅 Azure 文档中的订阅和部署说明,例如,在 Azure 中创建 ASP.NET Framework Web 应用。
配置沙盒
若要使用沙盒,请在项目根 Web.config文件的 appSettings> 节点内<将 BingAdsEnvironment 密钥设置为沙盒。
<add key="BingAdsEnvironment" value ="Sandbox"/>
还可以为每个 ServiceClient 单独设置环境,如下所示。
_customerService = new ServiceClient<ICustomerManagementService>(_authorizationData, ApiEnvironment.Sandbox);
无论是全局设置还是单独设置 ServiceClient 环境,你还需要将 OAuth 环境设置为沙盒。
var oAuthWebAuthCodeGrant = new OAuthWebAuthCodeGrant(ClientId, ClientSecret, new Uri(RedirectionUri), ApiEnvironment.Sandbox);