通过一个中间件解决ASP.Net Core中Request.Body为空的问题

    在ASP.Net Core的机制中,当接收到http的头为 application/x-www-form-urlencoded 或者 multipart/form-data 时,netcore会通过 FormReader 预先解析 Request.Body 的 Form 的内容,经过 Reader 读取后 Request.Body 就会变 null,这样我们在代码中需要再次使用 Request.Body 时就会报空异常。

详见代码:https://github.com/aspnet/HttpAbstractions/blob/release/2.0/src/Microsoft.AspNetCore.Http/Features/FormFeature.cs

如果需要在代码中再次使用 Request.Body 是数据流时,就必须开启 Request 的 Rewind 模式,这里提供了一个中间件去开启

using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Internal;

namespace Microsoft.AspNetCore.Http
{
    public class EnableRequestRewindMiddleware
    {
        private readonly RequestDelegate _next;

        public EnableRequestRewindMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            context.Request.EnableRewind();
            await _next(context);
        }
    }

    public static class EnableRequestRewindExtension
    {
        public static IApplicationBuilder UseEnableRequestRewind(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<EnableRequestRewindMiddleware>();
        }
    }
}

在 Startup.cs 的 Configure 方法引用中间件即可

//引入EnableRequestRewind中间件
app.UseEnableRequestRewind(); 

另外,这个中间件已经作为 Senparc.Weixin.MP.MvcExtension 的一个组件提供,使用 Senparc Weixin SDK 的直接引入即可

https://github.com/JeffreySu/WeiXinMPSDK/pull/1131

从Win服务启动UI程序

从windows服务启动一个带UI程序的界面,这个需求在xp中是很随意的,从Vista开始似乎没有那么随意了,因为Vista中加入了Session的概念,那么什么是Session,我想这篇文章介绍的应该比我权威的多。Session隔离介绍

明白了Session的概念后,我将通过Win32 API来实现从windows服务启动一个带UI的界面(从Session 0中启动Session *的程序),这个实现过程是我从C++代码翻译过来的。

实现的思路

  1. 找到一个除Session 0之外的活动Session
  2. 通过Session ID获取用户Token
  3. 通过Token来启动UI程序

涉及的Win32 API

  1. WTSGetActiveConsoleSessionId获取活动的Session ID
  2. WTSQueryUserToken根据Session ID获取用户Token
  3. CreateProcessAsUser使用用户Token来启动UI程序
public class ProcessAsUser
{
    public struct SECURITY_ATTRIBUTES
    {
        public uint nLength;
        public uint lpSecurityDescriptor;
        public bool bInheritHandle;
    }

    public struct STARTUPINFO
    {
        public uint cb;
        public string lpReserved;
        public string lpDesktop;
        public string lpTitle;
        public uint dwX;
        public uint dwY;
        public uint dwXSize;
        public uint dwYSize;
        public uint dwXCountChars;
        public uint dwYCountChars;
        public uint dwFillAttribute;
        public uint dwFlags;
        public ushort wShowWindow;
        public ushort cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;

    }
    public struct PROCESS_INFORMATION
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public uint dwProcessId;
        public uint dwThreadId;

    }

    [DllImport("kernel32.dll")]
    static extern uint WTSGetActiveConsoleSessionId();

    [DllImport("Wtsapi32.dll")]
    private static extern bool WTSQueryUserToken(uint SessionId, out uint hToken);

    [DllImport("Kernel32.dll")]
    private static extern uint GetLastError();

    [DllImport("kernel32.dll")]
    private static extern bool CloseHandle(IntPtr hSnapshot);

    [DllImport("advapi32.dll")]
    public extern static bool CreateProcessAsUser(IntPtr hToken,
                                            string lpApplicationName,
                                            string lpCommandLine,
                                            ref SECURITY_ATTRIBUTES lpProcessAttributes,
                                            ref SECURITY_ATTRIBUTES lpThreadAttributes,
                                            bool bInheritHandle,
                                            uint dwCreationFlags,
                                            uint lpEnvironment,
                                            string lpCurrentDirectory,
                                            ref STARTUPINFO lpStartupInfo,
                                            out PROCESS_INFORMATION lpProcessInformation);

    public static bool StartUIProcessFromService(string exePath)
    {
        //获取Session ID
        var sId=WTSGetActiveConsoleSessionId();
        if (sId == 0)
        {
            return false;
        }
        uint hToken;
        var isOk=WTSQueryUserToken(sId, out hToken);
        if (!isOk || hToken == 0)
        {
            return false;
        }
        var lpProcessAttr = new SECURITY_ATTRIBUTES();
        lpProcessAttr.nLength = (uint)Marshal.SizeOf(lpProcessAttr);

        var lpThreadAttr = new SECURITY_ATTRIBUTES();
        lpThreadAttr.nLength = (uint)Marshal.SizeOf(lpThreadAttr);

        var lpStratupInfo = new STARTUPINFO();
        lpStratupInfo.cb = (uint)Marshal.SizeOf(lpStratupInfo);
        lpStratupInfo.lpDesktop = @"winsta0\default";

        PROCESS_INFORMATION lpProcessInfo;
        isOk=CreateProcessAsUser((IntPtr)hToken,
                                    exePath,
                                    null,
                                    ref lpProcessAttr,
                                    ref lpThreadAttr,
                                    false,
                                    0,
                                    0,
                                    null,
                                    ref lpStratupInfo,
                                    out lpProcessInfo
                                );
        CloseHandle((IntPtr)hToken);
        return isOk;            
    }    
}
 

