我有一个允许用户更新其个人资料的表格。提交表单后,会提出ajax请求:
$.ajax({
url: url,
data: formData,
type: 'POST',
success: function (response) {
alert(true);
},
error: function (jqXHR, textStatus, errorThrown) {
//Handle error
}
});
在ajax请求中,我需要根据生成的错误检查是否发生了错误(如果是),我想显示一条不同的异常消息。
现在主要的问题是,调用的方法返回更新后用户的ViewModel
,例如:
publi class UserController : Controller
{
private readonly IUserRepository _repo;
public UserController(IUserRepository repo)
{
_repo = repo;
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UpdateUser(UserProfileViewModel updateUser)
{
if(ModelState.IsValid)
{
updateUser = await _repo.UpdateUserAsync(updateUser);
}
return RedirectToAction("Profile");
}
控制器具有IUserRepository
的依赖项注入,它实际上处理了更新用户的逻辑,例如:
public async Task<User> UpdateUserAsync(UserProfileViewModel updatedUser)
{
if(updatedUser.FirstName == "")
throw new Exception("FirstName not filled");
}
从上面的示例中可以看到,如果FirstName
未填充,则会引发异常。
我想避免使用异常;经过研究发现,BadRequest()
是BadRequest
中缺少AspNetCore
,似乎仅在API
版本中可用。
有人可以很好地解决这个问题吗?
答案 0 :(得分:0)
正如poke告诉您的那样,用户可以使用UserProfileViewModel.FirstName
属性修饰Required
来使用model validation >
public class UserProfileViewModel
{
[Required]
public string Name { get; set; }
}
我在配置中添加了过滤器以分解代码。
一个检查模型:
/// <summary>
/// Check api model state filter
/// </summary>
public class ApiCheckModelStateFilter : IActionFilter
{
private readonly PathString _apiPathString = PathString.FromUriComponent("/api");
/// <summary>
/// Called after the action executes, before the action result.
/// </summary>
/// <param name="context">The <see cref="T:Microsoft.AspNetCore.Mvc.Filters.ActionExecutedContext" />.</param>
public void OnActionExecuted(ActionExecutedContext context)
{
}
/// <summary>
/// Called before the action executes, after model binding is complete.
/// </summary>
/// <param name="context">The <see cref="T:Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext" />.</param>
/// <exception cref="InvalidOperationException"></exception>
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.HttpContext.Request.Path.StartsWithSegments(_apiPathString))
{
return;
}
var state = context.ModelState;
if (!state.IsValid)
{
var message = string.Join("; ", state.Values
.SelectMany(x => x.Errors)
.Select(x => x.ErrorMessage));
throw new InvalidOperationException(message);
}
}
}
另外一个根据异常来管理状态代码:
/// <summary>
/// Api exception filter
/// </summary>
public class ApiExceptionFilter : IExceptionFilter, IAsyncExceptionFilter
{
private readonly PathString _apiPathString = PathString.FromUriComponent("/api");
private readonly ILogger<ApiExceptionFilter> _logger;
/// <summary>
/// Initialize a new instance of <see cref="ApiExceptionFilter"/>
/// </summary>
/// <param name="logger">A logger</param>
public ApiExceptionFilter(ILogger<ApiExceptionFilter> logger)
{
_logger = logger;
}
/// <summary>
/// Called after an action has thrown an <see cref="T:System.Exception" />.
/// </summary>
/// <param name="context">The <see cref="T:Microsoft.AspNetCore.Mvc.Filters.ExceptionContext" />.</param>
/// <returns>
/// A <see cref="T:System.Threading.Tasks.Task" /> that on completion indicates the filter has executed.
/// </returns>
public Task OnExceptionAsync(ExceptionContext context)
{
Process(context);
return Task.CompletedTask;
}
/// <summary>
/// Called after an action has thrown an <see cref="T:System.Exception" />.
/// </summary>
/// <param name="context">The <see cref="T:Microsoft.AspNetCore.Mvc.Filters.ExceptionContext" />.</param>
public void OnException(ExceptionContext context)
{
Process(context);
}
private void Process(ExceptionContext context)
{
var e = context.Exception;
_logger.LogError(e, e.Message);
if (!context.HttpContext.Request.Path.StartsWithSegments(_apiPathString))
{
return;
}
else if (e is EntityNotFoundException)
{
context.Result = WriteError(HttpStatusCode.NotFound, e);
}
else if (e is InvalidOperationException)
{
context.Result = WriteError(HttpStatusCode.BadRequest, e);
}
else if (e.GetType().Namespace == "Microsoft.EntityFrameworkCore")
{
context.Result = WriteError(HttpStatusCode.BadRequest, e);
}
else
{
context.Result = WriteError(HttpStatusCode.InternalServerError, e);
}
}
private IActionResult WriteError(HttpStatusCode statusCode, Exception e)
{
var result = new ApiErrorResult(e.Message, e)
{
StatusCode = (int)statusCode,
};
return result;
}
}
它返回ApiErrorResult
并在原因短语中显示错误消息:
/// <summary>
/// Api error result
/// </summary>
/// <seealso cref="Microsoft.AspNetCore.Mvc.ObjectResult" />
public class ApiErrorResult : ObjectResult
{
private readonly string _reasonPhrase;
/// <summary>
/// Initializes a new instance of the <see cref="ApiErrorResult"/> class.
/// </summary>
/// <param name="reasonPhrase">The reason phrase.</param>
/// <param name="value">The value.</param>
public ApiErrorResult(string reasonPhrase, object value) : base(value)
{
_reasonPhrase = reasonPhrase;
}
/// <inheritdoc />
public override async Task ExecuteResultAsync(ActionContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var reasonPhrase = _reasonPhrase;
reasonPhrase = reasonPhrase.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)[0];
context.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = reasonPhrase;
await base.ExecuteResultAsync(context);
}
}
这些过滤器是在Startup ConfigureServices方法中设置的:
/// <summary>
/// This method gets called by the runtime. Use this method to add services to the container.
/// </summary>
/// <param name="services">A service collection</param>
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(configure =>
{
var filters = configure.Filters;
filters.Add<ApiExceptionFilter>();
filters.Add<ApiCheckModelStateFilter>();
})
.AddJsonOptions(configure =>
{
configure.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
这样,我无需检查控制器方法中的模型是否有效:
[HttpPost]
[ValidateAntiForgeryToken]
public Task<IActionResult> UpdateUser(UserProfileViewModel updateUser) => _repo.UpdateUserAsync(updateUser);
答案 1 :(得分:0)
@poke和@agua是完全正确的;您应该使用模型验证。但是,如果您的验证有点复杂,并且您必须在服务中处理它,则可以使用此模式。首先,创建一个不仅代表数据的类,而且代表服务成功获取数据的指示器。
public class Result<T>
{
public bool HasError { get; set; }
public T Data { get; set; }
}
以上内容非常简单;您可能需要更多信息。
然后,在您的服务中,更改签名以返回Result<T>
并添加代码以创建适当的签名:
public async Task<Result<User>> UpdateUserAsync(UserProfileViewModel updatedUser) {
if (updatedUser.FirstName == "")
return new Result<User> {
HadError = true,
Data = (User)null
};
// do something to save
return new Result<User> {
HadError = false,
Data = updatedUser
};
}
然后更新您的操作以接收Result<T>
并返回适当的结果:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UpdateUser(UserProfileViewModel updateUser) {
var result = new Result<User> {
HadError = true,
Data = (User) null
};
if (ModelState.IsValid) {
result = await _repo.UpdateUserAsync(updateUser);
}
return result.HadError ? BadRequest() : RedirectToAction("Profile");
}
同样,对于诸如必需属性之类的东西,还有更好的方法。我发现上述模式在服务中发生错误时非常有用,并且我想将此事实传达给控制器/操作/ UI。