Archive

Archive for janvier 2011

[C# 4 SSIS] Exécuter un package SSIS

Qui n’a jamais rêver d’exécuter un package SSIS ?
Mouais…bref, c’est quand même parfois bien utile.
Notre utilisation de SSIS se résumais à consommer un fichier type CSV normé et de le mettre dans une base de données sans se taper le code à la main.

Déjà, il faut savoir que SSIS s’exécute toujours sur la machine ayant demander l’exécution du package.

Ce qui veut dire que même si vous avez un serveur SSIS et un serveur Web séparé, si le serveur Web doit exécuter un package SSIS, alors SSIS doit être installé sur le serveur Web.
Si ce n’est pas le cas, le monsieur renvoie une erreur ayant la description suivante : «To run a SSIS package outside of Business Intelligence Development Studio you must install Standard Edition of Integration Services or higher. »
Au moins, on peut difficilement faire plus clair…

Si SSIS ne peut pas être installé sur le serveur Web (dans mon exemple), alors il faut imaginer une autre solution. Pour nous, ça a été de créer une procédure stockée et d’exécuter le package depuis SQL Server via la commande xp_cmdshell (qui lance dtexec).
Ca donne plus ou moins ce résultat :

	-- CREATION DE LA LIGNE DE COMMANDE -----------------------
	set @cmdline = 'dtexec /sq Directory\<BDD>\<Nom_Package> /ser ' + @serveurSSIS
	set @cmdline = @cmdline + ' '
	set @cmdline = @cmdline + '/set \package.variables[CheminSource].Value;"\"'+@cheminSource+'\""'
	set @cmdline = @cmdline + ' '
--paramètres […]
	-- INTEGRATION DES DONNEES ---------------------------------
	-- Exécution du package SSIS d'intégration des données
	EXEC @return = xp_cmdshell @cmdline, no_output
	-- Retour du code erreur, 0 si OK
	select @return

Dans ce cas, il suffit de paramétrer un tout petit peu la procédure pour prendre en compte les paramètres souhaités.

Par contre, si on veut quand même lancer le code via C#, là, il y a encore un pré-requis : c’est la DLL Microsoft.SQLServer.ManagedDTS.dll qu’il faut référencer.
Sur notre serveur, elle se trouvait dans C:\Program Files (x86)\Microsoft SQL Server\100\SDK\Assemblies\.

Après, on peut enfin coder…

using Microsoft.SqlServer.Dts.Runtime;

public static string RunImportPackage(string InputFile, string SSISServer)
{
    StringBuilder str = new StringBuilder();
    Application app = new Application();
    Package package = app.LoadFromSqlServer(PackageName, SSISServer, null, null, null);
    package.Variables["CheminSource"].Value = InputFile;
    // parameters […]
    DTSExecResult result = package.Execute();
    if (result != DTSExecResult.Success)
    {
        for (int i = 0; i < package.Errors.Count; i++)
        {
            str.AppendLine(package.Errors[i].Description);
        }
    }
    else str.AppendLine("OK");
    return str.ToString();
}

Ici, le StringBuilder est uniquement utilisé pour remonter les erreurs dans le cadre d’une application de test. A voir ce qu’il est pertinent de remonter en fonction de la gestion d’erreur…

Publicités
Catégories :.Net, C#, Sql Server, SSIS

[C# 4 MVC2 JQuery] Appeler une action depuis JQuery

La situation : j’ai un champ qui prend une date, selon cette date, je dois alimenter une combobox avec toutes les valeurs valides pour cette date.

Par exemple, je choisis le 24/12/2010 et je dois afficher tous les évènements de cette date dans ma combo.

Donc, coté HTML :

<tr>
    <td><%: Resources.GlobalResource.Date %></td>
    <td><%: Html.TextBox("InputDate", Model.InputDate) %></td>
</tr>
<tr>
    <td><%:Resources.GlobalResource.Events%></td>
    <td><select id="InputSelect" name="InputSelect"></select></td>
</tr>

J’ai utilisé le DatePicker qui est trouvable ici (http://www.eyecon.ro/datepicker/), même si je l’ai un poil modifié pour répondre aux besoins.

Il est pas mal, même si je l’ai trouvé un peu compliqué à skiner (mais mon goût pour faire de la CSS étant…discutable, je ne suis pas forcément partial sur ce point).

$(document).ready(function () {
    $("#InputDate").live('blur', function () {// binding sur l’action de blur (perte du focus)
        var date = $("#InputDate").val();
        if (date != "") {
            $.getJSON("/Controller/GetEvents", // action en question
                    { date: $("#InputDate").val() },// paramètre(s) passé(s) à l’action
                    function (data) {
                        $("#InputSelect").empty();
                        $.each(data, function (index, item) {//pour chaque item
$("#InputSelect").get(0).options[$("InputSelect").get(0).options.length] = new Option(item.Name, item.Code); //on ajoute un item à la combo
                        });
                    });
        }
    });
    $("#InputDate").blur();
});

Là, je renvois une liste d’entités contenant les deux propriétés Name et Code.

Il faut réellement bien faire attention aux fautes de frappe qui peuvent faire perdre un temps fou…

Coté controller, l’action prend en paramètre une string qui sera convertie en date et qui renvoie un JsonResult contenant une liste d’Events, objet qui possède deux propriétés : Name et Code.

public JsonResult GetEvents(string date)
{
   List<Events> list = new List<Events>();
   // Traitements
   return Json(list, JsonRequestBehavior.AllowGet);
}

Très important, en MVC 2, il FAUT ajouter le paramètres JsonRequestBehavior.AllowGet.

Sinon, ça ne marche tout simplement pas…

Catégories :.Net, ASP.Net, JQuery, MVC2

[C#4 MVC2] SSRS et l’export de rapport

Ca, c’est une chose qui m’a fait perdre un temps…considérable.

Honnêtement, c’est assez horrible d’avoir à intégrer les rapports de SSRS dans MVC2…

Bien sûr, il y a le report viewer qui fait tout.

Mais c’est un composant ASP.NET, pas un composant MVC (et dans notre projet, ça importait).

Du coup, on a travaillé avec 4 exports particuliers.

Les exports PDF et Excel ne posent pas de problèmes.

L’export au format image est….embêtant.

L’export au format HTML4.0…c’est pire…

Commençons par les bases.

Pour exporter, il faut appeler le service de SSRS.

Déjà, il faut donc que l’application MVC (son pool d’application) ai les autorisations pour. Comme on avait un Cube derrière, il faut que le user du pool ai aussi les autorisations sur le Cube.

Il est donc préférable d’avoir un seul et unique user de bout en bout. Sinon, c’est…je dirais pas impossible, mais faut avoir du temps à perdre (beaucoup de temps).

Ensuite, on veut exporter depuis un service, il faut donc construire en premier lieu son URL.

Pour nous, ça donnait :

http://<serveur>/ReportServer?/<Répertoire_Rapports><Rapport><Paramètres&gt;

Déjà, il faut savoir quels sont les paramètres liés au rapport et ensuite les paramètres dédiés à « la mise en forme » du rapport.

Dans notre cas, l’URL pouvait faire plus de 300 caractères, avec tous les paramètres…

Pour les paramètres, on avait une base fixe, qui permet de cadrer l’export :

&rs:Command=Render&r:Format=HTML4.0&rc:Parameters=false&rc:Toolbar=false

Voici des screenshots (le haut du rapport) qui permettent de voir ce que font les options Parameters et Toolbar

Parameters & Toolbar à « true » :

Toolbar = « true »; Parameters = « false »

Toolbar et Parameters à « false ».

C’est donc, pour une intégration dans une page web, la dernière option que nous avons retenue : il n’y a aucune toolbar génante.

Voici le code utilisé, dans le cadre d’un export HTML :

string stringUrl = GetUrl(reportName, parameters, "HTML4.0"); // méthode permettant de construire l’URL GetUrl(string, List<string>, string)
WebClient client = new WebClient();
client.Credentials = CredentialCache.DefaultCredentials;

System.IO.StreamReader sr = new System.IO.StreamReader(new System.IO.MemoryStream(buffer));
string content = sr.ReadLine();
sr.Close();

Dans la variable content, on aura donc les données exportées.

Seulement, c’est une page entière !!!
C’est-à-dire qu’il y a la balise <html>, la balise <head>…
Donc, si on veut l’intégrer dans une autre page, eh bien…il faut parser le code HTML…
Si on spécifie la Toolbar à true, alors on a en plus le Javascript (cf. screenshots plus haut) qui doit obligatoirement se positionner dans le <head> pour fonctionner. De même pour le style…

Dans le cadre d’un export au format image, il est possible d’injecter directement le résultat dans une balise <img>.
Cependant, il est pertinent de mettre l’image dans un <div> qui permettra de réduire la taille de l’image, puisque le rapport exporté n’est pas de la taille exact du contenu.
Du coup, il y a des marges blanches tout autour qui peuvent être bien plus grandes que le contenu en lui-même…
Dans notre cas, on avait une image de 200*200px pour une image exportée de la taille d’une page normale (A4).

Par contre, pour les exports Excel et PDF, c’est bien plus simple.

Et pour le coup, bien plus utilisable (autant pour le développeur que pour l’utilisateur final).

string stringUrl = GetUrl(reportName, parameters, "Excel");
name = string.Format("{0}.XLS", name);

WebClient client = new WebClient();
client.Credentials = CredentialCache.DefaultCredentials;
byte[] buffer = client.DownloadData(new Uri(stringUrl, UriKind.Absolute));

return File(buffer, "application/vnd.ms-excel", name);

Et voilà, c’est tout !

Pour l’export PDF, il suffit de changer l’extension et le type MIME, l’extension, « Excel » devant « PDF » et le tour est joué !

Catégories :.Net, ASP.Net, C#, MVC2, Sql Server, SSRS

Sortie de MVC3 en RTM

Ca y est, MVC3 sort en RTM.

Avec l’overview, on peut télécharger cette version sur le site asp.net.

On peut trouver également (parmi d’autres renseignements) une procédure de migration mvc2 -> mvc3.
Edit : il y a également un outil de migration dont il est question sur un blog de la MSDN.

Et puis la documentation sur la msdn.

Que du bon, du coup 🙂

Catégories :.Net, ASP.Net, MVC3

[C# 4] Requêter un cube

Durant mes projets, j’ai également eu le plaisir de faire la connaissance des Cubes (SSAS).

Enfin, c’est pas moi qui bossais dessus, heureusement…

En fait, il y avait notre cube donc, et des rapports avec SSRS qui tapaient dessus.

Les rapports, je les utilisais au format HTML4.0 (j’en reparle bientôt).

Mais là, c’était…dramatiquement long à charger (avant optimisation).

D’où l’interrogation suivante : est-ce que ça ne serais pas plus rapide de prendre les requêtes faites sur le Cube (requêtes MDX) et de les intégrer dans une DAL pour finalement générer le HTML sur le site ?

C’est pertinent, comme question.

Mais avant de répondre, il faut plusieurs éléments :

  • Savoir comment requêter un Cube (c’est plus pratique)
  • Avoir les requêtes MDX (elles s’inventent pas !)
  • Tester et comparer

Déjà, pour interroger un Cube, il y a un pré-requis :

Microsoft.AnalysisServices.AdomdClient.dll.

Il faut aller sur la MSDN et télécharger « Microsoft ADOMD.NET ».

Là, une fois installé, on référence notre DLL dans le projet, on met le using sur Microsoft.AnalysisServices.AdomdClient et on peut commencer à coder.

Voilà le code, du moins son condensé :

string connectionString = "Provider=MSOLAP.4;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=<MyCube>;Data Source=<MySource>";

string requete = "SELECT NON EMPTY { [Measures].[j_Field1], [Measures].[j_Field2] } ON COLUMNS, NON EMPTY { ([DIMENSION_NAME].[Id].[Id].ALLMEMBERS * [DIMENSION_WHO].[Id].[Id].ALLMEMBERS ) } ON ROWS FROM [MYCUBE]";

AdomdConnection connection = new AdomdConnection();
connection.ConnectionString = connectionString;
connection.Open();

AdomdCommand command = _connection.CreateCommand();
command.CommandText = requete;
AdomdDataReader reader = command.ExecuteReader();

connection.Close();

Après, c’est comme toutes les requêtes, on travaille sur le DataReader, donc tout va bien !

A noter, quand même, que j’ai choisis la requête la plus simple…

Mais honnêtement, les requêtes sont super complexes, mais c’est le seul problème qui peut être rencontré.

Mise à jour – 20/02/2013

Bon, comme ce billet à du succès et que les liens spécifiés étaient morts, je mets à jour avec des liens valident 🙂
Decouverte du langage MDX Partie 1
Decouverte du langage MDX Partie 2
Decouverte du langage MDX Partie 3
Decouverte du langage MDX Partie 4

Catégories :.Net, C#, Sql Server, SSAS

[C# 4 MVC2] Global.asax et gestion des 404

Dans le billet précédent, il y avait la gestion de l’action non reconnue.

Mais qu’en est-il du controller non reconnu ?

Si, comme moi, vous utilisez la route par défaut, il est possible de tomber sur des 404 en spécifiant une URL qui n’existe pas : http://localshot/Unknow.

Là, malheureusement, je n’ai pas trouvé de moyen de gérer cela dans mon BaseController.

Du coup, c’est du coté du global.asax que je me suis tourné.

Comme toute erreur au niveau du global.asax, c’est dans Application_Error que je vais mettre mon code.

/// <summary>
/// Méthode permettant de gérer les exceptions non gérées jusque là
/// </summary>
/// <param name="sender">Objet ayant provoqué l'exception</param>
/// <param name="e">Paramètres</param>
protected void Application_Error(object sender, EventArgs e)
{
    Exception exception = Server.GetLastError();

    // Par défaut, Firefox cherche le /favicon.ico, et génère une 404 en son absence
    if (exception.Message != null && exception.Message.Contains("/favicon.ico"))
        return;
    // Publication de l'exception
    Response.Clear();
    Server.ClearError();

    // Gestion des HttpException
    if (exception.GetType() == typeof(HttpException))
    {
        HttpException httpException = exception as HttpException;
        if (httpException != null)
        {
            // Gestion des 404
            if (httpException.GetHttpCode() == 404)
            {
                IController icontroller = new Controllers.HomeController();
                RouteData routeData = new RouteData();
                routeData.Values.Add("controller", "Home");
                routeData.Values.Add("action", "Error404");
                icontroller.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
            }
        }
    }
}

Ici, comme on est pas dans le BaseController, on doit se créer un propre controller afin de gérer la redirection de l’utilisateur vers la page d’erreur qui va bien. Comme c’est dans le HomeController que j’ai les actions qui vont bien, c’est une instance de HomeController que je créé.

Catégories :.Net, ASP.Net, MVC2

[C#4 MVC2] Les méthodes d’action du controller

Dans l’article précédent, j’ai parlé de la méthode OnActionExecuting(ActionExecutingContext).

Ceci dit, il y a d’autres méthodes qui peuvent être utiles (elles seront également présentes dans la BaseController).

Déjà, qu’en est-il de leur ordre d’exécution ?

Le voici (du moins pour celles qui me paraissaient pertinentes) :

Méthode d’action Description MSDN
void Execute(System.Web.Routing.RequestContext) Exécute le contexte de la requête spécifiée.
void Initialize(System.Web.Routing.RequestContext) Initialise des données qui peuvent ne pas être disponibles lorsque le constructeur est appelé.
void ExecuteCore() Exécute la requête.
ITempDataProvider CreateTempDataProvider() Crée un fournisseur de données temporaire.
IActionInvoker CreateActionInvoker() Crée un demandeur d’action.
void OnAuthorization(AuthorizationContext) Méthode appelée lors de l’autorisation.
void OnActionExecuting(ActionExecutingContext) Méthode appelée avant la méthode d’action.
void OnActionExecuted(ActionExecutedContext) Méthode appelée après la méthode d’action.
void OnResultExecuting(ResultExecutingContext) Méthode appelée avant l’exécution du résultat d’action qui est retourné par une méthode d’action.
void OnResultExecuted(ResultExecutedContext) Méthode appelée après l’exécution du résultat d’action qui est retourné par une méthode d’action.
void Dispose(bool) Libère toutes les ressources qui sont utilisées par l’instance actuelle de la classe Controller.

A noter pour la méthode Execute, la MSDN dit :
« Cette méthode est une implémentation de Execute. Vous ne devez pas en général substituer cette méthode. Substituez Initialize ou ExecuteCore à la place. »

Et voici deux autres méthodes qui sont intéressantes :

Méthode d’action Description
protected virtual void OnException(ExceptionContext) Méthode appelée lorsqu’une exception non gérée se produit au cours de l’action.
protected virtual void HandleUnknownAction(string) Méthode appelée lorsqu’une requête correspond à ce contrôleur, mais qu’aucune méthode portant le nom d’action spécifié n’est trouvée dans le contrôleur.

J’ai choisis, dans l’article précédent, de vérifier que l’utilisateur n’est pas anonyme dans la méthode OnActionExecuting, mais la méthode OnAuthorization est bien placée pour cela.

Dans la méthode OnException, on va naturellement gérer les exceptions qui peuvent arriver.

Une des questions que je me suis poser : comment alimenter un model spécifique et rediriger vers une page qui va l’exploiter ?

C’est assez simple :

/// <summary>
/// Evènement appelé lorsqu'une exception non gérée est levée.
/// </summary>
/// <param name="filterContext">Le context de l'action.</param>
protected override void OnException(ExceptionContext filterContext)
{
    try
    {
        // Publication de l'exception
    }
    catch { }
    // On redirige vers la page d'erreur
    HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception,
        filterContext.Controller.ToString(),
        (string)filterContext.RouteData.Values["action"]);
    ViewResult result = new ViewResult() {
        ViewName = "Error",
        TempData = filterContext.Controller.TempData,
        ViewData = new ViewDataDictionary<HandleErrorInfo>(model)
    };
    filterContext.Result = result;
    filterContext.ExceptionHandled = true;
    filterContext.HttpContext.Response.Clear();
    filterContext.HttpContext.Response.StatusCode = 500;
    filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}

Dans le cas où la route par défaut est utilisée, il y a toujours moyen de modifier l’url pour faire pointer vers des actions qui ne sont pas prises en charge.

Dans ce cas là, on va devoir utiliser la méthode HandleUnknwonAction (elle porte bien son nom, n’est ce pas ?).

Dans le cas d’une action non connue, par exemple http://localhost/Home/Unknow, une HttpException est levée dans la méthode HandleUnknwonAction.

Du coup, il suffit simplement d’avoir un code du type :

/// <summary>
/// Evènement appelé lorsqu'une action non reconnue est appelée (ex : /Home/UnknowAction).
/// </summary>
/// <param name="actionName">Nom de l'action.</param>
protected override void HandleUnknownAction(string actionName)
{
    try
    {
        base.HandleUnknownAction(actionName);
    }
    catch (HttpException ex)
    {
        HandleErrorInfo model = new HandleErrorInfo(ex,
            this.ToString(),
            actionName);
        // Publication de l'exception
        // […]
        this.View("Error", model).ExecuteResult(this.ControllerContext);
    }
}
Catégories :.Net, ASP.Net, C#, MVC2