枚举活动Session ID

之前我们通过WTSGetActiveConsoleSessionId获取活动Session ID,当有多个用户登录时,Windows提供了WTSEnumerateSessions方法枚举多个Session ID。

主要涉及API

  1. WTSEnumerateSessions 检索在远程桌面会话主机 (RD 会话主机) 服务器上的会话的列表。
  2. WTSFreeMemory 释放由远程桌面服务函数分配的内存。

实现代码

[DllImport("Wtsapi32.dll")]
private static extern void WTSFreeMemory(IntPtr pSessionInfo);

[DllImport("Wtsapi32.dll")]
private extern static bool WTSEnumerateSessions(IntPtr hServer, uint reserved, uint version, out IntPtr ppSessionInfo, out uint pCount);
struct WTS_SESSION_INFO
{
    public uint SessionId;
    public string pWinStationName;
    public WTS_CONNECTSTATE_CLASS State;
}

enum WTS_CONNECTSTATE_CLASS
{
    WTSActive,
    WTSConnected,
    WTSConnectQuery,
    WTSShadow,
    WTSDisconnected,
    WTSIdle,
    WTSListen,
    WTSReset,
    WTSDown,
    WTSInit
}

private static uint EnumerateActiveSession()
{
    uint dwSessionID = 0xFFFFFFFF;
    uint dwCount = 0;
    IntPtr intPtr = IntPtr.Zero;
    try
    {
        IntPtr hServer = IntPtr.Zero;
        if (WTSEnumerateSessions(hServer, 0, 1, out intPtr, out dwCount))
        {
            var tmp = intPtr;
            for (var i = 0; i < dwCount; ++i)
            {
                var pSessionInfo = (WTS_SESSION_INFO)Marshal.PtrToStructure(tmp, typeof(WTS_SESSION_INFO));

                if (WTS_CONNECTSTATE_CLASS.WTSActive == pSessionInfo.State)
                {
                    dwSessionID = pSessionInfo.SessionId;
                    break;
                }
                if (WTS_CONNECTSTATE_CLASS.WTSConnected == pSessionInfo.State)
                {
                    dwSessionID = pSessionInfo.SessionId;
                }
                tmp += Marshal.SizeOf(typeof(WTS_SESSION_INFO));
            }
            WTSFreeMemory(intPtr);
        }
        var eCode = GetLastError();
    }
    catch (Exception ex)
    {
        var eCode = GetLastError();
    }
    return dwSessionID;
}

 

 

树莓派+Win10 iot跑NetCore2网站

前期准备

  1. 一个树莓派刷上最新的Win10 iot 15063系统
  2. 一个NetCore2的网站,能在PC上正常运行就可以了

PS:如何刷Win10 iot系统和如何创建Asp.Net Core网站不在本文��讨论范围,请自行百度

网站发布

运行指令

dotnet publish -c release -r win-arm

