Jak przekonwertować model widoku na obiekt JSON w ASP.NET MVC?

156

Jestem programistą Java, nowym w .NET. Pracuję nad projektem .NET MVC2, w którym chcę mieć częściowy widok do zawijania widżetu. Każdy obiekt widgetu JavaScript zawiera obiekt danych JSON, który zostałby zapełniony danymi modelu. Następnie metody aktualizowania tych danych są powiązane ze zdarzeniami w przypadku zmiany danych w widgecie lub w przypadku zmiany tych danych w innym widgecie.

Kod wygląda mniej więcej tak:

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>

Nie wiem, jak przesłać dane jako, SomeModelViewa następnie móc ich użyć do wypełnienia widżetu, a także przekonwertować go na JSON. Widziałem naprawdę proste sposoby, aby to zrobić w kontrolerze, ale nie w widoku. Myślę, że to podstawowe pytanie, ale od kilku godzin próbuję zrobić to gładko.

Chris Stephens
źródło
1
Wiem, że to stare pytanie. Ale od dzisiaj są na to lepsze sposoby. Nie mieszaj wbudowanego kodu JSON z wynikiem w widoku. JSON można łatwo serializować za pośrednictwem AJAX i można go traktować jak obiekty. Wszystko w JavaScript powinno być oddzielone od widoku. Możesz łatwo zwrócić modele bez żadnego wysiłku za pomocą kontrolera.
Piotr Kula

Odpowiedzi:

346

W mvc3 z brzytwą @Html.Raw(Json.Encode(object))wydaje się działać.

Dave
źródło
1
+1 Użyłem Html.Raw, ale nigdy nie znalazłem Json.Encode i właśnie użyłem JavaScriptSerializer, aby dodać ciąg w kontrolerze do modelu widoku
AaronLS
5
To podejście działa nawet wtedy, gdy chcesz przekazać wynikowy kod JSON do JavaScript. Razor narzeka na zielone zawijasy, jeśli umieścisz kod @ Html.Raw (...) jako parametr funkcji wewnątrz tagów <script>, ale JSON rzeczywiście dociera do wywoływanego JS. Bardzo poręczny i zręczny. +1
Carl Heinrich Hancke
1
Jest to sposób na zrobienie tego, ponieważ MVC3 i powinien być zwracany przez kontroler. Nie osadzaj json w swoim Viewresult. Zamiast tego utwórz kontroler do osobnej obsługi JSON i wykonaj żądanie JSON za pośrednictwem AJAX. Jeśli potrzebujesz JSON w widoku, robisz coś nie tak. Użyj ViewModel lub czegoś innego.
Piotr Kula
3
Json.Encode koduje moją dwuwymiarową tablicę do jednowymiarowej tablicy w json. Newtonsoft.Json.JsonConvert.SerializeObject serializuje dwa wymiary poprawnie do formatu JSON . Dlatego proponuję skorzystać z tego drugiego.
mono68
12
Korzystając z MVC 6 (może również 5), musisz użyć Json.Serializezamiast Encode.
KoalaBear
31

Dobra robota, dopiero zacząłeś używać MVC i znalazłeś pierwszą poważną wadę.

Naprawdę nie chcesz konwertować go do formatu JSON w widoku i tak naprawdę nie chcesz konwertować go w kontrolerze, ponieważ żadna z tych lokalizacji nie ma sensu. Niestety utkniesz w tej sytuacji.

Najlepszą rzeczą, jaką znalazłem, jest wysłanie JSON do widoku w ViewModel, na przykład:

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

return View("viewname", viewModel);

następnie użyj

<%= Model.JsonData %>

Twoim zdaniem. Należy pamiętać, że standardowy .NET JavaScriptSerializer jest dość gówniany.

zrobienie tego w kontrolerze przynajmniej czyni go testowalnym (chociaż nie dokładnie tak jak powyżej - prawdopodobnie chcesz wziąć ISerializer jako zależność, abyś mógł go kpić)

Zaktualizuj również, jeśli chodzi o JavaScript, dobrą praktyką byłoby owinięcie WSZYSTKICH widgetów JS, które masz powyżej, w następujący sposób:

(
    // all js here
)();

w ten sposób, jeśli umieścisz wiele widżetów na stronie, nie wystąpią konflikty (chyba że potrzebujesz dostępu do metod z innego miejsca na stronie, ale w takim przypadku i tak powinieneś zarejestrować widżet w jakimś frameworku widżetów). To może nie być teraz problemem, ale dobrym zwyczajem byłoby dodanie nawiasów teraz, aby zaoszczędzić sobie dużo wysiłku w przyszłości, kiedy stanie się to wymaganiem. Dobrą praktyką OO jest również hermetyzowanie funkcjonalności.

