65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (30投票s)

2013 年 12 月 11 日

CPOL

19分钟阅读

viewsIcon

183404

使用 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 接受**。您仍然可以手动将应用包发送给用户安装,所以更多的是曝光度的问题。

查看此MSDN 文章,了解有关托管选项的更深入的介绍

本文将重点介绍为 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_ 文件(见上图),其中包含所有与安全相关的代码——但它们是如何被调用的?应用身份验证机制大致如下

安装及之前

  1. 您在 **AnimalApp** 的 _AppManifest.xml_ 文件中配置应用所需的权限(请注意,运行时有效权限是当前用户的权限与授予您应用的权限的组合)
  2. 管理员安装您的应用并确认应用请求的权限

安装后,流程如下

  1. SharePoint 用户单击您的应用图标
  2. SharePoint 将用户重定向到您的应用默认页面,并使用一个 POST 请求,其中包含您的应用返回 SharePoint 进行身份验证所需的所有详细信息——包括站点 URL 和 OAuth 访问令牌
  3. 您的默认页面将所有这些身份验证数据存储到 HTTP 会话中1
  4. 当用户在您的应用中导航时,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 参数未传递,因此身份验证失败。在 AnimalControllerCreate 方法中,这条线负责

发生的情况是,动物被创建并插入,然后我们重定向回索引页面——但没有至关重要的 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 应用几个属性——UserNameUserId。您可以从 OnActionExecution 方法调用此方法,如下所示

我们传递了当前的 HttpContext,以便我们可以创建一个 SharePoint 上下文,以及 ViewBag——ViewBag 被用作一个方便的位置来缓存数据,这些数据可以从 ViewController 访问。

然后打开 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-headerdiv 标签。
  • 从 MVC 导航栏 div 标签中删除类 navbar-inversenavbar-fixed-top
  • 删除类 navbar-collapsecollapse

您的 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 应用。希望它有所帮助——请在评论中告诉我!编码愉快!

© . All rights reserved.