1

I am using jquery datatables with server side C#/asp.net mvc3 processing. it all works now but i have a new task to do:

i want to hide some columns for security reasons to certain users. how do i create columns in server side c# and pass to datatable in the view?

4
  • the short answer is to assign everything to a big json object and then parse it in client side. that json should ideally be having atleast column names and data Commented Dec 27, 2011 at 2:31
  • i tried it but no luck. do you mean i have to add columdef or acolum property in c# results JSON that I pass to view? Commented Dec 27, 2011 at 2:37
  • please show what you have tried and how you are binding currently. Commented Dec 27, 2011 at 2:39
  • js code or c#? when i remove column property in js, i get datatable warning that the parameter 9 for row 0 is empty Commented Dec 27, 2011 at 2:40

2 Answers 2

2

Controller might look like:

public JsonResult NewsJSON(jQueryDataTableParamModel param)
    {
        var news = _newsRepository.GetNews();

        string col = param.sColumns.Split(',')[param.iSortCol_0];
        string orderby = col + " " + param.sSortDir_0;

        if (param.sSearch != null)
            news = news.Search(param.sSearch);

        var qry = new PaginatedList<News>(news.OrderBy(orderby), param.iDisplayStart, param.iDisplayLength);

        return Json(new
        {
            sEcho = param.sEcho,
            iTotalRecords = qry.TotalCount,
            iTotalDisplayRecords = qry.TotalCount,
            aaData = (
                from n in qry
                select new[]
                {
                    n.Headline, 
                    n.DateCreated.ToString() 
                }).ToArray()
        }, JsonRequestBehavior.AllowGet);
    }

jQueryDataTableParamModel.cs

public class jQueryDataTableParamModel
{
    /// <summary>
    /// Request sequence number sent by DataTable,
    /// same value must be returned in response
    /// </summary>       
    public string sEcho { get; set; }

    /// <summary>
    /// Text used for filtering
    /// </summary>
    public string sSearch { get; set; }

    /// <summary>
    /// Number of records that should be shown in table
    /// </summary>
    public int iDisplayLength { get; set; }

    /// <summary>
    /// First record that should be shown(used for paging)
    /// </summary>
    public int iDisplayStart { get; set; }

    /// <summary>
    /// Number of columns in table
    /// </summary>
    public int iColumns { get; set; }

    /// <summary>
    /// Number of columns that are used in sorting
    /// </summary>
    public int iSortingCols { get; set; }

    /// <summary>
    /// Comma separated list of column names
    /// </summary>
    public string sColumns { get; set; }

    /// <summary>
    /// Sort column
    /// </summary>
    public int iSortCol_0 { get; set; }

    /// <summary>
    /// Asc or Desc
    /// </summary>
    public string sSortDir_0 { get; set; }

}

And in view:

<script type="text/javascript" charset="utf-8">

$(function () {

$('#news').dataTable({    
"bSort" : true,        
"bServerSide": true,    
"sAjaxSource": "/News/NewsJSON",    
"bProcessing": true,    
"sPaginationType": "full_numbers",    
"iDisplayLength": 25,    
"aLengthMenu": [[25, 50, 100, 150, 200], [25, 50, 100, 150, 200]],

"aaSorting": [[1,'asc']],    
"aoColumns": [    
{ "sName": "Headline" }    
,{ "sName": "DateCreated" }    
]    
});    
});    
</script>


<table id="news" class="default-table" cellpadding="0" cellspacing="0">
<thead>

        <tr>
            <th>
                Headline
            </th>
            <th style="width: 114px">
                Created
            </th>
        </tr>
    </thead>
</table>

Or similar :) not sure if I got the js correct but.

/Lasse

UPDATE:

Bit messy but something like:

public class DataTableOptions
{
    public string ID { get; set; }
    public string Url { get; set; }
    public string Cols { get; set; }
    public bool Sort { get; set; }
    public string ViewUrlLinkname { get; set; }
    public string EditUrlLinkname { get; set; }
    public string DeleteLinkname { get; set; }
    public string DeleteTitle { get; set; }
    public string DeleteYes { get; set; }
    public string DeleteNo { get; set; }
    public string DeleteMessage { get; set; }
}

public static class DataTableHelpers
{
    private static string aLengthMenu = "[[25, 50, 100, 150, 200], [25, 50, 100, 150, 200]]";

