Browse Source

Merge pull request #20687 from abpframework/AbpCompilationRazorPageBase

Use razor page to return error pages.
pull/21005/head
Halil İbrahim Kalkan 1 year ago
committed by GitHub
parent
commit
f3fd4ce9a1
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo.Abp.AspNetCore.MultiTenancy.csproj
  2. 15
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyOptions.cs
  3. 96
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/Views/MultiTenancyMiddlewareErrorPage.Designer.cs
  4. 28
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/Views/MultiTenancyMiddlewareErrorPage.cshtml
  5. 14
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/Views/MultiTenancyMiddlewareErrorPageModel.cs
  6. 5
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj
  7. 50
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsErrorPage.Designer.cs
  8. 22
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsErrorPage.cshtml
  9. 19
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsService.cs
  10. 283
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/RazorViews/AbpCompilationRazorPageBase.cs
  11. 34
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/RazorViews/AttributeValue.cs
  12. 19
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/RazorViews/HelperResult.cs
  13. 1
      framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj
  14. 1
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs
  15. 227
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateRazorPage.cs

5
framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo.Abp.AspNetCore.MultiTenancy.csproj

@ -23,4 +23,9 @@
<ProjectReference Include="..\Volo.Abp.MultiTenancy\Volo.Abp.MultiTenancy.csproj" />
</ItemGroup>
<ItemGroup>
<Content Remove="Volo\Abp\AspNetCore\MultiTenancy\Views\MultiTenancyMiddlewareErrorPage.cshtml" />
<None Include="Volo\Abp\AspNetCore\MultiTenancy\Views\MultiTenancyMiddlewareErrorPage.cshtml" />
</ItemGroup>
</Project>

15
framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyOptions.cs

@ -16,6 +16,8 @@ using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using Volo.Abp.AspNetCore.MultiTenancy.Views;
using Volo.Abp.AspNetCore.RazorViews;
using Volo.Abp.Http;
using Volo.Abp.Json;
using Volo.Abp.MultiTenancy;
@ -68,7 +70,7 @@ public class AbpAspNetCoreMultiTenancyOptions
}
}
context.Response.Headers.Add("Abp-Tenant-Resolve-Error", HtmlEncoder.Default.Encode(exception.Message));
context.Response.Headers.Append("Abp-Tenant-Resolve-Error", HtmlEncoder.Default.Encode(exception.Message));
if (isCookieAuthentication && context.Request.Method.Equals("Get", StringComparison.OrdinalIgnoreCase) && !context.Request.IsAjax())
{
context.Response.Redirect(context.Request.GetEncodedUrl());
@ -133,18 +135,11 @@ public class AbpAspNetCoreMultiTenancyOptions
}
else
{
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
context.Response.ContentType = "text/html";
var message = exception.Message;
var details = exception is BusinessException businessException ? businessException.Details : string.Empty;
await context.Response.WriteAsync($"<html lang=\"{HtmlEncoder.Default.Encode(CultureInfo.CurrentCulture.Name)}\"><body>\r\n");
await context.Response.WriteAsync($"<h3>{HtmlEncoder.Default.Encode(message)}</h3>{HtmlEncoder.Default.Encode(details!)}<br>\r\n");
await context.Response.WriteAsync("</body></html>\r\n");
// Note the 500 spaces are to work around an IE 'feature'
await context.Response.WriteAsync(new string(' ', 500));
var errorPage = new MultiTenancyMiddlewareErrorPage(new MultiTenancyMiddlewareErrorPageModel(message, details!));
await errorPage.ExecuteAsync(context);
}
return true;

96
framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/Views/MultiTenancyMiddlewareErrorPage.Designer.cs

