Merge branch 'dev-hangfire'

# Conflicts:
#	Bootstrap.Admin/Bootstrap.Admin.csproj
#	Bootstrap.Admin/Startup.cs
#	Bootstrap.Client.DataAccess/Bootstrap.Client.DataAccess.csproj
#	Bootstrap.Client/Bootstrap.Client.csproj
This commit is contained in:
Argo Zhang 2019-07-08 08:49:41 +08:00
commit 6c90b13ba5
No known key found for this signature in database
GPG Key ID: 152E398953DDF19F
21 changed files with 636 additions and 42 deletions

View File

@ -14,6 +14,7 @@
<ItemGroup>
<PackageReference Include="Bootstrap.Security.Mvc" Version="2.2.10" />
<PackageReference Include="Longbow.Configuration" Version="2.2.4" />
<PackageReference Include="Longbow.Tasks" Version="1.5.0" />
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="3.1.2" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.7.10" />

View File

@ -114,7 +114,7 @@ namespace Bootstrap.Admin.Controllers
///
/// </summary>
/// <returns></returns>
public ActionResult Tasks() => View(new NavigatorBarModel(this));
public ActionResult Tasks() => View(new TaskModel(this));
/// <summary>
///

View File

@ -1,24 +1,66 @@
using Bootstrap.DataAccess;
using Longbow.Tasks;
using Longbow.Web.SignalR;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Bootstrap.Admin.Controllers.Api
{
/// <summary>
///
///
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class TasksController : ControllerBase
{
/// <summary>
///
///
/// </summary>
/// <returns></returns>
[HttpGet]
public IEnumerable<Task> Get()
public IEnumerable<object> Get()
{
return TaskHelper.Retrieves();
TaskServicesManager.GetOrAdd("测试任务", token => Task.Delay(1000), TriggerBuilder.WithCronExpression("*/5 * * * * *"));
return TaskServicesManager.ToList().Select(s => new { s.Name, s.Enabled, s.Status, s.LastRuntime, s.CreatedTime, s.NextRuntime, Triggers = s.Triggers.Count, s.LastRunResult, TriggerExpression = s.Triggers.FirstOrDefault().ToString() });
}
/// <summary>
///
/// </summary>
/// <returns></returns>
[HttpPost]
public bool Post()
{
// UNDONE: 待完善
return true;
}
/// <summary>
///
/// </summary>
/// <param name="name"></param>
/// <param name="hub"></param>
/// <returns></returns>
[HttpPut]
public bool Put([FromQuery]string name, [FromServices]IHubContext<SignalRHub> hub)
{
var sche = TaskServicesManager.GetOrAdd(name);
sche.Triggers[0].RegisterPulseCallback(async t =>
{
var success = t.Cancelled ? "Cancelled" : "Success";
var result = $"{t.Scheduler.LastRuntime.Value.DateTime}: Trigger({t.GetType().Name}) Run({success}) NextRuntime: {t.NextRuntime.Value.DateTime} Elapsed: {t.LastRunElapsedTime.Seconds}s";
await SignalRManager.SendTaskLog(hub.Clients.All, result);
});
return true;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
[HttpDelete]
public bool Delete() => true;
}
}
}

View File

@ -0,0 +1,25 @@
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace Bootstrap.Admin.Models
{
/// <summary>
///
/// </summary>
public class TaskModel : NavigatorBarModel
{
/// <summary>
///
/// </summary>
/// <param name="controller"></param>
public TaskModel(ControllerBase controller) : base(controller)
{
Tasks = new string[] { "测试任务" };
}
/// <summary>
/// 获得 系统配置的所有任务
/// </summary>
public IEnumerable<string> Tasks { get; }
}
}

View File

@ -16,7 +16,16 @@ namespace Bootstrap.Admin
/// <param name="client"></param>
/// <param name="args"></param>
/// <returns></returns>
public static async System.Threading.Tasks.Task Send(IClientProxy client, MessageBody args) => await client.SendAsync("rev", args);
public static System.Threading.Tasks.Task Send(IClientProxy client, MessageBody args) => client.SendAsync("rev", args);
/// <summary>
///
/// </summary>
/// <param name="client"></param>
/// <param name="args"></param>
/// <returns></returns>
public static System.Threading.Tasks.Task SendTaskLog(IClientProxy client, string args) => client.SendAsync("taskRev", args);
/// <summary>
///
/// </summary>

