Consumir Azure Analysis Services desde una Web API

Estos meses atr谩s no me ha dado la vida para publicar en mi querido blog, aunque eso no significa que no tenga ciento de cosas que contarte 馃檪 He estado metida en miles de pruebas de concepto para clientes, nuevas tecnolog铆as, la universidad y siempre con muchas ganas de seguir aprendiendo m谩s y m谩s. Espero poder retomar el blog a partir de ahora y seguir compartiendo contigo las cosas chulas que me van pasado cada semana.

Hoy quer铆a contarte una de las pruebas de concepto en la que estuve trabajando: c贸mo consumir Azure Analysis Services desde una Web API. Este escenario puede ser interesante cuando necesitas acceder a Azure Analysis Services desde una aplicaci贸n que no tiene conector con esta tecnolog铆a, como por ejemplo Java, o bien cuando quieres modularizar esta funcionalidad en un servicio aparte de la l贸gica de tu aplicaci贸n/es.

Todo el c贸digo de esta prueba de concepto est谩 alojado en mi cuenta de GitHub.聽Aqu铆 te muestro los pasos que deber铆as de seguir para conseguir nuestro objetivo:

Crea una cuenta de Azure Analysis Services

Create an Azure Analysis Services Account

Los par谩metros que se necesitan para poder crear la cuenta son los siguientes:

Server Name: El nombre de tu Analysis Server.
Subscription: La suscripci贸n de Microsoft Azure que quieres utilizar.
Resource group: El grupo de recursos donde quieres almacenar el nuevo servicio.
Location: El datacenter en el cual quieres desplegarlo.
Pricing tier: La opci贸n B1 ser铆a m谩s que suficiente para probar esta demo.
Administrator: Tu cuenta es elegida por defecto.
Backup Storage Settings: Necesitas seleccionar o crear una nueva cuenta de almacenamiento para el backup.
Storage key expiration: Para esta demo podemos dejarlo con la opci贸n聽Never.

Crear un modelo de Adventure Works

Para mostrar este ejemplo de la manera m谩s sencilla y r谩pida posible, vamos a utilizar el modelo de Adventure Works. Para ello solamente tienes que hacer clic sobre el bot贸n聽+聽New model.

Create a new model

Los datos de ejemplo son seleccionados por defecto y s贸lo debes hacer clic en el bot贸n聽Add.

Crear una aplicaci贸n nativa en Azure Active Directory

El objetivo de esta aplicaci贸n es poder solicitar tokens en Azure Active Directory para el cliente que va a hacer uso de esta API. Accede al servicio Azure Active Directory a trav茅s del portal de Azure y haz clic en la secci贸n聽App registrations y genera una nueva a trav茅s de聽+ New application registration. Necesitar谩s:

Name: Elige un nombre descriptivo para tu aplicaci贸n
Application type: Native
Redirect URI: Puedes elegir la URI que quieras. No es necesario que apunte a ning煤n sitio real.

Create Azure Active Directory App

El paso final es dar acceso a la aplicaci贸n al servicio de Azure Analysis Services. Haz clic en el apartado聽Settings:

Registered app - Settings

Accede a la secci贸n聽Required permissions,聽haz clic en聽+ Add, busca Azure Analysis Services y seleccionalo.

Select an API - Azure Analysis Services

Habilita el check聽Read and Write all Models聽y finaliza pulsando en聽Done.

Configurar AADAccessToken

El proyecto de AADAccessToken es el cliente que va a comunicarse con la Web API. El c贸digo es bastante sencillo ya que simplemente lanza el login para poder recuperar el token y una vez que la validaci贸n ha sido satisfactoria queda a la espera de que escribas cualquier query, se la lance a la API y muestre el resultado:

