共用方式為


Line of Business Sample

Microsoft Silverlight will reach end of support after October 2021. Learn more.

This topic provides an overview of a line of business sample named Issue Tracker that is built using some of the new data-centric features of Silverlight 4. The Issue Tracker sample allows you to create new issues, open existing issues, and edit issues in a database. Issue Tracker also demonstrates some of the reporting features in Silverlight. The following illustration shows how this sample looks:

Home page

To try this sample, click the following link and follow the steps in Readme.

Download this sample

Prerequisites

You need the following components to build the Issue Tracker sample:

  • Silverlight 4 or later.

  • Silverlight Tools for Visual Studio 2010.

  • Visual Studio 2010.

  • Silverlight Toolkit

  • WCF RIA Services

  • WCF RIA Services Toolkit

All of the Silverlight software is available from the Silverlight download site.

Silverlight Features in IssueTracker

The Issue Tracker sample uses some of the new features in Silverlight, the Silverlight Toolkit, and WCF RIA Services. The following table summarizes these features:

Feature

Where used in IssueTracker

More Information

RichTextBox

Used to enter issue description and repro steps.

RichTextBox Overview

RichTextBox

Charts

Used to display the issue trends in the Reports page.

Silverlight Toolkit

Themes

Used to customize the look of the application.

Silverlight Toolkit

Data validation

Used to validate user input for various fields of an issue.

INotifyDataErrorInfo

Commanding

Used to associate actions to Save and Create buttons that are used in saving and creating issues.

ICommand

Drag-and-drop

Used to add files to an issue by dragging and dropping them onto an issue.

FileInfo

Drop

DragEventArgs

WCF RIA Services

Used to link the Silverlight application to the database in the ASP.NET application.

WCF RIA Services Documentation

ContextMenu

The themes that are used to customize the look of the application are displayed in a context menu.

Silverlight Toolkit

Printing

Used to print reports about bug trends.

Printing

PrintDocument

DataBinding: Selector support

Used to data bind the SelectedValue property of a few combo boxes, such as the Status combo box.

SelectedValue

DataBinding: Specify default display values for a data bound control

Used to set the default display value for some controls, such as the Opened By combo box.

FallbackValue

TargetNullValue

DataBinding: Group collection items

Used to group the list of platforms for an issue by processor type.

GroupDescriptions

DataBinding: String formatting

Used to display the formatted text in the text block that displays the Issue ID.

StringFormat

IssueTracker Overview

The Issue Tracker sample consists of the following components:

  • A Silverlight application named IssueTracker, which is the client application. This project forms the presentation layer where you can create, update, and display issues.

  • An ASP.NET server project named IssueTracker.Web that hosts the backend database and the middle-tier code.

  • A WCF RIA Services link that enables the client to access the data on the server.

IssueTracker Client Application

The IssueTracker client is a Silverlight application that enables you to perform the following functions:

  • Create a new issue and specify various attributes, such as issue title, priority, severity, description, and assigned user.

  • Update and save changes to existing issues.

  • View all issues or issues assigned to you.

  • View and print reports on bug trends.

  • Attach files to an issue by using the drag-and-drop.

  • Perform input validation for data entry.

  • Customize the look of the application by applying the appropriate theme from the context menu.

The UI of the Issue Tracker sample is divided into a navigation toolbar and a view that is displayed based on the option that you select in the navigation toolbar. The navigation toolbar is implemented by using a StackPanel element that has HyperlinkButton objects for the menu items. The following are the menu items:

  • Home

  • New Issue

  • All Issues

  • My Issue

  • Reports

When you click any of these menu items, the corresponding view is loaded. The following XAML shows how the New Issue menu item is implemented by using a HyperlinkButton control.

<HyperlinkButton x:Name="Link3" Style="{StaticResource LinkStyle}" NavigateUri="/NewIssue" TargetName="ContentFrame" Content="new issue" />

