URL 對(duì)外服務

插件最終需要以 URL 路徑的形式對(duì)外提供服務,可以是簡單的靜态地址、Razor風格的cshtml文件以及RESTful 風格的Controller地址,根據實際需要可以靈活選擇。

我們以 XYCMS 分享插件 (opens new window)爲例說(shuō)明如何配置及使用URL 對(duì)外服務,下載分享插件源碼并使用 Visual Studio 打開(kāi)項目,我們會(huì)發(fā)現如下結構:

2

紅框1 區域在項目根目錄的 wwwroot 文件夾下,通常爲 .css、.js、.png 或者 .html 等靜态資源文件,對(duì)外提供靜态資源訪問,資源文件的最終訪問路徑與文件在 wwwroot 文件夾下的結構一緻,例如項目中 index.js 文件的訪問地址爲 /assets/share/index.js。

紅框2 區域在項目根目錄的 Pages 文件夾下,用于支持 ASP.NET Core 中的 Razor Pages 功能(néng),後(hòu)綴通常爲 .cshtml。關于 Razor Pages 的更多信息可以參考 教程:在 ASP.NET Core 中開(kāi)始使用 Razor Pages (opens new window)。

紅框3 區域在項目根目錄的 Controllers 文件夾下,用于支持 ASP.NET Core 中的 Web API 功能(néng)。關于 Web API 的更多信息可以參考 使用 ASP.NET Core 創建 Web API (opens new window)。

靜态資源訪問

XYCMS 分享插件 (opens new window)通過(guò) stl:share 實現前台頁面(miàn)分享功能(néng),最終在前台頁面(miàn)生成(chéng)如下所示的小圖标,點擊圖标將(jiāng)實現分享:

2

由于前台頁面(miàn)需要展示圖标并通過(guò)js實現分享功能(néng),我們需要暴露靜态資源文件并提供給外部訪問,所以我們將(jiāng)資源文件統一放置到 /wwwroot/assets/share 文件夾下。

最終插件資源文件的訪問地址將(jiāng)以 /assets/share/ 爲URL地址前綴。之所以以此路徑爲前綴是爲了避免與網站或其他插件的資源文件地址沖突,實際上,我們可以將(jiāng)靜态資源文件放置在 wwwroot 文件夾下的任意路徑之下。

Razor Pages 頁面(miàn)

XYCMS 插件的 Razor Pages 頁面(miàn)開(kāi)發(fā)與 ASP.NET Core 中的 Razor Pages 頁面(miàn)開(kāi)發(fā)并無區别,XYCMS 系統啓動時(shí)將(jiāng)自動啓用各個插件的 Razor Pages 頁面(miàn)支持。

關于 Razor Pages 的更多信息可以參考 教程:在 ASP.NET Core 中開(kāi)始使用 Razor Pages (opens new window)。

以 XYCMS 分享插件 (opens new window)爲例,我們需要在後(hòu)台提供插件設置頁面(miàn),如下圖:

2

爲了與 XYCMS 系統界面(miàn)風格保持一緻,我們複用了 XYCMS 的基礎Layout布局:

@{ Layout = "_Layout"; }
1

以下是插件 Pages/ss-admin/share/Index.cshtml 文件的源代碼,此頁面(miàn)訪問路徑爲 /ss-admin/share/:

@page
@{ Layout = "_Layout"; }