View File

@ -63,6 +63,7 @@ namespace Bootstrap.Admin
services.AddBootstrapAdminAuthentication();
services.AddSwagger();
services.AddButtonAuthorization();
services.AddTaskServices();
services.AddMvc(options =>
{
options.Filters.Add<BootstrapAdminAuthorizeFilter>();

View File

@ -41,7 +41,7 @@
</div>
</form>
}
@section body {
@section cardbody {
<div class="alert alert-danger" role="alert" asp-condition="@Model.IsDemo">
<span>演示系统禁止修改系统使用字典配置项</span>
</div>

View File

@ -90,7 +90,7 @@
@section gear {
<a id="tb_assignRole" href="#" title="分配角色" asp-auth="assignRole"><i class="fa fa-sitemap"></i></a>
}
@section body {
@section cardbody {
<div class="alert alert-danger" role="alert" asp-condition="@Model.IsDemo">
<span>演示系统禁止修改系统菜单,可修改外部菜单</span>
</div>

View File

@ -58,7 +58,7 @@
<a id="tb_assignMenu" href="#" title="分配菜单" asp-auth="assignMenu"><i class="fa fa-dashboard"></i></a>
<a id="tb_assignApp" href="#" title="分配应用" asp-auth="assignApp"><i class="fa fa-cubes"></i></a>
}
@section body {
@section cardbody {
<div class="alert alert-danger" role="alert" asp-condition="@Model.IsDemo">
<span>演示系统禁止修改内置角色Administrators、Default</span>
</div>

View File

@ -1,18 +1,99 @@
@model NavigatorBarModel
@model TaskModel
@{
ViewBag.Title = "任务管理";
Layout = "_Default";
}
@section css {
<link href="~/lib/longbow-select/longbow-select.css" rel="stylesheet" />
<link href="~/css/tasks.css" rel="stylesheet" asp-append-version="true" />
}
@section javascript {
<script src="~/lib/longbow-select/longbow-select.js"></script>
<script src="~/js/tasks.js" asp-append-version="true"></script>
}
<div class="card">
<div class="card-header">任务消息<span class="pull-right"><a id="refreshTask" href="javascript:;" class="fa fa-refresh"></a></span></div>
<div class="card-body">
<div class="tasks-widget">
<ul id="list-task" class="task-list ui-sortable"></ul>
@section tableButtons {
<button class='info btn btn-sm btn-info' asp-auth="info"><i class='fa fa-info'></i><span>日志</span></button>
}
@section cardbody {
<div class="alert alert-danger" role="alert" asp-condition="@Model.IsDemo">
<span>演示系统禁止修改定时后台任务</span>
</div>
}
@section query {
<div>
<div class="tip">
<p class="font-weight-bold">后台任务说明:</p>
<p>1. 默认任务 (立即执行,仅执行一次)</p>
<p>
<code>
TaskServicesManager.GetOrAdd("简单任务", token => Task.Delay(1000));
</code>
</p>
<p>2. 周期性任务 (1 分钟后间隔 5 秒执行2次任务)</p>
<p>
<code>
var trigger = TriggerBuilder.Default.WithInterval(TimeSpan.FromSeconds(5)).WithRepeatCount(2).WithStartTime(DateTimeOffset.Now.AddMinutes(1)).Build();
TaskServicesManager.GetOrAdd("测试任务", token => Task.Delay(1000), trigger);
</code>
</p>
<p>3. Cron表达式任务 (间隔 5 秒循环执行任务)</p>
<p>
<code>
TaskServicesManager.GetOrAdd("Cron表达式任务", token => Task.Delay(1000), TriggerBuilder.WithCronExpression("*/5 * * * * *"));
</code>
</p>
</div>
</div>
</div>
}
@section modal {
<div class="modal-header">
<h5 class="modal-title" id="myModalLabel">任务编辑窗口</h5>
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
</div>
<div class="modal-body">
<form class="form-inline">
<div class="row">
<input type="hidden" id="taskID" />
<div class="form-group col-12">
<label class="control-label" for="taskName">任务名称</label>
<input type="text" class="form-control flex-sm-fill" id="taskName" placeholder="不可为空50字以内" maxlength="50" data-valid="true" />
</div>
<div class="form-group col-12">
<label class="control-label" for="taskCron">Cron表达式</label>
<input type="text" class="form-control flex-sm-fill" id="taskCron" placeholder="不可为空2000字以内" maxlength="2000" data-valid="true" />
</div>
<div class="form-group col-12">
<label class="control-label" for="taskContent">内置任务</label>
<select id="taskList" data-toggle="lgbSelect">
@foreach (var name in Model.Tasks)
{
<option value="@name">@name</option>
}
</select>
</div>
</div>
</form>
</div>
}
@section customModal {
<div class="modal fade" id="dialogLog" tabindex="-1" role="dialog" data-backdrop="static" aria-labelledby="taskModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="taskModalLabel">任务日志窗口</h5>
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
</div>
<div class="modal-body" style="background-color: #174482; color: #fff;">
<div id="taskMsg" class="form-group" style="height: 500px; overflow: auto;">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">
<i class="fa fa-times"></i>
<span>关闭</span>
</button>
</div>
</div>
</div>
</div>
}

View File

@ -38,7 +38,7 @@
@section tableButtons {
<button class="reset btn btn-warning" asp-auth="resetPassword"><i class="fa fa-remove"></i><span>重置</span></button>
}
@section body {
@section cardbody {
<div class="alert alert-danger" role="alert" asp-condition="@Model.IsDemo">
<span>演示系统禁止修改Admin账户</span>
</div>

View File

@ -80,7 +80,7 @@
查询结果
</div>
<div class="card-body">
@await RenderSectionAsync("body", false)
@await RenderSectionAsync("cardbody", false)
<table></table>
</div>
</div>
@ -90,4 +90,5 @@
<button class='del btn btn-sm btn-danger' asp-auth="del"><i class='fa fa-remove'></i><span>删除</span></button>
@RenderSection("tableButtons", false)
</div>
</div>
</div>
@await RenderSectionAsync("body", false)

View File

@ -536,3 +536,11 @@ input.pending {
.alert-danger {
border-left: solid 4px #f78792;
}
.tip {
padding: 0.5rem 1rem;
background-color: #ecf8ff;
border-radius: 4px;
border-left: 5px solid #50bfff;
margin-bottom: 1rem;
}

View File

@ -1,21 +1,82 @@
$(function () {
var htmlTask = '<li class="list-primary"><i class="fa fa-ellipsis-v"></i><div class="task-title notifi"><span class="task-title-sp">{0}</span><span class="task-value">{1}</span><span class="task-time">{2}</span><div class="pull-right hidden-phone"><button class="btn btn-success btn-xs fa fa-check" data-id="{3}" data-result="1"></button><button class="btn btn-danger btn-xs fa fa-remove" data-id="{3}" data-result="0" data-placement="left" data-original-title="拒绝授权"></button></div></div></li>';
$('#refreshTask').on('click', function () {
var that = $(this);
that.toggleClass('fa-spin');
$.bc({
url: Tasks.url,
autoFooter: true,
callback: function (result) {
if (result) {
var content = result.map(function (task) {
return $.format(htmlTask, task.TaskName, task.UserName, task.AssignTime, task.Id);
}).join('');
$('#list-task').html(content);
}
that.toggleClass('fa-spin');
var $taskMsg = $('#taskMsg');
var $taskLogModelTitle = $('#taskModalLabel');
var stateFormatter = function (value) {
var template = "<button class='btn btn-sm btn-{0}'><i class='fa fa-{1}'></i><span>{2}<span></button>";
var content = "";
if (value === "0") {
content = $.format(template, 'info', 'fa', '未开始');
}
else if (value === "1") {
content = $.format(template, 'success', 'play-circle', '运行中');
}
else if (value === "2") {
content = $.format(template, 'primary', 'stop-circle', '已停止');
}
else if (value === "3") {
content = $.format(template, 'danger', 'times-circle', '已禁用');
}
return content;
};
var enabledFormatter = function (value) {
var template = "<i class='fa fa-toggle-{0}'></i>";
return $.format(template, value ? 'on' : 'off');
};
$('.card-body table').lgbTable({
url: Tasks.url,
dataBinder: {
map: {
Id: "#taskID",
Name: "#taskName"
}
});
}).trigger('click');
},
smartTable: {
sortName: 'CreateTime',
sortOrder: 'desc',
queryParams: function (params) { return $.extend(params, { operateType: $("#txt_operate_type").val(), OperateTimeStart: $("#txt_operate_start").val(), OperateTimeEnd: $("#txt_operate_end").val() }); },
columns: [
{ title: "名称", field: "Name", sortable: true },
{ title: "创建时间", field: "CreatedTime", sortable: true },
{ title: "上次执行时间", field: "LastRuntime", sortable: true },
{ title: "下次执行时间", field: "NextRuntime", sortable: true },
{ title: "触发条件", field: "TriggerExpression", sortable: false },
{ title: "是否启用", field: "Enabled", sortable: true, formatter: enabledFormatter },
{ title: "状态", field: "Status", sortable: true, align: 'center', width: 106, formatter: stateFormatter }
],
editButtons: {
events: {
'click .info': function (e, value, row, index) {
$taskLogModelTitle.html(row.Name + ' - 任务日志窗口(最新50条)');
$.bc({
url: 'api/Tasks?name=' + row.Name,
method: 'put'
});
$('#dialogLog').modal('show').on('hide.bs.modal', function () {
// close hub
if ($taskMsg.hub) $taskMsg.hub.stop();
$taskMsg.html('');
});
var lastMsg = "";
// open hub
$taskMsg.notifi({
url: 'NotiHub',
method: 'taskRev',
callback: function (result) {
if (lastMsg === result) return;
lastMsg = result;
while (this.children().length > 50) {
this.children().first().remove();
}
this.append('<div>' + result + '</div>');
},
onclose: function (error) {
console.log(error);
}
});
}
}
}
}
});
});

View File

@ -0,0 +1,148 @@
.form-select {
position: relative;
}
.form-select .dropdown-menu {
margin-top: 14px;
max-height: 274px;
overflow-x: hidden;
padding: 0.25rem 0;
}
.form-select .dropdown-menu-arrow {
width: 0;
height: 0;
border-width: 0 6px 6px;
border-style: solid;
border-color: transparent transparent rgba(0,0,0,.15);
position: absolute;
left: 20px;
z-index: 1001;
margin-top: 8px;
}
.form-select .dropdown-menu, .form-select .dropdown-menu-arrow {
display: none;
opacity: 0;
transition: opacity .3s;
}
.form-select .dropdown-menu-arrow:after {
content: " ";
width: 0;
height: 0;
border-width: 0 6px 6px;
border-style: solid;
border-color: transparent transparent #fff;
position: absolute;
top: 1px;
left: -6px;
}
.form-select .form-select-input[readonly] {
background-color: #fff;
}
.form-select.open .dropdown-menu, .form-select.open .dropdown-menu-arrow {
display: block;
opacity: 1;
}
.form-select.open .form-select-append i {
transform: rotate(0);
}
.form-select.is-disabled .form-select-input {
background-color: #f5f7fa;
border-color: #e4e7ed;
color: #c0c4cc;
cursor: not-allowed;
}
.form-select-input {
color: #606266;
outline: none;
padding-right: 30px;
cursor: pointer;
}
.form-select-input:hover {
border-color: #c0c4cc;
}
.form-select-input.primary:hover {
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102,175,233,.6);
border-color: #66afe9;
}
.form-select-input.info:hover {
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(23, 162, 184, 0.5);
border-color: #17a2b8;
}
.form-select-input.success:hover {
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(40, 167, 69, 0.5);
border-color: #28a745;
}
.form-select-input.warning:hover {
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(255, 193, 7, 0.5);
border-color: #ffc107;
}
.form-select-input.danger:hover {
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(220, 53, 69, 0.5);
border: 1px solid #dc3545;
}
.form-select-input:focus {
border-color: #409eff;
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102,175,233,.6);
}
.form-select-input.primary:focus {
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102,175,233,.6);
border-color: #66afe9;
}
.form-select-input.info:focus {
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(23, 162, 184, 0.5);
border-color: #17a2b8;
}
.form-select-input.success:focus {
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(40, 167, 69, 0.5);
border-color: #28a745;
}
.form-select-input.warning:focus {
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(255, 193, 7, 0.5);
border-color: #ffc107;
}
.form-select-input.danger:focus {
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(220, 53, 69, 0.5);
border: 1px solid #dc3545;
}
.form-select-input.is-valid, .form-select-input.is-invalid {
background-image: none;
}
.form-select-append {
position: absolute;
height: 100%;
width: 30px;
right: 0;
top: 0;
color: #c0c4cc;
pointer-events: none;
display: flex;
align-items: center;
justify-content: center;
}
.form-select-append i {
transition: all .3s;
transform: rotate(180deg);
}

View File

@ -0,0 +1,166 @@
(function ($) {
'use strict';
var lgbSelect = function (element, options) {
this.$element = $(element);
this.options = $.extend({}, lgbSelect.DEFAULTS, options);
this.init();
};
lgbSelect.VERSION = '1.0';
lgbSelect.Author = 'argo@163.com';
lgbSelect.DataKey = "lgb.select";
lgbSelect.Template = '<div class="form-select">';
lgbSelect.Template += '<input type="text" readonly="readonly" class="form-control form-select-input" />';
lgbSelect.Template += '<span class="form-select-append">';
lgbSelect.Template += ' <i class="fa fa-angle-up"></i>';
lgbSelect.Template += '</span>';
lgbSelect.Template += '<div class="dropdown-menu-arrow"></div>';
lgbSelect.Template += '<div class="dropdown-menu"></div>';
lgbSelect.Template += '</div>';
lgbSelect.DEFAULTS = {
placeholder: "请选择 ...",
borderClass: null
};
lgbSelect.AllowMethods = /disabled|enable|val|reset/;
function Plugin(option) {
var params = $.makeArray(arguments).slice(1);
return this.each(function () {
var $this = $(this);
var data = $this.data(lgbSelect.DataKey);
var options = typeof option === 'object' && option;
if (!data) $this.data(lgbSelect.DataKey, data = new lgbSelect(this, options));
if (!lgbSelect.AllowMethods.test(option)) return;
if (typeof option === 'string') {
data[option].apply(data, params);
}
});
}
$.fn.lgbSelect = Plugin;
$.fn.lgbSelect.Constructor = lgbSelect;
var _proto = lgbSelect.prototype;
_proto.init = function () {
var getUID = function (prefix) {
if (!prefix) prefix = 'lgb';
do prefix += ~~(Math.random() * 1000000);
while (document.getElementById(prefix));
return prefix;
};
var that = this;
this.$element.addClass('d-none');
this.$ctl = $(lgbSelect.Template).insertBefore(this.$element);
this.$input = this.$ctl.find('.form-select-input');
this.$menus = this.$ctl.find('.dropdown-menu');
// init for
var $for = this.$element.parent().find('[for="' + this.$element.attr('id') + '"]');
if ($for.length > 0) {
var id = getUID();
this.$input.attr('id', id);
$for.attr('for', id);
}
if (this.options.borderClass) {
this.$input.addClass(this.options.borderClass);
}
this.$input.attr('placeholder', this.options.placeholder);
// init dropdown-menu
var data = this.$element.find('option').map(function () {
return { value: this.value, text: this.text, selected: this.selected }
});
this.reset(data);
// bind attribute
["data-valid", "data-required-msg"].forEach(function (v, index) {
if (that.$element.attr(v) !== undefined) {
that.$input.attr(v, that.$element.attr(v));
}
});
// replace element select -> input hidden
var eid = this.$element.attr('id');
this.$element.remove();
this.$element = $('<input type="hidden" data-toggle="lgbSelect" />').attr('id', eid).val(that.val()).insertAfter(this.$ctl);
this.$element.data(lgbSelect.DataKey, this);
// bind event
this.$ctl.on('click', '.form-select-input', function (e) {
e.preventDefault();
that.$ctl.toggleClass('open');
// calc width
that.$ctl.find('.dropdown-menu').outerWidth(that.$ctl.outerWidth());
});
this.$ctl.on('click', 'a.dropdown-item', function (e) {
e.preventDefault();
var $this = $(this);
$this.parent().children().removeClass('active');
that.val($this.attr('data-val'), true);
});
$(document).on('click', function (e) {
if (that.$input[0] !== e.target)
that.closeMenu();
});
};
_proto.closeMenu = function () {
this.$ctl.removeClass('open');
};
_proto.disabled = function () {
this.$ctl.addClass('is-disabled');
this.$input.attr('disabled', 'disabled');
};
_proto.enable = function () {
this.$ctl.removeClass('is-disabled');
this.$input.removeAttr('disabled');
};
_proto.reset = function (value) {
var that = this;
// keep old value
var oldValue = this.$element.val();
this.val('');
this.$menus.html('');
$.each(value, function () {
var $item = $('<a class="dropdown-item" href="#" data-val="' + this.value + '">' + this.text + '</a>');
that.$menus.append($item);
if (this.selected === true || this.value.toString() === oldValue) {
that.val(this.value);
}
});
};
_proto.val = function (value, valid) {
if (value !== undefined) {
var text = this.$menus.find('a.dropdown-item[data-val="' + value + '"]').text();
this.$input.val(text);
this.$element.val(value).attr('data-text', text);
this.$menus.find('.dropdown-item').removeClass('active');
this.$menus.find('.dropdown-item[data-val="' + value + '"]').addClass('active');
// trigger changed.lgbselect
this.$element.trigger('changed.lgbSelect');
// trigger lgbValidate
if (valid && this.$input.attr('data-valid') === 'true') this.$input.trigger('input.lgb.validate');
}
else {
return this.$element.val();
}
};
$(function () {
$('[data-toggle="lgbSelect"]').lgbSelect();
});
})(jQuery);

View File

@ -444,8 +444,12 @@
if ($.isFunction(op.callback)) op.callback.apply(that, arguments);
return console.error(err.toString());
}).then(function () {
// 连接成功
// invoke 为 调用服务端方法
// invoke: function (connection) { return connection.invoke('RetrieveDashboard'); }
if (op.invoke) op.invoke(connection).then(function (result) { console.log(result); }).catch(function (err) { console.error(err.toString()); });
});
this.hub = connection;
return this;
}
});

View File

@ -9,7 +9,7 @@
<ItemGroup>
<PackageReference Include="Bootstrap.Security.Mvc" Version="2.2.10" />
<PackageReference Include="Longbow.Configuration" Version="2.2.4" />
<PackageReference Include="Longbow.Logging" Version="2.2.8" />
<PackageReference Include="Longbow.Logging" Version="2.2.9" />
<PackageReference Include="Longbow.Web" Version="2.2.14" />
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="2.2.4" />

View File

@ -7,7 +7,7 @@
<ItemGroup>
<PackageReference Include="Bootstrap.Security.DataAccess" Version="2.2.6" />
<PackageReference Include="Longbow.Data" Version="2.3.2" />
<PackageReference Include="Longbow.Logging" Version="2.2.8" />
<PackageReference Include="Longbow.Logging" Version="2.2.9" />
<PackageReference Include="Longbow.Security.Cryptography" Version="1.3.0" />
<PackageReference Include="Longbow.Web" Version="2.2.14" />
<PackageReference Include="Longbow.Cache" Version="2.2.12" />

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.572
VisualStudioVersion = 15.0.28307.705
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SQLServer", "SQLServer", "{87319AF5-7C40-4362-B67C-35F9DD737DB4}"
ProjectSection(SolutionItems) = preProject

47
UnitTest/TaskTest.cs Normal file
View File

@ -0,0 +1,47 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
namespace UnitTest
{
public class TaskTest
{
private ITestOutputHelper _helper;
public TaskTest(ITestOutputHelper helper) => _helper = helper;
[Fact]
public async void Task_Ok()
{
var cts = new CancellationTokenSource(3000);
var sw = Stopwatch.StartNew();
try
{
// await Task.Delay(3000, cts.Token);
await TestAsync(cts.Token);
}
catch (Exception) { }
finally
{
sw.Stop();
}
_helper.WriteLine(sw.ElapsedMilliseconds.ToString());
//var waiter = new CancellationTokenSource(1000);
//var task = TestAsync(waiter.Token);
//task.Wait(waiter.Token);
////try
////{
//// task.Wait(2000, waiter.Token);
////}
////catch (Exception) { }
//sw.Stop();
}
private Task TestAsync(CancellationToken token) => Task.Delay(2000, token);
}
}