Exigo Developer Resources

This database contains the documentation for Exigo's OData API's, as well as C# sample code and fully-realized demo applications ready to be customized for your needs. Start building downline viewers, reporting tools and shopping carts on the Exigo platform today!


Create an account Sign In

jqGrid Report

Overview

A jQuery-powered gridview framework using the popular jqGrid plugin, jQuery ThemeRoller and a custom C# wrapper to deliver OData reports with style and ease.

Namespaces

This sample requires the following namespaces:

using ExigoOData;
using Exigo.WebControls;
using System.Data.Services.Client;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.ComponentModel;

Exigo API Authentication

This sample accesses OData using the ExigoContext object:

    public ExigoContext ExigoOData
    {
        get
        {
            var context = new ExigoOData.ExigoContext(new Uri("http://api.exigo.com/4.0/" + exigoAPICompany + "/model"));
            context.IgnoreMissingProperties = true;
            var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(exigoAPILoginName + ":" + exigoAPIPassword));
            context.SendingRequest +=
                (object s, SendingRequestEventArgs e) =>
                    e.RequestHeaders.Add("Authorization", "Basic " + credentials);
            return context;
        }
    }
    

jQuery

We use jQuery for easier Ajax calls and theming.

<link href="<%=this.ResolveUrl("../../Themes/start/jquery-ui.custom.css") %>" rel="stylesheet" type="text/css" />
<script src="<%=this.ResolveUrl("../../Scripts/jquery.min.js") %>" type="text/javascript"></script>
<script src="<%=this.ResolveUrl("../../Scripts/jquery-ui.min.js") %>" type="text/javascript"></script>

jqGrid GridView Framework

