Utiliser BIDS 2012 pour déployer vers Reporting Services 2008 (R1 ou R2)

Contexte

Si on prend pour acquis que Reporting Services 2008 est la première version acceptable pour le développement d’applications BI, il y a quand même plusieurs options pour le développement de rapports:

BIDS Version

Visual Studio

Description

2008 R1 2008 Installé à partir du DVD de SQL Server 2008.
2008 R2 2008 Installé à partir du DVD de SQL Server 2008 R2.
2012 2010 ou 2012 Installé à partir du DVD de SQL Server 2012. S’enregistre dans VStudio 2010 et / ou 2013.

Au niveau de la du schéma XML des fichiers de rapports (RDL), il y a une différence entre R1 et R2. La version 2012 n’apporte aucun changement de ce côté. Lors du déploiement vers un serveur, il est possible de spécifier R1 ou R2:

image

Au niveau des Dlls de Reporting Services installés dans le répertoire PrivateAssemblies de Visual Studio, le nom de l’assembly change entre les versions 2008 et la version 2012:

Ex. Microsoft.ReportingServices.ProcessingObjectModel.dll

BIDS Version

Assembly Version

File Version

2008 R1 10.0.0.0 10.0.1600.22
2008 R2 10.0.0.0 10.50.2500.0
2012 11.0.0.0 11.0.0.3360.12

Pour ceux qui sont familier avec le développement .NET, c’est la version de l’assembly qui compte lorsque l’on crée une référence vers un Dll. La version du fichier permet au développeur de corriger des problèmes sans forcer la recompilation de tous les programmes utilisant la librairie.

Description de la problématique

Ce qui (finalement) nous amène au sujet de ce blogue. Qu’est-ce qui arrive si l’on est dans la situation suivante:

  1. On utilise BIDS 2012;
  2. On déploie vers Reporting Services 2008 R1 ou R2;
  3. On utilise un Dll en support à nos rapports;
  4. Ce Dll réfère un Dll de Microsoft (ex. ProcessingObjectModel.dll) dans sa version 10 afin de fonctionner avec le serveur lors du déploiement.

Dans cette situation, le rapport déployé fonctionnera car le Dll du serveur aura le bon Assembly Name. Par contre, ni le débogage, ni la prévisualisation dans BIDS ne fonctionneront. L’erreur sera que la version 10.0.0.0 ne peut être trouvée. Effectivement, la version chargée par BIDS est 11.0.0.0.

Si vous changez la référence de votre Dll pour utiliser la version 11, vous aurez le problème inverse! Tout fonctionne localemenet mais pas une fois déployé! Sad smile

Solution: Binding Redirect

Ceux qui ont fait du ASP.Net sont peut-être habitué d’ajouter des Binding Redirect à leur Web.Config afin de rediriger les références à de vielles versions automatiquement à la toute dernière version disponible. Dans ce cas, c’est facile de savoir où l’ajouter (Web.Config de l’application Web).

Mais quand on exécute le rapport dans BIDS, à quel endroit doit-on ajouter le Binding Redirect? Vous avez répondu DevEnv.config? Ce n’est pas la bonne réponse!

Lorsque l’on utilise le Preview:

image

L’exécutable qui charge nos Dlls et leurs dépendances pour exécuter le rapport est PreviewProcessingService.exe:

image

Tandis que lorsqu’on veut déboguer un rapport:

image

Il s’agit plutôt de l’exécutable RSReportHost.exe

image

La solution au problème est donc d’ajouter ceci:

<dependentAssembly>
<assemblyIdentity name= »Microsoft.ReportingServices.ProcessingObjectModel » publicKeyToken= »89845dcd8080cc91″ culture= »neutral » />
<bindingRedirect oldVersion= »10.0.0.0″ newVersion= »11.0.0.0″ />
</dependentAssembly>

Aux fichiers:

  1. PreviewProcessingService.exe.configimage
  2. RSReportDesigner.configimage

Du répertoire PrivateAssemblies du Visual Studio dans lequel on utilise Report Designer.

Références

<bindingRedirect> Element
http://msdn.microsoft.com/fr-fr/library/eftw1fys(v=vs.110).aspx

Comment exécuter du code dans un Assembly sans le charger dans le AppDomain principal


Description du problème

Supposons que votre programme utilise un ou des Dlls définis par vos utilisateurs. Ceux-vi vont s’attendre à être en mesure de recompiler leur(s) Dll(s) plusieurs fois pendant l’exécution de votre programme. Il y a de fortes chances que vous ne puissiez pas leur dire de redémarrer votre programme dès qu’ils modifient l’un de leurs Dlls.

