Comment convertir View Model en objet JSON dans ASP.NET MVC?

156

Je suis un développeur Java, nouveau sur .NET. Je travaille sur un projet .NET MVC2 où je souhaite avoir une vue partielle pour envelopper un widget. Chaque objet de widget JavaScript a un objet de données JSON qui serait rempli par les données du modèle. Ensuite, les méthodes pour mettre à jour ces données sont liées aux événements lorsque les données sont modifiées dans le widget ou si ces données sont modifiées dans un autre widget.

Le code est quelque chose comme ceci:

MyController:

virtual public ActionResult DisplaySomeWidget(int id) {
  SomeModelView returnData = someDataMapper.getbyid(1);

  return View(myview, returnData);
}

myview.ascx:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<SomeModelView>" %>

<script type="text/javascript">
  //creates base widget object;
  var thisWidgetName = new Widget();

  thisWidgetName.updateTable = function() {
    //  UpdatesData
  };
  $(document).ready(function () {
    thisWidgetName.data = <% converttoJSON(model) %>
    $(document).bind('DATA_CHANGED', thisWidgetName.updateTable());
  });
</script>

<div><%:model.name%></div>

Ce que je ne sais pas, c'est comment envoyer les données au fur SomeModelViewet à mesure , puis pouvoir les utiliser pour remplir le widget et le convertir en JSON. J'avais vu des moyens très simples de le faire dans le contrôleur mais pas dans la vue. Je suppose que c'est une question de base, mais j'essaie depuis quelques heures de créer cette nappe.

Chris Stephens
la source
1
Je sais que c'est une vieille question. Mais à partir d'aujourd'hui, il existe de meilleures façons de le faire. Ne mélangez pas JSON en ligne avec votre résultat View. JSON est facilement seriales via AJAX et peut être traité comme des objets. Tout ce qui est en JavaScript doit être séparé de la vue. Vous pouvez facilement retourner des modèles sans aucun effort via un contrôleur.
Piotr Kula

Réponses:

346

Dans mvc3 avec un rasoir @Html.Raw(Json.Encode(object))semble faire l'affaire.

Dave
la source
1
+1 J'ai utilisé Html.Raw, mais je n'ai jamais trouvé Json.Encode et j'ai juste utilisé JavaScriptSerializer pour ajouter la chaîne dans le contrôleur au modèle de vue
AaronLS
5
Cette approche fonctionne même lorsque vous souhaitez transmettre le JSON résultant à Javascript. Razor se plaint des gribouillis verts si vous mettez le code @ Html.Raw (...) comme paramètre de fonction dans les balises <script>, mais le JSON parvient effectivement au JS appelé. Très pratique et élégant. +1
Carl Heinrich Hancke
1
C'est la manière de le faire depuis MVC3 et doit être renvoyé par un contrôleur. N'intégrez pas json dans votre Viewresult. Au lieu de cela, créez un contrôleur pour gérer le JSON séparément et faites une requête JSON via AJAX. Si vous avez besoin de JSON sur la vue, vous faites quelque chose de mal. Utilisez soit un ViewModel ou autre chose.
Piotr Kula
3
Json.Encode encode mon tableau à 2 dimensions en un tableau à 1 dimension dans json. Newtonsoft.Json.JsonConvert.SerializeObject sérialise correctement les deux dimensions dans json. Je suggère donc d'utiliser ce dernier.
mono68
12
Lorsque vous utilisez MVC 6 (peut-être aussi 5), vous devez utiliser à la Json.Serializeplace de Encode.
KoalaBear
31

Bravo, vous venez tout juste de commencer à utiliser MVC et vous avez trouvé son premier défaut majeur.

Vous ne voulez pas vraiment le convertir en JSON dans la vue, et vous ne voulez pas vraiment le convertir dans le contrôleur, car aucun de ces emplacements n'a de sens. Malheureusement, vous êtes coincé dans cette situation.

La meilleure chose que j'ai trouvée à faire est d'envoyer le JSON à la vue dans un ViewModel, comme ceci:

var data = somedata;
var viewModel = new ViewModel();
var serializer = new JavaScriptSerializer();
viewModel.JsonData = serializer.Serialize(data);

return View("viewname", viewModel);

puis utilisez

<%= Model.JsonData %>

