Thursday, November 11, 2010

Getting Current User information in Silverlight 4

Let’s assume for a Silverlight application. You may be wondering where to find out who the current user is. In ASP.NET there is a User property on the Page object (also other places such as the HttpContext.Current), but where is that in Silverlight 4 (not in early versions that I am aware of). The answer is the WebContext.

I think the WebContext was introduced in Silverlight 4.You The WebContext is created in the constructor of the App class that is in the App.xaml.cs file. If it isn’t you may be using a different type of Silverlight 4 project than the Silverlight Business Application or Silverlight Navigation Application. If you app doesn’t have this, I am assuming you can add it. Just create on of these projects and copy what is done there.

Among other things, the WebContext has a User property. This gives you what acts like what you may expect if you are accustomed to ASP.NET. For example, it has a Roles property to see what roles the user in. It has a IsUserInRole() method to see if a user is in a particular role. It has a Name property that gives you the username.

The WebContext is accessible from code by using WebContext and is also added to the Application Resources in the Application_Startup method which makes it accessible via XAML. To show the current username using XAML you would do something like the following:

<TextBlock x:Name="CurrentUser" Text="{Binding Current.User.Name, Source={StaticResource WebContext}}" />

Using XAML is really great because as Current.User changes from null to some value when the user logs in or Windows Authentication logs them in automatically, the TextBlock will be notified and display the change.

. The same is NOT true if you use code that you may write in ASP.NET to set the value. For example:

Assume you XAML is now defined like this:

<TextBlock x:Name="CurrentUser" />

You might be tempted to write the following line of code

CurrentUser.Text = WebContext.Current.User.Name;

The problem is that WebContext.Current.User may be null initially AND the biggest problem is that you won’t be able to pick up the Name since it may NOT be set yet. Really what we want is to using Bindings which notify the other side of the binding when the value changes. This means that initially we would get no username, but as soon as it is loaded we would be notified that there is a new value and we can show it.

The code is a little more complex than above, but not really too bad. Basically, we are trying to do what we did in XAML in the first example, but do it in code. Here is all you have to do:

CurrentUser.SetBinding(TextBlock.TextProperty, WebContext.Current.CreateOneWayBinding("User.Name"));

Now we the CurrentUser textblock will always display the current value of WebContext.Current.User.Name even as it changes.

Wednesday, November 3, 2010

Getting the Action or DynamicDataRoute from a DynamicData page

If you are working in a Dynamic Data web application sometimes you may need to know what the Action (List, Details, Edit, Insert) is. The Action is just a string and is set in the Global.asax.cs in the RegisterRoutes() method that is called on application startup. Actually, if you look at each route that is added, the object being added is a DynamicDataRoute object. This is the object we eventually want to access since it has our Action property.

When you are on a PageTemplate, a CustomPage, EntityTemplate, or a UserControl that you use in one of the previous items you have access to a class called DynamicDataRouteHandler. It has a static method called GetRequestContext() which has a RouteData property. This gives us the RouteData object which has a Route property which is of type BaseRoute. Remember, we need to get an instance of the DynamicDataRoute. As it turns out DynamicDataRoute is a subclass of Route which is a subclass of BaseRoute. So, all we have to do is cast the BaseRoute object we now have to a DynamicDataRoute and we now have access to the Action property.

I guess that is a lot of explaining for a few lines of code :) Here is the code.

RouteData route = DynamicDataRouteHandler.GetRequestContext(Context).RouteData;
DynamicDataRoute ddRouteData = route.Route as DynamicDataRoute;
string action = ddRouteData.Action;

Tuesday, November 2, 2010

Adding an item to the ValidationSummary programmatically

In ASP.NET, the ValidationSummary can be used to display errors to the end user. They can specific to a field or just be entity specific, etc. In general, field specific validation shows up next to a control that it is validating (assuming you put the validators next to it). But what about validation that happens in a Domain Service Class or your Custom BLL for example? These exceptions will by default be caught by the application and show as a nasty error to the user, or go to the error page. This is hardly the desired behavior for a validation error.

First I like to change the default behavior of bubbling up to the application to be caught to being handled at the button or page level. To do this I put a try-catch in my button action or other applicable event that you can tap into. In the catch, it would be ideal to add a custom error message to the ValidationSummary. How do we do that though?

Thankfully, it is quite easy to add an item to the ValidationSummary. The key is that the Page has a Validators property that all validators are automatically added to when you put them on your .aspx page. The problem is that we don’t have a CustomValidator.

Thus we need to create a CustomValidator, but what a pain really since we only want to use it when we actually have an exception in our BLL. My solution is to create method to encapsulate the logic to create a new CustomValidator and add it to the Page’s Validators collection. So that it can easily be accessed on any page, I have implemented it as an Extension to the Page class. Below is the code to do so.

namespace MyExtensions
{
    public static class PageExtensions
    {
        public static void AddValidationSummaryItem(this Page page, string errorMessage)
        {
            var validator = new CustomValidator();
            validator.IsValid = false;
            validator.ErrorMessage = errorMessage;
            page.Validators.Add(validator);
        }
    }
}

To use this method just put the using MyExtensions; statement at the top of your code-behind of the page that you want to use it on. Then you can do the following:

protected void btnSubmit_Click(object sender, EventArgs e)
{
    try
    {
        // do some stuff like call my BLL that may throw an exception
    }
    catch (Exception ex)
    {
        if (ex.Message == "Some key string I want to handle")
        {
            Page.AddValidationSummaryItem("Password must be at least 6 characters in length.");
        }
        else
        {
            throw ex;
        }
    }
}

WARNING: Be careful with what you display to the end user. You should never catch an exception and display the Message directly to the user. It could have information that a hacker can use to compromise your application.

UPDATE 5/5/2011:

You may notice that there are times the above method does not work as expected. In particular you may notice that the error message does not show in the ValidationSummary control as you expected even though the Page.IsValid is false (as expected). The problem is likely that you have specified the ValidationGroup on the ValidationSummary control. This is a common issue when using validators and ValiationSummary controls. They must have the same value for ValidationGroup in order to tie them together. When using DynamicData templates the ValidationGroup is NOT set so the above method works great. However, in other situations such as a simple form or maybe the FormView the ValidationGroup property may be specified depending on how your code was generated or if you changed the defaults.

To help with that issue, I have created another version (overloaded method) of this method that takes one addition parameter called validationGroup.

public static void AddValidationSummaryItem(this Page page, string errorMessage, string validationGroup)
{
    var validator = new CustomValidator();
    validator.IsValid = false;
    validator.ErrorMessage = errorMessage;
    validator.ValidationGroup = validationGroup;
    page.Validators.Add(validator);
}

WARNING: If you call Page.Validate() or Page.Validate(“your validation group here”) after the above call, the validator will be reset to valid since we hardcoded the IsValid value and the default is valid.