mercredi 18 février 2009

[Sharepoint]Erreur lors de l'intégration d'une application Asp.net contenant un ReportViewer

Tout d'abord, le pitch :

J'ai en test, un site de publication Sharepoint qui intégre une application Asp.net contenant des ReportViewer.

L'intégration de l'application Asp.net est très simple, il s'agit d'un répertoire virtuel sous le Site web IIS de mon site Sharepoint (avec son propre Pool Applicatif)

Le site ayant besoin d'Ajax, de web services, de reportViewer,..., tout est ajouté dans le web.config du site Sharepoint (httpHandlers, HttpModule, CAS modifié,..).

Tant que je fais du HTTP, pas de souci, tout fonctionne et l'intégration est parfaite ( allez on y croit :p)

Mais Voilà, le site doit être appelé en HTTPS/SSL.

Mise en place du SSL

Le Https est géré par le reverse proxy, qui fait son boulot et publie mon site.

Donc, je configure les adresses de substitutions dans la centrale d'admin.

Et ça fonctionne

L'application Asp.net fonctionne très bien, comme avant

Sauf  lorsque les pages aspx contiennent un ReportViewer, dans ce cas l'application Asp.net me renvoi :


Code de l'événement : 3005
Message d'événement : Une exception non gérée s'est produite.
Heure de l'événement : 18/02/2009 16:21:46
Heure de l'événement (UTC) : 18/02/2009 15:21:46
ID d'événement : e95e6ec874e54379ac3e2f42b0d86dea
Séquence d'événements : 55
Occurrence de l'événement : 3
Code de détail de l'événement : 0

 

Informations d'application : ... Informations sur l'exception : Type d'exception : NullReferenceException Message d'exception : La référence d'objet n'est pas définie à une instance d'un objet. Informations sur la demande : URL de la demande : ma page aspx Chemin d'accès à la demande : ma page aspx Adresse d'hôte de l'utilisateur : x.x.x.x ... Trace de la pile : à Microsoft.Office.Server.Administration.SqlSessionStateResolver.System.Web.IPartitionResolver.ResolvePartition(Object key) à System.Web.PartitionManager.GetPartition(IPartitionResolver partitionResolver, String id) à System.Web.SessionState.SqlSessionStateStore.GetConnection(String id, Boolean& usePooling) à System.Web.SessionState.SqlSessionStateStore.SetAndReleaseItemExclusive(HttpContext context, String id, SessionStateStoreData item, ObjectlockId, Boolean newItem) à System.Web.SessionState.SessionStateModule.OnReleaseState(Object source, EventArgs eventArgs) à System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() à System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

Après de longues recherches, voilà la solution qui m'a débloqué:

Solution (ou pas)

Ne voulant toucher à rien dans le web.config du site Sharepoint, car celui-ci fonctionne, les modifications se trouvent dans le web.config de mon application.

Voilà juste ce que j'ai rajouter dans le noeud system.web:

[sourcecode language="xml"]

     stateConnectionString="tcpip=nomserveurBDD:Port" sqlConnectionString="data source=nomserveurBDD;Trusted_Connection=yes"
     cookieless="false"
     />

[/sourcecode]

Le plus important est de mettre partitionResolverType=""  ainsi, il ne reprend pas le Resolver du site parent (Sharepoint)
ensuite, je lui ai défini manuellement la chaine de connexion.

Je ne suis pas sûr que ça soit la meilleure solution, mais au bout d'une après midi de test, c'est la seule qui a bien voulue fonctionner.

 

 

 

 

mardi 17 février 2009

[ReportViewer] Suppression d'un format d'Export

Suite de l'article précédent : Ajouter un LinkButton dans la Toolbar du ReportViewer.

Dans certain cas, nous pouvons avoir besoin de plus d'un type d'exportation du rapport, de ce fait, il faut utiliser le groupe d'export de la toolbar du ReportViewer.

Si l'on veut enlever un seul de ces types de la liste, par exemple "Excel", voilà la méthode que l'on peut employer.

Je me suis basé sur cet article : Enable HTML in ReportViewer LocalReport

[sourcecode language='c-sharp']

public static void DisableUnwantedExportFormats(ReportViewer viewer)
        {
            const BindingFlags Flags = BindingFlags.NonPublic |
                                       BindingFlags.Public |
                                       BindingFlags.Instance;

            FieldInfo previewService =
             viewer.LocalReport.GetType().GetField("m_previewService", Flags);

            MethodInfo ListRenderingExtensions =
              previewService.FieldType.GetMethod("ListRenderingExtensions", Flags);

            object previewServiceInstance = previewService.GetValue(viewer.LocalReport);

            IList extensions = ListRenderingExtensions.Invoke(previewServiceInstance, null) as IList;

            PropertyInfo name = extensions[0].GetType().GetProperty("Name", Flags);

            foreach (object extension in extensions)
            {
                if (string.Compare(name.GetValue(extension, null).ToString(), "Excel", true) == 0)
                {
                    FieldInfo m_isVisible = extension.GetType().GetField("m_isVisible", BindingFlags.NonPublic | BindingFlags.Instance);
                    FieldInfo m_isExposedExternally = extension.GetType().GetField("m_isExposedExternally", BindingFlags.NonPublic | BindingFlags.Instance);
                    m_isVisible.SetValue(extension, false);
                    m_isExposedExternally.SetValue(extension, false);
                    break;
                }
            }

        } 

