Friday, March 19, 2010

Getting Started with RIA Services

WCF RIA Services -  You can download a version of RIA Services for either Visual Studio 2010 or 2008. This is also a great page to find sample code, walk-throughs, videos, forums, etc for RIA Services. This is a great starting point.

Below are some links to some more advanced RIA Services topics.

Silverlight 3 and RIA Services - The advanced things – Several interesting advanced topics

Walkthrough: Creating a RIA Services Class Library – If you have a Web Site instead of a Web Application, you may want to look at this since Web Sites can’t be linked to RIA Services. This serves as a nice workaround though and allows for reuse, which is very nice.

How to: Add or Remove a RIA Services Link – Nice if you want to change your RIA Services Link

WCF RIA Services Code Gallery – a good place to get source code for the walk-throughs.

Thursday, March 18, 2010

How to determine if Navigation Property in the Entity Framework is set without loading the related record.

Boy that is a long title. There are times (particularly with a Domain Service) that you may want to just check if an Navigation Property has a related record or not. If you just check the Navigation Property for null then you can really mess things up because if you don’t explicitly load the related record, it will be null. It is often a waste to load the related record just to save, etc.

Luckily, there is an easy solution. In fact, it also provides you with the actual foreign key value (if there is one). This can be useful for other things. The Entity Framework removes foreign key properties from the Scalar Properties section of the entity. At first glance it appears that you no longer have access to the foreign key value.

The solution is quite simple. Let’s assume we have two entities. One is Department and it has a relationship (Navigation Property) called Manager that points to the Person entity. What I propose is we do the following to determine if the Manager Navigation Property on Department points to anything.

bool isMgr = myDept.ManagerReference.EntityKey != null;

That is it. So simple, but a little hidden. You can also get the actual key values if you access the properties on the EntityKey property.

Thursday, March 11, 2010

Useful SharePoint Links

Here's a great page on how to "re-parent" a SharePoint site.  This allows us to move sites within SharePoint from one URL to another:  http://blog.solanite.com/keith/Lists/Posts/Post.aspx?List=11564265-6153-4557-aee2-72d1f62fa4c1&ID=7

Here's a great page to answer the question: "What can SharePoint do?"  It doesn't answer the question: "What does SharePoint do well?" (for example, I don't like wikis in SharePoint), but it is a start. 

http://office.microsoft.com/en-us/sharepointtechnology/fx101758691033.aspx

Wednesday, March 10, 2010

RIA Services Rocks!

I cannot believe how cool RIA Services is. I knew it was cool, but I think it does everything I have been dreaming about.

Here is a video that shows how to do a pretty nice application in an hour. Simply AMAZING!!!

http://silverlight.net/learn/videos/all/net-ria-services-intro/

You can download RIA Services for Visual Studio 2008 or VS 2010 Beta. Click here for more info on downloading.

BTW, the entire sample in the video is in the Documentation / Samples of the download.

I’m sold!

Friday, March 5, 2010

Adding a new row to a DataForm in Silverlight

Below is a snippet of code that shows the code that can be used to programmatically add a new record to a DataForm. In this snippet, the DataForm is called _dataform and has a PagedCollectionView (list) of Person objects. The DataForm UI has a + button that adds records. We want to do the same thing, but with a different button, and also set some default or maybe even values that are not on the DataForm. To do this, we call AddNewItem() which is exactly what the + button does. Next we get the CurrentAddItem. This is of type Person so we can cast it as a Person and set any properties that are on the Person object, not just the ones on the DataForm.

public PagedCollectionView Persons { get; set; }

private void btnAdd_Click(object sender, RoutedEventArgs e)
{
_dataform.AddNewItem();

Person newPerson = Persons.CurrentAddItem as Person;
newPerson.Name = "zzz";

newPerson.City = "Tempe";
}
A working version of this code is available here. See the MainPageWithOnlyCustomButtons.xaml.cs file.

Another snippet that may come in handy for adding just like the above, but more for the scenario where you have not displayed any records in the DataForm, but you want to add a new one. This was the scenario I had when I used the DataForm in a Popup Window and showed only one record at a time or in this case needed to allow the user to add a new Person. Here is the snippet for that. The big difference is we have to populate the Persons variable with an empty list so that the DataForm knows what type of objects it is supposed to add. Otherwise, _datagrid will be null and the operation will have failed.
 
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
// the DataForm needs to know what we have a list of, so give it an empty list to start with
// then wehn we tell it to add a new one, it knows what to add/create.
Persons = new PagedCollectionView(new List<Person>());
_dataform.DataContext = null;
_dataform.DataContext = Persons;

_dataform.AddNewItem();
Person newPerson = Persons.CurrentAddItem as Person;
newPerson.Name = "zzz";
newPerson.City = "Tempe";
}