We are also using jqGrid (http://trirand.com/blog/jqgrid/jqgrid.html) to display our data.

<link href="<%=this.ResolveUrl("../../Plugins/jquery.jqGrid/css/ui.jqgrid.css") %>" rel="stylesheet" type="text/css" />
<script src="<%=this.ResolveUrl("../../Plugins/jquery.jqGrid/js/i18n/grid.locale-en.js") %>" type="text/javascript"></script>
<script src="<%=this.ResolveUrl("../../Plugins/jquery.jqGrid/js/jquery.jqGrid.min.js") %>" type="text/javascript"></script>

With our custom C# wrapper, rendering a jqGrid is as easy as using the standard ASP.Net GridView object:

    <exigo:jqGrid ID="ReportGrid" runat="server" Width="818" Height="251" JSONDataSource="jqGridEnrollerReport.aspx?report=data">
        <Settings AutoScaleColumnWidthsOnLoad="true" InitialSortField="CustomerID" InitialSortDirection="Ascending" LoadingText="Loading..." />
        <Columns>
            <exigo:jqGridColumn Name="ID" DataField="CustomerID" ColumnHeader="ID" Width="80" PrimaryKey="true" Frozen="true" Formatter="Integer" />
            <exigo:jqGridColumn Name="FirstName" DataField="Customer.FirstName" ColumnHeader="First Name" Width="80" Frozen="true" />
            <exigo:jqGridColumn Name="LastName" DataField="Customer.LastName" ColumnHeader="Last Name" Width="80" Frozen="true" />
            <exigo:jqGridColumn Name="Level" DataField="Level" ColumnHeader="Level" Width="70" />
            <exigo:jqGridColumn Name="Email" DataField="Customer.Email" ColumnHeader="Email" Width="170" Formatter="Email" />
            <exigo:jqGridColumn Name="Phone" DataField="Customer.Phone" ColumnHeader="Phone" Width="90" />
            <exigo:jqGridColumn Name="CreatedDate" DataField="Customer.CreatedDate" ColumnHeader="Enrollment Date" Width="120" Align="Right" Formatter="Date">
                <Format DateFormat="ShortDate" />
            </exigo:jqGridColumn>
        </Columns>
    </exigo:jqGrid>
    

Rendering Report Data

On the server-side, we have overridden the Page's Render method to handle page requests differently if the query string parameter "report" exists.

    protected override void Render(HtmlTextWriter writer)
    {
        if (!string.IsNullOrEmpty(Request.QueryString["report"]))
        {
            Response.Clear();
            switch (Request.QueryString["report"])
            {
                case "data": RenderReportGridJSON(writer); break;
                default: writer.Write("Invalid Report Key"); break;
            }
            Response.End();
        }
        else
            base.Render(writer);
    }
    

Fetching Report Data

To fetch our report data, we are breaking our OData query into two methods. The ReportDataQuery method prepares our data query, and returns an IQueryable object. This method is responsible for establishing what OData query we are calling from, and applying any sorting and searchin parameters passed from the grid.

    private IQueryable<EnrollerNode> ReportDataQuery()
    {
        // Base Query
        var query = ExigoOData.EnrollerTree
            .Where(c => c.TopCustomerID == customerID)
            .Where(c => c.EnrollerID == customerID);


        // Sorting and Searching
        if (!string.IsNullOrEmpty(ReportGridParameters.SortFieldName)) query = query.OrderBy(ReportGridParameters.SortFieldName, ReportGridParameters.SortDirection.ToString());
        if (!string.IsNullOrEmpty(ReportGridParameters.SearchCriteria)) query = query.Where(ReportGridParameters.SearchFieldName, ReportGridParameters.SearchCriteria);


        // Now that we have a finished query, set up the total record count in our parameters object
        ReportGridParameters.SetTotalRowCount(query.Count());


        // Return the base query
        return query;
    }
    

The FetchReportData method takes the IQueryable object returned from the ReportDataQuery, and selects the desired fields from the query. We also handle pagination here by deciding how many records to skip and take.

    private IQueryable FetchReportData()
    {
        // Define your report's fields
        return ReportDataQuery().Select(c => new
        {
            c.CustomerID,
            c.Customer.FirstName,
            c.Customer.LastName,
            c.Level,
            c.Customer.Email,
            c.Customer.Phone,
            c.Customer.CreatedDate
        })
            .Skip((ReportGridParameters.RequestedPage - 1) * ReportGridParameters.RequestedPageSize)
            .Take(ReportGridParameters.RequestedPageSize);
    }
    

Finally, the RenderReportGridJSON method renders the JSON requested by the jqGrid to the response:

    private void RenderReportGridJSON(HtmlTextWriter writer)
    {
        jqGridDataSource datasource = new jqGridDataSource(ReportGrid, FetchReportData(), ReportGridParameters);
        writer.Write(datasource.ToJSON());
    }
    

Custom LINQ Extensions

We created some custom LINQ extensions to help us sort and filter our OData queries.The following custom extensions help us order our OData queries using strings rather than explicitly-casted objects:

    public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string property, string method)
    {
        string methodName = string.Empty;
        switch (method)
        {
            case "asc": methodName = "OrderBy"; break;
            case "desc": methodName = "OrderByDescending"; break;
        }
        return ApplyOrder<T>(source, property, methodName);
    }
    private static IOrderedQueryable<T> ApplyOrder<T>(IQueryable<T> source, string property, string methodType)
    {
        string[] props = property.Split('.');
        Type type = typeof(T);
        ParameterExpression arg = Expression.Parameter(type, "x");
        Expression expr = arg;
        foreach (string prop in props)
        {
            // use reflection (not ComponentModel) to mirror LINQ
            PropertyInfo pi = type.GetProperty(prop);
            expr = Expression.Property(expr, pi);
            type = pi.PropertyType;
        }
        Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
        LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);

        object result = typeof(Queryable).GetMethods().Single(
                method => method.Name == methodType
                        && method.IsGenericMethodDefinition
                        && method.GetGenericArguments().Length == 2
                        && method.GetParameters().Length == 2)
                .MakeGenericMethod(typeof(T), type)
                .Invoke(null, new object[] { source, lambda });

        return (IOrderedQueryable<T>)result;
    }
    

The following custom extension allows us to filter our OData queries using strings rather than explicitly-casted objects:

    public static IQueryable<T> Where<T>(this IQueryable<T> source, string property, object value)
    {
        var param = Expression.Parameter(typeof(T));

        string[] props = property.Split('.');
        Type type = typeof(T);
        ParameterExpression arg = Expression.Parameter(type, "x");
        Expression leftExpression = arg;
        foreach (string prop in props)
        {
            // use reflection (not ComponentModel) to mirror LINQ
            PropertyInfo pi = type.GetProperty(prop);
            leftExpression = Expression.Property(leftExpression, pi);
            type = pi.PropertyType;
        }

        dynamic typedValue = Convert.ChangeType(value, type);

        BinaryExpression condition = Expression.Equal(leftExpression, Expression.Constant(typedValue));

        return source.Where(Expression.Lambda<Func<T, bool>>(condition, param));
    }