Skip to content
Nubgrammer
Menu
  • Home
  • Contact Me
Menu

Turn a Razor Model into a JavaScript Model

Posted on October 29, 2017April 22, 2019 by Tyler Sells

Note:  This post is out of date, please see the updated version here

If you really want to read it, please be my guest but this method is unnecessary and @Html.Raw presents many XSS security issues.

TL;DR:

This one is a little different than my other posts.  Basically, I wrote some code that I’m happy about that allows me to edit data in a very UX/UI friendly fashion by passing Razor model data into a JS function.

What I wanted to do:

This post isn’t going to be as long as my normal ones.  I’m just excited to share some code I figured out today.  I’ve been working on an issue that I came across while trying to create an Admin page.  What I want to do is display all of my users on the page in a table like structure with edit and delete buttons for each one.  So before I get into the problem and how I solved it, let’s look at what I wanted to achieve:

  • Keep each user row template in it’s own partial view for better manageability
  • Same goes for the edit user row template
  • Adhere to as many best practices as possible
    • Render scripts in the scripts section of the layout page, rather than wherever is convenient
    • Use view models appropriately
    • Pull view data from the Controller
    • Valid HTML
  • No page refresh, so everything is editable in-place

The Model Problem…

Because I wanted to keep my views as independent as possible, I had a hard time juggling the actual model data that came from my Controller.  Since sections from partial views aren’t rendered into the final view, getting the correct model data into a JavaScript function was tricky.  I also wanted it to be as clean as possible.  There were hacky methods I could have used but I’m trying to be better than that.  There’s also a second problem.  JavaScript essentially only handles one submit action for a form.  In order for my user rows to have an Edit button AND a Delete button, I had to figure out how to handle that as well.

The Solution on the HTML side:

This is part of the partial view for a single user row:

@model UserManagementSystem.Models.UserViewModel
<form action="" method="post" data-ref="@Model.Name">
     <div>
          @Html.DisplayFor(m => m.Name)
          <input name="Name" type="hidden" value="@Model.Name" />
     </div>
     <div>
          @Html.DisplayFor(m => m.Role)
          <button type="button" data-ref="@Model.Name" 
          onclick='Edit(this,@Html.Raw(Json.Encode(Model)));'
          name="action" value="edit" class="no-default">
               <span class="glyphicon glyphicon-edit"> </span>
          </button>
     </div>
     <div>
          <button type="submit" name="action" value="delete" class="no-default">
               <span class="glyphicon glyphicon-remove"> </span>
          </button>
     </div>
</form>

The extra divs allow me to display this form like a row in a table, but there’s some extra CSS that goes along with that to make it work.  If you’d like me to go into detail about that, let me know and I might write another post about it but I don’t want to muddy this up any further.

If you’re familiar with ASP.NET MVC, much of the above code should be self explanatory.  But if you’re not, the @model UserManagementSystem.Models.UserViewModel is a Razor line that holds the model data that came from the controller.  The @Html.DisplayFor lines are also Razor syntax that are called HTML helpers that create the necessary HTML markup for the item(s) from the data model that are passed as parameters.  Then there are the html input tags and button tags.  HTML helpers do exist for creating these input tags BUT they have a nasty habit of automatically assigning IDs to the element that are not easily controllable.  They work perfectly fine when you’re only displaying one item on a page, but in this case, I needed to have this form appear many times for different users on the same page.  Ergo, I wrote those tags manually, without even needing to give each one and ID.

The part that matters is the onclick action for the edit button.

<button type="button" data-ref="@Model.Name"
onclick='Edit(this,@Html.Raw(Json.Encode(Model)));'
name="action" value="edit" class="no-default">

With that statement, I’m passing the button (which allows me to have access to all of the data attributes associated with the button) along with the model data that was passed into the view originally.  @Html.Raw(Json.Encode(Model))is another HTML helper line that uses Json.Encode to encode the model data as a JSON object.  The @Html.Raw function prevents the Json.Encode function from automatically replacing certain symbols that may appear in your model data with more HTML friendly ones.

The Solution on the JS/jQ side:

Now the JavaScript/jQuery functions in the parent View:

@section scripts{
     <script>
          function Edit(button, model) {
               var model = new UserViewModel(model);
               var formRef = $(button).attr("data-ref")
               $.ajax({
                    url: "@Url.Action("EditUser")",
                    data: model,
                    success: function (data) {
                       var ref = "form[data-ref='"+formRef+"']";
                       $(ref).html(data);
                    }
               })
          }
          function UserViewModel(model) {
               this.Name = model.Name;
               this.Role = model.Role;
          }
     </script>
}

This is a pretty standard JS snippet.  The Edit function creates a new JS UserViewModel based off the model data that was passed into the function, gets the data attribute in the button that tells us where to find the form we want to update with the new HTML data that is returned from our controller, and finally calls our controller action with an ajax function to get the EditUser view data that will replace the current HTML in the form.

Because this code exists within the root view, I can render this script as a section that will be rendered correctly in the _Layout shared view.  If it were in the view that displays a user row, it would not be rendered at all since that particular view is rendered as a child.  I could have put the code directly inside a <script></script> block on that view BUT that code would be rendered before the jQuery framework was loaded into the document, throwing all sorts of errors.  Why not just load the jQuery framework first?  Well, that would increase the loading time of the page and I would violate the best practice of loading all JS at the end of the document.

