我的 MVC 控制器真的应该知道 JSON 吗?

     2023-03-31     239

关键词:

【中文标题】我的 MVC 控制器真的应该知道 JSON 吗?【英文标题】:Should my MVC controller really know about JSON? 【发布时间】:2010-10-03 16:47:39 【问题描述】:

JsonResult 类是一种通过 AJAX 将 Json 作为操作返回给客户端的非常有用的方法。

public JsonResult JoinMailingList(string txtEmail)

        // ...

       return new JsonResult()
       
           Data = new  foo = "123", success = true 
       ;

但是(至少根据我的第一印象)这确实不是一个很好的关注点分离。

单元测试方法更难编写,因为它们没有很好的强类型数据来测试并且必须知道如何解释 Json。 未来不通过 HTTP(或任何涉及序列化的远程协议)的其他视图更难被“插入”,因为在这种情况下不需要序列化和反序列化响应。 如果您有两个不同的地方需要该操作的结果怎么办?一个想要 Json,另一个想要 XML,或者可能是一个完全 or partially 呈现的视图。

我想知道为什么对象和 Json 之间的转换不是通过属性以声明方式实现的。在下面的代码中,您实际上是在告诉 MVC this method is convertible to Json,然后如果它是从 AJAX 客户端调用的,则会检查内部执行的 new JsonResult() 转换属性。

单元测试可以只取action结果(ObjectActionResult),拉出强类型的Foo

[JsonConvertible]
public ActionResult JoinMailingList(string txtEmail)

        // ...

       return new ObjectActionResult()
       
           Data = new Foo(123, true)
       ;

我只是好奇人们的想法和任何其他可以遵循的模式。

这些也只是我最初的观察——这可能不是一个理想的设计可能还有更多的原因(可能还有很多原因为什么它是一个完全可以接受和实用的设计!)我今晚只是感觉理论和魔鬼的拥护者。

 * 免责声明:我什至还没有开始考虑该属性将如何实现或它可能具有的副作用或反作用等。

【问题讨论】:

就是我的想法。鉴于权力,我会为这个问题投票 5 次! 这是 MVC3 中的一个有效问题。但是,在较新的 MVC4 中,任何 ApiController 操作方法都会自动评估 Accept HTTP 标头并相应地返回 JSON 或 XML。 action 方法只需要返回一个视图模型,这一切都是自动发生的,这使得单元测试再次变得容易! 【参考方案1】:

我认为你正在为无所事事而烦恼。那么如果控制器在其公共接口中知道 JSON 怎么办?

曾经有人告诉我:“让你的代码通用,不要让你的应用程序通用。”

您正在这里编写应用程序控制器。应用程序控制器(其职责是在模型和视图之间进行缓和并调用模型中的更改)了解某个视图(JSON、HTML、PList、XML、YAML)是可以的。

在我自己的项目中,我通常有这样的:

interface IFormatter 
    ActionResult Format(object o);

class HtmlFormatter : IFormatter 
    // ...

class JsonFormatter : IFormatter 
    // ...

class PlistFormatter : IFormatter 
    // ...

class XmlFormatter : IFormatter 
    // ...

基本上是“格式化程序”,它接受对象并赋予它们不同的表示形式。如果HtmlFormatters 的对象实现了IEnumerable,那么HtmlFormatters 甚至可以输出表格。

现在返回数据(或可以使用HtmlFormatters 生成网站部分内容)的控制器采用“格式”参数:

public ActionResult JoinMailingList(string txtEmail, string format) 
    // ...
    return Formatter.For(format).Format(
        new  foo = "123", success = true 
    );

您可以为单元测试添加“对象”格式化程序:

class ObjectFormatter : IFormatter 
    ActionResult Format(object o) 
        return new ObjectActionResult() 
            Data = o
        ;
    

使用这种方法,您的任何查询/操作/过程/ajax 调用,无论您想调用什么,都可以以多种格式输出。

【讨论】:

