Skip to content

Commit

Permalink
Update action result.
Browse files Browse the repository at this point in the history
  • Loading branch information
kingcean committed Jan 8, 2025
1 parent 4e056c0 commit 3cf89b3
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 32 deletions.
21 changes: 21 additions & 0 deletions Console/CommandLine/HighlightConsoleStyle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,27 @@ namespace Trivial.CommandLine;
/// </summary>
public class HighlightConsoleStyle : IConsoleTextPrettier
{
/// <summary>
/// Initialzies a new instance of the HighlightConsoleStyle class.
/// </summary>
/// <param name="highlight">The highlight style.</param>
/// <param name="q">The query string.</param>
public HighlightConsoleStyle(ConsoleTextStyle highlight, IEnumerable<string> q)
: this(null, highlight, q)
{
}

/// <summary>
/// Initialzies a new instance of the HighlightConsoleStyle class.
/// </summary>
/// <param name="highlight">The highlight style.</param>
/// <param name="q">The query string.</param>
/// <param name="comparisonType">One of the enumeration values that specifies the rules for the search.</param>
public HighlightConsoleStyle(ConsoleTextStyle highlight, string q, StringComparison? comparisonType = null)
: this(null, highlight, q, comparisonType)
{
}

/// <summary>
/// Initialzies a new instance of the HighlightConsoleStyle class.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion Console/Console.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<AssemblyName>Trivial.Console</AssemblyName>
<RootNamespace>Trivial</RootNamespace>
<PackageId>Trivial.Console</PackageId>
<Description>The console utilities and rich user interface console.</Description>
<Description>The utilities and rich interface component of console.</Description>
<PackageProjectUrl>https://github.com/nuscien/trivial/wiki/console</PackageProjectUrl>
<ApplicationIcon>Logo.ico</ApplicationIcon>
<PackageIcon>cmd.png</PackageIcon>
Expand Down
2 changes: 1 addition & 1 deletion Site/Controllers/TestController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public async Task<TokenInfo> Login()
}

[HttpGet("Data")]
public ActionResult GetData()
public IActionResult GetData()
{
var jwt = Request.GetJsonWebToken<JsonWebTokenPayload>(DemoServer.Instance.GetSignatureProvider<JsonWebTokenPayload>);
var json = new JsonObjectNode
Expand Down
6 changes: 3 additions & 3 deletions Tools/Common.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
<Product>Trivial</Product>
<Authors>Kingcean Tuan</Authors>
<Company>Nanchang Jinchen Software Co., Ltd.</Company>
<Version>9.1.0</Version>
<AssemblyVersion>9.1.0.0</AssemblyVersion>
<FileVersion>9.1.0.0</FileVersion>
<Version>9.2.0</Version>
<AssemblyVersion>9.2.0.0</AssemblyVersion>
<FileVersion>9.2.0.0</FileVersion>
<Copyright>Copyright (c) 2018 Kingcean Tuan.</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://nuscien.github.io/winkit</PackageProjectUrl>
Expand Down
2 changes: 1 addition & 1 deletion Web/Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<ApplicationIcon>Logo.ico</ApplicationIcon>
<PackageIcon>web.png</PackageIcon>
<PackageIconUrl>https://github.com/nuscien/trivial/raw/master/Materials/logo.png</PackageIconUrl>
<PackageTags>web mvc</PackageTags>
<PackageTags>web mvc json</PackageTags>
</PropertyGroup>

<Import Project="..\Tools\Build.props" />
Expand Down
32 changes: 28 additions & 4 deletions Web/Web/ActionResult.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Net.Http.Headers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Trivial.Data;
using Trivial.Net;
using Trivial.Text;

namespace Trivial.Web;

Expand All @@ -21,9 +25,6 @@ namespace Trivial.Web;
/// <param name="prepare">The preparing callback.</param>
internal class DataHandlingActionResult<T>(T data, Func<T, HttpResponse, Task> handler, Action<HttpResponse> prepare) : IActionResult
{
private readonly Func<T, HttpResponse, Task> handler = handler;
private readonly Action<HttpResponse> prepare = prepare;

/// <inheritdoc />
public async Task ExecuteResultAsync(ActionContext context)
{
Expand All @@ -41,8 +42,8 @@ internal class PushingCollectionActionResult<T> : IActionResult
private readonly SemaphoreSlim slim1;
private readonly SemaphoreSlim slim2;
private readonly Action<HttpResponse> prepare;
private readonly Action<CollectionResultBuilder<T>> fill;
private CollectionResultBuilder<T> data;
private Action<CollectionResultBuilder<T>> fill;
private HttpResponse response;

/// <summary>
Expand Down Expand Up @@ -252,3 +253,26 @@ private void Release()
}
}
}

