Commit 95f55d38 authored by kiler398's avatar kiler398

添加docfx文档方案文件

parent 5beb347b
# CAP                       [English](https://github.com/dotnetcore/CAP/blob/develop/README.md)
[![Travis branch](https://img.shields.io/travis/dotnetcore/CAP/develop.svg?label=travis-ci)](https://travis-ci.org/dotnetcore/CAP)
[![AppVeyor](https://ci.appveyor.com/api/projects/status/4mpe0tbu7n126vyw?svg=true)](https://ci.appveyor.com/project/yuleyule66/cap)
[![NuGet](https://img.shields.io/nuget/v/DotNetCore.CAP.svg)](https://www.nuget.org/packages/DotNetCore.CAP/)
[![NuGet Preview](https://img.shields.io/nuget/vpre/DotNetCore.CAP.svg?label=nuget-pre)](https://www.nuget.org/packages/DotNetCore.CAP/)
[![Member project of .NET China Foundation](https://img.shields.io/badge/member_project_of-.NET_CHINA-red.svg?style=flat&colorB=9E20C8)](https://github.com/dotnetcore)
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/dotnetcore/CAP/master/LICENSE.txt)
CAP 是一个在分布式系统(SOA、MicroService)中实现最终一致性的库,它具有轻量级、易使用、高性能等特点。
你可以在这里[CAP Wiki](https://github.com/dotnetcore/CAP/wiki)看到更多详细资料。
## 预览(OverView)
CAP 是在一个 ASP.NET Core 项目中使用的库,当然他可以用于 ASP.NET Core On .NET Framework 中。
你可以把 CAP 看成是一个 EventBus,因为它具有 EventBus 的所有功能,并且 CAP 提供了更加简化的方式来处理 EventBus 中的发布和订阅。
CAP 具有消息持久化的功能,当你的服务进行重启或者宕机时它可以保证消息的可靠性。CAP提供了基于Microsoft DI 的 Publisher Service 服务,它可以和你的业务服务进行无缝结合,并且支持强一致性的事务。
这是CAP集在ASP.NET Core 微服务架构中的一个示意图:
![](document/images/cap.png)
> 图中实线部分代表用户代码,虚线部分代表CAP内部实现。
## Getting Started
### NuGet
你可以运行以下下命令在你的项目中安装 CAP。
```
PM> Install-Package DotNetCore.CAP
```
如果你的消息队列使用的是 Kafka 的话,你可以:
```
PM> Install-Package DotNetCore.CAP.Kafka
```
如果你的消息队列使用的是 RabbitMQ 的话,你可以:
```
PM> Install-Package DotNetCore.CAP.RabbitMQ
```
CAP 默认提供了 Sql Server 的扩展作为数据库存储(MySql的正在开发中):
```
PM> Install-Package DotNetCore.CAP.SqlServer
```
### Configuration
首先配置CAP到 Startup.cs 文件中,如下:
```cs
public void ConfigureServices(IServiceCollection services)
{
......
services.AddDbContext<AppDbContext>();
services.AddCap(x =>
{
// 如果你的 SqlServer 使用的 EF 进行数据操作,你需要添加如下配置:
// 注意: 你不需要再次配置 x.UseSqlServer(""")
x.UseEntityFramework<AppDbContext>();
// 如果你使用的Dapper,你需要添加如下配置:
x.UseSqlServer("数据库连接字符串");
// 如果你使用的 RabbitMQ 作为MQ,你需要添加如下配置:
x.UseRabbitMQ("localhost");
//如果你使用的 Kafka 作为MQ,你需要添加如下配置:
x.UseKafka("localhost");
});
}
public void Configure(IApplicationBuilder app)
{
.....
app.UseCap();
}
```
### 发布
在 Controller 中注入 `ICapPublisher` 然后使用 `ICapPublisher` 进行消息发送
```cs
public class PublishController : Controller
{
private readonly ICapPublisher _publisher;
public PublishController(ICapPublisher publisher)
{
_publisher = publisher;
}
[Route("~/checkAccount")]
public async Task<IActionResult> PublishMessage()
{
//指定发送的消息头和内容
await _publisher.PublishAsync("xxx.services.account.check", new Person { Name = "Foo", Age = 11 });
return Ok();
}
[Route("~/checkAccountWithTrans")]
public async Task<IActionResult> PublishMessageWithTransaction([FromServices]AppDbContext dbContext)
{
using (var trans = dbContext.Database.BeginTransaction())
{
await _publisher.PublishAsync("xxx.services.account.check", new Person { Name = "Foo", Age = 11 });
trans.Commit();
}
return Ok();
}
}
```
### 订阅
**Action Method**
在 Action 上添加 CapSubscribeAttribute 来订阅相关消息。
```cs
public class PublishController : Controller
{
private readonly ICapPublisher _publisher;
public PublishController(ICapPublisher publisher)
{
_publisher = publisher;
}
[NoAction]
[CapSubscribe("xxx.services.account.check")]
public async Task CheckReceivedMessage(Person person)
{
Console.WriteLine(person.Name);
Console.WriteLine(person.Age);
return Task.CompletedTask;
}
}
```
**Service Method**
如果你的订阅方法没有位于 Controller 中,则你订阅的类需要继承 `ICapSubscribe`
```cs
namespace xxx.Service
{
public interface ISubscriberService
{
public void CheckReceivedMessage(Person person);
}
public class SubscriberService: ISubscriberService, ICapSubscribe
{
[CapSubscribe("xxx.services.account.check")]
public void CheckReceivedMessage(Person person)
{
}
}
}
```
然后在 Startup.cs 中的 `ConfigureServices()` 中注入你的 `ISubscriberService`
```cs
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ISubscriberService,SubscriberService>();
}
```
## 贡献
贡献的最简单的方法之一就是是参与讨论和讨论问题(issue)。你也可以通过提交的 Pull Request 代码变更作出贡献。
### License
MIT
// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
var common = require('./common.js');
var classCategory = 'class';
var namespaceCategory = 'ns';
exports.transform = function (model) {
if (!model) return null;
langs = model.langs;
handleItem(model, model._gitContribute, model._gitUrlPattern);
if (model.children) {
model.children.forEach(function (item) {
handleItem(item, model._gitContribute, model._gitUrlPattern);
});
}
if (model.type) {
switch (model.type.toLowerCase()) {
case 'namespace':
model.isNamespace = true;
if (model.children) groupChildren(model, namespaceCategory);
break;
case 'class':
case 'interface':
case 'struct':
case 'delegate':
case 'enum':
model.isClass = true;
if (model.children) groupChildren(model, classCategory);
model[getTypePropertyName(model.type)] = true;
handleNamespace(model);
break;
default:
break;
}
}
return model;
}
exports.getBookmarks = function (model, ignoreChildren) {
if (!model || !model.type || model.type.toLowerCase() === "namespace") return null;
var bookmarks = {};
if (typeof ignoreChildren == 'undefined' || ignoreChildren === false) {
if (model.children) {
model.children.forEach(function (item) {
bookmarks[item.uid] = common.getHtmlId(item.uid);
if (item.overload && item.overload.uid) {
bookmarks[item.overload.uid] = common.getHtmlId(item.overload.uid);
}
});
}
}
// Reference's first level bookmark should have no anchor
bookmarks[model.uid] = "";
return bookmarks;
}
exports.groupChildren = groupChildren;
exports.getTypePropertyName = getTypePropertyName;
exports.getCategory = getCategory;
function groupChildren(model, category) {
if (!model || !model.type) {
return;
}
var typeChildrenItems = getDefinitions(category);
var grouped = {};
model.children.forEach(function (c) {
if (c.isEii) {
var type = "eii";
} else {
var type = c.type.toLowerCase();
}
if (!grouped.hasOwnProperty(type)) {
grouped[type] = [];
}
// special handle for field
if (type === "field" && c.syntax) {
c.syntax.fieldValue = c.syntax.return;
c.syntax.return = undefined;
}
// special handle for property
if (type === "property" && c.syntax) {
c.syntax.propertyValue = c.syntax.return;
c.syntax.return = undefined;
}
// special handle for event
if (type === "event" && c.syntax) {
c.syntax.eventType = c.syntax.return;
c.syntax.return = undefined;
}
grouped[type].push(c);
})
var children = [];
for (var key in typeChildrenItems) {
if (typeChildrenItems.hasOwnProperty(key) && grouped.hasOwnProperty(key)) {
var typeChildrenItem = typeChildrenItems[key];
var items = grouped[key];
if (items && items.length > 0) {
var item = {};
for (var itemKey in typeChildrenItem) {
if (typeChildrenItem.hasOwnProperty(itemKey)){
item[itemKey] = typeChildrenItem[itemKey];
}
}
item.children = items;
children.push(item);
}
}
}
model.children = children;
}
function getTypePropertyName(type) {
if (!type) {
return undefined;
}
var loweredType = type.toLowerCase();
var definition = getDefinition(loweredType);
if (definition) {
return definition.typePropertyName;
}
return undefined;
}
function getCategory(type) {
var classItems = getDefinitions(classCategory);
if (classItems.hasOwnProperty(type)) {
return classCategory;
}
var namespaceItems = getDefinitions(namespaceCategory);
if (namespaceItems.hasOwnProperty(type)) {
return namespaceCategory;
}
return undefined;
}
function getDefinition(type) {
var classItems = getDefinitions(classCategory);
if (classItems.hasOwnProperty(type)) {
return classItems[type];
}
var namespaceItems = getDefinitions(namespaceCategory);
if (namespaceItems.hasOwnProperty(type)) {
return namespaceItems[type];
}
return undefined;
}
function getDefinitions(category) {
var namespaceItems = {
"class": { inClass: true, typePropertyName: "inClass", id: "classes" },
"struct": { inStruct: true, typePropertyName: "inStruct", id: "structs" },
"interface": { inInterface: true, typePropertyName: "inInterface", id: "interfaces" },
"enum": { inEnum: true, typePropertyName: "inEnum", id: "enums" },
"delegate": { inDelegate: true, typePropertyName: "inDelegate", id: "delegates" }
};
var classItems = {
"constructor": { inConstructor: true, typePropertyName: "inConstructor", id: "constructors" },
"field": { inField: true, typePropertyName: "inField", id: "fields" },
"property": { inProperty: true, typePropertyName: "inProperty", id: "properties" },
"method": { inMethod: true, typePropertyName: "inMethod", id: "methods" },
"event": { inEvent: true, typePropertyName: "inEvent", id: "events" },
"operator": { inOperator: true, typePropertyName: "inOperator", id: "operators" },
"eii": { inEii: true, typePropertyName: "inEii", id: "eii" }
};
if (category === 'class') {
return classItems;
}
if (category === 'ns') {
return namespaceItems;
}
console.err("category '" + category + "' is not valid.");
return undefined;
}
// reserve "namespace" of string for backward compatibility
// will replace "namespace" with "namespaceExpanded" of object
function handleNamespace(model) {
model.namespaceExpanded = model.namespace;
if (model.namespaceExpanded) {
model.namespace = model.namespaceExpanded.uid;
}
}
function handleItem(vm, gitContribute, gitUrlPattern) {
// get contribution information
vm.docurl = common.getImproveTheDocHref(vm, gitContribute, gitUrlPattern);
vm.sourceurl = common.getViewSourceHref(vm, null, gitUrlPattern);
// set to null incase mustache looks up
vm.summary = vm.summary || null;
vm.remarks = vm.remarks || null;
vm.conceptual = vm.conceptual || null;
vm.syntax = vm.syntax || null;
vm.implements = vm.implements || null;
vm.example = vm.example || null;
common.processSeeAlso(vm);
// id is used as default template's bookmark
vm.id = common.getHtmlId(vm.uid);
if (vm.overload && vm.overload.uid) {
vm.overload.id = common.getHtmlId(vm.overload.uid);
}
if (vm.supported_platforms) {
vm.supported_platforms = transformDictionaryToArray(vm.supported_platforms);
}
if (vm.requirements) {
var type = vm.type.toLowerCase();
if (type == "method") {
vm.requirements_method = transformDictionaryToArray(vm.requirements);
} else {
vm.requirements = transformDictionaryToArray(vm.requirements);
}
}
if (vm && langs) {
if (shouldHideTitleType(vm)) {
vm.hideTitleType = true;
} else {
vm.hideTitleType = false;
}
if (shouldHideSubtitle(vm)) {
vm.hideSubtitle = true;
} else {
vm.hideSubtitle = false;
}
}
function shouldHideTitleType(vm) {
var type = vm.type.toLowerCase();
return ((type === 'namespace' && langs.length == 1 && (langs[0] === 'objectivec' || langs[0] === 'java' || langs[0] === 'c'))
|| ((type === 'class' || type === 'enum') && langs.length == 1 && langs[0] === 'c'));
}
function shouldHideSubtitle(vm) {
var type = vm.type.toLowerCase();
return (type === 'class' || type === 'namespace') && langs.length == 1 && langs[0] === 'c';
}
function transformDictionaryToArray(dic) {
var array = [];
for(var key in dic) {
if (dic.hasOwnProperty(key)) {
array.push({"name": key, "value": dic[key]})
}
}
return array;
}
}
\ No newline at end of file
// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
/**
* This method will be called at the start of exports.transform in ManagedReference.html.primary.js
*/
exports.preTransform = function (model) {
return model;
}
/**
* This method will be called at the end of exports.transform in ManagedReference.html.primary.js
*/
exports.postTransform = function (model) {
return model;
}
\ No newline at end of file
// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
var mrefCommon = require('./ManagedReference.common.js');
var extension = require('./ManagedReference.extension.js');
var overwrite = require('./ManagedReference.overwrite.js');
exports.transform = function (model) {
if (overwrite && overwrite.transform) {
return overwrite.transform(model);
}
if (extension && extension.preTransform) {
model = extension.preTransform(model);
}
if (mrefCommon && mrefCommon.transform) {
model = mrefCommon.transform(model);
}
if (model.type.toLowerCase() === "enum") {
model.isClass = false;
model.isEnum = true;
}
model._disableToc = model._disableToc || !model._tocPath || (model._navPath === model._tocPath);
if (extension && extension.postTransform) {
model = extension.postTransform(model);
}
return model;
}
exports.getOptions = function (model) {
if (overwrite && overwrite.getOptions) {
return overwrite.getOptions(model);
}
return {
"bookmarks": mrefCommon.getBookmarks(model)
};
}
\ No newline at end of file
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
{{!master(layout/_master.tmpl)}}
{{#isNamespace}}
{{>partials/namespace}}
{{/isNamespace}}
{{#isClass}}
{{>partials/class}}
{{/isClass}}
{{#isEnum}}
{{>partials/enum}}
{{/isEnum}}
{{>partials/customMREFContent}}
\ No newline at end of file
This diff is collapsed.
// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
/**
* This method will be called at the start of exports.transform in RestApi.html.primary.js
*/
exports.preTransform = function (model) {
return model;
}
/**
* This method will be called at the end of exports.transform in RestApi.html.primary.js
*/
exports.postTransform = function (model) {
return model;
}
\ No newline at end of file
// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
var restApiCommon = require('./RestApi.common.js');
var extension = require('./RestApi.extension.js')
exports.transform = function (model) {
if (extension && extension.preTransform) {
model = extension.preTransform(model);
}
if (restApiCommon && restApiCommon.transform) {
model = restApiCommon.transform(model);
}
model._disableToc = model._disableToc || !model._tocPath || (model._navPath === model._tocPath);
if (extension && extension.postTransform) {
model = extension.postTransform(model);
}
return model;
}
exports.getOptions = function (model) {
return { "bookmarks": restApiCommon.getBookmarks(model) };
}
\ No newline at end of file
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
{{!master(layout/_master.tmpl)}}
{{>partials/rest}}
\ No newline at end of file
// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
exports.path = {};
exports.path.getFileNameWithoutExtension = getFileNameWithoutExtension;
exports.path.getDirectoryName = getDirectoryName;
exports.getHtmlId = getHtmlId;
exports.getViewSourceHref = getViewSourceHref;
exports.getImproveTheDocHref = getImproveTheDocHref;
exports.processSeeAlso = processSeeAlso;
exports.isAbsolutePath = isAbsolutePath;
exports.isRelativePath = isRelativePath;
function getFileNameWithoutExtension(path) {
if (!path || path[path.length - 1] === '/' || path[path.length - 1] === '\\') return '';
var fileName = path.split('\\').pop().split('/').pop();
return fileName.slice(0, fileName.lastIndexOf('.'));
}
function getDirectoryName(path) {
if (!path) return '';
var index = path.lastIndexOf('/');
return path.slice(0, index + 1);
}
function getHtmlId(input) {
return input.replace(/\W/g, '_');
}
// Note: the parameter `gitContribute` won't be used in this function
function getViewSourceHref(item, gitContribute, gitUrlPattern) {
if (!item || !item.source || !item.source.remote) return '';
return getRemoteUrl(item.source.remote, item.source.startLine - '0' + 1, null, gitUrlPattern);
}
function getImproveTheDocHref(item, gitContribute, gitUrlPattern) {
if (!item) return '';
if (!item.documentation || !item.documentation.remote) {
return getNewFileUrl(item, gitContribute, gitUrlPattern);
} else {
return getRemoteUrl(item.documentation.remote, item.documentation.startLine + 1, gitContribute, gitUrlPattern);
}
}
function processSeeAlso(item) {
if (item.seealso) {
for (var key in item.seealso) {
addIsCref(item.seealso[key]);
}
}
item.seealso = item.seealso || null;
}
function isAbsolutePath(path) {
return /^(\w+:)?\/\//g.test(path);
}
function isRelativePath(path) {
if (!path) return false;
return !exports.isAbsolutePath(path);
}
var gitUrlPatternItems = {
'github': {
// HTTPS form: https://github.com/{org}/{repo}.git
// SSH form: git@github.com:{org}/{repo}.git
// generate URL: https://github.com/{org}/{repo}/blob/{branch}/{path}
'testRegex': /^(https?:\/\/)?(\S+\@)?(\S+\.)?github\.com(\/|:).*/i,
'generateUrl': function (gitInfo) {
var url = normalizeGitUrlToHttps(gitInfo.repo);
url += '/blob' + '/' + gitInfo.branch + '/' + gitInfo.path;
if (gitInfo.startLine && gitInfo.startLine > 0) {
url += '/#L' + gitInfo.startLine;
}
return url;
},
'generateNewFileUrl': function (gitInfo, uid) {
var url = normalizeGitUrlToHttps(gitInfo.repo);
url += '/new';
url += '/' + gitInfo.branch;
url += '/' + getOverrideFolder(gitInfo.apiSpecFolder);
url += '/new?filename=' + getHtmlId(uid) + '.md';
url += '&value=' + encodeURIComponent(getOverrideTemplate(uid));
return url;
}
},
'vso': {
// HTTPS form: https://{user}.visualstudio.com/{org}/_git/{repo}
// SSH form: ssh://{user}@{user}.visualstudio.com:22/{org}/_git/{repo}
// generated URL: https://{user}.visualstudio.com/{org}/_git/{repo}?path={path}&version=GB{branch}
'testRegex': /^(https?:\/\/)?(ssh:\/\/\S+\@)?(\S+\.)?visualstudio\.com(\/|:).*/i,
'generateUrl': function (gitInfo) {
var url = normalizeGitUrlToHttps(gitInfo.repo);
url += '?path=' + gitInfo.path + '&version=GB' + gitInfo.branch;
if (gitInfo.startLine && gitInfo.startLine > 0) {
url += '&line=' + gitInfo.startLine;
}
return url;
},
'generateNewFileUrl': function (gitInfo, uid) {
return '';
}
}
}
function normalizeGitUrlToHttps(repo) {
var pos = repo.indexOf('@');
if (pos == -1) return repo;
return 'https://' + repo.substr(pos + 1).replace(/:[0-9]+/g, '').replace(/:/g, '/');
}
function getNewFileUrl(item, gitContribute, gitUrlPattern) {
// do not support VSO for now
if (!item.source) {
return '';
}
var gitInfo = getGitInfo(gitContribute, item.source.remote);
if (!gitInfo.repo || !gitInfo.branch || !gitInfo.path) {
return '';
}
if (gitInfo.repo.substr(-4) === '.git') {
gitInfo.repo = gitInfo.repo.substr(0, gitInfo.repo.length - 4);
}
var patternName = getPatternName(gitInfo.repo, gitUrlPattern);
if (!patternName) return patternName;
return gitUrlPatternItems[patternName].generateNewFileUrl(gitInfo, item.uid);
}
function getRemoteUrl(remote, startLine, gitContribute, gitUrlPattern) {
var gitInfo = getGitInfo(gitContribute, remote);
if (!gitInfo.repo || !gitInfo.branch || !gitInfo.path) {
return '';
}
if (gitInfo.repo.substr(-4) === '.git') {
gitInfo.repo = gitInfo.repo.substr(0, gitInfo.repo.length - 4);
}
var patternName = getPatternName(gitInfo.repo, gitUrlPattern);
if (!patternName) return '';
gitInfo.startLine = startLine;
return gitUrlPatternItems[patternName].generateUrl(gitInfo);
}
function getGitInfo(gitContribute, gitRemote) {
// apiSpecFolder defines the folder contains overwrite files for MRef, the default value is apiSpec
var defaultApiSpecFolder = "apiSpec";
if (!gitContribute && !gitRemote) {
return {
apiSpecFolder: defaultApiSpecFolder
}
}
if (!gitContribute) {
return {
apiSpecFolder: defaultApiSpecFolder,
repo: gitRemote.repo,
branch: gitRemote.branch,
// path defines the relative path from current git repo.
path: gitRemote.path
}
}
return {
apiSpecFolder: gitContribute.apiSpecFolder || defaultApiSpecFolder,
repo: gitContribute.repo || gitRemote.repo,
branch: gitContribute.branch || gitRemote.branch,
path: gitRemote.path
}
}
function getPatternName(repo, gitUrlPattern) {
if (gitUrlPattern && gitUrlPattern.toLowerCase() in gitUrlPatternItems) {
return gitUrlPattern.toLowerCase();
} else {
for (var p in gitUrlPatternItems) {
if (gitUrlPatternItems[p].testRegex.test(repo)) {
return p;
}
}
}
return '';
}
function getOverrideFolder(path) {
if (!path) return "";
path = path.replace('\\', '/');
if (path.charAt(path.length - 1) == '/') path = path.substring(0, path.length - 1);
return path;
}
function getOverrideTemplate(uid) {
if (!uid) return "";
var content = "";
content += "---\n";
content += "uid: " + uid + "\n";
content += "summary: '*You can override summary for the API here using *MARKDOWN* syntax'\n";
content += "---\n";
content += "\n";
content += "*Please type below more information about this API:*\n";
content += "\n";
return content;
}
function addIsCref(seealso) {
if (!seealso.linkType || seealso.linkType.toLowerCase() == "cref") {
seealso.isCref = true;
}
}
// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
/**
* This method will be called at the start of exports.transform in conceptual.html.primary.js
*/
exports.preTransform = function (model) {
return model;
}
/**
* This method will be called at the end of exports.transform in conceptual.html.primary.js
*/
exports.postTransform = function (model) {
return model;
}
\ No newline at end of file
// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
var common = require('./common.js');
var extension = require('./conceptual.extension.js')
exports.transform = function (model) {
if (extension && extension.preTransform) {
model = extension.preTransform(model);
}
model._disableToc = model._disableToc || !model._tocPath || (model._navPath === model._tocPath);
model.docurl = model.docurl || common.getImproveTheDocHref(model, model._gitContribute, model._gitUrlPattern);
if (extension && extension.postTransform) {
model = extension.postTransform(model);
}
return model;
}
\ No newline at end of file
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
{{!master(layout/_master.tmpl)}}
{{{rawTitle}}}
{{{conceptual}}}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
var path = require('path');
var gulp = require('gulp');
var minify = require('gulp-minify-css');
var rename = require('gulp-rename');
var concat = require('gulp-concat');
var copy = require('gulp-copy');
var vendor = {
css: ['bower_components/bootstrap/dist/css/bootstrap.css',
'bower_components/highlightjs/styles/github-gist.css'
],
js: ['bower_components/jquery/dist/jquery.min.js',
'bower_components/bootstrap/dist/js/bootstrap.min.js',
'bower_components/highlightjs/highlight.pack.min.js',
'bower_components/lunr.js/lunr.min.js',
'bower_components/js-url/url.min.js',
'bower_components/twbs-pagination/jquery.twbsPagination.min.js',
"bower_components/mark.js/dist/jquery.mark.min.js"
],
webWorker: {
src: ['lunr.min.js'],
cwd: 'bower_components/lunr.js/'
},
font: {
src: ['*'],
cwd: 'bower_components/bootstrap/dist/fonts/'
}
}
gulp.task('concat', function () {
gulp.src(vendor.css)
.pipe(minify({keepBreaks: true}))
.pipe(rename({
suffix: '.min'
}))
.pipe(concat('docfx.vendor.css'))
.pipe(gulp.dest('./styles/'))
;
gulp.src(vendor.js)
.pipe(concat('docfx.vendor.js'))
.pipe(gulp.dest('./styles/'))
;
});
gulp.task('copy', function () {
gulp.src(vendor.font.src, {cwd: vendor.font.cwd})
.pipe(copy('./fonts/'))
;
gulp.src(vendor.webWorker.src, {cwd:vendor.webWorker.cwd})
.pipe(copy('./styles/'))
;
});
gulp.task('default', ['concat', 'copy']);
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
{{!include(/^styles/.*/)}}
{{!include(/^fonts/.*/)}}
{{!include(favicon.ico)}}
{{!include(logo.svg)}}
{{!include(search-stopwords.json)}}
<!DOCTYPE html>
<!--[if IE]><![endif]-->
<html>
{{>partials/head}}
<body data-spy="scroll" data-target="#affix">
<div id="wrapper">
<header>
{{^_disableNavbar}}
{{>partials/navbar}}
{{/_disableNavbar}}
{{^_disableBreadcrumb}}
{{>partials/breadcrumb}}
{{/_disableBreadcrumb}}
</header>
{{#_enableSearch}}
<div class="container body-content">
{{>partials/searchResults}}
</div>
{{/_enableSearch}}
<div role="main" class="container body-content hide-when-search">
{{^_disableToc}}
{{>partials/toc}}
<div class="article row grid-right">
{{/_disableToc}}
{{#_disableToc}}
<div class="article row grid">
{{/_disableToc}}
{{#_disableAffix}}
<div class="col-md-12">
{{/_disableAffix}}
{{^_disableAffix}}
<div class="col-md-10">
{{/_disableAffix}}
<article class="content wrap" id="_content" data-uid="{{uid}}">
{{!body}}
</article>
</div>
<div class="col-md-10">
<a name="cloudcomment">
</a>
</div>
{{^_disableAffix}}
{{>partials/affix}}
{{/_disableAffix}}
</div>
</div>
{{^_disableFooter}}
{{>partials/footer}}
{{/_disableFooter}}
</div>
{{>partials/scripts}}
</body>
</html>
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="38.000000pt" height="38.000000pt" viewBox="0 0 172.000000 172.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by Docfx
</metadata>
<g transform="translate(0.000000,172.000000) scale(0.100000,-0.100000)"
fill="#dddddd" stroke="none">
<path d="M230 1359 c0 -18 11 -30 44 -48 80 -42 81 -45 81 -441 0 -400 -1
-404 -79 -436 -36 -15 -46 -24 -46 -43 0 -23 2 -24 61 -17 34 3 88 6 120 6
l59 0 0 495 0 495 -82 0 c-46 0 -100 3 -120 6 -35 6 -38 5 -38 -17z"/>
<path d="M618 1373 l-118 -4 0 -493 0 -494 154 -7 c181 -9 235 -3 313 34 68
33 168 130 207 202 75 136 75 384 1 536 -71 145 -234 240 -399 231 -23 -1 -94
-4 -158 -5z m287 -119 c68 -24 144 -101 176 -179 22 -54 24 -75 24 -210 0
-141 -2 -153 -26 -206 -36 -76 -89 -132 -152 -160 -45 -21 -68 -24 -164 -24
-71 0 -116 4 -123 11 -22 22 -31 175 -28 463 2 208 6 293 15 302 32 32 188 33
278 3z"/>
<path d="M1170 1228 c75 -104 110 -337 76 -508 -21 -100 -56 -178 -105 -233
l-36 -41 34 20 c75 43 160 133 198 212 37 75 38 78 38 191 -1 129 -18 191 -75
270 -28 38 -136 131 -153 131 -4 0 6 -19 23 -42z"/>
</g>
</svg>
{% comment -%}Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.{% endcomment -%}
<div class="hidden-sm col-md-2" role="complementary">
<div class="sideaffix">
{%- if not _disableContribution -%}
<div class="contribution">
<ul class="nav">
{%- if docurl -%}
<li>
<a href="{{docurl}}" class="contribution-link">{{__global.improveThisDoc}}</a>
</li>
{%- endif -%}
{%- if sourceurl -%}
<li>
<a href="{{sourceurl}}" class="contribution-link">{{__global.viewSource}}</a>
</li>
{%- endif -%}
<li>
<div style="margin-left: 10px; margin-top: 3px; margin-bottom: 3px;" class="bdsharebuttonbox"><a href="#" class="bds_more" data-cmd="more">分享到:</a><a href="#" class="bds_weixin" data-cmd="weixin" title="分享到微信"></a><a href="#" class="bds_sqq" data-cmd="sqq" title="分享到QQ好友"></a></div>
</li>
</ul>
</div>
{%- endif -%}
<nav class="bs-docs-sidebar hidden-print hidden-xs hidden-sm affix" id="affix">
<!-- <p><a class="back-to-top" href="#top">返回顶部</a><p> -->
</nav>
</div>
</div>
{% comment -%}Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.{% endcomment -%}
<div class="subnav navbar navbar-default">
<div class="container hide-when-search" id="breadcrumb">
<ul class="breadcrumb">
<li>{{_tocTitle}}</li>
</ul>
</div>
</div>
{% comment -%}Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.{% endcomment -%}
<footer>
<div class="grad-bottom"></div>
<div class="footer">
<div class="container">
<span class="pull-right">
<a href="#top">返回顶部</a>
</span>
{%- if _appFooter -%}
{{_appFooter}}
{%- else -%}
<span>Copyright © 2015-2017 Microsoft<br>Generated by <strong>DocFX</strong></span>
{%- endif -%}
</div>
</div>
</footer>
<script>
(function(){
var bp = document.createElement('script');
var curProtocol = window.location.protocol.split(':')[0];
if (curProtocol === 'https'){
bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
}
else{
bp.src = 'http://push.zhanzhang.baidu.com/push.js';
}
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(bp, s);
})();
</script>
{% comment -%}Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.{% endcomment -%}
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
{%- if title and _appTitle -%}
<title>{{title}} | {{appTitle}}</title>
<meta name="title" content="{{title}} | {{appTitle}}">
{%- else -%}
{%- if title or _appTitle -%}
<title>{{title}}{{appTitle}}</title>
<meta name="title" content="{{title}}{{appTitle}}">
{%- endif -%}
{%- endif -%}
<meta name="viewport" content="width=device-width">
<meta name="generator" content="docfx {{_docfxVersion}}">
{%- if _description -%}
<meta name="description" content="{{_description}}">
{%- endif -%}
{%- if _appFaviconPath -%}
<link rel="shortcut icon" href="{{_rel}}{{_appFaviconPath}}">
{%- else -%}
<link rel="shortcut icon" href="{{_rel}}favicon.ico">
{%- endif -%}
<link rel="stylesheet" href="{{_rel}}styles/docfx.vendor.css">
<link rel="stylesheet" href="{{_rel}}styles/docfx.css">
<link rel="stylesheet" href="{{_rel}}styles/main.css">
<meta property="docfx:navrel" content="{{_navRel}}">
<meta property="docfx:tocrel" content="{{_tocRel}}">
{%- if _enableSearch -%}
<meta property="docfx:rel" content="{{_rel}}">
{%- endif -%}
{%- if _enableNewTab -%}
<meta property="docfx:newtab" content="true">
{%- endif -%}
</head>
{% comment -%}Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.{% endcomment -%}
<a class="navbar-brand" href="{{_rel}}index.html">
{%- if _appLogoPath -%}
<img id="logo" class="svg" src="{{_rel}}{{_appLogoPath}}" alt="{{_appName}}" >
{%- else -%}
<img id="logo" class="svg" src="{{_rel}}logo.svg" alt="{{_appName}}" >
{%- endif -%}
</a>
{% comment -%}Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.{% endcomment -%}
<nav id="autocollapse" class="navbar navbar-inverse ng-scope" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar">
<span class="sr-only">切换导航</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
{% include partials/logo -%}
</div>
<div class="collapse navbar-collapse" id="navbar">
<form class="navbar-form navbar-right" role="search" id="search">
<div class="form-group">
<input type="text" class="form-control" id="search-query" placeholder="搜索" autocomplete="off">
</div>
</form>
</div>
</div>
</nav>
{% comment -%}Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.{% endcomment -%}
<script type="text/javascript" src="{{_rel}}styles/docfx.vendor.js"></script>
<script type="text/javascript" src="{{_rel}}styles/docfx.js"></script>
<script type="text/javascript" src="{{_rel}}styles/main.js"></script>
{% comment -%}Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.{% endcomment -%}
<div class="sidenav hide-when-search">
<a class="btn toc-toggle collapse" data-toggle="collapse" href="#sidetoggle" aria-expanded="false" aria-controls="sidetoggle">显示 / 隐藏目录</a>
<div class="sidetoggle collapse" id="sidetoggle">
<div id="sidetoc"></div>
</div>
</div>
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<div class="hidden-sm col-md-2" role="complementary">
<div class="sideaffix">
{{^_disableContribution}}
<div class="contribution">
<ul class="nav">
{{#docurl}}
<li>
<a href="{{docurl}}" class="contribution-link">{{__global.improveThisDoc}}</a>
</li>
{{/docurl}}
{{#sourceurl}}
<li>
<a href="{{sourceurl}}" class="contribution-link">{{__global.viewSource}}</a>
</li>
{{/sourceurl}}
<li>
<div style="margin-left: 10px; margin-top: 3px; margin-bottom: 3px;" class="bdsharebuttonbox">
<a href="#" class="bds_more" data-cmd="more"></a>
<a href="#" class="bds_mshare" data-cmd="mshare" title="分享到一键分享"></a>
<a href="#" class="bds_weixin" data-cmd="weixin" title="分享到微信"></a>
<a href="#" class="bds_tsina" data-cmd="tsina" title="分享到新浪微博"></a>
<a href="#" class="bds_sqq" data-cmd="sqq" title="分享到QQ好友"></a>
<a href="#" class="bds_copy" data-cmd="copy" title="分享到复制网址"></a>
<a href="#" class="bds_print" data-cmd="print" title="分享到打印"></a>
</div>
</li>
</ul>
</div>
{{/_disableContribution}}
<nav class="bs-docs-sidebar hidden-print hidden-xs hidden-sm affix" id="affix">
<!-- <p><a class="back-to-top" href="#top">返回顶部</a><p> -->
</nav>
</div>
</div>
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<div class="subnav navbar navbar-default">
<div class="container hide-when-search" id="breadcrumb">
<ul class="breadcrumb">
<li>{{_tocTitle}}</li>
</ul>
</div>
</div>
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<h1 id="{{id}}" data-uid="{{uid}}">{{>partials/title}}</h1>
<div class="markdown level0 summary">{{{summary}}}</div>
<div class="markdown level0 conceptual">{{{conceptual}}}</div>
{{#inClass}}
<div class="inheritance">
<h5>{{__global.inheritance}}</h5>
{{#inheritance}}
<div class="level{{index}}">{{{specName.0.value}}}</div>
{{/inheritance}}
<div class="level{{level}}"><span class="xref">{{name.0.value}}</span></div>
</div>
{{/inClass}}
{{#derivedClasses}}
<div class="level{{index}}">{{{specName.0.value}}}</div>
{{/derivedClasses}}
{{#inheritedMembers.0}}
<div class="inheritedMembers">
<h5>{{__global.inheritedMembers}}</h5>
{{/inheritedMembers.0}}
{{#inheritedMembers}}
<div>
{{#definition}}
<xref uid="{{definition}}" text="{{nameWithType.0.value}}" alt="{{fullName.0.value}}"/>
{{/definition}}
{{^definition}}
<xref uid="{{uid}}" text="{{nameWithType.0.value}}" alt="{{fullName.0.value}}"/>
{{/definition}}
</div>
{{/inheritedMembers}}
{{#inheritedMembers.0}}
</div>
{{/inheritedMembers.0}}
<h6><strong>{{__global.namespace}}</strong>:{{namespace}}</h6>
<h6><strong>{{__global.assembly}}</strong>:{{assemblies.0}}.dll</h6>
<h5 id="{{id}}_syntax">{{__global.syntax}}</h5>
<div class="codewrapper">
<pre><code class="lang-{{_lang}} hljs">{{syntax.content.0.value}}</code></pre>
</div>
{{#syntax.parameters.0}}
<h5 class="parameters">{{__global.parameters}}</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{{__global.type}}</th>
<th>{{__global.name}}</th>
<th>{{__global.description}}</th>
</tr>
</thead>
<tbody>
{{/syntax.parameters.0}}
{{#syntax.parameters}}
<tr>
<td>{{{type.specName.0.value}}}</td>
<td><span class="parametername">{{{id}}}</span></td>
<td>{{{description}}}</td>
</tr>
{{/syntax.parameters}}
{{#syntax.parameters.0}}
</tbody>
</table>
{{/syntax.parameters.0}}
{{#syntax.return}}
<h5 class="returns">{{__global.returns}}</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{{__global.type}}</th>
<th>{{__global.description}}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{{type.specName.0.value}}}</td>
<td>{{{description}}}</td>
</tr>
</tbody>
</table>
{{/syntax.return}}
{{#syntax.typeParameters.0}}
<h5 class="typeParameters">{{__global.typeParameters}}</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{{__global.name}}</th>
<th>{{__global.description}}</th>
</tr>
</thead>
<tbody>
{{/syntax.typeParameters.0}}
{{#syntax.typeParameters}}
<tr>
<td><span class="parametername">{{{id}}}</span></td>
<td>{{{description}}}</td>
</tr>
{{/syntax.typeParameters}}
{{#syntax.typeParameters.0}}
</tbody>
</table>
{{/syntax.typeParameters.0}}
{{#remarks}}
<h5 id="{{id}}_remarks"><strong>{{__global.remarks}}</strong></h5>
<div class="markdown level0 remarks">{{{remarks}}}</div>
{{/remarks}}
{{#example.0}}
<h5 id="{{id}}_examples"><strong>{{__global.examples}}</strong></h5>
{{/example.0}}
{{#example}}
{{{.}}}
{{/example}}
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
{{>partials/class.header}}
{{#children}}
<h3 id="{{id}}">{{>partials/classSubtitle}}</h3>
{{#children}}
{{^_disableContribution}}
{{#docurl}}
<span class="small pull-right mobile-hide">
<span class="divider">|</span>
<a href="{{docurl}}">{{__global.improveThisDoc}}</a>
</span>{{/docurl}}
{{#sourceurl}}
<span class="small pull-right mobile-hide">
<a href="{{sourceurl}}">{{__global.viewSource}}</a>
</span>{{/sourceurl}}
{{/_disableContribution}}
{{#overload}}
<a id="{{id}}" data-uid="{{uid}}"></a>
{{/overload}}
<h4 id="{{id}}" data-uid="{{uid}}">{{name.0.value}}</h4>
<div class="markdown level1 summary">{{{summary}}}</div>
<div class="markdown level1 conceptual">{{{conceptual}}}</div>
<h5 class="decalaration">{{__global.declaration}}</h5>
{{#syntax}}
<div class="codewrapper">
<pre><code class="lang-{{_lang}} hljs">{{syntax.content.0.value}}</code></pre>
</div>
{{#parameters.0}}
<h5 class="parameters">{{__global.parameters}}</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{{__global.type}}</th>
<th>{{__global.name}}</th>
<th>{{__global.description}}</th>
</tr>
</thead>
<tbody>
{{/parameters.0}}
{{#parameters}}
<tr>
<td>{{{type.specName.0.value}}}</td>
<td><span class="parametername">{{{id}}}</span></td>
<td>{{{description}}}</td>
</tr>
{{/parameters}}
{{#parameters.0}}
</tbody>
</table>
{{/parameters.0}}
{{#return}}
<h5 class="returns">{{__global.returns}}</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{{__global.type}}</th>
<th>{{__global.description}}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{{type.specName.0.value}}}</td>
<td>{{{description}}}</td>
</tr>
</tbody>
</table>
{{/return}}
{{#typeParameters.0}}
<h5 class="typeParameters">{{__global.typeParameters}}</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{{__global.name}}</th>
<th>{{__global.description}}</th>
</tr>
</thead>
<tbody>
{{/typeParameters.0}}
{{#typeParameters}}
<tr>
<td><span class="parametername">{{{id}}}</span></td>
<td>{{{description}}}</td>
</tr>
{{/typeParameters}}
{{#typeParameters.0}}
</tbody>
</table>
{{/typeParameters.0}}
{{#fieldValue}}
<h5 class="fieldValue">{{__global.fieldValue}}</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{{__global.type}}</th>
<th>{{__global.description}}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{{type.specName.0.value}}}</td>
<td>{{{description}}}</td>
</tr>
</tbody>
</table>
{{/fieldValue}}
{{#propertyValue}}
<h5 class="propertyValue">{{__global.propertyValue}}</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{{__global.type}}</th>
<th>{{__global.description}}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{{type.specName.0.value}}}</td>
<td>{{{description}}}</td>
</tr>
</tbody>
</table>
{{/propertyValue}}
{{#eventType}}
<h5 class="eventType">{{__global.eventType}}</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{{__global.type}}</th>
<th>{{__global.description}}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{{type.specName.0.value}}}</td>
<td>{{{description}}}</td>
</tr>
</tbody>
</table>
{{/eventType}}
{{/syntax}}
{{#overridden}}
<h5 class="overrides">{{__global.overrides}}</h5>
<div><xref uid="{{uid}}" altProperty="fullName" displayProperty="nameWithType"/></div>
{{/overridden}}
{{#implements.0}}
<h5 class="implements">{{__global.implements}}</h5>
{{/implements.0}}
{{#implements}}
{{#definition}}
<div><xref uid="{{definition}}" altProperty="fullName" displayProperty="nameWithType"/></div>
{{/definition}}
{{^definition}}
<div><xref uid="{{uid}}" altProperty="fullName" displayProperty="nameWithType"/></div>
{{/definition}}
{{/implements}}
{{#remarks}}
<h5 id="{{id}}_remarks">{{__global.remarks}}</h5>
<div class="markdown level1 remarks">{{{remarks}}}</div>
{{/remarks}}
{{#example.0}}
<h5 id="{{id}}_examples">{{__global.examples}}</h5>
{{/example.0}}
{{#example}}
{{{.}}}
{{/example}}
{{#exceptions.0}}
<h5 class="exceptions">{{__global.exceptions}}</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{{__global.type}}</th>
<th>{{__global.condition}}</th>
</tr>
</thead>
<tbody>
{{/exceptions.0}}
{{#exceptions}}
<tr>
<td>{{{type.specName.0.value}}}</td>
<td>{{{description}}}</td>
</tr>
{{/exceptions}}
{{#exceptions.0}}
</tbody>
</table>
{{/exceptions.0}}
{{#seealso.0}}
<h5 id="{{id}}_seealso">{{__global.seealso}}</h5>
<div class="seealso">
{{/seealso.0}}
{{#seealso}}
{{#isCref}}
<div>{{{type.specName.0.value}}}</div>
{{/isCref}}
{{^isCref}}
<div>{{{url}}}</div>
{{/isCref}}
{{/seealso}}
{{#seealso.0}}
</div>
{{/seealso.0}}
{{/children}}
{{/children}}
{{#extensionMethods.0}}
<h3 id="extensionmethods">{{__global.extensionMethods}}</h3>
{{/extensionMethods.0}}
{{#extensionMethods}}
<div>
{{#definition}}
<xref uid="{{definition}}" altProperty="fullName" displayProperty="nameWithType"/>
{{/definition}}
{{^definition}}
<xref uid="{{uid}}" altProperty="fullName" displayProperty="nameWithType"/>
{{/definition}}
</div>
{{/extensionMethods}}
{{#seealso.0}}
<h3 id="seealso">{{__global.seealso}}</h3>
<div class="seealso">
{{/seealso.0}}
{{#seealso}}
{{#isCref}}
<div>{{{type.specName.0.value}}}</div>
{{/isCref}}
{{^isCref}}
<div>{{{url}}}</div>
{{/isCref}}
{{/seealso}}
{{#seealso.0}}
</div>
{{/seealso.0}}
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
{{#inConstructor}}
{{__global.constructorsInSubtitle}}
{{/inConstructor}}
{{#inField}}
{{__global.fieldsInSubtitle}}
{{/inField}}
{{#inProperty}}
{{__global.propertiesInSubtitle}}
{{/inProperty}}
{{#inMethod}}
{{__global.methodsInSubtitle}}
{{/inMethod}}
{{#inEvent}}
{{__global.eventsInSubtitle}}
{{/inEvent}}
{{#inOperator}}
{{__global.operatorsInSubtitle}}
{{/inOperator}}
{{#inEii}}
{{__global.eiisInSubtitle}}
{{/inEii}}
\ No newline at end of file
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
{{!Add your own custom template for the content for ManagedReference here}}
\ No newline at end of file
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
{{>partials/class.header}}
{{#children}}
<h3 id="{{id}}">{{>partials/classSubtitle}}</h3>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{{__global.name}}</th>
<th>{{__global.description}}</th>
</tr>
<thead>
<tbody>
{{#children}}
<tr>
<td id="{{id}}">{{name.0.value}}</td>
<td>{{{summary}}}</td>
</tr>
{{/children}}
</tbody>
</table>
{{/children}}
{{#extensionMethods.0}}
<h3 id="extensionmethods">{{__global.extensionMethods}}</h3>
{{/extensionMethods.0}}
{{#extensionMethods}}
<div>
{{#definition}}
<xref uid="{{definition}}" fullName="{{fullName.0.value}}" name="{{nameWithType.0.value}}"/>
{{/definition}}
{{^definition}}
<xref uid="{{uid}}" fullName="{{fullName.0.value}}" name="{{nameWithType.0.value}}"/>
{{/definition}}
</div>
{{/extensionMethods}}
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<footer>
<div class="grad-bottom"></div>
<div class="footer">
<div class="container">
<span class="pull-right">
<a href="#top">返回顶部</a>
</span>
{{{_appFooter}}}
{{^_appFooter}}<span>Copyright © 2015-2017 Microsoft<br>Generated by <strong>DocFX</strong></span>{{/_appFooter}}
</div>
</div>
</footer>
<script>
(function(){
var bp = document.createElement('script');
var curProtocol = window.location.protocol.split(':')[0];
if (curProtocol === 'https'){
bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
}
else{
bp.src = 'http://push.zhanzhang.baidu.com/push.js';
}
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(bp, s);
})();
</script>
\ No newline at end of file
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}</title>
<meta name="viewport" content="width=device-width">
<meta name="title" content="{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}">
<meta name="generator" content="docfx {{_docfxVersion}}">
{{#_description}}<meta name="description" content="{{_description}}">{{/_description}}
<link rel="shortcut icon" href="{{_rel}}{{{_appFaviconPath}}}{{^_appFaviconPath}}favicon.ico{{/_appFaviconPath}}">
<link rel="stylesheet" href="{{_rel}}styles/docfx.vendor.css">
<link rel="stylesheet" href="{{_rel}}styles/docfx.css">
<link rel="stylesheet" href="{{_rel}}styles/main.css">
<meta property="docfx:navrel" content="{{_navRel}}">
<meta property="docfx:tocrel" content="{{_tocRel}}">
{{#_enableSearch}}<meta property="docfx:rel" content="{{_rel}}">{{/_enableSearch}}
{{#_enableNewTab}}<meta property="docfx:newtab" content="true">{{/_enableNewTab}}
</head>
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<ul class="nav level{{level}}">
{{#items}}
<li>
{{^leaf}}
<span class="expand-stub"></span>
{{/leaf}}
{{#topicHref}}
<a href="{{topicHref}}" name="{{tocHref}}" title="{{name}}">{{name}}</a>
{{/topicHref}}
{{^topicHref}}
<a>{{{name}}}</a>
{{/topicHref}}
{{^leaf}}
{{>partials/li}}
{{/leaf}}
</li>
{{/items}}
</ul>
\ No newline at end of file
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<a class="navbar-brand" href="{{_rel}}index.html">
<img id="logo" class="svg" src="{{_rel}}{{{_appLogoPath}}}{{^_appLogoPath}}logo.svg{{/_appLogoPath}}" alt="{{_appName}}" >
</a>
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<h1 id="{{id}}" data-uid="{{uid}}">{{>partials/title}}</h1>
<div class="markdown level0 summary">{{{summary}}}</div>
<div class="markdown level0 conceptual">{{{conceptual}}}</div>
<div class="markdown level0 remarks">{{{remarks}}}</div>
{{#children}}
<h3 id="{{id}}">{{>partials/namespaceSubtitle}}</h3>
{{#children}}
<h4><xref uid="{{uid}}" altProperty="fullName" displayProperty="name"/></h4>
<section>{{{summary}}}</section>
{{/children}}
{{/children}}
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
{{#inClass}}
{{__global.classesInSubtitle}}
{{/inClass}}
{{#inStruct}}
{{__global.structsInSubtitle}}
{{/inStruct}}
{{#inInterface}}
{{__global.interfacesInSubtitle}}
{{/inInterface}}
{{#inEnum}}
{{__global.enumsInSubtitle}}
{{/inEnum}}
{{#inDelegate}}
{{__global.delegatesInSubtitle}}
{{/inDelegate}}
\ No newline at end of file
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<nav id="autocollapse" class="navbar navbar-inverse ng-scope" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar">
<span class="sr-only">切换导航</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
{{>partials/logo}}
</div>
<div class="collapse navbar-collapse" id="navbar">
<form class="navbar-form navbar-right" role="search" id="search">
<div class="form-group">
<input type="text" class="form-control" id="search-query" placeholder="搜索" autocomplete="off">
</div>
</form>
</div>
</div>
</nav>
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
{{^_disableContribution}}
{{#docurl}}
<span class="small pull-right mobile-hide">
<span class="divider">|</span>
<a href="{{docurl}}">Improve this Doc</a>
</span>{{/docurl}}
{{#sourceurl}}
<span class="small pull-right mobile-hide">
<a href="{{sourceurl}}">View Source</a>
</span>{{/sourceurl}}
{{/_disableContribution}}
<h3 id="{{htmlId}}" data-uid="{{uid}}" class="text-capitalize">{{operationId}}</h3>
{{#summary}}
<div class="markdown level1 summary">{{{summary}}}</div>
{{/summary}}
{{#description}}
<div class="markdown level1 description">{{{description}}}</div>
{{/description}}
{{#conceptual}}
<div class="markdown level1 conceptual">{{{conceptual}}}</div>
{{/conceptual}}
<h5>Request</h5>
<div class="codewrapper">
<pre><code class="lang-restApi hljs">{{operation}} {{path}}</code></pre>
</div>
{{#parameters.0}}
<h5>Parameters</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Value</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
{{/parameters.0}}
{{#parameters}}
<tr>
<td><span class="parametername">{{#required}}*{{/required}}{{name}}</span></td>
<td>{{type}}</td>
<td>{{default}}</td>
<td>{{{description}}}</td>
</tr>
{{/parameters}}
{{#parameters.0}}
</tbody>
</table>
{{/parameters.0}}
{{#responses.0}}
<div class="responses">
<h5>Responses</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>Status Code</th>
<th>Description</th>
<th>Samples</th>
</tr>
</thead>
<tbody>
{{/responses.0}}
{{#responses}}
<tr>
<td><span class="status">{{statusCode}}</span></td>
<td>{{{description}}}</td>
<td class="sample-response">
{{#examples}}
<div class="mime-type">
<i>Mime type: </i><span class="mime">{{mimeType}}</span>
</div>
<pre class="response-content"><code class="lang-js json hljs">{{content}}</code></pre>
{{/examples}}
</td>
</tr>
{{/responses}}
{{#responses.0}}
</tbody>
</table>
</div>
{{/responses.0}}
{{#footer}}
<div class="markdown level1 api-footer">{{{footer}}}</div>
{{/footer}}
\ No newline at end of file
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<h1 id="{{htmlId}}" data-uid="{{uid}}" class="text-capitalize">{{name}}</h1>
{{#summary}}
<div class="markdown level0 summary">{{{summary}}}</div>
{{/summary}}
{{#description}}
<div class="markdown level0 description">{{{description}}}</div>
{{/description}}
{{#conceptual}}
<div class="markdown level0 conceptual">{{{conceptual}}}</div>
{{/conceptual}}
{{#tags}}
<h2 id="{{htmlId}}">{{name}}</h2>
{{#description}}
<div class="markdown level0 description">{{{description}}}</div>
{{/description}}
{{#conceptual}}
<div class="markdown level0 conceptual">{{{conceptual}}}</div>
{{/conceptual}}
{{#children}}
{{>partials/rest.child}}
{{/children}}
{{/tags}}
{{!if some children are not tagged while other children are tagged, add default title}}
{{#children.0}}
{{#isTagLayout}}
<h2 id="other-apis">Other APIs</h2>
{{/isTagLayout}}
{{/children.0}}
{{#children}}
{{>partials/rest.child}}
{{/children}}
{{#footer}}
<div class="markdown level0 api-footer">{{{footer}}}</div>
{{/footer}}
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<script type="text/javascript" src="{{_rel}}styles/docfx.vendor.js"></script>
<script type="text/javascript" src="{{_rel}}styles/docfx.js"></script>
<script type="text/javascript" src="{{_rel}}styles/main.js"></script>
<script type="text/javascript">window._bd_share_config = { "common": { "bdSnsKey": {}, "bdText": "", "bdMini": "2", "bdMiniList": false, "bdPic": "", "bdStyle": "0", "bdSize": "16" }, "share": {} }; with (document) 0[(getElementsByTagName('head')[0] || body).appendChild(createElement('script')).src = 'http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion=' + ~(-new Date() / 36e5)];</script>
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<div id="search-results">
<div class="search-list"></div>
<div class="sr-items"></div>
<ul id="pagination"></ul>
</div>
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
{{#isNamespace}}
Namespace {{name.0.value}}
{{/isNamespace}}
{{#inClass}}
Class {{name.0.value}}
{{/inClass}}
{{#inStruct}}
Struct {{name.0.value}}
{{/inStruct}}
{{#inInterface}}
Interface {{name.0.value}}
{{/inInterface}}
{{#inEnum}}
Enum {{name.0.value}}
{{/inEnum}}
{{#inDelegate}}
Delegate {{name.0.value}}
{{/inDelegate}}
{{#inConstructor}}
Constructor {{name.0.value}}
{{/inConstructor}}
{{#inField}}
Field {{name.0.value}}
{{/inField}}
{{#inProperty}}
Property {{name.0.value}}
{{/inProperty}}
{{#inMethod}}
Method {{name.0.value}}
{{/inMethod}}
{{#inEvent}}
Event {{name.0.value}}
{{/inEvent}}
{{#inOperator}}
Operator {{name.0.value}}
{{/inOperator}}
{{#inEii}}
Explict Interface Implementation {{name.0.value}}
{{/inEii}}
\ No newline at end of file
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<div class="sidenav hide-when-search">
<a class="btn toc-toggle collapse" data-toggle="collapse" href="#sidetoggle" aria-expanded="false" aria-controls="sidetoggle">显示 / 隐藏目录</a>
<div class="sidetoggle collapse" id="sidetoggle">
<div id="sidetoc"></div>
</div>
</div>
[
"a",
"able",
"about",
"across",
"after",
"all",
"almost",
"also",
"am",
"among",
"an",
"and",
"any",
"are",
"as",
"at",
"be",
"because",
"been",
"but",
"by",
"can",
"cannot",
"could",
"dear",
"did",
"do",
"does",
"either",
"else",
"ever",
"every",
"for",
"from",
"get",
"got",
"had",
"has",
"have",
"he",
"her",
"hers",
"him",
"his",
"how",
"however",
"i",
"if",
"in",
"into",
"is",
"it",
"its",
"just",
"least",
"let",
"like",
"likely",
"may",
"me",
"might",
"most",
"must",
"my",
"neither",
"no",
"nor",
"not",
"of",
"off",
"often",
"on",
"only",
"or",
"other",
"our",
"own",
"rather",
"said",
"say",
"says",
"she",
"should",
"since",
"so",
"some",
"than",
"that",
"the",
"their",
"them",
"then",
"there",
"these",
"they",
"this",
"tis",
"to",
"too",
"twas",
"us",
"wants",
"was",
"we",
"were",
"what",
"when",
"where",
"which",
"while",
"who",
"whom",
"why",
"will",
"with",
"would",
"yet",
"you",
"your"
]
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
(function () {
importScripts('lunr.min.js');
var lunrIndex = lunr(function () {
this.pipeline.remove(lunr.stopWordFilter);
this.ref('href');
this.field('title', { boost: 50 });
this.field('keywords', { boost: 20 });
});
lunr.tokenizer.seperator = /[\s\-\.]+/;
var stopWordsRequest = new XMLHttpRequest();
stopWordsRequest.open('GET', '../search-stopwords.json');
stopWordsRequest.onload = function () {
if (this.status != 200) {
return;
}
var stopWords = JSON.parse(this.responseText);
var docfxStopWordFilter = lunr.generateStopWordFilter(stopWords);
lunr.Pipeline.registerFunction(docfxStopWordFilter, 'docfxStopWordFilter');
lunrIndex.pipeline.add(docfxStopWordFilter);
}
stopWordsRequest.send();
var searchData = {};
var searchDataRequest = new XMLHttpRequest();
searchDataRequest.open('GET', '../index.json');
searchDataRequest.onload = function () {
if (this.status != 200) {
return;
}
searchData = JSON.parse(this.responseText);
for (var prop in searchData) {
if (searchData.hasOwnProperty(prop)) {
lunrIndex.add(searchData[prop]);
}
}
postMessage({ e: 'index-ready' });
}
searchDataRequest.send();
onmessage = function (oEvent) {
var q = oEvent.data.q;
var hits = lunrIndex.search(q);
var results = [];
hits.forEach(function (hit) {
var item = searchData[hit.ref];
results.push({ 'href': item.href, 'title': item.title, 'keywords': item.keywords });
});
postMessage({ e: 'query-ready', q: q, d: results });
}
})();
// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
/**
* This method will be called at the start of exports.transform in toc.html.js
*/
exports.preTransform = function (model) {
return model;
}
/**
* This method will be called at the end of exports.transform in toc.html.js
*/
exports.postTransform = function (model) {
return model;
}
\ No newline at end of file
// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
var extension = require('./toc.extension.js')
exports.transform = function (model) {
if (extension && extension.preTransform) {
model = extension.preTransform(model);
}
transformItem(model, 1);
if (model.items && model.items.length > 0) model.leaf = false;
model.title = "目录";
model._disableToc = true;
if (extension && extension.postTransform) {
model = extension.postTransform(model);
}
return model;
function transformItem(item, level) {
// set to null incase mustache looks up
item.topicHref = item.topicHref || null;
item.tocHref = item.tocHref || null;
item.name = item.name || null;
item.level = level;
if (item.items && item.items.length > 0) {
var length = item.items.length;
for (var i = 0; i < length; i++) {
transformItem(item.items[i], level + 1);
};
} else {
item.items = [];
item.leaf = true;
}
}
}
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
{{!master(layout/_master.tmpl)}}
<div id="sidetoggle">
<div>
{{^_disableSideFilter}}
<div class="sidefilter">
<form class="toc-filter">
<span class="glyphicon glyphicon-filter filter-icon"></span>
<input type="text" id="toc_filter_input" placeholder="输入关键字过滤..." onkeypress="if(event.keyCode==13) {return false;}">
</form>
</div>
{{/_disableSideFilter}}
<div class="sidetoc">
<div class="toc" id="toc">
{{^leaf}}
{{>partials/li}}
{{/leaf}}
</div>
</div>
</div>
</div>
\ No newline at end of file
{
"classesInSubtitle": "类",
"structsInSubtitle": "结构体",
"interfacesInSubtitle": "接口",
"enumsInSubtitle": "枚举",
"delegatesInSubtitle": "委托",
"constructorsInSubtitle": "构造器",
"fieldsInSubtitle": "Fields",
"propertiesInSubtitle": "属性",
"methodsInSubtitle": "方法",
"eventsInSubtitle": "事件",
"operatorsInSubtitle": "Operators",
"eiisInSubtitle": "显式接口实现",
"improveThisDoc": "改进文档",
"viewSource": "查看代码",
"inheritance": "继承",
"inheritedMembers": "继承成员",
"namespace": "名称空间",
"assembly": "应用程序集",
"syntax": "语法",
"overrides": "Overrides",
"implements": "实现",
"remarks": "备注",
"examples": "示例",
"seealso": "参考",
"declaration": "声明",
"parameters": "参数",
"typeParameters": "泛型参数",
"type": "类型",
"name": "名称",
"description": "描述",
"returns": "Returns",
"fieldValue": "Field Value",
"propertyValue": "Property Value",
"eventType": "Event Type",
"exceptions": "异常",
"condition": "Condition",
"extensionMethods": "扩展方法",
"note": "<h5>备注</h5>",
"warning": "<h5>警告</h5>",
"tip": "<h5>提示</h5>",
"important": "<h5>重要</h5>",
"caution": "<h5>小心</h5>"
}
\ No newline at end of file
{
"build": {
"content": [
{
"files": [
"document/**.md",
"document/**/toc.yml",
"toc.yml",
"*.md"
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**"
]
}
],
"resource": [
{
"files": [
"document/images/**"
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**"
]
}
],
"overwrite": [
{
"files": [
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**"
]
}
],
"dest": "_site",
"globalMetadata": {
"_appTitle": "CAP 中文文档"
},
"postProcessors": [],
"noLangKeyword": false
}
}
\ No newline at end of file
## 1、Getting Started
### 1.1 介绍
CAP 是一个遵循 .NET Standard 标准库的C#库,用来处理分布式事务以及提供EventBus的功能,她具有轻量级,高性能,易使用等特点。
目前 CAP 使用的是 .NET Standard 1.6 的标准进行开发,目前最新预览版本已经支持 .NET Standard 2.0.
### 1.2 应用场景
CAP 的应用场景主要有以下两个:
* 1. 分布式事务中的最终一致性(异步确保)的方案。
分布式事务是在分布式系统中不可避免的一个硬性需求,而目前的分布式事务的解决方案也无外乎就那么几种,在了解 CAP 的分布式事务方案前,可以阅读以下 [这篇文章](http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency)
CAP 没有采用两阶段提交(2PC)这种事务机制,而是采用的 本地消息表+MQ 这种经典的实现方式,这种方式又叫做 异步确保。
* 2. 具有高可用性的 EventBus。
CAP 实现了 EventBus 中的发布/订阅,它具有 EventBus 的所有功能。也就是说你可以像使用 EventBus 一样来使用 CAP,另外 CAP 的 EventBus 是具有高可用性的,这是什么意思呢?
CAP 借助于本地消息表来对 EventBus 中的消息进行了持久化,这样可以保证 EventBus 发出的消息是可靠的,当消息队列出现宕机或者连接失败的情况时,消息也不会丢失。
### 1.3 Quick Start
* **引用 NuGet 包**
使用一下命令来引用CAP的NuGet包:
```
PM> Install-Package DotNetCore.CAP
```
根据使用的不同类型的消息队列,来引入不同的扩展包:
```
PM> Install-Package DotNetCore.CAP.RabbitMQ
PM> Install-Package DotNetCore.CAP.Kafka
```
根据使用的不同类型的数据库,来引入不同的扩展包:
```
PM> Install-Package DotNetCore.CAP.SqlServer
PM> Install-Package DotNetCore.CAP.MySql
```
* **启动配置**
在 ASP.NET Core 程序中,你可以在 `Startup.cs` 文件 `ConfigureServices()` 中配置 CAP 使用到的服务:
```cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>();
services.AddCap(x =>
{
// If your SqlServer is using EF for data operations, you need to add the following configuration:
// Notice: You don't need to config x.UseSqlServer(""") again!
x.UseEntityFramework<AppDbContext>();
// If you are using Dapper,you need to add the config:
x.UseSqlServer("Your ConnectionStrings");
// If your Message Queue is using RabbitMQ you need to add the config:
x.UseRabbitMQ("localhost");
// If your Message Queue is using Kafka you need to add the config:
x.UseKafka("localhost");
});
}
```
`Configure()` 中配置启动 CAP :
```
public void Configure(IApplicationBuilder app)
{
app.UseCap();
}
```
## 2、API接口
CAP 的 API 接口只有一个,就是 `ICapPublisher` 接口,你可以从 DI 容器中获取到该接口的实例进行调用。
### 2.1 发布/发送
你可以使用 `ICapPublisher` 接口中的 `Publish<T>` 或者 `PublishAsync<T>` 方法来发送消息:
```cs
public class PublishController : Controller
{
private readonly ICapPublisher _publisher;
//在构造函数中获取接口实例
public PublishController(ICapPublisher publisher)
{
_publisher = publisher;
}
[Route("~/checkAccount")]
public async Task<IActionResult> PublishMessage()
{
await _publisher.PublishAsync("xxx.services.account.check", new Person { Name = "Foo", Age = 11 });
return Ok();
}
}
```
下面是PublishAsync这个接口的签名:
**`PublishAsync<T>(string name,T object)`**
默认情况下,在调用此方法的时候 CAP 将在内部创建事务,然后将消息写入到 `Cap.Published` 这个消息表。
#### 2.1.1 事务
事务在 CAP 具有重要作用,它是保证消息可靠性的一个基石。 在发送一条消息到消息队列的过程中,如果不使用事务,我们是没有办法保证我们的业务代码在执行成功后消息已经成功的发送到了消息队列,或者是消息成功的发送到了消息队列,但是业务代码确执行失败。
这里的失败原因可能是多种多样的,比如连接异常,网络故障等等。
*只有业务代码和CAP的Publish代码必须在同一个事务中,才能够保证业务代码和消息代码同时成功或者失败。*
以下是两种使用事务进行Publish的代码:
* EntityFramework
```cs
using (var transaction = dbContext.Database.BeginTransaction())
{
await _publisher.PublishAsync("xxx.services.account.check",
new Person { Name = "Foo", Age = 11 });
// 你的业务代码。
transaction.Commit();
}
```
你的业务代码可以位于 Publish 之前或者之后,只需要保证在同一个事务。
当CAP检测到 Publish 是在EF事务区域内的时候,将使用当前的事务上下文进行消息的存储。
其中,发送的内容会序列化为Json存储到消息表中。
* Dapper
```cs
var connString = "数据库连接字符串";
using (var connection = new MySqlConnection(connString))
{
connection.Open();
using (var transaction = connection.BeginTransaction())
{
await _publisher.PublishAsync("xxx.services.bar",
new Person { Name = "Foo", Age = 11 },
connection,
transaction);
// 你的业务代码。
transaction.Commit();
}
}
```
在 Dapper 中,由于不能获取到事务上下文,所以需要用户手动的传递事务上下文到CAP中。
### 2.2 订阅/消费
**注意:消息端在方法实现的过程中需要实现幂等性。**
使用 `CapSubscribeAttribute` 来订阅 CAP 发布出去的消息。
```
[CapSubscribe("xxx.services.bar")]
public void BarMessageProcessor()
{
}
```
这里,你也可以使用多个 `CapSubscribe[""]` 来同时订阅多个不同的消息 :
```
[CapSubscribe("xxx.services.bar")]
[CapSubscribe("xxx.services.foo")]
public void BarAndFooMessageProcessor()
{
}
```
其中,`xxx.services.bar` 为订阅的消息名称,内部实现上,这个名称在不同的消息队列具有不同的代表。 在 Kafka 中,这个名称即为 Topic Name。 在RabbitMQ 中,为 RouteKey。
> RabbitMQ 中的 RouteKey 支持绑定键表达式写法,有两种主要的绑定键:
>
> \*(星号)可以代替一个单词.
>
> \# (井号) 可以代替0个或多个单词.
>
> 比如在下面这个图中(P为发送者,X为RabbitMQ中的Exchange,C为消费者,Q为队列)
>
> ![](http://images2017.cnblogs.com/blog/250417/201708/250417-20170807093230268-283915002.png)
>
> 在这个示例中,我们将发送一条关于动物描述的消息,也就是说 Name(routeKey) 字段中的内容包含 3 个单词。第一个单词是描述速度的(celerity),第二个单词是描述颜色的(colour),第三个是描述哪种动物的(species),它们组合起来类似:“<celerity>.<colour>.<species>”。
>
> 然后在使用 `CapSubscribe` 绑定的时候,Q1绑定为 `CapSubscribe["*.orange.*"]`, Q2 绑定为 `CapSubscribe["*.*.rabbit"]` 和 `[CapSubscribe["lazy.#]`。
>
> 那么,当发送一个名为 "quick.orange.rabbit" 消息的时候,这两个队列将会同时收到该消息。同样名为 `lazy.orange.elephant`的消息也会被同时收到。另外,名为 "quick.orange.fox" 的消息将仅会被发送到Q1队列,名为 "lazy.brown.fox" 的消息仅会被发送到Q2。"lazy.pink.rabbit" 仅会被发送到Q2一次,即使它被绑定了2次。"quick.brown.fox" 没有匹配到任何绑定的队列,所以它将会被丢弃。
>
> 另外一种情况,如果你违反约定,比如使用 4个单词进行组合,例如 "quick.orange.male.rabbit",那么它将匹配不到任何的队列,消息将会被丢弃。
>
> 但是,假如你的消息名为 "lazy.orange.male.rabbit",那么他们将会被发送到Q2,因为 #(井号)可以匹配 0 或者多个单词。
在 CAP 中,我们把每一个拥有 `CapSubscribe[]`标记的方法叫做**订阅者**,你可以把订阅者进行分组。
**组(Group)**,是订阅者的一个集合,每一组可以有一个或者多个消费者,但是一个订阅者只能属于某一个组。同一个组内的订阅者订阅的消息只能被消费一次。
如果你在订阅的时候没有指定组,CAP会将订阅者设置到一个默认的组 `cap.default.group`
以下是使用组进行订阅的示例:
```cs
[CapSubscribe("xxx.services.foo", Group = "moduleA")]
public void FooMessageProcessor()
{
}
```
#### 2.2.1 例外情况
这里有几种情况可能需要知道:
**① 消息发布的时候订阅方还未启动**
Kafka:
当 Kafka 中,发布的消息存储于持久化的日志文件中,所以消息不会丢失,当订阅者所在的程序启动的时候会消费掉这些消息。
RabbitMQ:
在 RabbitMQ 中,应用程序**首次启动**会创建具有持久化的 Exchange 和 Queue,CAP 会针对每一个订阅者Group会新建一个消费者队列,**由于首次启动时候订阅者未启动的所以是没有队列的,消息无法进行持久化,这个时候生产者发的消息会丢失**
针对RabbitMQ的消息丢失的问题,有两种解决方式:
i. 部署应用程序之前,在RabbitMQ中手动创建具有durable特性的Exchange和Queue,默认情况他们的名字分别是(cap.default.topic, cap.default.group)。
ii. 提前运行一遍所有实例,让Exchange和Queue初始化。
我们建议采用第 ii 种方案,因为很容易做到。
**② 消息没有任何订阅者**
如果你发送了一条个没有被任何订阅者订阅的消息,那么此消息将会被丢弃。
## 3、配置
Cap 使用 Microsoft.Extensions.DependencyInjection 进行配置的注入,你也可以依赖于 DI 从json文件中读取配置。
### 3.1 Cap Options
你可以使用如下方式来配置 CAP 中的一些配置项,例如
```cs
services.AddCap(capOptions => {
capOptions.FailedCallback = //...
});
```
`CapOptions` 提供了一下配置项:
NAME | DESCRIPTION | TYPE | DEFAULT
:---|:---|---|:---
PollingDelay | 处理消息的线程默认轮询等待时间(秒) | int | 15 秒
QueueProcessorCount | 启动队列中消息的处理器个数 | int | 2
FailedMessageWaitingInterval| 轮询失败消息的间隔(秒) | int | 180 秒
FailedCallback| 执行失败消息时的回调函数,详情见下文 | Action | NULL
CapOptions 提供了 `FailedCallback` 为处理失败的消息时的回调函数。当消息多次发送失败后,CAP会将消息状态标记为`Failed`,CAP有一个专门的处理者用来处理这种失败的消息,针对失败的消息会重新放入到队列中发送到MQ,在这之前如果`FailedCallback`具有值,那么将首先调用此回调函数来告诉客户端。
FailedCallback 的类型为 `Action<MessageType,string,string>`,第一个参数为消息类型(发送的还是接收到),第二个参数为消息的名称(name),第三个参数为消息的内容(content)。
### 3.2 RabbitMQ Options
CAP 采用的是针对 CapOptions 进行扩展来实现RabbitMQ的配置功能,所以针对 RabbitMQ 的配置用法如下:
```cs
services.AddCap(capOptions => {
capOptions.UseRabbitMQ(rabbitMQOption=>{
// rabbitmq options.
});
});
```
`RabbitMQOptions` 提供了有关RabbitMQ相关的配置:
NAME | DESCRIPTION | TYPE | DEFAULT
:---|:---|---|:---
HostName | 宿主地址 | string | localhost
UserName | 用户名 | string | guest
Password | 密码 | string | guest
VirtualHost | 虚拟主机 | string | /
Port | 端口号 | int | -1
TopicExchangeName | CAP默认Exchange名称 | string | cap.default.topic
RequestedConnectionTimeout | RabbitMQ连接超时时间 | int | 30,000 毫秒
SocketReadTimeout | RabbitMQ消息读取超时时间 | int | 30,000 毫秒
SocketWriteTimeout | RabbitMQ消息写入超时时间 | int | 30,000 毫秒
QueueMessageExpires | 队列中消息自动删除时间 | int | (10天) 毫秒
### 3.3 Kafka Options
CAP 采用的是针对 CapOptions 进行扩展来实现 Kafka 的配置功能,所以针对 Kafka 的配置用法如下:
```cs
services.AddCap(capOptions => {
capOptions.UseKafka(kafkaOption=>{
// kafka options.
// kafkaOptions.MainConfig.Add("", "");
});
});
```
`KafkaOptions` 提供了有关 Kafka 相关的配置,由于Kafka的配置比较多,所以此处使用的是提供的 MainConfig 字典来支持进行自定义配置,你可以查看这里来获取对配置项的支持信息。
[https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md](https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md)
### 3.4 SqlServer Options
如果你使用的是 EntityFramewrok,你用不到该配置项下的内容。
CAP 采用的是针对 CapOptions 进行扩展来实现 SqlServer 的配置功能,所以针对 SqlServer 的配置用法如下:
```cs
services.AddCap(capOptions => {
capOptions.UseSqlServer(sqlserverOptions => {
// sqlserverOptions.ConnectionString
});
});
```
NAME | DESCRIPTION | TYPE | DEFAULT
:---|:---|---|:---
Schema | Cap表架构 | string | Cap
ConnectionString | 数据库连接字符串 | string | null
### 3.5 MySql Options
如果你使用的是 EntityFramewrok,你用不到该配置项下的内容。
CAP 采用的是针对 CapOptions 进行扩展来实现 MySql 的配置功能,所以针对 MySql 的配置用法如下:
```cs
services.AddCap(capOptions => {
capOptions.UseMySql(mysqlOptions => {
// mysqlOptions.ConnectionString
});
});
```
NAME | DESCRIPTION | TYPE | DEFAULT
:---|:---|---|:---
TableNamePrefix | Cap表名前缀 | string | cap
ConnectionString | 数据库连接字符串 | string | null
\ No newline at end of file
## 4、设计原理
### 4.1 动机
随着微服务架构的流行,越来越多的人在尝试使用微服务来架构他们的系统,而在这其中我们会遇到例如分布式事务的问题,为了解决这些问题,我没有发现简单并且易于使用的解决方案,所以我决定来打造这样一个库来解决这个问题。
最初 CAP 是为了解决分布式系统中的事务问题,她采用的是 异步确保 这种机制实现了分布式事务的最终一致性,更多这方面的信息可以查看第6节。
现在 CAP 除了解决分布式事务的问题外,她另外一个重要的功能就是作为 EventBus 来使用,她具有 EventBus 的所有功能,并且提供了更加简化的方式来处理EventBus中的发布/订阅。
### 4.2 持久化
CAP 依靠本地数据库实现消息的持久化,CAP 使用这种方式来应对一切环境或者网络异常导致消息丢失的情况,消息的可靠性是分布式事务的基石,所以在任何情况下消息都不能丢失。
对于消息的持久化分为两种:
**① 消息进入消息队列之前的持久化**
在消息进入到消息队列之前,CAP使用本地数据库表对消息进行持久化,这样可以保证当消息队列出现异常或者网络错误时候消息是没有丢失的。
为了保证这种机制的可靠性,CAP使用和业务代码相同的数据库事务来保证业务操作和CAP的消息在持久化的过程中是强一致的。也就是说在进行消息持久化的过程中,任何一方发生异常情况数据库都会进行回滚操作。
**② 消息进入到消息队列之后的持久化**
消息进入到消息队列之后,CAP会启动消息队列的持久化功能,我们需要说明一下在 RabbitMQ 和 Kafka 中CAP的消息是如何持久化的。
针对于 RabbitMQ 中的消息持久化,CAP 使用的是具有消息持久化功能的消费者队列,但是这里面可能有例外情况,参加 2.2.1 章节。
由于 Kafka 天生设计的就是使用文件进行的消息持久化,在所以在消息进入到Kafka之后,Kafka会保证消息能够正确被持久化而不丢失。
### 4.3 通讯数据流
CAP 中消息的流转过程大致如下:
![](http://images2017.cnblogs.com/blog/250417/201708/250417-20170803174645928-1813351415.png)
> “ P ” 代表消息发送者(生产者)。 “ C ” 代表消息消费者(订阅者)。
### 4.4 一致性
CAP 采用最终一致性作为的一致性方案,此方案是遵循 CAP 理论,以下是CAP理论的描述。
C(一致性)一致性是指数据的原子性,在经典的数据库中通过事务来保障,事务完成时,无论成功或回滚,数据都会处于一致的状态,在分布式环境下,一致性是指多个节点数据是否一致;
A(可用性)服务一直保持可用的状态,当用户发出一个请求,服务能在一定的时间内返回结果;
P(分区容忍性)在分布式应用中,可能因为一些分布式的原因导致系统无法运转,好的分区容忍性,使应用虽然是一个分布式系统,但是好像一个可以正常运转的整体
根据 [“CAP”分布式理论](https://en.wikipedia.org/wiki/CAP_theorem), 在一个分布式系统中,我们往往为了可用性和分区容错性,忍痛放弃强一致支持,转而追求最终一致性。大部分业务场景下,我们是可以接受短暂的不一致的。
第 6 节将对此做进一步介绍。
\ No newline at end of file
## 5、实现
CAP 封装了在 ASP.NET Core 中的使用依赖注入来获取 Publisher (`ICapPublisher`)的接口。而启动方式类似于 “中间件” 的形式,通过在 Startup.cs 配置 `ConfigureServices``Configure` 进行启动。
### 5.1 消息表
当系统引入CAP之后并首次启动后,CAP会在客户端生成 3 个表,分别是 Cap.Published, Cap.Received, Cap.Queue。注意表名可能在不同的数据库具有不同的大小写区分,如果你在运行项目的时候没有显式的指定数据库生成架构(SQL Server)或者表名前缀(MySql)的话,默认情况下就是以上3个名字。
**Cap.Published**:这个表主要是用来存储 CAP 发送到MQ(Message Queue)的客户端消息,也就是说你使用 `ICapPublisher` 接口 Publish 的消息内容。
**Cap.Received**:这个表主要是用来存储 CAP 接收到 MQ(Message Queue) 的客户端订阅的消息,也就是使用 `CapSubscribe[]` 订阅的那些消息。
**Cap.Queue**: 这个表主要是CAP内部用来处理发送和接收消息的一个临时表,通常情况下,如果系统不出现问题,这个表将是空的。
`Published``Received` 表具有 StatusName 字段,这个字段用来标识当前消息的状态。目前共有 Scheduled,Enqueued,Processing,Successed,Failed 等几个状态。CAP 在处理消息的过程中会依次从 Scheduled 到 Successed 来改变这些消息状态的值。如果是状态值为 Successed,代表该消息已经成功的发送到了 MQ 中。如果为 Failed 则代表消息发送失败,消息发送失败后 CAP 会对消息进行重试,直到成功。
**关于数据清理**: CAP 默认情况下会每隔一个小时将消息表的数据进行清理删除,避免数据量过多导致性能的降低。清理规则为 ExpiresAt 不为空并且小于当前时间的数据。
### 5.2 消息格式
CAP 采用 JSON 格式进行消息传输,以下是消息的对象模型:
NAME | DESCRIPTION | TYPE
:---|:---|:---
Id | 消息编号 | int
Name | 消息名称 | string
Content | 内容 | string
Group | 所属消费组 | string
Added | 创建时间 | DateTime
ExpiresAt | 过期时间 | DateTime
Retries | 重试次数 | int
StatusName | 状态 | string
>对于 Cap.Received 中的消息,会多一个 `Group` 字段来标记所属的消费者组。
### 5.3 EventBus
EventBus 采用 发布-订阅 风格进行组件之间的通讯,它不需要显式在组件中进行注册。
![](http://images2017.cnblogs.com/blog/250417/201708/250417-20170804153901240-1774287236.png)
上图是EventBus的一个Event的流程,关于 EventBus 的更多信息就不在这里介绍了...
在 CAP 中,为什么说 CAP 实现了 EventBus 中的全部特性,因为 EventBus 具有的两个大功能就是发布和订阅, 在 CAP 中 使用了另外一种优雅的方式来实现的,另外一个 CAP 提供的强大功能就是消息的持久化,以及在任何异常情况下消息的可靠性,这是EventBus不具有的功能。
![](https://camo.githubusercontent.com/452505edb71d41f2c1bd18907275b76291621e46/687474703a2f2f696d61676573323031352e636e626c6f67732e636f6d2f626c6f672f3235303431372f3230313730372f3235303431372d32303137303730353137353832373132382d313230333239313436392e706e67)
CAP 里面发送一个消息可以看做是一个 “Event”,一个使用了CAP的ASP.NET Core 应用程序既可以进行发送也可以进行订阅接收。
### 5.4 重试
重试在实现分布式事务中具有重要作用,CAP 中会针对发送失败或者执行失败的消息进行重试。在整个 CAP 的设计过程中有以下几处采用的重试策略。
**① 消息发送重试**
在消息发送过程中,当出现 Broker 宕机或者连接失败的情况亦或者出现异常的情况下,这个时候 CAP 会对发送的重试,重试策略为默认 **15** 次失败重试,当15次过后仍然失败时,CAP会将此消息状态标记为失败。
**② 消息消费重试**
当 Consumer 接收到消息时,会执行消费者方法,在执行消费者方法出现异常时,会进行重试。这个重试策略和 ① 是相同的。
**③ 失败消息重试**
CAP 会定期针对 ① 和 ② 中状态为“失败的”消息进行重试,CAP会对他们进行重新“入队(Enqueue)”,入队时会将消息中的重试次数标记为0,状态置为 Enqueued。
\ No newline at end of file
## 6、分布式事务
针对于分布式事务的处理,CAP 采用的是“异步确保”这种方案。
### 6.1 异步确保
异步确保这种方案又叫做本地消息表,这是一种经典的方案,方案最初来源于 eBay,参考资料见段末链接。这种方案目前也是企业中使用最多的方案之一。
相对于 TCC 或者 2PC/3PC 来说,这个方案对于分布式事务来说是最简单的,而且它是去中心化的。在TCC 或者 2PC 的方案中,必须具有事务协调器来处理每个不同服务之间的状态,而此种方案不需要事务协调器。
另外 2PC/TCC 这种方案如果服务依赖过多,会带来管理复杂性增加和稳定性风险增大的问题。试想如果我们强依赖 10 个服务,9 个都执行成功了,最后一个执行失败了,那么是不是前面 9 个都要回滚掉?这个成本还是非常高的。
但是,并不是说 2PC 或者 TCC 这种方案不好,因为每一种方案都有其相对优势的使用场景和优缺点,这里就不做过多介绍了。
> 中文:[http://www.cnblogs.com/savorboard/p/base-an-acid-alternative.html](http://www.cnblogs.com/savorboard/p/base-an-acid-alternative.html)
> 英文:[http://queue.acm.org/detail.cfm?id=1394128](http://queue.acm.org/detail.cfm?id=1394128)
## 7、FAQ
暂无
- name: Getting Started
href: 1.Getting-Started.md
- name: API 接口
href: 2.API接口.md
- name: 配置
href: 3.配置.md
- name: 实现
href: 5.实现.md
- name: 分布式事务
href: 6.分布式事务.md
- name: FAQ
href: 7.FAQ.md
\ No newline at end of file
- name: 首页
href: Index.md
topicHref: Index.md
- name: 使用手册
href: document/
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment