Browse Source

短信工具修改

warr.qian 1 year ago
parent
commit
d7ff463122
42 changed files with 3887 additions and 147 deletions
  1. 181 0
      Tools/MiniWebApi/Handler/Attributes.cs
  2. 173 0
      Tools/MiniWebApi/Handler/BaseHandler.cs
  3. 63 0
      Tools/MiniWebApi/Handler/CallingMethod.cs
  4. 54 0
      Tools/MiniWebApi/Handler/CallingParameter.cs
  5. 12 0
      Tools/MiniWebApi/Handler/IWebApiMethod.cs
  6. 26 0
      Tools/MiniWebApi/Handler/WebApiArgument.cs
  7. 34 0
      Tools/MiniWebApi/Handler/WebApiMethod.cs
  8. 10 0
      Tools/MiniWebApi/Handler/WebApiType.cs
  9. 8 0
      Tools/MiniWebApi/MiniWebApi.csproj
  10. 23 0
      Tools/MiniWebApi/Network/BaseResponse.cs
  11. 44 0
      Tools/MiniWebApi/Network/IWebApiHttpRequest.cs
  12. 108 0
      Tools/MiniWebApi/Network/IWebApiHttpResponse.cs
  13. 21 0
      Tools/MiniWebApi/Network/WebApiHttpContext.cs
  14. 105 0
      Tools/MiniWebApi/Network/WebApiHttpRequest.cs
  15. 306 0
      Tools/MiniWebApi/Network/WebApiHttpResponse.cs
  16. 187 0
      Tools/MiniWebApi/Network/WebApiRouter.cs
  17. 121 0
      Tools/MiniWebApi/Network/WebApiServer.cs
  18. 46 0
      Tools/MiniWebApi/Utilities/ContextBase.cs
  19. 267 0
      Tools/MiniWebApi/Utilities/FormDataUploadHelper.cs
  20. 92 0
      Tools/MiniWebApi/Utilities/HttpContentParser.cs
  21. 16 0
      Tools/MiniWebApi/Utilities/HttpListenerHandler.cs
  22. 58 0
      Tools/MiniWebApi/Utilities/HttpMisc.cs
  23. 133 0
      Tools/MiniWebApi/Utilities/HttpMultipartParser.cs
  24. 232 0
      Tools/MiniWebApi/Utilities/MinmeMapping.cs
  25. 177 0
      Tools/MiniWebApi/Utilities/ObjectGenerator.cs
  26. 158 0
      Tools/MiniWebApi/Utilities/SessionBox.cs
  27. 39 0
      Tools/MiniWebApi/Utilities/SpinThread.cs
  28. 162 0
      Tools/MiniWebApi/Utilities/WebApiArgumentParser.cs
  29. 73 0
      Tools/MiniWebApi/Utilities/WebApiConverter.cs
  30. 34 0
      Tools/MiniWebApi/Utilities/WebApiHttpContentTypeConverter.cs
  31. 183 0
      Tools/MiniWebApi/Utilities/WebApiMethodMatcher.cs
  32. 107 0
      Tools/MiniWebApi/Utilities/WebApiUrlInfoParser.cs
  33. 14 0
      Tools/MiniWebApi/Utilities/XssException.cs
  34. 157 0
      Tools/SmsTool/Controllers/SendSmsController.cs
  35. 18 121
      Tools/SmsTool/Program.cs
  36. 6 2
      Tools/SmsTool/SmsTool.csproj
  37. 190 0
      Tools/SmsTool/ViewEngine/ApiExtends.cs
  38. 163 0
      Tools/SmsTool/ViewEngine/ConvertHelper.cs
  39. 86 0
      Tools/SmsTool/ViewEngine/DefenseInjectionHelper.cs
  40. 0 12
      Tools/SmsTool/appsettings.Development.json
  41. 0 12
      Tools/SmsTool/appsettings.json
  42. BIN
      src/Tools/win/sms.exe

+ 181 - 0
Tools/MiniWebApi/Handler/Attributes.cs

@@ -0,0 +1,181 @@
+using System;
+
+namespace MiniWebApi.Handler
+{
+    /// <summary>
+    /// Attribute for the handler.
+    /// </summary>
+    public class WebApiHandlerAttribute : Attribute
+    { 
+        /// <summary>
+        /// Gets the Url string
+        /// </summary>
+        public string Name { get; }
+
+
+        /// <summary>
+        /// Gets the version of the handler.
+        /// </summary>
+        public int Version { get; }
+
+
+        /// <summary>
+        /// Router to api handler with url string
+        /// </summary>
+        /// <param name="name">The url string</param>
+        public WebApiHandlerAttribute(string name)
+        {
+            Version = 1;
+            Name = name;
+        }
+
+        /// <summary>
+        /// Router to api handler with url string
+        /// </summary>
+        /// <param name="name">The url string</param>
+        /// <param name="version">The api version</param>
+        public WebApiHandlerAttribute(string name, int version = 1)
+        {
+            Version = version;
+            Name = name;
+        }
+    }
+
+
+    /// <summary>
+    /// The WebApi Attribute, which could be Get,Put,Post,Delete...
+    /// </summary>
+    public abstract class WebApiMethodAttribute : Attribute
+    {
+        /// <summary>
+        /// Gets the name of this method for web api.
+        /// </summary>
+        public string Name { get; }
+
+        protected WebApiMethodAttribute()
+        {
+            Name = string.Empty;
+        }
+
+        protected WebApiMethodAttribute(string name)
+        {
+            Name = name;
+        }
+
+        /// <summary>
+        /// Get the WebApiType from this Attribute.
+        /// </summary>
+        /// <returns>The WebApiType</returns>
+        public abstract WebApiType ToWebApiType();
+    }
+
+    /// <inheritdoc />
+    /// <summary>
+    /// Get attribute
+    /// </summary>
+    public class GetAttribute : WebApiMethodAttribute
+    {
+
+        public GetAttribute()
+        {
+        }
+
+        public GetAttribute(string name):base(name)
+        {
+           
+        }
+
+        public override WebApiType ToWebApiType()
+        {
+            return WebApiType.Get;
+        }
+    }
+
+    /// <inheritdoc />
+    /// <summary>
+    /// Put attribute
+    /// </summary>
+    public class PutAttribute : WebApiMethodAttribute
+    {
+
+        public PutAttribute()
+        {
+        }
+
+        public PutAttribute(string name) : base(name)
+        {
+
+        }
+
+        public override WebApiType ToWebApiType()
+        {
+            return WebApiType.Put;
+        }
+    }
+
+    /// <inheritdoc />
+    /// <summary>
+    /// Post attribute
+    /// </summary>
+    public class PostAttribute : WebApiMethodAttribute
+    {
+        public PostAttribute()
+        {
+        }
+
+        public PostAttribute(string name) : base(name)
+        {
+
+        }
+
+        public override WebApiType ToWebApiType()
+        {
+            return WebApiType.Post;
+        }
+    }
+
+    /// <inheritdoc />
+    /// <summary>
+    /// Delete attribute
+    /// </summary>
+    public class DeleteAttribute : WebApiMethodAttribute
+    {
+
+        public DeleteAttribute()
+        {
+        }
+
+        public DeleteAttribute(string name) : base(name)
+        {
+
+        }
+
+        public override WebApiType ToWebApiType()
+        {
+            return WebApiType.Delete;
+        }
+    }
+
+    /// <summary>
+    /// Defines the parent for FromUrl and FromBody.
+    /// </summary>
+    public class WebApiParameterAttribute : Attribute
+    {
+
+    }
+
+    /// <summary>
+    /// FromUrl attribute
+    /// </summary>
+    public class FromUrlAttribute: WebApiParameterAttribute
+    {
+
+    }
+
+    /// <summary>
+    /// FromBody attribute
+    /// </summary>
+    public class FromBodyAttribute: WebApiParameterAttribute
+    {
+    }
+}

+ 173 - 0
Tools/MiniWebApi/Handler/BaseHandler.cs

@@ -0,0 +1,173 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection.Emit;
+using MiniWebApi.Network;
+
+namespace MiniWebApi.Handler
+{
+    public class BaseHandler
+    {
+        private readonly Dictionary<string, CallingMethod> _callingMethods = new Dictionary<string, CallingMethod>();
+
+        public BaseHandler()
+        {
+            RegisterCallingMethods();
+        }
+
+        private void RegisterCallingMethods()
+        {
+            //Load all methods.
+            var methods = GetType().GetMethods();
+            foreach (var method in methods.Where(x => x.IsVirtual == false && x.IsStatic == false && x.ReturnType == typeof(void)))
+            {
+                //Get if the method support WebApiAttribute
+                var methodWebApiAttrs = method.GetCustomAttributes(typeof(WebApiMethodAttribute), true);
+                if (methodWebApiAttrs.Length == 0)
+                {
+                    //If no WebApiAttribute, do not add it into the method dictionary.
+                    continue;
+                }
+
+                if (methodWebApiAttrs.Length >1)
+                {
+                    // Can not define more than 2 attributes
+                    throw new InvalidOperationException($"Method {method.Name} defined more than one WebApi method attributes.");
+                }
+
+                if (!method.IsPublic)
+                {
+                    //Web api method should be public
+                    throw new InvalidOperationException($"Method {method.Name} should be public for WebApi method.");
+                }
+
+                var webApiMethodAttribute = (WebApiMethodAttribute)methodWebApiAttrs[0];
+
+                var methodWebApiType = webApiMethodAttribute.ToWebApiType();
+                var methodName = string.IsNullOrWhiteSpace(webApiMethodAttribute.Name)
+                    ? method.Name
+                    : webApiMethodAttribute.Name;
+
+                //Get all parameters
+                var parameters = method.GetParameters();
+
+                //If the first parameter is not WebApiHttpContext throw the exception
+                if (parameters[0].ParameterType != typeof(WebApiHttpContext))
+                {
+                    throw new InvalidDataException($"The first argument of method {method.Name} must be WebApiHttpContext");
+                }
+
+                //Generate CallingParameters
+                var paramInfos = new List<CallingParameter>();
+                foreach (var parameterInfo in parameters)
+                {
+                    if (parameterInfo.ParameterType == typeof(WebApiHttpContext))
+                    {
+                        continue;
+                    }
+                    var fromType = FromType.None;
+                    //Get if the parameter support WebApiAttribute
+                    var paramWebApiAttrs = parameterInfo.GetCustomAttributes(typeof(WebApiParameterAttribute), true);
+                    if (paramWebApiAttrs.Length > 0)
+                    {
+                        if (paramWebApiAttrs[0] is FromUrlAttribute)
+                        {
+                            fromType = FromType.FromUrl;
+                        }
+                        else if (paramWebApiAttrs[0] is FromBodyAttribute)
+                        {
+                            fromType = FromType.FromBody;
+                        }
+                    }
+                    paramInfos.Add(new CallingParameter(parameterInfo.Name, parameterInfo.ParameterType, fromType));
+                }
+
+                //More than one params' FormType is FromUrl
+                if (paramInfos.Count(x => x.FromType == FromType.FromUrl) > 1)
+                {
+                    throw new InvalidOperationException($"Method {method.Name} defined more than one [FromUrl] parameters.");
+                }
+
+                //More than one params' FormType is FromBody
+                if (paramInfos.Count(x => x.FromType == FromType.FromBody) > 1)
+                {
+                    throw new InvalidOperationException($"Method {method.Name} defined more than one [FromBody] parameters.");
+                }
+
+                //More than one normal params and one of params the FormType is FromUrl
+                if (paramInfos.Any(x => x.FromType == FromType.FromUrl) && paramInfos.Any(x=>x.FromType == FromType.None))
+                {
+                    throw new InvalidOperationException($"Only one [FromUrl] parameter can be define in Method {method.Name} without any other parameters.");
+                }
+
+                //Get Method can not contains FromBody
+                if (methodWebApiType == WebApiType.Get && paramInfos.Any(x => x.FromType == FromType.FromBody))
+                {
+                    throw new InvalidOperationException($"Get method {method.Name} can not contains [FromBody] parameter.");
+                }
+
+                //Generate the delegate by Emit.
+                var dynamicMethod = new DynamicMethod("", null, new[] { typeof(object), typeof(object[]) }, GetType().Module);
+                var il = dynamicMethod.GetILGenerator();
+
+                //Put the first arg in stack which is this object..
+                il.Emit(OpCodes.Ldarg_S, 0);
+                //Cast the object to the real type.
+                il.Emit(OpCodes.Castclass, GetType());
+
+                //Put WebApiHttpContext
+                il.Emit(OpCodes.Ldarg_S, 1);
+                //Put an integer value in stack which is the index in object[]
+                il.Emit(OpCodes.Ldc_I4_S, 0);
+                //Get the reference of index which is the integer value from the object[].
+                il.Emit(OpCodes.Ldelem_Ref);
+                //Cast or unbox the reference.
+                il.Emit(OpCodes.Castclass, typeof(WebApiHttpContext));
+
+                //Put all args which is object[] in stack.
+                for (var i = 0; i < paramInfos.Count; i++)
+                {
+                    var parameterInfo = paramInfos[i];
+                    //Put the arg 1 which is object[] in stack.
+                    il.Emit(OpCodes.Ldarg_S, 1);
+                    //Put an integer value in stack which is the index in object[]
+                    il.Emit(OpCodes.Ldc_I4_S, i+1);
+                    //Get the reference of index which is the integer value from the object[].
+                    il.Emit(OpCodes.Ldelem_Ref);
+                    //Cast or unbox the reference.
+                    il.Emit(parameterInfo.ParameterType.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, parameterInfo.ParameterType);
+                }
+                //Call the method.
+                il.Emit(OpCodes.Call, method);
+                //Exit the method.
+                il.Emit(OpCodes.Ret);
+
+                //Create the delegate by dynamic method.
+                var action = (Action<object, object[]>)dynamicMethod.CreateDelegate(typeof(Action<object, object[]>));
+                var callingMethod = new CallingMethod(methodName.ToLower(), methodWebApiType, paramInfos, this, new WebApiMethod(action));
+                _callingMethods.Add(callingMethod.Name, callingMethod);
+            }
+
+        }
+
+        /// <summary>
+        /// Get the CallingMethod from the handler by name.
+        /// </summary>
+        /// <param name="name">The name of the CallingMethod, if is null, will use the parameters to find the method.</param>
+        /// <returns>The CallingMethod, null if not exist.</returns>
+        public CallingMethod GetCallingMethod(string name)
+        {
+            return !_callingMethods.ContainsKey(name) ? null : _callingMethods[name];
+        }
+
+        /// <summary>
+        /// Get all calling method provided by this handler.
+        /// </summary>
+        /// <returns>All CallingMethods</returns>
+        public CallingMethod[] GetCallingMethods()
+        {
+            return _callingMethods.Values.ToArray();
+        }
+    }
+}

+ 63 - 0
Tools/MiniWebApi/Handler/CallingMethod.cs

@@ -0,0 +1,63 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MiniWebApi.Handler
+{
+    /// <summary>
+    /// CallingMethod contains the WebApi method, Call the Call method to call the WebApi.
+    /// </summary>
+    public class CallingMethod
+    {
+        /// <summary>
+        /// The this object for call, the class method need the this object for the first argument.
+        /// </summary>
+        private readonly object _thisObject;
+
+        /// <summary>
+        /// The WebApi to call.
+        /// </summary>
+        private readonly IWebApiMethod _method;
+
+        /// <summary>
+        /// Gets the name of the method.
+        /// </summary>
+        public string Name { get; }
+
+        /// <summary>
+        /// Gets the information for the parameters. 
+        /// </summary>
+        public IReadOnlyList<CallingParameter> Parameters { get; }
+
+        /// <summary>
+        /// Gets the WebApiType of the method.
+        /// </summary>
+        public WebApiType WebApiType { get; }
+
+        /// <summary>
+        /// Create the CallingMethod.
+        /// </summary>
+        /// <param name="name">The name of the method.</param>
+        /// <param name="webApiType">The WebApi Type of the method.</param>
+        /// <param name="parameters">The parameters information of the method.</param>
+        /// <param name="thisObject">The object which contains the WebApi Method.</param>
+        /// <param name="method">The method to call the WebApi.</param>
+        public CallingMethod(string name, WebApiType webApiType, IEnumerable<CallingParameter> parameters, object thisObject, IWebApiMethod method)
+        {
+            Name = name;
+            WebApiType = webApiType;
+            Parameters = parameters.ToList();
+            _thisObject = thisObject;
+            _method = method;
+        }
+
+        /// <summary>
+        /// Call the WebApi method.
+        /// </summary>
+        /// <param name="webApiArguments"></param>
+        public void Call(WebApiArgument[] webApiArguments)
+        {
+            var args = webApiArguments.Select(x => x.Value).ToArray();
+            _method.Call(_thisObject, args);
+        }
+    }
+}

+ 54 - 0
Tools/MiniWebApi/Handler/CallingParameter.cs