à votre avis. Sachez que le JavaScriptSerializer standard de .NET est une merde.

le faire dans le contrôleur le rend au moins testable (mais pas exactement comme ci-dessus - vous voulez probablement prendre un ISerializer comme dépendance afin que vous puissiez vous en moquer)

Mettez à jour également, concernant votre JavaScript, il serait bon d'encapsuler TOUS les widgets JS que vous avez ci-dessus comme ceci:

(
    // all js here
)();

de cette façon, si vous mettez plusieurs widgets sur une page, vous n'obtiendrez pas de conflits (à moins que vous n'ayez besoin d'accéder aux méthodes depuis un autre endroit de la page, mais dans ce cas, vous devriez quand même enregistrer le widget avec un framework de widget). Ce n'est peut-être pas un problème maintenant, mais il serait bon d'ajouter les crochets maintenant pour vous épargner beaucoup d'efforts à l'avenir quand cela deviendra une exigence, c'est aussi une bonne pratique OO pour encapsuler la fonctionnalité.

Andrew Bullock
la source
1
Cela semble intéressant. J'allais simplement faire une copie des données en tant que json et les passer en tant que viewData, mais de cette façon, cela semble plus intéressant. Je vais jouer avec ça et vous le faire savoir. BTW c'est la première fois que je publie une question sur stackoverflow et il a fallu quoi..5 minutes pour obtenir de bonnes réponses, c'est génial !!
Chris Stephens
rien de mal avec cela, ce n'est tout simplement pas personnalisable, si vous voulez un formatage de valeur personnalisé, vous devez le faire avant la main, en faisant essentiellement tout une chaîne: (c'est le plus important avec les dates. Je sais qu'il existe des solutions de contournement faciles, mais elles ne devraient pas être nécessaire!
Andrew Bullock
@Update J'allais voir comment utiliser un compteur statique .net de ce widget pour générer un nom d'objet unique. Mais ce que vous avez écrit semble accomplir la même chose et le rendre plus lisse. J'ai également cherché à lier la création d'un widget «new_widget_event» à un objet au niveau de la page pour les suivre, mais la raison originale de le faire est devenue OBE. Je pourrais donc y revenir plus tard. Merci pour toute l'aide que je vais publier une version modifiée de ce que vous avez suggéré mais expliquez pourquoi je l'aime mieux.
Chris Stephens
2
Je rétracte ma déclaration précédente "il n'y a rien de mal à cela". Il y a tout ce qui ne va pas avec ça.
Andrew Bullock
Pourquoi dites-vous que nous ne pouvons pas renvoyer JSON à partir du contrôleur. C'est ainsi que c'est censé être fait. L'utilisation JavaScriptSerializerou les Return Json(object)deux utilisent les mêmes sérialiseurs. La publication du même JSON sur le contrôleur reconstruira également l'objet pour vous tant que vous définissez le modèle correct. Peut-être que pendant MVC2 c'était un inconvénient majeur ... mais aujourd'hui c'est un jeu d'enfant et très pratique. Vous devriez mettre à jour votre réponse pour refléter cela.
Piotr Kula
18

J'ai trouvé que c'était assez agréable de le faire comme ça (utilisation dans la vue):

    @Html.HiddenJsonFor(m => m.TrackingTypes)

Voici la classe d'extension de la méthode d'assistance correspondante:

public static class DataHelpers
{
    public static MvcHtmlString HiddenJsonFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
    {
        return HiddenJsonFor(htmlHelper, expression, (IDictionary<string, object>) null);
    }

