Share via


レポート コードの例

次の例は、ホテル価格広告のパフォーマンス レポートを要求し、レポートの状態をポーリングし、完了したらレポートをダウンロードする方法を示しています。

この例で使用される要素の詳細については、 リファレンス セクションを参照してください。

詳細については、「 ホテル価格広告レポート API」を参照してください。

この例では、 CodeGrantFlow コード例 を使用してアクセス トークンを取得します。 この例を機能させるには、プロジェクトに CodeGrantFlow の例を含める必要があります。 OAuth アクセス トークンと更新トークンを取得するために使用できるその他のオプションについては、「はじめに」を参照してください。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Timers;
using System.Windows.Forms;  // Add reference; required for CodeGrantFlow
using System.IO.Compression; // Add reference; required for ZIP compression
using Content.OAuth;
using Newtonsoft.Json;       // NuGet Json.NET
using Newtonsoft.Json.Linq;

namespace Reporting
{
    class Program
    {
        // The client ID that you were given when you
        // registered your application. This is a desktop app so there's no secret.

        private static string clientId = "<CLIENTIDGOESHERE>";
        private static string storedRefreshToken = null; 
        private static CodeGrantOauth tokens = null;
        private static DateTime tokenExpiration;

        private static string customerId = "<CUSTOMERIDGOESHERE>"; 
        private static string accountId = "<ACCOUNTIDGOESHERE>";

        private static string BaseUri = "https://partner.api.sandbox.bingads.microsoft.com/Travel/V1";
        private static string ReportJobsTemplate = "/Customers({0})/Accounts({1})/ReportJobs";
        private static string ReportJobTemplate = "/Customers({0})/Accounts({1})/ReportJobs('{2}')";

        private static WebHeaderCollection headers = null;
        private static string jobId = null;
        private static string downloadUrl = null;

        // The extension you use depends of the requested format and whether you requested compression.
        // This example downloads a report in uncompressed CSV format so the file's extension is .csv.
        // If you request compression, use .zip.

        public static string downloadPath = string.Format(@"c:\reports\{0}.csv",
            "Performance_" + DateTime.Now.ToString("yyyyMMddThhmm"));


        // You can optionally pass the ID of a previous report job.

