Thursday, 10 September 2015

Automatic Totals on your List page in Dynamic Data

So the idea here is add an attribute to your table (TotalsAttribute in this case) and then have a total appear on your list page.

Note: Note this will only work on Numeric columns int, Decimal, float etc.

Figure 1 shows what we are going to achieve, the columns here don’t make sense to total but it’s only an example  for real i would add a computed column that multiplied the unit cost with the qty in stock and total that but here it’s just to show how it works with decimal and int values.

finished-page

Figure 1 – Footer row with totals.

The Attribute

We will need an attribute because we want this to happen automatically and not have to create a custom page, for this I have decided to add a class level attribute called TotalsAttribute you can see the code in Listing 1.

[AttributeUsage(AttributeTargets.Class)]
public class TotalsAttribute : Attribute
{
public String[] Columns { get; set; }

public TotalsAttribute()
{
Columns = new String[0];
}

public TotalsAttribute(params String[] columns)
{
Columns = columns;
}
}

Listing 1 – TotalsAttribute

All we are doing here is keeping an array of column names that we want totals on, most of the time it will be single column, but it’s nice to have the option of multiple.

[Totals("UnitPrice", "UnitsInStock", "UnitsOnOrder")]
[MetadataType(typeof(Product.Metadata))]
public partial class Product
{
internal sealed class Metadata
{
public Int32 ProductID { get; set; }

public String ProductName { get; set; }

[DataType(DataType.Currency)]
public Nullable<int> UnitPrice { get; set; }

public Nullable<int> UnitsInStock { get; set; }

//... other column removed for simplicity
}
}

Listing 2 – example of attribute in use.

In the example [Totals("UnitPrice", "UnitsInStock", "UnitsOnOrder")] in Listing 2 we are telling the system that we want totals on three columns “UnitPrice”, “UnitsInStock”, “UnitsOnOrder”.

The Custom Code in the List page

There are two things we need in the page first we will need to check if there are totals on this table and if so wire it all up. In Listing 3 we have all the code we need to test if there are totals for the current Table and if so wire up the Row DataBound even handler which you can see in Listing 4.

In Listing 3 we single get the attribute from the table and test to see if what we got is not null, if not we can the wire up the event handler. But also we need to turn on the footer.

!Important: I found this bit missing from most of the articles I found whilst searching for examples of how to do this; everything works without this, it just doesn't display. The bit you must have is GridView1.ShowFooter = true;
Note: I am using some custom extension methods to get the attribute, these are in the root of the application and the file is called “AttributeExtensionMethods.cs”
public partial class List : System.Web.UI.Page
{
    protected TotalsAttribute totalsAttribute;
    protected MetaTable table;

    protected void Page_Init(object sender, EventArgs e)
    {
        table = DynamicDataRouteHandler.GetRequestMetaTable(Context);
        GridView1.SetMetaTable(table, table.GetColumnValuesFromRoute(Context));
        GridDataSource.EntityTypeFilter = table.EntityType.Name;

        // get the attribute
        totalsAttribute = table.GetAttribute<TotalsAttribute>();

        // if the attribute is not null then we have some totals
        if (totalsAttribute != null && totalsAttribute.Columns.Count() > 0)
        {
            // show the footer
            GridView1.ShowFooter = true;
            // wire up the row data bound event
            GridView1.RowDataBound += OnRowDataBound;
        }
    }
// rest of code behind removed for simplicity

Listing 3 – testing if we have any totals for this table.

Now all we need the workhorse code the stuff that is going to total up and then display the totals in the footer. See Listing 4 I have tried to put a lot of comments in there to help but here’s a brief explanation of what it does:

The code in the event handler is split into two sections one for the DataRow and one for the Footer you can see there are encased in two if statements. Also note we have a global variable “totals” this is used to keep a total of each column we are totalling and is a dictionary of Decimal values.

The DataRow

Here we iterate through the totals columns from the attribute and sum up each one, you will notice that I am testing if the column is a valid column by checking the metadata to see if it is an int or an floating point this stops us having a nasty error.

// NOTE: if you are using a column generator (IAutoFieldGenerator) 
// the this may not work if it re-orders the displayed columns
protected Dictionary<String, Decimal> totals = new Dictionary<String, Decimal>();
protected void OnRowDataBound(object sender, GridViewRowEventArgs e)
{
    // this will only be wired up and called if there are totals so we don't need to test.
    // Get a List<String> of column names if the order that they appear in the GridView1
    var displayedColumns = table.GetScaffoldColumns(DataBoundControlMode.ReadOnly, ContainerType.List).ToList();

    // if this is a data row get the totals
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        foreach (var column in totalsAttribute.Columns)
        {
            // get the MetaColumn
            var metaColumn = displayedColumns.First(c => c.Name == column);

            // check this column is a valid column to total i.e. int Decimal, float etc.
            if (metaColumn.IsFloatingPoint || metaColumn.IsInteger)
            {
                // initialize variable if not present
                if (!totals.ContainsKey(column))
                    totals.Add(column, 0);

                // add to total
                totals[column] += Convert.ToDecimal(DataBinder.Eval(e.Row.DataItem, column));
            }
        }
    }