@ -0,0 +1,96 @@
// <auto-generated/>
#pragma warning disable 1591
namespace Volo.Abp.AspNetCore.RazorViews
{
#line hidden
using System;
using System.Threading.Tasks;
#nullable restore
#line 1 "MultiTenancyMiddlewareErrorPage.cshtml"
using System.Globalization;
#line default
#line hidden
#nullable disable
#nullable restore
#line 2 "MultiTenancyMiddlewareErrorPage.cshtml"
using Volo.Abp.AspNetCore.MultiTenancy.Views;
#line default
#line hidden
#nullable disable
#nullable restore
#line 3 "MultiTenancyMiddlewareErrorPage.cshtml"
using Volo.Abp.AspNetCore.RazorViews;
#line default
#line hidden
#nullable disable
internal class MultiTenancyMiddlewareErrorPage : AbpCompilationRazorPageBase
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
#nullable restore
#line 5 "MultiTenancyMiddlewareErrorPage.cshtml"
Response.ContentType = "text/html; charset=utf-8";
Response.StatusCode = 404;
#line default
#line hidden
#nullable disable
WriteLiteral("\n");
WriteLiteral("\n<html");
BeginWriteAttribute("lang", " lang=\"", 453, "\"", 512, 1);
#nullable restore
#line 19 "MultiTenancyMiddlewareErrorPage.cshtml"
WriteAttributeValue("", 460, HtmlEncoder.Encode(CultureInfo.CurrentCulture.Name), 460, 52, false);
#line default
#line hidden
#nullable disable
EndWriteAttribute();
WriteLiteral(">\n <head>\n <meta charset=\"utf-8\" />\n <title>");
#nullable restore
#line 22 "MultiTenancyMiddlewareErrorPage.cshtml"
Write(HtmlEncoder.Encode(Model.Message));
#line default
#line hidden
#nullable disable
WriteLiteral("</title>\n </head>\n <body>\n <h3>");
#nullable restore
#line 25 "MultiTenancyMiddlewareErrorPage.cshtml"
Write(HtmlEncoder.Encode(Model.Message));
#line default
#line hidden
#nullable disable
WriteLiteral("</h3>\n <p>");
#nullable restore
#line 26 "MultiTenancyMiddlewareErrorPage.cshtml"
Write(HtmlEncoder.Encode(Model.Details));
#line default
#line hidden
#nullable disable
WriteLiteral("<p/>\n </body>\n</html>\n");
}
#pragma warning restore 1998
#nullable restore
#line 10 "MultiTenancyMiddlewareErrorPage.cshtml"
public MultiTenancyMiddlewareErrorPage(MultiTenancyMiddlewareErrorPageModel model)
{
Model = model;
}
public MultiTenancyMiddlewareErrorPageModel Model { get; set; }
#line default
#line hidden
#nullable disable
}
}
#pragma warning restore 1591

28
framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/Views/MultiTenancyMiddlewareErrorPage.cshtml

@ -0,0 +1,28 @@
@using System.Globalization
@using Volo.Abp.AspNetCore.MultiTenancy.Views
@using Volo.Abp.AspNetCore.RazorViews
@inherits AbpCompilationRazorPageBase
@{
Response.ContentType = "text/html; charset=utf-8";
Response.StatusCode = 404;
}
@functions{
public MultiTenancyMiddlewareErrorPage(MultiTenancyMiddlewareErrorPageModel model)
{
Model = model;
}
public MultiTenancyMiddlewareErrorPageModel Model { get; set; }
}
<html lang="@HtmlEncoder.Encode(CultureInfo.CurrentCulture.Name)">
<head>
<meta charset="utf-8" />
<title>@HtmlEncoder.Encode(Model.Message)</title>
</head>
<body>
<h3>@HtmlEncoder.Encode(Model.Message)</h3>
<p>@HtmlEncoder.Encode(Model.Details)<p/>
</body>
</html>

14
framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/Views/MultiTenancyMiddlewareErrorPageModel.cs

@ -0,0 +1,14 @@
namespace Volo.Abp.AspNetCore.MultiTenancy.Views;
public class MultiTenancyMiddlewareErrorPageModel
{
public string Message { get; set; }
public string Details { get; set; }
public MultiTenancyMiddlewareErrorPageModel(string message, string details)
{
Message = message;
Details = details;
}
}

5
framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj

@ -35,4 +35,9 @@
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" />
</ItemGroup>
<ItemGroup>
<Content Remove="Volo\Abp\AspNetCore\Mvc\Libs\AbpMvcLibsErrorPage.cshtml" />
<None Include="Volo\Abp\AspNetCore\Mvc\Libs\AbpMvcLibsErrorPage.cshtml" />
</ItemGroup>
</Project>

50
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsErrorPage.Designer.cs

@ -0,0 +1,50 @@
// <auto-generated/>
#pragma warning disable 1591
namespace Volo.Abp.AspNetCore.RazorViews
{
#line hidden
using System;
using System.Threading.Tasks;
#nullable restore
#line 1 "AbpMvcLibsErrorPage.cshtml"
using Volo.Abp.AspNetCore.RazorViews;
#line default
#line hidden
#nullable disable
internal class AbpMvcLibsErrorPage : AbpCompilationRazorPageBase
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
#nullable restore
#line 3 "AbpMvcLibsErrorPage.cshtml"
Response.ContentType = "text/html; charset=utf-8";
Response.StatusCode = 500;
#line default
#line hidden
#nullable disable
WriteLiteral(@"
<html>
<head>
<meta charset=""utf-8"" />
<title>Error - The Libs folder is missing!</title>
</head>
<body>
<h1> &#9888;&#65039; The Libs folder under the <code style=""background-color: #e7e7e7;"">wwwroot/libs</code> directory is empty!</h1>
<p>The Libs folder contains mandatory NPM Packages for running the project.</p>
<p>Make sure you run the <code style=""background-color: #e7e7e7;"">abp install-libs</code> CLI tool command.</p>
<p>For more information, check out the <a href=""https://abp.io/docs/latest/CLI#install-libs"">ABP CLI documentation</a></p>
</body>
</html>
");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591

22
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsErrorPage.cshtml

@ -0,0 +1,22 @@
@using Volo.Abp.AspNetCore.RazorViews
@inherits AbpCompilationRazorPageBase
@{
Response.ContentType = "text/html; charset=utf-8";
Response.StatusCode = 500;
}
<html>
<head>
<meta charset="utf-8" />
<title>Error - The Libs folder is missing!</title>
</head>
<body>
<h1> &#9888;&#65039; The Libs folder under the <code style="background-color: #e7e7e7;">wwwroot/libs</code> directory is empty!</h1>
<p>The Libs folder contains mandatory NPM Packages for running the project.</p>
<p>Make sure you run the <code style="background-color: #e7e7e7;">abp install-libs</code> CLI tool command.</p>
<p>For more information, check out the <a href="https://abp.io/docs/latest/CLI#install-libs">ABP CLI documentation</a></p>
</body>
</html>

19
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsService.cs

@ -10,6 +10,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.FileProviders;
using Volo.Abp.AspNetCore.RazorViews;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.Libs;
@ -35,22 +36,8 @@ public class AbpMvcLibsService : IAbpMvcLibsService, ITransientDependency
{
if (!await CheckLibsAsyncOnceAsync(httpContext))
{
httpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
httpContext.Response.ContentType = "text/html";
await httpContext.Response.WriteAsync(
"<html>" +
" <head>" +
" <title>Error - The Libs folder is missing!</title>" +
" </head>" +
" <body>" +
" <h1> &#9888;&#65039; The Libs folder under the <code style='background-color: #e7e7e7;'>wwwroot/libs</code> directory is empty!</h1>" +
" <p>The Libs folder contains mandatory NPM Packages for running the project.</p>" +
" <p>Make sure you run the <code style='background-color: #e7e7e7;'>abp install-libs</code> CLI tool command.</p>" +
" <p>For more information, check out the <a href='https://abp.io/docs/latest/CLI#install-libs'>ABP CLI documentation</a></p>" +
" </body>" +
"</html>",
Encoding.UTF8
);
var errorPage = new AbpMvcLibsErrorPage();
await errorPage.ExecuteAsync(httpContext);
return;
}

283
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/RazorViews/AbpCompilationRazorPageBase.cs

@ -0,0 +1,283 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace Volo.Abp.AspNetCore.RazorViews;
public abstract class AbpCompilationRazorPageBase
{
private readonly static Encoding UTF8NoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
private readonly static char[] NewLineChars = new[] { '\r', '\n' };
private readonly Stack<TextWriter> _textWriterStack = new Stack<TextWriter>();
/// <summary>
/// The request context
/// </summary>
protected HttpContext Context { get; private set; } = default!;
/// <summary>
/// The request
/// </summary>
protected HttpRequest Request { get; private set; } = default!;
/// <summary>
/// The response
/// </summary>
protected HttpResponse Response { get; private set; } = default!;
/// <summary>
/// The output stream
/// </summary>
protected TextWriter Output { get; private set; } = default!;
/// <summary>
/// Html encoder used to encode content.
/// </summary>
protected HtmlEncoder HtmlEncoder { get; set; } = HtmlEncoder.Default;
/// <summary>
/// Url encoder used to encode content.
/// </summary>
protected UrlEncoder UrlEncoder { get; set; } = UrlEncoder.Default;
/// <summary>
/// JavaScript encoder used to encode content.
/// </summary>
protected JavaScriptEncoder JavaScriptEncoder { get; set; } = JavaScriptEncoder.Default;
/// <summary>
/// Execute an individual request
/// </summary>
/// <param name="stream">The stream to write to</param>
public async Task ExecuteAsync(Stream stream)
{
// We technically don't need this intermediate buffer if this method accepts a memory stream.
var buffer = new MemoryStream();
Output = new StreamWriter(buffer, UTF8NoBOM, 4096, leaveOpen: true);
await ExecuteAsync();
await Output.FlushAsync();
await Output.DisposeAsync();
buffer.Seek(0, SeekOrigin.Begin);
await buffer.CopyToAsync(stream);
}
/// <summary>
/// Execute an individual request
/// </summary>
/// <param name="context"></param>
public async Task ExecuteAsync(HttpContext context)
{
Context = context;
Request = Context.Request;
Response = Context.Response;
var buffer = new MemoryStream();
Output = new StreamWriter(buffer, UTF8NoBOM, 4096, leaveOpen: true);
await ExecuteAsync();
await Output.FlushAsync();
await Output.DisposeAsync();
buffer.Seek(0, SeekOrigin.Begin);
await buffer.CopyToAsync(Response.Body);
}
/// <summary>
/// Execute an individual request
/// </summary>
public abstract Task ExecuteAsync();
protected virtual void PushWriter(TextWriter writer)
{
ArgumentNullException.ThrowIfNull(writer);
_textWriterStack.Push(Output);
Output = writer;
}
protected virtual TextWriter PopWriter()
{
Output = _textWriterStack.Pop();
return Output;
}
/// <summary>
/// Write the given value without HTML encoding directly to <see cref="Output"/>.
/// </summary>
/// <param name="value">The <see cref="object"/> to write.</param>
protected void WriteLiteral(object value)
{
WriteLiteral(Convert.ToString(value, CultureInfo.InvariantCulture));
}
/// <summary>
/// Write the given value without HTML encoding directly to <see cref="Output"/>.
/// </summary>
/// <param name="value">The <see cref="string"/> to write.</param>
protected void WriteLiteral(string? value)
{
if (!string.IsNullOrEmpty(value))
{
Output.Write(value);
}
}
private List<string>? AttributeValues { get; set; }
protected void WriteAttributeValue(string thingy, int startPostion, object value, int endValue, int dealyo, bool yesno)
{
if (AttributeValues == null)
{
AttributeValues = new List<string>();
}
AttributeValues.Add(value.ToString()!);
}
private string? AttributeEnding { get; set; }
protected void BeginWriteAttribute(string name, string beginning, int startPosition, string ending, int endPosition, int thingy)
{
Debug.Assert(string.IsNullOrEmpty(AttributeEnding));
Output.Write(beginning);
AttributeEnding = ending;
}
protected void EndWriteAttribute()
{
Debug.Assert(AttributeValues != null);
Debug.Assert(!string.IsNullOrEmpty(AttributeEnding));
var attributes = string.Join(" ", AttributeValues);
Output.Write(attributes);
AttributeValues = null;
Output.Write(AttributeEnding);
AttributeEnding = null;
}
/// <summary>
/// Writes the given attribute to the given writer
/// </summary>
/// <param name="name">The name of the attribute to write</param>
/// <param name="leader">The value of the prefix</param>
/// <param name="trailer">The value of the suffix</param>
/// <param name="values">The <see cref="AttributeValue"/>s to write.</param>
protected void WriteAttribute(
string name,
string leader,
string trailer,
params AttributeValue[] values)
{
ArgumentNullException.ThrowIfNull(name);
ArgumentNullException.ThrowIfNull(leader);
ArgumentNullException.ThrowIfNull(trailer);
WriteLiteral(leader);
foreach (var value in values)
{
WriteLiteral(value.Prefix);
// The special cases here are that the value we're writing might already be a string, or that the
// value might be a bool. If the value is the bool 'true' we want to write the attribute name
// instead of the string 'true'. If the value is the bool 'false' we don't want to write anything.
// Otherwise the value is another object (perhaps an HtmlString) and we'll ask it to format itself.
string? stringValue;
if (value.Value is bool)
{
if ((bool)value.Value)
{
stringValue = name;
}
else
{
continue;
}
}
else
{
stringValue = value.Value as string;
}
// Call the WriteTo(string) overload when possible
if (value.Literal && stringValue != null)
{
WriteLiteral(stringValue);
}
else if (value.Literal)
{
WriteLiteral(value.Value);
}
else if (stringValue != null)
{
Write(stringValue);
}
else
{
Write(value.Value);
}
}
WriteLiteral(trailer);
}
/// <summary>
/// <see cref="HelperResult.WriteTo(TextWriter)"/> is invoked
/// </summary>
/// <param name="result">The <see cref="HelperResult"/> to invoke</param>
protected void Write(HelperResult result)
{
result.WriteTo(Output);
}
/// <summary>
/// Writes the specified <paramref name="value"/> to <see cref="Output"/>.
/// </summary>
/// <param name="value">The <see cref="object"/> to write.</param>
/// <remarks>
/// <see cref="HelperResult.WriteTo(TextWriter)"/> is invoked for <see cref="HelperResult"/> types.
/// For all other types, the encoded result of <see cref="object.ToString"/> is written to
/// <see cref="Output"/>.
/// </remarks>
protected void Write(object value)
{
if (value is HelperResult helperResult)
{
helperResult.WriteTo(Output);
}
else
{
Write(Convert.ToString(value, CultureInfo.InvariantCulture));
}
}
/// <summary>
/// Writes the specified <paramref name="value"/> with HTML encoding to <see cref="Output"/>.
/// </summary>
/// <param name="value">The <see cref="string"/> to write.</param>
protected void Write(string? value)
{
if (!string.IsNullOrEmpty(value))
{
WriteLiteral(HtmlEncoder.Encode(value));
}
}
protected string HtmlEncodeAndReplaceLineBreaks(string input)
{
if (string.IsNullOrEmpty(input))
{
return string.Empty;
}
// Split on line breaks before passing it through the encoder.
return string.Join("<br />" + Environment.NewLine,
input.Split("\r\n", StringSplitOptions.None)
.SelectMany(s => s.Split(NewLineChars, StringSplitOptions.None))
.Select(HtmlEncoder.Encode));
}
}

34
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/RazorViews/AttributeValue.cs

@ -0,0 +1,34 @@
using System;
namespace Volo.Abp.AspNetCore.RazorViews;
public class AttributeValue
{
public AttributeValue(string prefix, object value, bool literal)
{
Prefix = prefix;
Value = value;
Literal = literal;
}
public string Prefix { get; }
public object Value { get; }
public bool Literal { get; }
public static AttributeValue FromTuple(Tuple<string, object, bool> value)
{
return new AttributeValue(value.Item1, value.Item2, value.Item3);
}
public static AttributeValue FromTuple(Tuple<string, string, bool> value)
{
return new AttributeValue(value.Item1, value.Item2, value.Item3);
}
public static implicit operator AttributeValue(Tuple<string, object, bool> value)
{
return FromTuple(value);
}
}

19
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/RazorViews/HelperResult.cs

@ -0,0 +1,19 @@
using System;
using System.IO;
namespace Volo.Abp.AspNetCore.RazorViews;
public class HelperResult
{
public HelperResult(Action<TextWriter> action)
{
WriteAction = action;
}
public Action<TextWriter> WriteAction { get; }
public void WriteTo(TextWriter writer)
{
WriteAction(writer);
}
}

1
framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj

@ -24,6 +24,7 @@
<PackageReference Include="LibGit2Sharp" />
<PackageReference Include="StackExchange.Redis" />
<PackageReference Include="DeepL.net" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" />
</ItemGroup>
<ItemGroup>

1
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs

@ -70,6 +70,7 @@ public class AbpCliCoreModule : AbpModule
options.Commands[CliCommand.Name] = typeof(CliCommand);
options.Commands[ClearDownloadCacheCommand.Name] = typeof(ClearDownloadCacheCommand);
options.Commands[RecreateInitialMigrationCommand.Name] = typeof(RecreateInitialMigrationCommand);
options.Commands[GenerateRazorPage.Name] = typeof(GenerateRazorPage);
options.DisabledModulesToAddToSolution.Add("Volo.Abp.LeptonXTheme.Pro");
options.DisabledModulesToAddToSolution.Add("Volo.Abp.LeptonXTheme.Lite");

227
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateRazorPage.cs

@ -0,0 +1,227 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Cli.Args;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Cli.Commands;
public class GenerateRazorPage : IConsoleCommand, ITransientDependency
{
public const string Name = "generate-razor-page";
public ILogger<GenerateRazorPage> Logger { get; set; }
public GenerateRazorPage()
{
Logger = NullLogger<GenerateRazorPage>.Instance;
}
public Task ExecuteAsync(CommandLineArgs commandLineArgs)
{
var targetProjectDirectory = Directory.GetCurrentDirectory();
var projectEngine = CreateProjectEngine(targetProjectDirectory);
var results = MainCore(projectEngine, targetProjectDirectory);
foreach (var result in results)
{
File.WriteAllText(result.FilePath, result.GeneratedCode);
}
Logger.LogInformation($"{results.Count} files successfully generated.");
return Task.CompletedTask;
}
public string GetUsageInfo()
{
var sb = new StringBuilder();
sb.AppendLine("");
sb.AppendLine("Usage:");
sb.AppendLine("abp generate-razor-page");
sb.AppendLine("");
sb.AppendLine("See the documentation for more info: https://abp.io/docs/latest/cli");
return sb.ToString();
}
public string GetShortDescription()
{
return "Generates code files for Razor page.";
}
private RazorProjectEngine CreateProjectEngine(string targetProjectDirectory, Action<RazorProjectEngineBuilder>? configure = null)
{
var fileSystem = RazorProjectFileSystem.Create(targetProjectDirectory);
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem, builder =>
{
builder
.SetNamespace("Volo.Abp.AspNetCore.RazorViews")
.ConfigureClass((document, @class) =>
{
@class.ClassName = Path.GetFileNameWithoutExtension(document.Source.FilePath);
@class.Modifiers.Clear();
@class.Modifiers.Add("internal");
});
SectionDirective.Register(builder);
builder.Features.Add(new SuppressChecksumOptionsFeature());
builder.Features.Add(new SuppressMetadataAttributesFeature());
if (configure != null)
{
configure(builder);
}
builder.AddDefaultImports(@"
@using System
@using System.Threading.Tasks");
});
return projectEngine;
}
private List<RazorPageGeneratorResult> MainCore(RazorProjectEngine projectEngine, string targetProjectDirectory)
{
var results = new List<RazorPageGeneratorResult>();
Logger.LogInformation("Generating code files for pages in {0}", targetProjectDirectory);
var cshtmlFiles = projectEngine.FileSystem.EnumerateItems(targetProjectDirectory)
.Where(x => File.ReadAllText(x.PhysicalPath).Contains("@inherits AbpCompilationRazorPageBase"))
.ToList();
if (!cshtmlFiles.Any())
{
Logger.LogInformation("No .cshtml or .razor files were found.");
return results;
}
foreach (var item in cshtmlFiles)
{
Logger.LogInformation(" Generating code file for page {0} ...", item.FileName);
results.Add(GenerateCodeFile(projectEngine, item));
Logger.LogInformation(" Done!");
}
return results;
}
private RazorPageGeneratorResult GenerateCodeFile(RazorProjectEngine projectEngine, RazorProjectItem projectItem)
{
var projectItemWrapper = new FileSystemRazorProjectItemWrapper(Logger, projectItem);
var codeDocument = projectEngine.Process(projectItemWrapper);
var cSharpDocument = codeDocument.GetCSharpDocument();
if (cSharpDocument.Diagnostics.Any())
{
var diagnostics = string.Join(Environment.NewLine, cSharpDocument.Diagnostics);
Logger.LogInformation($"One or more parse errors encountered. This will not prevent the generator from continuing: {Environment.NewLine}{diagnostics}.");
}
var generatedCodeFilePath = Path.ChangeExtension(projectItem.PhysicalPath, ".Designer.cs");
return new RazorPageGeneratorResult
{
FilePath = generatedCodeFilePath,
GeneratedCode = cSharpDocument.GeneratedCode,
};
}
private class SuppressChecksumOptionsFeature : RazorEngineFeatureBase, IConfigureRazorCodeGenerationOptionsFeature
{
public int Order { get; set; }
public void Configure(RazorCodeGenerationOptionsBuilder options)
{
Check.NotNull(options, nameof(options));
options.SuppressChecksum = true;
}
}
private class SuppressMetadataAttributesFeature : RazorEngineFeatureBase, IConfigureRazorCodeGenerationOptionsFeature
{
public int Order { get; set; }
public void Configure(RazorCodeGenerationOptionsBuilder options)
{
Check.NotNull(options, nameof(options));
options.SuppressMetadataAttributes = true;
}
}
private class FileSystemRazorProjectItemWrapper : RazorProjectItem
{
private readonly ILogger<GenerateRazorPage> _logger;
private readonly RazorProjectItem _source;
public FileSystemRazorProjectItemWrapper(ILogger<GenerateRazorPage> logger, RazorProjectItem item)
{
_logger = logger;
_source = item;
// Mask the full name since we don't want a developer's local file paths to be committed.
PhysicalPath = $"{_source.FileName}";
}
public override string BasePath => _source.BasePath;
public override string FilePath => _source.FilePath;
public override string PhysicalPath { get; }
public override bool Exists => _source.Exists;
public override Stream Read()
{
var processedContent = ProcessFileIncludes();
return new MemoryStream(Encoding.UTF8.GetBytes(processedContent));
}
private string ProcessFileIncludes()
{
var basePath = Path.GetDirectoryName(_source.PhysicalPath);
var cshtmlContent = File.ReadAllText(_source.PhysicalPath);
var startMatch = "<%$ include: ";
var endMatch = " %>";
var startIndex = 0;
while (startIndex < cshtmlContent.Length)
{
startIndex = cshtmlContent.IndexOf(startMatch, startIndex, StringComparison.Ordinal);
if (startIndex == -1)
{
break;
}
var endIndex = cshtmlContent.IndexOf(endMatch, startIndex, StringComparison.Ordinal);
if (endIndex == -1)
{
throw new InvalidOperationException($"Invalid include file format in {_source.PhysicalPath}. Usage example: <%$ include: ErrorPage.js %>");
}
var includeFileName = cshtmlContent.Substring(startIndex + startMatch.Length, endIndex - (startIndex + startMatch.Length));
_logger.LogInformation(" Inlining file {0}", includeFileName);
var includeFileContent = File.ReadAllText(Path.Combine(basePath, includeFileName));
cshtmlContent = string.Concat(cshtmlContent.Substring(0, startIndex), includeFileContent, cshtmlContent.Substring(endIndex + endMatch.Length));
startIndex += includeFileContent.Length;
}
return cshtmlContent;
}
}
private class RazorPageGeneratorResult
{
public string FilePath { get; set; }
public string GeneratedCode { get; set; }
}
}
Loading…
Cancel
Save