@@ -0,0 +1,54 @@
+using System;
+
+namespace MiniWebApi.Handler
+{
+    public enum FromType
+    {
+        /// <summary>
+        /// SimpleType
+        /// </summary>
+        None,
+        /// <summary>
+        /// The object is from Url, can not to large.
+        /// </summary>
+        FromUrl,
+        /// <summary>
+        /// The object is from body.
+        /// </summary>
+        FromBody
+    }
+
+    /// <summary>
+    /// The parameter information for the calling method.
+    /// </summary>
+    public class CallingParameter
+    {
+        /// <summary>
+        /// Gets the parameter's name.
+        /// </summary>
+        public string Name { get; }
+
+        /// <summary>
+        /// Gets the parameter's type.
+        /// </summary>
+        public Type ParameterType { get; }
+
+        /// <summary>
+        /// Gets the From type of this parameter.
+        /// </summary>
+        public FromType FromType { get; }
+
+        /// <summary>
+        /// Create the CallingParameter.
+        /// </summary>
+        /// <param name="name">The name of the parameter.</param>
+        /// <param name="parameterType">The type of the parameter.</param>
+        /// <param name="fromType">The From type of this parameter.</param>
+        public CallingParameter(string name, Type parameterType, FromType fromType)
+        {
+            Name = name;
+            ParameterType = parameterType;
+            FromType = fromType;
+        }
+    }
+}

+ 12 - 0
Tools/MiniWebApi/Handler/IWebApiMethod.cs

@@ -0,0 +1,12 @@
+namespace MiniWebApi.Handler
+{
+    public interface IWebApiMethod
+    {
+        /// <summary>
+        /// Call the WebApi implementation
+        /// </summary>
+        /// <param name="thisObject">The this object which will pass to the method for the first argument</param>
+        /// <param name="args">The arguments for the WebApi method.</param>
+        void Call(object thisObject, object[] args);
+    }
+}

+ 26 - 0
Tools/MiniWebApi/Handler/WebApiArgument.cs

@@ -0,0 +1,26 @@
+namespace MiniWebApi.Handler
+{
+    public class WebApiArgument
+    {
+        /// <summary>
+        /// Gets the name of the parameter.
+        /// </summary>
+        public string Name { get; }
+
+        /// <summary>
+        /// Gets the value of the parameter.
+        /// </summary>
+        public object Value { get; }
+
+        /// <summary>
+        /// Create the WebApiParameter
+        /// </summary>
+        /// <param name="name">The name of the parameter.</param>
+        /// <param name="value">The value of the parameter.</param>
+        public WebApiArgument(string name, object value)
+        {
+            Name = name;
+            Value = value;
+        }
+    }
+}

+ 34 - 0
Tools/MiniWebApi/Handler/WebApiMethod.cs

@@ -0,0 +1,34 @@
+using System;
+
+namespace MiniWebApi.Handler
+{
+    /// <summary>
+    /// Default implementation for the IWebApiMethod
+    /// </summary>
+    internal class WebApiMethod : IWebApiMethod
+    {
+        /// <summary>
+        /// The action which to call the WebApi.
+        /// </summary>
+        private readonly Action<object, object[]> _action;
+
+        /// <summary>
+        /// Create a WebApiMethod which implemented the IWebApiMethod
+        /// </summary>
+        /// <param name="action">The delegate to call the WebApi</param>
+        public WebApiMethod(Action<object, object[]> action)
+        {
+            _action = action;
+        }
+
+        /// <summary>
+        /// Call the WebApi implementation
+        /// </summary>
+        /// <param name="thisObject">The this object which will pass to the method for the first argument</param>
+        /// <param name="args">The arguments for the WebApi method.</param>
+        public void Call(object thisObject, object[] args)
+        {
+            _action(thisObject, args);
+        }
+    }
+}

+ 10 - 0
Tools/MiniWebApi/Handler/WebApiType.cs

@@ -0,0 +1,10 @@
+namespace MiniWebApi.Handler
+{
+    public enum WebApiType
+    {
+        Get,
+        Post,
+        Put,
+        Delete,
+    }
+}

+ 8 - 0
Tools/MiniWebApi/MiniWebApi.csproj

@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net6.0</TargetFramework>
+    <AssemblyName>MiniWebApi</AssemblyName>
+    <RootNamespace>MiniWebApi</RootNamespace>
+  </PropertyGroup>
+</Project>

+ 23 - 0
Tools/MiniWebApi/Network/BaseResponse.cs

@@ -0,0 +1,23 @@
+namespace MiniWebApi.Network
+{
+    /// <summary>
+    /// Base response entity
+    /// </summary>
+    public class BaseResponse
+    {
+        /// <summary>
+        /// response status
+        /// </summary>
+        public int Status { get; set; } = 200;
+
+        /// <summary>
+        /// response message
+        /// </summary>
+        public string Message { get; set; } = string.Empty;
+
+        /// <summary>
+        /// response content
+        /// </summary>
+        public object Content { get; set; }
+    }
+}

+ 44 - 0
Tools/MiniWebApi/Network/IWebApiHttpRequest.cs

@@ -0,0 +1,44 @@
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.IO;
+using System.Net;
+
+namespace MiniWebApi.Network
+{
+    public interface IWebApiHttpRequest
+    {
+        /// <summary>
+        /// Gets the query string of this request.
+        /// </summary>
+        NameValueCollection QueryString { get; }
+
+        /// <summary>
+        /// Get InputStreamValue
+        /// </summary>
+        /// <returns></returns>
+        string GetInputStreamValue();
+
+        /// <summary>
+        ///  Get header value
+        /// </summary>
+        NameValueCollection GetHeader();
+
+        /// <summary>
+        /// Get user host name
+        /// </summary>
+        /// <returns></returns>
+        IPEndPoint GetRemoteEndPoint();
+
+        /// <summary>
+        /// Form data type Analysis
+        /// </summary>
+        /// <returns></returns>
+        Dictionary<string, object> GetFormDataValues();
+
+        /// <summary>
+        /// Get input files
+        /// </summary>
+        /// <returns></returns>
+        Stream GetFiles();
+    }
+}

+ 108 - 0
Tools/MiniWebApi/Network/IWebApiHttpResponse.cs

@@ -0,0 +1,108 @@
+using System.Collections.Specialized;
+using System.Net;
+
+namespace MiniWebApi.Network
+{
+    public interface IWebApiHttpResponse
+    {
+        /// <summary>
+        /// Gets or sets the result status code.
+        /// </summary>
+        int StatusCode { get; set; }
+
+        /// <summary>
+        /// Gets or sets the result content type
+        /// </summary>
+        string ContentType { get; set; }
+
+        /// <summary>
+        ///  Gets or sets the result content length64
+        /// </summary>
+        long ContentLength64 { get; set; }
+
+        /// <summary>
+        ///  Get header value
+        /// </summary>
+        NameValueCollection GetHeader();
+
+        /// <summary>
+        /// Add header item
+        /// </summary>
+        /// <param name="name"></param>
+        /// <param name="value"></param>
+        public void AddHeader(string name, string value);
+
+        /// <summary>
+        /// Write an object back to the requester.
+        /// </summary>
+        void Write(object obj);
+
+        /// <summary>
+        /// Write binary back to the requester.
+        /// </summary>
+        /// <param name="data">The data to write</param>
+        void Write(byte[] data);
+
+        /// <summary>
+        /// return standard response entity
+        /// </summary>
+        /// <param name="content">content</param>
+        /// <param name="statusCode">status code</param>
+        /// <param name="ignoreProps">ignore props</param>
+        void Content(object content, HttpStatusCode statusCode = HttpStatusCode.OK, System.Text.Json.JsonSerializerOptions settings = null);
+
+        /// <summary>
+        /// return not found status
+        /// </summary>
+        void NotFound();
+
+        /// <summary>
+        /// return Json response
+        /// </summary>
+        /// <param name="obj">object</param>
+        /// <returns></returns>
+        void Json(object obj, bool useCamelCase = false);
+
+        /// <summary>
+        /// download file from path
+        /// </summary>
+        /// <param name="contentType">contentType</param>
+        /// <param name="filePath">filePath</param>
+        /// <param name="downloadFileName">download file name</param>
+        void Download(string contentType, string filePath, string downloadFileName);
+
+        /// <summary>
+        /// Write image
+        /// </summary>
+        /// <param name="imageBytes">image bytes</param>
+        void WriteImage(byte[] imageBytes);
+
+        /// <summary>
+        /// Write an OK to client without content.
+        /// </summary>
+        void WriteOk();
+
+        /// <summary>
+        /// Redirect
+        /// </summary>
+        /// <param name="url">redirect location</param>
+        void Redirect(string url);
+
+        /// <summary>
+        /// Clear Cookie
+        /// </summary>
+        /// <param name="key"></param>
+        void ClearCookie(string key);
+
+        /// <summary>
+        /// Close Response
+        /// </summary>
+        void Close();
+
+        /// <summary>
+        /// 保持长连接写入,完成后不会自动关闭 ,需要手动关闭
+        /// </summary>
+        /// <param name="data"></param>
+        void KeepWrite(byte[] data);
+    }
+}

+ 21 - 0
Tools/MiniWebApi/Network/WebApiHttpContext.cs

@@ -0,0 +1,21 @@
+namespace MiniWebApi.Network
+{
+    public class WebApiHttpContext
+    {
+        /// <summary>
+        /// Gets the request from the caller.
+        /// </summary>
+        public IWebApiHttpRequest Request { get; }
+
+        /// <summary>
+        /// Gets the response to write back.
+        /// </summary>
+        public IWebApiHttpResponse Response { get; }
+
+        public WebApiHttpContext(IWebApiHttpRequest request, IWebApiHttpResponse response)
+        {
+            Request = request;
+            Response = response;
+        }
+    }
+}

+ 105 - 0
Tools/MiniWebApi/Network/WebApiHttpRequest.cs

@@ -0,0 +1,105 @@
+using MiniWebApi.Utilities;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System;
+
+namespace MiniWebApi.Network
+{
+    class WebApiHttpRequest : IWebApiHttpRequest
+    {
+        private HttpListenerContext _context;
+        public WebApiHttpRequest(HttpListenerContext context)
+        {
+            _context = context;
+        }
+
+        /// <summary>
+        /// Gets the query string of this request
+        /// </summary>
+        public NameValueCollection QueryString { get { return _context.Request.QueryString; } }
+
+        /// <summary>
+        /// Get InputStreamValue
+        /// </summary>
+        /// <returns></returns>
+        public string GetInputStreamValue()
+        {
+            int readLength = 0;
+            List<byte> contentBytes = new List<byte>();
+            do
+            {
+                byte[] bytes = new byte[2048];
+                readLength = _context.Request.InputStream.Read(bytes, 0, bytes.Length);
+                if (readLength > 0)
+                {
+                    contentBytes.AddRange(bytes.Take(readLength));
+                }
+            }
+            while (readLength > 0);
+            string contentStr = Encoding.UTF8.GetString(contentBytes.ToArray());
+            contentBytes = null;
+            return contentStr;
+        }
+
+        /// <summary>
+        ///  Get header value
+        /// </summary>
+        public NameValueCollection GetHeader()
+        {
+            return _context.Request.Headers;
+        }
+
+        /// <summary>
+        /// Get user host name
+        /// </summary>
+        /// <returns></returns>
+        public IPEndPoint GetRemoteEndPoint()
+        {
+            return _context.Request.RemoteEndPoint;
+        }
+
+        /// <summary>
+        /// Get fomr-data values
+        /// </summary>
+        /// <returns></returns>
+        public Dictionary<string, object> GetFormDataValues()
+        {
+            Dictionary<string, object> analysisDir = new Dictionary<string, object>();
+            var boundary = FormDataUploadHelper.GetBoundary(_context.Request.ContentType);
+            if (!string.IsNullOrWhiteSpace(boundary))
+            {
+                Console.WriteLine("Start GetFormDataValues");
+                analysisDir = FormDataUploadHelper.AnalysisFile(Encoding.UTF8, boundary, _context.Request.InputStream);
+                Console.WriteLine("End GetFormDataValues");
+            }
+            return analysisDir;
+        }
+
+        /// <summary>
+        /// Get input files
+        /// </summary>
+        /// <returns></returns>
+        public Stream GetFiles()
+        {
+            if (_context.Request.InputStream != null && _context.Request.InputStream.CanRead)
+            {
+                MemoryStream memoryStream = new MemoryStream();
+                int readByteLength = 0;
+                byte[] bytes = new byte[204800];
+                do
+                {
+                    readByteLength = _context.Request.InputStream.Read(bytes, 0, bytes.Length);
+                    memoryStream.Write(bytes, 0, readByteLength);
+                }
+                while (readByteLength > 0);
+                return memoryStream;
+            }
+            return null;
+
+        }
+    }
+}

+ 306 - 0
Tools/MiniWebApi/Network/WebApiHttpResponse.cs

@@ -0,0 +1,306 @@
+using MiniWebApi.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.IO;
+using System.Net;
+using System.Text;
+
+namespace MiniWebApi.Network
+{
+    class WebApiHttpResponse : IWebApiHttpResponse
+    {
+        private readonly HttpListenerContext _context;
+
+        /// <summary>
+        /// Gets or sets the status code.
+        /// </summary>
+        public int StatusCode { get; set; }
+
+        public string ContentType { get { return _context.Response.ContentType; } set { _context.Response.ContentType = value; } }
+
+        public long ContentLength64 { get { return _context.Response.ContentLength64; } set { _context.Response.ContentLength64 = value; } }
+
+        public WebApiHttpResponse(HttpListenerContext context)
+        {
+            _context = context;
+            StatusCode = _context.Response.StatusCode;
+        }
+
+        /// <summary>
+        /// Write an object back to the caller.
+        /// </summary>
+        /// <param name="obj"></param>
+        public void Write(object obj)
+        {
+            try
+            {
+                if (obj == null)
+                {
+                    _context.Response.StatusCode = StatusCode;
+                    _context.Response.OutputStream.Close();
+                }
+                else
+                {
+                    _context.Response.StatusCode = StatusCode;
+                    _context.Response.ContentEncoding = Encoding.UTF8;
+                    _context.Response.ContentType = "application/json";
+                    var result = Encoding.UTF8.GetBytes(WebApiConverter.ObjectToJson(obj));
+                    WriteReponseBytes(result);
+                }
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Write object back to client error:{ex}");
+            }
+        }
+
+        /// <summary>
+        ///  Get header value
+        /// </summary>
+        public NameValueCollection GetHeader()
+        {
+            return _context.Response.Headers;
+        }
+
+        /// <summary>
+        /// Add header item
+        /// </summary>
+        /// <param name="name"></param>
+        /// <param name="value"></param>
+        public void AddHeader(string name,string value)
+        {
+            _context.Response.Headers.Add(name, value);
+        }
+
+        /// <summary>
+        /// Write binary back to the requester.
+        ///it can not close response
+        /// </summary>
+        /// <param name="data"></param>
+        public void KeepWrite(byte[] data)
+        {
+            if (data == null || data.Length == 0)
+            {
+                _context.Response.StatusCode = StatusCode;
+            }
+            else
+            {
+                _context.Response.StatusCode = StatusCode;
+                WriteReponseBytes(data, true);
+            }
+        }
+
+
+        /// <summary>
+        /// Write binary back to the requester.
+        /// </summary>
+        /// <param name="data">The data to write</param>
+        public void Write(byte[] data)
+        {
+            try
+            {
+                if (data == null || data.Length == 0)
+                {
+                    _context.Response.StatusCode = StatusCode;
+                    _context.Response.OutputStream.Close();
+                }
+                else
+                {
+                    _context.Response.StatusCode = StatusCode;
+                    WriteReponseBytes(data);
+                }
+            }
+            catch (HttpListenerException ex)
+            {
+                if (ex.ErrorCode == 64)
+                {
+                    // request abort.
+                    Console.WriteLine("【ERROR】Request Abort.");
+                }
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine("【ERROR】" + ex);
+            }
+        }
+
+        /// <summary>
+        /// return standard response entity
+        /// </summary>
+        /// <param name="content">content</param>
+        /// <param name="statusCode">status code</param>
+        /// <param name="ignoreProps">ignore props</param>
+        public void Content(object content, HttpStatusCode statusCode = HttpStatusCode.OK, System.Text.Json.JsonSerializerOptions settings = null)
+        {
+            _context.Response.ContentEncoding = Encoding.UTF8;
+            _context.Response.ContentType = "application/json";
+            byte[] result = Encoding.UTF8.GetBytes(System.Text.Json.JsonSerializer.Serialize(new BaseResponse
+            {
+                Status = statusCode.GetHashCode(),
+                Message = statusCode.ToString(),
+                Content = content
+            }, settings));
+            WriteReponseBytes(result);
+        }
+
+        /// <summary>
+        /// return not found status
+        /// </summary>
+        public void NotFound()
+        {
+            Content(null, HttpStatusCode.NotFound);
+        }
+
+        /// <summary>
+        /// get api version
+        /// </summary>
+        /// <returns></returns>
+        public int GetVersion()
+        {
+            int version = Convert.ToInt32(_context.Request.QueryString["v"] ?? "0");
+            return version == 0 ? 1 : version;
+        }
+
+        byte[] GetInputBytes(string method)
+        {
+            if (method != "put" && method != "post") return new byte[0];
+            using (var inputStream = _context.Request.InputStream)
+            {
+                List<byte> bytes = new List<byte>();
+                while (inputStream.CanRead)
+                {
+                    byte[] byts = new byte[10240];
+                    int byteLen = inputStream.Read(byts, 0, byts.Length);
+                    if (byteLen < 1) break;
+                    bytes.AddRange(byts);
+                }
+                return bytes.ToArray();
+            }
+        }
+
+        /// <summary>
+        /// return Json response
+        /// </summary>
+        /// <param name="obj">object</param>
+        /// <returns></returns>
+        public void Json(object obj, bool useCamelCase = false)
+        {
+            //设置序列化时key为驼峰样式  
+            System.Text.Json.JsonSerializerOptions settings = new();
+            settings.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase;//启用驼峰格式
+            settings.WriteIndented = true;
+            _context.Response.ContentEncoding = Encoding.UTF8;
+            _context.Response.ContentType = "application/json";
+            byte[] result = Encoding.UTF8.GetBytes(useCamelCase ? System.Text.Json.JsonSerializer.Serialize(obj, settings) : System.Text.Json.JsonSerializer.Serialize(obj));
+            WriteReponseBytes(result);
+        }
+
+        /// <summary>
+        /// download file from path
+        /// </summary>
+        /// <param name="contentType">contentType</param>
+        /// <param name="filePath">filePath</param>
+        /// <param name="downloadFileName">download file name</param>
+        public void Download(string contentType, string filePath, string downloadFileName)
+        {
+            _context.Response.ContentType = contentType;
+            _context.Response.AddHeader("Content-Disposition", $"attachment;fileName={downloadFileName}");
+            using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
+            {
+                byte[] array = new byte[fs.Length];
+                fs.Read(array, 0, array.Length);
+                WriteReponseBytes(array);
+            }
+        }
+
+        /// <summary>
+        /// Write image
+        /// </summary>
+        /// <param name="imageBytes">image bytes</param>
+        public void WriteImage(byte[] imageBytes)
+        {
+            _context.Response.ContentType = "image/jpeg";
+            WriteReponseBytes(imageBytes);
+        }
+
+        /// <summary>
+        /// Write an OK to client without content.
+        /// </summary>
+        public void WriteOk()
+        {
+            _context.Response.StatusCode = (int)HttpStatusCode.NoContent;
+            _context.Response.OutputStream.Close();
+        }
+
+        /// <summary>
+        /// Redirect
+        /// </summary>
+        /// <param name="url">redirect location</param>
+        public void Redirect(string url)
+        {
+            url = url.Replace("<", "").Replace(">", "");
+            _context.Response.Redirect(url);
+            _context.Response.Close();
+        }
+
+        public void ClearCookie(string key)
+        {
+            Cookie currentSessionCookie = _context.Response.Cookies[key];
+            Cookie sessionCookie = currentSessionCookie ?? new Cookie(key, null);
+            sessionCookie.Expires = DateTime.Now.AddDays(-1);
+            sessionCookie.Path = "/";
+            if (currentSessionCookie == null)
+            {
+                _context.Response.SetCookie(sessionCookie);
+            }
+        }
+
+        /// <summary>
+        /// Close Response
+        /// </summary>
+        public void Close()
+        {
+            _context.Response.Close();
+        }
+
+        private void WriteReponseBytes(byte[] bytes, bool keep = false)
+        {
+            try
+            {
+                if (!keep)
+                {
+                    _context.Response.ContentLength64 = bytes.Length;
+                }
+                if (_context.Response.OutputStream.CanWrite)
+                {
+                    _context.Response.OutputStream.Write(bytes, 0, bytes.Length);
+                }
+                if (!keep)
+                {
+                    _context.Response.OutputStream.Flush();
+                    _context.Response.OutputStream.Close();
+                }
+            }
+            catch (Exception ex)
+            {
+                if (ex is HttpListenerException httpListenerException)
+                {
+                    if (httpListenerException.ErrorCode == 64)
+                    {
+                        // client aborted request.
+                        Console.WriteLine("[WriteReponseBytes]User aborted request.url:" + _context.Request.Url);
+                    }
+                    else
+                    {
+                        Console.WriteLine("[WriteReponseBytes]HttpListenerException" + ex);
+                    }
+                }
+                else
+                {
+                    Console.WriteLine("[WriteReponseBytes]" + ex);
+                }
+            }
+        }
+    }
+}