You can customize the look of the Issue Tracker sample by using the context menu that appears when you right-click anywhere on the sample. This is implemented by using Themes and ContextMenu controls, which are available in the Silverlight Toolkit.

The sample also has a busy indicator that is displayed when the IssueTracker client is in the process of communicating with the IssueTracker.Web service. This is implemented by using the BusyIndicator control, which is also in the Silverlight Toolkit.

For more information about using a Silverlight client for RIA Services applications, see Silverlight Clients.

IssueTracker.Web ASP.NET Application

The IssueTracker.Web is an ASP.NET application that hosts a database named IssueTrackerData.mdf and the middle-tier logic that manages the interaction between the IssueTracker client application and the database.

The following table lists the various tables that are present in this database.

Table Name

Description

Attributes

Stores attributes that are associated with issues. Each attribute consist of a name/value pair.

Files

Stores file name and file data that are uploaded for a given issue.

Issues

Stores the list of issues and its associated details, such as priority and severity.

Platforms

Stores the list of permitted platforms, such as Windows7 or Windows Vista.

Resolutions

Stores the list of resolution types, such as Fixed or Duplicate.

Statuses

Stores the list of statuses that can be associated with an issue such as Active, Resolved, or Closed.

Users

Stores the list of permitted users who can create and modify issues.

To create a RIA Services link between the server and the Silverlight client, you need an entity model that represents the tables and columns present in the database. An entity model represents the tables and columns as CLR types (tables are represented as classes and columns and foreign key associations are represented as properties). In this project, the Link to SQL entity model is used and is represented by the IssueTrackerData.Dbml file.

Once you have the entity model, you can create a RIA Services link by adding a LinqToSqlDomainService class to the server. You use the Add New Domain Service Class dialog box to do this. In this dialog box, you can select the types (that represents the tables) in the entity model that you want to include in the domain service. You must select the Enable Editing check box to enable a two-way data binding between the client and the server. The domain service class for this project is the IssueTrackerDomainService class. Creating the Domain Service auto-generates the methods for querying, updating, and deleting rows on each table associated with it. It also makes the same methods available on the Silverlight client project as the IssueTrackerDomainContext class. You modify these methods to customize the result of the queries. For more information about creating a domain service class, see Walkthough: Creating a RIA Services Solution. For more information about Domain Services, see Domain Services.

Querying and Displaying Issues

You can use the Silverlight client to view all issues present in the database or just the issues assigned to you. You can use the All Issues page to view all the issues present in the database. The following illustration shows the All Issues page.

All Issues Page

This page is implemented in the AllIssues.xaml file. The AllIssues.xaml file is a Page control that consists of a DataGrid control that displays all the issues in the database and a user control named IssueEditor that displays the details of an issue that is currently selected in the DataGrid. The page also has Save and Cancel buttons.

The DataGrid control is bound to the GetIssues query by using the RIA Services DomainDataSource control, as shown in the following XAML.

<sdk:DataGrid AutoGenerateColumns="False" ItemsSource="{Binding ElementName=issueDomainDataSource, Path=Data}" ...
...
<riaControls:DomainDataSource AutoLoad="True" Height="0" Name="issueDomainDataSource" QueryName="GetIssuesQuery" Width="0" DomainContext="{StaticResource issueTrackerData}" />
...

The GetIssues query is auto-generated in the IssueTrackerDomainService.cs file when you create the domain service. By default, the query returns all values for all columns in the Issue table. You can modify this query to include the data in other tables that are referenced by foreign keys in the Issues table. For example, ChangedByID, which is a foreign key in the Issues table, refers to the UserID in the Users table. So, the row referenced by this ChangedByID value is also included in the GetsIssues query. The following code shows the GetIssues query.