        static void Main(string[] args)
        {
            try
            {
                tokens = GetOauthTokens(storedRefreshToken);

                if (tokens.AccessToken != null)
                {
                    if (args.Length == 1)  // Passed report job ID
                    {
                        jobId = args[0];
                    }
                    else
                    {
                        jobId = RequestReport();
                    }

                    downloadUrl = GetReportJobStatus(jobId);

                    if (null != downloadUrl)
                    {
                        DownloadReport(downloadUrl, downloadPath);
                        var reportPath = DecompressReport(downloadPath);  // Include if you use compression
                        Console.WriteLine("Report downloaded to: " + reportPath);  // Include if you use compression
                    }
                }
            }
            catch (WebException e)
            {
                HttpWebResponse response = (HttpWebResponse)e.Response;

                Console.WriteLine("\nHTTP status: " + response.StatusCode);
                Console.WriteLine("\n" + e.Message);

                // If the request is bad, the API returns the errors in the 
                // body of the response. 

                if (response != null &&
                    (HttpStatusCode.BadRequest == response.StatusCode))
                {
                    using (Stream stream = response.GetResponseStream())
                    {
                        StreamReader reader = new StreamReader(stream);
                        string json = reader.ReadToEnd();
                        reader.Close();

                        var collection = JsonConvert.DeserializeObject<CollectionResponse>(json);

                        PrintErrors(collection.value);
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("\n" + e.Message);
            }
        }


        private static string RequestReport()
        {
            headers = new WebHeaderCollection();
            headers.Add(HttpRequestHeader.Authorization, "Bearer " + tokens.AccessToken);

            Console.WriteLine("**** Request a report with the following parameters: ****\n");

            // Must specify at least one dimension column and one measure column.

            var columns = new List<string>();
            columns.Add(PerformanceColumn.Date.ToString());
            columns.Add(PerformanceColumn.SubAccountId.ToString());
            columns.Add(PerformanceColumn.HotelGroupId.ToString());
            columns.Add(PerformanceColumn.Clicks.ToString());
            columns.Add(PerformanceColumn.CTR.ToString());
            columns.Add(PerformanceColumn.Impressions.ToString());
            columns.Add(PerformanceColumn.SpendUSD.ToString());
            columns.Add(PerformanceColumn.UserCountry.ToString());


            var job = new ReportJob()
            {
                // Required parameters

                ReportType = ReportType.Performance.ToString(),
                StartDate = DateTime.UtcNow.AddDays(-30).ToString("yyyy-MM-dd"),  // today minus 30 days
                EndDate = DateTime.UtcNow.ToString("yyyy-MM-dd"),
                Columns = columns,

                // Optional parameters

                //Format = ReportFormat.Csv.ToString(),
                //Compression = CompressionType.Zip.ToString(),
                //Filter = "DeviceType eq Enum.DeviceType'Desktop' or DeviceType eq Enum.DeviceType'Tablet'"
            };

            PrintReportParameters(job);

            var uri = string.Format(BaseUri + ReportJobsTemplate, customerId, accountId);
            jobId = AddResource(uri, headers, job, typeof(ReportJob));

            return jobId;
        }


        private static string GetReportJobStatus(string jobId)
        {
            headers = new WebHeaderCollection();
            headers.Add(HttpRequestHeader.Authorization, "Bearer " + tokens.AccessToken);

            Console.WriteLine("**** Get the status of report job {0} ****\n", jobId);

            var uri = string.Format(BaseUri + ReportJobTemplate, customerId, accountId, jobId);
            ReportJob pollJob = null;

            // Poll for the status of the report job every 5 seconds. Try getting 
            // the status up to five times until the job completes or fails. If 
            // the job doesn't complete or fail in five attempts (~25 seconds), give up
            // and try again later.

            for (int i = 0; i < 5; i++)
            {
                pollJob = GetResource(uri, headers, typeof(ReportJob)) as ReportJob;

                Console.WriteLine($"Status: {pollJob.Status}");

                if (pollJob.Status == ReportJobStatus.Completed.ToString() ||
                    pollJob.Status == ReportJobStatus.Failed.ToString())
                {
                    if (pollJob.Status == ReportJobStatus.Completed.ToString())
                    {
                        downloadUrl = pollJob.Url;
                    }

                    break;
                }

                Thread.Sleep(5000);
            }

            if (null == downloadUrl)
            {
                if (pollJob.Status != ReportJobStatus.Failed.ToString())
                {
                    Console.WriteLine("The report job is taking longer than expected. Use the ID ({0}) later to check the report's status.", jobId);
                }
                else
                {
                    // Consider getting the X-MS-RequestId header from the response and printing it.
                    // Providing the request ID to support will help track down the issue.

                    Console.WriteLine("The job failed. Try again and if it fails again, contact support.");
                }
            }

            return downloadUrl;
        }


        // Gets the status of the report job.

        private static object GetResource(string uri, WebHeaderCollection headers, Type resourceType)
        {
            object resource = null;
            var request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = "GET";
            request.Headers = headers;
            request.Accept = "application/json";
            var response = (HttpWebResponse)request.GetResponse();

            using (Stream responseStream = response.GetResponseStream())
            {
                var reader = new StreamReader(responseStream);
                string json = reader.ReadToEnd();
                reader.Close();
                resource = JsonConvert.DeserializeObject(json, resourceType);
            }

            return resource;
        }


        // Adds the report job.

        private static string AddResource(string uri, WebHeaderCollection headers, object resource, Type type)
        {
            var request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = "POST";
            request.Headers = headers;
            request.ContentType = "application/json";

            var serializerSettings = new JsonSerializerSettings();
            serializerSettings.NullValueHandling = NullValueHandling.Ignore;

            var json = JsonConvert.SerializeObject(resource, type, serializerSettings);
            request.ContentLength = json.Length;
            using (Stream requestStream = request.GetRequestStream())
            {
                StreamWriter writer = new StreamWriter(requestStream);
                writer.Write(json);
                writer.Close();
            }

            var response = (HttpWebResponse)request.GetResponse();

            AddResponse addResponse = null;

            using (Stream responseStream = response.GetResponseStream())
            {
                var reader = new StreamReader(responseStream);
                var jsonOut = reader.ReadToEnd();
                reader.Close();
                addResponse = JsonConvert.DeserializeObject<AddResponse>(jsonOut);
            }

            return addResponse.value;
        }



        // Prints the report's parameters.

        private static void PrintReportParameters(ReportJob job)
        {
            Console.WriteLine("Report type: " + job.ReportType);
            Console.WriteLine("Start date: " + job.StartDate);
            Console.WriteLine("End date: " + job.EndDate);

            Console.WriteLine("Columns:");
            foreach (var column in job.Columns)
            {
                Console.WriteLine("\t" + column);
            }

            Console.WriteLine("Format: " + (job.Format ?? "default CSV"));
            Console.WriteLine("Compression: " + (job.Compression ?? "uncompressed"));
            Console.WriteLine("Filter: " + (job.Filter ?? "none"));

            Console.WriteLine("Subaccount ID: " + job.SubaccountId ?? "");
            Console.WriteLine("Hotel group ID: " + job.HotelGroupId ?? "");

            Console.WriteLine("Status: " + job.Status);
            Console.WriteLine("Download URL: " + job.Url ?? "");

            Console.WriteLine();
        }


        // Gets the OAuth tokens. If the refresh token doesn't exist, get 
        // the user's consent and a new access and refresh token.

        private static CodeGrantOauth GetOauthTokens(string refreshToken)
        {
            CodeGrantOauth auth = null;

            if (string.IsNullOrEmpty(refreshToken))
            {
                auth = new CodeGrantOauth(clientId);
                auth.GetAccessToken();
            }
            else
            {
                auth = new CodeGrantOauth(clientId);
                auth.RefreshAccessToken(refreshToken);

                // Refresh tokens can become invalid for several reasons
                // such as the user's password changed.

                if (!string.IsNullOrEmpty(auth.Error))
                {
                    auth = GetOauthTokens(null);
                }
            }

            // TODO: Store the new refresh token in secured storage
            // for the logged on user.

            storedRefreshToken = auth.RefreshToken;
            tokenExpiration = DateTime.Now.AddSeconds(auth.Expiration);

            return auth;
        }

        private static void PrintErrors(List<object> errors)
        {
            foreach (var error in errors)
            {
                var e = ((JObject)error).ToObject<AdsApiError>();

                Console.WriteLine("\nError code: " + e.Code);
                Console.WriteLine("Error message: " + e.Message);
                Console.WriteLine("Error parameter: " + e.Property);
            }
        }


        // Download the report to the specified local file.

        private static void DownloadReport(string uri, string path)
        {
            var request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = "GET";
            //request.Accept = "application/zip";  // Include if you request compression
            var response = (HttpWebResponse)request.GetResponse();

            var fileInfo = new FileInfo(path);
            if (fileInfo.Directory != null && !fileInfo.Directory.Exists)
            {
                fileInfo.Directory.Create();
            }

            var bufferSize = 100 * 1024;
            var fileStream = new FileStream(fileInfo.FullName, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize);

            using (BinaryReader reader = new BinaryReader(response.GetResponseStream()))
            {
                using (BinaryWriter writer = new BinaryWriter(fileStream))
                {
                    while (true)
                    {
                        byte[] buffer = reader.ReadBytes(bufferSize);
                        writer.Write(buffer);
                        if (buffer.Length != bufferSize)
                        {
                            break;
                        }
                    }
                }
            }

            fileStream.Close();
        }

        // Decompresses the report. Include if you use compression.

        private static string DecompressReport(String path)  
        {
            var fileInfo = new FileInfo(downloadPath);
            var decompressedFileName = fileInfo.Name.Remove(fileInfo.Name.Length - fileInfo.Extension.Length);
            var decompressedPath = fileInfo.Directory.FullName + "\\" + decompressedFileName;
            ZipFile.ExtractToDirectory(downloadPath, decompressedPath);
            return decompressedPath;
        }

    }


    // Defines the report job.

    class ReportJob
    {
        // Contains the ID of the report job.
        public string Id { get; set; }

        // The list of columns to include in the report.
        public List<string> Columns { get; set; }

        // The compression algorithm to apply to the report.
        public string Compression { get; set; }

        // The format of the report's contents.
        public string Format { get; set; }

        // The report's type.
        public string ReportType { get; set; }

        // The start date of the reporting period.
        public string StartDate { get; set; }

        // The end date of the reporting period.
        public string EndDate { get; set; }

        // The OData filter string to apply to the report data.
        public string Filter { get; set; }

        // The subaccount to scope the report to.
        public string SubaccountId { get; set; }

        // The hotel group within SubaccountId to scope the report to.
        public string HotelGroupId { get; set; }

        // The status of the report job.
        public string Status { get; set; }

        // The URL that you use to download the report.
        public string Url { get; set; }
    }


    // Defines the possible column names for the Performance report.

    enum PerformanceColumn
    {
        Date,
        DeviceType,
        SubAccountId,
        SubAccountName,
        HotelGroupId,
        HotelGroupName,
        HotelId,
        HotelName,
        PartnerHotelId,
        LengthOfStay,
        HotelCountry,
        UserCountry,
        SlotType,
        Clicks,
        CTR,
        Impressions,
        Spend,
        AverageCPC,
        AveragePosition,
        AverageSlotPosition
    }


    // Defines the possible compression algorithms to apply to the report.
    // Currently not supported.

    enum CompressionType
    {
        Zip
    }


    // Defines the possible report formats.

    enum ReportFormat
    {
        Csv
    }

    
    // Defines the possible report types.

    enum ReportType
    {
        Performance
    }


    // Defines the possible report job status values.

    enum ReportJobStatus
    {
        Completed,
        Failed,
        InProgress,
        PendingExecution
    }


    // Defines the response object that POSTs return when
    // you add a resource.

    class AddResponse
    {
        // Contains the ID of the newly added resource (report job).

        public string value { get; set; }

    }


    // Defines the response object if the report request returns errors.

    class CollectionResponse
    {
        // Contains the list of errors.

        public List<object> value { get; set; }

    }



    // Defines the error object that an HTTP status code 400 includes in the
    // response body. 

    class AdsApiError
    {
        public string Code { get; set; }
        public string Message { get; set; }
        public string Property { get; set; }
    }

}