@frank 更多的是我不希望控制器对 JSON 了解太多。即使使用我建议的解决方案,控件仍然“了解 JSON”(至少是 JsonAttribute)。不过,看看您如何解决相同的基本问题很有趣。谢谢。正如我提到的单元测试也是一个驱动问题 @frank 你不同意,尽管框架级解决方案会更优雅。他们基本上可以采用您自己的格式化程序模式,其中“格式”参数是“秘密”框架参数。 我已经厌倦了等待框架为我提供让我的开发生活更美好的东西。这些格式化程序允许我查询我的数据库并获得很好的结果集。它们的实现短小精悍,并且对控制器的干扰很小。 查看更新。不要过度架构或等待其他人过度架构。 @frank 同意。我刚来 MVC 有点晚(5 天前) - 在最佳实践上追赶一点。对于 V1 这样的功能显然为时已晚,但如果有足够多的人同意我的观点,我会建议将其作为增强功能。似乎它很容易对现有代码产生零影响。【参考方案2】:

我通常尽量不担心。 Asp.Net MVC 足以将关注点分离以将泄漏降至最低。不过你是对的;测试时有一点障碍。

这是我使用的一个测试助手,效果很好:

protected static Dictionary<string, string> GetJsonProps(JsonResult result)

    var properties = new Dictionary<string, string>();
    if (result != null && result.Data != null)
    
        object o = result.Data;
        foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(o))
            properties.Add(prop.Name, prop.GetValue(o) as string);
    
    return properties;

您可以使用 Request.IsAjaxRequest() 扩展方法返回不同的 ActionResult 类型:

if (this.Request != null && this.Request.IsAjaxRequest())
    return Json(new  Message = "Success" );
else
    return RedirectToAction("Some Action");

注意:您需要 Request != null 才不会破坏您的测试。

【讨论】:

谢谢 - 当然 Request.IsMvcAjaxRequest 现在更名为 IsAjaxRequest()【参考方案3】:

我并不像以前那样担心返回 JSON。 AJAX 的性质似乎是,您想要在 Javascript 中处理的消息仅适用于该 AJAX 情况。 AJAX 对性能的需求只需要以某种方式影响代码。您可能不想将相同的数据返回给不同的客户端。

我注意到一些关于测试 JSonResult 的事情(我还没有为我的应用编写任何测试):

1) 当您从测试方法“接收”的操作方法返回 JSonResult 时,您仍然可以访问原始数据对象。起初这对我来说并不明显(尽管有些明显)。 Rob 在上面(或者可能在下面!)的答案使用这个事实来获取 Data 参数并从中创建一个字典。如果 Data 是已知类型,那么您当然可以将其转换为该类型。

就我个人而言,我只通过 AJAX 返回非常非常简单的消息,没有任何结构。我想出了一个扩展方法,如果你只有一个从匿名类型构造的简单消息,它可能对测试很有用。如果您的对象有多个“级别” - 无论如何,您最好创建一个实际的类来表示 JSON 对象,在这种情况下,您只需将 jsonResult.Data 转换为该类型。

先示例使用:

动作方法:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult ContactUsForm(FormCollection formData)

     // process formData ...

     var result = new JsonResult()
     
          Data = new  success = true, message = "Thank you " + firstName 
     ;

     return result;

单元测试:

var result = controller.ContactUsForm(formsData);
if (result is JSonResult) 

     var json = result as JsonResult;
     bool success = json.GetProperty<bool>("success");
     string message = json.GetProperty<string>("message");

     // validate message and success are as expected

然后您可以在测试中运行断言或任何您想要的东西。另外,如果类型不符合预期,扩展方法会抛出异常。

扩展方法:

public static TSource GetProperty<TSource>(this JsonResult json, string propertyName) 

    if (propertyName == null) 
    
        throw new ArgumentNullException("propertyName");
    

    if (json.Data == null)
    
        throw new ArgumentNullException("JsonResult.Data"); // what exception should this be?
    

    // reflection time!
    var propertyInfo = json.Data.GetType().GetProperty(propertyName);

    if (propertyInfo == null) 
        throw new ArgumentException("The property '" + propertyName + "' does not exist on class '" + json.Data.GetType() + "'");
    

    if (propertyInfo.PropertyType != typeof(TSource))
    
        throw new ArgumentException("The property '" + propertyName + "' was found on class '" + json.Data.GetType() + "' but was not of expected type '" + typeof(TSource).ToString());
    

    var reflectedValue = (TSource) propertyInfo.GetValue(json.Data, null);
    return reflectedValue;