然后把红线部分的内容复制到树莓派上

这里可以通过文件共享复制

\\192.168.99.147\c$\

PS:192.168.99.147是我树莓派的ip

然后登录到树莓派的PowerShell(可通过 Iot Dashboard 上的 我的设备 右键 找到)

运行指令,开放防火墙端口

netsh advfirewall firewall add rule name="NetCore2 APP" dir=in action=allow protocol=TCP localport=5001

默认端口是5000,我这里项目中修改成5001

然后运行

cd C:\Web
.\WeChat.exe

WeChat是我的项目名,需要自行替换成自己的

最终效果

VsCode离线配置OmniSharp

环境的安装

第一步你要安装一个vscode

如果网速不给力,可以使用百度云的离线下载,然后再下载到本机安装。

第二步安装.Net Core运行环境和客户端命令

请移步dot.net站点下载.Net Core编译运行环境

第三步安装OmniSharp

在线安装在线安装方法
离线安装vsix文件安装

最后安装OmniSharp智能提示+Debugger

这一步是最慢的因为要联网下载OmniSharp和Debugger,如果网速不好的童鞋请按照如下方式安装:
打开vscode扩展安装目录中的c:\users*.vscode\extensions\ms-vscode.csharp-..\package.json文件找到下图中的url进行下载:* 这个runtimeDependencies节点包涵了所有类型的系统的智能提示Debugger工具,因为我使用的是windows,所以我需要下载了这两个工具:

  1. 智能提示:https://omnisharpdownload.blob.core.windows.net/ext/omnisharp-win-x86-1.9-beta22.zip
    解压到C:\Users**.vscode\extensions\ms-vscode.csharp-..\bin\omnisharp目录中即可
  2. Debugger:https://vsdebugger.azureedge.net/coreclr-debug-1-6-3/coreclr-debug-win7-x64.zip
    解压到C:\Users**.vscode\extensions\ms-vscode.csharp-...debugger目录中即可
  3. 最后创建C:\Users**.vscode\extensions\ms-vscode.csharp-..\install.LOCK空文本文件即可

LBS位置服务的SQL实现原理以及SQL代码

一、位置服务(搜索周边商家POI)

  1. 自身有海量的周边搜索数据,并且一定要有经纬度坐标;(数据来源可以从网上购买,如果要直接从地图API中获取,那是要付费的,而且费用不菲)
  2. 手机定位后将经纬度发送给服务器,服务器根据经纬度在数据库中匹配数据,并按照距离由近到远排序(此处省略一万字...)

SQL实现经纬度匹配以及距离排序代码

1、SQL计算经纬度距离的自定义函数

CREATE FUNCTION dbo.GetDistance  
(  
    @LatBegin REAL  
    , @LngBegin REAL  
    , @LatEnd REAL  
    , @LngEnd REAL  
)  
RETURNS FLOAT  
AS  
BEGIN  
    DECLARE @Distance REAL  
    DECLARE @EARTH_RADIUS REAL  
    SET @EARTH_RADIUS = 6378.137  
      
    DECLARE @RadLatBegin REAL, @RadLatEnd REAL, @RadLatDiff REAL, @RadLngDiff REAL  
    SET @RadLatBegin = @LatBegin * PI() / 180.0  
    SET @RadLatEnd = @LatEnd * PI() / 180.0  
    SET @RadLatDiff = @RadLatBegin - @RadLatEnd  
    SET @RadLngDiff = @LngBegin * PI() / 180.0 - @LngEnd * PI() / 180.0  
      
    SET @Distance = 2 * ASIN(SQRT(POWER(Sin(@RadLatDiff / 2), 2) + COS(@RadLatBegin) * COS(@RadLatEnd) * POWER(SIN(@RadLngDiff/2),2)))  
    SET @Distance = @Distance * @EARTH_RADIUS  
    --SET @Distance = Round(@Distance * 10000) / 10000  
      
    RETURN @Distance * 1000  
END  

2、获取周边数据的存储过程

