Browse Source

Apewer-6.1.0:WebAPI 新增功能;TextUtility 新增支持 MD。

dev
Elivo 4 years ago
parent
commit
ac8b7934d7
  1. 26
      Apewer/Apewer.csproj
  2. 3
      Apewer/DateTimeUtility.cs
  3. 20
      Apewer/Externals/MarkdownSharp-1.13/Demo.cs
  4. 84
      Apewer/Externals/MarkdownSharp-1.13/Info.cs
  5. 1656
      Apewer/Externals/MarkdownSharp-1.13/Markdown.cs
  6. 45
      Apewer/Externals/MarkdownSharp-1.13/MarkdownOptions.cs
  7. 28
      Apewer/Externals/MarkdownSharp-1.13/Token.cs
  8. 3
      Apewer/Internals/Constant.cs
  9. 45
      Apewer/Json.cs
  10. 2
      Apewer/Models/StringPairs.cs
  11. 4
      Apewer/NetworkUtility.cs
  12. 54
      Apewer/Source/ColumnAttribute.cs
  13. 128
      Apewer/Source/MySql.cs
  14. 6
      Apewer/Source/Record.cs
  15. 22
      Apewer/Source/TableAttribute.cs
  16. 148
      Apewer/Source/TableStructure.cs
  17. 16
      Apewer/TextUtility.cs
  18. 176
      Apewer/Web/ApiInternals.cs
  19. 195
      Apewer/Web/ApiInvoker.cs
  20. 3
      Apewer/Web/ApiRequest.cs
  21. 9
      Apewer/Web/ApiResponse.cs
  22. 108
      Apewer/Web/WebUtility.cs
  23. 14
      Apewer/_ChangeLog.md
  24. 12
      Apewer/_Class.cs
  25. 7
      Apewer/_Extensions.cs

26
Apewer/Apewer.csproj

@ -2,15 +2,15 @@
<!--<Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" />-->
<!-- Info -->
<!-- Info: Package -->
<PropertyGroup>
<AssemblyName>Apewer</AssemblyName>
<PackageId>Apewer</PackageId>
<Title>Apewer</Title>
<Version>6.0.14</Version>
<Version>6.1.0</Version>
</PropertyGroup>
<!-- Info -->
<!-- Info: Copyright -->
<PropertyGroup>
<Authors>Elivo</Authors>
<Company>Apewer Lab</Company>
@ -21,7 +21,7 @@
<Product>Apewer Libraries</Product>
</PropertyGroup>
<!-- Info -->
<!-- Info: Build -->
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
@ -30,19 +30,19 @@
<OutputType>Library</OutputType>
</PropertyGroup>
<!-- NuGet -->
<!-- Info: NuGet -->
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IsPackable>true</IsPackable>
</PropertyGroup>
<!-- For .NET Standard 2.1 -->
<!-- .NET Standard 2.1 -->
<PropertyGroup Condition="'$(TargetFramework)'=='netstandard2.1'">
<AssemblyTitle>$(AssemblyName) - .NET Standard 2.1</AssemblyTitle>
<DefineConstants>NETSTD;HAVE_ASYNC;HAVE_BIG_INTEGER;$(AdditionalConstants)</DefineConstants>
</PropertyGroup>
<!-- For .NET Framework 2.0 -->
<!-- .NET Framework 2.0 -->
<PropertyGroup Condition="'$(TargetFramework)'=='net20'">
<AssemblyTitle>$(AssemblyName) - .NET Framework 2.0</AssemblyTitle>
<DefineConstants>NETFX;NET20;$(AdditionalConstants)</DefineConstants>
@ -50,7 +50,6 @@
<ItemGroup Condition="'$(TargetFramework)'=='net20'">
<Reference Include="CustomMarshalers" />
<Reference Include="System.Configuration" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Design" />
<Reference Include="System.Management" />
<Reference Include="System.Security" />
@ -59,7 +58,7 @@
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<!-- For .NET Framework 4.0 -->
<!-- .NET Framework 4.0 -->
<PropertyGroup Condition="'$(TargetFramework)'=='net40'">
<AssemblyTitle>$(AssemblyName) - .NET Framework 4.0</AssemblyTitle>
<DefineConstants>NETFX;NET40;HAVE_BIG_INTEGER;$(AdditionalConstants)</DefineConstants>
@ -70,7 +69,6 @@
<Reference Include="PresentationCore" />
<Reference Include="System.Core" />
<Reference Include="System.Configuration" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Design" />
<Reference Include="System.Drawing" />
<Reference Include="System.Management" />
@ -83,7 +81,7 @@
<Reference Include="WindowsBase" />
</ItemGroup>
<!-- For .NET Framework 4.6.1 -->
<!-- .NET Framework 4.6.1 -->
<PropertyGroup Condition="'$(TargetFramework)'=='net461'">
<AssemblyTitle>$(AssemblyName) - .NET Framework 4.6.1</AssemblyTitle>
<DefineConstants>NETFX;NET461;NET45;HAVE_ASYNC;HAVE_BIG_INTEGER;$(AdditionalConstants)</DefineConstants>
@ -94,7 +92,6 @@
<Reference Include="PresentationCore" />
<Reference Include="System.Core" />
<Reference Include="System.Configuration" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Design" />
<Reference Include="System.Drawing" />
<Reference Include="System.Management" />
@ -107,7 +104,7 @@
<Reference Include="WindowsBase" />
</ItemGroup>
<!-- For .NET Core 3.1 -->
<!-- .NET Core 3.1 -->
<PropertyGroup Condition="'$(TargetFramework)'=='netcoreapp3.1'">
<AssemblyTitle>$(AssemblyName) - .NET Core 3.1</AssemblyTitle>
<DefineConstants>NETCORE;HAVE_ASYNC;HAVE_BIG_INTEGER;$(AdditionalConstants)</DefineConstants>
@ -115,9 +112,6 @@
<!--<UseWindowsForms>true</UseWindowsForms>-->
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netcoreapp3.1'">
<!-- Condition="'$(DisableImplicitFrameworkReferences)' != 'true' And '$(TargetFrameworkIdentifier)' == '.NETCoreApp' And '$(_TargetFrameworkVersionWithoutV)' &gt;= '3.0'" -->
<!-- IsImplicitlyDefined="true" -->
<!-- Condition="('$(UseWPF)' == 'true') And ('$(UseWindowsForms)' == 'true')" -->
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<FrameworkReference Include="Microsoft.WindowsDesktop.App" />
</ItemGroup>

3
Apewer/DateTimeUtility.cs

@ -276,6 +276,9 @@ namespace Apewer
return new Nullable<DateTime>(entity);
}
/// <summary>创建新的零值 DateTime 对象。</summary>
public static DateTime Zero() => new DateTime(0L, DateTimeKind.Utc);
#endregion
}

20
Apewer/Externals/MarkdownSharp-1.13/Demo.cs

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MarkdownSharp
{
internal class Demo
{
public static string ToHtml(string markdown)
{
var processor = new Markdown();
var html = processor.Transform(markdown);
return html;
}
}
}

84
Apewer/Externals/MarkdownSharp-1.13/Info.cs

@ -0,0 +1,84 @@
/*
* MarkdownSharp
* -------------
* a C# Markdown processors
*
* Markdown is a text-to-HTML conversion tool for web writers
* Copyright (c) 2004 John Gruber
* https://daringfireball.net/projects/markdown/
*
* Markdown.NET
* Copyright (c) 2004-2009 Milan Negovan
* https://www.aspnetresources.com
* https://aspnetresources.com/blog/markdown_announced.aspx
*
* MarkdownSharp
* Copyright (c) 2009-2011 Jeff Atwood
* https://stackoverflow.com
* https://blog.codinghorror.com
* https://github.com/StackExchange/MarkdownSharp
*
* History: Milan ported the Markdown processor to C#. He granted license to me so I can open source it
* and let the community contribute to and improve MarkdownSharp.
*
*/
#region Copyright and license
/*
Copyright (c) 2009 - 2010 Jeff Atwood
http://www.opensource.org/licenses/mit-license.php
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Copyright (c) 2003-2004 John Gruber
<http://daringfireball.net/>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name "Markdown" nor the names of its contributors may
be used to endorse or promote products derived from this software
without specific prior written permission.
This software is provided by the copyright holders and contributors "as
is" and any express or implied warranties, including, but not limited
to, the implied warranties of merchantability and fitness for a
particular purpose are disclaimed. In no event shall the copyright owner
or contributors be liable for any direct, indirect, incidental, special,
exemplary, or consequential damages (including, but not limited to,
procurement of substitute goods or services; loss of use, data, or
profits; or business interruption) however caused and on any theory of
liability, whether in contract, strict liability, or tort (including
negligence or otherwise) arising in any way out of the use of this
software, even if advised of the possibility of such damage.
*/
#endregion

1656
Apewer/Externals/MarkdownSharp-1.13/Markdown.cs

File diff suppressed because it is too large

45
Apewer/Externals/MarkdownSharp-1.13/MarkdownOptions.cs

@ -0,0 +1,45 @@
namespace MarkdownSharp
{
/// <summary>
/// Options for configuring MarkdownSharp.
/// </summary>
internal class MarkdownOptions
{
/// <summary>
/// when true, (most) bare plain URLs are auto-hyperlinked
/// WARNING: this is a significant deviation from the markdown spec
/// </summary>
public bool AutoHyperlink { get; set; }
/// <summary>
/// when true, RETURN becomes a literal newline
/// WARNING: this is a significant deviation from the markdown spec
/// </summary>
public bool AutoNewlines { get; set; }
/// <summary>
/// use ">" for HTML output, or " />" for XHTML output
/// </summary>
public string EmptyElementSuffix { get; set; }
/// <summary>
/// when false, email addresses will never be auto-linked
/// WARNING: this is a significant deviation from the markdown spec
/// </summary>
public bool LinkEmails { get; set; }
/// <summary>
/// when true, bold and italic require non-word characters on either side
/// WARNING: this is a significant deviation from the markdown spec
/// </summary>
public bool StrictBoldItalic { get; set; }
/// <summary>
/// when true, asterisks may be used for intraword emphasis
/// this does nothing if StrictBoldItalic is false
/// </summary>
public bool AsteriskIntraWordEmphasis { get; set; }
}
}

28
Apewer/Externals/MarkdownSharp-1.13/Token.cs

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MarkdownSharp
{
internal enum TokenType
{
Text,
Tag
}
internal struct Token
{
public Token(TokenType type, string value)
{
Type = type;
Value = value;
}
public TokenType Type;
public string Value;
}
}

3
Apewer/Internals/Constant.cs

@ -38,6 +38,9 @@ namespace Apewer.Internals
/// <summary>所有 GUID 中的字符,包含字母、数字和连字符。</summary>
public const string GuidCollection = "0123456789ABCDEFabcdef-";
/// <summary>所有主键字符。</summary>
public const string KeyCollection = "0123456789abcdefghijklmnopqrstuvwxyz";
/// <summary>所有十六进制字符。</summary>
public const string HexCollection = "0123456789abcdef";

45
Apewer/Json.cs

@ -16,6 +16,9 @@ namespace Apewer
/// <summary>Json。</summary>
[Serializable]
public class Json
#if !NET20
: DynamicObject
#endif
{
#region 配置。
@ -1698,6 +1701,48 @@ namespace Apewer
#endregion
#region Dynamic
#if !NET20
/// <summary></summary>
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var contains = false;
if (IsObject)
{
var property = GetProperty(binder.Name);
contains = property != null;
result = contains ? property.Value : null;
return contains;
}
result = null;
return contains;
}
/// <summary></summary>
public override bool TrySetMember(SetMemberBinder binder, object value)
{
var name = binder.Name;
if (value == null) return SetProperty(name);
if (value is Json) return SetProperty(name, (Json)value);
if (value is string) return SetProperty(name, (string)value);
if (value is bool) return SetProperty(name, (bool)value);
if (value is int) return SetProperty(name, (int)value);
if (value is long) return SetProperty(name, (long)value);
if (value is float) return SetProperty(name, (float)value);
if (value is double) return SetProperty(name, (double)value);
if (value is decimal) return SetProperty(name, (decimal)value);
return false;
}
#endif
#endregion
#region Statics
#region 创建实例。

2
Apewer/Models/StringPairs.cs

@ -15,7 +15,7 @@ namespace Apewer.Models
public string Add(string key, string value)
{
if (string.IsNullOrEmpty(key)) return "参数 Name 无效。";
if (string.IsNullOrEmpty(value)) return "参数 Value 无效。";
// if (string.IsNullOrEmpty(value)) return "参数 Value 无效。";
var kvp = new KeyValuePair<string, string>(key, value);
Add(kvp);

4
Apewer/NetworkUtility.cs

@ -58,7 +58,7 @@ namespace Apewer
}
}
catch { }
return new DateTime(1, 0, 0, 0, 0, 0, 0, DateTimeKind.Utc);
return DateTimeUtility.Zero();
}
/// <summary>从 NTP 服务器获取 UTC 时间。</summary>
@ -90,7 +90,7 @@ namespace Apewer
return utc;
}
catch { }
return new DateTime(1, 0, 0, 0, 0, 0, 0, DateTimeKind.Utc);
return DateTimeUtility.Zero();
}
private static uint SwapEndian(ulong x)

54
Apewer/Source/ColumnAttribute.cs

@ -21,6 +21,8 @@ namespace Apewer.Source
private bool _independent = false;
private bool _locked = false;
/// <summary>使用自动的列名称。当类型为 VarChar 或 NVarChar 时必须指定长度。</summary>
/// <exception cref="System.ArgumentException"></exception>
public ColumnAttribute(ColumnType type = ColumnType.NVarChar255, int length = 0)
@ -41,19 +43,55 @@ namespace Apewer.Source
}
/// <summary>属性。</summary>
public PropertyInfo Property { get { return _property; } internal set { _property = value; } }
public PropertyInfo Property
{
get { return _property; }
internal set
{
if (_locked) return;
_property = value;
}
}
/// <summary>字段名。</summary>
public string Field { get { return _field; } set { _field = value; } }
public string Field
{
get { return _field; }
set
{
if (_locked) return;
_field = value;
}
}
/// <summary>指定字段的最大长度。</summary>
public int Length { get { return _length; } set { _length = value; } }
public int Length
{
get { return _length; }
set
{
if (_locked) return;
_length = value;
}
}
/// <summary>字段类型。</summary>
public ColumnType Type { get { return _type; } set { _type = value; } }
public ColumnType Type
{
get { return _type; }
set
{
if (_locked) return;
_type = value;
}
}
/// <summary>Independent 特性。</summary>
public bool Independent { get { return _independent; } internal set { _independent = value; } }
public bool Independent
{
get { return _independent; }
internal set { _independent = value; }
}
/// <exception cref="System.ArgumentException"></exception>
private void New(string field, ColumnType type, int length, bool underline)
@ -77,6 +115,12 @@ namespace Apewer.Source
}
}
/// <summary>锁定属性,阻止修改。</summary>
public void Lock()
{
_locked = true;
}
}
}

128
Apewer/Source/MySql.cs

@ -1,7 +1,5 @@
#if NET40 || NET461
/* 2020.9.4.0 */
using Apewer;
using Apewer.Source;
using Externals.MySql.Data.MySqlClient;
@ -415,12 +413,12 @@ namespace Apewer.Source
return CreateTable(model.GetType());
}
/// <summary></summary>
/// <summary>插入记录。成功时候返回空字符串,发生异常时返回异常信息。</summary>
public string Insert(Record entity)
public string Insert(Record entity, bool resetKey = false)
{
if (entity == null) return "参数无效。";
entity.FixProperties();
if (resetKey) Record.ResetKey(entity);
var structure = null as TableStructure;
try { structure = TableStructure.ParseModel(entity); }
@ -477,7 +475,7 @@ namespace Apewer.Source
}
}
/// <summary></summary>
/// <summary>获取记录。</summary>
public Result<T> QueryRecord<T>(string key, long flag = 0) where T : Record
{
var k = TextUtility.RestrictGuid(key);
@ -515,6 +513,25 @@ namespace Apewer.Source
}
}
/// <summary>获取记录。</summary>
/// <param name="skip">要跳过的记录数,可用最小值为 0。</param>
/// <param name="count">要获取的记录数,可用最小值为 1。</param>
public Result<List<T>> QueryRecords<T>(int skip, int count) where T : Record
{
try
{
if (skip < 0) return new Result<List<T>>(new ArgumentOutOfRangeException(nameof(skip)));
if (count < 1) return new Result<List<T>>(new ArgumentOutOfRangeException(nameof(count)));
var tableName = TableStructure.ParseTable(typeof(T)).Name;
var sql = $"select * from `{tableName}` where _flag = 1 limit {skip}, {count}; ";
return QueryRecords<T>(sql);
}
catch (Exception exception)
{
return new Result<List<T>>(exception);
}
}
/// <summary>查询有效的 Key 值。</summary>
public Result<List<string>> QueryKeys(Type model, long flag = 0)
{
@ -562,10 +579,111 @@ namespace Apewer.Source
}
}
/// <summary>对表添加列,返回错误信息。</summary>
/// <typeparam name="T">记录类型。</typeparam>
/// <param name="column">列名称。</param>
/// <param name="type">字段类型。</param>
/// <param name="length">字段长度,仅对 VarChar 和 NVarChar 类型有效。</param>
/// <returns></returns>
public string AddColumn<T>(string column, ColumnType type, int length = 0) where T : Record
{
var columnName = SafeColumn(column);
if (columnName.IsEmpty()) return "列名无效。";
var tableName = TableStructure.ParseTable(typeof(T)).Name;
var columeType = "";
switch (type)
{
case ColumnType.Integer:
columeType = "bigint";
break;
case ColumnType.Float:
columeType = "double";
break;
case ColumnType.Binary:
columeType = "longblob";
break;
case ColumnType.DateTime:
columeType = "datetime";
break;
case ColumnType.VarChar:
case ColumnType.NVarChar:
columeType = $"varchar({length})";
break;
case ColumnType.VarChar255:
case ColumnType.NVarChar255:
columeType = "varchar(255)";
break;
case ColumnType.VarCharMax:
case ColumnType.NVarCharMax:
columeType = "varchar(max)";
break;
case ColumnType.Text:
columeType = "longtext";
break;
}
if (columeType.IsEmpty()) return "类型不支持。";
var sql = $"alter table `{tableName}` add {columnName} {columeType}; ";
var execute = Execute(sql) as Execute;
var error = execute.Error;
return error;
}
#endregion
#region static
/// <summary>对文本转义,符合 SQL 安全性。可根据字段类型限制 UTF-8 字节数,默认为 0 时不限制字节数。</summary>
public static string Escape(string text, int bytes = 0)
{
if (text.IsEmpty()) return "";
var t = text ?? "";
t = t.Replace("\\", "\\\\");
t = t.Replace("'", "\\'");
t = t.Replace("\n", "\\n");
t = t.Replace("\r", "\\r");
t = t.Replace("\b", "\\b");
t = t.Replace("\t", "\\t");
t = t.Replace("\f", "\\f");
if (bytes > 5)
{
if (t.GetBytes(Encoding.UTF8).Length > bytes)
{
while (true)
{
t = t.Substring(0, t.Length - 1);
if (t.GetBytes(Encoding.UTF8).Length <= (bytes - 4)) break;
}
t = t + " ...";
}
}
return t;
}
/// <summary></summary>
public static string SafeTable(string table)
{
const string chars = "0123456789_-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var safety = TextUtility.RestrictCharacters(table, chars).SafeLower();
var pc = 0;
for (var i = 0; i < safety.Length; i++)
{
if (safety[i] == '-') pc += 1;
else break;
}
if (pc == safety.Length) return "";
if (pc > 0) safety = safety.Substring(pc);
return safety;
}
/// <summary></summary>
public static string SafeColumn(string column) => SafeTable(column);
/// <summary></summary>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidOperationException"></exception>

6
Apewer/Source/Record.cs

