Monday, June 13, 2011

Working with dynamically added controls in ASP.NET

The two main problems that are frequently encountered when working with dynamically added controls in ASP.Net are:

1. The loss of the controls after a post back occurs

2. The loss of the control’s value after a post back occurs

A typical problem that one could encounter when using dynamically added controls could be as follows:

IDEA:

1. At run time, a list of controls is added to a table

2. There is a “Submit” button to submit the information at the bottom of the page

3. Validation is performed on all of the controls on the page on click of the button, if the validation fails, the user is prompted to update the values

protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
//First, add a new row to the table.
TableRow newRow = new TableRow();
//Create the LabelCell
TableCell labelCell = new TableCell();
//Create the control cell
TableCell controlCell = new TableCell();

Label nameLabel
= new Label();
nameLabel.Text
= "Name:";
labelCell.Controls.Add(nameLabel);
TextBox tb
= new TextBox();
tb.ID
= Guid.NewGuid().ToString();
controlCell.Controls.Add(tb);
newRow.Cells.Add(labelCell);
newRow.Cells.Add(controlCell);
tblCustomFields.Rows.Add(newRow);


newRow
= new TableRow();
labelCell
= new TableCell();
controlCell
= new TableCell();

nameLabel
= new Label();
nameLabel.Text
= "Surname:";
labelCell.Controls.Add(nameLabel);
tb
= new TextBox();
tb.ID
= Guid.NewGuid().ToString();
controlCell.Controls.Add(tb);
newRow.Cells.Add(labelCell);
newRow.Cells.Add(controlCell);
tblCustomFields.Rows.Add(newRow);

newRow
= new TableRow();
labelCell
= new TableCell();
controlCell
= new TableCell();

nameLabel
= new Label();
nameLabel.Text
= "Submit";
labelCell.Controls.Add(nameLabel);
Button submitButton
= new Button();
submitButton.Click
+= submitButton_Click;
submitButton.Text
= "SUBMIT";
controlCell.Controls.Add(submitButton);
newRow.Cells.Add(labelCell);
newRow.Cells.Add(controlCell);
tblCustomFields.Rows.Add(newRow);
}
}
PROBLEM:

When the user clicks the “Submit” button, a postback occurs. This causes all the controls that have been added to the page to disappear

REASON:

The reason for this problem is that because the controls are not part of the page’s markup, the framework is not aware that it should be redrawing the controls. In order for the controls to be visible on every postback, they must be redrawn on every postback of the page. The controls can be added in the Page_Init method or at the latest, within the Page_Load method.


To stop the controls from disappearing, remove the if(!IsPostBack) check and perform this logic on every postback:


 


 protected void Page_Load(object sender, EventArgs e)
{
//First, add a new row to the table.
TableRow newRow = new TableRow();
//Create the LabelCell
TableCell labelCell = new TableCell();
//Create the control cell
TableCell controlCell = new TableCell();

Label nameLabel
= new Label();
nameLabel.Text
= "Name:";
labelCell.Controls.Add(nameLabel);
TextBox tb
= new TextBox();
tb.ID
= Guid.NewGuid().ToString();
controlCell.Controls.Add(tb);
newRow.Cells.Add(labelCell);
newRow.Cells.Add(controlCell);
tblCustomFields.Rows.Add(newRow);


newRow
= new TableRow();
labelCell
= new TableCell();
controlCell
= new TableCell();

nameLabel
= new Label();
nameLabel.Text
= "Surname:";
labelCell.Controls.Add(nameLabel);
tb
= new TextBox();
tb.ID
= Guid.NewGuid().ToString();
controlCell.Controls.Add(tb);
newRow.Cells.Add(labelCell);
newRow.Cells.Add(controlCell);
tblCustomFields.Rows.Add(newRow);

newRow
= new TableRow();
labelCell
= new TableCell();
controlCell
= new TableCell();

nameLabel
= new Label();
nameLabel.Text
= "Submit";
labelCell.Controls.Add(nameLabel);
Button submitButton
= new Button();
submitButton.Click
+= submitButton_Click;
submitButton.Text
= "SUBMIT";
controlCell.Controls.Add(submitButton);
newRow.Cells.Add(labelCell);
newRow.Cells.Add(controlCell);
tblCustomFields.Rows.Add(newRow);

}

But the control’s values are lost!