Le problème, c’est qu’avec le .Net Framework, dès qu’un Assembly est chargé dans un AppDomain, il ne peut plus jamais être déchargé pour la vie du AppDomain et cela empêche de modifier le Dll sur le disque.

Un programme .Net a toujours un AppDomain principal et tous les Assemblies que vous chargez dans celui-ci ne seront pas déchargés tant et aussi longtemps que votre programme s’exécutera.

Description de la solution

Il faut donc créer un nouvel AppDomain temporaire, charger les Dlls de vos utilisateurs, les utiliser, puis le détruire.

Ce premier article va aborder un cas simple mais courant, où les utilisateurs doivent définir des classes implantant une interface. L’interface sera définie dans un Dll qui sera chargé à la fois par le client (votre programme) et les composantes de vos utilisateurs.

L’exemple que je vais utiliser a 4 composants:

1) L’exécutable principal (HostApplication.exe)

image

2) Le Dll contenant l’interface (Interfaces.dll)

3) Deux Dlls d’exemple (PluginA.dll, PluginB)

On peut voir un diagramme ici qui illustre l’EXE, les AppDomains, les Dlls et les classes.

image

On voit que Interfaces.dll sera chargé dans les deux AppDomains mais que PluginA.dll et PluginB.dll resteront dans le AppDomain temporaire. Cela aura pour effet de permettre la modification de ceux-ci sur le disque pendant que le programme s’exécute.

Détails sur ce qui se passe

Au démarrage de mon programme, on voit qu’il n’y a qu’un seul AppDomain appelé comme l’exécutable:

image

Il suffit de choisir le Dll et la classe à appeler puis cliquer sur Execute:

image

Le résultat sera affiché et un message apparaîtra afin d’empêcher l’AppDomain temporaire d’être détruit:

image

image

Si on rafraîchit Application Domain Viewer avant de fermer le message, on voit l’AppDomain temporaire qui a chargé PluginA.dll et Interfaces.dll:

image

Une fois qu’on ferme le message, l’AppDomain est détruit et on voit que Interfaces.dll est toujours chargé dans le AppDomain principal:

image

Aperçu du code source

En gros, j’ai défini une classe AppDomainProxy qui supporte IDisposable afin de pouvoir être utilisée dans une clause using.

Cette classe définit une méthode CreateInstance qui accepte un AssemblyName et un ClassName et qui retourne une référence de type ISampleInterface.

using (AppDomainProxy pAppDomainProxy = new AppDomainProxy())
{
  ISampleInterface pI = pAppDomainProxy.CreateInstance(cboAssemblyName.Text, cboClassName.Text);

  if (pI != null)
  {
    txtOutput.Text = pI.GetValue(txtParameter.Text);
    MessageBox.Show(« Now is the time to look both AppDomain before it gets deleted! »);
  }
  else
  {
    throw new ApplicationException(« Could not load assembly  » + cboAssemblyName.Text);
  }
}

Dans cette exemple, l’interface est toute simple:

public interface ISampleInterface
{
  string GetValue(string sParam);
}

Les classes des utilisateurs doivent implanter l’interface ISampleInterface et hériter de MarshallByRef:

public class ClassA : MarshalByRefObject, ISampleInterface
{
  public string GetValue(string sParam)
  {
    return String.Format(« {0} from ClassA », sParam);
  }
}

La création d’un AppDomain se fait ainsi (à l’interne de la classe AppDomainProxy):

mpAppDomain = AppDomain.CreateDomain(Guid.NewGuid().ToString(« B »));

Et sa destruction ainsi (dans Dispose):

AppDomain.Unload(mpAppDomain);

J’ai mis l’exemple au complet dans GitHub ici (VS.Net 2008).

AppDomain 102

Ma prochaine entrée de blogue vous montrera comment faire de la .Net Reflection sur des classes chargés dans un AppDomain temporaire, sans les charger dans le AppDomain principal.

C’est pratique s’il vous faut explorer dynamiquement des Assembly sans empêcher les utilisateurs de les modifier sur le disque pendant l’exécution de votre programme.

Références

AppDomainProxy (GitHub)
https://github.com/samsonfr/AppDomainProxy

AppDomain Viewer
http://labs.workshare.com/2010/08/app-domain-viewer.html#comment-form