1

I am trying to create a C# HTTP-triggered Function from the Azure portal (Embedded Analytics with Power BI). I follow the post of Taygan ( https://www.taygan.co/blog/2018/05/14/embedded-analytics-with-power-bi ) and using the ref for System.Web.Extensions but it throw:

Metadata file 'System.Web.Extensions' could not be found

This is the code:

#r "System.Web.Extensions"
using System.Configuration;
using System.Net;
using System.Text;
using System.Web.Script.Serialization;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.PowerBI.Api.V2;
using Microsoft.PowerBI.Api.V2.Models;
using Microsoft.Rest;

// Static Values
static string authorityUrl = "https://login.windows.net/common/oauth2/authorize/";
static string resourceUrl = "https://analysis.windows.net/powerbi/api";
static string apiUrl = "https://api.powerbi.com/";
static string clientId = ConfigurationManager.AppSettings["PBIE_CLIENT_ID"];
static string username = ConfigurationManager.AppSettings["PBIE_USERNAME"];
static string password = ConfigurationManager.AppSettings["PBIE_PASSWORD"];
static string groupId = ConfigurationManager.AppSettings["PBIE_GROUP_ID"];
static string reportId = ConfigurationManager.AppSettings["PBIE_REPORT_ID"];

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{

    // Authenticate with Azure Ad > Get Access Token > Get Token Credentials
    var credential = new UserPasswordCredential(username, password);
    var authenticationContext = new AuthenticationContext(authorityUrl);
    var authenticationResult = await authenticationContext.AcquireTokenAsync(resourceUrl, clientId, credential);
    string accessToken = authenticationResult.AccessToken;
    var tokenCredentials = new TokenCredentials(accessToken, "Bearer");

    using (var client = new PowerBIClient(new Uri(apiUrl), tokenCredentials))
    {
        // Embed URL
        Report report = client.Reports.GetReportInGroup(groupId, reportId);
        string embedUrl = report.EmbedUrl;

        // Embed Token
        var generateTokenRequestParameters = new GenerateTokenRequest(accessLevel: "view");
        EmbedToken embedToken = client.Reports.GenerateTokenInGroup(groupId, reportId, generateTokenRequestParameters);

        // JSON Response
        EmbedContent data = new EmbedContent();
        data.EmbedToken = embedToken.Token;
        data.EmbedUrl = embedUrl;
        data.ReportId = reportId;
        JavaScriptSerializer js = new JavaScriptSerializer();
        string jsonp = "callback(" +  js.Serialize(data) + ");";

        // Return Response
        return new HttpResponseMessage(HttpStatusCode.OK) 
        {
            Content = new StringContent(jsonp, Encoding.UTF8, "application/json")
        };
    }
}

public class EmbedContent
{
    public string EmbedToken { get; set; }
    public string EmbedUrl { get; set; }
    public string ReportId { get; set; }
}

How can I fix it? Please help me, thank you.

2
  • Are you using Azure Function v1 or v2? Your code looks like it's .NET Framework. But if you are using Functions v2, you need to use .NET Core. Commented Oct 12, 2018 at 7:05
  • Yes, I've used v1, thanks for your help. Commented Oct 15, 2018 at 1:51

1 Answer 1

4

Problem is caused by the difference of Function runtime.

The tutorial you follow creates function on ~1 runtime where code targets at .NET Framework , while the one you create is on ~2 runtime which runs on .NET Core env. When we create a new Function app its runtime is set to ~2 by default now.

Solution is to set FUNCTIONS_EXTENSION_VERSION to ~1 in Application settings on portal.

To achieve that in ~2 runtime, create a httptrigger in ~2 and replace the sample code with snippet below. Note you also have to add PBIE_TENANT_ID in Application settings with value get from Azure Active Directory> Properties>Directory ID.

#r "Newtonsoft.Json"

using System.Net;
using System.Text;
using Newtonsoft.Json;

