控制器返回后修改 JSON 对象

Posted

技术标签:

【中文标题】控制器返回后修改 JSON 对象【英文标题】:Modify JSON Object After Controller Returns 【发布时间】:2018-03-26 18:41:30 【问题描述】:

我将 C# 与 MVC 一起使用,并尝试在控制器返回后、但在它到达客户端之前将数据添加到 JSON 消息中。

有没有我可以使用的 C# 框架/工具?

示例

控制器返回特定订单的订单数据,其中包括 Unix 时间的日期(int 表示秒)。

消息在返回给调用者之前被截获。

另一个从 Unix 时间转换而来的公历日期/时间字段被附加到 JSON 消息中。

这可能吗?

【问题讨论】:

为什么不在服务器端控制器上修改然后返回呢? 查看Action Filters 所以你想从你的控制器返回json数据给客户端?这是问题吗?你能显示你想要返回的代码或数据样本吗 如果你使用的是 Json.net,你可以在使用 DateTimeConverterBase 序列化数据时编写一个转换器来执行此操作。 @LG8 是的,您需要在所有端点上还是只在特定端点上? 【参考方案1】:

有两种解决方案,但都不推荐!!!都需要使用反射和递归来解析并在结果上注入值。

主要问题,你怎么知道一个数字是unix时间跨度而不是一个数字!在我的示例中,我比较结果日期是否在 2000 年和当前年份之间。 一个简单的匹配是使用 Unix 时间垃圾邮件的命名约定,例如:

public int UnixSaleDate get; set;

如果存在命名约定,请在下面的代码 sn-p 中更改:

if (value.Type == JTokenType.Integer && IsValidDateTime(value)) // <= Change it
if (value.Type == JTokenType.Integer && name.Contains("Unix"))  // <= With these

选项 1:WebApi 操作过滤器

可以在使用的女巫动作上指定,这很方便。

public class ActionInjectDateTimeFromUnixActionWebApiFilter : ActionFilterAttribute

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    
        var objectContent = actionExecutedContext.Response.Content as ObjectContent;
        if (objectContent != null)
        
            //var type = objectContent.ObjectType; //type of the returned object
            var value = objectContent.Value; //holding the returned value
            var injection = RecursiveInjectUnixToDateTime(JObject.FromObject(value)); // recursively inject value
            objectContent.Value = injection;
            // Overwrite return value
            actionExecutedContext.Response.Content = objectContent;
        

    

    /// <summary>
    /// Recursive DateTime Injection 
    /// </summary>
    /// <param name="obj">'obj' need to be a JObject</param>
    /// <returns></returns>
    private dynamic RecursiveInjectUnixToDateTime(JToken obj)
    
        var expObj = new ExpandoObject() as IDictionary<string, Object>;
        foreach (JProperty p in obj)
        
            string name = p.Name;
            JToken value = p.Value;
            switch (value.Type)
            
                case JTokenType.Integer:
                    
                        // Make sure your int is a date time after, after all isn't sure you are returning an int or a UNIX date time
                        if (value.Type == JTokenType.Integer && IsValidDateTime(value))
                        

                            expObj.Add(name + "_DateTime", UnixToDateTime((long) value));
                        
                        else
                        
                            expObj.Add(name, value);
                        
                    
                    break;
                case JTokenType.Object:
                    
                        // Go deep
                        // expObj[name] = RecursiveInjectUnixToDateTime(p.Value);
                        expObj.Add(name, RecursiveInjectUnixToDateTime(p.Value));
                    
                    break;
                case JTokenType.Array:
                    
                        // Go deep
                        var arr = new List<dynamic>();
                        foreach (var val in value.ToArray())
                        
                            arr.Add(RecursiveInjectUnixToDateTime(val));
                        
                        // expObj[name] = arr;
                        expObj.Add(name, arr);
                    
                    break;
                default:
                    
                        // expObj[name] = value;
                        expObj.Add(name, value);
                    
                    break;

            
        



        return expObj;
    

    /// <summary>
    /// Validate if long value is a valid date time between 2000 and current year
    /// </summary>
    /// <param name="longUnix"></param>
    /// <returns></returns>
    private bool IsValidDateTime(JToken longUnix)
    
        long unixDateTime = (long)longUnix;
        if (unixDateTime > 0)
        
            try
            
                var date = UnixToDateTime(unixDateTime);
                return date.Year >= 2000 && date.Year <= DateTime.UtcNow.Year;
            
            catch (Exception ex)
            
                return false;
            
        
        return false;
    

    private DateTime UnixToDateTime(long unixTimestamp)
    
        DateTime unixYear0 = new DateTime(1970, 1, 1);
        long unixTimeStampInTicks = unixTimestamp * TimeSpan.TicksPerSecond;
        DateTime dtUnix = new DateTime(unixYear0.Ticks + unixTimeStampInTicks);
        return dtUnix;
    

在控制器中:

        [HttpGet]
        [ActionInjectDateTimeFromUnixActionWebApiFilter]
        [Route("something1")]
        public IHttpActionResult GetSomething1()
        
            return
                Ok(
                    new
                    
                        unix = 1267480860,
                        some_int = 2,
                        obj = new
                        
                            description = "Yah Right"
                        ,
                        items = new List<object> 
                            new 
                                id = 1,
                                name = "dude",
                                unix2 = 1403473486
                            
                        
                    );
        

        [HttpGet]
        [ActionInjectDateTimeFromUnixActionWebApiFilter]
        [Route("something2")]
        public Object GetSomething2()
        
            return new
                    
                        unix = 1267480860,
                        some_int = 2,
                        obj = new
                        
                            description = "Yah Right"
                        ,
                        items = new List<object> 
                            new 
                                id = 1,
                                name = "dude",
                                unix2 = 1403473486
                            
                        
                    ;
        

对两个控制器操作的响应:


  "unix_DateTime": "2010-03-01T22:01:00",
  "some_int": 2,
  "obj": 
    "description": "Yah Right"
  ,
  "items": [
    
      "id": 1,
      "name": "dude",
      "unix2_DateTime": "2014-06-22T21:44:46"
    
  ]

选项 2:DelegatingHandler

女巫是最糟糕的解决方案!!!更多细节以及如何 DelegatingHandler for response in WebApi

shebang 的其余部分,从选项 1 代码 sn-p 中重用它用于日期时间注入(注入方法)。

祝你好运,明智地选择。

【讨论】:

感谢您的示例!我希望存在一个框架,可以让我轻松更新各种消息类型的字段。但是,看起来我必须尝试单独使用每个控制器。【参考方案2】:

从控制器返回数据后,您无法对 C# 中的数据执行任何操作。现在它正在发送给客户。您要么必须在类中添加一个公历时间字段并在返回之前对其进行初始化,要么在客户端处理它。在 javascript 中向对象添加字段非常简单。考虑以下对象:

var obj = 
   foo: "hello"

现在,让我们添加到这个对象。我们有两个选择:

obj.bar = "world" //Dot Notation

obj["bar"] = "world" //Square Bracket Notation

对于您的情况,使用点表示法最有意义,因为您的变量名称不会被动态声明。至于在 Javascript 中计算公历时间,我推荐一个名为 moment.js 的有用库,尽管它应该足够简单,使用 JS 中的常规 Date() 对象。

话虽如此,通常最好的做法是避免在 JS 中执行您可以在后端执行的操作。让浏览器执行您的服务器可以轻松执行的计算只会浪费客户端内存,占用加载时间,并且从长远来看可能会花费您宝贵的流量。

为了从 Unix 时间戳计算 UTC 时间,如果您使用的是 .Net 4.6,这非常容易。假设您的 unix 时间戳存储在名为“myTimestamp”的变量中:

DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeSeconds(myTimestamp); //Offset from Unix epoch
DateTime dateTime = dateTimeOffset.UtcDateTime; //Gregorian

【讨论】:

感谢您的推荐。看起来 Web 操作过滤器可能允许在控制器返回后进行修改。【参考方案3】:

为此,您可以执行以下操作(添加一个额外的属性来表示公历日期/时间):

public class MyModel

  public long UnixDateTime  get; set; 

  public DateTime GregorianDateTime  get  return new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc).AddSeconds(UnixDateTime);  

如@Brandon 所述,您可以将以下代码块用于 .NET 4.6 或更高版本:

public DateTime GregorianDateTime  get  return DateTimeOffset.FromUnixTimeSeconds(UnixDateTime).DateTime;  

【讨论】:

谢谢,但是我真的在找一个框架来修改从控制器方法返回后的消息。

以上是关于控制器返回后修改 JSON 对象的主要内容,如果未能解决你的问题,请参考以下文章

将 Json 对象从控制器操作返回到 jQuery

从 Spring Boot 控制器返回非 JSON 数据(对象列表)

向 IQueryable 添加扩展方法,该方法对列表进行分页并在从 API 控制器方法返回时返回 json 对象

REST - 使用 Spring MVC 返回创建的对象

Swift - 从模型中的 API 调用返回 JSON 对象作为字典以在视图控制器中使用

从 Symfony 中的控制器返回 JSON 数组