    public static MvcHtmlString DataTable(this HtmlHelper helper, DataTableOptions options)
    {
        string[] arrcols = options.Cols.Split(',');
        int sortOffset = arrcols.Where(x => x == "Delete" || x == "View" || x == "Edit").Count();

        StringBuilder sb = new StringBuilder();

        sb.AppendLine("<script type=\"text/javascript\" charset=\"utf-8\">");
        sb.AppendLine("$(function () {");
        sb.AppendLine("$('#" + options.ID + "').dataTable({");
        sb.AppendLine("\"bSort\" : " + options.Sort.ToString().ToLower() + ",");
        sb.AppendLine("\"oLanguage\": { \"sUrl\": \"" + oLanguage + "\" },");
        sb.AppendLine("\"bServerSide\": true,");
        sb.AppendLine("\"sAjaxSource\": \"" + options.Url + "\",");
        sb.AppendLine("\"bProcessing\": true,");
        sb.AppendLine("\"sPaginationType\": \"full_numbers\",");
        sb.AppendLine("\"iDisplayLength\": 25,");
        sb.AppendLine("\"aLengthMenu\": " + aLengthMenu + ",");
        sb.AppendLine("\"aaSorting\": [[" + sortOffset.ToString() + ",'asc']],");
        sb.AppendLine("\"aoColumns\": [");


        for (int i = 0; i < arrcols.Length; i++)
        {
            if (i > 0)
                sb.Append(",");

            switch (arrcols[i])
            {
                case "Delete":
                    sb.AppendLine("{");
                    sb.AppendLine("\"sName\": \"" + arrcols[i] + "\",");
                    sb.AppendLine("\"bSearchable\": false,");
                    sb.AppendLine("\"bSortable\": false,");
                    sb.AppendLine("\"fnRender\": function (oObj) {");
                    sb.Append("return '");
                    sb.Append("<a class=\"deletelink\" href=\"' + oObj.aData["+ i.ToString() + "] + '\">" + options.DeleteLinkname + "</a> ");
                    sb.Append("';");
                    sb.AppendLine("}");
                    sb.AppendLine("}");

                    break;
                case "Edit":
                    sb.AppendLine("{");
                    sb.AppendLine("\"sName\": \"" + arrcols[i] + "\",");
                    sb.AppendLine("\"bSearchable\": false,");
                    sb.AppendLine("\"bSortable\": false,");
                    sb.AppendLine("\"fnRender\": function (oObj) {");
                    sb.Append("return '");
                    sb.Append("<a class=\"editlink\" href=\"' + oObj.aData["+ i.ToString() + "] +'\">" + options.EditUrlLinkname + "</a> ");
                    sb.Append("';");
                    sb.AppendLine("}");
                    sb.AppendLine("}");

                    break;
                case "View":
                    sb.AppendLine("{");
                    sb.AppendLine("\"sName\": \"" + arrcols[i] + "\",");
                    sb.AppendLine("\"bSearchable\": false,");
                    sb.AppendLine("\"bSortable\": false,");
                    sb.AppendLine("\"fnRender\": function (oObj) {");
                    sb.Append("return '");
                    sb.Append("<a class=\"viewlink\" href=\"' + oObj.aData["+ i.ToString() + "] + '\">" + options.ViewUrlLinkname + "</a> ");
                    sb.Append("';");
                    sb.AppendLine("}");
                    sb.AppendLine("}");

                    break;
                default:
                    sb.AppendLine("{ \"sName\": \"" + arrcols[i] + "\" }");
                    break;
            }

        }

        sb.AppendLine("]");
        sb.AppendLine("});");
        sb.AppendLine("});");
        sb.AppendLine("</script>");

        if (options.DeleteLinkname != null)
        {
            sb.Append(ConfirmHelpers.RenderConfirm(options.DeleteTitle, options.DeleteYes, options.DeleteNo, options.DeleteMessage));
        }

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

and in controller pass the links as well:

    return Json(new
    {
        sEcho = param.sEcho,
        iTotalRecords = qry.TotalCount,
        iTotalDisplayRecords = qry.TotalCount,
        aaData = (
            from n in qry
            select new[]
            {
                Url.Action("Detail", new { newsID = n.NewsID, headline = n.Headline.ToURLParameter() }),
                Url.Action("Edit", new { newsID = n.NewsID, headline = n.Headline.ToURLParameter() }),
                Url.Action("Delete", new { newsID = n.NewsID }),
                n.Headline, 
                n.DateCreated.ToString() 
            }).ToArray()
    }, JsonRequestBehavior.AllowGet);

then

    @Html.DataTable(Model.DataTableOptions)

    <table id="news" class="default-table" cellpadding="0" cellspacing="0">
<thead>
        <tr>
            <th style="width: 20px">
            </th>
            <th style="width: 50px">
            </th>
            <th style="width: 40px"></th>
            <th>
                Headline
            </th>....

For delete I use plugin: http://tutorialzine.com/2010/12/better-confirm-box-jquery-css3/

public static string RenderConfirm(string title, string yes, string no, string deleteMessage)
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendLine("<script type=\"text/javascript\" charset=\"utf-8\">");
        sb.AppendLine("$(function () {");
        sb.AppendLine("$('.deletelink').live('click', function (e) {");
        sb.AppendLine("e.preventDefault();");
        sb.AppendLine("var elem = $(this);");
        sb.AppendLine("$.confirm({");
        sb.AppendLine("'title': '" + title + "',");
        //sb.AppendLine("'message': $(this).attr('data-delete-message'),");
        sb.AppendLine("'message': '" + deleteMessage + "',");
        sb.AppendLine("'buttons': {");
        sb.AppendLine("'" + yes + "': {");
        sb.AppendLine("'class': 'confirm-yes',");
        sb.AppendLine("'action': function () {");
        sb.AppendLine("$.post($(elem).attr('href'));");
        sb.AppendLine("}");
        sb.AppendLine("},");
        sb.AppendLine("'" + no + "': {");
        sb.AppendLine("'class': 'confirm-no',");
        sb.AppendLine("'action': function () { } // Nothing to do in this case. You can as well omit the action property.");
        sb.AppendLine("}");
        sb.AppendLine("}");
        sb.AppendLine("});");
        sb.AppendLine("});");
        sb.AppendLine("});");
        sb.AppendLine("</script>");

        return sb.ToString();
    }
Sign up to request clarification or add additional context in comments.

9 Comments

its good. i have that. all i want is the aoColumns property to be generated in controller so i only send data for the columns which user can see. some users cannot see Headline column and data for Headline column for example so i dont send that data but because of aoColumns property, jquery expects two columns (not just created column)
You can write a html-helper for that, where you pass a comma separated value (or similar) to and it generates the script for you. @Html.DataTable(...)
sorry i didn't understand. you mean "aoColumns":@Html.GetColumnsForUser("UserRole")? i dont know if i have been clear. i want to hide a column. so if a column contains Edit hyperlink and the user is not admin then i want to hide this column for all of the rows. if the user is admin then i want to hide Edit when the row was created by him
or are you suggesting that i will create @Html.DataTable and in the helper, i will write script that generates javascript? how will i then inject into document.getready function?
Excellent. that is definetly more secure than what i had. can you please post what you have in ConfirmHelpers.RenderConfirm(options.DeleteTitle, options.DeleteYes, options.DeleteNo, options.DeleteMessage) so i can clean it up and create a solution to see if it works?
|
1

If that's a security concern I wouldn't push everything and parse it on client side as this means no security at all. the fact that you don't render certain columns doesn't mean that data is unavailable for unauthorized users. It can be easily reached by developer tools from any browsers (Firebug) or proxies.

You need to ensure that users will get only the data they need. If you have users with different permission / access levels you need to implement RBAC - Role Based Access Control approach. Then, based on a role that particular user belongs to, you serve him with the data he needs & he is entitled to. From here you can take many paths to reach your goal. Quick and dirty way would be to create a dynamic LINQ query to select only the columns that are needed for a particular user and a simple switch/case-like structure on Role variable to construct proper query. More sophisticated would need to create column name collections for each role and filtering logic for generic LINQ result. It will be more flexible and will allow to add/remove columns easily without the need of changing the logic.

These are some high level advices/thoughts as you didn't provide any code or background on what the application is and why it needs to work like that. But again, if you're talking about security reasons for hiding data don't send it plain text to the clients browser. Someone will eventually find out that you're sending the complete set and it's only not being rendered. Using javascript for security is never good idea because ist's all on client side and can be modified in runtime.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.