As you can see in the example above, I have deliberately set the ID’s of the text box controls to a random Guid on declaration. This is to illustrate that if you do not maintain the same ID for the controls on a postback, the values of the controls that were set before the postback get cleared after they are redrawn. The behaviour in the example above is that on clicking the “Submit” button, even though the controls don’t disappear as they originally did, the values entered in the textboxes are blank after post back.


The solution here is to always ensure that:


1. You must always give your controls an ID


2. This ID must be persisted across postback


The next article will discuss using the ViewState to persist the values in the controls when a postback caused by an external control causes the values in the form to clear. Watch this space.


Technorati Tags: ,,

Tuesday, February 15, 2011

Eager loading EF4 entities with RIA services using Silverlight 4.0

I have recently come across a problem trying to access navigation properties using EF4 from the client side. Consider the following Data structure:



In this example, the LeaveLog table has a reference to the LeaveType table, therefore making a Navigation Property "LeaveType". 

Step 1: Disable lazy loading
The first stumbling block when trying to access these navigation properties from the client side was due to the fact that LazyLoading was enabled by default. This is not possible when transferring your entities through services, as described here. You therefore need to turn off lazy loading by following these steps:

1. Double click on your .edmx file in the solution explorer
2. Right click anywhere in the white space and click "Properties"
3. In the properties window, set "LazyLoadingEnabled" to false.

You will then see that in the generated file, the following line will be added:

  this.ContextOptions.LazyLoadingEnabled = false;

However, if you build and run the solution, you will still run into trouble accessing the navigation properties (they will still return null).

Step 2: Add .Includes to the RIA service
In this situation where you are using RIA services, you should have methods in the RIA service such as the following:

 public IQueryable<LeaveLog> GetLeaveLogs()
        {
            return ObjectContext.LeaveLogs;
        }

This exposes the "LeaveLogs" collection to the client side. However, you need to actually specify the navigation properties to return along with the query by adding ".Include" methods like this:

public IQueryable<LeaveLog> GetLeaveLogs()
        {
            return ObjectContext.LeaveLogs.Include("Employee").Include("LeaveType");
        }

What this means is that when querying a LeaveLog from the client side, the "Employee" and "LeaveLog" navigation properties will be propagated with data.

Final Step: Add the [Include] attribute to the navigation properties
At this point, even after adding the .Includes, you are not finished. You need to actually add an [Include] attribute above the generated Navigation Properties. I edited the .tt template to include this attribute so that going forward all navigation properties are accessible.

1. Open your .tt template file
2. Locate the section where Navigation Properties are set
3. Add [Include] as an attribute beneath the [DataMember()] attribute
4. Add  using System.ServiceModel.DomainServices.Server; to the using section in the template.
5. Save and run the template.

And thats it?
After following these steps, you should be able to access your navigation properties from the client side. This seems like a lot of effort to get what I'd expect to happen automatically if Lazy Loading is disabled. My opinion is that there should be an option to at least automatically include the ".Include" methods in the generated code to save us the effort.


Wednesday, February 9, 2011

Keeping a developer happy 101

As most developers know, our jobs our not glamorous at the best of times. We don't want or need them to be. What we do require, however, is that management provides basic provisions to keep us happy. The following is a list of basic requirements I have found over the years are essential to keep a developer motivated and happy.

1. Appreciation & Praise for work done
There is nothing better for a developer than to get some praise every now and then for the work that they have done. A simple "Wow, thats looking great" helps motivate developers more than one would think.

2. Keep developers away from clients
Yes, I am about to generalize. Developers are not, and do not want to be, people persons. They do not want to answer support calls and talk clients through "How to's". Whilst happy to fix bugs in systems, having to hear them reiterated over and over again over the phone harbors serious resentment towards the job and towards the system and company as a whole. Furthermore, reputation is King. Developers are (in general), not marketing / client liaison experts. Therefore it is best to keep communication between clients and the company through the correct channels. Support desks, electronic issue tracking systems, etc. Management, for crying out loud. Anyone but a developer.

3. Keep developers busy
A bored developer is an unhappy developer, and if bored for long enough will not stick around. Always ensure that developers have enough work to keep them occupied (and no, compiling a list of outstanding bugs / reports does not count as keeping a developer occupied).

4. Provide developers with the best tools for the job
If a developer can motivate a certain tool will increase their productivity on the job, provide it for him / her. Making a developer do things the 'long way round' makes no sense. A developer's job is to solve problems in the most efficient way. So give them the tools they need to make their job more efficient and you're already one step in the right direction.

