【问题标题】:Is it possible to intercept an action from becoming a ContentResult?是否可以拦截某个动作成为 ContentResult?
【发布时间】:2016-06-30 15:46:02
【问题描述】:

我正在尝试编写一个过滤器来包装数据以遵循JSON API spec,到目前为止,我已经让它适用于我直接返回 ActionResult 的所有情况,例如ComplexTypeJSON。我试图让它在像ComplexType 这样的情况下工作,我不必经常运行Json 函数。

[JSONAPIFilter]
public IEnumerable<string> ComplexType()
{
    return new List<string>() { "hello", "world" };
}

[JSONAPIFilter]
public JsonResult ComplexTypeJSON()
{
    return Json(new List<string>() { "hello", "world" });
}

但是,当我导航到 ComplexTypepublic override void OnActionExecuted(ActionExecutedContext filterContext) 运行时,filterContext.Result 是一个内容结果,这只是一个字符串,其中 filterContext.Result.Content 只是:

"System.Collections.Generic.List`1[System.String]"

有没有办法让ComplexType 变成JsonResult 而不是ContentResult

关于上下文,这里是确切的文件:

TestController.cs

namespace MyProject.Controllers
{
    using System;
    using System.Collections.Generic;
    using System.Web.Mvc;

    using MyProject.Filters;

    public class TestController : Controller
    {
        [JSONAPIFilter]
        public IEnumerable<string> ComplexType()
        {
            return new List<string>() { "hello", "world" };
        }

        [JSONAPIFilter]
        public JsonResult ComplexTypeJSON()
        {
            return Json(new List<string>() { "hello", "world" });
        }

        // GET: Test
        [JSONAPIFilter]
        public ActionResult Index()
        {
            return Json(new { foo = "bar", bizz = "buzz" });
        }

        [JSONAPIFilter]
        public string SimpleType()
        {
            return "foo";
        }

        [JSONAPIFilter]
        public ActionResult Throw()
        {
            throw new InvalidOperationException("Some issue");
        }
    }
}

JSONApiFilter.cs

namespace MyProject.Filters
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web.Mvc;

    using MyProject.Exceptions;
    using MyProject.Models.JSONAPI;

    public class JSONAPIFilterAttribute : ActionFilterAttribute, IExceptionFilter
    {
        private static readonly ISet<Type> IgnoredTypes = new HashSet<Type>()
                                                              {
                                                                  typeof(FileResult),
                                                                  typeof(JavaScriptResult),
                                                                  typeof(HttpStatusCodeResult),
                                                                  typeof(EmptyResult),
                                                                  typeof(RedirectResult),
                                                                  typeof(ViewResultBase),
                                                                  typeof(RedirectToRouteResult)
                                                              };

        private static readonly Type JsonErrorType = typeof(ErrorModel);

        private static readonly Type JsonModelType = typeof(ResultModel);

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException("filterContext");
            }

            if (IgnoredTypes.Any(x => x.IsInstanceOfType(filterContext.Result)))
            {
                base.OnActionExecuted(filterContext);
                return;
            }

            var resultModel = ComposeResultModel(filterContext.Result);
            var newJsonResult = new JsonResult()
                                    {
                                        JsonRequestBehavior = JsonRequestBehavior.AllowGet,
                                        Data = resultModel
                                    };

            filterContext.Result = newJsonResult;
            base.OnActionExecuted(filterContext);
        }

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var modelState = filterContext.Controller.ViewData.ModelState;

            if (modelState == null || modelState.IsValid)
            {
                base.OnActionExecuting(filterContext);
            }
            else
            {
                throw new ModelStateException("Errors in ModelState");
            }
        }

        public virtual void OnException(ExceptionContext filterContext)
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException("filterContext");
            }

            if (filterContext.Exception == null) return;

            // Todo: if modelstate error, do not provide that message
            // set status code to 404

            var errors = new List<string>();

            if (!(filterContext.Exception is ModelStateException))
            {
                errors.Add(filterContext.Exception.Message);
            }

            var modelState = filterContext.Controller.ViewData.ModelState;
            var modelStateErrors = modelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage).ToList();
            if (modelStateErrors.Any()) errors.AddRange(modelStateErrors);

            var errorCode = (int)System.Net.HttpStatusCode.InternalServerError;
            var errorModel = new ErrorModel()
                                 {
                                     status = errorCode.ToString(),
                                     detail = filterContext.Exception.StackTrace,
                                     errors = errors,
                                     id = Guid.NewGuid(),
                                     title = filterContext.Exception.GetType().ToString()
                                 };
            filterContext.ExceptionHandled = true;
            filterContext.HttpContext.Response.Clear();
            filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
            filterContext.HttpContext.Response.StatusCode = errorCode;

            var newResult = new JsonResult() { Data = errorModel, JsonRequestBehavior = JsonRequestBehavior.AllowGet };

            filterContext.Result = newResult;
        }

        private ResultModel ComposeResultModel(ActionResult actionResult)
        {
            var newModelData = new ResultModel() { };

            var asContentResult = actionResult as ContentResult;
            if (asContentResult != null)
            {
                newModelData.data = asContentResult.Content;
                return newModelData;
            }

            var asJsonResult = actionResult as JsonResult;
            if (asJsonResult == null) return newModelData;

            var dataType = asJsonResult.Data.GetType();
            if (dataType != JsonModelType)
            {
                newModelData.data = asJsonResult.Data;
            }
            else
            {
                newModelData = asJsonResult.Data as ResultModel;
            }

            return newModelData;
        }
    }
}