    public static MvcHtmlString HiddenJsonFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
    {
        return HiddenJsonFor(htmlHelper, expression, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
    }

    public static MvcHtmlString HiddenJsonFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
    {
        var name = ExpressionHelper.GetExpressionText(expression);
        var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

        var tagBuilder = new TagBuilder("input");
        tagBuilder.MergeAttributes(htmlAttributes);
        tagBuilder.MergeAttribute("name", name);
        tagBuilder.MergeAttribute("type", "hidden");

        var json = JsonConvert.SerializeObject(metadata.Model);

        tagBuilder.MergeAttribute("value", json);

        return MvcHtmlString.Create(tagBuilder.ToString());
    }
}

Ce n'est pas super-sophistiqué, mais cela résout le problème de savoir où le mettre (dans Controller ou en vue?) La réponse est évidemment: ni l'un ni l'autre;)

Wolfgang
la source
C'était agréable et propre à mon avis et enveloppé dans une aide réutilisable. Cheers, J
John
6

Vous pouvez utiliser Jsondirectement à partir de l'action,

Votre action serait quelque chose comme ceci:

virtual public JsonResult DisplaySomeWidget(int id)
{
    SomeModelView returnData = someDataMapper.getbyid(id);
    return Json(returnData);
}

Éditer

Je viens de voir que vous supposez que c'est la vue Modeld'une vue, donc ce qui précède n'est pas strictement correct, vous devrez faire un Ajaxappel à la méthode du contrôleur pour obtenir cela, il ascxn'aurait alors pas de modèle en soi, je laisserai mon code au cas où cela vous serait utile et vous pouvez modifier l'appel

Edit 2 il suffit de mettre l'identifiant dans le code

Pharabus
la source
1
mais il ne peut pas rendre cela dans la vue, il devrait faire un deuxième appel ajax
Andrew Bullock
Merci, à l'origine, je faisais cela en utilisant un appel jQuery get json et je prévoyais que les éléments HTML les remplissent eux-mêmes à partir de json. Cependant, le modèle que nous suivons actuellement est que nos vues doivent renvoyer un modelView et c'est le moyen le plus simple de remplir des éléments HTML de base comme des tableaux, etc. Je pourrais envoyer les mêmes données au format JSON que ViewData, mais cela semble inutile.
Chris Stephens
2
Vous ne devez PAS intégrer JSON avec un résultat d'affichage. L'OP doit soit s'en tenir au MVC standard pratiqué et retourner un modèle ou un viewmodel, soit faire un AJAX. Il n'y a absolument AUCUNE RAISON d'intégrer JSON avec une vue. C'est juste du code très sale. Cette réponse est la manière correcte et la manière recommandée par Microsoft Practices. Le vote défavorable était inutile, c'est certainement la bonne réponse. Nous ne devons pas encourager les mauvaises pratiques de codage. Soit JSON via AJAX ou Modèles via des vues. Personne n'aime le code spaghetti avec un balisage mixte!
Piotr Kula
Cette solution entraînera le problème suivant: stackoverflow.com/questions/10543953/…
Jenny O'Reilly
2

@ Html.Raw (Json.Encode (object)) peut être utilisé pour convertir l'objet modal View en JSON

Priya Payyavula
la source
1

Prolonger la grande réponse de Dave . Vous pouvez créer un HtmlHelper simple .

public static IHtmlString RenderAsJson(this HtmlHelper helper, object model)
{
    return helper.Raw(Json.Encode(model));
}

Et à votre avis:

@Html.RenderAsJson(Model)

De cette façon, vous pouvez centraliser la logique de création du JSON si, pour une raison quelconque, vous souhaitez modifier la logique ultérieurement.

smoksnes
la source
0

Andrew a eu une bonne réponse mais je voulais la tweek un peu. La différence est que j'aime que mes ModelViews ne contiennent pas de données de surcharge. Juste les données de l'objet. Il semble que ViewData corresponde à la facture des données supplémentaires, mais bien sûr, je suis nouveau dans ce domaine. Je suggère de faire quelque chose comme ça.

Manette

virtual public ActionResult DisplaySomeWidget(int id)
{
    SomeModelView returnData = someDataMapper.getbyid(1);
    var serializer = new JavaScriptSerializer();
    ViewData["JSON"] = serializer.Serialize(returnData);
    return View(myview, returnData);
}

Vue

//create base js object;
var myWidget= new Widget(); //Widget is a class with a public member variable called data.
myWidget.data= <%= ViewData["JSON"] %>;

Ce que cela fait pour vous, c'est qu'il vous donne les mêmes données dans votre JSON que dans votre ModelView afin que vous puissiez potentiellement renvoyer le JSON à votre contrôleur et il aurait toutes les parties. Ceci est similaire à la simple demande via un JSONRequest, mais cela nécessite un appel de moins, ce qui vous évite cette surcharge. BTW c'est génial pour Dates mais cela semble être un autre fil.

Chris Stephens
la source