5. Provide a casual, comfortable working environment
Good development requires serious creativity (as well as skill). You have to do everything you can to encourage creativity in your development team. Making your developers dress in smart, uncomfortable clothing is the first step to stifling that creativity. Freezing them to death with overrun air conditioners, or baking them in a room with no air conditioner, is the second way to stifle such creativity. Simple stuff, but get an office temperature and environment comfortable, and you will be surprised at the difference this makes to the productivity levels in your development team.

6. Provide regular training
The development sphere is continuously evolving. If you don't ensure that your developers are kept up to date with the latest trends, they will leave to skill-up elsewhere. Send all your developers on courses and open days. Even if they don't bring the knowledge learned directly back to the workplace, you are feeding a craving that all developers have to 'stay in the loop'.

7. Ensure that the work a developer is doing has purpose 
Last but not least, let the developers know the purpose of the project / section they are working on. If one cant see the purpose in what they're doing, they'll do it slowly, and badly. Ultimately, developers are problem solvers. If a developer cannot understand the problem, then it is safe to say that the solution will not be optimal.

Silverlight - making use of Converters

Having trouble figuring out how to bind the Visibility property of a UIElement to a boolean datasource? This is an example of when a Converter will come in handy.

Converters are there to reduce the logic written in the UI itself and to provide a mechanism to reuse logic. 

All converter classes need to implement the IValueConverter interface. This interface will enforce you to implement two methods: Convert and ConvertBack. 

Step 1: Create a converter class:

The converter class below will take in a boolean value and convert it into Visibility. Visible if the parameter is "true" and Visibility.Collapsed if false. One can specify an optional parameter, specifying whether to perform a negative boolean result (i.e., if the value is false, return visible) which is often a requirement:

 public class VisibilityConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            bool mustReverse = false;
            if (parameter != null)
            {
                mustReverse = System.Convert.ToBoolean(parameter);
            }
            bool visibility = !mustReverse ? (bool)value : !(bool)value;
            return visibility ? Visibility.Visible : Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            Visibility visibility = (Visibility)value;
            bool mustReverse = false;
            if (parameter != null)
            {
                mustReverse = System.Convert.ToBoolean(parameter);
            }
            return (mustReverse ? visibility == Visibility.Visible : visibility == Visibility.Collapsed);
        }

        #endregion
    }


Step 2: Add the converter to your application resources

Once you have compiled your solution with the new converter class, you need to add your converter class to your application resource dictionary, usually located in the App.Xaml class. You will need to add the following:




  <Application.Resources>
        <ResourceDictionary>
            <DataStore1:DataStore x:Key="DataStore" d:IsDataSource="True" />
            <!--Global View Model Locator-->
            <vm:GlobalViewModelLocator x:Key="Locator" d:IsDataSource="True" />
            <namespace:VisibilityConverter x:Key="VisibilityConverter" />
        </ResourceDictionary>
    </Application.Resources>


Step 3: Make use of your converter!


Your converter is now ready to be used. You can use the converter in the following way:

<TextBlock x:Name="Video_Title" TextWrapping="NoWrap" Text="Video Title:" Visibility="{Binding MustShowTitle, Converter={StaticResource VisibilityConverter}, ConverterParameter=false}" FontFamily="Arial" Foreground="White" FontSize="14.667" Height="17" d:LayoutOverrides="Width" />

Auto updating a Silverlight 4.0 OOB Application

It's really simple to set up your Silverlight 4.0 OOB Application to automatically update itself.

Step 1 - set up the CheckAndDownloadUpdateCompleted event:
In the App.Xaml class, add the following to the constructor:


Current.CheckAndDownloadUpdateCompleted += Current_CheckAndDownloadUpdateCompleted;


Now, make a call to check for an update and download if necessary:


Current.CheckAndDownloadUpdateAsync();


Step 2 - Handle the CheckAndDownloadUpdateCompleted event:


While it is not essential to handle this event, it is recommended to let your users know that a new version of the application has been downloaded, or even write it to the log file.