Andrew Bullock
źródło
1
To wygląda interesująco. Miałem zamiar zrobić kopię danych jako json i przekazać ją jako viewData, ale w ten sposób wygląda to bardziej interesująco. Zagram w to i dam ci znać. Przy okazji, to jest mój pierwszy raz, kiedy publikuję pytanie na temat stackoverflow i zajęło mi to… 5 minut, aby uzyskać dobre odpowiedzi, to jest niesamowite !!
Chris Stephens
nic w tym złego , po prostu nie można go dostosować, jeśli chcesz dowolne niestandardowe formatowanie wartości, musisz to zrobić wcześniej, w zasadzie tworząc ciąg: (jest to najbardziej widoczne w przypadku dat. Wiem, że są łatwe obejścia, ale nie powinny być konieczne!
Andrew Bullock
@Update Miałem zamiar użyć statycznego licznika .net tego widżetu do wygenerowania unikalnej nazwy obiektu. Ale to, co napisałeś, wygląda na to, że osiągnie to samo i zrobi to gładko. Zajrzałem również do powiązania tworzenia widżetu „new_widget_event” z obiektem na poziomie strony, aby je śledzić, ale pierwotnym powodem tego stało się OBE. Więc może powrócę do tego później. Dzięki za wszelką pomoc, którą zamierzam opublikować zmodyfikowana wersja tego, co zasugerowałeś, ale umieść wyjaśnij, dlaczego mi się bardziej podoba.
Chris Stephens
2
wycofuję swoje poprzednie stwierdzenie „nie ma w tym nic złego”. Wszystko w tym jest nie tak.
Andrew Bullock,
Dlaczego mówisz, że nie możemy zwrócić JSON z kontrolera. Tak to powinno być zrobione. Używanie JavaScriptSerializerlub Return Json(object)oba używają tych samych serializatorów. Również wysłanie tego samego kodu JSON z powrotem do kontrolera spowoduje odbudowanie obiektu, o ile zdefiniujesz prawidłowy model. Może podczas MVC2 była to poważna wada… ale dzisiaj jest to bardzo proste i wygodne. Zaktualizuj swoją odpowiedź, aby to odzwierciedlić.
Piotr Kula
18

Okazało się, że całkiem fajnie jest to zrobić w ten sposób (użycie w widoku):

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

Oto odpowiednia metoda pomocnika Klasa rozszerzenia:

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

Nie jest to zbyt skomplikowane, ale rozwiązuje problem, gdzie go umieścić (w kontrolerze czy na widoku?) Odpowiedź oczywiście brzmi: ani;)

Wolfgang
źródło
To było moim zdaniem ładne i czyste i zapakowane w pomocnik wielokrotnego użytku. Pozdrawiam, J
John
6

Możesz użyć Jsonbezpośrednio z akcji,

Twoje działanie wyglądałoby mniej więcej tak:

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

Edytować

Właśnie zobaczyłem, że zakładasz, że to jest ModelView, więc powyższe nie jest do końca poprawne, musiałbyś wykonać Ajaxwywołanie metody kontrolera, aby to uzyskać, ascxnie miałby wtedy modelu jako takiego, zostawię swój kod na wszelki wypadek, gdyby było to przydatne i możesz zmienić wezwanie

Edytuj 2 po prostu umieść identyfikator w kodzie

Pharabus
źródło
1
ale nie może wyrenderować tego w widoku, musiałby wykonać drugie połączenie Ajax
Andrew Bullock
Dzięki, początkowo robiłem to za pomocą wywołania jQuery get json i planowałem, aby elementy HTML zapełniały je same z json. Jednak wzorzec, który teraz podążamy, jest taki, że nasze widoki powinny zwracać modelView i jest to najłatwiejszy sposób na wypełnienie podstawowych elementów HTML, takich jak tabele itp. Mogę przesłać te same dane w formacie JSON co ViewData, ale wydaje się to marnotrawstwem.
Chris Stephens
2
NIE powinieneś osadzać JSON z wynikiem widoku. OP musi albo trzymać się standardowej praktyki MVC i zwrócić model lub model widoku, albo wykonać AJAX. Nie ma absolutnie ŻADNEGO POWODU, aby osadzać JSON w widoku. To jest po prostu bardzo brudny kod. Ta odpowiedź jest poprawna i zalecana przez praktykę firmy Microsoft. Głos przeciw był niepotrzebny, to zdecydowanie poprawna odpowiedź. Nie powinniśmy zachęcać do złych praktyk kodowania. JSON przez AJAX lub modele za pośrednictwem widoków. Nikt nie lubi kodu spaghetti z mieszanymi znacznikami!
Piotr Kula
To rozwiązanie spowoduje następujący problem: stackoverflow.com/questions/10543953/…
Jenny O'Reilly
2

@ Html.Raw (Json.Encode (obiekt)) może służyć do konwersji obiektu modalnego widoku do formatu JSON

Priya Payyavula
źródło
0

Przedłużenie wspaniałej odpowiedzi od Dave'a . Możesz utworzyć prosty HtmlHelper .

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

Twoim zdaniem:

@Html.RenderAsJson(Model)

W ten sposób możesz scentralizować logikę tworzenia JSON, jeśli z jakiegoś powodu chciałbyś zmienić logikę później.

smoksnes
źródło
0

Andrew miał świetną odpowiedź, ale chciałem ją trochę poprawić. Różnica polega na tym, że wolę, aby moje ModelViews nie zawierały danych ogólnych. Tylko dane dotyczące obiektu. Wydaje się, że ViewData pasuje do rachunku za dane ogólne, ale oczywiście jestem w tym nowy. Proponuję zrobić coś takiego.

Kontroler

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

Widok

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

To daje ci te same dane w twoim JSON, co w twoim ModelView, więc możesz potencjalnie zwrócić JSON z powrotem do twojego kontrolera i będzie miał wszystkie części. Jest to podobne do żądania tego za pośrednictwem JSONRequest, ale wymaga jednego wywołania mniej, więc oszczędza ci tego narzutu. Przy okazji, to jest fajne dla randek, ale wydaje się, że to kolejny wątek.

Chris Stephens
źródło