terug naar het overzicht

Excel-like filters for Kendo UI Grid

door Sander 22-3-2012

In my previous post I showed how to set up the grid so that all data operations occur on the server. In this post, I’ll build on that a little further to enable filtering like Excel does.

Here is the default method of filtering:

old

And this is what I want:

new

Excuse the blurs, that is ‘real’ testdata Smile

First we need to set up the little screens that will replace the default filtering. I used a div with some Razor to create a list of checkboxes:

<div id="customers" class="filterDropdown">
        @foreach (var c in ViewBag.CustomerFilterSource)
        {
            var customer = "Customers_" + c;
            <input type="checkbox" id="@customer" name="CustomerName" value="@c" checked="checked" />
            <label for="@customer">@c</label>
            <br />
        }
        <input type="button" id="applyCustomerFilter" value="Apply" />
    </div>

The data comes from a collection in the ViewBag that is loaded when the page is served.

Next thing is getting rid of the default filter window. It took me a while to figure this one out but it turned out to be really simple. I used jQuery to locate all the little filter buttons in the header and remove their click events and adding my own:

$("#reportGrid").find(".k-grid-filter").unbind("click").click(function () {
            var parent = $(this).parent();
            var filterList;
            switch (parent.attr("data-field")) {
                case "CustomerName":
                    filterList = $("#customers");
                    break;
                case "CertificateNumber":
                    filterList = $("#certificates");
                    break;
                case "ReportDate":
                    filterList = $("#reportdate");
                    break;
                case "ReportType":
                    filterList = $("#reporttypes");
                    break;
            }
 
            var offset = $(this).offset();
            filterList.css('top', offset.top + $(this).height()).css('left', offset.left + $(this).width()).slideDown();
            return false;
        });

The filter buttons are easy to locate as they have a specific CSS class applied to them. In the switch-case I determine which popup I want to show when the button is clicked. In the last three lines I place the popup in the same spot as the filter button so that it rolls out from where the user clicked.

Some code for the Apply buttons: (just showing one here)

$("#reportdate").hide();
        $("#applyReportDateFilter").click(function () {
            setDsFilter(generateDsFilter());
            $("#reportdate").slideUp();
        })

The popup is hidden since we only want to see it when one of the filterbuttons is clicked. The click event takes care of resetting the datasource filters and hiding the popup.

Now we get to the fun part! To keep the existing filter functionality of the grid’s datasource intact, we need to generate the filters ourselves. That way, the controller code from my previous post can be left unchanged to pick up the filters that the grid posts.

The filters on the datasource are contained in a single object, this object has a property called ‘logic’ which specifies if this is an AND or an OR query. The other is an array of objects named ‘filters’ that hold the columns and their filter values. So here is what needs to happen: Gather all the checkboxes that are checked, translate them into a valid JSON filter object and apply the new filter to the datasource.

function generateDsFilter() {
 
        var checkboxContainer = $("#checkboxFilters");
        var flt = { logic: "and", filters: [] };
 
        checkboxContainer.find("input:checked").each(function () {
            var name = $(this).attr("name");
            var val = $(this).attr("value");
 
            flt.filters.push({ field: name, operator: "eq", value: val });
        });
 
        return flt;
    }

The checkbox container you see holds all the windows that contain checkboxes. We need all of them so I put the windows together in a div for easy selecting with jQuery.

‘flt’ is the object that we need to fill with the filters. The logic is set to AND because we want an exclusive filter. We only want to see stuff that is checked. In a loop we pass all the checked checkboxes using jQuery and add them to the filters property of the main filter object.

To apply the filters to the datasource I added this function:

function setDsFilter(customFilter) {
        var grid = $("#reportGrid").data("kendoGrid").dataSource.filter(customFilter);
    }

I put this in a separate function because there is one thing we still need to fix. The datasource has no initial filter. With the filter being empty, no data is loaded.

To make sure the grid has an initial filter we need to set the ‘filter’ property on the datasource that is part of the grid code:

dataSource: {
                type: "json",
                serverPaging: true,
                serverSorting: true,
                serverFiltering: true,
                allowUnsort: true,
                filter: generateDsFilter(),
                pageSize: 25,
                transport: {
                    read: {
                        url: "Export/PagedData",
                        type: "POST",
                        dataType: "json",
                        contentType: "application/json; charset=utf-8"
                    },
                    parameterMap: function (options) {
                        return JSON.stringify(options);
                    }
                },
                schema: { data: "Items", total: "TotalItemCount" }
            }

This ensures that when the grid is first created, all the filters are applied BEFORE the data is loaded, in this case, all the data is loaded because all the checkboxes are checked.

When clicking the Apply button in one of the filter windows (see the code somewhere above) we regenerate the filters and apply them to the datasource. The grid will then call the Controller Action and post the filters to the server for you to handle.

Tags:

ASP.NET MVC | Development | Kendo UI

Kendo UI Grid with server paging filtering and sorting (with MVC3)

door Sander 21-3-2012

I gave the Kendo UI framework a try today. I needed a grid that could do anything (filtering, sorting and paging – the real kind, on the server that is).

Going about my coding business I came across some things that I found hard to solve using the existing documentation, which inspired this blogpost.

As I mentioned, this is about the Kendo UI KendoGrid. The definition of the grid itself is very easy to when you use the “getting started” tutorial and I ended up with something like this:

$(document).ready(function () {
        $("#reportGrid").kendoGrid({
            dataSource: {
                type: "json",
                serverPaging: true,
                serverSorting: true,
                serverFiltering: true,
                allowUnsort: true,
                pageSize: 10,
                transport: { 
                    read: { 
                        url: "Export/PagedData",
                        type: "POST",
                        dataType: "json",
                        contentType: "application/json; charset=utf-8"
                    },
                    parameterMap: function(options) {
                        return JSON.stringify(options);
                    }
                },
                schema: { data: "Items", total: "TotalItemCount" }
            },
            pageable: true,
            sortable: true,
            filterable: true,
            columns: [
                    { field: "CustomerName", title: "Klant" },
                    { field: "SerialNumber", title: "Serienummer" },
                    { field: "CertificateNumber", title: "Polisnummer" },
                    { field: "ReportDate", title: "Melddatum" },
                    { field: "ReportType", title: "Meldingtype" },
                    { field: "Description", title: "Omschrijving" }
                ],
        });
    });

One point of interest in this code here, the parameterMap in the transport section of the datasource. Without that mapping there, MVC3 will have a hard time reading the parameters that are posted by the grid. The stringify function translates it to something we can use in our controller action.

Lets take a look at that:

public JsonResult PagedData(int skip, int take, int page, int pageSize, List<SortDescription> sort, FilterContainer filter)
        {
            int itemCount = 0;
            var data = _export.GetData(page - 1, pageSize, out itemCount, filter, sort);
 
            return Json(new ExportModel(data, itemCount), JsonRequestBehavior.AllowGet);
        }

The first four parameters are straightforward and let me handle the paging in my query. Note that you need to define the serverXXXXX properties on the grid for these to be posted to the controller on the server! The schema tells the grid the properties to look for when data is read from the server. When paging serverside, you will also need to tell the grid how many items exist on the server so it can calculate the number of pages to show.

After that, things get interesting. Before I came to the solution I now have I spent a fair amount of time searching high and low on how to solve the parsing of array in the querystring. I gave up on that after finding out about the stringify function for the parameterMap in the grid.

If you want some more insight on how to configuring the datasource, look here:

http://www.kendoui.com/documentation/framework/datasource/configuration.aspx

It has everything on configuring a datasource for the Kendo Grid. I was looking for filtering and sorting, which comes down to this:

When filtering, the grid produces and object that contains an array of objects that hold our filter parameters. It holds the columnname, the filter value and the operator used in the filter.

When sorting, an array of objects is produced where each element has an object that contains the column and sort direction for every sort operation.

Translating that into code, we come to this:

public class SortDescription
    {
        public string field { get; set; }
        public string dir { get; set; }
    }
 
public class FilterContainer
    {
        public List<FilterDescription> filters { get; set; }
        public string logic { get; set; }
    }
 
public class FilterDescription
    {
        public string @operator { get; set; }
        public string field { get; set; }
        public string value { get; set; }
    }
If you look at the parameters in the controlleraction, we have a list of sortdescriptions, and an object that contains a list of filters.

The controller is now aware of the filtering and sorting on the grid and all you have left to do is translate the filters and sorting into a query.

Tags:

ASP.NET MVC | Development

Get more than 1500 members from an Active Directory group

door Ronald 5-1-2012

When retrieving the members of a group, Active Directory will never return more than 1000 (Win2000) or 1500 (Win2003) entries. This is true for all multi-valued properties. So suppose you have a group with more than 1500 members, it’s difficult to get all of them.

 

   1: DirectoryEntry groupEntry = ...;
   2: var members = groupEntry.Properties["member"];
   3: var nrMembers = members.Count;

 

In the example, nrMembers will never be larger than 1500, even when the group has more than 1500 members.

 

To overcome this problem, there are two possible solutions. First, instead of taking the group and reading the member property, look at all the potential members and check their memberOf property.

 

   1: // Construct a memberOf LDAP filter.
   2: var groupWithMembersDn =
   3:    "CN=TestGroup,OU=Groups,DC=itq,DC=local";
   4: var filter = String.Format("memberOf={0}", groupDn);
   5:  
   6: // Load only the objectSid of every member.
   7: var properties = new[] { "objectSid" };
   8:  
   9: // Get a root entry to start the search from.
  10: DirectoryEntry rootEntry = ...;
  11:  
  12: // Find sid's for all group members.
  13: var searcher = new DirectorySearcher(
  14:    rootEntry, filter, properties, SearchScope.Subtree);
  15: searcher.PageSize = 100;
  16: using (searcher)
  17: {
  18:    var memberResults = searcher.FindAll();
  19:    foreach (SearchResult memberResult in memberResults)
  20:    {
  21:       var memberSidBytes =
  22:          (byte[]) memberResult.Properties["objectSid"][0];
  23:       var memberSid = new SecurityIdentifier(sidBytes, 0);
  24:     }
  25: }

 

In the example we get the SID of every member of the group with distinguished name CN=TestGroup,OU=Groups,DC=itq,DC=local.

 

This approach has two disadvantages. One issue is that you always have to scope the search from a specific root entry. If you want to make sure you get every group member, your scope should always be the root of the Active Directory domain. This probably makes your search space too wide and therefore has a negative impact on performance.

 

A second and larger disadvantage is that this approach fails in a multi-domain or multi-forest environment. Since you scope the search to a specific root entry, you will find nothing outside this scope. So if the group has a member outside the current domain or forest, this member will not be found.

 

So, it would be nice if we could look directly at the group’s members without being bound by the 1500 entries limit. This can be accomplished via range retrieval of attribute values.

 

To use this, you first need to get the DirectoryEntry for the group you want to find the members of. This entry will be the search root of a DirectorySearcher. Next, you append a range option to the multi-valued property you want to load (in this case, the member property). Finally, you loop through each range and collect the member property values.

 

   1: var memberDns = new List<string>();
   2: const int increment = 999;
   3: var groupEntry = ...;
   4: int from = 0;
   5: while (true)
   6: {
   7:    // End of the range.
   8:    int to = from + increment - 1;
   9:  
  10:    // Attach a range option to the properties to load,
  11:    // for example: range=0-999.
  12:    var properties = new[]
  13:       { string.Format("member;range={0}-{1}", @from, to) };
  14:  
  15:    // Perform a search using the group entry as the base.
  16:    var filter = "(objectClass=*)";
  17:    var memberSearcher = new DirectorySearcher(
  18:       groupEntry, filter, properties, SearchScope.Base);
  19:    using (memberSearcher)
  20:    {
  21:       try
  22:       {
  23:          var memberResults = memberSearcher.FindAll();
  24:          foreach (SearchResult memberResult in memberResults)
  25:          {
  26:             var membersProperties = memberResult.Properties;
  27:             var membersPropertyNames =
  28:                membersProperties.PropertyNames
  29:                   .OfType<string>()
  30:                   .Where(n => n.StartsWith("member;"));
  31:             foreach (var propertyName in membersPropertyNames)
  32:             {
  33:                // Get all members from the ranged result.
  34:                var members = membersProperties[propertyName];
  35:                foreach (string memberDn in members)
  36:                {
  37:                   memberDns.Add(memberDn);
  38:                }
  39:             }
  40:          }
  41:       }
  42:       catch (DirectoryServicesCOMException)
  43:       {
  44:          // When the start of the range exceeds the number
  45:          // of available results, an exception is thrown
  46:          // and we exit the loop.
  47:          break;
  48:       }
  49:    }
  50:    // Increment for the next range.
  51:    from += increment;
  52: }

 

 

On lines 12 and 13 you can see the range option being added to the member property we wish to load. The from and to values of a range are inclusive so a range=0-4 contains the elements 0, 1, 2, 3, 4.

 

Line 24, where we iterate the found results, throws a DirectoryServicesCOMException when the from part of the range extends the number of elements to be found. This is one way to check whether we have found all results. Another way to check that we have arrived at the last result is described below.

 

The results contain member properties that are indexed as follows: for each result that is not the last result, the key to the actual members is member;range=0-999, member;range=1000-1999, etc. For the last result it is member;range=2000-* (even if we specify member;range=2000-2999 in the search). By checking that the returned property name ends with *, we can determine that it is the last result.

 

UPDATE: After some testing it appeared that my implementation had a bug. I wrote a while (true) {...} loop that was supposed to exit with an exception. Well, not always.... When the group has no members, the loop keeps running without ever exiting. So between lines 3 and 4 in the last example, you should check whether the group has any members and make sure never to enter the loop: if (groupEntry.Properties["member"].Count == 0) {...}. Alternatively, inside the loop, you could rewrite your end condition not to depend on an exception, but on the method described in the previous paragraph.

Tags: , ,

Development

Persist Telerik RadTreeView (ASP.NET Ajax) state in cookie

door Sander 24-11-2011

Finding myself without a nice example of how to accomplish this, there was no other way of doing this myself :-)

First, we need to find out which nodes are expanded. The right time to check for this is when nodes are expanded/collapsed. So here is the client-side function:

function uxProfileTree_NodeToggle(sender, args) {
    var tree = $find("<%= uxProfileTree.ClientID %>");
    var allNodes = tree.get_allNodes();
    var nodeString = "";
    for (var i = 0; i < allNodes.length; i++) {
        var node = allNodes[i];
        var expanded = node.get_expanded();
            if (expanded) {
                nodeString = nodeString + node.get_value() + "*";
                createCookie("preofileTreeState", nodeString, 365);
        }
    }
}

This function is hooked into the OnClientCollapsed and OnClientExpanded client-side handlers of the treeview that you want to persist.

We now know what nodes are expanded so lets put the nodeString to work. In the Page_LoadComplete handler of my ASP.NET page I check for the cookie we created on the client and parse its value into an array by splitting the nodeString at the * char.

HttpCookie cookie = Request.Cookies[treeCookieName];
            try
            {
                if (cookie != null)
                {
                    string[] toggleParts = cookie.Value.Split(new[] { "*" }, StringSplitOptions.RemoveEmptyEntries);
                    foreach (string part in toggleParts)
                    {
                        RadTreeNode toggledNode = uxProfileTree.FindNodeByValue(part);
 
                        toggledNode.Expanded = true;
                    }
                }
            }
            catch
            {
                // Remove the cookie if it messes up!
                Request.Cookies.Remove(treeCookieName);
            }

For every value we find in the array, we look up the corresponding node in the tree. The only thing left is setting the node’s Expanded property to true. As a failsafe, the cookie is removed if anything messes up while trying to find the matching node. This is to make sure the user isn’t stuck with a broken cookie and a tree that wont expand its nodes.

Tags:

Telerik | Development

Removing dependencies from a Windows service

door Ronald 18-8-2011

Provisior, one of the products we develop here at ITQ, has two components: an intranet website and a Windows service. We use the Windows service for long-running processes that you typically do not want to run inside a web server.

 

The Windows service is installed the first time you start the website. When it’s installed, we add a dependency to the NetLogon service. This prevents the Provisior service from starting before the NetLogon service. We did this because our service attempts to access a SQL Server database right when it starts using integrated authentication. This fails when the NetLogon service has not started yet.

 

All of this works fine when the machine that runs Provisior is joined to a domain. However, when that is not the case, the NetLogon service doesn’t run. You can start it, but it stops again immediately with the following error in the event log (at least on my machine):

 

This computer is configured as a member of a workgroup, not as a member of a domain. The Netlogon service does not need to run in this configuration.

 

So now I have a Windows service with a dependency on NetLogon that doesn’t run. How to fix this? Apparently, removing service dependencies is only possible from the Windows registry. You need to go to the following registry entry: HKLM\SYSTEM\CurrentControlSet\services\<service>. There you find a REG_MULTI_SZ key named DependOnService where you can remove any services your service depends on. After doing so I had to reboot my machine because the service control manager did not see my update.

 

I could have updated Provisior to install the NetLogon dependency only when the machine is joined to the domain (here is some info on detecting whether your machine is joined to a domain). However, Provisior is hardly ever run on a machine that is not joined to a domain. This only happens in testing scenario’s so I’d be changing code just for this very specific case.

Tags: , , ,

Development | Provisior

Prevent duplicate form submits in combination with jQuery client-side validation

door Ronald 8-5-2011

The title says it all: I want to prevent clients from posting the exact same data twice (or more) but I also want to use client-side validation. Let’s first explain why this is problematic.

 

Preventing duplicate form submits is easy. You disable the submit button in the onclick event handler:

<input type="submit"
       onclick="$(this).attr('disabled', 'disabled');" />

This works fine without client-side validation. But now suppose that some client-side validation rule has triggered and the form is invalid. You now have a disabled submit button on your form.

 

To fix this, we must make sure to only disable the button when the form is valid. We introduce a Javascript function to do this:

   1: function disableSubmitButton(b) {
   2:   // Disable submit button as soon as possible to prevent
   3:   // duplicate submits.
   4:   $(b).attr('disabled', 'disabled');
   5:   var valid = $(b.form).valid();
   6:   if (!valid) {
   7:     // Re-enable submit button when form is invalid.
   8:     $(b).removeAttr('disabled');
   9:   }
  10: }

And our button now looks like this:

<input type="submit"
       onclick="disableSubmitButton(this);" />

The valid() method is available from the jQuery Validation Plugin. It provides a lot of client-side validation options and can be used out-of-the-box with ASP.NET MVC3.

 

That was easy. When a user clicks the button, it is disabled immediately unless the form is invalid. However, this appears to work only in Internet Explorer. In Chrome and Safari (I haven’t tested Firefox), if the form is valid, it is no longer submitted. This has probably something to do with the fact that I define a custom onclick handler, preventing some default onclick handler from running. In Internet Explorer this problem doesn’t occur for some reason.

 

To fix this, I changed the disableSubmitButton function to the following:

   1: function disableSubmitButton(b) {
   2:   // Disable submit button as soon as possible to prevent
   3:   // duplicate submits.
   4:   $(b).attr('disabled', 'disabled');
   5:   var valid = $(b.form).valid();
   6:   if (valid) {
   7:     // Submit form when it is valid.
   8:     $(b.form).submit();
   9:   }
  10:   else {
  11:     // Re-enable submit button when form is invalid.
  12:     $(b).removeAttr('disabled');
  13:   }
  14:   // Always return false to prevent Internet Explorer from
  15:   // posting the form (which would then be the second post).
  16:   return false;
  17: }

The submit button is changed to:

<input type="submit"
       onclick="return disableSubmitButton(this);" />

A bit more work than I thought it should be but it works.

Tags:

Development

Using the FileHelpers library in a medium trust environment

door Ronald 6-5-2011

Yesterday I found out the hard way that you should never use a third-party library without doing some preliminary checks. First of all. always check the license: does the author allow you to use his library and if so, how exactly? Second, check whether the library allow partially trusted callers.

Many hosting providers allow you to run your web applications in medium trust instead of full trust. This means that code executing inside your application is not allowed to do a number of things like: opening files, accessing the registry, write to the event log or emitting dynamic classes using System.Reflection.Emit.

Yesterday I was deploying a website to a hosting provider that included the FileHelpers library. If you need to parse CSV files into something you can work with, this is the library for you. It parses CSV files into strongly-typed objects and provides a host of configuration options to do this. Obviously, it has to use some kind of reflection to set property values. At a minimum, it has to call PropertyInfo.SetPropertyValue somewhere. Fortunately, this call is not under any security restrictions.

However, probably for performance reasons, this is not how the FileHelpers library implements setting properties. Instead, it uses the System.Reflection.Emit namespace to build a dynamic class that is then used to set property values. The System.Reflection.Emit namespace is littered with methods that have the SecurityCritical attribute. These methods can therefore only be used in a full-trust environment. Which means that the FileHelpers library can not be used in my website...

The obvious question then is: couldn't you have checked this beforehand? Well, actually I could have. Assemblies that should allow calls from partially trusted callers must explicitly set the assembly-level attribute AllowPartiallyTrustedCallers. The FileHelpers library doesn't have this attribute. Therefore, the first call into the library generates an exception: System.Security.SecurityException: That assembly does not allow partially trusted callers.

This was a bit of a problem since I promised a customer that the website would be live today. There are probably better fixes but what I did was the following (only works for FileHelpers by the way). I downloaded the source code and included it into my solution. This still makes no difference because there are still the SecurityCritical attributes in the System.Reflection.Emit namespace. The only difference is that errors are now thrown a little later in the process but it is at least possible to call FileHelpers methods. Now I got a little lucky. FileHelpers has been around since the first version of the .NET framework when probably not all necessary functionality of System.Reflection.Emit was available to implement FileHelpers. Anyhow, the code contains some #if NET_1_1 directives that prevent the use of System.Reflection.Emit (instead, System.Reflection is used). So I simply set this compiler symbol and it all worked.

Tags: , ,

Development | Security

Confused by jQuery 1.6 .attr() and .prop()

door Ronald 4-5-2011

I just read the jQuery 1.6 release announcement and I must admit I'm a bit confused about the new .prop() method and the breaking change to the older .attr() method. In jQuery 1.6 a distinction is made between DOM attributes and DOM properties. Attributes are things like style, class and title and represent the state of the DOM as retrieved from the document. Properties are things like value, checked and disabled and represent dynamic state of the document.

For example, suppose an input element: <input id="myInput" type="checkbox" checked="checked" />.

If you called $('#myInput').attr('checked') in jQuery 1.5 you got back the value true. In jQuery 1.6 it would be "checked" (the actual value of the attribute). If you call $('#myInput').prop('checked') you get back true. So far so good although a lot of people will probably be annoyed by the breaking change to the .attr() method because there are thousands of lines of code that check the value of a checkbox by using .attr().

However, besides breaking .attr(), the announcement of this change is very confusing. If you want to set the checked attribute of a checkbox, I would expect you should use: $('myInput').prop('checked', true). But the announcement has the following line:

In jQuery 1.6 Boolean attributes (such as selected, checked, etc.) can now be toggled by passing in true or false to .attr() to either add or remove them.

This is inconsistent. If you call .attr('checked') you get back the value "checked" but you should set the attribute by passing in the value true. Or can you still call .attr('checked', 'checked') as you could in jQuery 1.5?

Tags:

Development

IE9 Jumplist

door Sander 28-3-2011

In the latest version of Internet Explorer users now have the option to pin a tab to the taskbar. It works like a favorite, but just one click away instead of having to open the browser first.

Pinning a tab is a nice feature, but they didn’t stop there, these pinned pages can also supply custom jumplists to make access to common tasks even friendlier.

To demonstrate this feature I added a jumplist to our own product Provisior Smile

 

pinnedtab3

 

Provisior uses a MasterPage to define the overall layout of the site. This is where the custom jumplist lives as well.

This is what a jumplist item looks like in the HTML source:

<meta name="msapplication-task" content="name=Mijn Info;action-uri=http://localhost/Celerior.Annapurna.UI/MyInfo.aspx;icon-uri=http://localhost/Celerior.Annapurna.UI/App_Themes/Light/Icon/myinfo.ico" />

Just another meta in the page head section, easy enough. But what about dynamic jumplist items? Here’s how I take care of that:

 

In the MasterPage codebehind I created a simple method that adds a new <meta /> to the page header. Note that the head section in the aspx needs to have the runat=”server” attribute set to programmatically manipulate the header.

private void addMeta(string name, string content)
        {
            var m = new HtmlMeta();
            m.Name = name;
            m.Content = content;
 
            Page.Header.Controls.Add(m);
        }

To add the jumplist item I can now write the following in the MasterPage Page_Load event:

addMeta("msapplication-task", "name=Mijn Info;action-uri=" + webHost + "/MyInfo.aspx;icon-uri=" + iconPath + "myinfo.ico");

The code above outputs the <meta /> in the first codesnippet.

 

In the Page_Load you can now write anything you want about when the jumplist items are shown.

 

There are some other tricks you can do like set the name of the jumplist and the size of the browser window when the site is launched from the taskbar:

<meta name="application-name" content="Provisior" />
<meta name="msapplication-window" content="width=1024;height=768" />

There is also a way to do all this in Javascript but I’ll let you discover that on your own (Here is a good start Smile)

 

To see some of these jumplists in action, try pinning Twitter or Facebook to your taskbar and right-clicking the icons.

 

Have fun!

Tags:

Development | Provisior | Internet Explorer 9

Comparing WIF claim sets from AppFabric Access Control Service V2

door Ronald 16-1-2011

Windows Identity Foundation or WIF is a Microsoft technology that allows you to move authentication and authorization logic out of your application. Suppose you are building a web site, then using WIF you no longer have to build your own login page and user store. Instead, you let a so-called Identity Provider, sometimes called a Security Token Service or STS authenticate your users. Your web application, called the Relying Party (RP), has a trust relationship with the STS and when the STS presents a user token to the RP, the RP knows that the user represented by the token is authenticated.

To clarify the example a little, suppose you choose Windows Live as your STS. When I hit a secure section of my web site, WIF intercepts the request and redirects it to the Windows Live authentication page. I sign in with my Windows Live credentials, Windows Live creates a security token for me and redirects me back to my web site using this security token. WIF checks the token and I’m authenticated.

Multiple identity providers

In the example above I used a single STS but suppose I want to allow users from multiple identity providers (Google, Yahoo, Facebook, etc). Microsoft has a solution for this called AppFabric Access Control Service V2, available on portal.appfabriclabs.com. It is a proxy between your application and currently five identity providers.

image

In the screenshot I have activated two identity providers.

Claim sets