A working version of this code is available here. See the MainPageWithLoadOneRecordAtATime.xaml.cs file.

Thursday, March 4, 2010

A better way of using the ComboBox on a DataForm in Silverlight 3

At first I tried to use the ComboBox on a DataForm in Silverlight 3 and was pretty successful. You read about it here. It worked fairly well for me. It doesn’t update the field that it is bound to, but I think that should be able to be done using the SelectionChanged event. I have not verified that though. The concept behind this implementation was that I had a ComboBox that was populated with Owners (people) objects and I wanted to bind to an OwnerID field (the foreign key).

There was a fair amount of code to translate between the object and id. I don’t really like having to write that code. It also seemed to me that there must be an easier way, especially in the day of RIA Services, Data Services, etc that use Object graphs and the like. The concept behind the implementation I want to cover here is a shift towards a more object based approach and one that I have to believe Microsoft had in mind when they designed the ComboBox because it works well.

Unfortunately, this example is not the same basic as the other implementation, but I think the point will still be pretty clear. This example uses Address and Country tables / objects. Think about an Address table, it could have a foreign key to a Country table so that users are not just typing countries, they are instead selecting them from a ComboBox of countries.

This example is a little more in-depth as far as a real world UI (example is still simple though). In this user interface the page starts with a DataGrid that shows a list of addresses (Address and Country information). When a row in the DataGrid is selected, the DataForm shows the detail of that row and allows the user to change the Country. Any changes are immediately reflected in the DataGrid. The user can save or cancel the changes. The cancel does NOT revert changes or save really, but you can do that by making the Person object implement the IEditableObject interface or by handling the EditEnded event on the DataForm.

This solution does properly notify the DataForm that the select has changed. You can tell because the OK button enables itself automatically when the ComboBox selection is changed. It also properly sets the Country property on the Person object automatically as you would expect. The translation of Country object to CountryID is handled by RIA Services seemlessly, but you could also (if you are not using RIA Services) do that when you save the Person object back to the database.

In order for the DataGrid to get the changes immediately, the Person object must implement the INotifyPropertyChanged interface.

The Country object MUST override the GetHashCode() and Equals() methods. This is because we want to treat instances of the Country class as the same object as long as the have the same foreign key. We can do this because the user can’t change Country, and we just really want a way of showing a user-friendly text version of the Country object to the user, but really have the entire Country object at our disposal.

In the database simulation methods I took care to always get new instances of objects and not reuse them between the ComboBox and the Country property on the Person object. The reason is that it will appear as though the GetHashCode() and Equals() methods don’t need to be overridden. That is because the objects would actually be the same. In a real life scenario, EF or web service, WCF, RIA Services, etc would return different instances of objects each time you return objects. We need to handle real life, so be sure to override those methods.

I populated the ComboBox using code, but you could do it in XML as described here. I find the code to be a little more comfortable to me, but either should work equally as well. The trick is to do that in the ContentLoaded (not to be confused with the Load event) event of the DataForm.

The Countries and Persons properties on the MainPage class are what everything will bind to. This means that we need to populate these variables, THEN set the LayoutRoot.DataContext = this. If you do it in the reverse order, the binding will bind to empty variables and your UI will not show any rows or details.

You can download a working solution from here. I have include the source code below for convenience.

Let’s start with the MainPage.xaml. Here is what it looks like:

<UserControl x:Class="DataFormTest2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns:dataFormToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<Grid x:Name="LayoutRoot">
<StackPanel Orientation="Vertical">
<TextBlock>Combobox example</TextBlock>

<StackPanel Orientation="Vertical">
<data:DataGrid x:Name="_datagrid" ItemsSource="{Binding Persons}" Margin="0,0,5,0" AutoGenerateColumns="False">
<data:DataGrid.Columns>
<data:DataGridTextColumn Binding="{Binding Name}" Header="Name"/>
<data:DataGridTemplateColumn Header="Country">
<data:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBlock Text="{Binding Country.CountryName}"/>
</DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
</data:DataGridTemplateColumn>
</data:DataGrid.Columns>
</data:DataGrid>
<data:DataPager Grid.Row="1" Source="{Binding Persons}" PageSize="10" Margin="0" />
</StackPanel>