【讨论】:

【参考方案4】:

我认为您有一个正确的观点 - 为什么不将“接受的响应类型与生成的响应类型解析”委托给它实际所属的某个地方?

这让我想起了 Jeremy Miller 关于如何制作 ASP.NET MVC 应用程序的观点之一:“Opinions” on the ASP.NET MVC

在他们的应用程序中,所有控制器操作都有一个简洁的界面 - 一些视图模型对象进入,另一个视图模型对象离开。

【讨论】:

【参考方案5】:

我不确定这实际上是一个多大的问题,但在 ASP.NET MVC 中遵循的“替代模式”将是编写一个 JSON ViewEngine。这实际上并没有那么困难,因为框架中内置的 JSON 功能将为您完成大部分繁重的工作。

我确实认为这会是一个更好的设计,但我不确定它是否值得反对“官方”实现 JSON 的方式。

【讨论】:

聪明 :) 我还没想好,但听起来可行。我有兴趣看看 MS 的任何人是否就这个问题发表意见【参考方案6】:

我有同样的想法并实现了一个JsonPox 过滤器来做到这一点。

【讨论】:

痘?如果你想让人们使用它,你不应该把它命名为 Pox :-)【参考方案7】:

或者,如果您不想使用反射,您可以使用结果的 Data 属性创建一个 RouteValueDictionary。使用 OP 的数据...

var jsonData = new RouteValueDictionary(result.Data);
Assert.IsNotNull(jsonData);

Assert.AreEqual(2,
                jsonData.Keys.Count);

Assert.AreEqual("123",
                jsonData["foo"]);

Assert.AreEqual(true,
                jsonData["success"]);

【讨论】:

我应该测试我的控制器(MVC)吗?

】我应该测试我的控制器(MVC)吗?【英文标题】:ShouldItestmycontrollers(MVC)?【发布时间】:2012-04-2922:31:12【问题描述】:我已经使用TDD几个月了,现在我想学习如何测试我的控制器(MVC)。单元测试是通过测试每个功能的最小单元... 查看详情

java swing vs mvc:这种模式真的可能吗?

...VC模式。是我,还是在JTree中使用SwingWorker,无法明确分离控制器/视图/模型?例如,我使用Swingworker 查看详情

为啥我的 MVC 控制器返回 JSON 用引号括起来且未格式化? [复制]

】为啥我的MVC控制器返回JSON用引号括起来且未格式化?[复制]【英文标题】:WhyismyMVCcontrollerreturningJSONwrappedinquotesandunformatted?[duplicate]为什么我的MVC控制器返回JSON用引号括起来且未格式化?[复制]【发布时间】:2021-07-0516:02:10【... 查看详情

我真的应该使用 NoSQL 吗?

...,我只显示数据而不是任何处理。我最近遇到了NoSql。在我的特殊情况下,除了能够在文 查看详情

ASP.NET MVC 和 WCF

...传输数据的最佳方式是什么。我应该只使用特殊视图/让控制器返回JSON或XML 查看详情

AngularJS 真的是 MVC 吗?

...)可以看成是视图,TodoCtrl从todo.js构造的对象可以看成是控制器,但模型在哪里呢?ng-model之类的属性映射到框架的一 查看详情

2019 年我应该在哪里存储我的 JWT,localStorage 真的不安全吗?

】2019年我应该在哪里存储我的JWT,localStorage真的不安全吗?【英文标题】:WhereshouldIstoremyJWTin2019andisthelocalStoragereallynotsecure?【发布时间】:2019-10-1506:46:16【问题描述】:有趣的话题。由于我正在使用Node.jsApi和ReactReduxClient创建... 查看详情

JSON - Spring MVC:如何将 json 数据发布到 Spring MVC 控制器