    // if we are on the footer row render the totals.
    if (e.Row.RowType == DataControlRowType.Footer)
    {

        // set total description name
        e.Row.Cells[0].Text = "Total:";
        // add alignment style
        e.Row.Cells[0].CssClass = "right";

        foreach (var column in totalsAttribute.Columns)
        {
            // get index of column plus offset of 1 for the command button column
            var index = displayedColumns.FindIndex(c => c.Name == column) + 1;

            var metaColumn = displayedColumns.First(c => c.Name == column);
            if (metaColumn.IsFloatingPoint || metaColumn.IsInteger)
            {
                // for the Footer, display the running totals
                e.Row.Cells[index].Text = metaColumn.FormatValue(totals[column]);

                // add alignment style
                e.Row.Cells[index].CssClass = "numeric";
            }
        }
    }
}

Listing 4 – OnRowDataBound event handler and global totals variable.

Finally the second section the Footer we we simply render the totals to the footer

Note: The plus 1 I am adding to the index, is to account for the Command column with the Edit buttons etc.

Sample Code

Tuesday, 2 June 2015

Asp.Net Identity for Web Forms Introduction

I am starting a new series on the new Asp.Net Identity there are plenty of resources for MVC etc. but little for Web Forms so i thought I’d do a little investigation and document it here.

I am using Visual Studio 2013 Ultimate, remember Visual Studio 2013 Community is the new free edition of Visual Studio 2013 it is equivalent to Pro but has been stripped down to make it a smaller download, but I believe most of the bits that have been removed to create the community edition can be added back in, see Connect(); Microsoft Visual Studio vNext & Azure on Channel9.

Overview of the new Project Templates

Requirements .Net 4.5 and above, if you select .Net 4 as your framework type from the new Project dialog you will not see the One ASP.Net dialog and you will get the classic Membership added and this is not what you want as we are talking about the new Identity system.

web-forms-dot-net-4-web-application

Add new project template here for comparison.

Figure 1 – .Net 4 Web Application Project

Now we need to select the project template with a .Net framework of 4.5 as a minimum.

selecting-the-project-template

Figure 2 – Selecting the project template

one-asp-net-dialog

Figure 3 – The One Asp.Net dialog

You will get the One Asp.net dialog from this you can change the the authentication type.

change-authentication-dialog

Figure 4 – Change Authentication dialog

Let’s examine the four options offered here:

No Authentication

This is simple enough no authentication will be added to the project, it’s worth noting that this is not the default so you will need to select this option of you want a project without authentication.

change-authentication-dialog-option-2

Figure 5 – Option 1

Individual User Accounts

This option is equivalent to the old Membership system default,

change-authentication-dialog-option-1

Figure 6 – Option 2

Organizational Accounts

This is an area of big improvement over the old Membership system allowing us greater flexibility, all these options are using some form of Active Directory Federation Services Overview i.e. ADFS, Azure Active Directory. but none connect directly with Active Directory if I understand this correctly, this is probably a good thing as the issue with AD is that it’s slow so if you are looking for Roles membership in AD you would need to cache as the delays can become very long.

change-authentication-dialog-option-3a

Figure 7 – Option 3

change-authentication-dialog-option-3b

Figure 8 – More Options

  1. Choose this option to connect to your Microsoft Azure Active Directory tenet.
  2. Enter the domain name of you Microsoft Azure Active Directory tenet.
  3. Indicate what access the application will have to the directory. For an application that queries the directory using the Directory Graph API, choose an option that enables reading or writing.
  4. Enter a unique URI to identify this application. (If you leave it blank, a URI will be created automatically by appending the project name to the Microsoft Azure Active Directory domain with a number if needed to make it unique.)

change-authentication-dialog-option-3c

Figure 9 – On-Premises option for Active Directory

  1. Choose On-Premises if the organization manages user accounts by using Windows Server Active Directory or ADFS and you don’t want to use Microsoft Azure Active Directory.
  2. The metadata document contains the coordinates of the authority. Your application will use those coordinates to drive the web sign on flow.
  3. Provide a unique URI that Windows Server Active Directory can use to identify this app.

Windows Authentication

This is the same as the original Windows Authentication, in fact if you look at Figure 11 you can see the changes to the web.config are the same as the always have been for “Windows Authentication”.

change-authentication-dialog-option-4

Figure 10 – Option 4

option-4-web-config

Figure 11 – web.config authentication changes

So that’s the introduction done next we will be adding Identity to a existing WebForms application.

Friday, 17 April 2015

Register to attend the Microsoft MVP Virtual Conference

Hi All – I wanted to let you know about a great free event that Microsoft and the MVPs are putting on, May 14th & 15th.  Join Microsoft MVPs from the Americas’ region as they share their knowledge and real-world expertise during a free event, the MVP Virtual Conference.

The MVP Virtual Conference will showcase 95 sessions of content for IT Pros, Developers and Consumer experts designed to help you navigate life in a mobile-first, cloud-first world.  Microsoft’s Corporate Vice President of Developer Platform, Steve Guggenheimer, will be on hand to deliver the opening Key Note Address.

Why attend MVP V-Conf? The conference will have 5 tracks, IT Pro English, Dev English, Consumer English, Portuguese mixed sessions & Spanish mixed sessions, there is something for everyone! Learn from the best and brightest MVPs in the tech world today and develop some great skills!

Be sure to register quickly to hold your spot and tell your friends & colleagues.

The conference will be widely covered on social media, you can join the conversation by following @MVPAward and using the hashtag #MVPvConf.

Register now and feel the power of community!

clip_image001