<el-tabs type="border-card">
  <el-tab-pane label="頁面(miàn)分享設置">

    <el-alert type="info">
      頁面(miàn)分享标簽:<strong>&lt;stl:share&gt;&lt;/stl:share&gt;</strong>
    </el-alert>

    <div style="height: 10px"></div>

    <el-form size="small" ref="settingsForm" label-width="260px" status-icon :model="settingsForm">
      <el-form-item label="默認頁面(miàn)标題" prop="defaultTitle" :rules="{ required: true, message: '請輸入默認頁面(miàn)标題' }">
        <el-input v-model="settingsForm.defaultTitle" placeholder="請輸入默認頁面(miàn)标題"></el-input>
        <div class="tips">當分享插件未獲取到頁面(miàn)标題時(shí)將(jiāng)使用默認頁面(miàn)标題</div>
      </el-form-item>
      <el-form-item label="默認封面(miàn)圖片" prop="defaultImageUrl" :rules="{ required: true, message: '請輸入圖片地址或點擊上方按鈕上傳' }">
        <el-button-group>
          <el-button size="mini" type="info" icon="el-icon-upload2" plain v-on:click="btnLayerClick({title: '上傳圖片', name: 'formLayerImageUpload', attributeName: 'defaultImageUrl', no: 0})">
            上傳
          </el-button>
          <el-button size="mini" type="info" icon="el-icon-folder-opened" plain v-on:click="btnLayerClick({title: '選擇圖片素材', name: 'materialLayerImageSelect', attributeName: 'defaultImageUrl', no: 0, full: true})">
            選擇
          </el-button>
          <el-button size="mini" type="info" icon="el-icon-view" plain :disabled="settingsForm.defaultImageUrl ? false : true" v-on:click="btnPreviewClick('defaultImageUrl', 0)">
            預覽
          </el-button>
        </el-button-group>
        <el-input
          v-model="settingsForm.defaultImageUrl"
          placeholder="請輸入圖片地址或點擊上方按鈕上傳">
        </el-input>
        <div class="tips">當分享插件未獲取到封面(miàn)圖片時(shí)將(jiāng)使用默認封面(miàn)圖片</div>
      </el-form-item>
      <el-form-item label="默認頁面(miàn)介紹" prop="defaultDescription" :rules="{ required: true, message: '請輸入默認頁面(miàn)介紹' }">
        <el-input v-model="settingsForm.defaultDescription" type="textarea" :rows="5" placeholder="請輸入默認頁面(miàn)介紹"></el-input>
        <div class="tips">當分享插件未獲取到頁面(miàn)介紹時(shí)將(jiāng)使用默認頁面(miàn)介紹</div>
      </el-form-item>
    </el-form>

    <el-divider></el-divider>
    <div style="height: 10px"></div>

    <el-row>
      <el-col :span="24" align="center">
        <el-button type="primary" v-on:click="btnSettingsSubmitClick" size="small">保 存</el-button>
      </el-col>
    </el-row>
  </el-tab-pane>
  <el-tab-pane label="微信分享設置">

    <el-alert v-if="mpResult && mpResult.success" type="success" title="微信公衆号設置保存成(chéng)功!"></el-alert>
    <el-alert v-else-if="mpResult && !mpResult.success" type="error" :title="mpResult.errorMessage" ></el-alert>

    <div style="height: 10px"></div>

    <el-form size="small" ref="wxShareForm" label-width="260px" status-icon :model="wxShareForm">
      <el-form-item label="是否啓用微信分享">
        <el-radio v-model="wxShareForm.isWxShare" :label="true">啓用</el-radio>
        <el-radio v-model="wxShareForm.isWxShare" :label="false">不啓用</el-radio>
        <div class="tips">啓用微信分享後(hòu),微信轉發(fā)或分享時(shí)將(jiāng)顯示完整的标題、封面(miàn)及介紹</div>
      </el-form-item>
      <el-form-item v-if="wxShareForm.isWxShare" label="AppId" prop="mpAppId" :rules="{ required: true, message: '請輸入AppId' }">
        <el-input v-model="wxShareForm.mpAppId" placeholder="請輸入AppId"></el-input>
        <div class="tips">請進(jìn)入微信公衆平台,獲取AppId</div>
      </el-form-item>
      <el-form-item v-if="wxShareForm.isWxShare" label="AppSecret" prop="mpAppSecret" :rules="{ required: true, message: '請輸入AppSecret' }">
        <el-input v-model="wxShareForm.mpAppSecret" placeholder="請輸入AppSecret"></el-input>
        <div class="tips">請進(jìn)入微信公衆平台,獲取AppSecret</div>
      </el-form-item>
    </el-form>

    <template v-if="wxShareForm.isWxShare">
      <div style="height: 10px"></div>
      <el-alert type="info">
        請進(jìn)入微信公衆平台,進(jìn)入<strong>開(kāi)發(fā) -> 基本配置 -> IP白名單</strong>,將(jiāng)以下信息填入并啓用。
      </el-alert>
      <div style="height: 10px"></div>

      <el-form size="small" label-width="260px" status-icon>
        <el-form-item label="IP白名單">
          {{ ipAddress }}
        </el-form-item>
      </el-form>
    </template>

    <el-divider></el-divider>
    <div style="height: 10px"></div>

    <el-row>
      <el-col :span="24" align="center">
        <el-button type="primary" v-on:click="btnWxShareSubmitClick" size="small">保 存</el-button>
      </el-col>
    </el-row>
  </el-tab-pane>
