【问题标题】:Returning Status Codes from a class outside my Controller从我的控制器外部的类返回状态代码
【发布时间】:2019-04-18 14:14:48
【问题描述】:

我正在尝试将我在所有控制器上执行的逻辑移动到一个类中,以遵循“不要重复自己”的原则。我正在苦苦挣扎的是如何优雅地返回错误代码。

以下是我目前在每个控制器中所做的一些示例:

public class SomethingRequest
{
    public SomethingModel Something { get; set; }


    public string Token { get; set; }
}

public ActionResult GetSomething(SomethingRequest request)
{

    var something = request.Something;

    var token = request.Token;

    if (something == null)
    {        
        return BadRequest("Something object is null. You may have sent data incorrectly");
    }

    if (token == null || token != "1234")
    {
        return Unauthorized("Token object is null");
    }
}

现在我想做的是将最后两部分移到他们自己的类中:

public class RequestValidation
{

    public void TokenCheck(string token)
    {
        if (token == null || token != "1234")
        {
            // doesn't work
            return Unauthorized("Token object is null");
        }
    }

    public void DataCheck(object someObject)
    {
        if (someObject == null)
        {
            // doesn't work
            return BadRequest("Object is null. You may have sent data incorrectly");
        }
    }       
}

然后我想像这样从 SomethingController 中调用它们

RequestValidation.TokenCheck(token);

RequestValidation.DataCheck(something);

然后让他们返回错误的请求或异常。

我应该如何做到这一点?

【问题讨论】:

  • 卡米洛为您提供了答案。就个人而言,这不是我通常会实施的事情。您正在创建复杂性并且仍然编写相同数量的代码。唯一可以提高可读性的方法是不为这些 if 语句使用括号,但是如果您想在 if 中添加其他任何内容,那就有点痛苦了
  • 您可以使用authorization filter 执行此操作。这使它与您的控制器代码分开。授权可以改变。您可能希望使用具有不同类型授权的同一控制器。即使是在控制器中进行授权的最佳解决方案也会导致重复的代码和对每个控制器的更改。

标签: c# .net asp.net-core-mvc http-status-codes


【解决方案1】:

一种常见的方法是创建一个帮助类,将验证和/或操作的结果返回给控制器:

public class ValidationResult
{
    public bool Succeeded { get; set; }
    public string Message { get; set; }
    public int StatusCode { get; set; }
}

由于问题是用 ASP.NET Core 标记的,因此正确的做法是首先创建接口:

public interface IRequestValidationService
{
    ValidationResult ValidateToken(string token);
    ValidationResult ValidateData(object data);
}

然后,创建实现:

public class RequestValidationService : IRequestValidationService
{
    public ValidationResult ValidateToken(string token)
    {
        if (string.IsNullOrEmpty(token) || token != "1234")
        {
            return new ValidationResult
            {
                Succeeded = false,
                Message = "invalid token",
                StatusCode = 403
            };
        }

        return new ValidationResult { Succeeded = true };
    }

    ...
}

将其添加到 DI 容器(在 Startup 类中):

services.AddScoped<IRequestValidationService, RequestValidationService>();

将其注入到 SomethingController 中:

public SomethingController(IRequestValidationService service)
{
    _requestValidationService = service;
}

最后使用它:

public IActionResult GetSomething(SomethingRequest request)
{
    var validationResult = _requestValidationService.ValidateToken(request?.Token);

    if (!validationResult.Succeeded)
    {
        return new StatusCode(validationResult.StatusCode, validationResult.Message);
    }
}

请注意,对于像验证某事不为空这样微不足道的事情,您应该使用模型验证:

public class SomethingRequest
{
    [Required(ErrorMessage = "Something is required, check your data")]
    public SomethingModel Something { get; set; }

    [Required(ErrorMessage = "Token is required!")]
    public string Token { get; set; }
}

【讨论】:

    【解决方案2】:

    @CamiloTerevinto 的想法让我走上了正确的道路。他的方法可行,但从我读到的in the documentation 来看,正确的方法是使用“Action Filters”。

    我使用this article 作为额外的灵感。

    这是我命名为ValidationFilterAttribute的过滤器

    using Microsoft.AspNetCore.Mvc.Filters;
    using Microsoft.AspNetCore.Mvc;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Routing;
    using System.Diagnostics;
    using Microsoft.Extensions.Logging;
    
    namespace Name_Of_Project.ActionFilters
    {   
        // This filter can be applied to classes to do the automatic token validation.
        // This filter also handles the model validation.
        // inspiration https://code-maze.com/action-filters-aspnetcore/
        public class ValidationFilterAttribute: IActionFilter
        {
            // passing variables into an action filter https://stackoverflow.com/questions/18209735/how-do-i-pass-variables-to-a-custom-actionfilter-in-asp-net-mvc-app    
    
            private readonly ILogger<ValidationFilterAttribute> _logger;
            public ValidationFilterAttribute(ILogger<ValidationFilterAttribute> logger)
            {
                _logger = logger;
            }
    
            public void OnActionExecuting(ActionExecutingContext context)
            {
                //executing before action is called
    
                // this should only return one object since that is all an API allows. Also, it should send something else it will be a bad request
                var param = context.ActionArguments.SingleOrDefault();
                if (param.Value == null)
                {
                    _logger.LogError("Object sent was null. Caught in ValidationFilterAttribute class.");
                    context.Result = new BadRequestObjectResult("Object sent is null");
                    return;
                }
    
                // the param should be named request (this is the input of the action in the controller)
                if (param.Key == "request")
                {
                    Newtonsoft.Json.Linq.JObject jsonObject = Newtonsoft.Json.Linq.JObject.FromObject(param.Value);
    
                    // case sensitive btw
                    string token = jsonObject["Token"].ToString();
    
                    // check that the token is valid
                    if (token == null || token != "1234")
                    {
                        _logger.LogError("Token object is null or incorrect.");
                        context.Result = new UnauthorizedObjectResult("");
                        return;
                    }
                }
    
                if (!context.ModelState.IsValid)
                {
                    context.Result = new BadRequestObjectResult(context.ModelState);
                }
            }
    
    
            public void OnActionExecuted(ActionExecutedContext context)
            {
                // executed after action is called
            }
        }
    }
    

    然后我的Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    
        // Adding an action Filter
        services.AddScoped<ValidationFilterAttribute>();
    
    }
    
    

    然后我可以将它添加到我的控制器中。

    
    using Name_Of_Project.ActionFilters;
    
    namespace Name_Of_Project.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class SomethingController : ControllerBase
        {
    
            // POST api/something
            [HttpGet]
            [ServiceFilter(typeof(ValidationFilterAttribute))]
            public ActionResult GetSomething(SomethingRequest request)
            {
                var something= request.Something;
    
                var token = request.Token;
        }
    }
    

    因为我想多次重复使用这个动作过滤器,所以我需要想办法传入一个参数以进行空检查(可能有许多不同的对象以“请求”的名义进入需要检查的对象) . This is the answer 我将寻找解决方案的那部分。

    【讨论】:

      猜你喜欢
      • 2010-11-28
      • 2022-02-09
      • 2011-07-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-04
      • 2019-01-27
      相关资源
      最近更新 更多