从控制器外部的类返回状态代码

时间:2019-04-18 14:14:49

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

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

以下是我目前在每个控制器中执行的操作的一些示例:

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);

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

我应该如何做到这一点?

2 个答案:

答案 0 :(得分:1)

执行此操作的常见方法是拥有一个帮助程序类,该类将验证和/或操作的结果返回给Controller:

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);
    }
}

请注意,对于与验证某项不为null一样琐碎的事情,您应该使用模型验证:

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; }
}

答案 1 :(得分:0)

@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;
    }
}

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