[/sourcecode]

[ReportViewer] Ajouter un LinkButton dans la Toolbar du ReportViewer

Avec le ReportViewer fournit dans VS2008, il est possible d’exporter les rapports dans plusieurs formats.

Il suffit pour cela de mettre la propriété “ShowExportControls” à True.

image Voilà ce qui apparait dans la toolbar du ReportViewer.

Mais dans ce cas, où l’on a qu’un seul type d’export (ici PDF), l’utilisateur ne comprend pas pourquoi il devrait choisir vu qu’il n’y a qu’un type.

Donc dans ce qui suit, je vais rapidement exposer comment faire pour rajouter un linkButton qui permettra le téléchargement du PDF.

 

Tout d’abord, pour faire simple, je créé une méthode permettant de rechercher le reportViewer contenu dans un control

[sourcecode language='c-sharp']

public static ReportViewer GetReportViewerFromControl(Control ctrl)
        {
            // récupere le ContentPlaceHolder1
            return Xtr.ToolBox.WebUiExtensions.ControlsAvecRecursivite(ctrl)
            .OfType()
            .FirstOrDefault();
        }

[/sourcecode]

Pour récupérer le premier reportViewer d’un control, j’utilise la méthode de Jérémy ( ControlsAvecRecursivite)

 

Dans le OnLoad de ma page je vais donc cacher le groupe Export et ajouter mon bouton d’export

[sourcecode language='c-sharp']

ReportViewer rv = GetReportViewerFromControl(this);
                if (rv != null)
                {
                    //la liste déroulante du format d'export
                    rv.ShowExportControls = false;

                    //Ajoute le bouton d'export PDF
                    AddExportButtonToReportToolbar(rv);
                }

[/sourcecode]

Dans mon cas, j’ai décidé d’ajouter mon bouton d’exportation dans le groupe d’impression.

Je passe donc le reportViewer en argument de ma méthode et celle-ci, cherchera le groupe d’impression et y ajoutera mon bouton.

[sourcecode language='c-sharp']

private void AddExportButtonToReportToolbar(ReportViewer rv)
        {
                if (rv != null)
                {
                    foreach (Control c in rv.Controls)
                    {
                        //recherche de la toolbar
                        if (c.ToString() == "Microsoft.Reporting.WebForms.ToolbarControl")
                        {
                            foreach (Control ssc in c.Controls)
                            {
                                //on l'ajoute dans le groupe de l'impression
                                if (ssc.ToString() == "Microsoft.Reporting.WebForms.PrintGroup")
                                {
                                    LinkButton lkButton = new LinkButton();
                                    lkButton.ID = "lkButtonExportPDF";
                                    lkButton.CausesValidation = false;
                                    lkButton.Text = "Exporter en PDF";
                                    lkButton.Click+=new EventHandler(lkButton_Click);
                                    lkButton.ToolTip = "Export PDF";
                                    ssc.Controls.Add(lkButton);
                                }
                            }
                        }
                    }
                }
        }

[/sourcecode]

La dernière étape consiste donc à gérer l’évenement click de mon bouton qui permettra l’ouverture du rapport exporté au format PDF

[sourcecode language='c-sharp']

private void lkButton_Click(object sender, EventArgs e)
        {
            ReportViewer rv = HelperBase.GetReportViewerFromControl(this);

            if (rv != null)
            {
                Warning[] warnings = null;
                string[] streamids = null;
                string mimeType = string.Empty;
                string encoding = string.Empty;
                string extension = string.Empty;
                Byte[] bytes = rv.LocalReport.Render("PDF", string.Empty, out mimeType, out encoding, out extension, out streamids, out warnings);
                Response.ContentType = "Application/pdf";
                Response.AddHeader("Content-Disposition", "attachment; filename=export.pdf");
                Response.AddHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
                Response.AddHeader("Pragma", "no-cache");
                Response.AddHeader("Expires", "0");
                Response.OutputStream.Write(bytes, 0, bytes.Length);
                Response.Flush();
            }
        }

[/sourcecode]

Le résultat est le suivant :

image

Le code est assez simple et certainement pas optimisé mais il permet une première approche dans l’ajout de fonctionnalités à la toolbar des reportViewer.