发布到 MVC 端点的无效 DTO 会引发异常,期望它在 ModelState 中记录它。
根据 Abp 应该如何优雅地处理这种异常?
我看到了关于验证 DTO 的 ABP 文档,它在其中抛出无效模型的异常,但是如何将错误消息传递给视图。
使用
- MVC + .NET Core 的 aspnetboilerplate 示例
- Abp 包版本 4.3
- 网核 2.2
我做了什么:
添加了一个 DTO
public class ExamDtoBaseTmp
{
[Required(ErrorMessage ="Can't add without a name")]
[StringLength(EntityCommons.MaxExamNameLength,ErrorMessage ="Keep is short")]
public string Name { get; set; }
[Required(ErrorMessage ="Description is needed")]
public string Description { get; set; }
}
添加了接受上述模型的 POST 端点
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ExamDtoBaseTmp model)
{
if (!ModelState.IsValid)
{
return View();
}
try
{
// TODO: Add insert logic here
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
提交空表单并得到以下异常:
AbpValidationException: Method arguments are not valid! See ValidationErrors for details.
Abp.Runtime.Validation.Interception.MethodInvocationValidator.ThrowValidationError() in MethodInvocationValidator.cs
Abp.Runtime.Validation.Interception.MethodInvocationValidator.Validate() in MethodInvocationValidator.cs
Abp.AspNetCore.Mvc.Validation.AbpValidationActionFilter.OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) in AbpValidationActionFilter.cs
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ExceptionContext context)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
Nivra.Authentication.JwtBearer.JwtTokenMiddleware+<>c__DisplayClass0_0+<<UseJwtTokenMiddleware>b__0>d.MoveNext() in JwtTokenMiddleware.cs
await next();
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) Nivra.Web.Startup.Startup+<>c+<<Configure>b__4_1>d.MoveNext() in Startup.cs
await next();
Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context) Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
启动.cs
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Castle.Facilities.Logging;
using Abp.AspNetCore;
using Abp.Castle.Logging.Log4Net;
using Nivra.Authentication.JwtBearer;
using Nivra.Configuration;
using Nivra.Identity;
using Nivra.Web.Resources;
using Abp.AspNetCore.SignalR.Hubs;
using System.IO;
using Microsoft.AspNetCore.SpaServices.AngularCli;
using System.Linq;
using Abp.Extensions;
using Swashbuckle.AspNetCore.Swagger;
using Microsoft.AspNetCore.Mvc.Cors.Internal;
using Nivra.OnlineExam.Models;
using Nivra.OnlineExam.Service;
using Microsoft.Extensions.Options;
namespace Nivra.Web.Startup
{
public class Startup
{
private const string _defaultCorsPolicyName = "localhost";
private readonly IConfigurationRoot _appConfiguration;
public Startup(IHostingEnvironment env)
{
_appConfiguration = env.GetAppConfiguration();
}
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.Configure<ExamDatabaseSettings>(
_appConfiguration.GetSection(nameof(ExamDatabaseSettings))
);
services.AddSingleton<IExamDatabaseSettings>(sp =>
sp.GetRequiredService<IOptions<ExamDatabaseSettings>>().Value);
services.AddSingleton<ExamService>();
// MVC
services.AddMvc(
options =>
options.Filters.Add(new CorsAuthorizationFilterFactory(_defaultCorsPolicyName))
//options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute())
);
services.AddSpaStaticFiles(c =>
{
c.RootPath = "wwwroot/dist";
});
IdentityRegistrar.Register(services);
AuthConfigurer.Configure(services, _appConfiguration);
services.AddSignalR();
// Configure CORS for angular2 UI
services.AddCors(
options => options.AddPolicy(
_defaultCorsPolicyName,
builder => builder
.AllowAnyOrigin()
//.WithOrigins(
// // App:CorsOrigins in appsettings.json can contain more than one address separated by comma.
// _appConfiguration["App:CorsOrigins"]
// .Split(",", StringSplitOptions.RemoveEmptyEntries)
// .Select(o => o.RemovePostFix("/"))
// .ToArray()
//)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
)
);
// Swagger - Enable this line and the related lines in Configure method to enable swagger UI
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new Info { Title = "aspnetCoreNg API", Version = "v1" });
options.DocInclusionPredicate((docName, description) => true);
// Define the BearerAuth scheme that's in use
options.AddSecurityDefinition("bearerAuth", new ApiKeyScheme()
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = "header",
Type = "apiKey"
});
});
services.AddScoped<IWebResourceManager, WebResourceManager>();
var configurations = AppConfigurations.Get(AppDomain.CurrentDomain.BaseDirectory);
services.Configure<ExamDatabaseSettings>(configurations.GetSection(nameof(ExamDatabaseSettings)));
// Configure Abp and Dependency Injection
return services.AddAbp<NivraWebMvcModule>(
// Configure Log4Net logging
options => options.IocManager.IocContainer.AddFacility<LoggingFacility>(
f => f.UseAbpLog4Net().WithConfig("log4net.config")
)
);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAbp(options => { options.UseAbpRequestLocalization = false; }); // Initializes ABP framework.
app.UseCors(_defaultCorsPolicyName); // Enable CORS!
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.Use(async (context, next) =>
{
await next();
if (
context.Response.StatusCode == 404
&& !Path.HasExtension(context.Request.Path.Value)
&& !context.Request.Path.Value.StartsWith("/api/services", StringComparison.InvariantCultureIgnoreCase)
)
{ context.Request.Path = "/index.html"; await next(); }
});
app.UseAuthentication();
app.UseJwtTokenMiddleware();
app.UseSignalR(routes =>
{
routes.MapHub<AbpCommonHub>("/signalr");
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "defaultWithArea",
template: "{area}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
// app.Map("/spa", l =>
// l.UseSpa(spa =>
// {
// // To learn more about options for serving an Angular SPA from ASP.NET Core,
// // see https://go.microsoft.com/fwlink/?linkid=864501
// spa.Options.SourcePath = "./";
// if (env.IsDevelopment())
// {
// spa.UseAngularCliServer(npmScript: "start");
// }
// })
//);
app.UseSwagger();
//Enable middleware to serve swagger - ui assets(HTML, JS, CSS etc.)
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "AbpZeroTemplate API V1");
}); //URL: /swagger
}
}
}