+ 187 - 0
Tools/MiniWebApi/Network/WebApiRouter.cs

@@ -0,0 +1,187 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Reflection;
+using System.Text;
+using MiniWebApi.Handler;
+using MiniWebApi.Utilities;
+
+namespace MiniWebApi.Network
+{
+    /// <summary>
+    /// WebApiRouter will find all implemented handler which inherit from the BaseHandler, and register into itself.
+    /// </summary>
+    internal class WebApiRouter
+    {
+        private readonly Dictionary<string, IWebApiArgumentParser> _parsers = new Dictionary<string, IWebApiArgumentParser>();
+        private readonly Dictionary<string, IWebApiMethodMatcher> _matchers = new Dictionary<string, IWebApiMethodMatcher>();
+        private readonly Dictionary<string, BaseHandler> _handlers = new Dictionary<string, BaseHandler>();
+
+        private readonly string _applicationName;
+        private Assembly _assembly;
+
+        public WebApiRouter(string applicationName, Assembly assembly)
+        {
+            _assembly = assembly;
+            _applicationName = applicationName;
+            _applicationName = _applicationName == null ? string.Empty : _applicationName.ToLower();
+            RegisterWebApiMethodMatchers();
+            RegisterWebApiArgumentParsers();
+            RegisterHandlers();
+            //Logger.WriteLineInfo($"WebApi Router created with application name:{applicationName}");
+        }
+
+        private void RegisterWebApiMethodMatchers()
+        {
+            var getMatcher = new GetMethodMatcher();
+            var postMatcher = new PostMethodMatcher();
+            var putMatcher = new PutMethodMatcher();
+            var deleteMatcher = new DeleteMethodMatcher();
+            _matchers.Add("get", getMatcher);
+            _matchers.Add("post", postMatcher);
+            _matchers.Add("put", putMatcher);
+            _matchers.Add("delete", deleteMatcher);
+        }
+
+        /// <summary>
+        /// Register all argument parser.
+        /// </summary>
+        private void RegisterWebApiArgumentParsers()
+        {
+            var getParser = new GetArgumentParser();
+            var postParser = new PostArgumentParser();
+            _parsers.Add("get", getParser);
+            _parsers.Add("post", postParser);
+            _parsers.Add("put", postParser);
+            _parsers.Add("delete", postParser);
+        }
+
+        /// <summary>
+        /// Register all handlers which implemented by users
+        /// </summary>
+        private void RegisterHandlers()
+        {
+            if(_assembly == null)
+            {
+                _assembly = Assembly.GetEntryAssembly();
+            }
+            var handlerTypes = _assembly. GetTypes().Where(x =>
+                typeof(BaseHandler).IsAssignableFrom(x) && x != typeof(BaseHandler)).ToArray();
+            foreach (var handlerType in handlerTypes)
+            {
+                var handlerAttrs = handlerType.GetCustomAttributes(typeof(WebApiHandlerAttribute), false);
+                if (handlerAttrs.Length > 0)
+                {
+                    var handlerAttr = (WebApiHandlerAttribute)handlerAttrs[0];
+                    if (string.IsNullOrEmpty(handlerAttr.Name)) continue;
+                    var key = $"v{handlerAttr.Version}/{handlerAttr.Name.ToLower()}";
+                    if (!_handlers.Keys.Contains(key))
+                    {
+                        _handlers.Add(key, (BaseHandler)handlerType.New());
+                    }
+                    //Logger.WriteLineInfo($"Register handler:{key}");
+                }
+            }
+        }
+
+        /// <summary>
+        /// Dispatch the http context to the router.
+        /// </summary>
+        /// <param name="context">The context to be dispatched.</param>
+        public void DispatchCall(HttpListenerContext context)
+        {
+            var handlerExist = false;
+            var httpMethod = context.Request.HttpMethod.ToLower();
+            //Logger.WriteLineInfo($"Handle request [{httpMethod}]: {context.Request.Url}");
+            try
+            {
+                var urlInfo = WebApiUrlInfoParser.Parser(_applicationName, context.Request.Url);
+                if (urlInfo != null)
+                {
+                    var key = $"{urlInfo.Version}/{urlInfo.HandlerName}";
+                    if (_handlers.ContainsKey(key))
+                    {
+                        var matcher = _matchers[httpMethod];
+                        CallingMethod callingMethod = null;
+                        if (!string.IsNullOrEmpty(urlInfo.ActionName))
+                        {
+                            callingMethod = _handlers[key].GetCallingMethod(urlInfo.ActionName);
+                            if (callingMethod != null)
+                            {
+                                if (!matcher.IsMatch(context, callingMethod))
+                                {
+                                    callingMethod = null;
+                                }
+                            }
+                        }
+                        else
+                        {
+                            var callingMethods = _handlers[key].GetCallingMethods();
+                            foreach (var method in callingMethods)
+                            {
+                                if (matcher.IsMatch(context, method))
+                                {
+                                    callingMethod = method;
+                                    break;
+                                }
+                            }
+                        }
+                        if (callingMethod != null)
+                        {
+                            if (_parsers.ContainsKey(httpMethod))
+                            {
+                                var parser = _parsers[httpMethod];
+                                var args = parser.Parse(context, callingMethod.Parameters.ToArray());
+                                //The parser will add context into the args, so the final count is parameter count + 1.
+                                if (args != null && args.Length == callingMethod.Parameters.Count + 1)
+                                {
+                                    handlerExist = true;
+                                    try
+                                    {
+                                        callingMethod.Call(args);
+                                    }
+                                    catch (Exception ex)
+                                    {
+                                        var argStr = new StringBuilder();
+                                        foreach (var arg in args)
+                                        {
+                                            argStr.AppendLine($"{arg.Name} = {arg.Value}");
+                                        }
+
+                                        Console.WriteLine($"{_applicationName} Call method {callingMethod.Name} with args:{Environment.NewLine + argStr + Environment.NewLine} error :{ex}");
+                                        
+                                        if (ex is XssException xss)
+                                        {
+                                            new WebApiHttpResponse(context).Json(new { success = false, msg = xss.Message });
+                                            return;
+                                        }
+                                        if (context != null && context.Response != null)
+                                        {
+                                            context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
+                                            context.Response.Close();
+                                        }
+
+                                    }
+
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Handle request error: {ex}");
+            }
+
+            if (!handlerExist)
+            {
+                Console.WriteLine($"Handler for request: {context.Request.Url} not found.");
+                context.Response.StatusCode = (int)HttpStatusCode.NotFound;
+                context.Response.Close();
+            }
+        }
+    }
+}

+ 121 - 0
Tools/MiniWebApi/Network/WebApiServer.cs

@@ -0,0 +1,121 @@
+using MiniWebApi.Utilities;
+using System;
+using System.Net;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace MiniWebApi.Network
+{
+    public class WebApiServer : IDisposable
+    {
+        private readonly WebApiRouter _router;
+        private HttpListener _listener;
+        private bool _disposed;
+        private bool _useSession;
+        private string _applicationName;
+        public WebApiServer(string applicationName, bool useSession = false, Assembly assembly= null)
+        {
+            _applicationName = applicationName;
+            _useSession = useSession;
+            _router = new WebApiRouter(applicationName, assembly);
+        }
+
+        ~WebApiServer()
+        {
+            DoDispose();
+        }
+
+        private void GetContextCallBack(IAsyncResult ar)
+        {
+            try
+            {
+                HttpListenerHandler listenerHandler = (HttpListenerHandler)ar.AsyncState;
+                var listener = listenerHandler.Listener;
+                HttpListenerContext context = listener.EndGetContext(ar);
+                listener.BeginGetContext(new AsyncCallback(GetContextCallBack), listenerHandler);
+                Task.Factory.StartNew(() =>
+                {
+                    try
+                    {
+                        BuilderSession(context);
+                        if (listenerHandler.BeforeAction != null && listenerHandler.BeforeAction.Invoke(context)) return;
+                        _router.DispatchCall(context);
+                        listenerHandler.AfterAction?.Invoke();
+                    }
+                    catch (HttpListenerException)
+                    {
+                        Console.WriteLine($"The specified network name is no longer available.");
+                    }
+                    catch (System.Exception ex)
+                    {
+                        Console.WriteLine($"Dispatch call error:{ex}");
+                    }
+                });
+            }
+            catch (HttpListenerException)
+            {
+                Console.WriteLine($"The specified network name is no longer available.");
+            }
+            catch (System.Exception ex)
+            {
+                Console.WriteLine($"GetContext error:{ex}");
+            }
+        }
+
+        private void BuilderSession(HttpListenerContext context)
+        {
+            if (_useSession)
+            {
+                if (!Regex.IsMatch(context.Request.RawUrl, "\\S*[\\.](css|gif|html|json|js|eot|svg|ttf|woff|woff2|jpg|png)"))
+                {
+                    var asyncLocalData = new System.Threading.AsyncLocal<HttpListenerContext>();
+                    asyncLocalData.Value = context;
+                    ContextBase.Current = new HttpContextData
+                    {
+                        AsyncLocalContext = asyncLocalData
+                    };
+                }
+            }
+        }
+
+
+        /// <summary>
+        /// start the web api service
+        /// </summary>
+        /// <param name="port">service port</param>
+        /// <param name="beforeAction">The method executed before the action, return true if you do not want to continue execution</param>
+        /// <param name="afterAction">The method executed after the action.</param>
+        public void Start(int port, Func<HttpListenerContext, bool> beforeAction = null, Action afterAction = null)
+        {
+            _listener = new HttpListener();
+            _listener.Prefixes.Add($"http://*:{port}/");
+            _listener.Start();
+            _listener.BeginGetContext(new AsyncCallback(GetContextCallBack),
+                new HttpListenerHandler { BeforeAction = beforeAction, AfterAction = afterAction, Listener = _listener });
+            Console.WriteLine($"{_applicationName} WebApiServer started on port:{port}");
+        }
+
+        public void Stop()
+        {
+            _listener.Close();
+            Console.WriteLine($"{_applicationName} WebApiServer stopped.");
+        }
+
+
+        private void DoDispose()
+        {
+            if (!_disposed)
+            {
+                Stop();
+                _disposed = true;
+            }
+        }
+
+        public void Dispose()
+        {
+            DoDispose();
+            GC.SuppressFinalize(this);
+        }
+    }
+}

+ 46 - 0
Tools/MiniWebApi/Utilities/ContextBase.cs

@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Concurrent;
+using System.Net;
+using System.Threading;
+
+namespace MiniWebApi.Utilities
+{
+    public static class ContextBase
+    {
+        private static ConcurrentDictionary<string, HttpContextData> _ContextDictionary = new ConcurrentDictionary<string, HttpContextData>();
+        public static HttpContextData Current
+        {
+            get
+            {
+                var key = Thread.CurrentThread.ManagedThreadId.ToString();
+                HttpContextData httpContext;
+                _ContextDictionary.TryGetValue(key, out httpContext);
+                return httpContext;
+            }
+            set
+            {
+                var key = Thread.CurrentThread.ManagedThreadId.ToString();
+                _ContextDictionary.AddOrUpdate(key, value, (k, v)=> {
+                    return value;
+                });
+            }
+        }
+    }
+
+    public class HttpContextData
+    {
+        public AsyncLocal<HttpListenerContext> AsyncLocalContext { get; set; }
+
+        public HttpListenerContext Context {
+            get {
+                return AsyncLocalContext.Value;
+            }
+        }
+
+        public SessionItem Session {
+            get {
+                return SessionBox.GetSession(AsyncLocalContext.Value);
+            }
+        }
+    }
+}

+ 267 - 0
Tools/MiniWebApi/Utilities/FormDataUploadHelper.cs

@@ -0,0 +1,267 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace MiniWebApi.Utilities
+{
+    public class FormDataUploadHelper
+    {
+        private FormDataUploadHelper() { }
+
+        /// <summary>
+        /// 获取 boundary 标识
+        /// </summary>
+        /// <param name="ctype"></param>
+        /// <returns></returns>
+        public static string GetBoundary(string ctype)
+        {
+            if (string.IsNullOrWhiteSpace(ctype)) { return string.Empty; }
+            return "--" + ctype.Split(';')[1].Split('=')[1];
+        }
+
+        /// <summary>
+        /// 解析FormData格式数据
+        /// </summary>
+        /// <param name="enc"></param>
+        /// <param name="boundary"></param>
+        /// <param name="inputArg"></param>
+        /// <returns></returns>
+        public static Dictionary<string, object> AnalysisFile(Encoding enc, string boundary, Stream inputArg)
+        {
+            try
+            {
+                //获取标志
+                byte[] boundaryBytes = enc.GetBytes(boundary);
+                byte[] fileStartTag = enc.GetBytes("\r\n\r\n");
+                List<byte> byteBuffer = new List<byte>();
+                Dictionary<string, object> values = new Dictionary<string, object>();
+                byte[] firstBoundary = new byte[boundaryBytes.Length];
+                inputArg.Read(firstBoundary, 0, firstBoundary.Length);
+                var readLength = 0;
+                do
+                {
+                    int findIndex = -1;
+                    int startEndTag = 0;
+                    int loopCount = 0;
+                    readLength = 0;
+                    Console.WriteLine($"GetFormDataValues AnalysisFile readLength={readLength}");
+                    do
+                    {
+                        var fileHeadStr = Encoding.UTF8.GetString(byteBuffer.ToArray());
+                        if (IsMatch(fileHeadStr, "Content-Disposition: form-data; name=\"file\";"))
+                        {
+                            byteBuffer.Clear();
+                            FindArgTag(inputArg, fileStartTag, byteBuffer);
+                            readLength = ReadArgData(inputArg, boundaryBytes, byteBuffer);
+                            AddArg(byteBuffer, values);
+                            return values;
+                        }
+                        if (startEndTag < 1)
+                        {
+                            byte[] boundaryFindByte;
+                            FindBoundaryTag(inputArg, boundaryBytes, byteBuffer, out readLength, out findIndex, out boundaryFindByte);
+                            if (findIndex > -1)
+                            {
+                                byteBuffer.Clear();
+                                //通过正则获取到文件信息
+                                fileHeadStr = Encoding.UTF8.GetString(boundaryFindByte.Take(findIndex).ToArray());
+                                byteBuffer.AddRange(boundaryFindByte.Skip(findIndex + boundaryBytes.Length));
+                                var key = MatchSub(fileHeadStr, "Content-Disposition: form-data; name=\"(\\S*)\"");
+                                if (string.IsNullOrWhiteSpace(key))
+                                {
+                                    break;
+                                }
+                                var value = MatchSub(fileHeadStr, "\r\n\r\n([\\s\\S\r\n]*)\r\n");
+                                var uploadFileName = MatchSub(fileHeadStr, "Content-Disposition: form-data; name=\"\\S*\"; filename=\"(\\S*)\"");
+                                if (!string.IsNullOrWhiteSpace(uploadFileName))
+                                {
+                                    value = uploadFileName;
+                                    values.Add("fileName", value);
+                                    var contentType = MatchSub(fileHeadStr, "Content-Disposition: form-data; name=\"\\S*\"; filename=\"\\S*\"\r\nContent-Type: (\\S*/\\S*)");
+                                    values.Add("ContentType", contentType);
+                                    break;
+                                }
+                                AddParams(values, key, value);
+                                startEndTag++;
+                            }
+                        }
+                        loopCount++;
+                    }
+                    while (startEndTag < 1);
+                } while (readLength > 0);
+                return values;
+            }
+            catch (Exception ex)
+            {
+                return new Dictionary<string, object>();
+            }
+        }
+
+        private static void AddParams(Dictionary<string, object> values, string key, string value)
+        {
+            if (values.Keys.Contains(key))
+            {
+                if (values[key] is List<string> pairs)
+                {
+                    pairs.Add(value);
+                }
+                else
+                {
+                    List<string> newPairs = new List<string>();
+                    var convertValue = values[key] as string;
+                    newPairs.Add(convertValue);
+                    newPairs.Add(value);
+                    values[key] = newPairs;
+                }
+            }
+            else
+            {
+                values.Add(key, value);
+            }
+        }
+
+        /// <summary>
+        /// 查找标记符号
+        /// </summary>
+        /// <param name="inputArg"></param>
+        /// <param name="boundaryBytes"></param>
+        /// <param name="byteBuffer"></param>
+        /// <param name="readLength"></param>
+        /// <param name="findIndex"></param>
+        /// <param name="boundaryFindByte"></param>
+        private static void FindBoundaryTag(Stream inputArg, byte[] boundaryBytes, List<byte> byteBuffer, out int readLength, out int findIndex, out byte[] boundaryFindByte)
+        {
+            boundaryFindByte = new byte[boundaryBytes.Length];
+            readLength = inputArg.Read(boundaryFindByte, 0, boundaryFindByte.Length);
+            byteBuffer.AddRange(boundaryFindByte);
+            boundaryFindByte = byteBuffer.ToArray();
+            findIndex = IndexOf(boundaryFindByte, boundaryFindByte.Length, boundaryBytes);
+        }
+
+        /// <summary>
+        /// 增加参数
+        /// </summary>
+        /// <param name="byteBuffer"></param>
+        /// <param name="values"></param>
+        private static void AddArg(List<byte> byteBuffer, Dictionary<string, object> values)
+        {
+            var realArgLen = byteBuffer.Count - 2;
+            if (realArgLen > 0)
+            {
+                var fileData = byteBuffer.Take(realArgLen).ToArray();
+                values.Add("fileData", fileData);
+            }
+        }
+
+        /// <summary>
+        /// 读取参数信息
+        /// </summary>
+        /// <param name="inputArg"></param>
+        /// <param name="boundaryBytes"></param>
+        /// <param name="byteBuffer"></param>
+        /// <returns></returns>
+        private static int ReadArgData(Stream inputArg, byte[] boundaryBytes, List<byte> byteBuffer)
+        {
+            int readLength;
+            byte[] readBuffer = new byte[8192];
+            do
+            {
+                readLength = inputArg.Read(readBuffer, 0, readBuffer.Length);
+                int indexPos = IndexOf(readBuffer, readBuffer.Length, boundaryBytes);
+                if (indexPos > 0)
+                {
+                    byteBuffer.AddRange(readBuffer.Take(indexPos));
+                }
+                else
+                {
+                    byteBuffer.AddRange(readBuffer);
+                }
+            } while (readLength == readBuffer.Length);
+            return readLength;
+        }
+
+        /// <summary>
+        /// 查找参数位置
+        /// </summary>
+        /// <param name="inputArg"></param>
+        /// <param name="fileStartTag"></param>
+        /// <param name="byteBuffer"></param>
+        private static void FindArgTag(Stream inputArg, byte[] fileStartTag, List<byte> byteBuffer)
+        {
+            int blank;
+            while ((blank = inputArg.ReadByte()) > -1)
+            {
+                byteBuffer.Add((byte)blank);
+                if (IndexOf(byteBuffer.ToArray(), byteBuffer.Count, fileStartTag) > -1)
+                {
+                    byteBuffer.Clear();
+                    break;
+                }
+            }
+        }
+
+        /// <summary>
+        /// 找到位置执行操作
+        /// </summary>
+        /// <param name="buffer"></param>
+        /// <param name="len"></param>
+        /// <param name="boundaryBytes"></param>
+        /// <param name="action"></param>
+        private static void WhenFindDo(byte[] buffer, int len, byte[] boundaryBytes,Action action)
+        {
+            if (IndexOf(buffer, len, boundaryBytes) > -1)
+            {
+                action();
+            }
+        }
+
+        /// <summary>
+        /// 查找开始标志在流中的位置
+        /// </summary>
+        /// <param name="buffer">读取的字节</param>
+        /// <param name="len">读取字节的长度</param>
+        /// <param name="boundaryBytes">需要查找的标志</param>
+        /// <returns></returns>
+        private static int IndexOf(byte[] buffer, int len, byte[] boundaryBytes)
+        {
+            len = len - boundaryBytes.Length;
+            for (int i = 0; i <= len; i++)
+            {
+                bool match = true;
+                //比对流中的每一字节如果存在不相等则进入下个循环,否则返回位置
+                for (int j = 0; j < boundaryBytes.Length; j++)
+                {
+                    if (buffer[i + j] != boundaryBytes[j])
+                    {
+                        match = false;
+                        break;
+                    }
+                }
+                //所有字节都匹配返回位置
+                if (match)
+                {
+                    return i;
+                }
+            }
+            //如果找不到则返回-1
+            return -1;
+        }
+
+        private static string MatchSub(string input, string pattern, RegexOptions regexOptions = RegexOptions.IgnoreCase)
+        {
+            var match = Regex.Match(input, pattern, regexOptions);
+            if (match.Success && match.Groups.Count > 1)
+            {
+                return match.Groups[1].Value;
+            }
+            return string.Empty;
+        }
+        private static bool IsMatch(string input, string pattern, RegexOptions regexOptions = RegexOptions.IgnoreCase)
+        {
+            return Regex.IsMatch(input, pattern, regexOptions);
+        }
+    }
+}

+ 92 - 0
Tools/MiniWebApi/Utilities/HttpContentParser.cs

@@ -0,0 +1,92 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace MiniWebApi.Utilities
+{
+    public class HttpContentParser
+    {
+        public IDictionary<string, string> Parameters = new Dictionary<string, string>();
+
+        public bool Success
+        {
+            get;
+            private set;
+        }
+
+        public string Content
+        {
+            get;
+            private set;
+        }
+
+        public HttpContentParser(Stream stream)
+        {
+            Parse(stream, Encoding.UTF8);
+        }
+
+        public HttpContentParser(Stream stream, Encoding encoding)
+        {
+            Parse(stream, encoding);
+        }
+
+        private void Parse(Stream stream, Encoding encoding)
+        {
+            Success = false;
+
+            // Read the stream into a byte array
+            byte[] data = HttpMisc.ToByteArray(stream);
+
+            // Copy to a string for header parsing
+            Content = encoding.GetString(data);
+
+            var name = string.Empty;
+            var value = string.Empty;
+            var lookForValue = false;
+            var charCount = 0;
+
+            foreach (var c in Content)
+            {
+                if (c == '=')
+                {
+                    lookForValue = true;
+                }
+                else if (c == '&')
+                {
+                    lookForValue = false;
+                    AddParameter(name, value);
+                    name = string.Empty;
+                    value = string.Empty;
+                }
+                else if (!lookForValue)
+                {
+                    name += c;
+                }
+                else
+                {
+                    value += c;
+                }
+
+                if (++charCount == Content.Length)
+                {
+                    AddParameter(name, value);
+                    break;
+                }
+            }
+
+            // Get the start & end indexes of the file contents
+            //int startIndex = nameMatch.Index + nameMatch.Length + "\r\n\r\n".Length;
+            //Parameters.Add(name, s.Substring(startIndex).TrimEnd(new char[] { '\r', '\n' }).Trim());
+
+            // If some data has been successfully received, set success to true
+            if (Parameters.Count != 0)
+                Success = true;
+        }
+
+        private void AddParameter(string name, string value)
+        {
+            if (!string.IsNullOrWhiteSpace(name) && !string.IsNullOrWhiteSpace(value) && !Parameters.ContainsKey(name.Trim()))
+                Parameters.TryAdd(name.Trim(), value.Trim());
+        }
+    }
+}

+ 16 - 0
Tools/MiniWebApi/Utilities/HttpListenerHandler.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Text;
+
+namespace MiniWebApi.Utilities
+{
+    public class HttpListenerHandler
+    {
+        public Func<HttpListenerContext, bool> BeforeAction { get; set; } = null;
+
+        public Action AfterAction { get; set; } = null;
+
+        public HttpListener Listener { get; set; }
+    }
+}

+ 58 - 0
Tools/MiniWebApi/Utilities/HttpMisc.cs

@@ -0,0 +1,58 @@
+using System;
+using System.IO;
+
+namespace MiniWebApi.Utilities
+{
+    public static class HttpMisc
+    {
+        public static int IndexOf(byte[] searchWithin, byte[] searchFor, int startIndex)
+        {
+            int index = 0;
+            int startPos = Array.IndexOf(searchWithin, searchFor[0], startIndex);
+
+            if (startPos != -1)
+            {
+                while ((startPos + index) < searchWithin.Length)
+                {
+                    if (searchWithin[startPos + index] == searchFor[index])
+                    {
+                        index++;
+                        if (index == searchFor.Length)
+                        {
+                            return startPos;
+                        }
+                    }
+                    else
+                    {
+                        startPos = Array.IndexOf(searchWithin, searchFor[0], startPos + index);
+                        if (startPos == -1)
+                        {
+                            return -1;
+                        }
+                        index = 0;
+                    }
+                }
+            }
+
+            return -1;
+        }
+
+        public static byte[] ToByteArray(Stream stream)
+        {
+            byte[] buffer = new byte[32768];
+            using (MemoryStream ms = new MemoryStream())
+            {
+                while (true)
+                {
+                    int read = stream.Read(buffer, 0, buffer.Length);
+                    if (read <= 0)
+                    {
+                        buffer = null;
+                        return ms.ToArray();
+                    }
+                    ms.Write(buffer, 0, read);
+                }
+            }
+        }
+    }
+}

+ 133 - 0
Tools/MiniWebApi/Utilities/HttpMultipartParser.cs

@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace MiniWebApi.Utilities
+{
+    public class HttpMultipartParser
+    {
+        public IDictionary<string, string> Parameters = new Dictionary<string, string>();
+
+        public string FilePartName
+        {
+            get;
+        }
+
+        public bool Success
+        {
+            get;
+            private set;
+        }
+
+        public string ContentType
+        {
+            get;
+            private set;
+        }
+
+        public string Filename
+        {
+            get;
+            private set;
+        }
+
+        public byte[] FileContents
+        {
+            get;
+            private set;
+        }
+
+        public HttpMultipartParser(Stream stream, string filePartName)
+        {
+            FilePartName = filePartName;
+            Parse(stream, Encoding.UTF8);
+        }
+
+        public HttpMultipartParser(Stream stream, Encoding encoding, string filePartName)
+        {
+            FilePartName = filePartName;
+            Parse(stream, encoding);
+        }
+
+        private void Parse(Stream stream, Encoding encoding)
+        {
+            Success = false;
+
+            // Read the stream into a byte array
+            var data = HttpMisc.ToByteArray(stream);
+
+            // Copy to a string for header parsing
+            var content = encoding.GetString(data);
+
+            // The first line should contain the delimiter
+            var delimiterEndIndex = content.IndexOf("\r\n", StringComparison.InvariantCultureIgnoreCase);
+
+            if (delimiterEndIndex > -1)
+            {
+                var delimiter = content.Substring(0, content.IndexOf("\r\n", StringComparison.InvariantCultureIgnoreCase));
+
+                var sections = content.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries);
+
+                foreach (string s in sections)
+                {
+                    if (s.Contains("Content-Disposition"))
+                    {
+                        // If we find "Content-Disposition", this is a valid multi-part section
+                        // Now, look for the "name" parameter
+                        Match nameMatch = new Regex(@"(?<=name\=\"")(.*?)(?=\"")").Match(s);
+                        string name = nameMatch.Value.Trim().ToLower();
+
+                        if (name == FilePartName)
+                        {
+                            // Look for Content-Type
+                            var re = new Regex(@"(?<=Content\-Type:)(.*?)(?=\r\n\r\n)");
+                            var contentTypeMatch = re.Match(content);
+
+                            // Look for filename
+                            re = new Regex(@"(?<=filename\=\"")(.*?)(?=\"")");
+                            var filenameMatch = re.Match(content);
+
+                            // Did we find the required values?
+                            if (contentTypeMatch.Success && filenameMatch.Success)
+                            {
+                                // Set properties
+                                ContentType = contentTypeMatch.Value.Trim();
+                                Filename = filenameMatch.Value.Trim();
+
+                                // Get the start & end indexes of the file contents
+                                var startIndex = contentTypeMatch.Index + contentTypeMatch.Length + "\r\n\r\n".Length;
+
+                                var delimiterBytes = encoding.GetBytes("\r\n" + delimiter);
+                                var endIndex = HttpMisc.IndexOf(data, delimiterBytes, startIndex);
+
+                                var contentLength = endIndex - startIndex;
+
+                                // Extract the file contents from the byte array
+                                var fileData = new byte[contentLength];
+
+                                Buffer.BlockCopy(data, startIndex, fileData, 0, contentLength);
+
+                                data = null;
+
+                                FileContents = fileData;
+                            }
+                        }
+                        else if (!string.IsNullOrWhiteSpace(name))
+                        {
+                            // Get the start & end indexes of the file contents
+                            int startIndex = nameMatch.Index + nameMatch.Length + "\r\n\r\n".Length;
+                            Parameters.Add(name, s.Substring(startIndex).TrimEnd('\r', '\n').Trim());
+                        }
+                    }
+                }
+
+                // If some data has been successfully received, set success to true
+                if (FileContents != null || Parameters.Count != 0)
+                    Success = true;
+            }
+        }
+
+    }
+}

+ 232 - 0
Tools/MiniWebApi/Utilities/MinmeMapping.cs

@@ -0,0 +1,232 @@
+using System;
+using System.Collections;
+
+namespace MiniWebApi.Utilities
+{
+    public static class MimeMapping
+    {
+        private static Hashtable _mimeMappingTable;
+
+        private static void AddMimeMapping(string extension, string MimeType)
+        {
+            MimeMapping._mimeMappingTable.Add(extension, MimeType);
+        }
+
+        public static string GetMimeMapping(string FileName)
+        {
+            string text = null;
+            int num = FileName.LastIndexOf('.');
+            if (0 < num && num > FileName.LastIndexOf('\\'))
+            {
+                text = (string)MimeMapping._mimeMappingTable[FileName.Substring(num)];
+            }
+            if (text == null)
+            {
+                text = (string)MimeMapping._mimeMappingTable[".*"];
+            }
+            return text;
+        }
+
+        static MimeMapping()
+        {
+            MimeMapping._mimeMappingTable = new Hashtable(190, StringComparer.CurrentCultureIgnoreCase);
+            MimeMapping.AddMimeMapping(".323", "text/h323");
+            MimeMapping.AddMimeMapping(".asx", "video/x-ms-asf");
+            MimeMapping.AddMimeMapping(".acx", "application/internet-property-stream");
+            MimeMapping.AddMimeMapping(".ai", "application/postscript");
+            MimeMapping.AddMimeMapping(".aif", "audio/x-aiff");
+            MimeMapping.AddMimeMapping(".aiff", "audio/aiff");
+            MimeMapping.AddMimeMapping(".axs", "application/olescript");
+            MimeMapping.AddMimeMapping(".aifc", "audio/aiff");
+            MimeMapping.AddMimeMapping(".asr", "video/x-ms-asf");
+            MimeMapping.AddMimeMapping(".avi", "video/x-msvideo");
+            MimeMapping.AddMimeMapping(".asf", "video/x-ms-asf");
+            MimeMapping.AddMimeMapping(".au", "audio/basic");
+            MimeMapping.AddMimeMapping(".application", "application/x-ms-application");
+            MimeMapping.AddMimeMapping(".bin", "application/octet-stream");
+            MimeMapping.AddMimeMapping(".bas", "text/plain");
+            MimeMapping.AddMimeMapping(".bcpio", "application/x-bcpio");
+            MimeMapping.AddMimeMapping(".bmp", "image/bmp");
+            MimeMapping.AddMimeMapping(".cdf", "application/x-cdf");
+            MimeMapping.AddMimeMapping(".cat", "application/vndms-pkiseccat");
+            MimeMapping.AddMimeMapping(".crt", "application/x-x509-ca-cert");
+            MimeMapping.AddMimeMapping(".c", "text/plain");
+            MimeMapping.AddMimeMapping(".css", "text/css");
+            MimeMapping.AddMimeMapping(".cer", "application/x-x509-ca-cert");
+            MimeMapping.AddMimeMapping(".crl", "application/pkix-crl");
+            MimeMapping.AddMimeMapping(".cmx", "image/x-cmx");
+            MimeMapping.AddMimeMapping(".csh", "application/x-csh");
+            MimeMapping.AddMimeMapping(".cod", "image/cis-cod");
+            MimeMapping.AddMimeMapping(".cpio", "application/x-cpio");
+            MimeMapping.AddMimeMapping(".clp", "application/x-msclip");
+            MimeMapping.AddMimeMapping(".crd", "application/x-mscardfile");
+            MimeMapping.AddMimeMapping(".deploy", "application/octet-stream");
+            MimeMapping.AddMimeMapping(".dll", "application/x-msdownload");
+            MimeMapping.AddMimeMapping(".dot", "application/msword");
+            MimeMapping.AddMimeMapping(".doc", "application/msword");
+            MimeMapping.AddMimeMapping(".dvi", "application/x-dvi");
+            MimeMapping.AddMimeMapping(".dir", "application/x-director");
+            MimeMapping.AddMimeMapping(".dxr", "application/x-director");
+            MimeMapping.AddMimeMapping(".der", "application/x-x509-ca-cert");
+            MimeMapping.AddMimeMapping(".dib", "image/bmp");
+            MimeMapping.AddMimeMapping(".dcr", "application/x-director");
+            MimeMapping.AddMimeMapping(".disco", "text/xml");
+            MimeMapping.AddMimeMapping(".exe", "application/octet-stream");
+            MimeMapping.AddMimeMapping(".etx", "text/x-setext");
+            MimeMapping.AddMimeMapping(".evy", "application/envoy");
+            MimeMapping.AddMimeMapping(".eml", "message/rfc822");
+            MimeMapping.AddMimeMapping(".eps", "application/postscript");
+            MimeMapping.AddMimeMapping(".flr", "x-world/x-vrml");
+            MimeMapping.AddMimeMapping(".fif", "application/fractals");
+            MimeMapping.AddMimeMapping(".gtar", "application/x-gtar");
+            MimeMapping.AddMimeMapping(".gif", "image/gif");
+            MimeMapping.AddMimeMapping(".gz", "application/x-gzip");
+            MimeMapping.AddMimeMapping(".hta", "application/hta");
+            MimeMapping.AddMimeMapping(".htc", "text/x-component");
+            MimeMapping.AddMimeMapping(".htt", "text/webviewhtml");
+            MimeMapping.AddMimeMapping(".h", "text/plain");
+            MimeMapping.AddMimeMapping(".hdf", "application/x-hdf");
+            MimeMapping.AddMimeMapping(".hlp", "application/winhlp");
+            MimeMapping.AddMimeMapping(".html", "text/html");
+            MimeMapping.AddMimeMapping(".htm", "text/html");
+            MimeMapping.AddMimeMapping(".hqx", "application/mac-binhex40");
+            MimeMapping.AddMimeMapping(".isp", "application/x-internet-signup");
+            MimeMapping.AddMimeMapping(".iii", "application/x-iphone");
+            MimeMapping.AddMimeMapping(".ief", "image/ief");
+            MimeMapping.AddMimeMapping(".ivf", "video/x-ivf");
+            MimeMapping.AddMimeMapping(".ins", "application/x-internet-signup");
+            MimeMapping.AddMimeMapping(".ico", "image/x-icon");
+            MimeMapping.AddMimeMapping(".jpg", "image/jpeg");
+            MimeMapping.AddMimeMapping(".jfif", "image/pjpeg");
+            MimeMapping.AddMimeMapping(".jpe", "image/jpeg");
+            MimeMapping.AddMimeMapping(".jpeg", "image/jpeg");
+            MimeMapping.AddMimeMapping(".js", "application/x-javascript");
+            MimeMapping.AddMimeMapping(".lsx", "video/x-la-asf");
+            MimeMapping.AddMimeMapping(".latex", "application/x-latex");
+            MimeMapping.AddMimeMapping(".lsf", "video/x-la-asf");
+            MimeMapping.AddMimeMapping(".manifest", "application/x-ms-manifest");
+            MimeMapping.AddMimeMapping(".mhtml", "message/rfc822");
+            MimeMapping.AddMimeMapping(".mny", "application/x-msmoney");
+            MimeMapping.AddMimeMapping(".mht", "message/rfc822");
+            MimeMapping.AddMimeMapping(".mid", "audio/mid");
+            MimeMapping.AddMimeMapping(".mpv2", "video/mpeg");
+            MimeMapping.AddMimeMapping(".man", "application/x-troff-man");
+            MimeMapping.AddMimeMapping(".mvb", "application/x-msmediaview");
+            MimeMapping.AddMimeMapping(".mpeg", "video/mpeg");
+            MimeMapping.AddMimeMapping(".m3u", "audio/x-mpegurl");
+            MimeMapping.AddMimeMapping(".mdb", "application/x-msaccess");
+            MimeMapping.AddMimeMapping(".mpp", "application/vnd.ms-project");
+            MimeMapping.AddMimeMapping(".m1v", "video/mpeg");
+            MimeMapping.AddMimeMapping(".mpa", "video/mpeg");
+            MimeMapping.AddMimeMapping(".me", "application/x-troff-me");
+            MimeMapping.AddMimeMapping(".m13", "application/x-msmediaview");
+            MimeMapping.AddMimeMapping(".movie", "video/x-sgi-movie");
+            MimeMapping.AddMimeMapping(".m14", "application/x-msmediaview");
+            MimeMapping.AddMimeMapping(".mpe", "video/mpeg");
+            MimeMapping.AddMimeMapping(".mp2", "video/mpeg");
+            MimeMapping.AddMimeMapping(".mov", "video/quicktime");
+            MimeMapping.AddMimeMapping(".mp3", "audio/mpeg");
+            MimeMapping.AddMimeMapping(".mpg", "video/mpeg");
+            MimeMapping.AddMimeMapping(".ms", "application/x-troff-ms");
+            MimeMapping.AddMimeMapping(".nc", "application/x-netcdf");
+            MimeMapping.AddMimeMapping(".nws", "message/rfc822");
+            MimeMapping.AddMimeMapping(".oda", "application/oda");
+            MimeMapping.AddMimeMapping(".ods", "application/oleobject");
+            MimeMapping.AddMimeMapping(".pmc", "application/x-perfmon");
+            MimeMapping.AddMimeMapping(".p7r", "application/x-pkcs7-certreqresp");
+            MimeMapping.AddMimeMapping(".p7b", "application/x-pkcs7-certificates");
+            MimeMapping.AddMimeMapping(".p7s", "application/pkcs7-signature");
+            MimeMapping.AddMimeMapping(".pmw", "application/x-perfmon");
+            MimeMapping.AddMimeMapping(".ps", "application/postscript");
+            MimeMapping.AddMimeMapping(".p7c", "application/pkcs7-mime");
+            MimeMapping.AddMimeMapping(".pbm", "image/x-portable-bitmap");
+            MimeMapping.AddMimeMapping(".ppm", "image/x-portable-pixmap");
+            MimeMapping.AddMimeMapping(".pub", "application/x-mspublisher");
+            MimeMapping.AddMimeMapping(".pnm", "image/x-portable-anymap");
+            MimeMapping.AddMimeMapping(".png", "image/png");
+            MimeMapping.AddMimeMapping(".pml", "application/x-perfmon");
+            MimeMapping.AddMimeMapping(".p10", "application/pkcs10");
+            MimeMapping.AddMimeMapping(".pfx", "application/x-pkcs12");
+            MimeMapping.AddMimeMapping(".p12", "application/x-pkcs12");
+            MimeMapping.AddMimeMapping(".pdf", "application/pdf");
+            MimeMapping.AddMimeMapping(".pps", "application/vnd.ms-powerpoint");
+            MimeMapping.AddMimeMapping(".p7m", "application/pkcs7-mime");
+            MimeMapping.AddMimeMapping(".pko", "application/vndms-pkipko");
+            MimeMapping.AddMimeMapping(".ppt", "application/vnd.ms-powerpoint");
+            MimeMapping.AddMimeMapping(".pmr", "application/x-perfmon");
+            MimeMapping.AddMimeMapping(".pma", "application/x-perfmon");
+            MimeMapping.AddMimeMapping(".pot", "application/vnd.ms-powerpoint");
+            MimeMapping.AddMimeMapping(".prf", "application/pics-rules");
+            MimeMapping.AddMimeMapping(".pgm", "image/x-portable-graymap");
+            MimeMapping.AddMimeMapping(".qt", "video/quicktime");
+            MimeMapping.AddMimeMapping(".ra", "audio/x-pn-realaudio");
+            MimeMapping.AddMimeMapping(".rgb", "image/x-rgb");
+            MimeMapping.AddMimeMapping(".ram", "audio/x-pn-realaudio");
+            MimeMapping.AddMimeMapping(".rmi", "audio/mid");
+            MimeMapping.AddMimeMapping(".ras", "image/x-cmu-raster");
+            MimeMapping.AddMimeMapping(".roff", "application/x-troff");
+            MimeMapping.AddMimeMapping(".rtf", "application/rtf");
+            MimeMapping.AddMimeMapping(".rtx", "text/richtext");
+            MimeMapping.AddMimeMapping(".sv4crc", "application/x-sv4crc");
+            MimeMapping.AddMimeMapping(".spc", "application/x-pkcs7-certificates");
+            MimeMapping.AddMimeMapping(".setreg", "application/set-registration-initiation");
+            MimeMapping.AddMimeMapping(".snd", "audio/basic");
+            MimeMapping.AddMimeMapping(".stl", "application/vndms-pkistl");
+            MimeMapping.AddMimeMapping(".setpay", "application/set-payment-initiation");
+            MimeMapping.AddMimeMapping(".stm", "text/html");
+            MimeMapping.AddMimeMapping(".shar", "application/x-shar");
+            MimeMapping.AddMimeMapping(".sh", "application/x-sh");
+            MimeMapping.AddMimeMapping(".sit", "application/x-stuffit");
+            MimeMapping.AddMimeMapping(".spl", "application/futuresplash");
+            MimeMapping.AddMimeMapping(".sct", "text/scriptlet");
+            MimeMapping.AddMimeMapping(".scd", "application/x-msschedule");
+            MimeMapping.AddMimeMapping(".sst", "application/vndms-pkicertstore");
+            MimeMapping.AddMimeMapping(".src", "application/x-wais-source");
+            MimeMapping.AddMimeMapping(".sv4cpio", "application/x-sv4cpio");
+            MimeMapping.AddMimeMapping(".tex", "application/x-tex");
+            MimeMapping.AddMimeMapping(".tgz", "application/x-compressed");
+            MimeMapping.AddMimeMapping(".t", "application/x-troff");
+            MimeMapping.AddMimeMapping(".tar", "application/x-tar");
+            MimeMapping.AddMimeMapping(".tr", "application/x-troff");
+            MimeMapping.AddMimeMapping(".tif", "image/tiff");
+            MimeMapping.AddMimeMapping(".txt", "text/plain");
+            MimeMapping.AddMimeMapping(".texinfo", "application/x-texinfo");
+            MimeMapping.AddMimeMapping(".trm", "application/x-msterminal");
+            MimeMapping.AddMimeMapping(".tiff", "image/tiff");
+            MimeMapping.AddMimeMapping(".tcl", "application/x-tcl");
+            MimeMapping.AddMimeMapping(".texi", "application/x-texinfo");
+            MimeMapping.AddMimeMapping(".tsv", "text/tab-separated-values");
+            MimeMapping.AddMimeMapping(".ustar", "application/x-ustar");
+            MimeMapping.AddMimeMapping(".uls", "text/iuls");
+            MimeMapping.AddMimeMapping(".vcf", "text/x-vcard");
+            MimeMapping.AddMimeMapping(".wps", "application/vnd.ms-works");
+            MimeMapping.AddMimeMapping(".wav", "audio/wav");
+            MimeMapping.AddMimeMapping(".wrz", "x-world/x-vrml");
+            MimeMapping.AddMimeMapping(".wri", "application/x-mswrite");
+            MimeMapping.AddMimeMapping(".wks", "application/vnd.ms-works");
+            MimeMapping.AddMimeMapping(".wmf", "application/x-msmetafile");
+            MimeMapping.AddMimeMapping(".wcm", "application/vnd.ms-works");
+            MimeMapping.AddMimeMapping(".wrl", "x-world/x-vrml");
+            MimeMapping.AddMimeMapping(".wdb", "application/vnd.ms-works");
+            MimeMapping.AddMimeMapping(".wsdl", "text/xml");
+            MimeMapping.AddMimeMapping(".xap", "application/x-silverlight-app");
+            MimeMapping.AddMimeMapping(".xml", "text/xml");
+            MimeMapping.AddMimeMapping(".xlm", "application/vnd.ms-excel");
+            MimeMapping.AddMimeMapping(".xaf", "x-world/x-vrml");
+            MimeMapping.AddMimeMapping(".xla", "application/vnd.ms-excel");
+            MimeMapping.AddMimeMapping(".xls", "application/vnd.ms-excel");
+            MimeMapping.AddMimeMapping(".xof", "x-world/x-vrml");
+            MimeMapping.AddMimeMapping(".xlt", "application/vnd.ms-excel");
+            MimeMapping.AddMimeMapping(".xlc", "application/vnd.ms-excel");
+            MimeMapping.AddMimeMapping(".xsl", "text/xml");
+            MimeMapping.AddMimeMapping(".xbm", "image/x-xbitmap");
+            MimeMapping.AddMimeMapping(".xlw", "application/vnd.ms-excel");
+            MimeMapping.AddMimeMapping(".xpm", "image/x-xpixmap");
+            MimeMapping.AddMimeMapping(".xwd", "image/x-xwindowdump");
+            MimeMapping.AddMimeMapping(".xsd", "text/xml");
+            MimeMapping.AddMimeMapping(".z", "application/x-compress");
+            MimeMapping.AddMimeMapping(".zip", "application/x-zip-compressed");
+            MimeMapping.AddMimeMapping(".*", "application/octet-stream");
+        }
+    }
+}

+ 177 - 0
Tools/MiniWebApi/Utilities/ObjectGenerator.cs

@@ -0,0 +1,177 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace MiniWebApi.Utilities
+{
+    static class ObjectGenerator
+    {
+        private static readonly Dictionary<Type, Func<object>> Creators = new Dictionary<Type, Func<object>>();
+        private static readonly Dictionary<Type,Tuple<PropertyInfo[], Action<object, object[]>>> Setters = new Dictionary<Type, Tuple<PropertyInfo[], Action<object, object[]>>>();
+
+        /// <summary>
+        /// Create new object by using Emit, it will be much faster than the reflection.
+        /// </summary>
+        /// <param name="type">The type to create.</param>
+        /// <returns>The created object.</returns>
+        public static object New(this Type type)
+        {
+            if (!Creators.ContainsKey(type))
+            {
+                var defaultConstructor = type.GetConstructor(new Type[] { });
+                if (defaultConstructor == null)
+                {
+                    throw new InvalidOperationException($"No default constructor for type:{type}");
+                }
+                var dynamicMethod = new DynamicMethod(
+                    name: $"Creare_{type.Name}",
+                    returnType: typeof(object),
+                    parameterTypes: null);
+
+                var il = dynamicMethod.GetILGenerator();
+                il.Emit(OpCodes.Newobj, defaultConstructor);
+                il.Emit(OpCodes.Ret);
+                var creator = (Func<object>)dynamicMethod.CreateDelegate(typeof(Func<object>));
+                Creators.Add(type,creator);
+            }
+            return Creators[type]();
+        }
+
+
+        /// <summary>
+        /// Create new object by using Emit, it will be much faster than the reflection.
+        /// </summary>
+        /// <param name="type">The type to create.</param>
+        /// <param name="parameters">The key value of the parameters, they will be converted according to the types.</param>
+        /// <returns>The created object.</returns>
+        public static object New(this Type type, NameValueCollection parameters)
+        {
+            if (!Setters.ContainsKey(type))
+            {
+                var properties = type.GetProperties().Where(x => x.CanRead && x.CanWrite).ToArray();
+                var methodList = new List<MethodInfo>();
+                foreach (var property in properties)
+                {
+                    var method = type.GetMethod($"set_{property.Name}");
+                    methodList.Add(method);
+                }
+                var dynamicMethod = new DynamicMethod(
+                    name: $"Set_{type.Name}_Parameters",
+                    returnType: null,
+                    parameterTypes: new[] { typeof(object), typeof(object[]) });
+
+                var il2 = dynamicMethod.GetILGenerator();
+
+                for (var i = 0; i < properties.Length; i++)
+                {
+                    var property = properties[i];
+                    il2.Emit(OpCodes.Ldarg_0);
+                    il2.Emit(type.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, type);
+                    il2.Emit(OpCodes.Ldarg_1);
+                    //Put an integer value in stack which is the index in object[]
+                    il2.Emit(OpCodes.Ldc_I4_S, i);
+                    //Get the reference of index which is the integer value from the object[].
+                    il2.Emit(OpCodes.Ldelem_Ref);
+                    //Cast or unbox the reference.
+                    il2.Emit(property.PropertyType.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, property.PropertyType);
+                    il2.Emit(OpCodes.Callvirt, methodList[i]);
+                }
+                il2.Emit(OpCodes.Ret);
+                var setter = (Action<object, object[]>)dynamicMethod.CreateDelegate(typeof(Action<object, object[]>));
+                Setters.Add(type, new Tuple<PropertyInfo[], Action<object, object[]>>(properties, setter));
+            }
+            var obj = type.New();
+            var propertyArray = Setters[type].Item1;
+            var args = new List<object>();
+            foreach (var property in propertyArray)
+            {
+                if (parameters.AllKeys.Contains(property.Name))
+                {
+                    var propertyValue = WebApiConverter.StringToObject(parameters[property.Name], property.PropertyType);
+                    if (propertyValue != null && property.PropertyType == typeof(string))
+                    {
+                        propertyValue = Uri.UnescapeDataString((string)propertyValue);
+                    }
+                    args.Add(propertyValue);
+                }
+                else
+                {
+                    args.Add(property.PropertyType.Default());
+                }
+            }
+            Setters[type].Item2(obj, args.ToArray());
+            return obj;
+        }
+
+
+        /// <summary>
+        /// Get the default value for value type, null for reference type.
+        /// </summary>
+        /// <param name="type">The type for the value.</param>
+        /// <returns>The default value.</returns>
+        public static object Default(this Type type)
+        {
+            if (!type.IsValueType) return null;
+            if (type == typeof(byte))
+            {
+                return default(byte);
+            }
+            if (type == typeof(sbyte))
+            {
+                return default(sbyte);
+            }
+            if (type == typeof(short))
+            {
+                return default(short);
+            }
+            if (type == typeof(int))
+            {
+                return default(int);
+            }
+            if (type == typeof(float))
+            {
+                return default(float);
+            }
+            if (type == typeof(long))
+            {
+                return default(long);
+            }
+            if (type == typeof(double))
+            {
+                return default(double);
+            }
+            if (type == typeof(ushort))
+            {
+                return default(ushort);
+            }
+            if (type == typeof(uint))
+            {
+                return default(uint);
+            }
+            if (type == typeof(ulong))
+            {
+                return default(ulong);
+            }
+            if (type == typeof(char))
+            {
+                return default(char);
+            }
+            if (type == typeof(decimal))
+            {
+                return default(decimal);
+            }
+            if (type == typeof(bool))
+            {
+                return default(bool);
+            }
+            if (type.IsEnum)
+            {
+                return type.GetEnumValues().GetValue(0);
+            }
+            return type.New();
+        }
+    }
+}

+ 158 - 0
Tools/MiniWebApi/Utilities/SessionBox.cs

@@ -0,0 +1,158 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+
+namespace MiniWebApi.Utilities
+{
+    //自定义Session的操作类
+    public static class SessionBox
+    {
+        private static SessionClear _sessionClear = new SessionClear();
+        private static ConcurrentDictionary<string, DateTime> _SessionExpiresTimeDictionary = new ConcurrentDictionary<string, DateTime>();
+        private static ConcurrentDictionary<string, ConcurrentDictionary<string, object>> _SessionDictionary = new ConcurrentDictionary<string, ConcurrentDictionary<string, object>>();
+
+        static SessionBox()
+        {
+            _sessionClear.StartSession();
+        }
+
+        public static ConcurrentDictionary<string, DateTime> GetSessionExpiresTime()
+        {
+            return _SessionExpiresTimeDictionary;
+        }
+
+        public static ConcurrentDictionary<string, ConcurrentDictionary<string, object>> GetSessionDictionarys()
+        {
+            return _SessionDictionary;
+        }
+
+        public static SessionItem GetSession(this HttpListenerContext httpContext)
+        {
+            if (httpContext != null)
+            {
+                string SessionId = httpContext.Request.Cookies["sessionId"]?.Value;
+                if (string.IsNullOrWhiteSpace(SessionId))
+                {
+                    SessionId = Guid.NewGuid().ToString("N");
+                    var dic = new ConcurrentDictionary<string, object>();
+                    _SessionDictionary.AddOrUpdate(SessionId, key => dic, (key, value) => dic);
+                }
+                var expiresTime = DateTime.Now.AddHours(1);
+                _SessionExpiresTimeDictionary.AddOrUpdate(SessionId, key => expiresTime, (key, value) => expiresTime);
+                var sessionCookie = new Cookie("sessionId", SessionId);
+                sessionCookie.Expires = expiresTime;
+                sessionCookie.Path = "/";
+                sessionCookie.HttpOnly = true;
+                httpContext.Response.Headers["Cache-Control"] = "no-cache";
+                httpContext.Response.Headers["Pragma"] = "no-cache";
+                httpContext.Response.Headers["Expires"] = "-1";
+                httpContext.Response.AppendCookie(sessionCookie);
+                var item = new SessionItem();
+                item.SessionId = SessionId;
+                return item;
+            }
+            return null;
+        }
+
+        public static T GetSessionValue<T>(this SessionItem sesssionItem, string key) where T : class
+        {
+            try
+            {
+                if (sesssionItem!=null && !string.IsNullOrWhiteSpace(sesssionItem.SessionId) && _SessionDictionary.Keys.Contains(sesssionItem.SessionId))
+                {
+                    var valueDictionary = _SessionDictionary[sesssionItem.SessionId];
+                    if (!string.IsNullOrWhiteSpace(key) && valueDictionary.Keys.Contains(key))
+                    {
+                        return valueDictionary[key] as T;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine(ex.Message + "|" + ex.StackTrace);
+            }
+            return null;
+        }
+
+        public static bool SetSessionValue(this SessionItem sesssionItem, string key, object value)
+        {
+            try
+            {
+                if (_SessionDictionary.Keys.Contains(sesssionItem.SessionId))
+                {
+                    if (!string.IsNullOrWhiteSpace(key))
+                    {
+                        _SessionDictionary[sesssionItem.SessionId][key] = value;
+                        return true;
+                    }
+                }
+                else
+                {
+                    var dic = new ConcurrentDictionary<string, object>();
+                    dic.TryAdd(key, value);
+                    _SessionDictionary.TryAdd(sesssionItem.SessionId, dic);
+                }
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine(ex.Message + "|" + ex.StackTrace);
+            }
+            return false;
+        }
+
+        public static string GetString(this SessionItem sesssionItem, string key)
+        {
+            return GetSessionValue<string>(sesssionItem, key);
+        }
+
+        public static bool SetString(this SessionItem sesssionItem, string key, string value)
+        {
+            return SetSessionValue(sesssionItem, key, value);
+        }
+
+        public static int GetInt32(this SessionItem sesssionItem, string key)
+        {
+            return Convert.ToInt32(GetSessionValue<string>(sesssionItem, key));
+        }
+
+        public static bool SetInt32(this SessionItem sesssionItem, string key, int value)
+        {
+            return SetSessionValue(sesssionItem, key, value.ToString());
+        }
+    }
+
+    public class SessionClear : CheckSessionThread
+    {
+        private bool IsStartSession;
+
+        public void StartSession()
+        {
+            if (!IsStartSession)
+            {
+                lock (this)
+                {
+                    WriteRunning(() =>
+                    {
+                        var times = SessionBox.GetSessionExpiresTime();
+                        var expiresSession = times.Where(f => f.Value < DateTime.Now);
+                        foreach (var session in expiresSession)
+                        {
+                            var dictionarys = SessionBox.GetSessionDictionarys();
+                            times.TryRemove(session.Key, out _);
+                            dictionarys.TryRemove(session.Key, out _);
+                        }
+                    });
+                    IsStartSession = true;
+                }
+            }
+        }
+    }
+
+    public class SessionItem 
+    {
+        public string SessionId { get; set; }
+    }
+}

+ 39 - 0
Tools/MiniWebApi/Utilities/SpinThread.cs

@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MiniWebApi.Utilities
+{
+    public class CheckSessionThread
+    {
+        bool keepRunning = false;
+        public void WriteRunning(Action action)
+        {
+            keepRunning = true;
+            Task.Factory.StartNew(() =>
+            {
+                do
+                {
+                    try
+                    {
+                        action.Invoke();
+                    }
+                    catch { }
+                } while (IsKeepRunning());
+            });
+        }
+
+        private bool IsKeepRunning()
+        {
+            Thread.Sleep(500);//加入等待,否则会消耗CPU
+            return keepRunning;
+        }
+
+        public void Stop()
+        {
+            keepRunning = false;
+        }
+    }
+}

+ 162 - 0
Tools/MiniWebApi/Utilities/WebApiArgumentParser.cs

@@ -0,0 +1,162 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Net;
+using System.Text;
+using MiniWebApi.Handler;
+using MiniWebApi.Network;
+
+namespace MiniWebApi.Utilities
+{
+    /// <summary>
+    /// A Parser for getting the arguments from the http request.
+    /// </summary>
+    internal interface IWebApiArgumentParser
+    {
+        /// <summary>
+        /// Parse the arguments from the http request.
+        /// </summary>
+        /// <param name="context">The http context from HttpListener</param>
+        /// <param name="callingParameters">The calling parameters from the found method.</param>
+        /// <returns>All args parsed from the request.</returns>
+        WebApiArgument[] Parse(HttpListenerContext context, CallingParameter[] callingParameters);
+    }
+
+    /// <summary>
+    /// Parser for Get request.
+    /// </summary>
+    internal class GetArgumentParser : IWebApiArgumentParser
+    {
+        /// <summary>
+        /// Parse the arguments from the http request.
+        /// </summary>
+        /// <param name="context">The http context from HttpListener</param>
+        /// <param name="callingParameters">The calling parameters from the found method.</param>
+        /// <returns>All args parsed from the request.</returns>
+        public WebApiArgument[] Parse(HttpListenerContext context, CallingParameter[] callingParameters)
+        {
+            var queryData = context.Request.QueryString;
+            var webApiHttpContext = new WebApiHttpContext(new WebApiHttpRequest(context), new WebApiHttpResponse(context));
+            var args = new List<WebApiArgument> { new WebApiArgument("context", webApiHttpContext) };
+
+            foreach (var parameter in callingParameters)
+            {
+                var paramType = parameter.ParameterType;
+                object paramValue = null;
+                if (parameter.FromType == FromType.FromUrl)
+                {
+                    //Do not get the propertyInfo here, the new method recorded all propertyInfos in cache, no need reflection.
+                    paramValue = paramType.New(queryData);
+                }
+                else
+                {
+                    if (queryData.AllKeys.Contains(parameter.Name))
+                    {
+                        paramValue = WebApiConverter.StringToObject(queryData[parameter.Name], paramType);
+                        if (paramValue != null && paramType == typeof(string))
+                        {
+                            paramValue = Uri.UnescapeDataString((string)paramValue);
+                        }
+                    }
+                }
+                if (paramValue == null)
+                {
+                    paramValue = paramType.Default();
+                }
+                args.Add(new WebApiArgument(parameter.Name, paramValue));
+            }
+
+            var argsArray = args.ToArray();
+            args.Clear();
+            return argsArray;
+        }
+    }
+
+
+    /// <summary>
+    /// Parser for Post, Put, Delete request.
+    /// </summary>
+    internal class PostArgumentParser : IWebApiArgumentParser
+    {
+        /// <inheritdoc />
+        /// <summary>
+        /// Parse the arguments from the http request.
+        /// </summary>
+        /// <param name="context">The http context from HttpListener</param>
+        /// <param name="callingParameters">The calling parameters from the found method.</param>
+        /// <returns>All args parsed from the request.</returns>
+        public WebApiArgument[] Parse(HttpListenerContext context, CallingParameter[] callingParameters)
+        {
+            var contentType = WebApiHttpContentTypeConverter.ToContentType(context.Request.ContentType);
+            var queryData = context.Request.QueryString;
+            var webApiHttpContext = new WebApiHttpContext(new WebApiHttpRequest(context), new WebApiHttpResponse(context));
+            var args = new List<WebApiArgument> { new WebApiArgument("context", webApiHttpContext) };
+
+            foreach (var parameter in callingParameters)
+            {
+                var paramType = parameter.ParameterType;
+                object paramValue = null;
+                //FromBody
+                if (parameter.FromType == FromType.FromBody)
+                {
+                    if (contentType == WebApiHttpContentType.Json)
+                    {
+                        var streamLength = (int)context.Request.ContentLength64;
+                        var bodyData = new byte[streamLength];
+                        var offset = 0;
+                        while (streamLength > 0)
+                        {
+                            var readLength = context.Request.InputStream.Read(bodyData, offset, streamLength);
+                            streamLength -= readLength;
+                            offset += readLength;
+                        }
+
+                        paramValue = WebApiConverter.JsonToObject(Encoding.UTF8.GetString(bodyData), paramType);
+                    }
+                    else if (contentType == WebApiHttpContentType.XWwwFormUrlencoded)
+                    {
+                        var parser = new HttpContentParser(context.Request.InputStream);
+                        if (parser.Success)
+                        {
+                            var formData = new NameValueCollection();
+                            foreach (var parserParameter in parser.Parameters)
+                            {
+                                formData.Add(parserParameter.Key, parserParameter.Value);
+                            }
+
+                            paramValue = paramType.New(formData);
+                        }
+                        else if (!string.IsNullOrWhiteSpace(parser.Content))
+                        {
+                            paramValue = WebApiConverter.JsonToObject(parser.Content, paramType);
+                        }
+                    }
+                }
+                else if (parameter.FromType == FromType.FromUrl)
+                {
+                    paramValue = paramType.New(queryData);
+                }
+                else
+                {
+                    if (queryData.AllKeys.Contains(parameter.Name))
+                    {
+                        paramValue = WebApiConverter.StringToObject(queryData[parameter.Name], paramType);
+                        if (paramValue != null && paramType == typeof(string))
+                        {
+                            paramValue = Uri.UnescapeDataString((string)paramValue);
+                        }
+                    }
+                }
+                if (paramValue == null)
+                {
+                    paramValue = paramType.Default();
+                }
+                args.Add(new WebApiArgument(parameter.Name, paramValue));
+            }
+            var argsArray = args.ToArray();
+            args.Clear();
+            return argsArray;
+        }
+    }
+}

+ 73 - 0
Tools/MiniWebApi/Utilities/WebApiConverter.cs

@@ -0,0 +1,73 @@
+using System;
+using System.Globalization;
+
+namespace MiniWebApi.Utilities
+{
+    static class WebApiConverter
+    {
+        /// <summary>
+        /// Convert an object to Json string
+        /// </summary>
+        /// <param name="obj">object</param>
+        /// <returns></returns>
+        public static string ObjectToJson(object obj)
+        {
+            try
+            {
+                if (obj != null)
+                {
+                    return System.Text.Json.JsonSerializer.Serialize(obj);
+                }
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Convert object <{obj}> to json error: {ex}");
+            }
+            return string.Empty;
+        }
+
+
+        /// <summary>
+        /// Convert a Json string to object
+        /// </summary>
+        /// <param name="jsonStr">Json string</param>
+        /// <param name="type">object type</param>
+        /// <returns></returns>
+        public static object JsonToObject(string jsonStr, Type type)
+        {
+            try
+            {
+                if (!string.IsNullOrEmpty(jsonStr) && !string.IsNullOrWhiteSpace(jsonStr))
+                {
+                    return System.Text.Json.JsonSerializer.Deserialize(jsonStr, type);
+                }
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Convert json [{jsonStr}] to object <{type}> error: {ex}");
+            }
+
+            return type.Default();
+        }
+
+
+        /// <summary>
+        /// Convert a string to an object.
+        /// </summary>
+        /// <param name="str"></param>
+        /// <param name="type"></param>
+        /// <returns></returns>
+        public static object StringToObject(string str, Type type)
+        {
+            try
+            {
+                return Convert.ChangeType(str, type, CultureInfo.InvariantCulture);
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Convert string [{str}] to object <{type}> error: {ex}");
+            }
+            return type.Default();
+        }
+    }
+}

+ 34 - 0
Tools/MiniWebApi/Utilities/WebApiHttpContentTypeConverter.cs

@@ -0,0 +1,34 @@
+namespace MiniWebApi.Utilities
+{
+    enum WebApiHttpContentType
+    {
+        NotSupported,
+        Json,
+        XWwwFormUrlencoded,
+    }
+
+    class WebApiHttpContentTypeConverter
+    {
+        /// <summary>
+        /// Get the WebApiHttpContentType Only support Json and XWwwFormUrlencoded.
+        /// </summary>
+        /// <param name="contentTypeStr">The ContentType to return.</param>
+        /// <returns></returns>
+        public static WebApiHttpContentType ToContentType(string contentTypeStr)
+        {
+            if (string.IsNullOrEmpty(contentTypeStr)) return WebApiHttpContentType.NotSupported;
+            var parts = contentTypeStr.ToLower().Split(';');
+            if (parts.Length > 0)
+            {
+                switch (parts[0].Trim())
+                {
+                    case "application/json":
+                        return WebApiHttpContentType.Json;
+                    case "application/x-www-form-urlencoded":
+                        return WebApiHttpContentType.XWwwFormUrlencoded;
+                }
+            }
+            return WebApiHttpContentType.NotSupported;
+        }
+    }
+}

+ 183 - 0
Tools/MiniWebApi/Utilities/WebApiMethodMatcher.cs

@@ -0,0 +1,183 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Reflection;
+using MiniWebApi.Handler;
+
+namespace MiniWebApi.Utilities
+{
+    /// <summary>
+    /// A matcher for check if the method is match for request.
+    /// </summary>
+    internal interface IWebApiMethodMatcher
+    {
+        /// <summary>
+        /// Check if current request is match the given parameters of the method.
+        /// </summary>
+        /// <param name="context">The HttpContext</param>
+        /// <param name="callingMethod">The method to compare.</param>
+        /// <returns>True if matched otherwise false.</returns>
+        bool IsMatch(HttpListenerContext context, CallingMethod callingMethod);
+    }
+
+
+    /// <summary>
+    /// Matcher for Get
+    /// </summary>
+    internal class GetMethodMatcher : IWebApiMethodMatcher
+    {
+        private readonly Dictionary<Type,PropertyInfo[]> _properties = new Dictionary<Type, PropertyInfo[]>();
+
+        /// <summary>
+        /// Check if current request is match the given parameters of the method.
+        /// </summary>
+        /// <param name="context">The HttpContext</param>
+        /// <param name="callingMethod">The method to compare.</param>
+        /// <returns>True if matched otherwise false.</returns>
+        public bool IsMatch(HttpListenerContext context, CallingMethod callingMethod)
+        {
+            if (callingMethod.WebApiType != WebApiType.Get) return false;
+
+            var queryData = context.Request.QueryString;
+
+            var argCount = 0;
+            foreach (var parameter in callingMethod.Parameters)
+            {
+                if (parameter.FromType == FromType.FromUrl)
+                {
+                    var paramType = parameter.ParameterType;
+                    if (!_properties.ContainsKey(paramType))
+                    {
+                        _properties.Add(paramType,
+                            paramType.GetProperties().Where(x => x.CanRead && x.CanWrite).ToArray());
+                    }
+
+                    var properties = _properties[paramType];
+                    foreach (var property in properties)
+                    {
+                        if (!queryData.AllKeys.Contains(property.Name))
+                        {
+                            return false;
+                        }
+                    }
+                }
+                else
+                {
+                    if (!queryData.AllKeys.Contains(parameter.Name))
+                    {
+                        return false;
+                    }
+                }
+
+                argCount++;
+            }
+
+            return argCount == callingMethod.Parameters.Count;
+        }
+    }
+
+    /// <summary>
+    /// Matcher for Post
+    /// </summary>
+    internal class PostMethodMatcher : IWebApiMethodMatcher
+    {
+        private readonly Dictionary<Type, PropertyInfo[]> _properties = new Dictionary<Type, PropertyInfo[]>();
+
+        /// <summary>
+        /// Check if match the WebApiType
+        /// </summary>
+        /// <param name="callingMethod">The method to check</param>
+        /// <returns>True if matched otherwise false.</returns>
+        protected virtual bool WebApiTypeMatched(CallingMethod callingMethod)
+        {
+            return callingMethod.WebApiType == WebApiType.Post;
+        }
+
+        /// <summary>
+        /// Check if current request is match the given parameters of the method.
+        /// </summary>
+        /// <param name="context">The HttpContext</param>
+        /// <param name="callingMethod">The method to compare.</param>
+        /// <returns>True if matched otherwise false.</returns>
+        public bool IsMatch(HttpListenerContext context, CallingMethod callingMethod)
+        {
+            if (!WebApiTypeMatched(callingMethod)) return false;
+            var queryData = context.Request.QueryString;
+
+            var argCount = 0;
+            foreach (var parameter in callingMethod.Parameters)
+            {
+                var paramType = parameter.ParameterType;
+                //FromBody
+                if (parameter.FromType == FromType.FromBody)
+                {
+                    if (context.Request.ContentLength64 == 0)
+                    {
+                        //We do not actually check the body data, just check if there is body data.
+                        return false;
+                    }
+                }
+                else if (parameter.FromType == FromType.FromUrl)
+                {
+                    if (!_properties.ContainsKey(paramType))
+                    {
+                        _properties.Add(paramType, paramType.GetProperties().Where(x => x.CanRead && x.CanWrite).ToArray());
+                    }
+
+                    var properties = _properties[paramType];
+                    foreach (var property in properties)
+                    {
+                        if (!queryData.AllKeys.Contains(property.Name))
+                        {
+                            return false;
+                        }
+                    }
+                }
+                else
+                {
+                    if (!queryData.AllKeys.Contains(parameter.Name))
+                    {
+                        return false;
+                    }
+                }
+
+                argCount++;
+            }
+
+            return argCount == callingMethod.Parameters.Count;
+        }
+    }
+
+    /// <summary>
+    /// Matcher for Put
+    /// </summary>
+    internal class PutMethodMatcher : PostMethodMatcher
+    {
+        /// <summary>
+        /// Check if match the WebApiType
+        /// </summary>
+        /// <param name="callingMethod">The method to check</param>
+        /// <returns>True if matched otherwise false.</returns>
+        protected override bool WebApiTypeMatched(CallingMethod callingMethod)
+        {
+            return callingMethod.WebApiType == WebApiType.Put;
+        }
+    }
+
+    /// <summary>
+    /// Matcher for Delete
+    /// </summary>
+    internal class DeleteMethodMatcher : PostMethodMatcher
+    {
+        /// <summary>
+        /// Check if match the WebApiType
+        /// </summary>
+        /// <param name="callingMethod">The method to check</param>
+        /// <returns>True if matched otherwise false.</returns>
+        protected override bool WebApiTypeMatched(CallingMethod callingMethod)
+        {
+            return callingMethod.WebApiType == WebApiType.Delete;
+        }
+    }
+}

+ 107 - 0
Tools/MiniWebApi/Utilities/WebApiUrlInfoParser.cs

@@ -0,0 +1,107 @@
+using System;
+
+namespace MiniWebApi.Utilities
+{
+    /// <summary>
+    /// WebApiUrlInfo contains the handler name, action name and the version.
+    /// </summary>
+    class WebApiUrlInfo
+    {
+        /// <summary>
+        /// Gets the Handler name.
+        /// </summary>
+        public string HandlerName { get; }
+
+        /// <summary>
+        /// Gets the Action 's name.
+        /// </summary>
+        public string ActionName { get; }
+
+        /// <summary>
+        /// Gets the version from the url.
+        /// </summary>
+        public string Version { get; }
+
+
+        public WebApiUrlInfo(string handlerName, string actionName, string version = "v1")
+        {
+            HandlerName = handlerName;
+            ActionName = actionName;
+            Version = version;
+        }
+    }
+
+    class WebApiUrlInfoParser
+    {
+        /// <summary>
+        /// Parser the request url, get the calling information.
+        /// </summary>
+        /// <param name="applicationName"></param>
+        /// <param name="requestUrl"></param>
+        /// <returns></returns>
+        public static WebApiUrlInfo Parser(string applicationName, Uri requestUrl)
+        {
+            var urlApplicationName = string.Empty;
+            var urlVersion = "v1";
+            string urlHandlerName;
+            var urlActionName = string.Empty;
+
+            var url = $"{requestUrl.AbsolutePath.Trim('/')}";
+            var urlParts = url.Split('/');
+            if (!string.IsNullOrEmpty(applicationName))
+            {
+                // AppName/Version/HandlerName/ActionName
+                // AppName/HandlerName/ActionName
+                if (urlParts.Length < 2 || urlParts.Length > 4) return null;
+                if (urlParts.Length == 4)
+                {
+                    //Contains version.
+                    urlApplicationName = urlParts[0].ToLower();
+                    urlVersion = urlParts[1].ToLower();
+                    urlHandlerName = urlParts[2].ToLower();
+                    //Action name can not to lower
+                    urlActionName = urlParts[3].ToLower();
+                }
+                else if(urlParts.Length == 3)
+                {
+                    urlApplicationName = urlParts[0].ToLower();
+                    urlHandlerName = urlParts[1].ToLower();
+                    //Action name can not to lower
+                    urlActionName = urlParts[2].ToLower();
+                }
+                else
+                {
+                    urlApplicationName = urlParts[0].ToLower();
+                    urlHandlerName = urlParts[1].ToLower();
+                }
+            }
+            else
+            {
+                // Version/HandlerName/ActionName
+                // HandlerName/ActionName
+                if (urlParts.Length <1 || urlParts.Length >3) return null;
+                if (urlParts.Length == 3)
+                {
+                    //Contains version.
+                    urlVersion = urlParts[0].ToLower();
+                    urlHandlerName = urlParts[1].ToLower();
+                    //Action name can not to lower
+                    urlActionName = urlParts[2].ToLower();
+                }
+                else if(urlParts.Length == 2)
+                {
+                    urlHandlerName = urlParts[0].ToLower();
+                    //Action name can not to lower
+                    urlActionName = urlParts[1].ToLower();
+                }
+                else
+                {
+                    urlHandlerName = urlParts[0].ToLower();
+                }
+            }
+
+            //Check if the application is matched.
+            return applicationName != urlApplicationName ? null : new WebApiUrlInfo(urlHandlerName, urlActionName, urlVersion);
+        }
+    }
+}

+ 14 - 0
Tools/MiniWebApi/Utilities/XssException.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MiniWebApi.Utilities
+{
+    public class XssException : Exception
+    {
+        public XssException(string message) : base(message)
+        {
+
+        }
+    }
+}

+ 157 - 0
Tools/SmsTool/Controllers/SendSmsController.cs

@@ -0,0 +1,157 @@
+using MiniWebApi.Handler;
+using MiniWebApi.Network;
+using SmsTool.Models;
+using SmsTool.Tencent;
+using SmsTool.ViewEngine;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using TencentCloud.Sms.V20210111.Models;
+
+namespace SmsTool.Controllers
+{
+    [WebApiHandler("SendSms")]
+    internal class SendSmsController : BaseHandler
+    {
+        private ConcurrentDictionary<string, VerifyCodeModel> _mobileVerifyCodes = new ConcurrentDictionary<string, VerifyCodeModel>();
+
+        /// <summary>发送短信验证码</summary>
+        /// <param name="context"></param>
+        [Post]
+        public void SendVerifyCode(WebApiHttpContext context)
+        {
+            var param = context.GetParams();
+            var mobile = param.GetStringValue("mobile");
+            var templateId = param.GetStringValue("templateid");
+            //var verifyCodeLength = param.GetStringValue("codeLen").ToInt();
+            var validMinutes = 10;
+
+            var verifyCode = GetVerifyCode(mobile, validMinutes);
+            var model = new SendSmsModel
+            {
+                Mobiles = new List<string> { mobile },
+                TemplateId = templateId,
+                Params = new List<string> { verifyCode }
+            };
+            //重置密码无需传入有效时间参数
+            if (templateId == "1009241")
+            {
+                model.Params.Add(validMinutes.ToString());
+            }
+            var result = TencentHelper.SendSmsSync(model);
+            context.Response.Json(new
+            {
+                msg = "",
+                obj = verifyCode,
+                code = GetSendSmsCode(result)
+            }, true);
+        }
+
+        private string GetVerifyCode(string mobile, int validMinutes)
+        {
+            if (_mobileVerifyCodes.ContainsKey(mobile))
+            {
+                _mobileVerifyCodes.TryRemove(mobile, out _);
+            }
+            var mobileVerifyCode = _mobileVerifyCodes.GetOrAdd(mobile, new VerifyCodeModel
+            {
+                VerifyCode = new Random().Next(1000, 9999).ToString(),
+                ValidMinutes = validMinutes,
+                CreateTime = DateTime.Now,
+            });
+            return mobileVerifyCode.VerifyCode;
+        }
+
+        private VerifyCodeModel GetVerifyCode(string mobile)
+        {
+            if (_mobileVerifyCodes.ContainsKey(mobile))
+            {
+                return _mobileVerifyCodes[mobile];
+            }
+            return null;
+        }
+
+        private int GetSendSmsCode(SendSmsResponse result)
+        {
+            if (result == null || result.SendStatusSet == null || !result.SendStatusSet.Any())
+            {
+                return 9999;
+            }
+            if (result.SendStatusSet.Any(x => x.Code == "Ok"))
+            {
+                return 200;
+            }
+            if (result.SendStatusSet.Any(x => x.Code == "LimitExceeded.PhoneNumberDailyLimit"))
+            {
+                return 416;
+            }
+            return 9999;
+        }
+
+        /// <summary>验证短信验证码</summary>
+        /// <param name="context"></param>
+        [Post]
+        public void CheckVerifyCode(WebApiHttpContext context)
+        {
+            var param = context.GetParams();
+            var mobile = param.GetStringValue("mobile");
+            var verifyCode = param.GetStringValue("code");
+
+            var mobileVerifyCode = GetVerifyCode(mobile);
+            Console.WriteLine($"[{DateTime.Now:MM-dd HH:mm:ss}]{mobile},{verifyCode},curr:{mobileVerifyCode.VerifyCode},{mobileVerifyCode.OverdueTime:MM-dd HH:mm:ss}");
+            if (mobileVerifyCode == null || string.IsNullOrWhiteSpace(mobileVerifyCode.VerifyCode))
+            {
+                context.Response.Json(new
+                {
+                    code = 404
+                });
+                return;
+            }
+            if (mobileVerifyCode.IsOverdue)
+            {
+                context.Response.Json(new
+                {
+                    code = 413
+                });
+                return;
+            }
+            if (mobileVerifyCode.VerifyCode != verifyCode)
+            {
+                context.Response.Json(new
+                {
+                    code = 414
+                });
+                return;
+            }
+            context.Response.Json(new
+            {
+                code = 200
+            });
+        }
+
+        /// <summary>发送短信</summary>
+        /// <param name="context"></param>
+        [Post]
+        public void SendMessage(WebApiHttpContext context)
+        {
+            var param = context.GetParams();
+            var mobiles = Newtonsoft.Json.JsonConvert.DeserializeObject<List<string>>(param.GetStringValue("mobiles"));
+            var templateId = param.GetStringValue("templateid");
+            var messageDatas = Newtonsoft.Json.JsonConvert.DeserializeObject<List<string>>(param.GetStringValue("params"));
+
+            var result = TencentHelper.SendSmsSync(new SendSmsModel
+            {
+                Mobiles = mobiles,
+                TemplateId = templateId,
+                Params = messageDatas
+            });
+            context.Response.Json(new
+            {
+                msg = "",
+                code = GetSendSmsCode(result)
+            }, true);
+        }
+    }
+}

+ 18 - 121
Tools/SmsTool/Program.cs

@@ -2,130 +2,27 @@ using System.Collections.Concurrent;
 using SmsTool.Models;
 using SmsTool.Tencent;
 using TencentCloud.Sms.V20210111.Models;
+using MiniWebApi.Network;
+using System;
 
-TencentHelper.Init();
-var builder = WebApplication.CreateBuilder(args);
-var port = builder.Configuration.GetSection("Sms")["Port"];
-builder.WebHost.UseUrls($"http://*:{port}");
-var app = builder.Build();
-
-ConcurrentDictionary<string, VerifyCodeModel> _mobileVerifyCodes = new ConcurrentDictionary<string, VerifyCodeModel>();
-
-
-app.MapPost("/SendVerifyCode", (string mobile,string templateId) => {
-    //var verifyCodeLength = param.GetStringValue("codeLen").ToInt();
-    var validMinutes = 10;
-
-    var verifyCode = _getVerifyCodeWithTime(mobile, validMinutes);
-    var model = new SendSmsModel
-    {
-        Mobiles = new List<string> { mobile },
-        TemplateId = templateId,
-        Params = new List<string> { verifyCode }
-    };
-    //重置密码无需传入有效时间参数
-    if (templateId == "1009241")
-    {
-        model.Params.Add(validMinutes.ToString());
-    }
-    var result = TencentHelper.SendSmsSync(model);
-    return new
-    {
-        msg = "",
-        obj = verifyCode,
-        code = _getSendSmsCode(result)
-    };
-});
-
-
-app.MapPost("/CheckVerifyCode",(string mobile, string verifyCode)=>
+namespace SmsTool
 {
-    var mobileVerifyCode = _getVerifyCode(mobile);
-    Console.WriteLine($"[{DateTime.Now:MM-dd HH:mm:ss}]{mobile},{verifyCode},curr:{mobileVerifyCode.VerifyCode},{mobileVerifyCode.OverdueTime:MM-dd HH:mm:ss}");
-    if (mobileVerifyCode == null || string.IsNullOrWhiteSpace(mobileVerifyCode.VerifyCode))
+    class Program
     {
-        return new
+        static void Main(string[] args)
         {
-            code = 404
-        };
-    }
-    if (mobileVerifyCode.IsOverdue)
-    {
-        return new
-        {
-            code = 413
-        };
-    }
-    if (mobileVerifyCode.VerifyCode != verifyCode)
-    {
-        return new
-        {
-            code = 414
-        };
-    }
-    return new
-    {
-        code = 200
-    };
-});
-
-app.MapPost("/SendMessage", (string mobiles, string templateId, string paras)=>
-{
-    var mobileList = Newtonsoft.Json.JsonConvert.DeserializeObject<List<string>>(mobiles);
-    var messageDatas = Newtonsoft.Json.JsonConvert.DeserializeObject<List<string>>(paras);
-
-    var result = TencentHelper.SendSmsSync(new SendSmsModel
-    {
-        Mobiles = mobileList,
-        TemplateId = templateId,
-        Params = messageDatas
-    });
-    return new
-    {
-        msg = "",
-        code = _getSendSmsCode(result)
-    };
-});
-
-string _getVerifyCodeWithTime(string mobile, int validMinutes)
-{
-    if (_mobileVerifyCodes.ContainsKey(mobile))
-    {
-        _mobileVerifyCodes.TryRemove(mobile, out _);
-    }
-    var mobileVerifyCode = _mobileVerifyCodes.GetOrAdd(mobile, new VerifyCodeModel
-    {
-        VerifyCode = new Random().Next(1000, 9999).ToString(),
-        ValidMinutes = validMinutes,
-        CreateTime = DateTime.Now,
-    });
-    return mobileVerifyCode.VerifyCode;
-}
-
-VerifyCodeModel _getVerifyCode(string mobile)
-{
-    if (_mobileVerifyCodes.ContainsKey(mobile))
-    {
-        return _mobileVerifyCodes[mobile];
+            TencentHelper.Init();
+            var webApiServer = new WebApiServer("SmsTool");
+            //webApiServer.Start(args[0]);
+            webApiServer.Start(8302);
+            Console.WriteLine("SmsTool server started");
+            var readLine = Console.ReadLine();
+            while (readLine != null && !readLine.Equals("q", StringComparison.OrdinalIgnoreCase))
+            {
+                Console.WriteLine("Type \"q\" to exit.");
+                readLine = Console.ReadLine();
+            }
+            webApiServer.Stop();
+        }
     }
-    return null;
 }
-
-int _getSendSmsCode(SendSmsResponse result)
-{
-    if (result == null || result.SendStatusSet == null || !result.SendStatusSet.Any())
-    {
-        return 9999;
-    }
-    if (result.SendStatusSet.Any(x => x.Code == "Ok"))
-    {
-        return 200;
-    }
-    if (result.SendStatusSet.Any(x => x.Code == "LimitExceeded.PhoneNumberDailyLimit"))
-    {
-        return 416;
-    }
-    return 9999;
-}
-
-app.Run();

+ 6 - 2
Tools/SmsTool/SmsTool.csproj

@@ -2,12 +2,16 @@
 
   <PropertyGroup>
     <TargetFramework>net6.0</TargetFramework>
-    <Nullable>enable</Nullable>
-    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>disable</Nullable>
+    <ImplicitUsings>disable</ImplicitUsings>
   </PropertyGroup>
 
   <ItemGroup>
     <PackageReference Include="TencentCloudSDK" Version="3.0.655" />
   </ItemGroup>
 
+  <ItemGroup>
+    <ProjectReference Include="..\MiniWebApi\MiniWebApi.csproj" />
+  </ItemGroup>
+
 </Project>

+ 190 - 0
Tools/SmsTool/ViewEngine/ApiExtends.cs

@@ -0,0 +1,190 @@
+using MiniWebApi.Network;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Web;
+
+namespace SmsTool.ViewEngine
+{
+    public static class ApiExtends
+    {
+        /// <summary>
+        /// 获取Get参数
+        /// </summary>
+        /// <param name="context"></param>
+        /// <param name="keyName"></param>
+        /// <param name="defaultValue"></param>
+        /// <returns></returns>
+        public static string GetParam(this WebApiHttpContext context, string keyName, string defaultValue = "")
+        {
+            var param = context.Request.QueryString.Get(keyName).XssEncode();
+            return !string.IsNullOrWhiteSpace(param) ? param : defaultValue;
+        }
+
+        /// <summary>
+        /// 获取Post参数
+        /// </summary>
+        /// <param name="context"></param>
+        /// <returns></returns>
+        public static Dictionary<string, object> GetParams(this WebApiHttpContext context)
+        {
+            try
+            {
+                var postContent = context.Request.GetInputStreamValue();
+                Dictionary<string, object> keyValuePairs = new Dictionary<string, object>();
+                var uriParams = postContent;
+                if (uriParams != null)
+                {
+                    var paramsArr = uriParams.Split('&');
+                    foreach (string param in paramsArr)
+                    {
+                        var keyValue = param.Split('=');
+                        var key = HttpUtility.UrlDecode(keyValue[0]).Replace("[]", "");
+                        var value = keyValue.Length > 1 ? HttpUtility.UrlDecode(keyValue[1]) : string.Empty;
+                        if (keyValuePairs.Keys.Contains(key))
+                        {
+                            if (keyValuePairs[key] is List<string> pairs)
+                            {
+                                pairs.Add(value);
+                            }
+                            else
+                            {
+                                List<string> newPairs = new List<string>();
+                                var convertValue = keyValuePairs[key] as string;
+                                newPairs.Add(convertValue);
+                                newPairs.Add(value);
+                                keyValuePairs[key] = newPairs;
+                            }
+                        }
+                        else
+                        {
+                            keyValuePairs.Add(key, value);
+                        }
+                    }
+                }
+
+                return keyValuePairs;
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine("GetParams error:" + ex.Message + "|" + ex.StackTrace);
+                return new Dictionary<string, object>();
+            }
+        }
+
+        /// <summary>
+        /// 获取Dictionary参数
+        /// </summary>
+        /// <param name="keyValuePairs"></param>
+        /// <param name="keyName"></param>
+        /// <param name="defaultValue"></param>
+        /// <returns></returns>
+        public static string GetStringValue(this Dictionary<string, object> keyValuePairs, string keyName, string defaultValue = "")
+        {
+            return GetValue(keyValuePairs, keyName, defaultValue);
+        }
+
+        /// <summary>
+        /// 获取Dictionary参数
+        /// </summary>
+        /// <param name="keyValuePairs"></param>
+        /// <param name="keyName"></param>
+        /// <param name="defaultValue"></param>
+        /// <returns></returns>
+        public static List<string> GetArrayValue(this Dictionary<string, object> keyValuePairs, string keyName)
+        {
+            return GetArrayValueData(keyValuePairs, keyName);
+        }
+
+        /// <summary>
+        /// 获取Dictionary参数
+        /// </summary>
+        /// <param name="keyValuePairs"></param>
+        /// <param name="keyName"></param>
+        /// <param name="defaultValue"></param>
+        /// <returns></returns>
+        private static List<string> GetArrayValueData(Dictionary<string, object> keyValuePairs, string keyName)
+        {
+            var list = GetValue(keyValuePairs, keyName, new List<string>());
+            if (list == null || list.Count == 0)
+            {
+                var onlyValue = GetValue(keyValuePairs, keyName, string.Empty);
+                if (!onlyValue.IsEmpty())
+                {
+                    list = new List<string> { onlyValue };
+                }
+            }
+            return list;
+        }
+
+        /// <summary>
+        /// 获取Dictionary参数
+        /// </summary>
+        /// <param name="keyValuePairs"></param>
+        /// <param name="keyName"></param>
+        /// <param name="defaultValue"></param>
+        /// <returns></returns>
+        public static T GetValue<T>(this Dictionary<string, object> keyValuePairs, string keyName, T defaultValue = null) where T : class
+        {
+            T paramValue = defaultValue ?? default(T);
+            try
+            {
+                paramValue = (keyValuePairs[keyName] as T);
+                if (paramValue == null)
+                {
+                    paramValue = defaultValue;
+                }
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine("GetValue error:" + ex.Message + "|" + ex.StackTrace);
+            }
+
+            return paramValue;
+        }
+
+
+        /// <summary>
+        /// 获取SortedDictionary参数
+        /// </summary>
+        /// <param name="keyValuePairs"></param>
+        /// <param name="keyName"></param>
+        /// <param name="defaultValue"></param>
+        /// <returns></returns>
+        public static string GetValue(this SortedDictionary<string, object> keyValuePairs, string keyName, string defaultValue = "")
+        {
+            string paramValue = defaultValue;
+            try
+            {
+                paramValue = keyValuePairs[keyName].ToStringEx().XssEncode();
+                if (string.IsNullOrWhiteSpace(paramValue))
+                {
+                    paramValue = defaultValue;
+                }
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine("GetValue error:" + ex.Message + "|" + ex.StackTrace);
+            }
+
+            return paramValue;
+        }
+
+        /// <summary>
+        /// ToString扩展函数
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="obj"></param>
+        /// <param name="defaultValue"></param>
+        /// <returns></returns>
+        public static string ToStringEx<T>(this T obj, string defaultValue = "")
+        {
+            if (obj == null)
+            {
+                return defaultValue;
+            }
+            return obj.ToString();
+        }
+    }
+}

+ 163 - 0
Tools/SmsTool/ViewEngine/ConvertHelper.cs

@@ -0,0 +1,163 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+
+namespace SmsTool.ViewEngine
+{
+    /// <summary>
+    /// use to convert data type
+    /// </summary>
+    public static class ConvertHelper
+    {
+        /// <summary>
+        /// convert object to Json string
+        /// </summary>
+        /// <param name="obj">object</param>
+        /// <param name="formatting">format</param>
+        /// <param name="settings">settings</param>
+        /// <returns></returns>
+        public static string ToJson(this object obj, Formatting formatting = Formatting.None, JsonSerializerSettings settings = null)
+        {
+            if (obj == null) return string.Empty;
+            try
+            {
+                return JsonConvert.SerializeObject(obj, formatting, settings);
+            }
+            catch { return string.Empty; }
+        }
+
+        /// <summary>
+        /// convert Json string to object
+        /// </summary>
+        /// <typeparam name="T">object type</typeparam>
+        /// <param name="jsonStr">Json string</param>
+        /// <param name="settings">settings</param>
+        /// <returns></returns>
+        public static T JsonToObj<T>(this string jsonStr, JsonSerializerSettings settings = null)
+        {
+            if (string.IsNullOrWhiteSpace(jsonStr)) return default(T);
+            try
+            {
+                return JsonConvert.DeserializeObject<T>(jsonStr, settings);
+            }
+            catch { return default(T); }
+        }
+
+        /// <summary>
+        /// convert Json string to object
+        /// </summary>
+        /// <param name="jsonStr">Json string</param>
+        /// <param name="type">object type</param>
+        /// <param name="settings">settings</param>
+        /// <returns></returns>
+        public static object JsonToObj(this string jsonStr, Type type, JsonSerializerSettings settings = null)
+        {
+            if (string.IsNullOrWhiteSpace(jsonStr)) return type.IsValueType ? Activator.CreateInstance(type) : null;
+            try
+            {
+                return JsonConvert.DeserializeObject(jsonStr, type, settings);
+            }
+            catch { return type.IsValueType ? Activator.CreateInstance(type) : null; }
+        }
+        /// <summary>
+        /// convert sting to int
+        /// </summary>
+        /// <param name="str">string</param>
+        /// <param name="defaultValue">default Value</param>
+        /// <returns></returns>
+        public static int ToInt(this string str, int defaultValue = 0)
+        {
+            if (string.IsNullOrWhiteSpace(str)) return defaultValue;
+            int.TryParse(str, out defaultValue);
+            return defaultValue;
+        }
+
+        public static double ToDouble(this string str, double defaultValue = 0)
+        {
+            if (string.IsNullOrWhiteSpace(str)) return defaultValue;
+            double.TryParse(str, out defaultValue);
+            return defaultValue;
+        }
+
+        /// <summary>
+        /// convert sting to float
+        /// </summary>
+        /// <param name="str"></param>
+        /// <param name="defaultValue"></param>
+        /// <returns></returns>
+        public static float ToFloat(this string str, float defaultValue = 0)
+        {
+            if (string.IsNullOrWhiteSpace(str)) return defaultValue;
+            float.TryParse(str, out defaultValue);
+            return defaultValue;
+        }
+
+        /// <summary>
+        /// convert sting to DateTime
+        /// </summary>
+        /// <param name="str"></param>
+        /// <param name="defaultValue"></param>
+        /// <returns></returns>
+        public static DateTime ToDateTime(this string str)
+        {
+            DateTime defaultValue;
+            if (string.IsNullOrWhiteSpace(str)) return DateTime.MinValue;
+            DateTime.TryParse(str, out defaultValue);
+            return defaultValue;
+        }
+
+        /// <summary>
+        /// convert sting to int
+        /// </summary>
+        /// <param name="str">string</param>
+        /// <param name="defaultValue">default Value</param>
+        /// <returns></returns>
+        public static long ToLong(this string str, long defaultValue = 0)
+        {
+            if (string.IsNullOrWhiteSpace(str)) return defaultValue;
+            long.TryParse(str, out defaultValue);
+            return defaultValue;
+        }
+
+        /// <summary>
+        /// Convert string to other type object
+        /// </summary>
+        /// <param name="str">string</param>
+        /// <param name="type">type</param>
+        /// <returns></returns>
+        public static object ToObject(this string str, Type type)
+        {
+            try
+            {
+                return Convert.ChangeType(str, type, CultureInfo.InvariantCulture);
+            }
+            catch
+            {
+                return type.IsValueType ? Activator.CreateInstance(type) : null;
+            }
+        }
+
+        /// <summary>
+        /// convert sting to Boolean
+        /// </summary>
+        /// <param name="str">sting</param>
+        /// <returns></returns>
+        public static bool ToBool(this string str)
+        {
+            if (string.IsNullOrWhiteSpace(str)) return false;
+            bool.TryParse(str, out bool val);
+            return val;
+        }
+
+        public static bool IsEmpty(this string str)
+        {
+            if (string.IsNullOrWhiteSpace(str))
+            {
+                return true;
+            }
+            return false;
+        }
+    }
+}

+ 86 - 0
Tools/SmsTool/ViewEngine/DefenseInjectionHelper.cs

@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SmsTool.ViewEngine
+{
+    public static class DefenseInjectionHelper
+    {
+        /**
+         * 将容易引起xss漏洞的半角字符直接替换成全角字符
+         *
+         * @param s
+         * @return
+         */
+        public static string XssEncode(this string s)
+        {
+            if (string.IsNullOrWhiteSpace(s))
+            {
+                return string.Empty;
+            }
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < s.Length; i++)
+            {
+                char c = s[i];
+                switch (c)
+                {
+                    case '>':
+                        sb.Append('>');// 全角大于号
+                        break;
+                    case '<':
+                        sb.Append('<');// 全角小于号
+                        break;
+                    case '\'':
+                        sb.Append('‘');// 全角单引号
+                        break;
+                    case '\"':
+                        sb.Append('“');// 全角双引号
+                        break;
+                    case '&':
+                        sb.Append('&');// 全角
+                        break;
+                    case '\\':
+                        sb.Append('\');// 全角斜线
+                        break;
+                    case '#':
+                        sb.Append('#');// 全角井号
+                        break;
+                    case '%':    // < 字符的 URL 编码形式表示的 ASCII 字符(十六进制格式) 是: %3c
+                        ProcessUrlEncoder(sb, s, i);
+                        break;
+                    default:
+                        sb.Append(c);
+                        break;
+                }
+            }
+            return sb.ToString().Trim();
+        }
+        private static void ProcessUrlEncoder(StringBuilder sb, string s, int index)
+        {
+            if (s.Length >= index + 2)
+            {
+                if (s[index + 1] == '3' && (s[index + 2] == 'c' || s[index + 2] == 'C'))
+                {    // %3c, %3C
+                    sb.Append('<');
+                    return;
+                }
+                if (s[index + 1] == '6' && s[index + 2] == '0')
+                {    // %3c (0x3c=60)
+                    sb.Append('<');
+                    return;
+                }
+                if (s[index + 1] == '3' && (s[index + 2] == 'e' || s[index + 2] == 'E'))
+                {    // %3e, %3E
+                    sb.Append('>');
+                    return;
+                }
+                if (s[index + 1] == '6' && s[index + 2] == '2')
+                {    // %3e (0x3e=62)
+                    sb.Append('>');
+                    return;
+                }
+            }
+            sb.Append(s[index]);
+        }
+    }
+}

+ 0 - 12
Tools/SmsTool/appsettings.Development.json

@@ -1,12 +0,0 @@
-{
-  "Logging": {
-    "LogLevel": {
-      "Default": "Information",
-      "Microsoft.AspNetCore": "Warning"
-    }
-  },
-  "AllowedHosts": "*",
-  "Sms": {
-    "Port":8302
-  }
-}

+ 0 - 12
Tools/SmsTool/appsettings.json

@@ -1,12 +0,0 @@
-{
-  "Logging": {
-    "LogLevel": {
-      "Default": "Information",
-      "Microsoft.AspNetCore": "Warning"
-    }
-  },
-  "AllowedHosts": "*",
-  "Sms": {
-    "Port":8302
-  }
-}

BIN
src/Tools/win/sms.exe