@ -46,13 +46,15 @@ namespace Apewer.Source
[Column("_key", ColumnType.NVarChar, 128)]
public virtual string Key { get { return _key; } set { _key = TextModifier.Compact(value, 128); } }
internal static void SetNewKey(Record record)
/// <summary>重置主键。</summary>
internal static void ResetKey(Record record)
{
if (record == null) return;
record.Key = GenerateKey();
}
internal static string GenerateKey() => Guid.NewGuid().ToString().ToLower().Replace("-", "");
/// <summary>生成新主键。</summary>
public static string GenerateKey() => Guid.NewGuid().ToString().ToLower().Replace("-", "");
internal static void FixProperties(Record record)
{

22
Apewer/Source/TableAttribute.cs

@ -15,6 +15,8 @@ namespace Apewer.Source
private string _name;
private bool _independent = false;
private bool _locked = false;
/// <summary></summary>
public TableAttribute(string name = null)
{
@ -27,18 +29,26 @@ namespace Apewer.Source
_name = TableStructure.RestrictName(name, underline);
}
/// <summary></summary>
/// <summary>表名。</summary>
public string Name
{
get { return _name; }
set { _name = TableStructure.RestrictName(value, false); }
set
{
if (_locked) return;
_name = TableStructure.RestrictName(value, false);
}
}
/// <summary></summary>
public bool Independent
{
get { return _independent; }
internal set { _independent = value; }
internal set
{
if (_locked) return;
_independent = value;
}
}
/// <summary></summary>
@ -47,6 +57,12 @@ namespace Apewer.Source
return _name.GetHashCode();
}
/// <summary>锁定属性,阻止修改。</summary>
public void Lock()
{
_locked = true;
}
}
}

148
Apewer/Source/TableStructure.cs

@ -14,6 +14,8 @@ namespace Apewer.Source
public sealed class TableStructure
{
private bool _locked = false;
private string _tablename = Constant.EmptyString;
private bool _independent = false;
@ -21,36 +23,95 @@ namespace Apewer.Source
internal TableStructure() { }
/// <summary></summary>
public bool Independent { get { return _independent; } private set { _independent = value; } }
/// <summary>不依赖 Record 公共属性。</summary>
public bool Independent
{
get { return _independent; }
private set { _independent = value; }
}
/// <summary></summary>
public string Table { get { return _tablename; } set { _tablename = value ?? ""; } }
/// <summary>表名称。</summary>
public string Table
{
get { return _tablename; }
private set { _tablename = value ?? ""; }
}
/// <summary></summary>
public Dictionary<string, ColumnAttribute> Columns { get { return _columns; } set { _columns = value; } }
/// <summary>列信息。</summary>
public Dictionary<string, ColumnAttribute> Columns
{
get
{
if (_locked)
{
var copy = new Dictionary<string, ColumnAttribute>(_columns.Count);
foreach (var c in _columns) copy.Add(c.Key, c.Value);
return copy;
}
return _columns;
}
private set { _columns = value; }
}
/// <summary>锁定属性,阻止修改。</summary>
public void Lock()
{
_locked = true;
foreach (var c in _columns) c.Value.Lock();
}
#region cache
private static Dictionary<string, TableStructure> _tsc = new Dictionary<string, TableStructure>();
private static Dictionary<string, TableAttribute> _tac = new Dictionary<string, TableAttribute>();
#endregion
#region static
/// <summary></summary>
/// <exception cref="System.Exception"></exception>
/// <exception cref="System.ArgumentNullException"></exception>
public static TableStructure ParseModel(object entity)
public static TableStructure ParseModel(object entity, bool useCache = true)
{
if (entity == null) throw new ArgumentNullException("参数无效");
var type = entity.GetType();
var result = ParseModel(type);
var result = ParseModel(type, useCache);
return result;
}
/// <summary></summary>
/// <exception cref="System.Exception"></exception>
/// <exception cref="System.ArgumentNullException"></exception>
public static TableStructure ParseModel(Type model)
public static TableStructure ParseModel<T>(bool useCache = true) where T : IRecord
{
return ParseModel(typeof(T), useCache);
}
/// <summary></summary>
/// <exception cref="System.Exception"></exception>
/// <exception cref="System.ArgumentNullException"></exception>
public static TableStructure ParseModel(Type model, bool useCache = true)
{
var type = model;
if (type == null) throw new ArgumentNullException("参数无效");
// 使用缓存。
var cacheKey = type.FullName;
if (useCache)
{
var hint = null as TableStructure;
lock (_tsc)
{
if (_tsc.ContainsKey(cacheKey))
{
hint = _tsc[cacheKey];
}
}
if (hint != null) return hint;
}
// 检查基类。
// if (type.BaseType.FullName.Equals(typeof(DatabaseRecord).FullName) == false) return "基类不受支持。";
@ -91,18 +152,57 @@ namespace Apewer.Source
columns = SortColumns(columns);
// 返回结果。
var result = new TableStructure();
result.Table = ta.Name;
result.Independent = ta.Independent;
result.Columns = columns;
return result;
var ts = new TableStructure();
ts.Table = ta.Name;
ts.Independent = ta.Independent;
ts.Columns = columns;
// 锁定属性。
ts.Lock();
// 加入缓存。
if (useCache)
{
lock (_tsc)
{
if (!_tsc.ContainsKey(cacheKey))
{
_tsc.Add(cacheKey, ts);
}
}
}
return ts;
}
/// <summary></summary>
/// <param name="type"></param>
/// <exception cref="Exception"></exception>"
public static TableAttribute ParseTable<T>(bool useCache = true) where T : IRecord
{
return ParseTable(typeof(T), useCache);
}
/// <summary></summary>
/// <param name="type"></param>
/// <exception cref="Exception"></exception>"
public static TableAttribute ParseTable(Type type)
public static TableAttribute ParseTable(Type type, bool useCache = true)
{
// 使用缓存。
var cacheKey = type.FullName;
if (useCache)
{
var hint = null as TableAttribute;
lock (_tac)
{
if (_tac.ContainsKey(cacheKey))
{
hint = _tac[cacheKey];
}
}
if (hint != null) return hint;
}
var tas = type.GetCustomAttributes(typeof(TableAttribute), false);
if (tas.LongLength < 1L) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 不包含 ", typeof(TableAttribute).FullName, "。"));
if (tas.LongLength > 1L) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 包含多个 ", typeof(TableAttribute).FullName, "。"));
@ -116,6 +216,21 @@ namespace Apewer.Source
ta.Independent = ClassUtility.ContainsAttribute<IndependentAttribute>(type, true);
// 锁定属性。
ta.Lock();
// 加入缓存。
if (useCache)
{
lock (_tac)
{
if (!_tac.ContainsKey(cacheKey))
{
_tac.Add(cacheKey, ta);
}
}
}
return ta;
}
@ -176,6 +291,9 @@ namespace Apewer.Source
ca.Property = property;
// 锁定属性。
ca.Lock();
return ca;
}

16
Apewer/TextUtility.cs