</el-tabs>

@section Scripts{
  <script src="/assets/share/index.js" type="text/javascript"></script>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

Web API 服務

XYCMS 插件的 Web API 開(kāi)發(fā)與 ASP.NET Core 中的 Web API 開(kāi)發(fā)同樣(yàng)無區别,XYCMS 系統啓動時(shí)將(jiāng)自動啓用各個插件的 Web API 功能(néng)支持。

關于 Web API 的更多信息可以參考 使用 ASP.NET Core 創建 Web API (opens new window)。

以 XYCMS 分享插件 (opens new window)的 PingController 爲例,訪問 /api/share/ping 地址獲取結果 pong 以确認插件在正常運行:

using Microsoft.AspNetCore.Mvc;

namespace XYCMS.Share.Controllers
{
    [Route("api/share/ping")]
    public class PingController : ControllerBase
    {
        private const string Route = "";

        [HttpGet, Route(Route)]
        public string Get()
        {
            return "pong";
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

以 XYCMS 分享插件 (opens new window)的 WxController 爲例,訪問地址 /api/share/wx?siteId=<站點Id>&url=<url地址> 將(jiāng)獲取到 JSON 格式的頁面(miàn)分享信息:

using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using XYCMS.Services;
using XYCMS.Share.Abstractions;

namespace XYCMS.Share.Controllers
{
    [Route("api/share/wx")]
    public partial class WxController : ControllerBase
    {
        private const string Route = "";

        private readonly IWxManager _wxManager;
        private readonly IShareManager _shareManager;

        public WxController(IWxManager wxManager, IShareManager shareManager)
        {
            _wxManager = wxManager;
            _shareManager = shareManager;
        }

        public class GetRequest
        {
            public int SiteId { get; set; }
            public string Url { get; set; }
        }

        public class GetResult
        {
            public bool Success { get; set; }
            public string ErrorMessage { get; set; }
            public string AppId { get; set; }
            public string Timestamp { get; set; }
            public string NonceStr { get; set; }
            public string Signature { get; set; }
        }

        [HttpGet, Route(Route)]
        public async Task<ActionResult<GetResult>> Get([FromQuery] GetRequest request)
        {
            var success = false;
            var errorMessage = string.Empty;
            var appId = string.Empty;
            var timestamp = string.Empty;
            var nonceStr = string.Empty;
            var signature = string.Empty;

            var settings = await _shareManager.GetSettingsAsync(request.SiteId);
            if (settings.IsWxShare)
            {
                string ticket;
                (success, ticket, errorMessage) = await _wxManager.GetJsApiTicketAsync(settings.MpAppId, settings.MpAppSecret);
                if (success)
                {
                    appId = settings.MpAppId;
                    timestamp = _wxManager.GetTimestamp();
                    nonceStr = _wxManager.GetNonceStr();
                    signature = _wxManager.GetJsApiSignature(ticket, nonceStr, timestamp, request.Url);
                }
            }

            return new GetResult
            {
                Success = success,
                ErrorMessage = errorMessage,
                AppId = appId,
                Timestamp = timestamp,
                NonceStr = nonceStr,
                Signature = signature
            };
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

在 Web API 中,我們可以使用預先注入的 XYCMS API,更多用法我們將(jiāng)在 XYCMS API 章節中進(jìn)行詳細說(shuō)明。