I. Présentation du projet▲
Ce projet a pour but de permettre aux visiteurs authentifiés d'évaluer des documents. Cette évaluation permettra d'établir une classification et de mettre en avant les documents/vidéos etc.. les plus appréciés par les visiteurs dans un environnement SharePoint.
La moyenne obtenue pour chaque document est fiable à 100% dans la mesure où seuls les utilisateurs authentifiés peuvent voter et qu'un seul vote pour un document sera pris en compte. Un utilisateur pourra changer son vote s'il le souhaite mais seul son dernier vote compte. On a donc la certitude d'éviter qu'une personne vote 10 fois pour un document juste pour faire monter la classification.
Cependant, le système est assez simpliste mais vous pourrez le faire évoluer puisque les sources seront disponibles (voir section téléchagement).
Voici en quelques captures d'écran, un aperçu du projet.
Le projet met à disposition un nouveau modèle de liste (Document Rating)
Lorsque l'on a créé une instance de ce nouveau type, on obtient une liste dont les documents sont évaluables. La moyenne des votes est symbolisée par des étoiles
Le système de vote est basé sur un type de contenu contenant un custom field et un autre champ caché contenant l'historique des votes pour un document. Vous n'êtes donc pas obligé d'utiliser le modèle de liste Document Rating. Vous pouvez simplement associer le type de contenu Document Rating à n'importe quelle bibliothèque.
Lorsque ce type de contenu est associé à l'un de vos documents, des custom actions sont disponibles pour permettre au visiteur de voter.
Lorsque le visiteur clique sur Vote ou sur les étoiles, il est redirigé vers une page lui permettant d'évaluer le document
Un gestionnaire de liste pourra accéder à deux types de rapports. Un rapport de tous les votes liés à un seul élément
et un rapport reprenant tous les votes pour une bibliothèque entière.
Enfin, grâce à ce système qui stocke simplement une valeur (1,2,3,4 ou 5), on va pouvoir utiliser les vues standard de SharePoint pour établir des tris, des filtres etc...sur nos bibliothèques
II. Structure de notre projet▲
- Document Rating : Contient la structure de note modèle de liste
- Features : Contient la description de notre feature et les custom actions
- IMAGES : Contient toutes les images du projet
- LAYOUTS : Contient les pages applicatives, à savoir, celle qui permet de voter, le rapport pour un document et le rapport pour toute une librairie
- XML : Le document XML décrivant notre colonne personnelle (étoiles)
- HighLight.cs : Classe principale du custom field dérivant de SPFieldText
- ItemRating.cs, ItemReport.cs et ListRatingReport.cs : code-behind des pages de vote et Rapports
- RatingValues.cs : Contient une énumération des niveaux de votes possibles
- UserVote.cs : Représente un utilisateur et son vote. Les objets créés via cette classe sont ensuite sérialisés en XML et stockés dans un champ caché
- UserVoteXmlHandler.cs : Classe s'occuptant de la sérialisation/désérialisation des votes
Nous retrouvons donc dans notre projet, un custom field, des pages applicatives et des custom actions
III. Fonctionnement du système▲
- Un utilisateur authentifié peut voter
- Si un utilisateur vote plusieurs fois pour un même document, seul son dernier vote est pris en compte
- L'utilisateur peut voter par le biais d'une custom action dans l'ECB ou en cliquant sur notre custom field (soit sur les étoiles, soit sur un lien explicite)
- Un administrateur de liste peut visualiser le rapport des votes d'un document via une custom action
- Un administrateur de liste peut visualiser le rapport des votes d'une bibliothèque entière via une custom action
- Chaque historique de vote est contenu dans l'élément de liste auquel se rapporte un document, il n'y a donc pas d'historique centralisée des votes
- Tout document peut-être évalué, la seule condition est qu'il ait le type de contenu "Document Rating" qui est lui-même dérivé du type de contenu "Document"
III-A. Structure de notre fichier Elements.xml▲
Ce fichier sert à déployer notre type de contenu Document Rating et nos custom actions
<?xml version="1.0" encoding="utf-8"?>
<Elements
xmlns
=
"http://schemas.microsoft.com/sharepoint/"
>
<!--The fields below are deployed as site columns-->
<Field
Type
=
"Note"
DisplayName
=
"Vote History"
Hidden
=
"TRUE"
Required
=
"FALSE"
UnlimitedLengthInDocumentLibrary
=
"TRUE"
RichText
=
"FALSE"
Sortable
=
"FALSE"
Group
=
"Custom Columns"
ID
=
"{B09C56CD-E2A8-4cc9-940C-2B5DE0CFE3CB}"
SourceID
=
"http://schemas.microsoft.com/sharepoint/v3"
StaticName
=
"RateStorage"
Name
=
"RateStorage"
ColName
=
"ntext2"
RowOrdinal
=
"0"
/>
<Field
Type
=
"HighLight"
DisplayName
=
"Vote Results"
ShowInEditForm
=
"FALSE"
ShowInNewForm
=
"FALSE"
Required
=
"FALSE"
ID
=
"{3432A932-642A-4ba1-B6F2-884316712535}"
SourceID
=
"http://schemas.microsoft.com/sharepoint/v3"
StaticName
=
"RateValue"
Name
=
"RateValue"
ColName
=
"nvarchar11"
RowOrdinal
=
"0"
/>
<!--This site content type includes the fields defined above-->
<ContentType
ID
=
"0x0101001B940DAB6AD6487085FD25BA3A462A9F"
Name
=
"Document Rating"
Description
=
"Allows Users To Rate Documents"
Version
=
"0"
>
<FieldRefs>
<FieldRef
ID
=
"{3432A932-642A-4ba1-B6F2-884316712535}"
DisplayName
=
"Vote Results"
Name
=
"RateValue"
ShowInEditForm
=
"FALSE"
ShowInNewForm
=
"FALSE"
ShowInDisplayForm
=
"TRUE"
/>
<FieldRef
ID
=
"{B09C56CD-E2A8-4cc9-940C-2B5DE0CFE3CB}"
Name
=
"RateStorage"
Hidden
=
"TRUE"
/>
</FieldRefs>
</ContentType>
<!--
This custom action is shown in the ECB of list items belonging
to the current above content type
-->
<CustomAction
Id
=
"70FCCA5C-B293-4778-8DF1-737F3545E5E1"
Rights
=
"ViewListItems"
RegistrationType
=
"ContentType"
RegistrationId
=
"0x0101001B940DAB6AD6487085FD25BA3A462A9F"
Location
=
"EditControlBlock"
Sequence
=
"2000"
Title
=
"Vote"
>
<UrlAction
Url
=
"javascript:window.location
= '{SiteUrl}/_layouts/ItemRating.aspx?id={ItemId}&List={ListId}&Source=' + window.location"
/>
</CustomAction>
<CustomAction
Id
=
"69B43F46-778D-4e10-A851-28715D683741"
Rights
=
"ManageLists"
RegistrationType
=
"ContentType"
RegistrationId
=
"0x0101001B940DAB6AD6487085FD25BA3A462A9F"
Location
=
"EditControlBlock"
Sequence
=
"2001"
Title
=
"View Vote Report"
>
<UrlAction
Url
=
"javascript:window.location
= '{SiteUrl}/_layouts/ItemReport.aspx?id={ItemId}&List={ListId}&Source=' + window.location"
/>
</CustomAction>
<!--This custom action is located in the list settings-->
<CustomAction
Id
=
"6B4A151D-B545-452c-ADE1-9B8FEA355F85"
GroupId
=
"GeneralSettings"
Location
=
"Microsoft.SharePoint.ListEdit"
Sequence
=
"2001"
Title
=
"View List Votes Report"
Description
=
"View List Votes Report"
Rights
=
"ManageLists"
>
<UrlAction
Url
=
"javascript:window.location= '{SiteUrl}/_layouts/ListRatingReport.aspx?List={ListId}&Source=' + window.location"
/>
</CustomAction>
</Elements>
Ce qu'il faut retenir de ce code
- Nos colonnes sont déployées en hidden pour l'une et en affichage uniquement pour l'autre (ShowInEditForm/NewForm = false)
- Nous déployons un type de contenu basé sur ces deux colonnes
- Notre custom action "Vote" n'apparaît que si l'élément est de type de contenu "Document Rating" et que si le visiteur courant a les droits de lecture sur l'item
- Notre custom action "View Vote Report" n'apparaît que si l'élément est de type de contenu "Document Rating" et que si le visiteur courant a les droits de gérer la liste
- Notre custom action "View List Votes Report" n'apparaît que si le visiteur courant a les droits de gérer la liste
- Toutes nos custom actions pointent respectivement sur l'une des pages applicatives décrites dans la section précédente
III-B. Comportement de notre colonne affichant le résultat des votes▲
Cette colonne étant un custom field, il faut lui spécifier son comportement en terme d'affichage. C'est le fichier FLDTYPES_xxx.xml qui se charge de cela. Voici celui que j'ai mis en place pour la colonne
<!--
Author
*******
Stéphane Eyskens (http://blogs.ezos.com/blog/sey)
Purpose
********
This file is the XML used to define and render the custom field type "HighLight".
This custom field is used to rate documents and displays a certain number of stars according to
the rate average. In case no rate has been done so far, a link is displayed to the user in order
to give him/her the possibility to rate the document. This link is only presented if the document
belongs to the content type associated with the custom field.
-->
<FieldTypes>
<FieldType>
<!--Defining field's properties-->
<Field
Name
=
"TypeName"
>
HighLight</Field>
<Field
Name
=
"TypeDisplayName"
>
HighLight</Field>
<Field
Name
=
"TypeShortDescription"
>
HighLight</Field>
<Field
Name
=
"ParentType"
>
Text</Field>
<Field
Name
=
"FieldTypeClass"
>
HighLight.HighLight, HighLight, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a020282aed59311d</Field>
<Field
Name
=
"UserCreatable"
>
FALSE</Field>
<!--The part below renders the field in the raw mode-->
<RenderPattern
Name
=
"DisplayPattern"
>
<!--
the below switch statement evalutes the current value. This
value can be from null to 5.
-->
<Switch>
<Expr>
<Column />
</Expr>
<!--Displays 1 star corresponding to the level : poor-->
<Case
Value
=
"1"
>
<HTML>
<![CDATA[
<a style="padding-left:2px;padding-right:12px" href="
]]>
</HTML>
<HttpVDir/>
<HTML>
<![CDATA[
/_layouts/ItemRating.aspx?Id=
]]>
</HTML>
<Column
Name
=
"ID"
HTMLEncode
=
"TRUE"
/>
<HTML>
<![CDATA[
&List=
]]>
</HTML>
<ListProperty
Select
=
'Name'
/>
<HTML>
<![CDATA[
" OnClick="GoToLink(this);return false;">
]]>
</HTML>
<HTML>
<![CDATA[
<img border="0" title="Poor" src="/_layouts/images/documentrating/rating_1.gif"/></a>
]]>
</HTML>
</Case>
<!--Displays 2 stars corresponding to the level : low-->
<Case
Value
=
"2"
>
<HTML>
<![CDATA[
<a style="padding-left:2px;padding-right:12px" href="
]]>
</HTML>
<HttpVDir/>
<HTML>
<![CDATA[
/_layouts/ItemRating.aspx?Id=
]]>
</HTML>
<Column
Name
=
"ID"
HTMLEncode
=
"TRUE"
/>
<HTML>
<![CDATA[
&List=
]]>
</HTML>
<ListProperty
Select
=
'Name'
/>
<HTML>
<![CDATA[
" OnClick="GoToLink(this);return false;">
]]>
</HTML>
<HTML>
<![CDATA[
<img border="0" title="Low" src="/_layouts/images/documentrating/rating_2.gif"/></a>
]]>
</HTML>
</Case>
<!--Displays 3 stars corresponding to the level : good-->
<Case
Value
=
"3"
>
<HTML>
<![CDATA[
<a style="padding-left:2px;padding-right:12px" href="
]]>
</HTML>
<HttpVDir/>
<HTML>
<![CDATA[
/_layouts/ItemRating.aspx?Id=
]]>
</HTML>
<Column
Name
=
"ID"
HTMLEncode
=
"TRUE"
/>
<HTML>
<![CDATA[
&List=
]]>
</HTML>
<ListProperty
Select
=
'Name'
/>
<HTML>
<![CDATA[
" OnClick="GoToLink(this);return false;">
]]>
</HTML>
<HTML>
<![CDATA[
<img border="0" title="Good" src="/_layouts/images/documentrating/rating_3.gif"/></a>
]]>
</HTML>
</Case>
<!--Displays 4 stars corresponding to the level : very good-->
<Case
Value
=
"4"
>
<HTML>
<![CDATA[
<a style="padding-left:2px;padding-right:12px" href="
]]>
</HTML>
<HttpVDir/>
<HTML>
<![CDATA[
/_layouts/ItemRating.aspx?Id=
]]>
</HTML>
<Column
Name
=
"ID"
HTMLEncode
=
"TRUE"
/>
<HTML>
<![CDATA[
&List=
]]>
</HTML>
<ListProperty
Select
=
'Name'
/>
<HTML>
<![CDATA[
" OnClick="GoToLink(this);return false;">
]]>
</HTML>
<HTML>
<![CDATA[
<img border="0" title="Very Good" src="/_layouts/images/documentrating/rating_4.gif"/></a>
]]>
</HTML>
</Case>
<!--Displays 5 stars corresponding to the level : excellent-->
<Case
Value
=
"5"
>
<HTML>
<![CDATA[
<a style="padding-left:2px;padding-right:12px" href="
]]>
</HTML>
<HttpVDir/>
<HTML>
<![CDATA[
/_layouts/ItemRating.aspx?Id=
]]>
</HTML>
<Column
Name
=
"ID"
HTMLEncode
=
"TRUE"
/>
<HTML>
<![CDATA[
&List=
]]>
</HTML>
<ListProperty
Select
=
'Name'
/>
<HTML>
<![CDATA[
" OnClick="GoToLink(this);return false;">
]]>
</HTML>
<HTML>
<![CDATA[
<img border="0" title="Excellent" src="/_layouts/images/documentrating/rating_5.gif"/></a>
]]>
</HTML>
</Case>
<!--In case no vote has been done yet-->
<Default>
<IfEqual>
<!--If the value of the content type is document rating-->
<Expr1>
<Field
Name
=
"ContentType"
/>
</Expr1>
<Expr2>
Document Rating
</Expr2>
<Then>
<!--Then display the link redirecting users to the voting page-->
<HTML>
<![CDATA[
<a style="padding-left:2px;padding-right:12px" href="
]]>
</HTML>
<HttpVDir/>
<HTML>
<![CDATA[
/_layouts/ItemRating.aspx?Id=
]]>
</HTML>
<Column
Name
=
"ID"
HTMLEncode
=
"TRUE"
/>
<HTML>
<![CDATA[
&List=
]]>
</HTML>
<ListProperty
Select
=
'Name'
/>
<HTML>
<![CDATA[
" OnClick="GoToLink(this);return false;">
]]>
</HTML>
<HTML>
<![CDATA[
Vote!</a>
]]>
</HTML>
</Then>
</IfEqual>
</Default>
</Switch>
</RenderPattern>
</FieldType>
</FieldTypes>
Ce qu'il faut retenir de ce code
- un switch se fait pour voir si le champ contient 1,2,3,4 ou 5 correspondant respectivement aux évaluations poor,low,good,high,excellent. En fonction de cette valeur, on affiche 1,2,3,4 ou 5 étoiles sous forme de lien cliquable redirigeant le visiteurs vers la page de votes.
- Si la valeur est nulle, on passe dans la section Default où l'on vérifie si l'élément de liste a bien le type de contenu "Document Rating", auquel cas, on affiche un lien "Vote" car aucun vote n'a encore été réalisé.
III-C. Enregistrement d'un vote▲
Lorsque l'utilisateur arrive sur la page de vote, il choisit un niveau d'évaluation et clique sur Ok. Voici la portion de code permettant l'enregistrement du vote pour le document.
SPSecurity.
RunWithElevatedPrivileges
(
delegate
(
)
{
//When impersonating, it is required to instanciate a new SPSite
using
(
SPSite Site =
new
SPSite
(
SPContext.
Current.
Web.
Url))
{
using
(
SPWeb Web =
Site.
OpenWeb
(
))
{
//Make sure we can update the list.
bool
AllowUnsafeUpdates =
Web.
AllowUnsafeUpdates;
Web.
AllowUnsafeUpdates =
true
;
Guid ListGuid =
new
Guid
(
Request.
QueryString[
"List"
].
ToString
(
));
SPList TargetList =
Web.
Lists[
ListGuid];
uint
RateFieldValue =
Convert.
ToUInt32
(
RateValue.
SelectedItem.
Value);
string
CurrentLogin =
SPContext.
Current.
Web.
CurrentUser.
LoginName;
string
CurrentLoginName =
SPContext.
Current.
Web.
CurrentUser.
Name;
string
StorageField =
TargetList.
Fields.
GetFieldByInternalName
(
"RateStorage"
).
Title;
string
TargetField =
TargetList.
Fields.
GetFieldByInternalName
(
"RateValue"
).
Title;
SPListItem Itm =
TargetList.
GetItemById
(
Convert.
ToInt16
(
Request.
QueryString[
"Id"
]
));
//Making sure that the targetted item does belong to the right content type
//It also makes sure that there is no injection (as we're running with elevated privileges...)
if
(
Itm.
ContentType.
Name ==
ContentType.
Text)
{
UserVoteXmlHandler VoteHandler =
new
UserVoteXmlHandler
(
);
if
(
Itm[
StorageField]
!=
null
)
{
List<
UserVote>
CurrentVoteList =
VoteHandler.
UnserializeUserVotes
(
Itm[
StorageField].
ToString
(
));
Itm[
StorageField]
=
VoteHandler.
SerializeUserVote
(
CurrentVoteList,
CurrentLogin,
CurrentLoginName,
RateFieldValue);
Itm[
TargetField]
=
VoteHandler.
GetVoteAverage
(
CurrentVoteList);
}
else
{
Itm[
StorageField]
=
VoteHandler.
SerializeUserVote
(
new
UserVote
(
CurrentLogin,
CurrentLoginName,
RateFieldValue));
Itm[
TargetField]
=
RateFieldValue;
}
try
{
Itm.
Update
(
);
}
catch
(
SPException Ex)
{
ShowError
(
Ex.
Message);
}
}
Web.
AllowUnsafeUpdates=
AllowUnsafeUpdates;
}
}
}
);
Ce qu'il faut retenir de ce code
- Ce n'est pas montré par ce code mais une vérification préalable se fait pour savoir si l'utilisateur est bien authentifié
- On fait une impersonation en tant qu'application pool car toute personne authentifiée peut voter, le seul rôle requis est "Read" et non pas "Contribute".
- Si le type de contenu est bien conforme, on vérifie si un vote a déjà été émis (tout utilisateur confondu) ou non pour ce document. Si c'est le cas, on récupère l'historique actuelle et on transmet le vote courant sinon on sérialise directement la valeur du vote actuel
- Enfin, on vérifie que l'on obtient pas une exception sur la mise à jour. En effet, ceci pourrait subvenir si deux utilisateurs votent en même temps pour un même document. En fonction du nombre de votes déjà réalisés, le temps de désérialiser/resérialiser les votes et de les enregistrer, peut-être qu'une mise à jour de l'élément se produira...dans ce cas, une exception sera lancée par SharePoint car il détectera que l'élément courant a été modifié depuis qu'on l'a récupéré via un SPListItem et le vote ne sera pas enregisté, ce qui nous garantit une totale cohérence au niveau des votes.
Pour mieux comprendre le mécanisme de sérialisation, voici le code de la classe se chargeant de la sérialisation.
internal
class
UserVoteXmlHandler
{
MemoryStream MemStr =
null
;
XmlSerializer VotesSerializer =
new
XmlSerializer
(
typeof
(
List<
UserVote>
));
XmlTextWriter Writer =
null
;
string
LogArg =
null
;
//returns a string from a byte array
private
string
GetString
(
byte
[]
InputStream)
{
UTF8Encoding Encoding =
new
UTF8Encoding
(
);
return
Encoding.
GetString
(
InputStream).
Substring
(
1
);
}
//Predicate called to check whether the user has already voted
private
bool
UserVoteEntry
(
UserVote Arg)
{
if
(
Arg.
LoginName ==
LogArg)
return
true
;
else
return
false
;
}
//serialises all the votes (previous+new one)
public
string
SerializeUserVote
(
List<
UserVote>
CurrentVotes,
string
LoginName,
string
Name,
uint
UserVoteValue)
{
UserVote NewVote =
new
UserVote
(
LoginName,
Name,
UserVoteValue);
if
(
CurrentVotes ==
null
)
{
CurrentVotes =
new
List<
UserVote>(
);
CurrentVotes.
Add
(
NewVote);
}
else
{
LogArg =
LoginName;
UserVote CheckEntry =
CurrentVotes.
Find
(
UserVoteEntry);
if
(
CheckEntry ==
null
)
CurrentVotes.
Add
(
NewVote);
else
CheckEntry.
Vote =
UserVoteValue;
}
MemStr =
new
MemoryStream
(
);
Writer =
new
XmlTextWriter
(
MemStr,
Encoding.
UTF8);
VotesSerializer.
Serialize
(
Writer,
CurrentVotes);
MemStr =
(
MemoryStream)Writer.
BaseStream;
return
GetString
(
MemStr.
ToArray
(
));
}
//called when registering only the current vote.
public
string
SerializeUserVote
(
UserVote CurrentVote)
{
return
SerializeUserVote
(
null
,
CurrentVote.
LoginName,
CurrentVote.
FullName,
CurrentVote.
Vote);
}
//Returns the vote average for a given document
public
int
GetVoteAverage
(
List<
UserVote>
Votes)
{
double
Total =
0
;
for
(
int
i =
0
;
i <
Votes.
Count;
i++
)
{
Total +=
Votes[
i].
Vote;
}
return
(
int
)Math.
Round
((
Double)Total /
Votes.
Count);
}
//returns all the votes until now
public
List<
UserVote>
UnserializeUserVotes
(
string
VoteData)
{
XmlTextReader Reader =
new
XmlTextReader
(
new
StringReader
(
VoteData));
List<
UserVote>
UnSerialized =
(
List<
UserVote>
)VotesSerializer.
Deserialize
(
Reader);
return
UnSerialized;
}
}
Et voici la fameuse classe que l'on sérialise et qui symbolise un vote
[XmlRootAttribute(ElementName =
"UserVote"
, IsNullable = false)]
public
class
UserVote
{
public
UserVote
(
) {
}
public
UserVote
(
string
UserArg,
string
NameArg,
uint
VoteArg)
{
LoginName =
UserArg;
FullName =
NameArg;
Vote =
VoteArg;
}
string
_LoginName;
public
string
LoginName
{
get
{
return
_LoginName;
}
set
{
_LoginName =
value
;
}
}
string
_FullName;
public
string
FullName
{
get
{
return
_FullName;
}
set
{
_FullName =
value
;
}
}
uint
_Vote;
public
uint
Vote
{
get
{
return
_Vote;
}
set
{
_Vote =
value
;
}
}
}
III-D. Rapport des votes de toute une bibliothèque▲
Le rapport des votes fonctionne selon le même principe, voici une version courte du code générant ce rapport
SPQuery VotingQuery =
new
SPQuery
(
);
VotingQuery.
Query =
"<Where><Eq><FieldRef Name=
\"
ContentType
\"
/><Value Type=
\"
Text
\"
>"
+
ContentType.
Text+
"</Value></Eq></Where>"
;
VotingQuery.
ViewFields =
"<FieldRef Name='RateValue' /><FieldRef Name='RateStorage' /><FieldRef Name='Title' />"
;
VotingQuery.
ViewAttributes =
"Scope='Recursive'"
;
SPListItemCollection Results =
TargetList.
GetItems
(
VotingQuery);
if
(
Results.
Count >
0
)
{
UserVoteXmlHandler VoteHandler =
new
UserVoteXmlHandler
(
);
List<
UserVote>
Votes =
null
;
string
DocName =
null
;
string
DocUrl =
null
;
foreach
(
SPListItem Itm in
Results)
{
DocName =
Itm[
"Name"
].
ToString
(
);
TreeNode DocNode =
new
TreeNode
(
DocName);
DocNode.
Value =
TargetList.
RootFolder.
Url +
Itm.
Url;
DocUrl =
SPContext.
Current.
Web.
Url +
"/"
+
Itm.
Url;
DocNode.
NavigateUrl =
DocNode.
ImageUrl =
"/_layouts/images/doclink.gif"
;
VoteData.
Nodes.
Add
(
DocNode);
if
(
Itm[
StorageField]!=
null
)
{
Votes =
VoteHandler.
UnserializeUserVotes
(
Itm[
StorageField].
ToString
(
));
for
(
int
i =
0
;
i <
Votes.
Count;
i++
)
{
TreeNode DocChild1 =
new
TreeNode
(
string
.
Format
(
"({0})-{1}"
,
Votes[
i].
LoginName,
Votes[
i].
FullName));
DocChild1.
ImageUrl =
"/_layouts/images/PERUSR.GIF"
;
DocChild1.
NavigateUrl =
String.
Format
(
"/_layouts/userdisp.aspx?ID={0}"
,
SPContext.
Current.
Web.
AllUsers[
Votes[
i].
LoginName].
ID);
TreeNode DocChild2 =
new
TreeNode
(
Enum.
GetName
(
typeof
(
RatingValues.
VotingValues),
Votes[
i].
Vote));
DocChild2.
ImageUrl =
"/_layouts/images/RECT.GIF"
;
DocChild2.
SelectAction =
TreeNodeSelectAction.
None;
DocChild1.
ChildNodes.
Add
(
DocChild2);
VoteData.
Nodes[
NodeLine].
ChildNodes.
Add
(
DocChild1);
}
}
NodeLine++;
}
}
Ce qu'il faut retenir de ce code
- On oublie pas de spécifier le scope au niveau de la requête CAML afin d'inclure TOUS les documents de la bibliothèque, tous répertoires confondus
- Pour chaque document, on désérialise l'historique et on récupère une liste de UserVote. On parcours les votes et on les ajoute au fur et à mesure à notre treeview
- Pour chaque noeud, on met un lien vers le document et/ou vers l'utilisateur
IV. Téléchargement▲
Le code source complet du projet ainsi que le fichier de solution sont disponibles sur codeplex à cette adresse