Analysis Services Report Pack

Publié par Fabrice Michellonet sous le(s) label(s) , le 20 janvier 2011

Avant-hier, mon client m'a demandé s'il était possible d'avoir une "cartographie" de sa base OLAP que nous sommes en train de construire.

Par cartographie, il entendait a minima avoir un listing de toutes les mesures, dimensions et niveaux existants. Ma première réaction fut de vérifier si BIDS Helper n'avait pas ce genre de feature cachée dans un coin. Circulez rien à voir de très intéressant de ce côté-là!

Je me suis ensuite rué sur Google pour voir s'il n'y avait pas un SQL Server Report Pack comme celui qui était apparu pour SQL Server 2005, mais axé Analysis Services. Rien non plus en direct de chez Redmond.
Par contre en creusant un peu je suis tombé sur l'excellent blog de la société Capstone et leur superbe Analysis Services 2008 Metadata Report Pack.

Voici un aperçu du rendu des rapports Reporting Services mis à disposition :

Database Cube Dimension

Et pour faire plaisir à mon ami François, il y a même des infos sur le Data Mining :

Dimension

Pour installer le pack il vous faudra créer un serveur lié dans SQL Server; Voici le script SQL a mettre en adéquation avec la configuration de votre machine :

EXEC master.dbo.sp_addlinkedserver @server = N'SSAS_METADATA', @srvproduct=N'SSAS', @provider=N'MSOLAP', @datasrc=N'localhost', @provstr=N'Provider=MSOLAP.4', @catalog=N'Adventure Works DW 2008R2'
EXEC master.dbo.sp_addlinkedsrvlogin @rmtsrvname=N'SSAS_METADATA',@useself=N'False',@locallogin=NULL,@rmtuser=NULL,@rmtpassword=NULL

Puis déployer les rapports sur Reporting Services.

Je me permets de reposter l'ensemble de la solution de Dan Meyers ici; version dans laquelle j'ai corrigé les connexions en dur par une connexion vers le serveur lié.

En dehors de répondre au besoin ponctuel de mon client, je me dis que ces rapports pourraient très bien trouver leur place dans une documentation technique à réaliser en fin de projet.

Injection HTML à base d'HTTPModule

Publié par Fabrice Michellonet sous le(s) label(s) , le 18 janvier 2011

Dernièrement j'ai échangé avec mon Boss, Jérôme, sur les possibilités d'enrichir un site web non dévellopé en .NET mais qui serait hosté dans IIS.
On pourrait se mettre à apprendre le Python... mais voila on est un peu fainéant :)

Une des solutions que je lui ai proposé étais de tirer parti du mécanisme d'extensibilité du pipeline de IIS7 afin d'intervenir directement sur le code HTML renvoyé au navigateur.

Green Injection

Je me propose donc de présenter dans les grandes lignes comment mettre en place ce genre de solution.
Pour illustrer mes propos simplement nous allons nous rajouter un div en fin de page, le div contenant l'heure du système.

On commence par créer notre HTTPModule et on répond à l'évenement BeginRequest :

using System;
using System.IO;
using System.Web;

namespace InjectorModule
{
    public class Injector : IHttpModule
    {
        private HttpApplication _application;

        public void Init(HttpApplication context)
        {
            context.BeginRequest += OnBeginRequest;
            _application = context;
        }

        private void OnBeginRequest(object sender, EventArgs e)
        {
            Stream filter = FilterFactory.GetFilter(_application);
            if (filter == null)
                return;

            _application.Response.Filter = filter;
        }

        public void Dispose() {}
    }
}

On délègue à une factory le role de créer ou non un filter en fonction de règles qui ne sont pas connues par le Module.
Le module reste ainsi une brique uniquement technique technique.

Passons à la factory

using System.IO;
using System.Web;

namespace InjectorModule
{
    internal static class FilterFactory
    {
        public static Stream GetFilter(HttpApplication application)
        {
            if (application == null || application.Response.ContentType == null)
                return null;

            if (application.Response.ContentType.ToUpperInvariant().Contains("HTML"))
                return new AppendDateTimeFilter(application.Response.Filter, application.Request.ContentEncoding);

            return null;
        }
    }
}

Ici pas de règles métiers compliquées, on ajoute un Filter si le content type est de type HTML.

Et finalement voici l'implémentation du Filter qui tranforme la réponse envoyée aux navigateurs :

using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace InjectorModule
{
    internal class AppendDateTimeFilter : Stream
    {

        private readonly Stream _inputStream;
        private readonly Encoding _encoding;
        private readonly StringBuilder _responseHtml;

        public AppendDateTimeFilter(Stream input, Encoding contentEncoding)
        {
            _inputStream = input;
            _encoding = contentEncoding;
            _responseHtml = new StringBuilder();
        }

        #region Filter overrides

        public override bool CanRead
        {
            get { return true; }
        }

        public override bool CanSeek
        {
            get { return true; }
        }

        public override bool CanWrite
        {
            get { return true; }
        }

        public override void Close()
        {
            _inputStream.Close();
        }

        public override void Flush()
        {
            _inputStream.Flush();
        }

        public override long Length
        {
            get { return 0; }
        }

        public override long Position { get; set; }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return _inputStream.Seek(offset, origin);
        }

        public override void SetLength(long length)
        {
            _inputStream.SetLength(length);
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            return _inputStream.Read(buffer, offset, count);
        }
        #endregion

        public override void Write(byte[] buffer, int offset, int count)
        {
            try
            {
                string bufferContent = _encoding.GetString(buffer);

                // Wait for the closing tag
                Regex eof = new Regex("", RegexOptions.IgnoreCase);

                _responseHtml.Append(bufferContent);
                if (!eof.IsMatch(bufferContent))
                    return;

                // Transform the response and write it back out
                string finalHtml = _responseHtml
                                        .Replace("", string.Format(@"
{0}
", DateTime.Now)) .ToString(); // Send. byte[] data = _encoding.GetBytes(finalHtml); _inputStream.Write(data, 0, data.Length); } catch (Exception) { } } } }

Pour tester rien de plus simple, on compile l'assembly, on la place dans le dossier bin du site web que l'on veux trafiquer.
Dans notre cas, nous avons aussi du créer le dossier bin car non existant dans l'appli Web Python.
Le fait de placer l'assembly dans un dossier bin, est une contrainte technique imposée par IIS; je n'ai pas trouvé d'alternatives et a priorit il n'y a pas de configuration qui pourrait influer sur cette contrainte.
Finalement dans le gestionnaire IIS, rendez vous dans la section Modules de votre application Web.
En ouvrant la fenetre d'ajout de modules, votre assembly sera désormais présente; Sélectionnez la, et pointez votre navigateur sur ce dernier.

Tadam!!! l'heure s'affiche tout en bas :)

J'esperes que cette solutions vous ouvre de nouvelles possibilités.

Un dernier mot pour vous conseiller l'excellente librairie htmlagilitypack qui vous donnera les moyens de parser facilement un document HTML, même malformé.