private void Current_CheckAndDownloadUpdateCompleted(object sender, CheckAndDownloadUpdateCompletedEventArgs e)
        {
            if (e.UpdateAvailable)
            {
                Logger.WriteLine(
                    "An application update has been downloaded and will be available on restart of the application");
                
//ADD CODE HERE TO ALERT THE USER AS REQUIRED


                }
            }
            else if (e.Error != null)
            {
                Logger.WriteLine(
                    "An application update is available, but an error has occurred.\nThis can happen, for example, when the update requires\na new version of Silverlight or requires elevated trust.\n" +
                    "To install the update, visit the application home page.");
            }
            else
            {
                Logger.WriteLine("There is no update available.");
            }
        }


And thats it!
A downfall of this is that there is no way to check for an update and only download it if the user says "Yes". Its either all or nothing.

Injecting content into Silverlight OOB application

I recently came across an interesting problem in our Silverlight 4.0 application. The application has function to select a video to download from a catalog and then downloads it using a built in download manager. A request was made to allow our application to accept "injected" content from another website, without having to go through web services and databases. This article will explain the approach we took to inject content into the OOB application.

Approach:
The approach that was taken to inject content was to queue the injected content into the application's IsolatedStorage. When the application is accessed in browser, it shares the same isolated storage as accessed by the application when run in OOB mode. We therefore redirected the user the application within the browser and sent through a DownloadId parameter to the in-browser page, which then got written to the IsolatedStorage.  On startup of the application in OOB mode, the IsolatedStorage was then read from, firing off any downloads in the queue.

Step 1: Set up the web page to allow initParams to be accepted
Open the Web project in your Silverlight 4.0 OOB application. Open the Default.aspx page and locate the body section, where the parameters are listed.

Add the following parameter:

<param name="initParams" runat="server" id="prmInitParams" value="dummy=" />

Note: It is important to have a value set for this parameter. If no value is set, an error is thrown in the markup at runtime and you will not be able to use the application.


Step 2: Set the values in the code behind
Next step is to open the code behind file for this page (if there isn't one you'll need to create one before continuing).

In the Page_Load event handler, we then check if the QueryString contains a key called "DownloadId". If it does, we then set the value for the initParams key to contain the Key/Value pair, where the Key is set to a random Guid  - all we care about here is the value.


 protected void Page_Load(object sender, EventArgs e)
        {
            try
            {
                if (Request.QueryString.GetValues("DownloadId") != null)
                {
                    var downloadId = Request.QueryString["DownloadId"];
                    prmInitParams.Attributes["value"] = Guid.NewGuid() + "=" + downloadId;
                }
            }
            catch (Exception)
            {


            }
        }


Now that this parameter has been set, it is then automatically sent through to the Application_Startup method in your App.Xaml class as the InitParams property within the StartupEventArgs.


Step 3: Writing the injected files to isolated storage
The final step is to actually store these values into the IsolatedStorage. As mentioned above, the Application_Startup event is fed with the parameters sent through from our web page. These parameters can be accessed in the following way (within App.Xaml):


  private void Application_Startup(object sender, StartupEventArgs e)
        {
            if (!Application.Current.IsRunningOutOfBrowser)
            {
                var oldList = IsoStoreAppSettings.QueuedDownloadIDs;
                InjectedDownload ij = new InjectedDownload();
                foreach (var injectedValue in e.InitParams)
                {
                    ij.DownloadId = injectedValue.Value;
                    oldList.Add(ij);
                }
                IsoStoreAppSettings.QueuedDownloadIDs = oldList;
            }
        }

As you can see we have a helper class called IsoStoreAppSettings which is just a simple class that accesses the isolated storage in the following way:
IsolatedStorageSettings.ApplicationSettings["QueuedDownloadIDs"]

Final Step: Accessing the injected content in OOB application
The final step is to access the injected content from the application whilst running the application in OOB mode.
This is simple - all you need to do is loop through the parameters in your IsolatedStorage and work with them as needed, as depicted in the else block below:
  if (!Application.Current.IsRunningOutOfBrowser)
            {
                var oldList = IsoStoreAppSettings.QueuedDownloadIDs;
                InjectedDownload ij = new InjectedDownload();
                foreach (var injectedValue in e.InitParams)
                {
                    ij.DownloadId = injectedValue.Value;
                    oldList.Add(ij);
                }

                IsoStoreAppSettings.QueuedDownloadIDs = oldList;
            }
            else
            {
                foreach (var downloadId in IsoStoreAppSettings.QueuedDownloadIDs)
                {
                    StartDownload(downloadId);
                }
            }