】JSON-SpringMVC:如何将json数据发布到SpringMVC控制器【英文标题】:JSON-SpringMVC:HowtopostjsondatatospringMVCcontroller【发布时间】:2013-01-1110:54:51【问题描述】:我在将JSON数据从jsp发布到controller时遇到问题。每次我尝试都会收到一个ajax... 查看详情

在 PHP 中使用 MVC 时,1 控制器应该能够加载整个动态用户配置文件吗?

】在PHP中使用MVC时,1控制器应该能够加载整个动态用户配置文件吗?【英文标题】:WhenusingMVCinPHP,Should1Controllerbeabletoloadaentiredynamicuserprofile?【发布时间】:2011-12-2305:27:16【问题描述】:这些天我几乎不敢在这里问问题,因为我... 查看详情

我的 Linq 查询应该存储在哪里 .NET MVC

...哪里存储linq查询?在下面的实例中,我将它们放在我的控制器中,就像这样publicActionResultIndex()varvehicles=fromvindb.Vehicle 查看详情

JSON 参数自动。当 ajax 请求向 MVC 操作方法发出时转换为小写?

...题描述】:有人知道为什么我的参数在遇到我的ASP.NETMVC控制器操作时被“转换”为小写吗?我只能假设它被转换为在ajax请求之前查看数据值,它的大小写正 查看详情

ASP.NET MVC - 控制器中应该存在业务逻辑吗?

】ASP.NETMVC-控制器中应该存在业务逻辑吗?【英文标题】:ASP.NETMVC-Shouldbusinesslogicexistincontrollers?【发布时间】:2010-09-1902:41:36【问题描述】:DerikWhitaker几天前发布了一个article,这引起了我一段时间以来的好奇:控制器中应该存... 查看详情

我们应该将 CancellationToken 与 MVC/Web API 控制器一起使用吗?

】我们应该将CancellationToken与MVC/WebAPI控制器一起使用吗?【英文标题】:ShouldweuseCancellationTokenwithMVC/WebAPIcontrollers?【发布时间】:2013-10-0109:29:33【问题描述】:异步控制器有不同的示例。其中一些在方法定义中使用了CancellationTok... 查看详情

我应该从 MVC 框架中的控制器或模型中调用 redirect() 吗?

】我应该从MVC框架中的控制器或模型中调用redirect()吗?【英文标题】:ShouldIcallredirect()fromwithinmyControllerorModelinanMVCframework?【发布时间】:2011-06-0320:10:54【问题描述】:我正在使用MVCPHP框架Codeigniter,我有一个直截了当的问题,... 查看详情

MVC - 我需要在视图中使用控制器吗?

】MVC-我需要在视图中使用控制器吗?【英文标题】:MVC-doIneedtouseControllerintheView?【发布时间】:2012-09-0921:18:44【问题描述】:据我所知,在MVC的标准实现中,我们将控制器和模型传递给视图但我有点不同意这个想法。我不希望... 查看详情

如何在 MVC4 网站中集成和测试 PayPal:我真的需要公开我的开发环境吗?

】如何在MVC4网站中集成和测试PayPal:我真的需要公开我的开发环境吗?【英文标题】:HowtointegrateandtestPayPalinanMVC4website:doIreallyneedtoexposemydevenvironment?【发布时间】:2014-11-1617:28:24【问题描述】:我在这里阅读了很多关于PayPal的... 查看详情

我真的需要将我的项目货币转换为 NSDecimalNumber 吗?

】我真的需要将我的项目货币转换为NSDecimalNumber吗?【英文标题】:DoIreallyneedtoconvertmyprojectcurrencytoNSDecimalNumber?【发布时间】:2015-10-0203:34:10【问题描述】:是的,我知道我应该使用NSDecimalNumber来处理货币、金钱、价格...I\'veread... 查看详情

我应该在 MVC 编码中使用啥标准

...将业务逻辑放在模型本身中,而在我的程序中,我直接在控制器的操作中使用EF,例如从直接数据库我正在做以下事情:publicActionResultCarList()using(var_db 查看详情