对于普通ASP.NET站点来说,要对该站点的URL进行访问授权控制,可以通过创建一个HttpModule来监控每个Request,如果Request Url为受控URL(即要访问该URL地址需要经过一种特定验证授权)时,则跳转到验证授权页面进行身份验证授权,完成后再返回即可正常访问Request Url。我想,这个实现并不难,网络上也可以找到诸多用HttpHandler和HttpModule来做这块处理的示例。
那么,SharePoint Portal Server 2003中,访问受控URL和普通ASP.NET站点有何不同吗?带着这个疑问,我们可以一开始也用HttpModule来做尝试。假设此时我们访问一个SharePoint 文档库的某个内容,其URL地址应该是 http://localhost/DocLib1/Test.doc,而在我们的受控URL数据库记录中发现 http://localhost/DocLib1 为受控URL,那么要访问 http://localhost/DocLib1/Test.doc,就不能让未经过验证授权的用户直接访问,而应该跳转到我们验证授权页面进行身份验证授权后方能访问。结果很让人遗憾,我们的访问畅通无阻。于是做了调试跟踪,发现在 HttpModule 中 Request.Url 不是我们想要的 http://localhost/DocLib1/Test.doc,而是一个对我们未知的 http://localhost/_vti_bin/owssvr.dll,正因为这个地址不是受控URL,所以HttpModule不做处理直接让用户继续访问了。
姑且不论owssvr.dll到底为何物,现在要解决的关键问题有两个:
- 为什么我们点的是 http://localhost/DocLib1/Test.doc 这个请求,而到HttpModule时,却变成了http://localhost/_vti_bin/owssvr.dll,谁干的好事?
- 我们能否在这家伙做这件事之前把执行权抢过来做我们自己的处理?
对于第一个问题:谁动了我的URL?在了解这个问题答案之前可以先参考以下文章:
看完上面两篇文章,或许你已经清晰知道是谁动了我们的URL。是stsfltr.dll(可以在IIS管理器-->Web站点属性窗口-->ISAPI 筛选器找到)这个ISAPI Filter在HttpModule之前抢先做了处理。
第一个问题找到了,第二个问题:怎么解决这个问题,把执行权抢过来?只能自己再写个 ISAPI Filter,并把该 ISAPI Filter排在stsfltr.dll之前了。于是,我们创建了一个C++ Win32 项目,定义了下面这个一个ISAPI Filter class:
class CRedirectorFilter : public CHttpFilter
{
public:
CRedirectorFilter();
~CRedirectorFilter();
BOOL IsSecureDocument(LPCTSTR docUrl, LPCTSTR agent, LPCTSTR cookie);
BOOL GetCookie(CHttpFilterContext* pCtxt,CString strName, CString & strValue);
BOOL GetAgent(CHttpFilterContext* pCtxt,CString strName, CString & strValue);
// Overrides
// ClassWizard generated virtual function overrides
// NOTE - the ClassWizard will add and remove member functions here.
// DO NOT EDIT what you see in these blocks of generated code !
//{{AFX_VIRTUAL(CRedirectorFilter)
public:
virtual BOOL GetFilterVersion(PHTTP_FILTER_VERSION pVer);
virtual DWORD OnPreprocHeaders(CHttpFilterContext* pCtxt, PHTTP_FILTER_PREPROC_HEADERS pHeaderInfo);
virtual DWORD OnEndOfNetSession(CHttpFilterContext* pCtxt);
//}}AFX_VIRTUAL
//{{AFX_MSG(CRedirectorFilter)
//}}AFX_MSG
};
通过OnPreprocHeaders来处理判断我们的受控 URL 逻辑,如果是受控URL且尚未经过验证,则绕过stsfltr.dll直接跳转到验证授权页面进行身份验证授权;如果不是受控URL或已经经过验证,则直接交给IIS继续处理。
对于MOSS 2007,没有了stsfiltr.dll的困扰,实现类似方案就相对方便许多了,有兴趣者可以利用HttpHandler或HttpModule进行类似实现。
BTW:对于对受控URL的判断逻辑,如果感觉C++实现比较吃力费时,可以考虑用.NET Assembly来写这块逻辑,然后利用C++调用托管DLL来实现这块逻辑。具体可以参考KB:
在MOSS Document Library中的Page,有Basic Page和WebPart Page两种,前者更多的体现WCM特性,后者则更侧重体现Portal特性。
不管是Basic Page还是WebPart Page,都是直接和MOSS本身结合非常密切,都直接采用Site中的MasterPage。如果我们想把一个普通的ASP.NET Page也加到MOSS站点里运行,比如也放在Document Library里,或者放在Site某个文件夹下面,然后通过MOSS站点URL直接访问运行该ASP.NET Page,就需要做一些工作了。这里就准备介绍这么一个Tip来完成这件事。我想,这种应用方式主要是针对一些需要特殊页面效果,或者追求短平快的小量自定义的场景的,也不失为各类复杂应用场景提供一个可以方便Work Around的通道。正常应用场景中一般比较少用到。
1、首先从最简单的Hello World Page开始。点击此处查看 HelloWorld.aspx 源代码。
<%@ Page Language="C#" %>
<html>
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Hello World Page</title>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
Response.Write("Hello World!");
}
</script>
</head>
<body>
<form id="form1" runat="server">
</form>
</body>
</html>
2、将HelloWorld.aspx上传到Documents文档库中。此时,你直接点击文档库中的HelloWorld.aspx进行浏览,其URL类似http://localhost/Documents/HelloWorld.aspx,将出现“An error occurred during the processing of /Documents/helloworld.aspx. Code blocks are not allowed in this file.”的错误异常。通过这个异常信息也就知道,其实我们要做的就是允许在Page Server Code能被顺利编译执行。
3、修改MOSS Site的web.config,比如C:\Inetpub\wwwroot\wss\VirtualDirectories\80\web.config。找到PageParserPaths这么一个节点,修改如下:
<PageParserPaths>
<PageParserPath VirtualPath="/Documents/helloworld.aspx" CompilationMode="Always" AllowServerSideScript="true" />
</PageParserPaths>
4、重新浏览http://localhost/Documents/helloworld.aspx,页面正常被编译执行。
至此已经验证普通的ASP.NET Page是可以顺利集成在MOSS中被执行的。可以看到CompilationMode="Always"就意味着这个操作是属于“不得已而为之”的了,但这类不得已的场景现实应用中毕竟还是存在的。下面发散思维,扩展下思路。
5、我们把SharePoint的一些接口也写在这个普通ASP.NET Page里,从这个ASP.NET Page去操作SharePoint的库表等相关信息。点击此处查看 HelloSharePoint.aspx源代码。
<%@ Page Language="C#" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Import Namespace="Microsoft.SharePoint.WebControls" %>
<html>
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Hello SharePoint</title>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
Response.Write("Hello SharePoint!<br/>");
}
protected void Button1_Click(object sender, EventArgs e)
{
try
{
string url = http://localhost;
string listName = "Documents";
SPWeb web = new SPSite(url).OpenWeb();
SPListItemCollection listItems = web.Lists[listName].Items;
foreach (SPListItem item in listItems)
{
Response.Write(item["Name"].ToString() + "<br/>");
}
}
catch (Exception ex)
{
Response.Write(ex.Message);
}
}
</script>
</head>
<body>
<form id="form1" runat="server">
<asp:Button runat="server" Text="GetItems" ID="Button1" OnClick="Button1_Click" />
</form>
</body>
</html>
6、将 HelloSharePoint.aspx上传到Documents文档库中。修改Web.config的PageParserPaths节点如下。表示Documents文档下所有页面都允许服务器端脚本并进行编译运行。
<PageParserPaths>
<PageParserPath VirtualPath="/Documents/*" CompilationMode="Always" AllowServerSideScript="true" IncludeSubFolders="false" />
</PageParserPaths>
7、浏览http://localhost/Documents/HelloSharePoint.aspx,点击GetItems按钮,正常获取到Documents文档库下所有Item的Name列表。
至此说明在被嵌入的ASP.NET Page可以利用SharePoint API操作SharePoint各类库表数据。可以看到多了一个IncludeSubFolders="false"的配置,如果为true则表示包括子文件夹在内所有页面每次都要编译运行。通过这个例子说明,我们甚至可以把这个普通的ASP.NET Page页做得和所在Site的页面风格一致,这样就达到瞒天过海了,在该ASP.NET Page内的操作感觉就如同在同一个MOSS Site中操作一般。
8、再延伸下思路。可以尝试把一些复杂业务逻辑操作封装在Assembly中,然后再在ASP.NET Page里调用即可。有兴趣者自己可以尝试这块操作。主要在ASP.NET Page中加入类似声明:
<%@ Register Tagprefix="MyAssembly" Namespace="ClassLibrary1" Assembly="ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=671d8e00903f3760" %>
最后,我在说上述异常错误时说“我们要做的就是允许在Page Server Code能被顺利编译执行”,那么对于Basic Page,WebPart Page等MOSS自己的Page,是否也可以在内部增加Server Code,然后配置PageParserPath来运行这些Server Code呢?答案是肯定的。
希望你的项目设计中尽量少考虑这个特性,但如果必须用,希望本文对你有所帮助。
通过以下代码枚举列出所有的Exchange Server、StoreGroups和MailStore,并获取每个MailStore中Mailbox的数量。本段C#代码为http://www.ureader.com/message/513012.aspx一文中的VBNET代码改写而成,在Exchange 2003环境中测试通过。
通过这段代码,结合创建 Mailbox 的代码,可以实现获取Exchange环境的Server、StoreGroup、MailStore和Mailbox信息,或在指定Store(比如在所有Store中最少Mailbox的那个,或者人为指定目标Store)中创建Mailbox。
protected void Page_Load(object sender, EventArgs e)
{
DirectoryEntry RootDSE = new DirectoryEntry("LDAP://RootDSE");
string rootPath = "LDAP://" + RootDSE.Properties["configurationNamingContext"].Value.ToString();
DirectoryEntry configContainer = new DirectoryEntry(rootPath);
DirectorySearcher configSearcher = new DirectorySearcher(configContainer);
configSearcher.SearchRoot = configContainer;
configSearcher.Filter = "(objectCategory=msExchExchangeServer)";
// Enumerate all Exchange Servers
SearchResultCollection serverResults = configSearcher.FindAll();
foreach (SearchResult serverResult in serverResults)
{
Response.Write("<br/><br/><font color=\"red\">=== Exchange Server: " + serverResult.GetDirectoryEntry().Properties["cn"].Value.ToString() + " ===</font><br/><br/>");
SearchResultCollection storeGroups;
SearchResultCollection stores;
int mailboxCount;
// Enumerate all Store Groups
storeGroups = SearchContainer(serverResult.Properties["distinguishedName"][0].ToString(),
"(objectCategory=msExchStorageGroup)");
foreach (SearchResult storeGroup in storeGroups)
{
string storeGroupName = storeGroup.GetDirectoryEntry().Properties["cn"].Value.ToString();
Response.Write(storeGroupName + "<br/>");
mailboxCount = 0;
// Enumerate All Stores
stores = SearchContainer(storeGroup.Properties["distinguishedName"][0].ToString(),
"(objectCategory=msExchPrivateMDB)");
foreach (SearchResult store in stores)
{
Response.Write(" " + store.GetDirectoryEntry().Properties["cn"].Value.ToString() + "<br/>");
Response.Write(" Number of Mailboxes: " +
store.GetDirectoryEntry().Properties["homeMDBBL"].Count.ToString() + "<br/>");
mailboxCount += store.GetDirectoryEntry().Properties["homeMDBBL"].Count;
}
string reportMsg = String.Format("Total Number of Mailboxes in Storage Group({0}): {1}<br/><br/>", storeGroupName, mailboxCount);
Response.Write(reportMsg);
}
}
}
private SearchResultCollection SearchContainer(string srvPath, string strFilter)
{
string ldapPath = "LDAP://" + srvPath;
DirectoryEntry serverContainer = new DirectoryEntry(ldapPath);
DirectorySearcher serverSearcher = new DirectorySearcher(serverContainer);
serverSearcher.Filter = strFilter;
serverSearcher.SearchScope = SearchScope.Subtree;
serverSearcher.PropertiesToLoad.Add("cn");
serverSearcher.PropertiesToLoad.Add("distinguishedName");
serverSearcher.PropertiesToLoad.Add("homeMDBBL");
SearchResultCollection results = serverSearcher.FindAll();
return results;
}
关于Exchange的收藏夹几个必备站点: