Don Wibier's Blog

September 2014 - Posts

  • BASTA! 2014: Products, presentations, prizes ... PARTY!

    From September 22nd through to September 26th the BASTA! 2014 event will take place in Mainz, Germany and DevExpress is happy to be a Gold Partner.uisuperhero

    We’ll be present at full Euro-strength with Oliver, Rachel, John and myself attending with our awesome booth, stuffed with tons of goodies and giveaways.

    We’ll be able to show you our Winforms, ASP.NET and DevExtreme Javascript suites as well as eXpress AppFramework and more at the booth. Stop by and talk to us about what you are working on and find out if there is a way that we can help you.

    Oliver and I are also giving a number of sessions during the conference. You can check out Oliver's speaker profile and my own for more information on the sessions we'll be delivering.

    If C# development is your area of interest, it is worth bearing in mind that Oliver is the track chair for C# Days. This is a track of sessions running throughout the conference that is designed to cater for the beginner right through to the advanced C# developer looking to be more effective and successful with C#.

    As always, we'll have some exciting giveaways and raffles on the DevExpress booth. Make sure you visit us to find out how to enter. You could go home with one of our product licenses!

    If that isn’t enough, we’ll be sponsoring a games night on Tuesday evening, September 23rd.
    Oliver will be hosting an exciting edition of Who Wants to be a .NET Millionaire! We'll have food, beer, fun and some amazing prizes just waiting to be won.

    SAM_2299

    If you are not going to be at BASTA! 2014 but you do want to join us for the games night, you can join us just for the evening entertainment. Our friends at BASTA! are allowing our invited guests to join the DevExpress Games Night. All you need to do is register so that we can be sure we get enough food and drinks for everyone who joins us.

    REGISTER NOW!

    We can’t wait to meet you next week!

  • Best Practices: The SalesViewer ASP.NET demo

    Many of you know that with the installation of our products, we also install a rich variety of demo applications demonstrating the countless cool features in our controls.

    Those demos are installed in the “%PUBLIC%\Public Documents\DevExpress Demos 14.1” folder.

    Background

    What you might not know is that all of our demo applications are designed and developed as if they are real customer projects with various design patterns and best practices in mind.

    We have several developers working on those demos for weeks and the demos are being tested thoroughly by our test team.

    Since these patterns and best practices could also be useful for you projects as well, I have decided to shine some light under the hood of the SalesViewer demo application.

    SalesViewer

    Data storage

    We are using a MS-SQL Server database to feed the application. This database file is ~/AppData/Sales.mdf. In the context of this application, we are only reading data and the database is designed for the purpose of this demo.

    Its diagram is displayed below:

    clip_image003

    Data Access

    The Data Transfer Object (DTO) pattern is used to retrieve only the necessary fields from the tables by queries. The performance improvement is considerable compared to a full select.

    For example, the DataContext.Product table contains 13 fields and returns values for all fields. The product DTO class is used and the query only returns necessary field values which are 7 in this case.

    public class ProductsProvider : BaseProvider<DataContext.Product> {
           public IEnumerable<Product> GetList() {
               return TryGetResult<IEnumerable<Product>>(() => {
                   return (from p in DataTable
                           select new Product {
                               Id = p.Id,
                               Name = p.Name,
                               BaseCost = p.BaseCost,
                               Description = p.Description,
                               ListPrice = p.ListPrice,
                               UnitsInInventory = p.UnitsInInventory,
                               UnitsInManufacturing = p.UnitsInManufacturing
                           }).OrderBy(p => p.Name).ToList();
        //...
    }
    public class Product {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public double BaseCost { get; set; }
        public double ListPrice { get; set; }
        public int UnitsInInventory { get; set; }
        public int UnitsInManufacturing { get; set; }
    }
    //...
           using(ProductsProvider provider = new ProductsProvider()) {
               ProductsGridView.DataSource = provider.GetList();
               ProductsGridView.DataBind();
    //...

    Caching data

    Some values used in calculations and/or selection criteria like the dates of Today / Yesterday / Last week are static for a day, so we stored them in the HttpContext.Current.Cache where they expire after a day.

    clip_image004

    In this application we also put some smaller datasets, which don’t frequently change, completely in cache like Customer, Product, Location, Contact and Plant.

    You will notice considerable performance increase by using objects stored in cache but in your scenario this might not always be possible.

    Below is the SalesProvider including caching:

    public class SalesProvider : BaseProvider<DataContext.Sale> {
       //...
       public double GetSalesRevenue(DateTime minDate, DateTime maxDate) {
           return TryGetResult<double>(() => {
               return DataTable.Where(s => s.SaleDate >= minDate && s.SaleDate <= maxDate)
                               .Sum(s => s.TotalCost);
           }, useCache: true, keySuffix: string.Format("{0}.{1}", minDate, maxDate));
       }
       // Note: keySuffix is suffix for cache key. 
       // The cache key should be unique and based on table name and keySuffix.
       //...
    }

    Optimizing performance for large datasets

    The Sales page of this demo shows sale totals using an ASPxPivotGrid. The table which is used to feed the Grid contains about 260,000 records. To improve the performance of this page we simulate an ‘OLAP cube’ by aggregating the data on the SQL Server which will return the calculated sums.

    clip_image006

    By using this approach instead of just feed all 260,000 records to the ASPxPivotGrid, we’re able to speed up this operation from around 7 seconds to 0.7 seconds.

    The code used to feed the ASPxPivotGrid and let the aggregates be calcultated by SQL Server looks like this:

    public IEnumerable<Sale> GetList(DateTime minDate, DateTime maxDate) {
        return TryGetResult<IEnumerable<Sale>>(() => {
            return (from s in DataTable
                    where s.SaleDate >= minDate && s.SaleDate <= maxDate
                    group s by new {
                        Year = s.SaleDate.Year,
                        Month = s.SaleDate.Month,
                        ProductName = s.Product.Name
                    } into saleGroup
                    select new {
                        Year = saleGroup.Key.Year,
                        Month = saleGroup.Key.Month,
                        ProductName = saleGroup.Key.ProductName,
                        TotalCost = saleGroup.Sum(x => x.TotalCost)
                    })
                    .ToList()
                    .Select(s => new Sale() {
                        ProductName = s.ProductName,
                        SaleDate = new DateTime(s.Year, s.Month, 1),
                        TotalCost = s.TotalCost
                    }).ToList();
        });
    }

    More information on this can be found in our Code Examples here.

    Making use of the AutoSeries

    Because we use the Data Transfer Object (DTO) pattern, it is possible to return data precisely in a way that we can use the AutoSeries feature in all charts of this demo. This approach reduces the amount of code and makes data binding easy. The charts controls are able to automatically create series based on data.

    What we have done is the following:

    Create a list of ChartDataBase items where every item has a SeriesName property e.g. “Today” series has 6 items with SeriesName = “Today”. Now the Chart Control is able to build the series automatically:

    <dxchartsui:WebChartControl ID="VerticalChartControl" 
            ClientIDMode="AutoID" 
            SeriesDataMember="SeriesName"
            … >

    (The SeriesDataMember is assigned to ChartDataBase.SeriesName)

    More information about AutoSeries and how to set it up can be found here.

    Global site setup, CSS and web-design

    The site uses a masterpage called ~/SiteBase.master and a couple of content pages.

    The masterpage includes a ~/Content/Css/Demo.css for the positioning and styling of the site. It also has 5 placeholders so the content pages can load the functionality in there.

    If you take a close look in your browser to the SalesViewer demo, you might notice that the footer of the pages always stays at the bottom of the page. When the content of a page is smaller as the browser window’s height, the footer will “stick” to the bottom of the browser window. (Check: https://demos.devexpress.com/RWA/SalesViewer/Sales.aspx and resize the browser in height)

    This can be accomplished by using a couple of css tricks:

    If you look at the markup in the masterpage, there is the following html:

    <body>
       <form id="form1" runat="server">
        <div class="contentHolder">...</div>
        <div class="footerHolder">...</div>

    If we apply the following css, the footerHolder will stick to the bottom even if the page length is shorter as the browser width:

    html, body {
        height: 100%;
    }
    form {
        position: relative;
        min-height: 100%;
    }
     
    .footerHolder {
        position: absolute;
        width: 100%;
        bottom: 0;
        height: 80px;
    }
    .contentHolder {
          /* should equal the height property of the .footerHolder class*/
        padding-bottom: 80px; 
    }

    In our case the footer has a height of 80px, but if you need a bigger footer you will need to change it on 2 places; the height of the footer and the padding-bottom of the contentHolder.

    In the code-behind of the masterpage there is one line of code worth mentioning; in the Page_Init method, we call the:

    protected void Page_Init(object sender, EventArgs e) {
       ASPxWebControl.SetIECompatibilityModeEdge(this);
    }

    This call enables better touch support (especially for the MS Surface Tablets) which is better described in our knowledge base here.

    Use your own hierarchy for pages, user controls and optional master page(s)

    If you take a look in the code-behind files of the content pages, you will see that they are not derived from the System.Web.UI.Page but from the BasePage:

    public partial class Products : BasePage {
    
        protected void Page_Load(object sender, EventArgs e) {
    
        //...

    This is also the case for the user-controls in the ~/UserControls folder:

    public partial class ProductDetails : UserControlBase {
        private string CityInfoFormatString = "{0}, {1} {2}";
    
        public void LoadContent(int productId) {
    
        //...

    For the user-controls, we have setup even a small hierarchy with a couple of base classes which derive from each other to support different functionality.

    This allows you to optimize and reduce (duplicate) code and makes maintenance and bug-fixing easier.

    Styling and coloring the charts

    In the SalesViewer demo we have adjusted the colors for the charts to match the design of the site.

    This can be done by using the Chart Designer to create and save a charts palette in a file (.xcp).

    clip_image007

    Such a palette file can be applied to all web chart controls by using the following code:

       // Helpers.cs
    
       private const string PalettePath = "~/Content/SalesViewerPalette.xcp";
    
       private static Palette GetCommonPalette() {
           using(FileStream stream = new FileStream(HttpContext.Current.Server.MapPath(PalettePath), 
                                                    FileMode.Open, FileAccess.Read))
               return DevExpress.XtraCharts.Native.PaletteSerializer.LoadFromStream(stream);
       }
       // ...
       public static void LoadCommonPalette(WebChartControl control) {
           control.PaletteWrappers.Add(new PaletteWrapper(CommonPallete));
           control.PaletteName = CommonPallete.Name;
       }

    Another interesting trick in the WebChartControl we are using in the ~/RevenueBySector.aspx page is that the Yesterday bar is semi-transparent.

    clip_image008

    This can be achieved by setting the alpha value in Color:

    <dxcharts:SideBySideBarSeriesView Color="128, 219, 219, 219">
        <border visibility="False" />
        <fillstyle fillmode="Solid">
        </fillstyle>
    </dxcharts:SideBySideBarSeriesView>

    Using the Bing Map

    On the ~/Customers.aspx page, we have a map displaying where the selected customer is located.

    We have gotten quite some inquiries from customers about generating an ASP.NET Map control like we have for some of our other product lines.

    In a web-page it is actually surprisingly simple to show up the Bing map.

    One thing to keep in mind is that version 6 of the Bing map causes some issues in Firefox so we are forcing to use version 7 which doesn’t appear to have this issue.

    In the aspx (or ascx) we specify the following markup:

    <div id="mapHolder"></div>
    <script type="text/javascript" id="dxss_map">
        CreateMap('<%= Location.Latitude %>', '<%= Location.Longitude %>');
    </script>

     

    Do notice the id of the script block; because it starts with “dxss_” this makes sure this JavaScript is executed after a callback of any of the parent (DevExpress) controls.

    The JavaScript function CreateMap() looks like:

    function CreateMap(lat, long) {
       var mapOptions = {
           credentials: "-- your credentials here --", 
           center: new Microsoft.Maps.Location(lat, long),
           mapTypeId: Microsoft.Maps.MapTypeId.road,
           zoom: 9,
           showScalebar: true,
           showMapTypeSelector: true,
           disableKeyboardInput: true
       }
       map = new Microsoft.Maps.Map(document.getElementById('mapHolder'), mapOptions);
       var center = map.getCenter();
       var pin = new Microsoft.Maps.Pushpin(center); 
       map.entities.push(pin);
       CustomersGridView.Focus();
    }

    Because we want to enable keyboard navigation in the Customer ASPxGridView, we have disabled the keyboard input in the map control.

    If you want to use this JavaScript function yourself, make sure you have credentials from Bing.

    Callback optimization

    Now we have touched some JavaScript with implementing the Bing Map, there is another portion of JavaScript which is quite interesting.

    This demo is heavily making use of callbacks to update only certain portions of the page e.g. when changing the focused row in an ASPxGridView.

    We also have taken care of good keyboard navigation in this demo particularly in the Grids.

    The combination of keyboard support and callbacks while changing focused rows in a grid is often resulting in a huge amount of callbacks where in most circumstances only the last callback will be used.

    An example of this is when a user wants to see the results of the fifth record under the current focused row. With the mouse he/she will click on the exact record while with the keyboard, he/she needs to press the down arrow 5 times (equals 5 callbacks).

    What we have used for this can be found in the ~/Scripts/Helper.js file and is called the CallbackHelper:

    var CallbackHelper = {
        CallbackControlQueue: [],
        CurrentCallbackControl: null,
        UpdateContent: function (callbackControl, args, sender) {
            if (!this.CurrentCallbackControl) {
                this.CurrentCallbackControl = callbackControl;
                callbackControl.EndCallback.RemoveHandler(this.OnEndCallback);
                callbackControl.EndCallback.AddHandler(this.OnEndCallback);
                callbackControl.PerformCallback(args);
            } else
                this.PlaceInQueue(callbackControl, args, this.GetSenderId(sender));
        },
        GetSenderId: function (senderObject) {
            if (senderObject.constructor === String)
                return senderObject;
            return senderObject.name || senderObject.id;
        },
        PlaceInQueue: function (callbackControl, args, sender) {
            var queue = this.CallbackControlQueue;
            for (var i = 0; i < queue.length; i++) {
                if (queue[i].control == callbackControl && queue[i].sender == sender) {
                    queue[i].args = args;
                    return;
                }
            }
            queue.push({ control: callbackControl, args: args, sender: sender });
        },
        OnEndCallback: function (sender) {
            sender.EndCallback.RemoveHandler(CallbackHelper.OnEndCallback);
            CallbackHelper.CurrentCallbackControl = null;
            var queuedPanel = CallbackHelper.CallbackControlQueue.shift();
            if (queuedPanel)
                CallbackHelper.UpdateContent(queuedPanel.control, queuedPanel.args, queuedPanel.sender);
        }
    };

    What happens here is that we create a queue with controls + callbacks which should be processed. Whenever a callback is added to the queue, we will check if this control already exists in the queue and if so, we will replace the callback. If the control is not found we’ll push this control + callback at the end of the queue.

    The queue will be processed whenever any of the controls in the queue raises the EndCallback JavaScript event.

    NOTE: We actually find the callback helper such a useful feature that we are implementing this as out of the box functionality for the upcoming major release

    The SparkLine control

    There is one control visible on every page which is the Sparkline control right above the footer of the page:

    clip_image010

    This is an interesting control since as you might have noticed, it is not straight out of the DevExpress toolbox.

    The thing is that you’re able to slide the beginning and end knobs of the range, which causes the charts and other controls on the page to change.

    For this we have used an ASPxTrackBar because it exactly behaves as we want.

    The SparkLine above the ASPxTrackBar is not a WebChartControl control but an ASPxImage instead. The ImageUrl will be set in the Code-Behind and is a runtime generated image.

    The ~/Chart.aspx contains the WebChartControl to create the chart but instead of rendering html, this page is outputting binary content which is an image containing the SparkLine.

    The reason for this construction is (obviously) performance.

    If we should have used the chart control instead of the image, this chart is calculated on every request (also callbacks) which will take time. This chart will not change during the day so it is a waste of CPU time to recalculate the values on every request.

    The beauty of this approach is that we can cache the image (which is created in the ~/Chart.aspx) and serve it out of the cache for the duration of this day (which is 86400 milliseconds).

    It will be cached by specifying the OutputCache directive in the ~/Chart.aspx:

    <%@ OutputCache Duration="86400" VaryByParam="start;end;" %>

    Conclusion

    As mentioned in the introduction of this post; we have built all of our demos as if they are production projects and there are lots of interesting bits and pieces which can be found when going through the demos.

    I hope you picked up a couple of tricks in the SalesViewer demo and do let me know if you did.

    If you find this interesting material, let me know as well, and we might put some other demo projects in the spotlight as well.

LIVE CHAT

Chat is one of the many ways you can contact members of the DevExpress Team.
We are available Monday-Friday between 7:30am and 4:30pm Pacific Time.

If you need additional product information, write to us at info@devexpress.com or call us at +1 (818) 844-3383

FOLLOW US

DevExpress engineers feature-complete Presentation Controls, IDE Productivity Tools, Business Application Frameworks, and Reporting Systems for Visual Studio, along with high-performance HTML JS Mobile Frameworks for developers targeting iOS, Android and Windows Phone. Whether using WPF, ASP.NET, WinForms, HTML5 or Windows 10, DevExpress tools help you build and deliver your best in the shortest time possible.

Copyright © 1998-2017 Developer Express Inc.
All trademarks or registered trademarks are property of their respective owners