演练:使用 ASP.NET MVC 5 创建 O365 SharePoint 2013 应用






4.78/5 (30投票s)
使用 ASP.NET MVC 5 创建 SharePoint 2013 自动托管应用(Autohosted Apps)的指南。
更新:自动托管应用已不再支持。您应该查看我关于创建提供商托管应用的新文章。或者,继续阅读,但请务必使用提供商托管的特性而不是自动托管。谢谢!
引言
SharePoint 应用与我们过去处理的 SharePoint 解决方案完全不同。使用应用模型,您的代码是一个完全独立的 Web 应用程序,运行在一个远离 SharePoint 服务器的 Web 服务器上。这带来了许多缺点——不再能使用服务器对象模型和提升权限来 hack 任何遇到的问题。但本文不是要沉湎于过去(太多了);而是关于如何使用 Visual Studio 2013 编写一个基于 MVC 5 的清晰架构的应用。而这本身将使 SharePoint 的编程更快、更清晰(理论上!)。
为什么选择应用?
您仍然可以编写传统的 SharePoint 解决方案。但这可能不是一个好主意
- 解决方案不是面向未来的:它们已被弃用,并且可能在 SharePoint 的未来版本中不受支持
- SharePoint Online 只支持沙盒解决方案,并且它们受到非常严格的限制
- 应用可以列在 Office Store 上,方便部署到 O365
- 应用可以使用任何 Web 技术编写——不一定是 .NET——使用开放的 Web 标准,例如 **OAuth**。
点击此处获取更多应用宣传资料!
所以,假设您已经决定编写一个应用,下一步是什么?一个重要的、初始的架构决策是关于托管模型:有三个选项,它们取决于您计划提供的功能以及您应用的潜在受众
- SharePoint 托管
您的应用将只包含客户端代码。您可以包含自定义列表和 Web 部件,并使用 JavaScript 客户端对象模型 (CSOM) 与 SharePoint 进行交互。由于您的 JavaScript 在 SharePoint 域的上下文中执行,因此安全性很简单。 - 提供商托管
您的应用将具有服务器端代码,该代码托管在另一台服务器上。这台服务器可以是您自己的(如果您想托管该应用),也可以安装在客户的 premises 中(例如,对于一个高度安全的政府自定义本地应用)。 - 自动托管
这是一个特殊的!架构与提供商托管类似——应用具有服务器端代码,并且再次在 SharePoint 外部的机器上运行。然而,那台机器在 Azure 上。您无需担心任何 Azure 部署细节——只需将您的应用标记为“**autohosted**”,当您安装它时,Azure 实例将自动创建并连接——仿佛施了魔法一般。
**请注意,截至 2013 年 12 月,自动托管应用尚未被 Office Store 接受**。您仍然可以手动将应用包发送给用户安装,所以更多的是曝光度的问题。
本文将重点介绍为 SharePoint 2013 Online 创建自动托管应用。
必备组件
您将需要:
- Visual Studio 2013
您可以使用 Visual Studio 2012 + Office Developer Tools 开发应用,但 MVC 项目模板将不可用。 - **SharePoint Online (O365)**,并配置了开发者站点(忽略关于 Napa 的内容,我们有 Visual Studio :-))。**自动托管应用仅适用于 Office 365 上的 SharePoint Online**。如果您希望针对自己的 SharePoint 安装(本地部署)进行构建,那么您需要构建一个**提供商托管**的应用。然而,本文中的许多概念(不涉及身份验证)仍然适用。
创建解决方案
我们将创建一个非常简单的应用,名为 AnimalApp。这将允许我们的用户管理一个动物列表。非常有用!功能包括
- 列表的 CRUD 操作
- 用户只能看到自己的动物
- 管理员可以看到所有人的动物
- 存储在数据库表中
现在,将列表存储在 SharePoint 列表中可能是明智的。但我希望展示自动托管应用中如何处理数据库连接字符串。
我们开始吧!单击 **文件 -> 新建 -> 项目**,然后选择 **App for SharePoint 2013**
选择 **autohosted**(自动托管),然后填写您的开发者站点的地址
在下一个屏幕上,选择 **ASP.NET MVC Web Application**,然后单击 **finish**(完成)。这将为您创建两个项目
- AnimalApp
此项目包含托管在 SharePoint 上的应用部分。它包含自定义列表或 Web 部件的任何定义,并定义了您的应用所需的权限。 - AnimalAppWeb
这是 Web 项目,代表将安装在 Azure 上的内容。此项目包含您的代码!
查看 **AnimalAppWeb** 项目。我们已经生成了很多代码!我们主要关注的区域是
- **Controllers**:MVC 中的 C!负责模型和视图(在本例中是数据库和网页)之间的交互
- **Filters**:一种在渲染特定页面时执行代码的便捷方式。我们将使用一个过滤器来确保用户在每次请求开始时都已登录 SharePoint
- **Models**:MVC 中的 M… 在本例中,是一个类,用于表示每个数据库表
- **Scripts**:我们不做任何脚本编写,但我们会查看 spcontext.js,它有一个重要函数。
- **Views**:包含每个页面的 .cshtml 文件——包含 HTML 和 Razor 视图渲染代码。
什么都不做,您就可以按 **F5** 部署您的应用。您可能需要登录到您的开发者站点。部署后,将打开一个浏览器窗口,SharePoint 将要求您批准您的应用
默认只请求基本权限。单击 Trust It(信任它),如果一切顺利,您将被重定向到
保护页面
在我们编写任何代码之前,让我们看看 SharePoint 安全性是如何“发生”的。有 _SharePointContext.cs_ 和 _TokenHelper.cs_ 文件(见上图),其中包含所有与安全相关的代码——但它们是如何被调用的?应用身份验证机制大致如下
安装及之前
- 您在 **AnimalApp** 的 _AppManifest.xml_ 文件中配置应用所需的权限(请注意,运行时有效权限是当前用户的权限与授予您应用的权限的组合)
- 管理员安装您的应用并确认应用请求的权限
安装后,流程如下
- SharePoint 用户单击您的应用图标
- SharePoint 将用户重定向到您的应用默认页面,并使用一个
POST
请求,其中包含您的应用返回 SharePoint 进行身份验证所需的所有详细信息——包括站点 URL 和 OAuth 访问令牌 - 您的默认页面将所有这些身份验证数据存储到 HTTP 会话中1
- 当用户在您的应用中导航时,
SPHostUrl
参数会传递到每个页面以维护上下文(即,应用当前正在处理的 SharePoint 站点)。将其作为 URL 参数传递可确保如果您的应用在多个站点上下文中被访问,该上下文将得到正确维护。
1 第 3 点有点含糊。您实际上不需要编写任何代码来存储身份验证数据——项目模板开箱即用。请查看 **Controllers\HomeController.cs**
public class HomeController : Controller
{
[SharePointContextFilter]
public ActionResult Index()
{
return View();
}
...
}
上面的 Index
方法(即您的应用主页)有一个有用的注解——SharePointContextFilter
。这定义在 **Filters\SharePointContextFilterAttribute.cs** 中。它的作用是调用我们提到的 SharePointContext
代码并处理用户检查。这就是 **OAuth** 调用所在的地方——它处理应用访问令牌验证并将其存储到用户会话中。每次调用时,它都会将用户会话中存储的内容与 SPHostUrl
相结合,并在必要时重定向到登录页面。
尝试登录机制——在 Chrome 的隐身模式(或 Internet Explorer 的 InPrivate 模式)下加载默认页面,就会显示登录窗口。现在在新标签页中打开“**About**”页面——它不需要登录,因为它默认情况下在其 Controller 方法上没有 SharePointContextFilter
属性。
使用 SharePointContextFilter 属性
您可以将此属性应用于您喜欢的任何控制器方法!如果您省略它,则任何人都可以访问该页面。所以,如果它仅供用户查看,或者您正在使用 CSOM 与 SharePoint 进行交互,那么您需要添加此属性。
进行 SharePoint 调用
现在您知道用户已成功登录,您可以创建一个 SharePoint 上下文并进行 CSOM 调用。这很简单
var spContext = SharePointContextProvider.Current.GetSharePointContext(httpContextBase);
using (var clientContext = spContext.CreateUserClientContextForSPHost())
{
if (clientContext != null)
{
//CSOM code
}
}
传递 SPHostUrl
我提到 SPHostUrl(您当前所在的 SharePoint 站点的 URL,在本例中是开发者站点)会传递到每个页面。这基本上是真的:_Scripts\spcontext.js_ 中的脚本被加载到每个页面(如何?请参阅 _App_Start\BundleConfig.cs_ 和 _Global.asax.cs_ 并将这些联系起来……)。一旦脚本加载到页面上,它就会执行一个小小的 hack——页面上的每个 URL 都会附加 SPHostUrl
参数,该参数稍后由 SharePointContextProvider
代码读取。然后又开始循环了!
现在,这会带来一个小小的复杂性——如果您的应用中有 AJAX 请求,或者一个表单需要将数据发布回服务器,那么 SPHostUrl
将不会自动包含在这些请求中。解决方案很简单,就是手动添加它。当我们将一些功能添加到我们的 AnimalApp 时,我们将看到这个“bug”。
添加 SQL Azure 数据库
如前所述,我们的应用是数据库驱动的——让我们看看这一切是如何工作的!
单击 **文件 -> 添加 -> 新项目**,然后选择 **Other Languages -> SQL Server Database Project**。
添加后,您需要将您的应用项目指向数据库项目。通过单击 AnimalApp 项目,然后在属性窗口中,在 SQL Database 属性下选择 **AnimalDatabase** 来执行此操作。
Visual Studio 会很乐意帮助您更新 SQL Server 项目以针对 Azure SQL。单击 Yes。现在,您的 SQL Azure 实例将与您应用的其余部分一起在部署时自动配置。太棒了!
现在我们将向数据库添加数据。右键单击 **AnimalDatabase** 项目,单击 **Add -> Table**。将其命名为 **Animals**,然后单击 **Add**。
添加一个 Name
列和一个 UserId
列
这将存储动物名称以及插入它的用户 ID。
如果您希望 ID 自动递增(我们希望),请将 Id
列的脚本更改为包含 IDENTITY
关键字。
使用 Entity Framework 生成数据访问层代码
好的,这实际上与 SharePoint 应用或 MVC 无关,但它很酷,所以我提到了这一点!
在过去,您会在 C# 项目中开始编写数据访问层来访问您的数据库。我们不会这样做:相反,我们将使用 Entity Framework 为我们生成所有代码。您也可以选择使用 Entity Framework 的“Code First”功能来编写 C# 类,并让它生成相应的 SQL 表结构;我更喜欢自己编写 SQL。
此时,如果您还没有安装 Entity Framework Power Tools,则需要将其添加到 Visual Studio。
单击 **Tools -> Extensions and Updates**,然后搜索 **Entity Framework Power Tools**。截至撰写本文时,当前版本是 Beta 4。
安装完成后,您可以使用它为每个数据库表生成一个 C# 类。请注意,如果您添加了多个表以及关系(外键等),则生成的 C# 类将创建相应的成员和集合。
在生成代码之前,您需要本地部署数据库,因此右键单击 **AnimalDatabase** 并单击 **Publish**。
单击 Target database connection 的 **Edit**,并将其设置为“**(localdb)\Projects**”。这对应于您开发计算机上的 SQL Express,但您显然可以使用任何可用的 SQL Server。
单击 **Publish**。Data Tools Operations 窗口应告知您它已成功发布。现在我们可以调用 Entity Framework——右键单击 **AnimalAppWeb** 项目(我们将从中访问数据库的项目)并单击 **Reverse Engineer Code First**。
再次,输入服务器名称为 (localdb)\Projects。在 'Connect to a database' 下,AnimalDatabase 应该存在,所以选择它。单击 OK。
现在,一旦它完成了所有精彩的生成,您会注意到它已将所有数据类放在 **Models** 文件夹下——恰到好处!
在这一点上,我想指出生成的类被标记为 partial
。这非常有帮助,因为您可能需要扩展它们。例如,让我们向 Animal
类添加一个新的构造函数。在 Animal.cs 的同一文件夹中添加一个文件,并将其命名为 _Animal_Partial.cs_。按如下方式编辑代码
public partial class Animal
{
public Animal() { }
public Animal(string name, int userId)
{
this.Name = name;
this.UserId = userId;
}
}
现在,当我们重新运行 Entity Framework 代码生成时,我们对 Animal
类的更改将不会被覆盖。
自动托管应用 SQL 连接字符串
现在我们需要处理我们的连接字符串。SharePoint 自动托管应用对连接字符串使用特定的约定:您在 _web.config_ 中将其定义为键 **SqlAzureConnectionString
**。这意味着当您的应用部署并且数据库安装到 Azure 实例时,安装程序将自动更新连接字符串以指向动态部署的数据库。聪明!所以,将此设置添加到您的 _web.config_ appSettings
节点
<add key="SqlAzureConnectionString"
value="Data Source=(localdb)\Projects;Initial Catalog=AnimalDatabase;Integrated
Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False" />
您在这里需要做的就是将 Initial Catalog
更改为您的数据库名称。
现在,Entity Framework 为我们创建了另一个类,我们需要查看它:AnimalsDatabaseContext
类,它为我们设置数据库连接。该类的基类 DbContext
接受连接字符串作为构造函数参数,所以我们只需要添加一个新的构造函数并从 _web.config_ 读取值。添加一个新文件,_AnimalDatabaseContext_Partial_,并像上次一样将其标记为 partial
我还添加了一个方便的功能,用于通过从 _web.config_ 读取连接字符串来创建该类的新实例。
用于编辑动物的 MVC 页面
我们将快速添加一些页面来读取/创建/更新/删除动物。Visual Studio 将根据您之前创建的实体框架模型为您生成这些页面。
右键单击 **Controllers** 文件夹,然后转到 **Add Controller**。选择“**MVC5 Controller with views, using Entity Framework**”
像这样填写 Add Controller 对话框(这应该相当容易理解)
单击 Add 后,将生成许多文件并添加到您的解决方案中
- 在 Controllers 文件夹中添加了一个新类
AnimalController
。负责您数据库上的创建/读取/更新/删除操作。 - 在 _Views/Animal_ 文件夹下,为每个操作创建了一个页面:_Create.cshtml_、_Delete.cshtml_ 等。
我们剩下的唯一工作就是在主导航中添加指向我们新 CRUD 页面的链接,主导航位于 _Views/Shared/_Layout.cshtml_。
打开该文件,找到渲染导航栏的部分
为您 Animal
页面添加链接
ActionLink
方法的三个参数是
title
——为链接渲染的字符串actionName
——这对应于 **AnimalController
** 中的方法名controllerName
——这应该是 Animal,以匹配我们刚刚创建的控制器。
按 F5,您将看到一些漂亮的 CRUD 页面。这有多容易??
保护新页面
您可能会注意到您可以匿名使用新的动物页面。要保护它们,我们将对它们应用 SharePointContextFilter
。
打开新的控制器文件 AnimalController
,并在每个方法上注解 SharePointContextFilter
现在这些页面已受到保护。
在 post-back 中传递 SPHostUrl
为新的 Animal CRUD 页面添加安全性所带来的复杂性在于,在 post-back 过程中(例如,当您添加、编辑或删除 Animal 时),它们需要传递 SPHostUrl
。如果您尝试添加新动物,它将成功插入,然后您将看到此消息
“未知用户:无法确定您的身份。请尝试通过启动安装在您站点上的应用再次尝试”。
为什么会这样?原因是(如您从 URL 中看到的)SPHostUrl
参数未传递,因此身份验证失败。在 AnimalController
的 Create
方法中,这条线负责
发生的情况是,动物被创建并插入,然后我们重定向回索引页面——但没有至关重要的 SPHostUrl
参数。我们可以通过将参数添加到重定向中来非常简单地修复它
return RedirectToAction("Index",
new { SPHostUrl = SharePointContext.GetSPHostUrl(HttpContext.Request).AbsoluteUri });
在这里,我们只是将 SPHostUrl
作为 URL 参数添加到对 Index
页面的请求中。您应该将此参数添加到任何执行 SharePoint 身份验证的目标页面 RedirectToAction
调用中。
不同内容,不同用户
我们将配置我们的页面,以便用户只能看到他们自己的动物。
首先,让我们做一些清理工作。从视图中删除 UserId
列
上面是需要从 _Create.cshtml_ 页面中删除的相关代码。您应该在 _Delete.cshtml_ 和 _Edit.cshtml_ 中定位并删除相关的 **UserId** 代码。我们暂时保留 _Details.cshtml_ 和 _Index.cshtml_;我们将在后面的步骤中为管理员显示 UserId
。
现在,我们将手动设置 User Id,在创建时设置为当前 SharePoint 用户。首先,我们需要检索 User Id——最合适的地方是 _Filters\SharePointContextFilterAttribute.cs_ 类——毕竟,它已经为任何受保护的页面执行。这也是我们知道用户已成功登录的时间点。
添加以下方法
private void GetSPUserDetails(HttpContextBase httpContextBase, dynamic viewBag)
{
var spContext = SharePointContextProvider.Current.GetSharePointContext(httpContextBase);
using (var clientContext = spContext.CreateUserClientContextForSPHost())
{
if (clientContext != null)
{
User spUser = clientContext.Web.CurrentUser;
clientContext.Load(spUser, user => user.Title, user => user.Id);
clientContext.ExecuteQuery();
viewBag.UserName = spUser.Title;
viewBag.UserId = spUser.Id;
}
}
}
这只是使用 CSOM 向 SharePoint 请求用户详细信息,并对提供的 viewbag 应用几个属性——UserName
和 UserId
。您可以从 OnActionExecution
方法调用此方法,如下所示
我们传递了当前的 HttpContext
,以便我们可以创建一个 SharePoint 上下文,以及 ViewBag
——ViewBag
被用作一个方便的位置来缓存数据,这些数据可以从 View
和 Controller
访问。
然后打开 AnimalController
并更新 Index()
以过滤当前用户 ID 的 Animals
public ActionResult Index()
{
int userId = ViewBag.UserId;
return View(db.Animals.Where(a => a.UserId == userId).ToList());
}
接下来,更新 Create
方法以在创建时设置用户 ID
请注意,我们还从绑定的属性中删除了 UserId
;我们之前已从 HTML 页面中删除。您还应该将其从 Edit
方法中删除。
再次运行解决方案——现在用户只能看到他们自己的动物。
SharePoint 样式
SharePoint 应用通常会匹配其安装的站点的样式。在此步骤中,我们将添加该样式并删除一些 MVC 默认样式。
首先,我们将 SPHostUrl
添加到 ViewBag
。原因将在几分钟内变得清晰。将其添加到我们之前创建的 GetSPUserDetails
方法中
请注意,我们删除了最后的斜杠,因为我们将连接另一个 URL 部分。
打开 _Views\Shared\_Layout.cshtml_ 并将此代码添加到 header
<link href='@ViewBag.SPHostUrl/_layouts/15/defaultcss.ashx' type='text/css' rel='stylesheet' />
<script src='@ViewBag.SPHostUrl/_layouts/15/SP.UI.Controls.js'></script>
这将从**您的 SharePoint 服务器**拉取 CSS 和控件脚本。这很棒,因为现在您的应用将匹配您的 SharePoint 站点的样式!目标是让您的应用尽可能地融合。
如果您现在运行您的应用,您实际上已经可以看到字体已更改为与您的 SharePoint 站点匹配。
渲染 SharePoint 导航栏
同样在 _Layout.cshtml_ 文件中,我们将从导入 **jQuery** 开始
@Scripts.Render("~/bundles/jquery")
jQuery 实际上已经导入,但位于文件底部。所以将其删除。或者,您也可以确保对 jQuery 的任何引用都位于底部的导入之后。
然后添加脚本以在页面加载时渲染顶部栏
$(function () {
var options = {
appHelpPageUrl: '@Url.Action("About","Home")',
appIconUrl: "AppIcon_Blue.png",
appTitle: "MVC5 app",
settingsLinks: [
{
linkUrl: '@Url.Action("Contact","Home")',
displayName: "Contact"
},
]
};
var nav = new SP.UI.Controls.Navigation("chrome_ctrl_container", options);
nav.setVisible(true);
});
请注意,我们将 **Contact** 页面渲染为 **Settings** 菜单下的一个选项。这只是为了说明;我知道联系方式不是设置!我还向我的项目添加了一个 _Img_ 文件夹,以及一张 96x96 像素的图片用作我的应用图标。
接下来,添加将指示在哪里渲染导航栏的 div
标签。这应该是 body 下的**第一个元素**
<body>
<div id="chrome_ctrl_container"></div>
现在,在这个阶段,我们进入了 CSS 的领域。您可能想找一个设计师来帮忙!我们基本上是将 MVC5 默认 CSS 与 SharePoint CSS 和应用导航栏结合起来。为了使其看起来*勉强可以接受*,我不得不这样做
- 打开 _Content/Site.css_ 并删除
body
上的padding-top: 50px
- 在 _Layout.cshtml_ 中,删除类为
navbar-header
的div
标签。 - 从 MVC 导航栏 div 标签中删除类
navbar-inverse
和navbar-fixed-top
。 - 删除类
navbar-collapse
和collapse
。
您的 body HTML 应该开始如下
<body>
<div id="chrome_ctrl_container"></div>
<div class="navbar">
<div class="container">
<ul class="nav navbar-nav">
<li>@Html.ActionLink("Create Animal", "Create", "Animal")</li>
<li>@Html.ActionLink("View Animals", "Index", "Animal")</li>
</ul>
</div>
</div>
...
</body>
您的应用看起来像这样
未来工作
这篇文章已经太长了,所以我在这里停止!但是,有一些我希望包含的内容
事件接收器
应用中的事件接收器是一个相当棘手的概念;我的意思是,微软会告诉你它们很简单,但我运气不好。您需要实现一个 WCF Web 服务(因此它们被称为**远程**事件接收器),SharePoint 在事件发生时会调用它。您在应用安装期间将该服务注册为事件接收器。 点击此处获取有关创建应用事件接收器的详细信息。
处理自定义列表
这实际上并不难,也与 MVC 没有太大关系,所以我没有包含它。您可以添加一个在应用安装时创建的自定义列表(右键单击您的应用项目,添加 -> 新项目 -> List),与之交互是 CSOM 的问题。
Web 部件
应用世界中的 Web 部件本质上是 iFrame 中的一个网页——它被放置在 SharePoint 页面上。因此,最好的方法可能是编写一个专门针对您的 Web 部件的控制器,然后创建一个关联的视图。
下载解决方案
单击此处下载包含示例代码的 zip 文件。它相当大(16MB),因为包含了大量的依赖项!
结束
您需要克服相当多的细微差别才能将 MVC 应用程序真正变成一个 SharePoint 2013 应用。希望它有所帮助——请在评论中告诉我!编码愉快!