Je vous rassure, je ne vais pas vous refaire le couplet bien connu “Les exceptions c’est mal, il faut faire des TryCatch, etc…”, je pars du point de vue que vous le savez déjà et que vous essayez tant bien que mal de faire au mieux.
Ce ne sera une nouveauté pour personne aucun SharePointeur, vous avez la possibilité d’enregistrer le comportement de votre application dans les logs SharePoint (14/Logs) ou dans l’Event Viewer. Mais souvent, on se demande comment le faire, comment choisir entre l’un ou l’autre, etc. Au final, peu de développeurs respectent ces pratiques et on se retrouve souvent avec des développements bancals.
Voyons donc une manière simple et rapide de mettre en place la gestion des logs et exception dans SharePoint via un cas concret :
Je souhaite développer une webpart qui affiche tout les listes présentes sur le site courant. Et je souhaite que cette webpart dans les cas où elle viendrait à “planter”, affiche un message d’erreur “propre” aux utilisateurs et des informations détaillés aux Développeurs/Administrateurs. Ces informations seront disponibles sur les logs ou/et dans l’event viewer.
On va créer cette webpart, j’ai pour habitude de partir sur un développement basé sur le pattern MVP mais ce que je vous propose marchera aussi si vous avez envie de la faire à l’ancienne (ie. ASP.Net spaghetti).
On part sur une vue et un modèle très simple
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DemoExceptionHandlingWebPart.ExceptionView
{
interface IExceptionView
{
List<string> SetSiteData { set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
namespace DemoExceptionHandlingWebPart.ExceptionView
{
interface IExceptionViewModel
{
List<string> GetSiteData();
}
}
La webpart en elle-même est très simple aussi. la seule particularité est l’utilisation d’un “ErrorVisualizer” dont je vous parlerais tout à l’heure.
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using DemoExceptionHandlingWebPart.ExceptionHandling;
using System.Collections.Generic;
namespace DemoExceptionHandlingWebPart.ExceptionView
{
[ToolboxItemAttribute(false)]
public class ExceptionView : WebPart, IExceptionView
{
private ExceptionViewPresenter presenter;
private GridView gvList = new GridView();
public ExceptionView()
{
}
protected override void CreateChildControls()
{
base.CreateChildControls();
IErrorVisualizer errorVisualizer = new ErrorVisualizer(this);
presenter = new ExceptionViewPresenter(this, new ExceptionViewModel());
this.presenter.ErrorVisualizer = errorVisualizer;
presenter.SetSiteData();
Controls.Add(gvList);
}
public List<string> SetSiteData
{
set
{
gvList.ID = "gridViewList" + this.ID;
gvList.DataSource = value;
gvList.DataBind();
}
}
}
}
On a donc ici, un minimum d’intelligence dans la vue, on fait appel au présenter et on instancie les contrôles dont on a besoin.
Vous vous en doutez, c’est dans le presenter que tout (ou presque) se passe.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DemoExceptionHandlingWebPart.ExceptionHandling;
using Microsoft.SharePoint;
namespace DemoExceptionHandlingWebPart.ExceptionView
{
class ExceptionViewPresenter
{
private IExceptionView view;
private IExceptionViewModel model;
private Random rand;
public ExceptionViewPresenter(IExceptionView view, IExceptionViewModel model)
{
this.view = view;
this.model = model;
this.rand = new Random(DateTime.Now.Millisecond);
}
public IErrorVisualizer ErrorVisualizer { get; set; }
public void SetSiteData()
{
int eventId = rand.Next(1000);
try
{
if (eventId > 500)
{
throw new SPException("The current user does not have the permissions to access this content");
}
else
{
this.view.SetSiteData = this.model.GetSiteData();
}
}
catch (Exception ex)
{
// If an unhandled exception occurs in the view, then instruct the ErrorVisualizer to replace
// the view with an errormessage.
ViewExceptionHandler viewExceptionHandler = new ViewExceptionHandler();
viewExceptionHandler.HandleViewException(ex, this.ErrorVisualizer, eventId);
}
}
}
}
Comme je souhaite pouvoir tester mon application (ie. je veux la voir “crasher” souvent), j’ai décidé de faire une simple condition sur la valeur d’un rand.next. Si cette valeur est > 500 alors je lance une exception sinon je récupère les données de mon model. Le tout englobé dans un TryCatch dans le but de récupérer l’erreur et de la traiter dans le ViewExceptionHandler.
Voyons maintenant le reste de la webpart :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
namespace DemoExceptionHandlingWebPart.ExceptionView
{
class ExceptionViewModel : IExceptionViewModel
{
public List<string> GetSiteData()
{
SPWeb web = SPContext.Current.Web;
List<string> result = new List<string>(web.Lists.Count);
foreach (SPList list in web.Lists)
{
if (!list.Hidden)
result.Add(list.Title);
}
return result;
}
}
}
Et j’ai terminé mon travail.
Comment ça terminé mon travail ? Et la gestion des exceptions me direz vous ? Et bien tout est géré dans le ViewExceptionHandler et sa méthode HandleViewException
/// <summary>
/// Handle an exception in a view. This method will log the error using the ILogger that's registered in
/// the <see cref="SharePointServiceLocator"/> and will show the error in the <paramref name="errorVisualizer"/>
/// </summary>
/// <param name="exception">The exception to handle.</param>
/// <param name="errorVisualizer">The error visualizer that will show the errormessage.</param>
/// <param name="eventId">The EventId to log the error under.</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Visualizer"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "In this case, we are building an unhandled exception handler. The ThrowExceptionHandlingException will throw the exception for us")]
[SharePointPermission(SecurityAction.InheritanceDemand, ObjectModel = true)]
[SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
public virtual void HandleViewException(Exception exception, IErrorVisualizer errorVisualizer, int eventId)
{
try
{
ILogger logger = GetLogger(exception);
logger.LogToOperations(exception, eventId, EventSeverity.Error, null);
EnsureErrorVisualizer(errorVisualizer, exception);
errorVisualizer.ShowDefaultErrorMessage();
}
catch (ExceptionHandlingException)
{
throw;
}
catch (Exception handlingException)
{
this.ThrowExceptionHandlingException(handlingException, exception);
}
}
Bonne nouvelle c’est ici que votre travail s’arrête, tout le reste (comprenez toute la plomberie nécessaire à ce que le code ci-dessus fonctionne a déjà été codée par l’équipe de Patterns & Practices qui ont fait tout le travail pour vous (et moi).
En effet, ils ont développé une manière uniforme et très pratique de gérer une bonne fois pour toutes les erreurs dans SharePoint.
L’exemple que vous voyez ci-dessus est appliqué à une utilisation dans des Webpart avec un contrôle “ErrorVisualiser” qui va nettoyer la vue et n’afficher que le message d’erreur “propre” destiné à l’utilisateur (ici ShowDefaultErrorMessage). Le logToOperations s’occupera des informations techniques et les placera dans les logs ou dans l’event viewer en fonction de l’EventSeverity.
Si par contre vous souhaitez utiliser le travail de P&P dans un event receiver ou autre, vous pourriez faire :
// ...
try
{
// ... a SharePoint operation ...
}
catch (SPException ex)
{
//Get an instance of ILogger
ILogger logger = SharePointServiceLocator.Current.GetInstance<ILogger>();
//Define your exception properties
string msg = "An error occurred while trying to retrieve the customer list";
string category = @"SalesTool/Data";
int eventID = 0;
EventLogEntryType severity = EventLogEntryType.Error;
//Log the exception
logger.LogToOperations(ex, msg, eventID, severity, category);
}
Vous pourrez retrouver toute cette gestion d’erreur dans le dernier drop à cet url : http://codeplex.com/spg
PS : Je vous parlerai de ce SharePointServiceLocator dans les posts à venir.
<Philippe/>