我们不建议使用 OD4B 网站中的内容类型和网站栏,以避免需要的字段或其他元素出现问题(这将导致正常使用 OD4B 网站时出现问题)。
-
将 OD4B 网站看做个人的非结构化数据和文档。 团队网站和协作网站则适用于公司数据和文档,当然你可以在其中使用任何你想使用的信息管理策略和元数据。
总之,Office 365 一定支持自定义,并且你可以在 OD4B 网站继续使用它们。 我们只想真正确保你能从操作和维护的角度考虑自定义的短期和长期影响。 这并非只针对 SharePoint,而是对于在任何平台构建的 IT 解决方案的经验法则。
此处是使用上述准则进行自定义的 OD4B 网站示例。 其中,结合使用 Office 365 主题、网站主题和所谓的 JavaScript 嵌入模式而实现了最终的结果。
应用 OneDrive for Business 网站自定义所面对的挑战
让我们首先定义所面临的挑战有哪些以及我们要尝试在这里解决的内容。 从技术上讲,每个OneDrive for Business网站当前使用的体系结构与 SharePoint 2007 或 2010 版本中使用的个人或我的网站相同。 这意味着从技术上讲,每个 OneDrive for Business 网站是其自身的网站集,且不会有任何集中的位置用于应用品牌或任何其他自定义。
将所需配置应用于 OneDrive for Business 网站(包括我的网站或个人网站)的经典解决方案基于服务器场级别的功能关联。 这意味着你将服务器场解决方案部署到 SharePoint 服务器场并使用功能框架来关联每次创建“我的网站”时被激活的自定义功能,然后该功能负责应用需要的自定义。 这种类似的方法无法在 Office 365 中使用,因为它要求部署服务器场解决方案,而这对于 Office 365 网站是不可能的。 因此,我们需要查看多种可选方案,将需要的更改应用到该网站。
在 Office 365 中,没有引发集中的事件,这种事件在创建 OD4B 网站时,我们可将自定义代码附加于其上。 这意味着,我们需要考虑另外的解决方案,这对应用模型方法十分常见。 不要困在旧模型上,思考如何使用新的 API 和技术实现相同的最终结果。 从纯需求角度来看,其实只要自定义应用到网站了,方式并不重要,因为业务需求不是使用功能关联,而是使用任何受支持的技术机制来应用需要的自定义。
应用自定义的不同方法
实际上我们确实有四种不同的机制,以将集中式自定义应用到 Office 365 中的 OD4B 网站。 你还可考虑将手动选项作为第五种机制,但是在具有成百上千个 OD4B 网站的情况下,使用手动选项并不是一个实际的方法。 下面是我们提供的不同选项。
-
Office 365 套件级别的设置(Office 365 主题和其他设置)。
-
具有用户上下文的隐藏应用部件
-
预先创建和应用配置
-
基于用户配置文件更新的远程计时器作业
每个选项都有各自的优点和缺点,并且正确的选择取决于具体的业务要求。 某些设置还可从 Office 365 套件级别应用,但通常你将寻找更多的细节,因此需要实际的自定义。 显然,这取决于确切的要求和对其长短期影响的业务案例分析。
Office 365 套件级别设置
如同你所知道的,Office 365 不仅仅是 SharePoint。 你可以找到更多其他服务,甚至包括不基于 SharePoint 体系结构的服务,如 Delve、Yammer 和许多即将推出的服务。 这意味着,企业品牌和配置并不只是关于控制 SharePoint 网站拥有的内容,而是我们应该考虑最终用户的整体体验和如何在不同的服务间提供一致的配置。
这些企业需求的典型示例是品牌打造,对此我们已推出了 Office 365 主题,可用于控制品牌打造的一些级别。 我们还有其他即将推出的功能,将有助于从网站集设置外的集中位置控制你的网站管理和其他设置,如即将推出的 Office 365 合规中心,当前在
Office 365 roadmap(Office 365 路线图)
中列出。
下图显示了目前有关 Office 365 主题的不同设置,以后将会应用到所有 Office 365 的服务中。
默认情况下,因为 Office 365 主题设置用于控制 OD4B 网站套件栏,你很可能要将此选项与其他选项配合使用来确保你在 OD4B 网站中至少可以提供正确的品牌要素。 请注意,当更改 Office 365 管理工具中的 Office 365 主题等设置时,确实需要很长时间将设置应用到 OD4B 网站,因此请耐心等待。
具有用户上下文的隐藏应用部件
这是一种方法,可以将集中登陆页面用作启动所需自定义流程的位置。 这意味着,你将必须拥有一个集中的位置,如公司内部网首页,用户在打开其浏览器时始终需要登录。 这是中大型企业非常典型的流程,将使用 AD 中的组策略设置控制公司登陆页。 这将确保最终用户不能重写公司已加入域的浏览器的默认欢迎页。
当用户访问内部网时,我们将隐藏网页中的应用程序部件,这将开始自定义流程。 它实际上还可以负责整个 OD4B 网站的创建,因为通常情况下,用户需要在网站创建过程启动前访问 OD4B 网站一次。 隐藏的应用部件实际上托管来自 Azure 中托管的提供商托管应用的页面。 然后,该页面负责启动自定义过程。
让我们深入了解一下此方法的逻辑设计。
-
将隐藏的应用程序部件放到集中的网站,最终用户将在该位置登录。 通常情况下,这是公司内部网的首页。
-
应用部件从提供商托管的应用托管页面,在服务器端代码中,我们通过将所需的元数据添加到 azure 存储队列来启动自定义过程。 这意味着此页面将仅接收自定义请求,但实际上并不会应用任何更改以保持正常的处理时间。
-
这是实际的 Azure 存储队列,它将收到排队以进行处理的消息。 这样,我们可以异步处理自定义控制流程,所以其实最终用户将停留在内部网首页的时间长短并不重要。 如果自定义过程是同步的,那么在页面执行完成前,我们需要依赖最终用户,以在内部网首页中保持浏览器打开的状态。 这不一定是最适合的方法,会面临
原始 OD4B 自定义过程
的问题(我之前的博客文章对此有所涉及)。
-
对 WebJob 进行挂钩以遵循存储队列,当新项目被放置到存储队列时对其进行调用。 此 WebJob 将收到来自排队消息的必需参数和元数据,从而访问正确的网站集。 WebJob 正使用仅限应用令牌,并且已被授予处理租户级别网站集所需的权限。
-
实际的自定义是逐个应用到这些访问内部网首页以启动自定义流程的用户的网站的。
这肯定是保证 OD4B 网站中配置正确的最可靠流程。 可以轻松将自定义版本控制逻辑添加到流程,该流程还在下次有所需更新和用户访问内部网首页时将任何所需更新应用到 OD4B 网站。 但是,此选项确实要求你具有最终用户可以登录的集中的位置。
如果你熟悉场解决方案的经典 SharePoint 开发模型,这与一次性执行计时器作业的过程非常相似。
预先创建和应用配置
此方法依赖于用户访问 OD4B 网站前对该网站的预创建。 可通过使用
相对较新的 API
(#相对较新的-api) 来实现,这为我们提供了使用 CSOM 或 REST 在批处理过程中为特定用户创建 OD4B 网站的方法。 可以使用 PowerShell 脚本或通过编写调用远程 API 的实际代码来启动所需的代码。
-
管理员使用远程创建 API 来为用户创建 OD4B 网站,并将所需的自定义作为脚本过程的一部分应用到 OD4B 网站。
-
为特定用户的 Office 365 创建了实际的 OD4B 网站,并将其与他们的用户配置文件关联
在某种意义上,这也是真正可靠的过程,但需要管理新人并“手动”更新,这意味着与使用
隐藏的应用部件
方法相比,该过程需要执行更多的操作。 如果你将一些其他文件共享解决方案迁移到 OD4B,并想避免最终用户需要在启动实际网站创建前对 OD4B 网站进行一次访问,这绝对是可取且特别有效的方法。
基于用户配置文件更新的远程计时器作业
这种方法意味着快速浏览用户配置文件,核查该 OD4B 网站创建针对的对象,然后根据需要将更改应用到该网站。 这指的是 SharePoint 外运行的计划的作业,该作业将定期检查状态并执行所需的自定义。 计划的作业可以作为 Azure 中的 WebJob 运行,或作为你自己 Windows 计划程序中简单的 PowerShell 脚本运行。 显然,部署的规模对已选择的计划选项具有重大影响。
-
计划任务已启动,它将访问用户的用户配置文件,用于检查配置了 OD4B 网站的用户
-
实际网站将基于业务需求逐一进行自定义
此方法的主要缺点之一是,用户在自定义被应用前就可访问 OD4B 网站(该情况显然是存在的)。 同时,该方法也是其他方法的有趣补充,可以确保最终用户没有更改网站上任何必要的设置,或检查 OD4B 网站内容是否符合公司政策。
基于增强的应用部件的自定义
以下是基于增强的应用部件的自定义的更详细的信息,似乎是应用和管理 OD4B 网站所需的自定义的典型方法。 本解决方案的源代码和其他详细信息可参见
Office 365 Developer Patterns and Practices guidance(Office 365 开发人员模式和实践指导)
。
实际逻辑设计遵循隐藏的应用部件方法,该方法在之前的
此博客文章
中有所提及。 这指的是,假定你在 Office 365 环境中具有可以放置所需应用部件的集中的内部网,且最终用户在打开浏览器时会登录到此欢迎页面。 每个公司浏览器都有使用组策略的相同的主页设置(这是很常见的),以便最终用户在打开他们的浏览器时始终从一个集中的位置开始。 这是你放置应用部件的位置(可被设置为 0 像素高和宽的大小)。 这里的要点是使用最终用户上下文来执行应用部件,该部件包含来自提供商托管的外接程序的页面。
性能优化和维护注意事项
由于用户每次登录到内部网的首页时都会执行该应用部件,我们需要考虑其性能影响,或如何编写代码以使其有效工作,并且仅在真正需要时执行代码执行的关键部分。 需要考虑的第二个优化项也是,在哪里放置每个网站中使用的实际资产。 这是处理任何自定义都会面临的典型问题。 以下是实施应用模型时需重点关注的事项的简短列表。
-
资产的位置 - 集中内容传递网络 (CDN) 解决方案,在每个网站集还是在根网站集?
-
资产的刷新频率或如何确保无论客户端浏览器的缓存如何,我们都可以确保我们执行脚本 (JavaScript) 的最新版本或显示图像的最新版本?
-
减少代码的执行以避免对 Azure 和 Office 365 服务进行不必要的加载。
-
应用到 OD4B 网站的版本自定义。
资产的位置
对此有几个不同的解决方案。 在引用代码示例中,我们在每个 OD4B 网站中使用 JavaScript 嵌入,以提供公司策略消息,并删除创建子网站(或隐藏链接)的可能性。 在该特定解决方案中,我们将所需的 JavaScript 文件上传到 OneDrive for Business 地址方案的根网站集,并直接从单个 OD4B 网站中的一个位置引用那个文件。 这意味着如果需要进行任何更改,你只有一个位置对 JavaScript 文件进行维护和更新。
在该引用实施中,我们实际上也是在每次执行 WebJob 时刷新这个文件,当然这是不必要的,但是相同的代码意味着更轻松地工作,而无需任何其他的步骤和可能。 你也可以将 JavaScript 文件手动上传到根网站集,然后从那里引用它。 替代解决方案也是使用一些 CND 以存储所需文件,或从提供商托管的应用端引用 JavaScript。 只要只有一个文件副本,就可以:
客户端缓存面临的挑战和解决方案
基于 JavaScript 的实施所面临的挑战之一是客户端缓存。 当浏览器下载使用的 JavaScript 文件时,它们将缓存这些文件以减少对以下请求下载资产的数量。 从性能优化的角度看,这样做很好,但是当我们需要更新 JavaScript 文件时则会出现问题。 在最坏的案例场景中,缓存的 JavaScript 文件将导致与使用更新版本引入的其他更新的异常。
若要删除此问题,我们可以从使用具有 JavaScript URL 引用的修订属性开始。 当我们将用户自定义操作关联到 OD4B 网站时,我们加入了 JavaScript 的 URL,以使其在 URL 中具有唯一的 GUID。 以下是指向网站集根网站的引用的例子。 请注意 URL 中的 rev 属性后添加的其他 GUID。 每次对特定 OD4B 网站执行自定义时,将更新该属性。 在实践中,这意味着在新版本添加到 OD4B 网站前,JavaScript 文件将被缓存到浏览器,这将更改 URL,随之浏览器将下载新的版本并在下次更新后缓存它。
-
/OneDriveCustomization/OneDriveConfiguration.js?rev=4bb89029e7ba470e893170d4cba7de00
以下是用来生成用户自定义操作的 JavaScript URL 的代码。
/// <summary>
/// Just to build the JS path which can be then pointed to the OneDrive site.
/// </summary>
/// <returns></returns>
public string BuildJavaScriptUrl(string siteUrl)
// Solve root site collection URL
Uri url = new Uri(siteUrl);
string scenarioUrl = String.Format("{0}://{1}:{2}/{3}",
url.Scheme, url.DnsSafeHost,
url.Port, JSLocationFolderName);
// Unique rev generated each time JS is added, so that we force browsers to
// refresh JS file wiht latest version
string revision = Guid.NewGuid().ToString().Replace("-", "");
return string.Format("{0}/{1}?rev={2}", scenarioUrl, JSFileName, revision);
减少了代码的执行
由于此过程是基于内部网的首页上具有隐藏的应用部件,这意味着该代码将在用户每次刷新他们的浏览器或移至该页面时执行。 由于该首页通常被设置为企业用户的默认浏览器主页,代码将在每次启动浏览器会话时执行。
因为我们并不那么经常更新应用到 OD4B 网站的自定义,所以实在没有必要首先启动整个自定义更新过程。 通过执行此操作,我们减少了存储队列的使用和 Web 作业的执行,这将直接减少与提供商托管的应用端相关联的成本,因为在我们的设计中将不会使用那么多的 CPU 和其他资源。
确保我们的处理不在每个请求中执行的最简单的方法是使用经典浏览器 cookie 方法(我们在特定的生命时间将特定 cookie 存储到客户端浏览器)。 通过检查该 cookie 是否存在,我们可以在该 cookie 过期前跳过此执行(即当我们重新查看 OD4B 网站中的实际自定义状态时)。
以下是我们能够提供的有关应用部件的页面加载方法。 此方法调用将检查 cookie 是否存在,如果 cookie 确实存在,我们将跳过此实际业务直到再次需要此执行。
// Check if we should skip this check. We do this only once per hour to avoid
// perf issues and there's really no point even hitting the user profile
// in every request.
if (CookieCheckSkip())
return;
实际的 cookie 状态和 cookie 的设置如下。
/// <summary>
/// Checks if we need to execute the code customization code again.
/// Timer set to 60 minutes to avoid constant execution of the code for nothing.
/// </summary>
/// <returns></returns>
private bool CookieCheckSkip()
// Get cookie from the current request.
HttpCookie cookie = Request.Cookies.Get("OneDriveCustomizerCheck");
// Check if cookie exists in the current request.
if (cookie == null)
// Create cookie.
cookie = new HttpCookie("OneDriveCustomizerCheck");
// Set value of cookie to current date time.
cookie.Value = DateTime.Now.ToString();
// Set cookie to expire in 60 minutes.
cookie.Expires = DateTime.Now.AddMinutes(60);
// Insert the cookie in the current HttpResponse.
Response.Cookies.Add(cookie);
// Output debugging information
WriteDebugInformationIfNeeded(
string.Format(@"Cookie did not exist, adding new cookie with {0}
as expiration. Execute code.",
cookie.Expires));
// Since cookie did not existed, let's execute the code,
// so skip is false.
return false;
// Output debugging information
WriteDebugInformationIfNeeded(string.Format(@"Cookie did existed,
with {0} as expiration. Skipping code.",
cookie.Expires));
// Since cookie did existed, let's skip the code
return true;
如果你对应用部件页面中的代码有进一步的了解,你将看到,在每个调用中,我们确实为最终用户检查 OD4B 网站是否存在。 因为仅能通过访问用户配置文件完成此操作,代码将对性能产生影响。 通过使用以上 cookie 检查,我们可以提升最终用户体验,并在没有实际要求的情况下,避免频繁命中用户配置文件服务。 值得注意的一点是,我们将 cookie 检查置于 Page_Load 方法中的第一步,以便我们可以在需要时跳过所有处理。 以下是来自 Page_Load 方法(来自 Customizer.aspx)的简短代码段,以显示代码过程。
protected void Page_Load(object sender, EventArgs e)
// Check if we should skip this check. We do this only once per hour to avoid
// perf issues and there's really no point even hitting the user profile
// in every request.
if (CookieCheckSkip())
return;
var spContext =
SharePointContextProvider.Current.GetSharePointContext(Context);
using (ClientContext clientContext =
spContext.CreateUserClientContextForSPHost())
// Get user profile
ProfileLoader loader = ProfileLoader.GetProfileLoader(clientContext);
UserProfile profile = loader.GetUserProfile();
Microsoft.SharePoint.Client.Site personalSite = profile.PersonalSite;
clientContext.Load(profile, prof => prof.AccountName);
clientContext.Load(personalSite);
clientContext.ExecuteQuery();
// Let's check if the site already exists
if (personalSite.ServerObjectIsNull.Value)
应用到 OD4B 网站的版本自定义。
有关代码处理的第二个优化级别是针对部署到 OD4B 网站的自定义进行版本控制。 这是指我们将自定义版本存储到 OD4B 网站属性包,并只在需要的时候更新文件和资产。 这意味着在 WebJob 执行过程中,我们将比较 OD4B 中的当前自定义版本和我们即将部署的版本,仅当 OD4B 网站中不存在自定义版本时,我们才上载所需的资产并应用其他设置。
该方法也会明显减少来自 Microsoft Azure 的所需的资源,因为在不需要时,我们会避免刷新或执行实际自定义代码。 这意味着减少 CPU 和 Microsoft Azure 中的其他资源的使用,以及对 Office 365 的更少请求。
存储在本示例中的所有自定义逻辑位于 OD4B.Configuration.Async.Common.SiteModificationManager 类中的 ApplySiteConfiguration 方法。 此方法也被具有正确参数的 WebJob 调用,以启动自定义过程。 在实际执行任何操作前,我们使用“Contoso_OneDriveVersion”密钥来检查属性包值,仅当网站中的当前版本低于我们计划应用的版本时,执行才会继续。 以下是使用 Office PnP 核心组件以简化该代码的实际代码。
// Check current site configuration status - is it already in right version?
if (ctx.Web.GetPropertyBagValueInt(
SiteModificationManager.OneDriveMarkerBagID, 0)
< SiteModificationManager.BrandingVersion)
对网站应用自定义时,我们将对以下执行设置属性包的应用自定义版本。
// Save current branding applied indicator to site
ctx.Web.SetPropertyBagValue(SiteModificationManager.OneDriveMarkerBagID, SiteModificationManager.BrandingVersion);
这是相对简单的过程,但会明显减少来自 Azure 的必要资源,也会减少对 Office 365 不必要的远程操作,将对性能产生积极的影响。
Azure 中所需的配置
该示例正常工作的关键要求是,你已经创建了 Azure 存储数据服务,并且对项目更新了相应的存储连接字符串。 只需从 Azure 管理门户 (manage.windowssazure.com) 创建存储服务,只需选择 “新建 -> 数据服务 -> 存储 -> 快速创建”。 完成此操作后,你只需定义名称、位置和其他几项设置即可开始。
存储创建后,需要复制密钥以用于连接字符串。 移动到存储详细信息页时,可以通过单击页面底部的“管理访问密钥”来访问密钥信息。
你需要为 Visual Studio 解决方案中的以下项目更新 App.config 文件。 稍后将在该博客文章中介绍有关每个项目更详细的信息。
OD4B.Configuration.Async.WebJob OD4B.Configuration.Async.Console.SendMessage WebJob 项目具有两个密钥,可将其更新,以指向同一连接,而 SendMessage 仅有一个更新的密钥。
引用解决方案结构
Visual Studio 解决方案包含很多解决方案,但是每个解决方案都有值得我们充分了解的理由。 以下是对该解决方案中每个项目的介绍,以及它们存在的原因和用途。
OD4B.Configuration.Async
这是实际 SharePoint 应用项目,它将向 SharePoint 引入提供商托管的应用,并请求所需权限。 请注意,尽管我们实际上并未从应用部件本身执行租户级别操作,但是我们正在对外接程序请求非常高级别的权限。 这是因为我们将在 WebJob 执行中使用此应用文件中的相同客户端 ID 和机密。 通过使用此方法,你无需手动向 SharePoint 注册应用 id 和密码,我们只需使用同一标识符和密码跨解决方案。
该项目也包含应用部件定义(之后将被部署到主机 Web)。
OD4B.Configuration.Async.Common
该项目包含了所有实际的业务逻辑和跨项目的共享代码,例如放置到存储队列的数据对象的定义,以及自定义 OD4B 网站的实际业务逻辑。 在此放置代码的原因很简单,它为我们提供了创建网站时开发和测试操作的更简单的方法。 就像常规的开发,你不应该真的将你的业务逻辑代码直接放置到 WebJob 或应用部件,而是将其定位在业务逻辑层以便更轻松地测试和重复使用代码。
有关 OD4B 网站的所有实际操作位于 OD4B.Configuration.Async.Common.SiteModificationManager 类。
OD4B.Configuration.Async.Console.Reset
该项目是我们对实际自定义的测试和调试项目。 它可以用来向任何 OD4B 网站手动应用所需的自定义。 在开发过程中,该项目在被挂钩到 WebJob 之前是我们测试自定义过程的测试项目。 项目还可以用来从 OD4B 网站重置自定义,以用于演示或测试。 由于实际业务逻辑位于通用项目中,该项目将使用相同的 SiteModificationManager 类,以从网站应用或重置自定义。
测试自定义时,你只需更改“应用”和“重置”间的 Main 方法中的代码,以更改所需的操作。
static void Main(string[] args)
Uri url =
new Uri("https://vesaj-my.sharepoint.com/personal/vesaj_veskuonline_com");
//get the new site collection
string realm = TokenHelper.GetRealmFromTargetUrl(url);
var token = TokenHelper.GetAppOnlyAccessToken(
TokenHelper.SharePointPrincipal,
url.Authority, realm).AccessToken;
using (var ctx =
TokenHelper.GetClientContextWithAccessToken(url.ToString(),
token))
// Uncomment the one you need for testing/reset
// Apply(ctx, url);
Reset(ctx);
请注意,你需要确保 app.config 中该项目的应用 id 和密码与对你的租户授予所需权限的应用 id 和密码相匹配。 你可以通过右键单击项目并选择“调试 - 启动新实例”来轻松地执行该项目,以便你可以演示逐行执行的实际代码。
OD4B.Configuration.Async.Console.SendMessage
该项目添加到解决方案中,以在该项目被挂钩到应用部件前测试存储队列机制。 项目可以用来绕过应用部件过程,以向存储队列添加新的消息。 请注意你需要在 app.config 中更新相应的存储队列连接字符串,以使项目正常工作。
你可以通过右键单击项目并选择“调试”-“启动新实例”来轻松地执行项目,以便你可以演示逐行执行的实际代码。
OD4B.Configuration.Async.WebJob
这是使用 webJob 项目模板创建的实际 WebJob 项目,在Visual Studio 2013 Update 4中引入。 此模板通过就地添加正确的引用可以更轻松地创建 WebJob 项目,并且它还通过对项目的右键单击支持提供良好的部署自动化。 只需将项目的初始版本或新版本部署到 Azure,只需右键单击并选择“发布为 Azure Web 作业...” 这将打开发布向导。
该 WebJob 作为连续 WebJob 创建(基于队列的处理需要它)。 这意味着在 Main 方法中,我们仅设置过程以使其连续执行,如下所示。
class Program
// Please set the following connection strings in app.config for this
// WebJob to run: AzureWebJobsDashboard and AzureWebJobsStorage
static void Main()
var host = new JobHost();
// The following code ensures that the WebJob will be
// running continuously
host.RunAndBlock();
实际的队列处理对 WebJob 来说的确很简单。 我们唯一要做的是为方法设置正确的属性,并确保应用配置中的 Azure 存储连接字符串会被相应地更新,并与你对 Microsoft Azure 创建的存储队列相匹配。 以下是来自 function.cs 类的 ProcessQueueMessage 方法。 请注意我们如何使用仅适用于应用的令牌模型访问来自 WebJob 的 SharePoint。 若要执行此操作,你需要确保向项目的 app.config 复制了正确的应用 id 和密码。 实际的业务逻辑位于 SiteModificationManager 类,所以我们只需使用正确的客户端上下文和参数调用它。
// This function will get triggered/executed when a new message is written
// on an Azure Queue called queue.
public static void ProcessQueueMessage(
[QueueTrigger(SiteModificationManager.StorageQueueName)]
SiteModificationData request, TextWriter log)
Uri url = new Uri(request.SiteUrl);
//Connect to the OD4B site sing App Only access
string realm = TokenHelper.GetRealmFromTargetUrl(url);
var token = TokenHelper.GetAppOnlyAccessToken(
TokenHelper.SharePointPrincipal, url.Authority, realm).AccessToken;
using (var ctx = TokenHelper.GetClientContextWithAccessToken(
url.ToString(), token))
// Set configuration object properly for setting the config
SiteModificationConfig config = new SiteModificationConfig()
SiteUrl = url.ToString(),
JSFile = Path.Combine(Environment.GetEnvironmentVariable
("WEBROOT_PATH"), "Resources\\OneDriveConfiguration.js"),
ThemeName = "Garage",
ThemeColorFile = Path.Combine(Environment.GetEnvironmentVariable
("WEBROOT_PATH"), "Resources\\Themes\\Garage\\garagewhite.spcolor"),
ThemeBGFile = Path.Combine(Environment.GetEnvironmentVariable
("WEBROOT_PATH"), "Resources\\Themes\\Garage\\garagebg.jpg"),
ThemeFontFile = "" // Ignored in this case, but could be also obviously set
new SiteModificationManager().ApplySiteConfiguration(ctx, config);
值得注意的另外一点是,你需要确保已为项目的 SharePoint CSOM 集引用属性设置了“复制本地”属性,以便在部署 Web 作业时,所有依赖程序集被正确复制到 Azure。 这仅仅是因为这些程序集在默认情况下并没有位于正常的 Azure 网站,因此通过将此属性设置为 True,你将确保引用的程序集也被复制到云。
OD4B.Configuration.AsyncWeb
这实际上是托管在 Microsoft Azure 中的实际提供商托管的应用。 它包含登录到应用部件的页面,该页面放置在内部网的首页上。 该应用的 Default.aspx 页实际上不包含任何操作,它显示了提供如何使用应用的详细信息。
请注意。 如果一般情况下,如果遇到 WebJob 或仅应用访问的权限被拒绝问题,请确保已更新app.config中的应用客户端 ID 和机密,以匹配此项目中web.config中的值。 在某些情况下,Visual Studio 可以更改这些值。
WebJob 中的队列处理
在 Azure 的可用 API 中使用存储队列的确是非常清楚的。 最简单的方法是从使用“Windows Azure 存储”Nuget 包开始,它会把所有所需的 API 和其他包与你的项目相关联。 添加了该 Nuget 包后,只需从使用用于处理的存储队列 API 开始。 以下是来自 OD4B.Configuration.Async.Common 项目(SiteModificationManager 类中的 AddConfigRequestToQueue 方法)的代码段,它包含实际队列消息处理代码,并通过多个项目使用(提供了更轻松的开发时间调试)。
public void AddConfigRequestToQueue(
string account, string siteUrl, string storageConnectionString)
CloudStorageAccount storageAccount =
CloudStorageAccount.Parse(storageConnectionString);
// Get queue... create if does not exist.
CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient();
CloudQueue queue =
queueClient.GetQueueReference(SiteModificationManager.StorageQueueName);
queue.CreateIfNotExists();
// Pass in data for modification
var newSiteConfigRequest = new SiteModificationData()
AccountId = account,
SiteUrl = siteUrl
// Add entry to queue
queue.AddMessage(
new CloudQueueMessage(
JsonConvert.SerializeObject(newSiteConfigRequest)));
使用 WebJob 项目完成了此情况中的实际队列处理。 在 WebJob 的案例中,我们只需使用特定的属性以自动进行队列处理。 我们唯一需要确保的是,我们在发送和接收部件中使用了相同的存储连接字符串和队列名称。
// This function will get triggered/executed when a new message is written
// on an Azure Queue called queue.
public static void ProcessQueueMessage(
[QueueTrigger(SiteModificationManager.StorageQueueName)]
SiteModificationData request, TextWriter log)
Uri url = new Uri(request.SiteUrl);
再没有比这更简单的方法了。 请注意,我们在两端都使用 SiteModificationManager.StorageQueueName,以确保队列名称是匹配的。
将实际配置应用到网站
在该引用实施中,我们对每个 OD4B 网站执行以下自定义。
添加公司策略消息(该消息将始终显示在 OD4B 网站中)
从网站内容页面隐藏创建子网站链接的选项。
将自定义主题应用到 OD4B 网站以匹配公司品牌打造
通过使用所谓的 JavaScript 嵌入模式(通过参考特定的 JavaScript 文件将自定义用户操作添加到网站级别)实现公司策略和隐藏创建子网站的链接(之后在每个页面请求中被执行)。 这意味着我们可以通过添加、删除或在任何页面中更新任何元素来使用客户端技术控制页面处理。 通过使用此方法,我们无需引入自定义母版页(否则将给我们带来高额的长期维护成本,尤其是当所需的更改非常少的时候)。
使用自定义主题的第二个操作要求我们将一些其他文件上传到网站,然后将这些文件设置为用作主题设置。 我们严格使用 CSOM 将所有必要的文件上传到网站,以避免将来出现与功能框架元素关联的复杂情况。 由于使用 CSOM 将文件上传到 SharePoint 非常简单,因此这绝对是执行自动化的最简单方法,并且无需担心沙盒解决方案的任何特定于 xml 的配置依赖项。下面是 OD4B 中的实际站点配置方法。Configuration.Async.Common.SiteModificationManager 类。 请注意,我们使用 Office 365 Developer PnP 核心组件来简化一些所需的操作。
值得注意的一点是,每次自定义个人 OD4B 网站时,我们的确将 JS 的新版本上载到根网站集。 这当然不是最佳解决方案,我们之所以介绍它是出于简化此引用解决方案的考虑。 你可以考虑将 JavaScript 文件上载操作添加到安装了应用的事件,以便在安装应用时,该操作仅执行一次,但是在这种情况下,你需要在该 JS 文件上进行附带任何更新的一些其他工作。
// This function will get triggered/executed when a new message is written
// on an Azure Queue called queue.
public static void ProcessQueueMessage(
[QueueTrigger(SiteModificationManager.StorageQueueName)]
SiteModificationData request, TextWriter log)
Uri url = new Uri(request.SiteUrl);
//Connect to the OD4B site using App Only token
string realm = TokenHelper.GetRealmFromTargetUrl(url);
var token = TokenHelper.GetAppOnlyAccessToken(
TokenHelper.SharePointPrincipal, url.Authority, realm).AccessToken;
using (var ctx = TokenHelper.GetClientContextWithAccessToken(
url.ToString(), token))
// Set configuration object properly for setting the config
SiteModificationConfig config = new SiteModificationConfig()
SiteUrl = url.ToString(),
JSFile = Path.Combine(Environment.GetEnvironmentVariable
("WEBROOT_PATH"), "Resources\\OneDriveConfiguration.js"),
ThemeName = "Garage",
ThemeColorFile =
Path.Combine(Environment.GetEnvironmentVariable
("WEBROOT_PATH"), "Resources\\Themes\\Garage\\garagewhite.spcolor"),
ThemeBGFile =
Path.Combine(Environment.GetEnvironmentVariable
("WEBROOT_PATH"), "Resources\\Themes\\Garage\\garagebg.jpg"),
ThemeFontFile = "" // Ignored in this case, but could be also set
new SiteModificationManager().ApplySiteConfiguration(ctx, config);
很显然,所需的操作主要依赖于你的业务需求。 你可以从 Office 365 Developer Patterns and Practices(Office 365 开发人员模式和实践)找到基于 CSOM 操作的多个不同的模式和方法。
基于 WebJob 解决方案的其他注意事项
以下是有关 Azure 中 WebJob 开发的几个其他注意事项。 这是一个非常强大的技术,必将跨 Office 365 自定义广泛使用。 你肯定会看到基于 WebJob 技术的新的和增强的解决方案也添加到Office 365开发人员模式&实践计划。
调试 WebJob 和自定义过程
对于代码的良好做法之一通常是找到实际最终执行过程之外的实际操作,以便你可以将注意力先放在通过使用控制台应用程序或诸如 Visual Studiao 中的测试项目轻松简单地测试所需代码上。 通过使用这种方式,你可以确保实际业务逻辑的完全功能(在你将其挂钩到最终过程前),在本案例中指的是 WebJob。 在此引用的解决方案案例中,我们将所有业务代码放置到 OD4B.Configuration.Async.Common.SiteModificationManager 类,并从多个位置对其调用。
这意味着在开发过程中,我们可以使用 OD4B.Configuration.Async.Console.Reset 控制台应用程序,并根据需要从网站对自定义进行多次测试和重置,以确保业务逻辑完全稳定。 这的确与 SharePoint 外接程序模型或 Azure 开发没有任何关系,而是实用的分步开发实践(不论使用什么技术)。 当我在 MCM 中作为 SharePoint 认证培训的演讲者时,我将 NKOTB 方法作为参考,但是它的确不是业内标准术语 :-)
从调试角度来说,对 WebJob 的重大改进之一是,在 Visual Studio 2014 更新 4 中引入。 随着 Azure 连接和项目模板的引入,你实际上可以使用运行在 Azure 端中的 WebJob 来进行远程调试。 你需要将 WebJob 部署到 Azure,之后你可以通过右键单击来自“服务器资源管理器的 WebJob 实例并选择来自上下文菜单的“附加调试器”来开始调试会话。
甚至还有一个测试程序,用于将格式正确的消息发送到参考解决方案中的队列。 OD4B。Configuration.Async.Console.SendMessage 项目的创建只是为了有机会调试 WebJob 进程,而无需强制将应用部件部署到任何位置。 在逐步调试和测试整个过程时,再次出现这种情况。
WebJob 环境变量
有关 WebJob 的有趣的一点是,它们虽然在 Azure 网站下运行,但是它们的执行位置与 Azure 中正常的网站稍有不同。 我的意思是,如果你将任何其他文件或资产与 WebJob 一起部署到 Azure,你可能会遇到问题(如果假定你可以使用 WebJob 代码中的经典相对路径直接引用那些资产)。
在这种情况下,我们有一个 JavaScript 文件和几个自定义主题的文件,这些文件被部署到 Azure 网站,以便它们可以被上传到所需的 SharePoint 网站。 如果你在特定网站下扩展文件分支,你将会在 Azure 中看到那些文件。
通常情况下,可以在 Azure 网站中使用以下格式来引用那些文件
string path = HostingEnvironment.MapPath(
string.Format("~/{0}", "Resources/OneDriveConfiguration.js"));
由于 WebJob 在不同的位置执行,并且不在 IIS 的上下文,以上对于文件的引用将不起作用,因为映射将从 WebJob 过程的上下文失败。 这就是 WebJob 特定环境变量发挥作用的时候了。 在引用解决方案的情况下,我们使用 WebJob 特定环境变量 WEBROOT_PATH 以在关联的网站文件夹上获取访问权限。
string jsFile = Path.Combine(
Environment.GetEnvironmentVariable("WEBROOT_PATH"),
"Resources\\OneDriveConfiguration.js");
还有几个适用于 WebJob 的其他环境变量,可能会对你有所帮助。 你可以使用代码检查不同的环境变量,在 Github 中有对于这个很好的引用。
演示解决方案和操作的视频
这个视频显示实践中的解决方案,包括解决方案结构的介绍以及如何在 Office 365 环境中使用它,以修改 OneDrive for Bueinsess 网站。
自定义 OneDrive for Business 网站
指南文章中 https://aka.ms/OfficeDevPnPGuidance
MSDN 中的引用位于 https://aka.ms/OfficeDevPnPMSDN
视频中的 https://aka.ms/OfficeDevPnPVideos
PnP 示例
OD4B.Configuration.Async(O365 PnP 示例)
Provisioning.OneDriveProvisioning(O365 PnP 示例)
Office PnP 核心组件
Microsoft 365 模式和做法中的示例和内容 (PnP)
Office 365 多租户 (MT)
Office 365 专用 (D) 部分
SharePoint 2013 本地 – 部分
专用模式和本地模式在使用 SharePoint 外接程序模型技术方面完全相同,但在可以使用的可能的技术方面存在差异。