public IQueryable<Issue> GetIssues()
{
    var loadOptions = new DataLoadOptions();
    loadOptions.LoadWith<Issue>((i) => i.Files);
    loadOptions.LoadWith<File>((f) => f.Data);
    loadOptions.LoadWith<File>((f) => f.FileName);
    loadOptions.LoadWith<Issue>((i) => i.AssignedTo);
    loadOptions.LoadWith<Issue>((i) => i.Attributes);
    loadOptions.LoadWith<Issue>((i) => i.ChangedBy);
    loadOptions.LoadWith<Issue>((i) => i.IssueType);
    loadOptions.LoadWith<Issue>((i) => i.OpenedBy);
    loadOptions.LoadWith<Issue>((i) => i.Platform);
    loadOptions.LoadWith<Issue>((i) => i.Resolution);
    loadOptions.LoadWith<Issue>((i) => i.Status);
    DataContext.LoadOptions = loadOptions;
    return this.DataContext.Issues;
}

The My Issues page is similar to the All Issues page except for the GetMyIssues query that is used to populate the DataGrid. The GetMyIssues query filters issues assigned to the current user. The following illustration shows the My Issues page.

My Issues Page

When you select an issue in the DataGrid, the IssueEditor control is populated with the details of the issue that is selected. The currently selected issue is passed as a DataContext to the IssueEditor control, as shown in the following XAML.

<my:IssueEditor Grid.Row="2" DataContext="{Binding Data.CurrentItem, ElementName=issueDomainDataSource}" x:Name="issueEditor1" />

The IssueEditor is a user control that is used to view and update details of an issue. The IssueEditor control is used to view and update issues in the All Issues page, view and update issues assigned to you in the My Issues page, and create a new issue in the New Issue page. The IssueEditor control is composed of many Silverlight controls that are bound to and display various columns in the Issues table. The following sections describe how the Issue Title, Status, Assigned To and Platform areas in the IssueEditor are implemented.

Issue Title

The issue title is displayed in a TextBox control that is bound to the Title column of the currently selected issue in the Issues table as shown in the following XAML.

<TextBox Name="textBox4" Grid.Column="1" Text="{Binding Path=Title, Mode=TwoWay, NotifyOnValidationError=True}" VerticalAlignment="Center" />

Status

The issue status is displayed in a ComboBox control. The items in the ComboBox control are bound to the Status table and the SelectedValue of the ComboBox is bound to the status of the currently selected issue. The following XAML shows how this is implemented.

<ComboBox DisplayMemberPath="Name" ItemsSource="{Binding Status, Source={StaticResource issueTrackerData}}" Name="comboBox7" SelectedValue="{Binding StatusID, Mode=TwoWay}" SelectedValuePath="StatusID" ...

Assigned To

The Assigned To value is the name of the person to whom the issue is currently assigned. The Assigned To value is displayed in a ComboBox control that is bound to the Users table. The user information obtained from the Users table is then displayed in "UserID-FirstName LastName" format using a DataTemplate. The SelectedValue of the ComboBox is bound to the user to whom the currently selected issue is assigned. The following XAML shows how this is implemented.

<ComboBox ItemTemplate="{StaticResource UserItemTemplate}"
ItemsSource="{Binding Users, Source={StaticResource issueTrackerData}}" SelectedValue="{Binding AssignedToID, Mode=TwoWay, TargetNullValue=Active}" SelectedValuePath="UserId" ...
...
<DataTemplate x:Key="UserItemTemplate">
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding UserId}" />
        <TextBlock Text=" - " />
        <TextBlock Text="{Binding FirstName}" />
        <TextBlock Text=" " />
        <TextBlock Text="{Binding LastName}" />
    </StackPanel>
</DataTemplate>

Platform

The platform information contains Operating System, Browser, and Language options that can be associated with an issue. The platform information is displayed in a DataGrid control that is bound to the Platforms table in the database, as shown in the following XAML.