<dataFormToolkit:DataForm x:Name="_dataform"
ItemsSource="{Binding Persons}"
CommandButtonsVisibility="All"
AutoGenerateFields="True"
AutoCommit="False">
<dataFormToolkit:DataForm.EditTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<dataFormToolkit:DataField>
<TextBox Text="{Binding Name, Mode=TwoWay}" />
</dataFormToolkit:DataField>
<dataFormToolkit:DataField>
<ComboBox x:Name="_comboCountries" DisplayMemberPath="CountryName"
SelectedItem="{Binding Country, Mode=TwoWay}" >
</ComboBox>
</dataFormToolkit:DataField>
</StackPanel>
</DataTemplate>
</dataFormToolkit:DataForm.EditTemplate>
</dataFormToolkit:DataForm>

</StackPanel>

</Grid>
</UserControl>
This is the MainPage.xaml.cs.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Data;
using System.ComponentModel;


// Basis for this code was copied from here: http://forums.silverlight.net/forums/p/165152/372651.aspx
// Giant help from: http://weblogs.asp.net/manishdalal/archive/2009/07/03/silverlight-3-combobox-control.aspx

namespace DataFormTest2
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
_dataform.ContentLoaded += new EventHandler<DataFormContentLoadEventArgs>(_dataform_ContentLoaded);

this.Loaded += new System.Windows.RoutedEventHandler(MainPage_Loaded);
}

// This could be done in the Constructor of this page also
void MainPage_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
// IMPORTANT: The Countries and People properties must be set
// BEFORE the DataContext is set to this
Countries = GetCountries();
Persons = new PagedCollectionView(GetPeople());
this.LayoutRoot.DataContext = this;
}

public List<Country> Countries { get; set; }
public PagedCollectionView Persons { get; set; }

#region Database Simulation

// simulate a database query
// used to populate the DataGrid and then the DataForm
private List<Person> GetPeople()
{

// get values for ComboBox
List<Country> countries = GetCountries();

// Get rows for DataGrid / DataForm
List<Person> persons = new List<Person>();
persons.Add(new Person { Name = "Charlie", Country = countries[2] });
persons.Add(new Person { Name = "Lola", Country = countries[1] });
persons.Add(new Person { Name = "Gabe", Country = countries[0] });
persons.Add(new Person { Name = "Jack", Country = countries[2] });
persons.Add(new Person { Name = "Vic", Country = countries[0] });

return persons;

}


// simulate a database query
// use this to populate the ComboBox list of values
private List<Country> GetCountries()
{
List<Country> countries = new List<Country>();
countries.Add(new Country("Andorra", 1));
countries.Add(new Country("Belgium", 2));
countries.Add(new Country("Canada", 3));

return countries;

}

#endregion

void _dataform_ContentLoaded(object sender, DataFormContentLoadEventArgs e)
{
// find the ComboBox in the DataForm and set the ItemsSource
var comboCountries = (ComboBox)_dataform.FindNameInContent("_comboCountries");
if (comboCountries != null)
{
comboCountries.ItemsSource = Countries;
}

}
}


// NOTE: You need INotifyPropertyChanged if you want the DataGrid to automatically update
public class Person : System.ComponentModel.INotifyPropertyChanged
{
private string _Name;

public string Name
{
get { return _Name; }
set { _Name = value; NotifyPropertyChanged("Name"); }
}

private Country _Country;

public Country Country
{
get { return _Country; }
set { _Country = value; NotifyPropertyChanged("Country"); }
}

public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(info));
}
}


}

public class Country
{
public Country(string countryName, int id)
{
CountryName = countryName;
ID = id;
}

public string CountryName { get; set; }
public int ID { get; set; }

// we need to override this for comparisons (see Equals method).
public override int GetHashCode()
{
return ID.GetHashCode();
}

// We override this method so that the values will be selected properly in the ComboBox
// The reason is that two objects are not considered equal if they are not the same object.
// We get two different objects because one would be the objects we got back from the
// database to populate the ComboBox. We would have a different instance of each object
// when we reference the Country property in the Person object we are editing.
// Since ID is a primary key, that is all we really need to use for comparison
// as far as the combo is concerned. This is ok, since we are not editing Country
// in the scenario so its data members never change.
public override bool Equals(object obj)
{
if (obj == null) return false;
Country cityToCompare = obj as Country;
if (cityToCompare == null) return false;
return ID.Equals(cityToCompare.ID);
}
}
}

References

http://betaforums.silverlight.net/forums/p/146398/325962.aspx

http://weblogs.asp.net/manishdalal/archive/2009/07/03/silverlight-3-combobox-control.aspx

http://weblogs.asp.net/dwahlin/archive/2009/08/20/creating-a-silverlight-datacontext-proxy-to-simplify-data-binding-in-nested-controls.aspx

http://msmvps.com/blogs/deborahk/archive/2009/11/25/silverlight-and-ria-adding-a-combobox-to-a-dataform.aspx