Don Wibier's Blog
  • Best practices: Secure ASP.NET Web with DevExpress: A2 - Broken Authentication and Session Management

    I’ve had a lot of experience in dealing with writing secure ASP.NET websites. I’ve created a custom CMS framework using DevExpress ASP.NET and been through several security audits with this project. I’d like to share my experience with you in using best practices to make your websites secure. In this series of blog posts, I'll explain common web security topics and how you can make sure your websites are secure.

    Open Web Application Security Project

    OWASP is one of the most well-known organizations which focuses on improving security of software. They have published a checklist with common design errors and issues. The OWASP Top Ten represents a broad consensus about what the most critical web application security flaws are.

    Let’s see what this type of attack is about and how to fix it:

    A2 - Broken Authentication and Session Management

    Session variables are great for maintaining a state or keeping some personalized information at hand in a web application. These variables are stored on the server and are globally accessible throughout the application. After a certain time of inactivity of a user, they are also cleaned up automatically.

    Every user has its own instances of these variables so the data is not exposed to any other users. 

    Hackers, however, try to exploit any weakness to gain access to other user’s session data.

    Lets see how the ASP.NET framework handles this so we can see if there are ways to tamper with this.

    In general, there are roughly 3 ways to identify a session:

    • Specifying a sessionID as part of the URL or QueryString parameter with all URL’s and requests to the web-server.
    • By submitting a (hidden) FormField with some sessionID with all requests
    • Storing a sessionID in a cookie which will be send over with all requests

    By default, ASP.NET uses the last method since it has the least side affects, but for the test we will tell the ASP.NET framework to use the first method by specifying the following in the web.config of a new empty project:

    <system.web>
      <sessionState cookieless="true" />
    </system.web>

    Next we create a simple default.aspx page which looks like this:

    <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication8.Default" %>
    
    <!DOCTYPE html>
    
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title></title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
              <asp:Panel runat="server" ID="pnlAnon">
                    <fieldset>
                         <asp:Label runat="server" ID="lbUsername" AssociatedControlID="tbUsername" Text="Username" /><br />
                         <asp:TextBox runat="server" ID="tbUsername" /><br />
                         <asp:Label runat="server" ID="lbPassword" AssociatedControlID="tbPassword" Text="Password" /><br />
                         <asp:TextBox runat="server" ID="tbPassword" /><br />
                    </fieldset>
                    <asp:Button runat="server" ID="btLogin" Text="Login" OnClick="btLogin_Click" />
              </asp:Panel>
              <asp:Panel runat="server" ID="pnlLoggedIn" Visible="false">
                    <asp:Label runat="server" ID="lbWelcome" /><br />
                    <asp:Button runat="server" ID="btLogout" Text="Logout" OnClick="btLogout_Click" />
              </asp:Panel>
        </div>
        </form>
    </body>
    </html>

    With the following code-behind:

    namespace WebApplication8
    {
         public partial class Default : System.Web.UI.Page
         {
              protected void Page_Load(object sender, EventArgs e)
              {
                    if ((!IsPostBack) && (!IsCallback))
                    {
                         UpdateControls();
                    }
              }
              protected void UpdateControls()
              {
                    pnlAnon.Visible = String.IsNullOrEmpty((string)Session["Username"]);
                    pnlLoggedIn.Visible = !pnlAnon.Visible;
                    if (pnlLoggedIn.Visible)
                         lbWelcome.Text = String.Format("Welcome {0}!", Session["Username"]);
    
              }
              protected void btLogin_Click(object sender, EventArgs e)
              {
                    Session["Username"] = tbUsername.Text;
                    UpdateControls();
                    Response.Redirect("~/");
              }
    
              protected void btLogout_Click(object sender, EventArgs e)
              {
                    Session.Remove("Username");
                    UpdateControls();
                    Response.Redirect("~/");
              }
         }
    }

    As you can see, the code is pretty straight forward; when no session variable is present, we show the pnlAnon which allows us to enter some username and password.

    When we submit,  the entered username is placed in the session variable and the controls are updated. If you run this in your web-browser, you will see the following:

    image

    Because of the cookieless setting, you see a mangled URL being produced by the ASP.NET framework which inserts the sessionID into the URL.

    Now we fill something in and hit Login:

    image

    Lets copy the URL in Internet Explorer:

    image

    As you can see both browsers are now working in the same session.

    When removing the web.config setting, ASP.NET will store this information in a cookie named ASP.NET_SessionId and it is created if it didn’t already exists. The cookie holds the same value as the URL portion in the example above, and when you copy the cookie value from one browser to another, you will have the same effect.

    This is a potential opening for a hacker which is called Session Fixation. For this to happen a hacker needs to setup a session first and next try to trick a user to go to the site by providing a hyperlink (in a fake e-mail from your bank for example). If this succeeds, the user will work in the session of the hacker. If you want to read more about these kind of attacks, there is a good article here.

    If you have developed your own login mechanism in your site which relies on Session variables like the example above, you can imagine that this is a potential security issue.

    By using the membership API which is available in ASP.NET from version 2 and up, you have reduced these kind of threads for a great deal. The membership API gives you all functionality you would expect for handling users and logins.

    The standard configuration of the Membership API uses its own data store for persisting user details, but if you have another data store to keep your users, consider creating your own Membership provider

    public class MyMembershipProvider : MembershipProvider
    {
         public override bool ValidateUser(string username, string password)
         {
               //...
         }
         public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
         {
               //...
         }
         public override MembershipUser GetUser(string username, bool userIsOnline)
         {
               //...
         }
         public override bool ValidateUser(string username, string password)
         {
               //...
         }
         // override other methods you need
    }

    You will need to configure it in your web.config:

    <system.web>
        <membership defaultProvider="MyMembershipProvider" userIsOnlineTimeWindow="10">
          <providers>
            <clear />
            <add name="MyMembershipProvider" type="MyNamespace.MyMembershipProvider, MyAssembly" 
                    enablePasswordReset="true" requiresQuestionAndAnswer="false" applicationName="/" />
          </providers>
        </membership>

    Optionally you could create a Role provider as well and configure it accordingly.

    The membership API uses an authentication ticket which will also be stored in a cookie but it is much harder to tamper with since it holds some additional information. A nice side-effect of using the membership API is that all controls in the toolbox like the LoginControl and LoginStatus control can be used without additional coding.

    So by using the membership API, we have reduced the risk of a hacker getting your sensitive information, and we can be pretty sure about the identity of a user but we still have the Session Fixation issue. There are several counter measures you can take:

    Make sure the business logic don’t depend on Session variables and use as less as possible. In several of our demo projects, we use session variables but only for persisting some status in between callbacks and we also make sure we initialize them first before reading them.

    If the above is not possible, you could introduce an extra cookie e.g. Auth which holds a unique generated value which will also be stored in a session variable as soon as a user logs in. Next with every page load, you can check if the cookie value is equal to the session variable.

    A login method for this could look like this:

    protected void btLogin_Click(object sender, EventArgs e)
    {
          if (Membership.ValidateUser(tbUsername.Text, tbPassword.Text))
          {
               string guid = Guid.NewGuid().ToString();
               Session["Auth"] = guid;
               // now create a new cookie with this guid value
               Response.Cookies.Add(new HttpCookie("Auth", guid));
          }
          else 
               throw new HttpException("Invalid username or password")
    }

    And a logout method would look like:

    protected void btLogout_Click(object sender, EventArgs e)
    {
          Session.Clear();
          Session.Abandon();
          Session.RemoveAll();
          if (Request.Cookies["ASP.NET_SessionId"] != null)
          {
               Response.Cookies["ASP.NET_SessionId"].Value = string.Empty;
               Response.Cookies["ASP.NET_SessionId"].Expires = DateTime.Now.AddMonths(-20);
          }
    
          if (Request.Cookies["Auth"] != null)
          {
               Response.Cookies["Auth"].Value = string.Empty;
               Response.Cookies["Auth"].Expires = DateTime.Now.AddMonths(-20);
          }
    }

    In every page load you could then place the following code:

    string sessionVal = (string)Session["Auth"];
    string cookieVal = Request.Cookies["Auth"] != null ? Request.Cookies["Auth"].Value : "";
    if (!Request.IsAuthenticated || String.IsNullOrEmpty(sessionVal) || 
          String.IsNullOrEmpty(cookieVal) || (sessionVal != cookieVal))
    {
       // NOT Logged in (or tampered) redirect to the login page
    }
    //else
       //Logged in so everything is ok

    For this to work effectively, you will need to give the user some incentive to use the Logout button on your site so you can cleanup the session yourself, and empty the ASP.NET_sessionId cookie so the user will receive a new one id on his next visit to the site:

    Sessions expire after a certain amount of inactivity of the user. This time is by default 20 minutes. If possible, make it even shorter by setting the following web.config setting:

    <system.web>
      <sessionState timeout="10" />
    </system.web>

    Another way of preventing Session Fixation is by changing the sessionID with every request in the cookie. This should be tested though since this might cause problems when embedding Java Applets or ActiveX controls in your page which also rely on the user session.

    Alternatively, you could build your own SessionStateProvider.

    As you can see, protecting your data is a serious task, but also consider the following points to reduce other types of hack attempts:

    • Install an SSL certificate on your web-site and make sure at least the login process runs on SSL
    • Try to assign a new sessionID to the user upon login
    • Store passwords encrypted in the database
    • Do not use home grown authentication mechanisms
    • Keep session time-outs short
    • Do not send one e-mail which contains both username and password (it is plain text after all)

    DevExpress is Secure

    DevExpress takes ASP.NET security as a top priority. We constantly test our tools and if by chance a flaw appears then we’ll notify you immediately on a fix/workaround/etc.

    Leave a comment below with your thoughts on ASP.NET security.

  • WinForms Data Grid: Excel Inspired Conditional Formatting (Coming soon in v14.2)

    One of the many features we’ve added to our Data Grid and Tree List controls is Excel inspired conditional formatting of individual data cells. This feature allows your end-user not only to sort or group the data inside the grid, but also to visualize selected cells, rows with data bars, icons and predefined or custom appearance schemes. 

    WinForms-Grid-ExcelFormatting

    We have built-in support for the following common comparison rules:

    • Top / Bottom
    • Above / Below Average
    • Greater / Less Than
    • Value List
    • Unique / Duplicate

    You can obviously define even more (complex) conditions, in code yourself. By using the design-time facilities, you can define style conditions without writing a single line of code!

    Conditional formatting rules allow you to modify cell backgrounds, text-colors and font-settings and can be applied to either a single cell or an entire row. In case of multiple formatting conditions apply on the same cell, we’ll just combine these conditions for you (unless specified otherwise).

    We have added an extra menu item to the grid's column context menu to allow the end-user to apply the conditional formatting.

    WinForms-Grid-ExcelFormatting-GridMenu

    Stay tuned for more exiting new features in our v14.2 release!

  • Best practices: Secure ASP.NET Web with DevExpress: A1- Injection

    I’ve had a lot of experience in dealing with writing secure ASP.NET websites. I’ve created a custom CMS framework using DevExpress ASP.NET and been through several security audits with this project. I’d like to share my experience with you in using best practices to make your websites secure. In this series of blog posts I’ll write about security so you can take your advantage of it before you start to code.

    Open Web Application Security Project

    OWASP is one of the most well-known organizations which focuses on improving security of software. They have published a checklist with common design errors and issues. The OWASP Top Ten represents a broad consensus about what the most critical web application security flaws are.

    Let’s see what this type of attack is about and how to fix it:

    A1 - Injection

    This kind of attack allows a hacker to send custom server-side commands to your web-server by manipulating the url or it’s query strings, or manipulate data entered in forms which are being posted back to the server.

    Let me give you a small example on how this works on the AdventureWorks database:

    Suppose we want to display a filtered grid with products, where the filter is specified by a query string parameter:

    protected void Page_Load(object sender, EventArgs e)
    {
          if (!IsPostBack)
          {
               using (SqlConnection conn = new SqlConnection(
                        ConfigurationManager.ConnectionStrings["AdventureWorks2012ConnectionString"].ConnectionString))
               {
                    string sqlText = String.Format(@"SELECT ProductID, ProductNumber, Name 
                                                     FROM [Production].[Product] 
                                                     WHERE Name LIKE '%{0}%'", 
                                                   Request.QueryString["q"]);
                    using (SqlCommand cmd = new SqlCommand(sqlText, conn))
                    {
                          conn.Open();
                          SqlDataReader rd = cmd.ExecuteReader();
                          GridView1.DataSource = rd;
                          GridView1.DataBind();
                    }
               }
          }
    }

    This al looks pretty nice and when we specify a url like: Default.aspx?q=Crank, the result will be like:

    clip_image001

    But what if we specify:

    Default.aspx?q=Crank ' union all select 0,'',TABLE_SCHEMA%2B'.'%2BTABLE_NAME from INFORMATION_SCHEMA.TABLES –

    clip_image002

    Wow! Look at that! It displays all tables in the database and that is how an injection attack works.

    Because the constructed SQL is being generated runtime with no checking at all, I was able to change the entire sql statement by injecting some obscure code and get sensitive information about the system for subsequent hacking attempts.

    The statement was altered from:

    SELECT ProductID, ProductNumber, Name 
    FROM [Production].[Product] 
    WHERE Name like '%Crank%'

    To:

    SELECT ProductID, ProductNumber, Name FROM [Production].[Product] 
    WHERE Name like '%Crank' union all 
    select 0, '', TABLE_SCHEMA + '.' + TABLE_NAME from INFORMATION_SCHEMA.TABLES --%'

    Perhaps you’re thinking: “Yeah but I have a database with different fields, and you don’t know the amount of fields I am selecting for the union etc.”

    Those thoughts are really of no concern for a hacker. He can give it a shot by spending 30 minutes every day for a week and he will sort it out.

    Remember: Hacking is not a quick job but it requires time and patience.

    So can this be fixed ?

    Sure, this can be fixed in several ways:

    Weak solution: Remove quotes

    We could strip out all single quotes by calling:

    Request.QueryString["q"].Replace("'", "")

    But this could cause culture related issues.

    Better solution: Parameterized Queries

    protected void Page_Load(object sender, EventArgs e)
    {
          if (!IsPostBack)
          {
               using (SqlConnection conn = new SqlConnection(
                        ConfigurationManager.ConnectionStrings["AdventureWorks2012ConnectionString"].ConnectionString))
               {
                    string sqlText = "SELECT ProductID, ProductNumber, Name 
                                      FROM [Production].[Product]     
                                      WHERE Name LIKE @SearchText";                                
                    using (SqlCommand cmd = new SqlCommand(sqlText, conn))
                    {
                          cmd.Parameters.Add(new SqlParameter("@SearchText", 
                                                String.Format("%{0}%", 
                                                    Request.QueryString["q"])));
                          conn.Open();
                          SqlDataReader rd = cmd.ExecuteReader();
                          GridView1.DataSource = rd;
                          GridView1.DataBind();
                    }
               }
          }
    }

    If you would try the same trick again, you will see that nothing is found since my attempt with the union is now part of the LIKE parameter.

    An additional bonus is that you will gain a bit of performance since MS-SQL Server is now capable of caching the SQL Statement internally, so it doesn’t need to parse your statement with every request.

    Alternative better solution: Use an ORM tool

    Another way of avoiding SQL injection is by using some ORM (Object Relational Mapping) tool like the Entity Frame Work or eXpress Persistent Objects.

    Because you are not working with SQL directly the ORM tool will construct the proper queries with parameters for you.

    DevExpress is Secure

    DevExpress takes ASP.NET security as a top priority. We constantly test our tools and if by chance a flaw appears then we’ll notify you immediately on a fix/workaround/etc.

    Leave a comment below with your thoughts on ASP.NET security.

  • BASTA!2014: Recap

    Last week, Oliver, John, Rachel and I were in Mainz Germany at the BASTA!2014 Event.

    We have talked with a lot of people and we showed them demos of our tools and products.

    clip_image002

    clip_image004

    clip_image006

    The sessions Oliver and I gave were visited well

    clip_image008

    And Oliver appeared to be quite professional Quiz Master on our Mixer Party with “Who wants to be a dotNET Millionaire”. Besides the amount of fun we had, it turned out to be a hit as well!

    clip_image010

    For everybody who was at Basta!2014, I hope you enjoyed it as much as we did and for everybody who couldn’t make it, we’ll be at Microsoft TechEd in Barcelona end of October!

  • 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.

  • Join us at MobileTech Conference 2014 in Berlin

    Next week from September 1st to September 3rd, I will be in Berlin Germany at MobileTech Conference 2014 together with John Martin.

    DevExpress is silver sponsor of this event which means we will be onsite for both days of the Expo Reception with our really cool booth. We will have some stylish U.I. Superhero t-shirts to giveaway as well as some other goodies.

    You will be able to find me on the booth giving DevExtreme demos all day, and I am also doing a session called “Your enterprise database on a mobile device – Yes !”, where I will take an existing database and create a multichannel app running on it.

    Make sure you visit John and I on the DevExpress booth because we will be running daily raffles and it could be you walking away with a free DevExtreme license!

    For those of you who aren't lucky enough to bag a free license in our raffle, speak to us about our exclusive MobileTech promotional discount for all conference attendees.

    We’re looking forward meeting you!

  • Developer Week Nuremburg Germany

    Last week I was with John Martin at the Developer Week Nuremburg, Germany.

    WP_20140713_16_33_14_Pro

    On Monday morning, our own Oliver Sturm joined us to perform the opening keynote of this event with a packed room.

    WP_20140714_10_05_06_Pro

    I have spoken with lots of people with interesting stories and questions and I have given dozens of demos.

    Demoing

    Also John was busy with our existing and potential new clients.

    JohnBusy

    We hope to meet you too on one of our upcoming events.

    Oliver-and-Don

  • Video: Creating an ASP.NET Blog Engine – Part 3

    The last episode of my webinar series called “Creating an ASP.NET Blog Engine” is now available on YouTube:

    In this episode, I have picked up the pieces from episode 1 and episode 2, and everything is now working together. I even introduced the ASPxFormLayout which helped us build good-looking forms with its powerful designer.

    I have also demonstrated a number of different methods you can use to select data from the database through eXpress Persistent Objects and I showed how you can execute various CRUD operations on the database.

    There were some really cool new features I enjoyed demonstrating like:

    Finally I showed you how to reuse user controls and as much code as possible and I even put in some smart JavaScript code which utilizes the rich client-side API of the DevExpress controls to perform clever callbacks and partial updates on the page.

    Background

    When starting a new project, I always try to setup some architecture where I define and develop smaller building blocks which can be stacked up in different forms and shapes. This results in less code and errors.  If you take a good look in the source of the Blog Engine project, you will notice that there is a (mini) architecture in place which allows you to expand its functionality quite easily.

    The rich and well-documented client-side API of our controls allows us to adjust and expand functionality and enrich the user interface of your applications as well.

    Why create a Blog Engine?

    I have picked the Blog Engine because it is a really small abstract of what I have been building over the last couple of years which is a full blown content management system CMS2GO.
    It covers a broad range of web development from data model to implementing a web-design including the use of some really cool DevExpress ASP.NET controls like the ASPxImageGallery, ASPxPopupControl and ASPxEditors.

    In case you missed the previous episodes, you can find episode 1 here, and episode 2 here.

    If you want to test-drive the project yourself, please download the source-code here.

    Obviously there are dozens of features which could be build into this little blog engine but time prevented us from covering them all. If you enjoyed this series and want to see some more functionality built in, please let me know by replying on this post, and maybe I’ll do an additional webinar.

    I also like to hear (and see) about any awesome features you might try adding to this tool yourself!

  • Video: Creating an ASP.NET Blog Engine – Part 2

    The second episode of my webinar series called “Creating an ASP.NET Blog Engine” is now available on YouTube:

    In this episode, I demonstrated what responsive web-design is and explained how to setup a responsive ASP.NET Master page with the Skeleton CSS boilerplate. I also showed you how to use the ASPxMenu and the ASPxPopupControl in a responsive site.

    Throughout this webinar I coded some CSS to get a basic web-design into our blog engine including CSS media queries which allowed us to define different looks on various devices.

    Background

    As a developer, my interest is in the business logic behind a site rather than in its web-design. Having said that, of course I like it if my projects look good!

    Boilerplates and frameworks like Skeleton and Bootstrap give a developer the power to setup webpages that look good on any device.

    All of our ASP.NET controls can be used in conjunction with these boilerplates and frameworks. Some controls work without any coding at all, and some of them require a bit of coding depending on the (desired) functionality.

    Why create a Blog Engine?

    I have picked the Blog Engine because it is a really small abstract of what I have been building over the last couple of years which is a full blown content management system CMS2GO.
    It covers a broad range of web development from data model to implementing a web-design including the use of some really cool DevExpress ASP.NET controls like the ASPxImageGallery, ASPxPopupControl and ASPxEditors.

    Make sure you register for episode 3 of 3. I will bind together what we covered in episodes 1 and 2 and show everything working together. I will introduce the ASPxFormLayout and ASPxImageGallery controls, I’ll show you how to select data from our persistent objects and you will see how to insert and update data through eXpress Persistent Objects.

    In case you missed episode 1, you can find it here.

1 2 3 4 5 6 7
8
9
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