@ -354,17 +354,22 @@ namespace Apewer
if (string.IsNullOrEmpty(text)) return Constant.EmptyString;
var input = ToLower(text);
var max = input.Length;
if (maxLength > 0 && maxLength < input.Length) max = maxLength;
var max = maxLength;
if (max < 1 || max > input.Length) max = input.Length;
var sb = new StringBuilder();
for (var i = 0; i < max; i++)
var total = input.Length;
var length = 0;
for (var i = 0; i < total; i++)
{
var c = input[i];
if (Constant.HexCollection.IndexOf(c) < 0) continue;
if (Constant.KeyCollection.IndexOf(c) < 0) continue;
sb.Append(c);
length += 1;
if (length >= max) break;
}
var result = sb.ToString();
if (result.Length > max) result = result.Substring(0, max);
return result;
}
@ -412,6 +417,9 @@ namespace Apewer
return match.Success;
}
/// <summary>渲染 Markdown 文本为 HTML 文本。</summary>
public static string RenderMarkdown(string markdown) => MarkdownSharp.Demo.ToHtml(markdown);
/// <summary>合并用于启动进程的参数。</summary>
public static string MergeProcessArgument(params object[] args)
{

176
Apewer/Web/ApiInternals.cs

@ -1,14 +1,16 @@
#if NETFX || NETCORE
using Apewer;
using Apewer.Network;
using Apewer.Models;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Reflection;
#if NETFX
using System.Web;
using Apewer.Models;
#endif
#if NETCORE
using Microsoft.AspNetCore.Http;
@ -45,6 +47,7 @@ namespace Apewer.Web
if (deduplicates.Contains(assembly)) continue;
deduplicates.Add(assembly);
}
_assemblies = deduplicates;
var aes = new ObjectSet<ApiApplication>();
foreach (var assembly in deduplicates)
@ -201,9 +204,9 @@ namespace Apewer.Web
internal static StringPairs ParseUrlParameters
#if NETFX
(System.Web.HttpRequest request) => WebUtility.ParseParameters(request.Url.Query);
(HttpRequest request) => WebUtility.ParseParameters(request.Url.Query);
#else
(Microsoft.AspNetCore.Http.HttpRequest request)
(HttpRequest request)
{
var list = new StringPairs();
foreach (var key in request.Query.Keys)
@ -217,11 +220,7 @@ namespace Apewer.Web
}
#endif
#if NETFX
internal static ApiRequest GetRequest(System.Web.HttpRequest request)
#else
internal static ApiRequest GetRequest(Microsoft.AspNetCore.Http.HttpRequest request)
#endif
internal static ApiRequest GetRequest(HttpRequest request)
{
if (request == null) return null;
if (string.IsNullOrEmpty(request.Path)) return null;
@ -232,7 +231,7 @@ namespace Apewer.Web
apiRequest.Parameters = ParseUrlParameters(request);
apiRequest.UserAgent = WebUtility.GetUserAgent(request);
#if NETFX
apiRequest.Referrer = request.UrlReferrer;
apiRequest.Referrer = request.UrlReferrer;
#endif
// 获取 Http Method。
@ -258,8 +257,11 @@ namespace Apewer.Web
}
}
// Cookies。
apiRequest.Cookies = WebUtility.GetCookies(request);
// 解析 POST 请求。
if (apiRequest.Method == HttpMethod.POST)
if (apiRequest.Method == Apewer.Network.HttpMethod.POST)
{
#if NETFX
var post = BinaryUtility.Read(request.InputStream);
@ -293,6 +295,9 @@ namespace Apewer.Web
if (string.IsNullOrEmpty(session)) session = WebUtility.GetParameter(apiRequest.Parameters, "session");
if (string.IsNullOrEmpty(page)) page = WebUtility.GetParameter(apiRequest.Parameters, "page");
// 从 Cookie 中获取 Ticket。
if (string.IsNullOrEmpty(ticket)) ticket = apiRequest.Cookies.GetValue("ticket");
// 最后检查 URL 路径。
var paths = request.Path.ToString().Split('/');
if (string.IsNullOrEmpty(application) && paths.Length >= 2) application = TextUtility.DecodeUrl(paths[1]);
@ -353,12 +358,12 @@ namespace Apewer.Web
}
/// <summary>输出 UTF-8 文本。</summary>
internal static void RespondText(ApiResponse response, string content, string type = "text/plain")
internal static void RespondText(ApiResponse response, string content, string type = "text/plain; charset=utf-8")
{
if (response == null) return;
response.Type = ApiFormat.Text;
response.TextString = content;
response.TextType = type ?? "text/plain";
response.TextType = type ?? "text/plain; charset=utf-8";
}
/// <summary>输出字节数组。</summary>
@ -466,9 +471,7 @@ namespace Apewer.Web
#region HttpResponse
public static void AddHeader
#if NETFX
(System.Web.HttpResponse response, string name, string value)
public static void AddHeader(HttpResponse response, string name, string value)
{
if (response == null || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value)) return;
#if NET20
@ -477,33 +480,14 @@ namespace Apewer.Web
try { response.Headers.Add(name, value); } catch { }
#endif
}
#else
(Microsoft.AspNetCore.Http.HttpResponse response, string name, string value)
{
if (response == null || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value)) return;
try { response.Headers.Add(name, value); } catch { }
}
#endif
public static void AddHeaders
#if NETFX
(System.Web.HttpResponse response, NameValueCollection collection)
public static void AddHeaders(HttpResponse response, NameValueCollection collection)
{
if (response == null || collection == null) return;
foreach (var key in collection.AllKeys) AddHeader(response, key, collection[key]);
}
#else
(Microsoft.AspNetCore.Http.HttpResponse response, NameValueCollection collection)
{
if (response == null) return;
if (collection == null) return;
foreach (var key in collection.AllKeys) AddHeader(response, key, collection[key]);
}
#endif
public static void AddHeaders
#if NETFX
(System.Web.HttpResponse response, StringPairs headers)
public static void AddHeaders(HttpResponse response, StringPairs headers)
{
if (response == null || headers == null) return;
foreach (var key in headers.GetAllKeys())
@ -512,73 +496,121 @@ namespace Apewer.Web
foreach (var value in values) AddHeader(response, key, value);
}
}
#else
(Microsoft.AspNetCore.Http.HttpResponse response, StringPairs headers)
#if NETFX
private static FieldInfo CacheControlField = null;
private static void SetCacheControlField(HttpResponse response, string value)
{
if (response == null || headers == null) return;
foreach (var key in headers.GetAllKeys())
if (CacheControlField == null)
{
var values = headers.GetValues(key);
foreach (var value in values) AddHeader(response, key, value);
var type = typeof(HttpResponse);
var field = type.GetField("_cacheControl", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
if (field == null) return;
CacheControlField = field;
}
if (CacheControlField != null) CacheControlField.SetValue(response, value);
}
#endif
public static void SetTextPlain
#if NETFX
(System.Web.HttpResponse response, string value = null)
/// <summary>设置缓存时间,单位为秒,最大为 2592000 秒(30 天)。</summary>
public static void SetCacheControl
(HttpResponse response, int seconds = 0)
{
if (response == null) return;
if (string.IsNullOrEmpty(value))
var s = seconds;
if (s < 0) s = 0;
if (s > 2592000) s = 2592000;
#if NETFX
if (s > 0)
{
response.ContentType = "text/plain";
response.Charset = "utf-8";
SetCacheControlField(response, $"public, max-age={s}, s-maxage={s}");
}
else
{
response.ContentType = value;
var minutes = s < 60 ? 0 : (s / 60);
try { response.CacheControl = "no-cache"; } catch { }
try { response.Expires = minutes; } catch { }
AddHeader(response, "Pragma", "no-cache");
}
}
#else
(Microsoft.AspNetCore.Http.HttpResponse response, string value = null)
if (s > 0)
{
AddHeader(response, "Cache-Control", $"public, max-age={s}, s-maxage={s}");
}
else
{
var minutes = s < 60 ? 0 : (s / 60);
AddHeader(response, "Cache-Control", "no-cache, no-store, must-revalidate");
AddHeader(response, "Expires", minutes.ToString());
AddHeader(response, "Pragma", "no-cache");
}
#endif
}
public static void SetTextPlain(HttpResponse response, string value = null)
{
if (response == null) return;
response.ContentType = string.IsNullOrEmpty(value) ? "text/plain; charset=utf-8" : value;
const string plain = "text/plain; charset=utf-8";
// response.ContentType = "text/plain";
// response.Charset = "utf-8";
response.ContentType = string.IsNullOrEmpty(value) ? plain : value;
}
#endif
public static void SetContentLength
#if NETFX
(System.Web.HttpResponse response, long value)
public static void SetContentLength(HttpResponse response, long value)
{
if (response == null) return;
if (value < 0L) return;
#if NETFX
response.AddHeader("Content-Length", value.ToString());
}
#else
(Microsoft.AspNetCore.Http.HttpResponse response, long value)
{
if (response == null) return;
if (value < 0L) return;
response.ContentLength = value;
}
#endif
}
public static Stream GetStream
#if NETFX
(System.Web.HttpResponse response)
public static Stream GetStream(HttpResponse response)
{
if (response == null) return null;
#if NETFX
return response.OutputStream;
}
#else
(Microsoft.AspNetCore.Http.HttpResponse response)
{
if (response == null) return null;
return response.Body;
}
#endif
}
/// <summary>设置响应的 Cookies。</summary>
public static void SetCookies(HttpResponse response, IEnumerable<KeyValuePair<string, string>> cookies)
{
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
// https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Set-Cookie
// <cookie-name> 可以是除了控制字符 (CTLs)、空格 (spaces) 或制表符 (tab)之外的任何 US-ASCII 字符。
// 同时不能包含以下分隔字符: ( ) < > @ , ; : \ " / [ ] ? = { }.
// <cookie-value> 是可选的,如果存在的话,那么需要包含在双引号里面。
// 支持除了控制字符(CTLs)、空格(whitespace)、双引号(double quotes)、逗号(comma)、分号(semicolon)以及反斜线(backslash)之外的任意 US-ASCII 字符。
// 关于编码:许多应用会对 cookie 值按照URL编码(URL encoding)规则进行编码,但是按照 RFC 规范,这不是必须的。不过满足规范中对于 <cookie-value> 所允许使用的字符的要求是有用的。
if (cookies == null) return;
var ps = new List<string>();
foreach (var kvp in cookies)
{
var k = kvp.Key;
var v = kvp.Value;
if (k.IsEmpty()) continue;
ps.Add(TextUtility.Merge(k, "="), v);
}
if (ps.Count < 1) return;
var value = TextUtility.Merge("; ", ps);
AddHeader(response, "Set-Cookie", value);
}
#endregion