【问题讨论】:

  • 你能展示更多你的代码吗?为我们提供更多背景信息?向不熟悉 JSON API 的人解释更多术语?
  • 这些都不是我的 JSON API 所特有的,但我已经为过滤器和测试控制器提供了确切的文件。
  • 所以,您无法通过ComposeResultModel 方法访问您的列表。我说的对吗?
  • 如果您更喜欢使用 Action 方法来返回异步请求,为什么不使用 JSON ActionResult?

标签: c# asp.net asp.net-mvc-4


【解决方案1】:

有两种选择:

1.使用 ApiController 代替 Controller

apicontroller会返回json结果,默认的序列化器是Newtonsoft.json(here),所以你可以像下面这样使用:

//the response type
public class SimpleRes
{
    [JsonProperty(PropertyName = "result")]
    public string Result;      
}

//the controller
 public class TestController : ApiController
 {  
    [HttpGet] 
    [HttpPost]
    [JSONAPIFilter]
    public SimpleRes TestAction()
    {
        return new SimpleRes(){Result = "hello world!"};
    }
 }

2.如果您坚持使用 Controller,请使用您自己的 ActionResult 包装您的响应:

//json container
public class AjaxMessageContainer<T>
{        
    [JsonProperty(PropertyName = "result")]
    public T Result { set; get; }
}

//your own actionresult
public class AjaxResult<T> : ActionResult
{        
    private readonly T _result;                      

    public AjaxResult(T result)
    {          
        _result = result;           
    }

    public override void ExecuteResult(ControllerContext context)
    {
        context.HttpContext.Response.Clear();
        context.HttpContext.Response.ContentType = "application/json";
        var result = JsonConvert.SerializeObject(new AjaxMessageContainer<T>
        {               
            Result = _result,               
        });
        var bytes =
            new UTF8Encoding().GetBytes(result);
        context.HttpContext.Response.OutputStream.Write(bytes, 0, bytes.Length);           
    }
}

//your controller
[JSONAPIFilter]
public AjaxResult<List<String>> TestSimple()
{
    return AjaxResult<List<String>>(new List<string>() { "hello", "world" });
}

如果你想从 filter 获取响应字符串以获取日志或其他内容:

var result  = filterContext.Response.Content.ReadAsStringAsync();

【讨论】:

  • 是的,这种请求应该由 API 处理。但是发回 OOB JSON result 有什么问题
  • 最终自定义 actionresult 和我的 filter 属性的组合导致了所需的行为。谢谢你的解释,太好了!
【解决方案2】:

我想这就是你要找的:

public class JSONAPIFilterAttribute : ActionFilterAttribute, IActionFilter
{
    void IActionFilter.OnActionExecuted(ActionExecutedContext context)
    {
        context.Result = new JsonResult
        {
            Data = ((ViewResult)context.Result).ViewData.Model
        };
    }
}

来自@roosteronacid:return jsonresult in actionfilter

【讨论】:

    【解决方案3】:

    我刚刚遇到了同样的问题,并发现了一种略有不同的方法。 基本思想来自NOtherDev。 我会介绍一个IActionInvoker

    public class ControllerActionInvokerWithDefaultJsonResult : ControllerActionInvoker
    {
        public const string JsonContentType = "application/json";
    
        protected override ActionResult CreateActionResult(ControllerContext controllerContext, ActionDescriptor actionDescriptor, object actionReturnValue)
        {
            if (controllerContext.HttpContext.Request.Path.StartsWith("/api/"))
            {
                return (actionReturnValue as ActionResult) 
                    ?? new JsonResult
                    {
                        Data = actionReturnValue,
                        JsonRequestBehavior = JsonRequestBehavior.AllowGet
                    };
            }
            return base.CreateActionResult(controllerContext, actionDescriptor, actionReturnValue);
        }
    }
    

    在这种情况下,每个以“/api/”开头的请求都会将结果转换为 json,但前提是 actionReturnValue 不是继承自 ActionResult 的类型。

    IActionInvokerDependencyResolver解析,所以你需要在你最喜欢的ioc容器中定义注册,你设置为DependencyResolver

    myFavoriteContainer.Register<IActionInvoker, ControllerActionInvokerWithDefaultJsonResult>(Lifestyle.Transient);
    

    对于JsonResult,您可以使用内置或this

    如果您使用异步操作方法,您应该从 AsyncControllerActionInvoker 而不是 ControllerActionInvoker 继承,我假设您还需要为 IAsyncActionInvoker 添加另一个注册。我不确定调用者本身的异步部分的变化。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-15
      • 2017-08-04
      • 2011-08-26
      • 1970-01-01
      • 2020-02-05
      相关资源
      最近更新 更多