static string resourceUrl = "https://analysis.windows.net/powerbi/api";
static string clientId = Environment.GetEnvironmentVariable("PBIE_CLIENT_ID");
static string username = Environment.GetEnvironmentVariable("PBIE_USERNAME");
static string password = Environment.GetEnvironmentVariable("PBIE_PASSWORD");
static string groupId = Environment.GetEnvironmentVariable("PBIE_GROUP_ID");
static string reportId = Environment.GetEnvironmentVariable("PBIE_REPORT_ID");
static string tenantId = Environment.GetEnvironmentVariable("PBIE_TENANT_ID");
static string tokenEndpoint = $"https://login.microsoftonline.com/{tenantId}/oauth2/token";

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, ILogger log)
{
    // Get access token 
    HttpClient authclient = new HttpClient();

    log.LogInformation(resourceUrl);
    log.LogInformation(tokenEndpoint);

    var authContent = new FormUrlEncodedContent(new[]
    {
        new KeyValuePair<string, string>("grant_type", "password"),
        new KeyValuePair<string, string>("username", username),
        new KeyValuePair<string, string>("password", password),
        new KeyValuePair<string, string>("client_id", clientId),
        new KeyValuePair<string, string>("resource", resourceUrl)
    });

    var accessToken = await authclient.PostAsync(tokenEndpoint, authContent).ContinueWith<string>((response) =>
    {
        log.LogInformation(response.Result.StatusCode.ToString());
        log.LogInformation(response.Result.ReasonPhrase.ToString());
        log.LogInformation(response.Result.Content.ReadAsStringAsync().Result);
        AzureAdTokenResponse tokenRes =
            JsonConvert.DeserializeObject<AzureAdTokenResponse>(response.Result.Content.ReadAsStringAsync().Result);
        return tokenRes?.AccessToken;
    });

    // Get PowerBi report url and embed token
    HttpClient powerBiClient = new HttpClient();
    powerBiClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken}");

    log.LogInformation(accessToken);

    var embedUrl =
        await powerBiClient.GetAsync($"https://api.powerbi.com/v1.0/myorg/groups/{groupId}/reports/{reportId}")
        .ContinueWith<string>((response) =>
        {
            log.LogInformation(response.Result.StatusCode.ToString());
            log.LogInformation(response.Result.ReasonPhrase.ToString());
            PowerBiReport report =
                JsonConvert.DeserializeObject<PowerBiReport>(response.Result.Content.ReadAsStringAsync().Result);
            return report?.EmbedUrl;
        });

    var tokenContent = new FormUrlEncodedContent(new[]
    {
        new KeyValuePair<string, string>("accessLevel", "view")
    });

    var embedToken = await powerBiClient.PostAsync($"https://api.powerbi.com/v1.0/myorg/groups/{groupId}/reports/{reportId}/GenerateToken", tokenContent)
        .ContinueWith<string>((response) =>
        {
            log.LogInformation(response.Result.StatusCode.ToString());
            log.LogInformation(response.Result.ReasonPhrase.ToString());
            PowerBiEmbedToken powerBiEmbedToken =
                JsonConvert.DeserializeObject<PowerBiEmbedToken>(response.Result.Content.ReadAsStringAsync().Result);
            return powerBiEmbedToken?.Token;
        });


    // JSON Response
    EmbedContent data = new EmbedContent
    {
        EmbedToken = embedToken,
        EmbedUrl = embedUrl,
        ReportId = reportId
    };
    string jsonp = "callback(" + JsonConvert.SerializeObject(data) + ");";

    // Return Response
    return new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new StringContent(jsonp, Encoding.UTF8, "application/json")
    };

}

public class AzureAdTokenResponse
{
    [JsonProperty("access_token")]
    public string AccessToken { get; set; }
}

public class PowerBiReport
{
    [JsonProperty(PropertyName = "id")]
    public string Id { get; set; }

    [JsonProperty(PropertyName = "name")]
    public string Name { get; set; }

    [JsonProperty(PropertyName = "webUrl")]
    public string WebUrl { get; set; }

    [JsonProperty(PropertyName = "embedUrl")]
    public string EmbedUrl { get; set; }

    [JsonProperty(PropertyName = "datasetId")]
    public string DatasetId { get; set; }
}

public class PowerBiEmbedToken
{
    [JsonProperty(PropertyName = "token")]
    public string Token { get; set; }

    [JsonProperty(PropertyName = "tokenId")]
    public string TokenId { get; set; }

    [JsonProperty(PropertyName = "expiration")]
    public DateTime? Expiration { get; set; }
}

public class EmbedContent
{
    public string EmbedToken { get; set; }
    public string EmbedUrl { get; set; }
    public string ReportId { get; set; }
}
Sign up to request clarification or add additional context in comments.

4 Comments

Thank you very much for your help. ^^. It's run now.
if I using ~2 runtime, any way to rewirte the trigger? I'm not family with .net
@KiKo I usually create a v2 function template in Visual Studio and fix the changes as we can got prompt in IDE. We basically don't need modify code when migrating those code back to portal(.csx file). If you really need this, I can post the code on ~2 runtime later.
yes, I really need it. Please give it to me. thank you. :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.