In Conclusion:

Did I achieve what I set out to?

  • Keep each user row template in it’s own partial view for better manageability
    • Yes.  I can change the user row view however I need to and as long as it is a child view to the nth-degree of the view that contains the necessary JS, it will work.
  • Same goes for the edit user row template
    • Yes.  I didn’t cover the edit user view because it’s very similar to the user row view
  • Adhere to as many best practices as possible
    • Render scripts in the scripts section of the layout page, rather than wherever is convenient
      • Yes.  Since I was able to pass the required model data up to the parent view, the JS is not required to be in the same view as the model.
    • Use view models appropriately
      • Maybe.  I think I did?  If I didn’t, please let me know!  It seemed a little superfluous to create a JS object to represent a model that was already being passed as a JSON object, but by doing so, I think I can ensure that only the data I specifically bind to in that JS object will be passed to my controller, even if extra data was maliciously inserted into the original model data somehow.
    • Pull view data from the Controller
      • Yes.  All of my view data is pulled from the controller instead of cheating with@Html.Partial.  Not that there is never a legitimate use for that, but I don’t feel like this was one of them.
    • Valid HTML
      • Yes.  By using divs and CSS to display my data as a pseudo-table instead of using<table></table>, I am able to keep my form markup valid.  Also, because I wrote out my input tags instead of letting Visual Studio “help” me out, I avoided duplicate element IDs.
  • No page refresh, so everything is editable in-place
    • Yes.  Thank you ajax!

What could be better?

  • This method requires some slightly obtrusive JS to handle the onclick event of the button.  However, I can’t think of a non-obtrusive way that would be clean enough to warrant the change.
  • While this solution hinges on @Html.Raw(Json.Encode()), it feels a little hacky.  It works well here, but I don’t know every security implication that comes along with it.  If anyone wants to enlighten me, I would greatly appreciate that.

Author: Tyler Sells

Github

Share this:

  • Share on X (Opens in new window) X
  • Share on Facebook (Opens in new window) Facebook
  • More
  • Share on LinkedIn (Opens in new window) LinkedIn
  • Email a link to a friend (Opens in new window) Email
  • Share on Reddit (Opens in new window) Reddit

Like this:

Like Loading...

1 thought on “Turn a Razor Model into a JavaScript Model”

  1. kristian says:
    May 24, 2022 at 10:59 pm

    thanks alot you solved my problem!!

    Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Follow me on Twitter

My Tweets

Github Repos

vtsells (Tyler Sells)

Tyler Sells

vtsells
http://www.nubgrammer.com
Joined on Jun 21, 2017
9 Public Repositories
100DaysOfCode
embers
MultiSelect
MVC-Project-Start
nubgrammer.com
PermIT
Spray
vtsells.github.io
Wizard
0 Public Gists

Categories

  • #100DaysOfCode (4)
  • ASP.NET (7)
  • ASP.NET Core (1)
  • ASP.NET MVC (3)
  • CSS (4)
  • General (13)
  • JS (3)
  • LESS (2)
  • Snippets (4)
  • Tools (4)
  • Tutorials (9)

Recent Posts

  • Creating a Knockout.js project on Codepen
  • 100DaysOfCode Day 3 – A State of Mind
  • 100DaysOfCode Day 2 – The Building Blocks
  • 100DaysOfCode Day 1 (Sort of cheated already)
  • Committing to #100DaysOfCode
© 2026 Nubgrammer | Powered by Superbs Personal Blog theme
We use cookies on our website to give you the most relevant experience by remembering your preferences and repeat visits. By clicking “Accept”, you consent to the use of ALL the cookies.
Do not sell my personal information.
Cookie SettingsAccept
Manage consent

Privacy Overview

This website uses cookies to improve your experience while you navigate through the website. Out of these, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may affect your browsing experience.
Necessary
Always Enabled
Necessary cookies are absolutely essential for the website to function properly. These cookies ensure basic functionalities and security features of the website, anonymously.
CookieDurationDescription
cookielawinfo-checkbox-analytics11 monthsThis cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics".
cookielawinfo-checkbox-functional11 monthsThe cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional".
cookielawinfo-checkbox-necessary11 monthsThis cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary".
cookielawinfo-checkbox-others11 monthsThis cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other.
cookielawinfo-checkbox-performance11 monthsThis cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance".
viewed_cookie_policy11 monthsThe cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data.
Functional
Functional cookies help to perform certain functionalities like sharing the content of the website on social media platforms, collect feedbacks, and other third-party features.
Performance
Performance cookies are used to understand and analyze the key performance indexes of the website which helps in delivering a better user experience for the visitors.
Analytics
Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics the number of visitors, bounce rate, traffic source, etc.
Advertisement
Advertisement cookies are used to provide visitors with relevant ads and marketing campaigns. These cookies track visitors across websites and collect information to provide customized ads.
Others
Other uncategorized cookies are those that are being analyzed and have not been classified into a category as yet.
SAVE & ACCEPT
%d

    Privacy Policy