using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Configuration;
using System.Diagnostics;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace AADAccessToken
{
    class Program
    {
        static void Main(string[] args)
        {
            string ApiUrl = ConfigurationManager.AppSettings["apiUrl"];

            var token = GetBearerToken().GetAwaiter().GetResult();

            Debug.WriteLine(token);

            if (token != null)
            {
                
                DisplayMessage("CONNECTED TO AZURE ANALYSIS SERVICES", ConsoleColor.White, ConsoleColor.DarkGreen);
                DisplayMessage("ENTER YOUR QUERY (TYPE 'quit' TO EXIT):", ConsoleColor.Yellow);
                var query = Console.ReadLine();

                while (query != "quit")
                {

                    var client = new HttpClient();
                    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

                    var response = client.GetAsync(new Uri($"{ApiUrl}{query}"));

                    string content = response.Result.Content.ReadAsStringAsync().Result;
                    
                    DisplayMessage(content,ConsoleColor.DarkYellow);                    
                    DisplayMessage("ENTER YOUR QUERY (TYPE 'quit' TO EXIT):",ConsoleColor.Yellow);                    
                    query = Console.ReadLine();
                }
            }
            else
            {
                DisplayMessage("ERROR DURING CONNECTION", ConsoleColor.White, ConsoleColor.Red);                
            }

        }

        static void DisplayMessage(string message, ConsoleColor foregroundColor = ConsoleColor.White, ConsoleColor backgroundColor = ConsoleColor.Black)
        {
            Console.BackgroundColor = backgroundColor;
            Console.ForegroundColor = foregroundColor;
            Console.WriteLine(message);
            Console.ResetColor();
        }


        /// 
<summary>
        /// Gets the access token to invoke Azure Analysis Services
        /// </summary>

        /// <returns></returns>
        static async Task<string> GetBearerToken()
        {
            var authority = ConfigurationManager.AppSettings["aad:Authority"];
            var resourceUri = ConfigurationManager.AppSettings["aad:ResourceUri"];
            var clientId = ConfigurationManager.AppSettings["aad:ClientId"];
            var redirectUri = new Uri(ConfigurationManager.AppSettings["aad:RedirectUri"]);

            var ctx = new AuthenticationContext(authority);
            var token = await ctx.AcquireTokenAsync(resourceUri, clientId, redirectUri, new PlatformParameters(PromptBehavior.Auto));

            return token.AccessToken;
        }

    }
}

Haciendo uso de los servicios que hemos creado anteriormente, necesitas configurar los siguientes valores en la secci贸n AppSettings del archivo de configuraci贸n de la aplicaci贸n de consola:

<!--Azure Active Directory values-->
<add key="aad:RedirectUri" value="[Reply URL from you Azure Active Directory Native App]"/>
<add key="aad:Authority" value="https://login.windows.net/common"/>
<add key="aad:ClientId" value="[Your Azure Active Directory App Id]"/>
<!-- Azure Analysis Services -->
<add key="aad:ResourceUri" value="https://westeurope.asazure.windows.net"/>
<!-- End Azure Active Directory values-->
<!-- Web API URL-->
<add key="apiUrl" value="http://localhost:50117/api/values/?query="/>    

Con esto ya quedar铆a el cliente listo para logarse contra tu Azure Active Directory y realizar las llamadas a la API.

Configurar WebAPI

La l贸gica implementada para la Web API est谩 localizada en el controlador ValuesController. Recuerda que se trata de una prueba de concepto y hay ciertos valores hardcodeados que deben de ser reubicados adem谩s de que es necesario implementar un sistema de autenticaci贸n para dicha API.

using Microsoft.AnalysisServices.AdomdClient;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Text;
using System.Web.Http;
using System.Web.Script.Serialization;

namespace ADOMDWebAPI.Controllers
{

    public class ValuesController : ApiController
    {
        readonly string asDataSource = ConfigurationManager.AppSettings["asDataSource"];

        // GET api/values
        public object Get(string query)
        {
            using (var ssasConnection = GetConnection()) //It closes Azure Analysis Services Connection when it finishes
            {
                var cmd = new AdomdCommand(query)
                {
                    Connection = ssasConnection
                };

                var json = CreateJsonFromDataReader(cmd);

                return (json);
            }
        }