<sdk:DataGrid Name="dataGrid3" IsReadOnly="True" ItemsSource="{Binding Source={StaticResource platformViewSource}}" SelectedItem="{Binding Platform, Mode=TwoWay}" ...
    <sdk:DataGrid.Columns>
        <sdk:DataGridTemplateColumn Header="Operating System" Width="*" SortMemberPath="OSVersion">
            <sdk:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                        <TextBlock Text="{Binding OS}" />
                        <TextBlock Text=" " />
                        <TextBlock Text="{Binding OSVersion}" />
                    </StackPanel>
                </DataTemplate>
            </sdk:DataGridTemplateColumn.CellTemplate>
        </sdk:DataGridTemplateColumn>
    <sdk:DataGridTemplateColumn Header="Browser" Width="*" SortMemberPath="BrowserVersion">
    <sdk:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal"
 VerticalAlignment="Center">
                <TextBlock Text="{Binding Browser}" />
                <TextBlock Text=" " />
                <TextBlock Text="{Binding BrowserVersion}" />
            </StackPanel>
        </DataTemplate>
    </sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
    <sdk:DataGridTextColumn Header="Language" Width="SizeToCells" Binding="{Binding Language}" />
</sdk:DataGrid.Columns>
</sdk:DataGrid>

The platform information is grouped by processor name. This is implemented by using a CollectionViewSource, as shown in the following XAML.

<CollectionViewSource x:Key="platformViewSource" Source="{Binding Platforms, Source={StaticResource issueTrackerData}}" d:DesignSource="{d:DesignInstance my:Platform, CreateList=True, IsDesignTimeCreatable=True}">
    <CollectionViewSource.GroupDescriptions>
        <PropertyGroupDescription PropertyName="Processor" />
    </CollectionViewSource.GroupDescriptions>
</CollectionViewSource>

Creating and Updating Issues

You can use the New Issue page to create a new issue. The following illustration shows the New Issue page.

New Issue Page

The New Issue page uses the IssueEditor control so that you can enter the details of the issue you are creating. The various Silverlight controls present in the IssueEditor control are two-way data bound to enable saving the changes made in the client to the database. You can create and save a new issue by entering the issue details and clicking the Submit button. The submit button is associated with a Command named AddIssue, as shown in the following XAML.

<Button Content="Submit" Command="{Binding AddIssue, Source={StaticResource issueTrackerData}}" CommandParameter="{StaticResource newIssue}"  ...

The Command property is bound to the AddIssue command, which is of type ICommand. The AddIssue class implements the ICommand interface as shown in the following code.

private class AddIssueCommand : ICommand
{
    ...
    #region ICommand Members
    public bool CanExecute(object parameter)
    {
        return parameter != null && parameter is Issue && !dc.IsLoading && !dc.IsSubmitting;
    }

    public event EventHandler CanExecuteChanged;
    public void Execute(object parameter)
    {
        ...
         dc.Issues.Add(parameter as Issue);
         (parameter as IEditableObject).EndEdit();
         dc.SubmitChanges();
    }
    #endregion
}

The CanExecute method is used to verify if the AddIssue command can be executed at this time. This method checks if there are any ongoing loading or submitting operation in the RIA Services link. The Execute method calls an EndEdit method on the new issue, adds the issue to the Issues table, and submits the pending changes to the RIA Services link.

You can make changes and update an existing issue by clicking the Save button in the All Issues and My Issues pages. The Save button is associated with a Command named SaveChanges, as shown in the following XAML.

<Button Content="Save" Command="{Binding SaveChanges, Source={StaticResource issueTrackerData}}" CommandParameter="{Binding Data.CurrentItem, ElementName=issueDomainDataSource}" ...

The SaveChanges class implements the ICommand interface, as shown in the following code.

private class SaveChangesCommand : ICommand
{
    ...
    #region ICommand Members
    public bool CanExecute(object parameter)
    {
        return dc.HasChanges && !dc.IsLoading && !dc.IsSubmitting;
    }
    public event EventHandler CanExecuteChanged;
    public void Execute(object parameter)
    {
        ...
        if (parameter is Entity)
        {
            (parameter as IEditableObject).EndEdit();
        }
        dc.SubmitChanges();
    }
    #endregion
}

