Click here to Skip to main content
15,892,674 members
Articles / Web Development / ASP.NET
Article

MVC JavaScript Injection

Rate me:
Please Sign up or sign in to vote.
4.54/5 (4 votes)
16 Oct 2012CPOL3 min read 45.9K   740   14   2
A way to inject JavaScript code in Razor.

Sample Image

Introduction

Suppose we have an MVC application which should display content from several tables in jqGrid controls. For each table we have to write JavaScript code for displaying data and more code for the controller allocated to each table. For each table, we have:

Model - generated by Visual Studio.

View:

JavaScript
$(function () {
	jQuery(listName).jqGrid({
	..........
}

Controller:

C#
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult LoadDataInGrid ()
{
	..........
}

If the database contains multiple tables that can mean a large amount of code/lost time.

Goal

Another approach would be that the application displays data without using the model. The advantage is that (at least) for displaying data we write less code. In addition, if you generalize the code, it may be reused.

Using the code

To try this method I created a project MVC4 - Web application / Razor (VS2010) and then I installed with nuget: jQuery, jQuery UI, and jqGrid (to get the latest version). To include these components in the application, we change:

App_Start/Bundle_Config:

C#
public static void RegisterBundles(BundleCollection bundles)
{
	//generated code
	..........
	// jqGrid
	// js
	bundles.Add(new ScriptBundle("~/bundles/jqGrid").Include(
                        "~/Scripts/i18n/grid.locale-en.js",
                        "~/Scripts/jquery.jqGrid.src.js"));
	// css
	bundles.Add(new StyleBundle("~/Content/jqGrid/css").Include(
                        "~/Content/jquery.jqGrid/ui.jqgrid.css"));
}

_Layout.cshtml:

XML
<!-- generated code -->
..........
<head>
	<meta charset="utf-8" />
	<title>@ViewBag.Title - My ASP.NET MVC Application </title>
	<link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
       <meta name="viewport" content="width=device-width" />
   	
	@Styles.Render("~/Content/css")
       @Styles.Render("~/Content/themes/base/css")

   	@Scripts.Render("~/bundles/jquery")
       @Scripts.Render("~/bundles/jqueryui")
       @Scripts.Render("~/bundles/modernizr")
</head>
..........
<body>
..........
	<nav>
         	    <ul id="menu">
                <li>@Html.ActionLink("Home", "Index", "Home")</li/>
                <li>@Html.ActionLink("About", "About", "Home")</li/>
                <li>@Html.ActionLink("Contact", "Contact", "Home")</li/>
                <li>@Html.ActionLink("Test", "Index", "Test")</li/>
             </ul>
         </nav>
..........
</body>

The automatically generated line @Scripts.Render("~/bundles/jquery") is now in the <head> section.

Then I created a TestController (empty MVC controller) and the corresponding menu option Index. TestController.cs now looks like:

C#
public ActionResult Index()
{	
	return View();
}

Now create the corresponding Index View (right-click on the text and AddView Index ...). Here we show the grid. First we include the js and css files for:

Views/Test/Index.cshtml:

XML
@Styles.Render("~/Content/jqGrid/css")

@{
    ViewBag.Title = "Index";
}

<div class="ui-corner-all floatLeft" style="margin-left:2%; width:96%;">
    <select id="tableSelect">
        <option value="" disabled selected>- select table-</option>
    </select>

    <div id="grid_wrapper">
        <table id="list" class="scroll"></table>
        <div id="pager" class="scroll" style="text-align: center;"></div>
    </div>
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqGrid")
}

I created a <select> control. It will display the tables contained in the database.

JavaScript code:

JavaScript
<script type="text/javascript">
    $(function () {
        var action = './Test/LoadTableNames';
        $.get(action, 'html', function (retval) {
            var arr = retval.split('*');
            for (i = 0; i < arr.length; i++) {
                $('#tableSelect').append(new Option(arr[i], arr[i]));
            }
        });
	..........

In the controller, we define the LoadTableNames function as follows:

C#
public string LoadTableNames()
{
    string sep = "*";
    string[] tbls = GetTables();
    return string.Join(sep, tbls);
}

The function returns the table names as "name1 * name2 * ..." and then uses JavaScript split function. To investigate the database:

C#
public string[] GetTables()
{
    string[] retval = null;

    DataTable tblSchema = new DataTable();
    SqlConnection cn = new SqlConnection(connectionString);
    using (cn)
    {
       //GetSchema example
       //http://msdn.microsoft.com/en-us/library/ms254934%28v=vs.80%29.aspx
       cn.Open();
       tblSchema = cn.GetSchema("Tables");
       cn.Close();
   }

   retval = new string[tblSchema.Rows.Count];

   for (int i = 0; i < tblSchema.Rows.Count; i++)
       retval[i] = tblSchema.Rows[i][2] as string;
	//string type = tblSchema.Rows[i][3] as string; type is "BASE TABLE" or "VIEW"

   Array.Sort(retval);

   return retval;
}

The connection string is defined as:

C#
string connectionString = 
  "data source=.\\SQLEXPRESS;attachdbfilename=|DataDirectory|\\" + 
  "NORTHWND.MDF;integrated security=True;user instance=True;multipleactiveresultsets=True;";

At this time the database is opened and table names are displayed in the control <select>.

Next intercept the select change event:

JavaScript
	..........

	$("#tableSelect").change(function () {
       	$("#list").jqGrid("GridUnload");
            	var tableName = $("#tableSelect option:selected").val();
            	var action = './Test/InjectScript?tableName=' + tableName;
            	$.get(action, 'html', function (retval) {
                try {
                       eval(retval);
                       jQuery("#list").setGridParam({ url: './Test/LoadGridData?tableName=' + 
                          tableName, page: 1 }). trigger("reloadGrid");
                    } catch (e) {
                       alert(e);
                }
            });
        });
    });
</script>

The steps:

  • If a grid has already been created, then destroy it: $("#list").jqGrid("GridUnload");
  • Generate JavaScript code to create a grid for the selected table:
  • JavaScript
    var action = './Test/InjectScript?tableName=' + tableName;
  • Grid display: eval(retval);
  • Load data: jQuery("#list").setGridParam({ url: './Test/LoadGridData?tableName=' + tableName, page: 1 }). trigger("reloadGrid");

So when the user selects a table, we try to generate JavaScript code:

C#
public string InjectScript(string tableName)
{
    string colNames = "colNames: [";
    string colModel = "colModel: [";

    string pk = string.Empty;

    SqlConnection cn = new SqlConnection(connectionString);
    using (cn)
    {
        DataTable t_struct = new DataTable();
        string strSQL = string.Format("SELECT * FROM [{0}]", tableName);

        SqlDataAdapter a_struct = new SqlDataAdapter(strSQL, cn);
        a_struct.FillSchema(t_struct, SchemaType.Source);

        foreach (DataColumn c in t_struct.Columns)
        {
            colNames += "'" + c.ColumnName + "', ";
            colModel += "{ name: '" + c.ColumnName + "', index: '" + c.ColumnName + "' }, ";
        }

        colNames += "]";
        colModel += "]";

        colNames = colNames.Replace(", ]", " ]");
        colModel = colModel.Replace(", ]", " ]");

        pk = GetTableFirstPrimaryKeyName(t_struct);
     }

     if (string.IsNullOrEmpty(pk))
     {
        return
           string.Format("alert('Table/View [{0}] does not have primary key')", tableName);
     }

     string r1 =
                @"function createJQGrid(tblListName, divPagerName, tableName) { 
                    var listName = '#' + tblListName;
                    var pagerName = '#' + divPagerName;

                    try {
                        var obg = $(listName);
                        if (obg != null) {
                            jQuery(listName).jqGrid({
                                url: '',
                        datatype: 'json',
                        mtype: 'POST',
                        autowidth: true,
                        height: '100%',
                        loadtext: 'Loading...',";
     string r2 =
                      @"pgtext: '{0} / {1}',
                        recordtext: '{0} - {1} / {2}',
                        rowList: [10, 20, 30],
                        rowNum: 10,
                        pager: jQuery(pagerName),
                        sortname: '" + pk + "'," +
                      @"viewrecords: true,
                        gridview: true,
                        sortorder: 'asc',
                        caption: tableName,
                        emptyrecords: 'Nu exista inregistrari',
                        autosearch: false,
                        jsonReader: {
                            root: 'rows',
                            page: 'page',
                            total: 'total',
                            records: 'records',
                            repeatitems: true,
                            cell: 'cell'," +
                           "id: '" + pk + "' " +
                      @"}
                    }).navGrid(pagerName, { edit: false, add: false, del: false, search: true, searchtext: '' });
                }
            }
            catch (err) {
                alert(err.description);
            }
        };";

      string r3 = string.Format("createJQGrid('list', 'pager', '{0}');", tableName);

      string retval =
                r1 + colNames + ", " + colModel + ", " + r2 + r3;

      return
         retval;
}

The GetTableFirstPrimaryKeyName function tries to return the first column contained in the primary key definition of the table. If no primary key is defined then we will use the first field in the table (this field used should not be empty). The constructed string is then used in the function eval () in JavaScript. At this time we have built a grid that contains the columns for the selected table.

Loading data:

C#
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult LoadGridData(string sidx, string sord, int page, int rows, 
       bool _search, string searchField, string searchOper, string searchString, string tableName)
{
    DataTable t_data = new DataTable();
    DataTable t_struct = new DataTable();

    string strSQL = string.Format("SELECT * FROM [{0}]", tableName);
    if (!string.IsNullOrEmpty(sidx))
        strSQL = string.Format("SELECT * FROM [{0}] ORDER BY [{1}] {2}", tableName, sidx, sord);

    SqlConnection cn = new SqlConnection(connectionString);
    using (cn)
    {
       //struct
       SqlDataAdapter a_struct = new SqlDataAdapter(strSQL, cn);
       a_struct.FillSchema(t_struct, SchemaType.Source);

       //date
       SqlDataAdapter a_data = new SqlDataAdapter(strSQL, cn);
       a_data.Fill(t_data);
    }

    string pk = GetTableFirstPrimaryKeyName(t_struct);
    string[] properties = new string[t_struct.Columns.Count];
    Type[] types = new Type[t_struct.Columns.Count];

    for (int i = 0; i < t_struct.Columns.Count; i++)
    {
        DataColumn dc = t_struct.Columns[i];
        properties[i] = dc.ColumnName;
        types[i] = dc.DataType;
    }

    string jsonstring = JsonConvert.SerializeObject(t_data, Formatting.Indented);

    GridResult jsonResults = null;

    JContainer obarr = (JContainer)JsonConvert.DeserializeObject(jsonstring);
    jsonResults = obarr.ToJQGridResult(page, rows, sidx, sord, pk, properties, types);

    return Content(jsonResults.ToJson());
}

The database is open to retrieve the field names and their types. Initially the data is contained in a DataTable. Data conversion in jqGrid accepted format (JSON) occurs in several stages:

C#
string jsonstring = JsonConvert.SerializeObject(t_data, Formatting.Indented);

(Formatting.Indented only for debugging.) jsonstring is converted into an array of type Jcontainer (Newtonsoft.Json.Linq) and finally the array is converted to the format supported by jqGrid with static class JQGridExtensions function ToJQGridResult. It should be noted that JQGridExtension contains several useful classes for data transformation. More about transforming data into JSON data is available here.

About source code

MVC generated project is too big so I have included only the needed files.

Steps to generate project

  1. Create MVC4 project (internet) with name MvcApp
  2. Package manager console\nuget
    • Install-Package jQuery
    • Install-Package jQuery.UI.Combined
    • Install-Package jQuery.jqGrid
  3. Add reference System.Runtime.Serialization
  4. Add empty controller TestController
  5. Add view for Index
  6. Create Helpers folder
  7. Copy files
    • _Layout.cshtml -> Views\Shared\_Layout.cshtml
    • Index.cshtml -> Views\Test\Index.xshtml
    • JQGridExtensions.cs -> Helpers\JQGridExtensions.cs (and include it in project)
    • TestController.cs -> Controllers\TestController.cs
    • BundleConfig.cs -> App_Start\BundleConfig.cs
    • NORTHWND files - > in App_Data (don't need to include in project)

Conclusion

We can write less code using JavaScript injection in MVC applications.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Romania Romania
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 2 Pin
Dmitry Mukalov20-Aug-14 3:43
Dmitry Mukalov20-Aug-14 3:43 
QuestionNot working Pin
lzliu6-Feb-14 1:44
lzliu6-Feb-14 1:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.