/// <summary>
/// The JSON action result for server-sent event.
/// </summary>
/// <param name="data">The source data to output.</param>
/// <param name="prepare">The preparing callback.</param>
/// <param name="mime">The content type.</param>
/// <param name="filling">The response filling.</param>
internal class JsonValueNodeActionResult(BaseJsonValueNode data, Action<HttpResponse> prepare, string mime, IHttpResponseFilling filling = null) : IActionResult
{
/// <inheritdoc />
public async Task ExecuteResultAsync(ActionContext context)
{
var resp = context.HttpContext.Response;
prepare?.Invoke(resp);
resp.ContentType = mime ?? ControllerExtensions.jsonMime;
filling?.ExecuteResult(resp);
var writer = new Utf8JsonWriter(resp.Body);
if (data == null) JsonValues.Null.WriteTo(writer);
else data.WriteTo(writer);
await writer.FlushAsync();
}
}
185 changes: 163 additions & 22 deletions Web/Web/ControllerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -555,39 +555,131 @@ public static ActionResult ToActionResult(this ChangeErrorKinds value, string me
/// </summary>
/// <param name="value">The value.</param>
/// <returns>The action result.</returns>
public static ContentResult ToActionResult(this JsonObjectNode value)
=> new()
{
ContentType = jsonMime,
StatusCode = 200,
Content = value?.ToString() ?? JsonValues.NullString
};
public static IActionResult ToActionResult(this JsonObjectNode value)
=> new JsonValueNodeActionResult(value, null, null);

/// <summary>
/// Convert to an action result.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="prepare">The preparing callback.</param>
/// <returns>The action result.</returns>
public static ContentResult ToActionResult(this JsonArrayNode value)
=> new()
{
ContentType = jsonMime,
StatusCode = 200,
Content = value?.ToString() ?? JsonValues.NullString
};
public static IActionResult ToActionResult(this JsonObjectNode value, Action<HttpResponse> prepare)
=> new JsonValueNodeActionResult(value, prepare, null);

/// <summary>
/// Convert to an action result.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="entityTag">The entity tag associated with the file.</param>
/// <param name="lastModified">The optional last modification date time of the resource.</param>
/// <param name="expires">The optional expiration date time of the resource.</param>
/// <param name="cacheControl">An optional policy to control caching in browsers and shared caches.</param>
/// <returns>The action result.</returns>
public static ContentResult ToActionResult(this IJsonObjectHost value)
=> new()
{
ContentType = jsonMime,
StatusCode = 200,
Content = value?.ToJson()?.ToString() ?? JsonValues.NullString
};
public static IActionResult ToActionResult(this JsonObjectNode value, EntityTagHeaderValue entityTag, DateTime? lastModified = null, DateTime? expires = null, CacheControlHeaderValue cacheControl = null)
=> new JsonValueNodeActionResult(value, null, null, new HttpResponseHeadersFilling(entityTag, lastModified, expires, cacheControl));

/// <summary>
/// Convert to an action result.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="prepare">The preparing callback.</param>
/// <param name="entityTag">The entity tag associated with the file.</param>
/// <param name="lastModified">The optional last modification date time of the resource.</param>
/// <param name="expires">The optional expiration date time of the resource.</param>
/// <param name="cacheControl">An optional policy to control caching in browsers and shared caches.</param>
/// <returns>The action result.</returns>
public static IActionResult ToActionResult(this JsonObjectNode value, Action<HttpResponse> prepare, EntityTagHeaderValue entityTag, DateTime? lastModified = null, DateTime? expires = null, CacheControlHeaderValue cacheControl = null)
=> new JsonValueNodeActionResult(value, prepare, null, new HttpResponseHeadersFilling(entityTag, lastModified, expires, cacheControl));

/// <summary>
/// Convert to an action result.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="prepare">The preparing callback.</param>
/// <param name="statusCode">The HTTP status code.</param>
/// <returns>The action result.</returns>
public static IActionResult ToActionResult(this JsonObjectNode value, int statusCode, Action<HttpResponse> prepare = null)
=> new JsonValueNodeActionResult(value, prepare, null, new HttpResponseStatusFilling(statusCode));

/// <summary>
/// Convert to an action result.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>The action result.</returns>
public static IActionResult ToActionResult(this JsonArrayNode value)
=> new JsonValueNodeActionResult(value, null, null);

/// <summary>
/// Convert to an action result.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="prepare">The preparing callback.</param>
/// <returns>The action result.</returns>
public static IActionResult ToActionResult(this JsonArrayNode value, Action<HttpResponse> prepare)
=> new JsonValueNodeActionResult(value, prepare, null);

/// <summary>
/// Convert to an action result.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="prepare">The preparing callback.</param>
/// <param name="statusCode">The HTTP status code.</param>
/// <returns>The action result.</returns>
public static IActionResult ToActionResult(this JsonArrayNode value, int statusCode, Action<HttpResponse> prepare = null)
=> new JsonValueNodeActionResult(value, prepare, null, new HttpResponseStatusFilling(statusCode));

/// <summary>
/// Convert to an action result.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>The action result.</returns>
public static IActionResult ToActionResult(this IJsonObjectHost value)
=> new JsonValueNodeActionResult(value?.ToJson(), null, null);

/// <summary>
/// Convert to an action result.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="prepare">The preparing callback.</param>
/// <returns>The action result.</returns>
public static IActionResult ToActionResult(this IJsonObjectHost value, Action<HttpResponse> prepare)
=> new JsonValueNodeActionResult(value?.ToJson(), prepare, null);

/// <summary>
/// Convert to an action result.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="entityTag">The entity tag associated with the file.</param>
/// <param name="lastModified">The optional last modification date time of the resource.</param>
/// <param name="expires">The optional expiration date time of the resource.</param>
/// <param name="cacheControl">An optional policy to control caching in browsers and shared caches.</param>
/// <returns>The action result.</returns>
public static IActionResult ToActionResult(this IJsonObjectHost value, EntityTagHeaderValue entityTag, DateTime? lastModified = null, DateTime? expires = null, CacheControlHeaderValue cacheControl = null)
=> new JsonValueNodeActionResult(value?.ToJson(), null, null, new HttpResponseHeadersFilling(entityTag, lastModified, expires, cacheControl));

/// <summary>
/// Convert to an action result.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="prepare">The preparing callback.</param>
/// <param name="entityTag">The entity tag associated with the file.</param>
/// <param name="lastModified">The optional last modification date time of the resource.</param>
/// <param name="expires">The optional expiration date time of the resource.</param>
/// <param name="cacheControl">An optional policy to control caching in browsers and shared caches.</param>
/// <returns>The action result.</returns>
public static IActionResult ToActionResult(this IJsonObjectHost value, Action<HttpResponse> prepare, EntityTagHeaderValue entityTag = null, DateTime? lastModified = null, DateTime? expires = null, CacheControlHeaderValue cacheControl = null)
=> new JsonValueNodeActionResult(value?.ToJson(), prepare, null, new HttpResponseHeadersFilling(entityTag, lastModified, expires, cacheControl));

/// <summary>
/// Convert to an action result.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="prepare">The preparing callback.</param>
/// <param name="statusCode">The HTTP status code.</param>
/// <returns>The action result.</returns>
public static IActionResult ToActionResult(this IJsonObjectHost value, int statusCode, Action<HttpResponse> prepare = null)
=> new JsonValueNodeActionResult(value?.ToJson(), prepare, null, new HttpResponseStatusFilling(statusCode));

/// <summary>
/// Convert to an action result.
Expand Down Expand Up @@ -887,6 +979,7 @@ public static async Task<TToken> SignInAsync<TToken, TAccount>(ControllerBase co
{
if (controller is null) return default;
var stream = controller.Request.Body;
tokenMaker ??= () => Activator.CreateInstance<TToken>();
if (stream is null)
{
result = tokenMaker();
Expand All @@ -895,7 +988,6 @@ public static async Task<TToken> SignInAsync<TToken, TAccount>(ControllerBase co
return default;
}

tokenMaker ??= () => Activator.CreateInstance<TToken>();
string input;
using (var reader = new StreamReader(controller.Request.Body, Encoding.UTF8))
{
Expand Down Expand Up @@ -1063,6 +1155,55 @@ public static FileStreamResult FileResult(Assembly assembly, string subPath, str
public static FileStreamResult FileResult(Assembly assembly, string subPath, EntityTagHeaderValue entityTag, string mime = null)
=> FileResult(assembly, subPath, null, entityTag, mime);

/// <summary>
/// Converts a date time to a string in HTTP-date format (defined by Date and Time Specification in Internet Message Format).
/// </summary>
/// <param name="time">The date time to convert.</param>
/// <returns>A string in HTTP-date format of a specific date and time.</returns>
public static string ToString(DateTime time)
{
// https://www.rfc-editor.org/rfc/rfc5322.html#section-3.3
var sb = new StringBuilder();
time = time.ToUniversalTime();
sb.Append(time.DayOfWeek switch
{
DayOfWeek.Sunday => "Sun, ",
DayOfWeek.Monday => "Mon, ",
DayOfWeek.Tuesday => "Tue, ",
DayOfWeek.Wednesday => "Wed, ",
DayOfWeek.Thursday => "Thu, ",
DayOfWeek.Friday => "Fri, ",
DayOfWeek.Saturday => "Sat, ",
_ => string.Empty
});
sb.Append(time.Day.ToString("00"));
sb.Append(time.Month switch
{
1 => " Jan ",
2 => " Feb ",
3 => " Mar ",
4 => " Apr ",
5 => " May ",
6 => " Jun ",
7 => " Jul ",
8 => " Aug ",
9 => " Sep ",
10 => " Oct ",
11 => " Nov ",
12 => " Dec ",
_ => string.Concat(' ', time.Month, ' ')
});
sb.Append(time.Year.ToString("0000"));
sb.Append(' ');
sb.Append(time.Hour.ToString("00"));
sb.Append(':');
sb.Append(time.Minute.ToString("00"));
sb.Append(':');
sb.Append(time.Second.ToString("00"));
sb.Append(" GMT");
return sb.ToString();
}

/// <summary>
/// Gets the status code.
/// </summary>
Expand Down
35 changes: 35 additions & 0 deletions Web/Web/HttpResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Net.Http.Headers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Trivial.Web;

internal interface IHttpResponseFilling
{
public void ExecuteResult(HttpResponse response);
}

internal class HttpResponseHeadersFilling(EntityTagHeaderValue entityTag, DateTime? lastModified = null, DateTime? expires = null, CacheControlHeaderValue cacheControl = null) : IHttpResponseFilling
{
public void ExecuteResult(HttpResponse response)
{
if (response == null) return;
if (entityTag != null) response.Headers.ETag = entityTag.ToString();
if (lastModified.HasValue) response.Headers.LastModified = ControllerExtensions.ToString(lastModified.Value);
if (expires.HasValue) response.Headers.Expires = ControllerExtensions.ToString(expires.Value);
if (cacheControl != null) response.Headers.CacheControl = cacheControl.ToString();
}
}

internal class HttpResponseStatusFilling(int statusCode) : IHttpResponseFilling
{
public void ExecuteResult(HttpResponse response)
{
if (response == null) return;
response.StatusCode = statusCode;
}
}

0 comments on commit 3cf89b3

Please sign in to comment.