The CanExecute method is called to verify if the SaveChanges command can be executed at this time. This method checks if there are any changes that have to be saved and if there are any loading or submitting operation on the RIA Services link. The Execute method calls an EndEdit method on the current issue and submits the pending changes to the RIA Services link.

Validation and Error Handling

The Issue Tracker sample validates the data input in three controls in the user interface. The following is the validation that is performed:

  • Validate that the priority of an issue is not lower that its severity (that is, the numerical value of priority is not higher than that of severity). The following illustration shows the priority validation.

    Validation

    If the priority of an issue is greater than its severity, a validation error is displayed in two places in the UI. An error message is displayed next to the Priority ComboBox and in a ValidationSummary control at the bottom of the IssueEditor control.

  • Validate that the title of an issue is not contained in another issue’s title.

    If the title of an issue is contained in another issue's title, validation errors are displayed next to the Priority ComboBox and in a ValidationSummary control at the bottom of the IssueEditor control.

The Issue class implements the INotifyDataErrorInfo interface to validate the previous error conditions. The INotifyDataErrorInfo interface provides an ErrorsChanged event, GetErrors method and a HasErrors property. The Priority, Severity, Title and the ValidationSummary controls use these APIs to implement the error validation.

Title Validation

As mentioned, a validation error is displayed when the title of an issue is contained in the title of another issue. The IssueTracker sample performs a query to check if the current issue title is a subset of any of the issues in the database. This is implemented in the IssueTrackerDomainService class, as shown in the following code.

public Issue[] GetSimilarIssues(string issueTitle)
{
    return (from i in this.DataContext.Issues where i.Title.Contains(issueTitle) && !i.Title.Equals(issueTitle)select i).ToArray();
}

If the previous query returns issues, an error string is constructed, the error string is added to an error list, and the ErrorsChanged event is raised. This is shown in the following code.

partial void OnTitleChanged()
{
    ...
    var data = new IssueTrackerDomainContext();
    var getRelated = data.Load(data.GetSimilarIssuesQuery(this.Title));
    ...
    if (getRelated.Entities.Count() > 0)
    {
        StringBuilder sb = new StringBuilder();
        sb.Append("Warning: The following issues may be related to this one: ");
        foreach (var issue in getRelated.Entities)
        {
            sb.AppendFormat("{0}, ", issue.IssueID);
        }
        sb.Remove(sb.Length - 2, 2);
        AddValidationError("Title", sb.ToString());
    }
}

This ErrorsChanged event is handled by the title TextBox by setting the NotifyOnValidationError property to true, as shown in the following XAML.

<TextBox Text="{Binding Path=Title, Mode=TwoWay, NotifyOnValidationError=True}" ...

The GetErrors method returns an IEnumerable list of error messages that are present for a given field. The HasErrors property indicates if there are any errors associated with the Issue class. The following code shows how the GetErrors method and the HasErrors property is implemented by the Issue class.

public System.Collections.IEnumerable GetErrors(string propertyName)
{
    if (errors != null && errors.ContainsKey(propertyName))
    return errors[propertyName];
    return null;
}

public bool HasErrors

    get
    {
        return (from errorList in errors.Values where (from e in errorList where !(e is string && ((string)e).StartsWith("Warning: ")) select e).Count() > 0 select errorList).Count() > 0;
    }
}

Priority/Severity Validation

A validation error is displayed if you set the priority of an issue to be lower than its severity. The following code shows how this validation is implemented.

private void CheckPriorityAndSeverity()
{
    if (this.Priority > this.Severity)
    {
        AddValidationError("Priority", priSevError);
        AddValidationError("Severity", priSevError);
    }
    else
    {
        RemoveValidationError("Priority", priSevError);
        RemoveValidationError("Severity", priSevError);
    }
}

The ErrorsChanged event is raised by setting the NotifyOnValidationError property of the priority and severity ComboBox to true.

See Also

Concepts