SharePoint 最受欢迎内容 Web 部件





5.00/5 (2投票s)
创建一个显示最受欢迎内容的 Web 部件

引言
我目前在一家有线电视公司工作,该公司希望设置一个公共 SharePoint 网站,允许客户查看有助于他们设置家庭系统(网络、Xbox 等)的内容。一位同事提出的一个好主意是在默认页面上显示一个 Web 部件,以显示我们客户查看次数最多的内容。这将使我们的客户能够轻松访问其他客户最常见问题的解决方案。
背景
如果您对 SharePoint 足够了解,那么您可能已经知道使用情况报告。您需要做的第一件事是为您的网站集设置使用情况报告。一篇很好的教程可以帮助您完成此操作,请在此处查看:此处。如果您使用过它们,那么您知道它们在功能方面非常有限。因此,我深入研究(反射 SharePoint DLL)发现,默认每晚运行的使用情况分析作业会填充位于您的 SharedServicesDB
中的 ANL<suffix> 表。
- ANLResource - 一行代表 SharePoint 中的任何给定内容项,无论是 PDF、ASPX 页面等。此表中没有重复项。
- ANLHit - 一行代表用户对特定资源的单个点击。
ANLResource 表非常有用,因为它包含两个非常重要的字段:WebGuid
和 SiteGuid
。这将允许我们将查询范围限定为用户当前正在浏览的网站。另外,另一个重要字段是 ANLHit.UserId
,因为它允许我们将查询范围限定为特定用户。然后,我们将只能显示用户点击过的内容。太棒了!!是吧?
请记住,使用情况报告每晚运行一次。因此,请注意,即使您点击链接一千次,在作业运行之前,Web 部件中也不会反映出任何更改。
安装
希望大多数人都知道如何安装 Web 部件,但如果您需要帮助,请按照以下步骤操作
- 将 Mullivan.Shared.dll 和 Mullivan.SharePoint.WebParts.dll 添加到 GAC。
- 在 SharePoint Web Config 中注册 Mullivan.SharePoint.WebParts.dll。
转到 C:\inetpub\wwwroot\wss\VirtualDirectories\<Port Number>\web.config。在
SafeControls
节点之间添加以下内容<SafeControl Assembly="Mullivan.SharePoint.WebParts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c37a514ec27d3057" Namespace="Mullivan.SharePoint.WebParts" TypeName="*" Safe="True" />
- 转到网站设置 -> Web 部件 -> 点击菜单上的“新建”。
滚动到底部并勾选 Mullivan.SharePoint.WebParts.MostViewedWebPart,然后滚动回顶部并点击“填充库”。
完成!您应该会在您的 Web 部件集合中看到它。
配置