        private object CreateJsonFromDataReader(AdomdCommand cmd)
        {
            var sb = new StringBuilder();
            var sw = new StringWriter(sb);
            var fieldVal = string.Empty;
            var prevFieldVal = string.Empty;
            var columnName = string.Empty;
            var curColumn = new List&lt;string&gt;();

            using (JsonWriter json = new JsonTextWriter(sw))
            {
                using (var reader = cmd.ExecuteReader())
                {
                    json.WriteStartArray();

                    while (reader.Read())
                    {
                        json.WriteStartObject();

                        for (int i = 0; i &lt; reader.FieldCount; i++)
                        {
                            if (reader[i] != null)
                            {
                                fieldVal = reader[i].ToString();
                                if (i != 0 &amp;&amp; reader[i - 1] != null)
                                    prevFieldVal = reader[i - 1].ToString();
                                else prevFieldVal = "First";
                                if ((fieldVal == null || fieldVal.ToLower().Trim() == "undefined" || fieldVal.ToLower().Trim() == "unknown") &amp;&amp; (prevFieldVal == null || prevFieldVal.ToLower().Trim() == "undefined" || prevFieldVal.ToLower().Trim() == "unknown"))
                                {
                                    continue;
                                }
                                else
                                {
                                    columnName = reader.GetName(i).Replace(".[MEMBER_CAPTION]", "").Trim();
                                    curColumn = columnName.Split(new string[] { "." }, StringSplitOptions.None).ToList();
                                    columnName = curColumn[curColumn.Count - 1].Replace("[", "").Replace("]", "");

                                    json.WritePropertyName(columnName);
                                    json.WriteValue(reader[i]);


                                }
                            }
                        }
                        json.WriteEndObject();
                    }
                    json.WriteEndArray();

                }


                var json_serializer = new JavaScriptSerializer();
                return json_serializer.DeserializeObject(sw.ToString());
            }
        }

        private AdomdConnection GetConnection()
        {
            /*User account token*/
            var header = Request.Headers.GetValues("Authorization").First();
            var token = header.ToString().Substring(header.ToString().LastIndexOf(' ')).Trim();

            var connectionString = $"Provider=MSOLAP;Data Source={asDataSource};Initial Catalog=adventureworks;User ID=;Password={token};Persist Security Info=True;Impersonation Level=Impersonate";

            var ssasConnection = new AdomdConnection(connectionString);
            ssasConnection.Open();

            return ssasConnection;
        }
    }
}

El 煤nico par谩metro que necesitas configurar en la secci贸n en el AppSettings es el data source de Analysis Services (asDataSource).

<!-- Azure Analysis Services -->    
<add key="asDataSource" value="[Azure Analysis Services Data Source - ex: asazure://westeurope.asazure.windows.net/returngis]"/>
<!-- End Azure Analysis Services -->

Una vez completados todos los pasos ya est谩s listo para consultar Azure Analysis Services desde la Web API.

Pru茅balo

Ejecuta los dos proyectos e inicia sesi贸n con tus credenciales en la aplicaci贸n de consola. Una vez que te has logado correctamente ver谩s el siguiente mensaje

Connected to Azure Analysis Services

Si has seguido el ejemplo, y est谩s usando el modelo Adventure Works, puedes probarlo con estas queries:

Evaluate TOPN(10,Customer,Customer[Customer Id],1)

Evaluate TOPN(10,Customer,Customer[Customer Id],1)

Evaluate(ROW(“Count”,COUNTROWS(‘Product’)))

Evaluate(ROW("Count",COUNTROWS('Product')))

Todo el c贸digo est谩 disponible en mi cuenta de GitHub. Adem谩s de la aplicaci贸n de consola, existe un tercer proyecto que permite el consumo de la Web API en un cliente web.

隆Saludos!

Deja tu comentario

Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *

Este sitio usa Akismet para reducir el spam. Aprende c贸mo se procesan los datos de tus comentarios.