From 630008198d0fafeceb212c6e6b0268563a459d8d Mon Sep 17 00:00:00 2001
From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com>
Date: Fri, 19 Sep 2025 23:58:52 +0800
Subject: [PATCH] =?UTF-8?q?refactor(flow)=20:=20=E9=87=8D=E6=96=B0?=
=?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=BA=86UIContextOperation=E7=9A=84=E6=B3=A8?=
=?UTF-8?q?=E5=85=A5=E9=80=BB=E8=BE=91=EF=BC=8C=E4=B8=BA=E5=90=8E=E7=BB=AD?=
=?UTF-8?q?=E5=BC=80=E5=8F=91Avalonia=E7=89=88=E6=9C=AC=E7=9A=84=E7=BC=96?=
=?UTF-8?q?=E8=BE=91=E5=99=A8=E5=81=9A=E5=87=86=E5=A4=87=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 6 +
Library/Utils/JsonHelper.cs | 22 +-
Library/Utils/SereinIoc.cs | 33 ++-
Library/Utils/UIContextOperation.cs | 5 +
NodeFlow/Env/FlowControl.cs | 8 +-
NodeFlow/Env/FlowEnvironment.cs | 39 +++-
NodeFlow/Env/LocalFlowEnvironment.cs | 58 ++++--
NodeFlow/Model/Librarys/FlowLibraryCache.cs | 10 +-
NodeFlow/Services/FlowLibraryService.cs | 11 +-
NodeFlow/Tool/NodeMethodDetailsHelper.cs | 2 +-
README.md | 8 +-
Serein.Proto.HttpApi/ApiHandleConfig.cs | 10 +-
Serein.Proto.HttpApi/HandleState.cs | 54 +++++
Serein.Proto.HttpApi/InvokeResult.cs | 56 ++++++
Serein.Proto.HttpApi/PathRouter.cs | 190 +++++-------------
Serein.Proto.HttpApi/RouterInfo.cs | 42 ++++
.../Serein.Proto.HttpApi.csproj | 2 +-
Serein.Proto.HttpApi/SereinWebApiService.cs | 50 ++++-
SereinFlow.sln | 6 +
Workbench/Serein.WorkBench.csproj | 2 +-
Workbench/Services/KeyEventService.cs | 1 -
21 files changed, 415 insertions(+), 200 deletions(-)
create mode 100644 Serein.Proto.HttpApi/HandleState.cs
create mode 100644 Serein.Proto.HttpApi/InvokeResult.cs
create mode 100644 Serein.Proto.HttpApi/RouterInfo.cs
diff --git a/.gitignore b/.gitignore
index 5c3a4ae..76834ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,9 @@
*.user
*.suo
+# 排除 Rider 相关文件夹
+.idea
+
# 排除编译输出文件夹
bin/
obj/
@@ -18,11 +21,14 @@ obj/
*.exe
# 排除发布文件夹
+
.Output/
/.git1
+
doc
WorkBench.ControlLibrary.Core
WorkBench.Remote
Serein.FlowStartTool
Serein.CloudWorkbench
Serein.CollaborationSync
+Serein.Workbench.Avalonia
diff --git a/Library/Utils/JsonHelper.cs b/Library/Utils/JsonHelper.cs
index c02bf1f..1123512 100644
--- a/Library/Utils/JsonHelper.cs
+++ b/Library/Utils/JsonHelper.cs
@@ -12,7 +12,7 @@ namespace Serein.Library.Utils
///
/// Json门户类,需要你提供实现
///
- private static IJsonProvider provider;
+ public static IJsonProvider Provider { get; private set; }
///
/// 使用第三方包进行解析
@@ -20,7 +20,7 @@ namespace Serein.Library.Utils
///
public static void UseJsonProvider(IJsonProvider jsonPortal)
{
- JsonHelper.provider = jsonPortal;
+ JsonHelper.Provider = jsonPortal;
}
///
@@ -32,7 +32,7 @@ namespace Serein.Library.Utils
public static T Deserialize(string jsonText)
{
- return provider.Deserialize(jsonText);
+ return Provider.Deserialize(jsonText);
}
///
@@ -43,7 +43,7 @@ namespace Serein.Library.Utils
///
public static object Deserialize(string jsonText, Type type)
{
- return provider.Deserialize(jsonText, type);
+ return Provider.Deserialize(jsonText, type);
}
@@ -55,7 +55,7 @@ namespace Serein.Library.Utils
public static IJsonToken Parse(string json)
{
- return provider.Parse(json);
+ return Provider.Parse(json);
}
///
@@ -66,7 +66,7 @@ namespace Serein.Library.Utils
///
public static bool TryParse(string json, out IJsonToken jsonToken)
{
- return provider.TryParse(json, out jsonToken);
+ return Provider.TryParse(json, out jsonToken);
}
@@ -78,7 +78,7 @@ namespace Serein.Library.Utils
///
public static string Serialize(object obj)
{
- return provider.Serialize(obj);
+ return Provider.Serialize(obj);
}
///
@@ -90,7 +90,7 @@ namespace Serein.Library.Utils
{
var dict = new Dictionary();
init(dict);
- return provider.CreateObject(dict);
+ return Provider.CreateObject(dict);
}
///
@@ -100,7 +100,7 @@ namespace Serein.Library.Utils
///
public static IJsonToken Array(IEnumerable
public FlowLibraryInfo LoadBaseLibrary()
{
- Assembly baseAssmbly = typeof(FlowBaseLibrary).Assembly;
+ var baseAssmbly = typeof(FlowBaseLibrary).Assembly;
var flowLibrary = new FlowLibraryCache(baseAssmbly);
flowLibrary.LoadFlowMethod();
var assemblyName = baseAssmbly.GetName().Name;
@@ -69,7 +74,9 @@ namespace Serein.NodeFlow.Services
throw new Exception($"程序集\"{baseAssmbly}\"返回 Name 为 null");
}
_flowLibraryCaches.TryAdd(assemblyName, flowLibrary);
- return flowLibrary.ToInfo();
+ var infos = flowLibrary.ToInfo();
+ IsLoadedBaseLibrary = true;
+ return infos;
}
///
diff --git a/NodeFlow/Tool/NodeMethodDetailsHelper.cs b/NodeFlow/Tool/NodeMethodDetailsHelper.cs
index b0450f7..fc616a6 100644
--- a/NodeFlow/Tool/NodeMethodDetailsHelper.cs
+++ b/NodeFlow/Tool/NodeMethodDetailsHelper.cs
@@ -332,7 +332,7 @@ public static class NodeMethodDetailsHelper
{
isExplicitData = nodeParmsAttribute.IsExplicit; // 设置是否是显式参数
}
- if (string.IsNullOrEmpty(nodeParmsAttribute.Name))
+ if (!string.IsNullOrEmpty(nodeParmsAttribute.Name))
{
description = nodeParmsAttribute.Name; // 设置显示的名称
}
diff --git a/README.md b/README.md
index d4ee366..4b41ea0 100644
--- a/README.md
+++ b/README.md
@@ -29,4 +29,10 @@ This project supports both **English** and **中文** documentation.
## 🔗 External Links
-- Bilibili: https://space.bilibili.com/33526379
\ No newline at end of file
+- Bilibili: https://space.bilibili.com/33526379
+
+# 社群
+
+QQ群 955830545
+提供技术交流与支持,欢迎加入。
+因为个人是社畜,所以可能不会及时回复,请谅解。
\ No newline at end of file
diff --git a/Serein.Proto.HttpApi/ApiHandleConfig.cs b/Serein.Proto.HttpApi/ApiHandleConfig.cs
index bfe3adb..5c4a815 100644
--- a/Serein.Proto.HttpApi/ApiHandleConfig.cs
+++ b/Serein.Proto.HttpApi/ApiHandleConfig.cs
@@ -154,7 +154,15 @@ namespace Serein.Proto.HttpApi
}
else if (jsonObject != null && PostArgTypes[i] == PostArgType.IsBobyData)
{
- args[i] = jsonObject.ToObject(type);
+ if (type.IsEnum)
+ {
+ args[i] = jsonObject.ToObject(type);
+ }
+
+ else
+ {
+ args[i] = jsonObject.ToObject(type);
+ }
}
else if (jsonObject != null)
{
diff --git a/Serein.Proto.HttpApi/HandleState.cs b/Serein.Proto.HttpApi/HandleState.cs
new file mode 100644
index 0000000..350e8fc
--- /dev/null
+++ b/Serein.Proto.HttpApi/HandleState.cs
@@ -0,0 +1,54 @@
+namespace Serein.Proto.HttpApi
+{
+ public enum HandleState
+ {
+ ///
+ /// 默认值
+ ///
+ None ,
+
+ ///
+ /// 没有异常
+ ///
+ Ok,
+
+ ///
+ /// 没有对应的控制器
+ ///
+ NotHanleController,
+
+ ///
+ /// 没有对应的Http请求类型
+ ///
+ NotHttpApiRequestType,
+
+ ///
+ /// 没有处理配置
+ ///
+ NotHandleConfig,
+
+ ///
+ /// 无法实例化控制器
+ ///
+ HanleControllerIsNull,
+
+ ///
+ /// 调用参数获取错误
+ ///
+ InvokeArgError,
+
+ ///
+ /// 调用发生异常
+ ///
+ InvokeErrored,
+
+ ///
+ /// 请求被阻止
+ ///
+ RequestBlocked,
+ }
+
+
+
+}
+
diff --git a/Serein.Proto.HttpApi/InvokeResult.cs b/Serein.Proto.HttpApi/InvokeResult.cs
new file mode 100644
index 0000000..1143f73
--- /dev/null
+++ b/Serein.Proto.HttpApi/InvokeResult.cs
@@ -0,0 +1,56 @@
+namespace Serein.Proto.HttpApi
+{
+ ///
+ /// 调用结果
+ ///
+ public class InvokeResult
+ {
+ ///
+ /// 处理工具记录的请求Id,用于匹配请求与响应
+ ///
+ public long RequestId { get; set; }
+ ///
+ /// 调用状态
+ ///
+ public HandleState State { get; set; }
+ ///
+ /// 调用正常时这里会有数据
+ ///
+ public object? Data{ get; set; }
+ ///
+ /// 调用失败时这里可能会有异常数据
+ ///
+ public Exception? Exception { get; set; }
+
+ ///
+ /// 调用成功
+ ///
+ ///
+ ///
+ ///
+ public static InvokeResult Ok(long requestId, object? data) => new InvokeResult
+ {
+ RequestId = requestId,
+ Data = data,
+ State = HandleState.Ok,
+ };
+
+ ///
+ /// 调用失败
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static InvokeResult Fail(long requestId, HandleState state, Exception? ex = null) => new InvokeResult
+ {
+ RequestId = requestId,
+ State = state,
+ Exception = ex,
+ };
+ }
+
+
+
+}
+
diff --git a/Serein.Proto.HttpApi/PathRouter.cs b/Serein.Proto.HttpApi/PathRouter.cs
index 5838557..72fc6bf 100644
--- a/Serein.Proto.HttpApi/PathRouter.cs
+++ b/Serein.Proto.HttpApi/PathRouter.cs
@@ -17,140 +17,18 @@ using Type = System.Type;
namespace Serein.Proto.HttpApi
{
- ///
- /// 路由接口
- ///
- public interface IPathRouter
- {
- ///
- /// 添加处理模块
- ///
- ///
- void AddHandle(Type controllerType);
- ///
- /// 获取路由信息
- ///
- ///
- List GetRouterInfos();
-
- ///
- /// 路由解析开始处理
- ///
- ///
- ///
- Task HandleAsync(HttpListenerContext context);
- }
-
-
- public enum HandleState
- {
- ///
- /// 默认值
- ///
- None ,
- ///
- /// 没有异常
- ///
- Ok,
- ///
- /// 没有对应的控制器
- ///
- NotHanleController,
- ///
- /// 没有对应的Http请求类型
- ///
- NotHttpApiRequestType,
- ///
- /// 没有处理配置
- ///
- NotHandleConfig,
- ///
- /// 无法实例化控制器
- ///
- HanleControllerIsNull,
-
- ///
- /// 调用参数获取错误
- ///
- InvokeArgError,
-
- ///
- /// 调用发生异常
- ///
- InvokeErrored,
- }
-
- ///
- /// 调用结果
- ///
- public class InvokeResult
- {
- ///
- /// 调用状态
- ///
- public HandleState State { get; set; }
- ///
- /// 调用正常时这里会有数据
- ///
- public object? Data{ get; set; }
- ///
- /// 调用失败时这里可能会有异常数据
- ///
- public Exception? Exception { get; set; }
-
- public static InvokeResult Ok(object? data) => new InvokeResult
- {
- Data = data,
- State = HandleState.Ok,
- };
- public static InvokeResult Fail(HandleState state, Exception? ex = null) => new InvokeResult
- {
- State = state,
- Exception = ex,
- };
- }
-
- ///
- /// 路由信息
- ///
- public class RouterInfo
- {
-#if NET6_0_OR_GREATER
- ///
- /// 接口类型
- ///
- public ApiType ApiType { get; set; }
- ///
- /// 接口URL
- ///
- public required string Url { get; set; }
- ///
- /// 对应的处理方法
- ///
- public required MethodInfo MethodInfo { get; set; }
-#else
- ///
- /// 接口类型
- ///
- public ApiType ApiType { get; set; }
- ///
- /// 接口URL
- ///
- public string Url { get; set; }
- ///
- /// 对应的处理方法
- ///
- public MethodInfo MethodInfo { get; set; }
-#endif
- }
-
-
+
///
/// 路由注册与解析
///
- public class PathRouter : IPathRouter
+ internal class PathRouter
{
- private readonly ISereinIOC SereinIOC; // 用于存储路由信息
+ ///
+ /// IOC容器
+ ///
+ private readonly ISereinIOC SereinIOC;
+
+ private long _requestId = 0;
///
/// 控制器实例对象的类型,每次调用都会重新实例化,[Url - ControllerType]
@@ -162,6 +40,12 @@ namespace Serein.Proto.HttpApi
///
private readonly ConcurrentDictionary> HandleModels = new ConcurrentDictionary>();
+ ///
+ /// 请求时的处理函数,传入API类型、URL、Body
+ ///
+ internal Func? OnBeforRequest;
+
+
public PathRouter(ISereinIOC SereinIOC)
{
this.SereinIOC = SereinIOC;
@@ -238,19 +122,35 @@ namespace Serein.Proto.HttpApi
public async Task HandleAsync(HttpListenerContext context)
{
var request = context.Request; // 获取请求对象
+ var uri = request.Url; // 获取请求的完整URL
var httpMethod = request.HttpMethod.ToUpper(); // 获取请求的 HTTP 方法
+ var fullUrl = uri.ToString(); // 获取完整URL字符串
+ var routeValues = GetUrlData(fullUrl); // 解析 URL 获取路由参数
+ var requestBody = await ReadRequestBodyAsync(request); // 读取请求体内容
+ var requestId = System.Threading.Interlocked.Increment(ref _requestId);
+ var requestInfo = new ApiRequestInfo
+ {
+ RequestId = requestId,
+ ApiType = httpMethod,
+ Url = fullUrl,
+ Body = requestBody,
+ };
+ if (OnBeforRequest?.Invoke(requestInfo) == false)
+ {
+ return InvokeResult.Fail(requestId, HandleState.RequestBlocked); // 请求被阻止
+ }
if (!HandleModels.TryGetValue(httpMethod, out var modules) || request.Url is null)
{
- return InvokeResult.Fail(HandleState.NotHttpApiRequestType); // 没有对应HTTP请求方法的处理
+ return InvokeResult.Fail(requestId, HandleState.NotHttpApiRequestType); // 没有对应HTTP请求方法的处理
}
var template = request.Url.AbsolutePath.ToLower() ;
if (!_controllerTypes.TryGetValue(template, out var controllerType))
{
- return InvokeResult.Fail(HandleState.NotHanleController); // 没有对应的处理器
+ return InvokeResult.Fail(requestId, HandleState.NotHanleController); // 没有对应的处理器
}
if (!modules.TryGetValue(template, out var config))
{
- return InvokeResult.Fail(HandleState.NotHandleConfig); // 没有对应的处理配置
+ return InvokeResult.Fail(requestId, HandleState.NotHandleConfig); // 没有对应的处理配置
}
ControllerBase controllerInstance;
@@ -260,13 +160,13 @@ namespace Serein.Proto.HttpApi
}
catch
{
- return InvokeResult.Fail(HandleState.HanleControllerIsNull); // 未找到控制器实例
+ return InvokeResult.Fail(requestId, HandleState.HanleControllerIsNull); // 未找到控制器实例
}
- var url = request.Url; // 获取请求的完整URL
- var routeValues = GetUrlData(url); // 解析 URL 获取路由参数
- controllerInstance.Url = url.AbsolutePath;
+
+
+ controllerInstance.Url = uri.AbsolutePath; // 设置控制器实例的 URL 属性
object?[] args;
switch (httpMethod)
@@ -275,32 +175,32 @@ namespace Serein.Proto.HttpApi
args = config.GetArgsOfGet(routeValues); // Get请求
break;
case "POST":
- var requestBody = await ReadRequestBodyAsync(request); // 读取请求体内容
controllerInstance.Body = requestBody;
if (!JsonHelper.TryParse(requestBody, out var requestJObject))
{
var exTips = $"body 无法转换为 json 数据, body: {requestBody}";
- return InvokeResult.Fail(HandleState.InvokeArgError, new Exception(exTips));
+ return InvokeResult.Fail(requestId, HandleState.InvokeArgError, new Exception(exTips));
}
(var isPass, var index, var type, var ex) = config.TryGetArgsOfPost(routeValues, requestJObject, out args);
if (!isPass)
{
var exTips = $"尝试转换第{index}个入参参数时,类型 {type.FullName} 参数获取失败:{ex?.Message}";
- return InvokeResult.Fail(HandleState.InvokeArgError, new Exception(exTips));
+ return InvokeResult.Fail(requestId, HandleState.InvokeArgError, new Exception(exTips));
}
break;
default:
- return InvokeResult.Fail(HandleState.NotHttpApiRequestType);
+ return InvokeResult.Fail(requestId, HandleState.NotHttpApiRequestType);
}
+
try
{
var invokeResult = await config.HandleAsync(controllerInstance, args);
- return InvokeResult.Ok(invokeResult);
+ return InvokeResult.Ok(requestId, invokeResult);
}
catch (Exception ex)
{
- return InvokeResult.Fail(HandleState.InvokeErrored, ex);
+ return InvokeResult.Fail(requestId, HandleState.InvokeErrored, ex);
}
}
@@ -403,11 +303,11 @@ namespace Serein.Proto.HttpApi
///
///
///
- private Dictionary GetUrlData(Uri uri)
+ private Dictionary GetUrlData(string uri)
{
Dictionary routeValues = new Dictionary();
- var pathParts = uri.ToString().Split('?'); // 拆分 URL,获取路径部分
+ var pathParts = uri.Split('?'); // 拆分 URL,获取路径部分
if (pathParts.Length > 1) // 如果包含查询字符串
{
diff --git a/Serein.Proto.HttpApi/RouterInfo.cs b/Serein.Proto.HttpApi/RouterInfo.cs
new file mode 100644
index 0000000..a6a8411
--- /dev/null
+++ b/Serein.Proto.HttpApi/RouterInfo.cs
@@ -0,0 +1,42 @@
+using System.Reflection;
+
+namespace Serein.Proto.HttpApi
+{
+ ///
+ /// 路由信息
+ ///
+ public class RouterInfo
+ {
+#if NET6_0_OR_GREATER
+ ///
+ /// 接口类型
+ ///
+ public ApiType ApiType { get; set; }
+ ///
+ /// 接口URL
+ ///
+ public required string Url { get; set; }
+ ///
+ /// 对应的处理方法
+ ///
+ public required MethodInfo MethodInfo { get; set; }
+#else
+ ///
+ /// 接口类型
+ ///
+ public ApiType ApiType { get; set; }
+ ///
+ /// 接口URL
+ ///
+ public string Url { get; set; }
+ ///
+ /// 对应的处理方法
+ ///
+ public MethodInfo MethodInfo { get; set; }
+#endif
+ }
+
+
+
+}
+
diff --git a/Serein.Proto.HttpApi/Serein.Proto.HttpApi.csproj b/Serein.Proto.HttpApi/Serein.Proto.HttpApi.csproj
index 3959e18..7019f40 100644
--- a/Serein.Proto.HttpApi/Serein.Proto.HttpApi.csproj
+++ b/Serein.Proto.HttpApi/Serein.Proto.HttpApi.csproj
@@ -8,7 +8,7 @@
..\.\.Output
基于Json数据载体的HttpApi交互工具包
- 0.0.9
+ 0.0.11
基于Json数据载体的HttpApi交互工具包
MIT
True
diff --git a/Serein.Proto.HttpApi/SereinWebApiService.cs b/Serein.Proto.HttpApi/SereinWebApiService.cs
index aeddd33..e7bd8e7 100644
--- a/Serein.Proto.HttpApi/SereinWebApiService.cs
+++ b/Serein.Proto.HttpApi/SereinWebApiService.cs
@@ -1,26 +1,27 @@
using Serein.Library;
+using Serein.Library.Api;
using Serein.Library.Utils;
using System.Net;
using System.Text;
namespace Serein.Proto.HttpApi
{
-
+
///
/// 对于 HttpListenerContext 的拓展服务
///
public class SereinWebApiService
{
- private readonly IPathRouter _pathRouter;
+ private readonly PathRouter _pathRouter;
//private RequestLimiter? requestLimiter;
///
/// 初始化处理器
///
///
- public SereinWebApiService(IPathRouter pathRouter)
+ public SereinWebApiService(ISereinIOC sereinIOC)
{
- _pathRouter = pathRouter;
+ _pathRouter = new PathRouter(sereinIOC);
}
///
@@ -50,7 +51,10 @@ namespace Serein.Proto.HttpApi
return _pathRouter.GetRouterInfos();
}
- private Func OnBeforeReplying;
+ ///
+ /// 传入方法调用结果,返回最终回复的内容和状态码
+ ///
+ private Func? OnBeforeReplying;
///
/// 设置回复前的处理函数
@@ -61,6 +65,16 @@ namespace Serein.Proto.HttpApi
OnBeforeReplying = func;
}
+ ///
+ /// 请求时的处理函数,传入API类型、URL、Body
+ ///
+ ///
+ public void SetOnBeforeRequest(Func func)
+ {
+ _pathRouter.OnBeforRequest = func;
+ }
+
+
///
/// 处理请求
///
@@ -118,4 +132,30 @@ namespace Serein.Proto.HttpApi
}
}
+
+ ///
+ /// 外部请求的信息
+ ///
+ public class ApiRequestInfo
+ {
+ ///
+ /// 请求编号
+ ///
+ public long RequestId { get; set; }
+
+ ///
+ /// API类型 GET/POST
+ ///
+ public string ApiType { get; set; }
+
+ ///
+ /// 请求的URL
+ ///
+ public string Url { get; set; }
+
+ ///
+ /// 请求的Body
+ ///
+ public string? Body { get; set; }
+ }
}
diff --git a/SereinFlow.sln b/SereinFlow.sln
index 8e92a96..180c0be 100644
--- a/SereinFlow.sln
+++ b/SereinFlow.sln
@@ -30,6 +30,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serein.Proto.Modbus", "Sere
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serein.Proto.HttpApi", "Serein.Proto.HttpApi\Serein.Proto.HttpApi.csproj", "{281B8E55-B9CD-4FE5-A72F-59CBB968D844}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serein.Workbench.Avalonia", "Serein.Workbench.Avalonia\Serein.Workbench.Avalonia.csproj", "{3115002B-4CDA-4793-803A-5C1BA95EC6C5}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -80,6 +82,10 @@ Global
{281B8E55-B9CD-4FE5-A72F-59CBB968D844}.Debug|Any CPU.Build.0 = Debug|Any CPU
{281B8E55-B9CD-4FE5-A72F-59CBB968D844}.Release|Any CPU.ActiveCfg = Release|Any CPU
{281B8E55-B9CD-4FE5-A72F-59CBB968D844}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3115002B-4CDA-4793-803A-5C1BA95EC6C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3115002B-4CDA-4793-803A-5C1BA95EC6C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3115002B-4CDA-4793-803A-5C1BA95EC6C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3115002B-4CDA-4793-803A-5C1BA95EC6C5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Workbench/Serein.WorkBench.csproj b/Workbench/Serein.WorkBench.csproj
index 21e22fc..6a3ab67 100644
--- a/Workbench/Serein.WorkBench.csproj
+++ b/Workbench/Serein.WorkBench.csproj
@@ -81,7 +81,7 @@
-
+
diff --git a/Workbench/Services/KeyEventService.cs b/Workbench/Services/KeyEventService.cs
index 8540b53..a7efdbf 100644
--- a/Workbench/Services/KeyEventService.cs
+++ b/Workbench/Services/KeyEventService.cs
@@ -55,7 +55,6 @@ namespace Serein.Workbench.Services
///
///
void KeyUp(Key key);
-
}
///