上面显示的是此 Web 部件的属性窗格。我将在下面解释每个字段
- 仅用户 - 如果选中,则将查询范围限定为仅显示当前用户点击过的内容。
- 范围 - 将查询设置为从所有网站中提取内容,或仅从 Web 部件所在的网站中提取内容。
- 返回计数 - 查询应返回的最大项目数。
- 跨度 - 查询应从现在开始追溯的天数,并据此确定点击次数。
- 扩展名 - 文件扩展名的逗号分隔列表,将查询限制为特定文件类型。因此,如果您只想显示 PDF 和 Word 文档,那么您可以使用“pdf, doc, docx”。
代码
您准备好了吗??我有点恶作剧,并且花很多时间反射 DLL。我不喜欢花时间阅读书本上的文字,而是喜欢直接上手弄清楚一切是如何工作的。:P
所以,我仔细思考(在我看来,就是 5 分钟)如何与共享服务数据库建立连接。好吧,使用反射,我发现 SPUsageSite.aspx 上的 TopPages
控件使用 PortalContext
对象上的一个名为“AnalyticsSqlSession
”的内部属性。所以,我决定使用反射将其提取出来并获得访问权限。您可能会觉得这很疯狂,但它避免了我存储到该数据库的某种连接字符串。
private SqlDataReader GetSqlReader(SqlCommand cmd)
{
PortalContext pContext = PortalApplication.GetContext();
Type tContext = pContext.GetType();
PropertyInfo pSqlSession = tContext.GetProperty("AnalyticsSqlSession",
BindingFlags.NonPublic | BindingFlags.Instance);
object sqlSession = pSqlSession.GetValue(pContext, null);
Type tSqlSession = sqlSession.GetType();
MethodInfo mExecute = tSqlSession.GetMethod("ExecuteReader",
new Type[1] { typeof(SqlCommand) });
return mExecute.Invoke(sqlSession, new object[1] { cmd }) as SqlDataReader;
}
所以现在,我们将要生成查询字符串。我们要确保可能频繁更改的值作为 SQL 参数构建到查询中。所以,我们将使用开始日期、用户名和网站 GUID 作为 SQL 参数。TOP 和文件扩展名不会改变,因此查询应该被缓存并在 SQL Server 数据库中重用。
private string GetQueryText(bool isUserQuery, int returnCount,
MostViewedScope scope, string[] extensions)
{
string query = @"
SELECT TOP {0} ANLResource.ResourceId AS ResourceId,
ANLResource.DocName AS DocName,
ANLResource.FullUrl AS FullUrl,
ANLResource.WebGuid AS WebGuid,
COUNT_BIG(*) AS HitCount
FROM ANLHit
INNER JOIN ANLResource
ON ANLHit.ResourceId = ANLResource.ResourceId";
if (isUserQuery)
{
query += @"
INNER JOIN ANLUser
ON ANLHit.UserId = ANLUser.UserId";
}
query += @"
INNER JOIN ANLDay
ON ANLHit.DayId = ANLDay.DayId
WHERE
ANLDay.FullDate > @StartDate";
if (scope == MostViewedScope.CurrentSite)
query += "AND ANLResource.WebGuid = @WebGuid";
else
query += "AND ANLResource.SiteGuid = @SiteGuid";
if (isUserQuery)
{
query += @"AND ANLUser.UserName = @UserName";
}
if (extensions != null && extensions.Length > 0)
{
query += @"AND (";
for (int i = 0; i < extensions.Length; i++)
{
if (i != 0)
query += " OR ";
query += string.Format("(CHARINDEX('.{0}', ANLResource.DocName) > 0)",
extensions[i].Trim());
}
query += ") ";
}
query += @"
GROUP BY ANLResource.ResourceId,
ANLResource.DocName,
ANLResource.FullUrl,
ANLResource.WebGuid
ORDER BY HitCount DESC";
return string.Format(query, returnCount); ;
}
我知道这看起来有点混乱,但这才是需要做到的一步。下面是生成查询后的示例
SELECT TOP 10 ANLResource.ResourceId AS ResourceId,
ANLResource.DocName AS DocName,
ANLResource.FullUrl AS FullUrl,
ANLResource.WebGuid AS WebGuid,
COUNT_BIG(*) AS HitCount
FROM ANLHit
INNER JOIN ANLResource
ON ANLHit.ResourceId = ANLResource.ResourceId
INNER JOIN ANLUser
ON ANLHit.UserId = ANLUser.UserId
INNER JOIN ANLDay
ON ANLHit.DayId = ANLDay.DayId
WHERE
ANLDay.FullDate > @StartDate
AND ANLResource.WebGuid = @WebGuid
AND ANLUser.UserName = @UserName
AND ((CHARINDEX('.aspx', ANLResource.DocName) > 0)
OR (CHARINDEX('.pdf', ANLResource.DocName) > 0)
OR (CHARINDEX('.docx', ANLResource.DocName) > 0)
OR (CHARINDEX('.doc', ANLResource.DocName) > 0))
GROUP BY ANLResource.ResourceId,
ANLResource.DocName,
ANLResource.FullUrl,
ANLResource.WebGuid
ORDER BY HitCount DESC
好的……现在,通过调用 GetReportData()
方法来执行我们的查询。
private DataTable GetReportData()
{
string currentUser = null;
if (this.IsUserQuery
&& this.Page.User != null
&& this.Page.User.Identity != null)
currentUser = this.Page.User.Identity.Name;
DataTable table = null;
using (SqlCommand command = new SqlCommand())
{
SqlDataReader reader = null;
command.CommandText = GetQueryText(!string.IsNullOrEmpty(currentUser),
this.ReturnCount, this.Scope, this.Extensions);
command.CommandType = CommandType.Text;
if (!string.IsNullOrEmpty(currentUser))
{
SqlParameter spUserName =
new SqlParameter("@UserName", SqlDbType.NVarChar, 50);
spUserName.Value = currentUser;
command.Parameters.Add(spUserName);
}
SqlParameter spScope = null;
if (this.Scope == MostViewedScope.CurrentSite)
{
spScope = new SqlParameter("@WebGuid", SqlDbType.UniqueIdentifier);
spScope.Value = SPControl.GetContextWeb(this.Context).ID;
}
else
{
spScope = new SqlParameter("@SiteGuid", SqlDbType.UniqueIdentifier);
spScope.Value = SPControl.GetContextSite(this.Context).ID;
}
command.Parameters.Add(spScope);
SqlParameter spStartDate = new SqlParameter("@StartDate", SqlDbType.DateTime);
spStartDate.Value = DateTime.Today.Subtract(
TimeSpan.FromDays(Convert.ToDouble(this.SpanDays)));
command.Parameters.Add(spStartDate);
table = new DataTable();
table.Locale = CultureInfo.InvariantCulture;
reader = GetSqlReader(command);
try
{
table.Load(reader);
return table;
}
finally
{
if (reader != null)
{
reader.Dispose();
}
}
}
}
好了,现在,让我们在部分中渲染我们的数据。
protected override void OnLoad(EventArgs e)
{
try
{
_data = GetReportData();
}
catch (Exception ex)
{
_errorMessage = ex.ToString();
}
base.OnLoad(e);
}
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
if (!string.IsNullOrEmpty(_errorMessage))
{
writer.Write(HttpUtility.HtmlEncode(_errorMessage).Replace("\n", "<br/>"));
return;
}
StringBuilder sb = new StringBuilder();
#region "Html"
string htmlItem = @"
<tr>
<td class=""ms-propertysheet"" style=""padding-left:1px"">
<table cellspacing=""0"" cellpadding=""0"" width=""100%"" border=""0"">
<tr>
<td valign=""top"" nowrap=""nowrap""
class=""ms-descriptiontext""
width=8px style=""padding-top:5px;"">
<IMG src=""{2}"" width=16px height=16px alt='' >
</td>
<td valign=top class=""ms-descriptiontext""
style=""padding-top:7px;padding-left: 3px;"">
<a href=""{0}"" title=""{3}"">{1}</a>
</td>
</tr>
</table>
</td>
</tr>
";
string htmlTable = @"
<table width=100% cellpadding=0 cellspacing=0 border=0>
<tr>
<td nowrap class=""ms-linksectionheader""
style=""padding: 4px;"" width=""100%"">
<H3 class=""ms-standardheader"">
{0}
</H3>
</td>
</tr>
<tr>
<td height=""1px"">
<IMG SRC=""/_layouts/images/blank.gif"" width=1 height=1 alt="""">
</td>
</tr>
<tr>
<td width=""100%"" style=""padding: 0px 4px 4px 4px;"" colspan=""2"">
<table cellpadding=""0"" cellspacing=""0"" border=""0"">
<tr>
<td valign=""top"" class=""ms-descriptiontext""
style=""padding-top:5px"">
</td>
</tr>
{1}
</table>
</td>
</tr>
<tr>
<td height=""15px"">
<IMG SRC=""/_layouts/images/blank.gif""
width=1 height=15 alt="""">
</td>
</tr>
</table>
";
#endregion "Html"
//DataRow dr1 = _data.NewRow();
//dr1["DocName"] = "Pages/mypage1.aspx";
//dr1["FullUrl"] = "http://sullyserver2008/sites/mullivan/Pages/mypage1.aspx";
//dr1["HitCount"] = 2345L;
//dr1["WebGuid"] = new Guid("{db7e891b-18c4-4c00-8c99-160ecbcee67f}");
//_data.Rows.Add(dr1);
foreach (DataRow dr in _data.Rows)
{
string docName = dr["DocName"].ToString();
string fullUrl = dr["FullUrl"].ToString();
long hitCount = (long)dr["HitCount"];
string desc = string.Format("This file has been requested {0} " +
"times in the past {1} days.", hitCount, this.SpanDays);
Guid webGuid = (Guid)dr["WebGuid"];
string extUrl = "/_layouts/images/icgen.gif";
try
{
SPSite site = SPControl.GetContextSite(this.Context);
using (SPWeb web = site.AllWebs[webGuid])
{
string relativeUrl = fullUrl;
int idx = relativeUrl.IndexOf(web.ServerRelativeUrl,
StringComparison.InvariantCultureIgnoreCase);
if (idx > -1)
relativeUrl =
relativeUrl.Substring(idx, relativeUrl.Length - idx);
SPListItem spListItem = web.GetListItem(relativeUrl);
docName = spListItem.Title;
object icon = spListItem["DocIcon"];
if (icon != null)
extUrl = string.Format("/_layouts/images/ic{0}.gif",
Convert.ToString(icon));
}
}
catch
{
//Item doesn't exist
continue;
}
sb.Append(string.Format(htmlItem, fullUrl, docName, extUrl, desc));
}
writer.Write(string.Format(htmlTable, this.Title, sb.ToString()));
}
好的……基本上就是这样。此 Web 部件附带一个用于配置的编辑器,但我不会深入介绍。有很多博客可以帮助您了解它是如何工作的。
关注点
该项目还包括另外两个 Web 部件。天气 Web 部件来自 Mossman 的博客,您可以在此处找到:此处。另一个是我昨天博客中介绍的 Web 部件。它是一个非常棒的 Web 部件,可以显示导航栏,请在此处查看:此处。
结论
请告诉我您的想法。如果您有任何想法,我会仔细考虑并看看是否可以提供更新。我希望您发现这个 Web 部件很有用。
历史
- 2009年1月15日:初始帖子
- 2009年3月19日:更新了源代码
- 2009 年 3 月 24 日:已更新源代码