CREATE PROCEDURE Up_Data_GetPOI 
	@lngbegin nvarchar(50), --当前经度
	@latbegin nvarchar(50), --当前纬度
	@distance int, --搜索半径,单位为米
	@WhereStr nvarchar(200) --附加搜索条件,如and 1=2 
	AS
	declare @SqlStr nvarchar(2000)
	
	set @SqlStr='select *, dbo.GetDistance('+@latbegin+','+@lngbegin+',纬度字段,经度字段) as distance from [存储商家的数据表名] where (纬度字段<>'' and 经度字段<>'') and dbo.GetDistance('+@latbegin+','+@lngbegin+',纬度字段,经度字段)<'+Convert(nvarchar(10),@distance)+' '+@WhereStr+' order by distance asc'
	
	--print @SqlStr
	Execute Sp_ExecuteSql @SqlStr
GO

二、位置交友

  1. 要有会员数据,同样也得有客户端是否在线以及经纬度的字段(不一定要获取到经纬度,默认为空)
  2. 手机客户端定位后,更新该会员的经纬度坐标以及客户端在线状态
  3. 通过上面的SQL代码即可实现数据列表,只要输出JSON/XML给手机客户端解析即可

.Net Core使用GB2312编码

.Net Core默认是UTF8编码,但有些时候需要使用GB2312编码

Encoding.GetEncoding("GB2312")

但代码运行时候会报错误

No data is available for encoding 936.

这时需要引用Nuget包

System.Text.Encoding.CodePages 4.3.0

再在 Startup 的构造函数中加上一行代码

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

 

Serenity For asp.net core项目发布后运行不正常解决

Serenity For asp.net core项目使用 dotnet publish 指令发布到iis后,使用时候报错

Script data Template.* is not found in registered script list!

解决方法:

修改 project.json 的 publishOptions 配置节 增加 "**/*.html", 项

  "publishOptions": {
			"include": [
				"wwwroot",
				"**/*.cshtml",
				"**/*.html",
				"appsettings.json",
				"web.config"
			]
  },

 

[开源]TFS与微信企业号的通知整合

直接上地址:

https://github.com/lishewen/TFSWebHook

使用方法:

1、修改web.config中的CorpId/Secret/AgentId,为你自己企业号中申请到的key

2、把站点发布到IIS

3、在TFS 服务挂钩 设置中,选择 Web挂钩 ,并填入URL

http://<host>/api/webhooks/incoming/vsts?code=D854DE27C4ED4A10BFC6BE6E21C3A5A1

如果修改了web.config中对应的code这里也要换成自己的

4、我这里只写了签入代码和git push的事件通知,如果需要其他通知 如工作项变更通知,可自行到Webhooks/VstsWebHookHandler.cs 中添加即可

效果图:

.Net Core msbuild配置编译条件

例如,我想只在Debug模式下才运行 npm run less 脚本

这时只需在csproj项目文件中对应的配置节

  <Target Name="PostcompileScript" AfterTargets="Build">
    <Exec Command="npm run less" />
  </Target>

加上判断条件,即可

Condition=" '$(Configuration)' == 'Debug' "

即修改为:

  <Target Name="PostcompileScript" AfterTargets="Build" Condition=" '$(Configuration)' == 'Debug' ">
    <Exec Command="npm run less" />
  </Target>

 

JRE升级导致Elasticsearch故障处理

今天发现Elasticsearch服务故障,重启后还是不行,具体日志如下:

[2017-02-10 10:01:32] [info]  [32412] Commons Daemon procrun (1.0.15.0 64-bit) started
[2017-02-10 10:01:32] [info]  [32412] Running 'elasticsearch-service-x64' Service...
[2017-02-10 10:01:32] [info]  [18112] Starting service...
[2017-02-10 10:01:32] [error] [18112] Failed creating java C:\Program Files\Java\jre1.8.0_111\bin\server\jvm.dll
[2017-02-10 10:01:32] [error] [18112] 系统找不到指定的路径。
[2017-02-10 10:01:32] [error] [18112] ServiceStart returned 1
[2017-02-10 10:01:32] [error] [18112] 系统找不到指定的路径。

原因分析:

去硬盘找了一下的确没有这个文件了,倒是在 C:\Program Files\Java\jre1.8.0_121\bin\server 路径下发现了此文件

应该是Java自动更新后把老版的文件Delete掉导致的

解决办法:

去cmd执行指令

service manager elasticsearch-service-x64

把路径改对即可