195
Apewer/Web/ApiInvoker.cs

@ -233,7 +233,7 @@ namespace Apewer.Web
if (request == null || response == null) return;
// 创建应用程序实例。
var target = null as ApiController;
var controller = null as ApiController;
try
{
if (application == null)
@ -246,7 +246,7 @@ namespace Apewer.Web
//var instance = assembly.CreateInstance(argApplication.FullName, false);
//var handler = Activator.CreateInstance(null, argApplication.FullName);
//target = (Application)handler.Unwrap();
target = (ApiController)Activator.CreateInstance(application.Type);
controller = (ApiController)Activator.CreateInstance(application.Type);
}
}
catch (Exception exception)
@ -258,13 +258,13 @@ namespace Apewer.Web
// 调用功能。
try
{
target.Request = request;
target.Response = response;
target.Context = Context;
controller.Request = request;
controller.Response = response;
controller.Context = Context;
if (target.AfterInitialized != null) target.AfterInitialized.Invoke();
if (controller.AfterInitialized != null) controller.AfterInitialized.Invoke();
var allowed = target.AllowFunction;
var allowed = controller.AllowFunction;
if (application.Independent) allowed = false;
if (allowed)
@ -275,8 +275,8 @@ namespace Apewer.Web
}
else
{
var result = function.Method.Invoke(target, null);
target.Dispose();
var result = function.Method.Invoke(controller, null);
controller.Dispose();
if (response.Status.IsBlank()) response.Status = "ok";
if (function.Returnable && result != null)
{
@ -288,102 +288,124 @@ namespace Apewer.Web
}
}
}
}
catch (Exception exception)
{
response.Error(exception.InnerException);
}
try { target.Dispose(); } catch { }
try { controller.Dispose(); } catch { }
}
private string Output()
private void OutputMain()
{
if (Context == null) return "Invoker 的 Context 无效。";
if (ApiResponse == null) return "Invoker 的 ApiResponse 无效。";
var response = Context.Response;
try
// 设置自定义头。
ApiInternals.AddHeaders(response, ApiResponse.Headers);
var setCookies = WebUtility.SetCookies(response, ApiResponse.Cookies);
if (setCookies.NotEmpty())
{
var response = Context.Response;
var body = ApiInternals.GetStream(response);
ApiInternals.AddHeaders(response, ApiResponse.Headers);
switch (ApiResponse.Type)
{
case ApiFormat.Json:
{
var text = ApiInternals.ExportJson(ApiResponse, ApiOptions.JsonIndent, ApiOptions.AllowException);
var data = TextUtility.ToBinary(text);
ApiInternals.SetTextPlain(response);
ApiInternals.SetContentLength(response, data.LongLength);
body.Write(data);
}
break;
case ApiFormat.Text:
ApiResponse.Error("系统错误。");
ApiResponse.Data.Reset(Json.NewObject());
ApiResponse.Data["cookies"] = setCookies;
}
// 过期。
ApiInternals.SetCacheControl(response, ApiResponse.Expires);
// 判断内容类型输出。
var body = ApiInternals.GetStream(response);
switch (ApiResponse.Type)
{
case ApiFormat.Json:
{
var text = ApiInternals.ExportJson(ApiResponse, ApiOptions.JsonIndent, ApiOptions.AllowException);
var data = TextUtility.ToBinary(text);
ApiInternals.SetTextPlain(response);
ApiInternals.SetContentLength(response, data.LongLength);
body.Write(data);
}
break;
case ApiFormat.Text:
{
var data = TextUtility.ToBinary(ApiResponse.TextString);
var type = ApiResponse.TextType;
ApiInternals.SetTextPlain(response, type);
ApiInternals.SetContentLength(response, data.LongLength);
body.Write(data);
}
break;
case ApiFormat.Binary:
{
Context.Response.ContentType = ApiResponse.BinaryType ?? "application/octet-stream";
if (ApiResponse.BinaryBytes != null)
{
var data = TextUtility.ToBinary(ApiResponse.TextString);
var type = ApiResponse.TextType;
ApiInternals.SetTextPlain(response, type);
ApiInternals.SetContentLength(response, data.LongLength);
body.Write(data);
var data = ApiResponse.BinaryBytes;
var length = data.LongLength;
ApiInternals.SetContentLength(response, length);
if (length > 0L) body.Write(data);
}
break;
case ApiFormat.Binary:
else if (ApiResponse.BinaryStream != null && ApiResponse.BinaryStream.CanRead)
{
Context.Response.ContentType = ApiResponse.BinaryType ?? "application/octet-stream";
if (ApiResponse.BinaryBytes != null)
var source = ApiResponse.BinaryStream;
try
{
var data = ApiResponse.BinaryBytes;
var length = data.LongLength;
var length = source.Length - source.Position;
ApiInternals.SetContentLength(response, length);
if (length > 0L) body.Write(data);
}
else if (ApiResponse.BinaryStream != null && ApiResponse.BinaryStream.CanRead)
{
var source = ApiResponse.BinaryStream;
try
{
var length = source.Length - source.Position;
ApiInternals.SetContentLength(response, length);
if (length > 0) body.Write(source);
}
catch { }
KernelUtility.Dispose(source);
if (length > 0) body.Write(source);
}
catch { }
KernelUtility.Dispose(source);
}
break;
case ApiFormat.File:
{
var type = ApiResponse.FileType ?? "application/octet-stream";
var name = TextUtility.EncodeUrl(ApiResponse.FileName);
var disposition = TextUtility.Merge("attachment; filename=", name);
}
break;
case ApiFormat.File:
{
var type = ApiResponse.FileType ?? "application/octet-stream";
var name = TextUtility.EncodeUrl(ApiResponse.FileName);
var disposition = TextUtility.Merge("attachment; filename=", name);
response.ContentType = type;
ApiInternals.AddHeader(response, "Content-Disposition", disposition);
response.ContentType = type;
ApiInternals.AddHeader(response, "Content-Disposition", disposition);
var source = ApiResponse.FileStream;
try
var source = ApiResponse.FileStream;
try
{
if (source != null && source.CanRead)
{
if (source != null && source.CanRead)
{
var length = source.Length - source.Position;
ApiInternals.SetContentLength(response, length);
if (length > 0) body.Write(source);
}
var length = source.Length - source.Position;
ApiInternals.SetContentLength(response, length);
if (length > 0) body.Write(source);
}
catch { }
KernelUtility.Dispose(source);
}
break;
case ApiFormat.Redirect:
{
WebUtility.RedirectResponse(response, ApiResponse.RedirectUrl);
}
break;
}
// Context.Response.Flush();
// Context.Response.End();
catch { }
KernelUtility.Dispose(source);
}
break;
case ApiFormat.Redirect:
{
WebUtility.RedirectResponse(response, ApiResponse.RedirectUrl);
}
break;
}
// Context.Response.Flush();
// Context.Response.End();
}
private string Output()
{
if (Context == null) return "Invoker 的 Context 无效。";
if (ApiResponse == null) return "Invoker 的 ApiResponse 无效。";
#if DEBUG
OutputMain();
#else
try
{
OutputMain();
}
catch (Exception ex) { return ex.Message; }
#endif
return null;
}
@ -406,6 +428,14 @@ namespace Apewer.Web
invoker.Run();
}
/// <summary>获取用于执行 API 的程序集。</summary>
public static Assembly[] GetAssemblies()
{
var list = ApiInternals.Assemblies;
if (list == null) return new Assembly[0];
return list.ToArray();
}
#if NETFX
/// <summary>解析请求,获取指定程序集中定义的入口,并执行。指定程序集为 NULL 值时,将自动从应用程序域获取所有 WebAPI 入口。</summary>
@ -467,8 +497,7 @@ namespace Apewer.Web
/// <summary>设置 Kestrel 的 API 入口。</summary>
public static void SetKestrelEntries(Assembly assembly, bool sort = true)
{
var assemblies = new Assembly[] { assembly };
if (assemblies[0] == null) assemblies[0] = Assembly.GetCallingAssembly();
var assemblies = new Assembly[] { assembly ?? Assembly.GetCallingAssembly() };
KestrelEntries = ApiInternals.GetEntries(assemblies, sort);
}