When you sign in to an identity provider, it generates a token that represents who you are. So what exactly is a token? A token contains a set of claims about a user and is digitally signed by the STS. So when I login to Windows Live, a token is generated that contains some claims about me. A claim can be an e-mail address or a last name or anything else that the identity provider may wish to disclose about you. And that’s what we are interested in today. Each identity provider provides a different set of claims. Some may produce more claims, others may produce just an identifier. So the question is: what are the claims provided by each identity provider currently supported by AppFabric ACS?

For this post I have not yet tested Microsoft Active Directory Federation Services 2.0 because I haven’t set that up yet. When I have, I’ll update this post or write a new one describing the possibilities that ADFS offers.

Before we start it is important to note that AppFabric ACS simply passes all claims from each identity provider through unchanged.

Windows Live ID

Windows Live is the provider that offers the least amount of claims. It only gives your application a unique user identifier. The other claim is added by AppFabric ACS to let you know what identity provider was used (you’ll see this claim type for every STS).

WindowsLive

Windows Live does not offer any options for getting more user information. This is a deliberate choice to protect user’s privacy. This is also the reason that Windows Live does not ask the user for confirmation to share information with a third-party application: no personal information is actually shared.

Google

Google offers a little more user information when asked for it. Besides, Google as an additional step explicitly asks your permission to share your information with AppFabric ACS. In my case this means that I have to confirm that rwwilden-appfabric-labs.accesscontrol.appfabriclabs.com is allowed access to information from my Google account.

Google

The reason Google asks for your permission to share information is that they actually provide information that can be traced back to a person.

Google does not offer any options for getting additional information.

Yahoo!

Yahoo! provides the same claims Google does. They also have a confirmation step to allow the user to think again before sharing information with a third-party application.

Yahoo!

Facebook

Facebook does not have a very good record of keeping its user’s data private (I inserted some random links to sites I found when searching for ‘facebook privacy violation’). However, the set of claims when using Facebook as your STS is limited. The extra claims are an access token and an expiration date.

Facebook

Besides, Facebook asks for your permission when you sign in for the first time, just as Google and Yahoo! do.

However, you can configure AppFabric ACS to ask the Facebook STS to grant access to an extensive set of permissions based on the access token claim. For example, I can let AppFabric ACS ask Facebook for permission to read a user’s birthday.

FacebookAppFabricACS

The above screenshot is from the AppFabric ACS portal. When I sign in this time via Facebook I get a new request for permission (in Dutch) asking me to allow the third-party application access to the birthday field.

image

When I allow this, there are no new claims added to the claim set. However, I can use the provided access token to get additional user data via Facebook’s Graph API. The url

https://graph.facebook.com/rwwilden?fields=birthday&access_token=

gives me a small JSON document:

{
   "birthday": "04/11/1977",
   "id": "100001960422926"
}

containing my birthday. So far so good. My application has requested permission to access my birthday, I have given this permission and using the provided access token claim I can access the birthday field.

What about other fields? The documentation for the Facebook API User object specifies that I need the user_work_history permission to read the work field. I have never given this permission so the following url should generate an error or at least no work history.

https://graph.facebook.com/rwwilden?fields=work&access_token=…

And it works as expected:

{
   "id": "100001960422926"
}

 

Conclusion

The differences between identity providers currently supported by AppFabric ACS are large. On the one end there is Microsoft with Windows Live, providing only a user identifier. On the other end there is Facebook with a lot of configuration options. If you want to support Windows Live ID, the only information the STS gives you is that a user is authenticated. Additional user information can only be stored inside your application.

Even if you use Google or Yahoo! you probably need to store additional user information. The Facebook API offers all personal information you may ever need to know about your users so there is no need to store any additional user information inside your application. Facebook clearly wins when you look at it from an application builder standpoint.

From a privacy standpoint it is clear that Microsoft wins. For a third-party application making use of Facebook as an STS it is very easy to know all about its users. It’s only one button click away.

Tags: , , , ,

Cloud | Development | Informatie beveiliging | Security