发布的无效 DTO 抛出异常
Invalid DTO posted throws exception
发布到 MVC 端点的无效 DTO 引发异常,期望它在 ModelState 中记录它。
根据 Abp 应该如何优雅地处理这种异常?
我看到 ABP documentation on validating DTOs 它在何处抛出无效模型的异常,但如何将错误消息传递给视图。
使用
- 用于 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)
Startup.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
}
}
}
Abp 默认只包装在 ObjectResult return 类型的操作中抛出的异常。例如public JsonResult 创建();
如果你的action是return查看结果,建议自己在action中捕获异常,手动添加到ModelState。
见https://aspnetboilerplate.com/Pages/Documents/AspNet-Core#exception-filter
参考以上link
Exception handling and logging behaviour can be changed using the
WrapResult and DontWrapResult attributes for methods and classes.
发布到 MVC 端点的无效 DTO 引发异常,期望它在 ModelState 中记录它。
根据 Abp 应该如何优雅地处理这种异常?
我看到 ABP documentation on validating DTOs 它在何处抛出无效模型的异常,但如何将错误消息传递给视图。
使用
- 用于 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)
Startup.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
}
}
}
Abp 默认只包装在 ObjectResult return 类型的操作中抛出的异常。例如public JsonResult 创建();
如果你的action是return查看结果,建议自己在action中捕获异常,手动添加到ModelState。
见https://aspnetboilerplate.com/Pages/Documents/AspNet-Core#exception-filter
参考以上link
Exception handling and logging behaviour can be changed using the WrapResult and DontWrapResult attributes for methods and classes.