3
Apewer/Web/ApiRequest.cs

@ -46,6 +46,9 @@ namespace Apewer.Web
/// <summary>HTTP 头。</summary>
public StringPairs Headers { get; set; } = new StringPairs();
/// <summary>Cookies。</summary>
public StringPairs Cookies { get; set; } = new StringPairs();
#endregion
#region API 请求。

9
Apewer/Web/ApiResponse.cs

@ -15,9 +15,12 @@ namespace Apewer.Web
private Json _data = Json.NewObject();
/// <summary></summary>
/// <summary>HTTP 头。</summary>
public StringPairs Headers { get; set; } = new StringPairs();
/// <summary>Cookies。</summary>
public StringPairs Cookies { get; set; } = new StringPairs();
#if NETFX
internal System.Web.HttpContext Context { get; set; }
#endif
@ -59,6 +62,10 @@ namespace Apewer.Web
internal ApiFormat Type = ApiFormat.Json;
internal Exception Exception { get; set; }
/// <summary>设置缓存过期时间,单位为秒。默认值:0,立即过期,不缓存。</summary>
/// <remarks>在 .NET Framework 中,此设置可能无效。</remarks>
public int Expires { get; set; }
#endregion
#region 输出纯文本。

108
Apewer/Web/WebUtility.cs

@ -154,8 +154,9 @@ namespace Apewer.Web
var d = GetDirectIP(request);
var f = GetForwardedIP(request);
// return string.IsNullOrEmpty(f) ? "NULL" : $"{f},NULL";
// return string.IsNullOrEmpty(f) ? $"{d}" : $"{f},{d}";
// 去除端口。
if (d.NotEmpty()) d = d.Split(':')[0];
if (f.NotEmpty()) f = f.Split(':')[0];
// 没有代理,返回 Direct IP。
if (string.IsNullOrEmpty(f)) return d;
@ -319,8 +320,13 @@ namespace Apewer.Web
foreach (var key in request.Headers.Keys)
#endif
{
var value = request.Headers[key].ToString();
sp.Add(new KeyValuePair<string, string>(key, value));
try
{
var values = request.Headers[key];
var text = values.ToString();
sp.Add(new KeyValuePair<string, string>(key, text));
}
catch { }
}
}
return sp;
@ -377,6 +383,20 @@ namespace Apewer.Web
#endif
}
/// <summary>设置响应。</summary>
public static string Respond(ApiResponse response, Json data, bool lower = true)
{
if (response == null) return "Response 对象无效。";
if (data != null)
{
if (lower) data = Json.ToLower(data);
response.Data.Reset(data);
}
return null;
}
/// <summary>设置响应,当发生错误时设置响应。返回错误信息。</summary>
public static string Respond(ApiResponse response, IList list, bool lower = true, int depth = -1, bool force = false)
{
@ -631,6 +651,86 @@ namespace Apewer.Web
#endregion
#region Cookies
/// <summary>获取 Cookies。</summary>
public static StringPairs GetCookies(HttpRequest request)
{
var sp = new StringPairs();
if (request != null && request.Cookies != null)
{
#if NETFX
foreach (var key in request.Cookies.AllKeys)
{
try
{
var cookie = request.Cookies[key];
var value = cookie.Value ?? "";
sp.Add(new KeyValuePair<string, string>(key, value));
}
catch { }
}
#else
foreach (var key in request.Cookies.Keys)
{
try
{
var value = request.Cookies[key] ?? "";
sp.Add(new KeyValuePair<string, string>(key, value));
}
catch { }
}
#endif
}
return sp;
}
/// <summary>设置响应的 Cookies。</summary>
public static string SetCookies(HttpResponse response, IEnumerable<KeyValuePair<string, string>> cookies)
{
// experimentation_subject_id
if (cookies == null) return "参数 Response 无效。";
var count = 0;
foreach (var kvp in cookies)
{
count++;
var error = SetCookie(response, kvp.Key, kvp.Value);
if (error.NotEmpty()) return error;
}
return null;
}
/// <summary>设置响应的 Cookie。</summary>
public static string SetCookie(HttpResponse response, string key, string value)
{
if (response == null) return null;
var k = key.SafeTrim();
var v = value.SafeTrim();
if (k.IsEmpty()) return "参数 Key 无效。";
try
{
#if NETFX
var cookie = new HttpCookie(k, v);
var now = DateTime.Now;
cookie.Expires = v.IsEmpty() ? now.AddDays(-1) : now.AddYears(1);
response.SetCookie(cookie);
#else
// var options = new CookieOptions();
// response.Cookies.Append(key, value, options);
response.Cookies.Append(key, value);
#endif
return null;
}
catch (Exception ex)
{
return ex.Message;
}
}
#endregion
#endif
}

14
Apewer/_ChangeLog.md

@ -5,6 +5,20 @@
### 最新提交
### 6.1.0
- DateTime:修正了超出范围值的问题;
- TextUtility:新增 RenderMarkdown 方法,支持将 Markdown 文本转换为 HTML 文本;
- TextUtility:放宽了 SafeKey 限制十六进制字符的限制,现在允许所有字母和数字;
- Source:解析 Record 结构加入缓存,以提高性能;
- MySql:新增 Escape 方法;
- MySql:新增 QueryRecords 重载,支持分页查询;
- MySql:新增 AddColumn 方法,支持增加单列;
- MySql:新增 SafeTable 方法和 SafeColumn 方法,提供对表明和列名的检查;
- WebAPI:支持设置 Response 的缓存过期时间(在 .NET Framework 中可能无效);
- WebAPI:添加 Response.Respond(Json) 方法;
- WebAPI:支持读取或设置 Cookies,且 ApiRequest 从 Cookies 中读取 Ticket;
- WebAPI:Response.Text 现在默认在 Content-Type 中指定 UTF-8。
### 6.0.14
- 修正了 Json.Parse 的 NULL 引用问题;
- 修正了生成 MySQL 的 Insert 语句字段报错问题。

12
Apewer/_Class.cs

@ -8,16 +8,16 @@ public class Class<T> : IComparable, IComparable<T>, IComparable<Class<T>>
private bool _equals = false;
/// <summary>装箱对象。</summary>
public T Value { get; set; }
public virtual T Value { get; set; }
/// <summary></summary>
public bool IsNull
public virtual bool IsNull
{
get { return Value == null; }
}
/// <summary></summary>
public bool HasValue
public virtual bool HasValue
{
get { return Value != null; }
}
@ -63,7 +63,7 @@ public class Class<T> : IComparable, IComparable<T>, IComparable<Class<T>>
/// <summary></summary>
/// <exception cref="MissingMemberException"></exception>
/// <exception cref="NotSupportedException"></exception>
public int CompareTo(object obj)
public virtual int CompareTo(object obj)
{
if (obj != null && obj is T) return CompareTo((T)obj);
if (obj != null && obj is Class<T>) return CompareTo(obj as Class<T>);
@ -76,7 +76,7 @@ public class Class<T> : IComparable, IComparable<T>, IComparable<Class<T>>
/// <summary></summary>
/// <exception cref="MissingMemberException"></exception>
/// <exception cref="NotSupportedException"></exception>
public int CompareTo(T other)
public virtual int CompareTo(T other)
{
if (Value == null) throw new MissingMemberException(typeof(T).FullName, nameof(Value));
if (!(Value is IComparable)) throw new NotSupportedException();
@ -86,7 +86,7 @@ public class Class<T> : IComparable, IComparable<T>, IComparable<Class<T>>
/// <summary></summary>
/// <exception cref="MissingMemberException"></exception>
/// <exception cref="NotSupportedException"></exception>
public int CompareTo(Class<T> other)
public virtual int CompareTo(Class<T> other)
{
if (Value == null) throw new MissingMemberException(typeof(T).FullName, nameof(Value));
if (other == null || !other.HasValue) return 1;

7
Apewer/_Extensions.cs

@ -482,7 +482,7 @@ public static class Extensions
#region Source
/// <summary>为记录的 Key 属性设置新值。</summary>
public static void SetNewKey(this Record @this) => Record.SetNewKey(@this);
public static void ResetKey(this Record @this) => Record.ResetKey(@this);
/// <summary>修补基本属性。</summary>
public static void FixProperties(this Record @this) => Record.FixProperties(@this);
@ -534,7 +534,7 @@ public static class Extensions
public static void Error(this ApiResponse @this, Exception exception) => ApiInternals.RespondError(@this, exception);
/// <summary>输出 UTF-8 文本。</summary>
public static void Text(this ApiResponse @this, string content, string type = "text/plain") => ApiInternals.RespondText(@this, content, type);
public static void Text(this ApiResponse @this, string content, string type = "text/plain; charset=utf-8") => ApiInternals.RespondText(@this, content, type);
/// <summary>输出字节数组。</summary>
public static void Binary(this ApiResponse @this, byte[] content, string type = "application/octet-stream") => ApiInternals.RespondBinary(@this, content, type);
@ -571,6 +571,9 @@ public static class Extensions
/// <summary>设置响应,当发生错误时设置响应。返回错误信息。</summary>
public static string Respond(this ApiResponse @this, Record record, bool lower = true) => WebUtility.Respond(@this, record, lower);
/// <summary>设置响应,当发生错误时设置响应。返回错误信息。</summary>
public static string Respond(this ApiResponse @this, Json data, bool lower = true) => WebUtility.Respond(@this, data, lower);
#endif
#endregion

Loading…
Cancel
Save