Up Start code samples
DevForce 2010 Resource Center » Code samples » Start code samples » Code sample: Hello DevForce (WPF)

Code sample: Hello DevForce (WPF)

Last modified on August 15, 2012 17:21

"Hello, DevForce" is a small application which walks you through many of the common steps in building a DevForce application.


Problem

The challenge:  you'd like to build a DevForce desktop application but don't know where to begin. Maybe you've seen the quickie demo and tour, but want a few more details before you build your own application.
 

Solution

"Hello, DevForce" is a simple WPF application which is not about the UI.  Instead, it shows how to choose a new project template, how to add an entity model and some simple logic, how to monitor activity, and how to run the application n-tier.  All in a few easy to follow steps.

Create a solution using a DevForce project template

DevForce installs several Visual Studio project templates, a few of which are shown below:

output_html_m32bafa2a.png

In this walk through we'll use the DevForce n-Tier WPF Application template. 

In Visual Studio, choose File / New Project from the main menu; under Visual Basic or C#, find the DevForce 2010 templates; select the DevForce n-Tier WPF Application template; name and locate the new solution (all as shown in the previous screen shot), and click OK. You should see something similar to the following:

output_html_mda856b8.png

The template created a WPF project named “WpfApplication1”, whose resulting assembly is destined for deployment on the client computer. Since we selected the n-Tier WPF application, the template also created a web project (“WpfApplication1Web”) that will ultimately be deployed on a web server and will host the DevForce EntityServer. Note, however, that it will be perfectly possible to run, and develop, this application on a single machine: to do so only requires changing the setting of a single property in the app.config file. We’ll do that later.

Add the entity model

Our next job will be to add the entity model. In a DevForce application, both the server-side and client-side applications need access to the domain model. We can accomplish that in a couple of different ways:

  1. We can create the model in the web project, and then add “shared file” links in the WPF project; or
  2. We can create a separate project for the model, then add references to that project in the web and Wpf Application projects (which already exist now in our solution).

Both options are perfectly fine, but we have to choose one, so we’re going to create a separate project for the model.

Create a new Windows class library project in the solution. Name it “DomainModel”.

output_html_6933b934.png

Delete the “Class1.cs” file that Visual Studio creates; we won’t need it.

To the DomainModel project, add a new item, an ADO.NET Entity Data Model. Name the file “NorthwindIBModel”.

output_html_2b303ccc.png

Clicking <Add> will launch a wizard to help you build your Entity Data Model. On the first dialog, accept the default choice of “Generate from database”:

ChooseModelContents.png

On the “Choose Your Data Connection” dialog, select or create a connection to the NorthwindIB database, and tell Visual Studio to save the connections as “NorthwindIBEntityManager”:

output_html_43ba643f.png

The EDM Wizard retrieves schema information from the database and presents you with a tree control of choices for items on which to base entities in your model. Expand the Tables node and select the following tables:

  • Customer
  • Employee
  • Order
  • OrderDetail
  • Product
  • Supplier

Accept the default settings for the two checkboxes:

output_html_553bafaf.png

Also accept the default model namespace of “NorthwindIBModel” (the name we specified earlier for the model itself).

output_html_m75cc140a.png

If you receive the following security warning message…

output_html_113285f.png

… click <OK> so that DevForce can use the Entity Data Model to generate a C# or VB domain model. Until and if you turn this warning off, you will see it whenever DevForce wants to regenerate your domain model code.

The EDM designer will create an XML file with extension .edmx to capture the information you (and the database) supplied using the wizard. Then it will render the newly created model visually:

Picture43.png

If you open the Properties panel you will see, in addition to standard Entity Framework properties, many DevForce-specific ones: If you cannot see the DevForce-specific options, please verify that the DevForce EDM Designer Extension is installed.

If you click in white space in the designer window, you see the properties that apply to the model as a whole:

Picture2.png

If you click on a single entity, such as Customer, you see a difference set of properties, including DevForce-specific properties:

output_html_7fc1165f.png

These properties allow you to specify authorization requirements for querying and saving the entity. This is discussed in more detail in a security-oriented Learning Resources example.

Click on a specific property such as the primary key CustomerID

Picture3.png

… or on a navigation property, such as Customer.Orders

output_html_m3f08c4fc.png

… to see the Entity Framework and DevForce properties specific to that item.

As you can see, DevForce is completely integrated with the Entity Model Designer. It also takes over all code generation for the .NET object model. If you select the NorthwindIBModel.edmx file in the Solution Explorer, then right-click it and select Properties, you can see that DevForce has removed the default Custom Tool name so that the default code generator won’t be used:

output_html_cfc42d2.png

If at any time you wish to restore use of the default (Entity Framework) code generator, open the EDMX in the designer and set the “DevForce Enabled” property to false, then save the model. This will remove the DevForce-generated files and re-enable Entity Framework code generation.

Now let’s have a look at the generated code.

output_html_m20516b53.png

Three files now accompany the .edmx:

NorthwindIBModel.edmx.tt The template used in the generation of the domain model to NorthwindIBModel.IB.Designer.cs.

Picture64.png

NorthwindIBModel.edmx.ReadMe describes how to customize the DevForce code generation templates.

NorthwindIBModel.IB.Designer.cs contains the.NET code generated by DevForce:

output_html_16c4e576.png

Note how, at the very top of this file, you are instructed not to modify this code. DevForce owns it, and will regenerate it at will to keep up with changes you make to the Entity Data Model. You’ll do your customization in “developer partial class” files; we’ll discuss those shortly. But for now, have a look at some of the items in the generated code:

output_html_mbffd270.png

You see entity classes such as Customer, Employee, Order, and OrderDetail. Associated with each of these entities is an EntityPropertyNames class, and a PropertyMetadata class. The EntityPropertyNames class provides string constants for the names of each of the entity’s properties: you can use these throughout your own code, wherever a property name is needed, in preference to hard-coding the string value. The PropertyMetadata class contains static EntityProperty fields corresponding to each of the entity’s business properties, which you can use in your code when an EntityProperty is required.

The main entity classes, such as Customer, inherit from IdeaBlade.EntityModel.Entity, and contain definitions of all of the entity’s business properties. Many other facilities are available via the Entity base class.

Before we leave our Entity Data Model, let’s fix a couple of property names. The Employee entity, like the Employee table in the NorthwindIB database, has an association with itself, in order to reflect the chain of management. This self-association yields two navigation properties, one to represent the Employee upstream from the current one – i.e., his Manager – another to represent those downstream – his DirectReports. The EDM designer knew from the discovered relationship that two such properties were needed, but it didn’t know enough about what they actually represent to name them very well, so it just called them Employee1 and Employee2.

By selecting the Employee1 property and viewing its properties, we see that its Return Type is a Collection of Employee:

output_html_m1555200.png

Employee1 must therefore represent the downstream Employees; we’ll rename it to “DirectReports”. Double-checking the Employee2 properties verifies what we expect – that it returns an Instance of Employee – so we know that one represents the Manager and rename it accordingly.

output_html_5198c7e3.png

We also happen to know that the Employee navigation property on the Order entity, which was automatically added to the model upon discovery of the many-to-one relationship in NorthwindIB between Orders and Employees, represents an employee who acts in the role of a sales representative for that Order. We’ll rename it to “SalesRep”.

Picture1.PNG

Write some code

There’s a great deal more we could, and eventually will, do to refine our model, but it’s already sufficient fleshed out to support data retrieval and persistence, so let’s see how we can use it to do that.

  1. Build the DomainModel project.
  2. Add references to the DomainModel assembly in the WpfApplication1 and WpfApplication1Web projects.

You may note that both of those projects already have references to several DevForce assemblies. These were put there by the project template that you used to create the solution initially.

The DevForce project template created a WPF Application project, so let’s add some code to that. First we’ll flesh out – ever so slightly -- the MainWindow window that the template created, by adding a TextBlock within a ScrollViewer inside the window’s layout grid.

The XAML we’re starting with is this:

XAML
<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" style="width:525px">
   <Grid>
   </Grid>
</Window>

We’ll enhance that to this:

XAML
<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" style="width:525px">
    <Grid>
        <ScrollViewer>
            <TextBlock Name="_outputTextBlock" TextWrapping="Wrap"
                FontFamily="Courier New" />
        </ScrollViewer>
    </Grid>
</Window>

For the purpose of this very simple application, we’re going to use that TextBlock control like the output window of a Console application, and simply write output strings to it. (We can get more sophisticated later, with streaming video, cascading 3D animations, chirping monkeys, and touch-screen facilities!)

We could, of course, put code to retrieve and display data in the “code behind” area of the Window itself, but let’s just put ourselves off to a good start and build a very basic Model-View-ViewModel architecture so we can (a) keep our view extremely lightweight and (b) minimize the obstacles to our application’s testability.

We’ll create a class we’ll call MainWindowViewModel, give it a public string property named Output, and just bind our TextBlock to the value of that property:

XAML
<Grid >
    <ScrollViewer>
        <TextBlock Name="_outputTextBlock" TextWrapping="Wrap"
            FontFamily="Courier New" Text="{Binding Output}" />
    </ScrollViewer>
</Grid>

The MainWindow’s code behind starts out like this:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication1 {
 /// <summary>
 /// Interaction logic for MainWindow.xaml
 /// </summary>
 public partial class MainWindow : Window {
   public MainWindow() {
      InitializeComponent();
    }
  }
}
VB
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Data
Imports System.Windows.Documents
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Imaging
Imports System.Windows.Navigation
Imports System.Windows.Shapes
Namespace WpfApplication1
''' <summary>
''' Interaction logic for MainWindow.xaml
''' </summary>
Partial Public Class MainWindow
Inherits Window
  Public Sub New()
 InitializeComponent()
  End Sub
End Class
End Namespace

We’ll add a handler for the window’s Loaded event to do all of the following:

  • Instantiate the view model;
  • Set the view’s DataContext to that view model; and
  • Call a Start() menu on the view model to initiate data retrieval and display.
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication1 {
 /// <summary>
 /// Interaction logic for MainWindow.xaml
 /// </summary>
 public partial class MainWindow : Window {
   public MainWindow() {
      InitializeComponent();
     this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
    }
   void MainWindow_Loaded(object sender, RoutedEventArgs e) {
      MainWindowViewModel aMainWindowViewModel =
       new MainWindowViewModel();
     this.DataContext = aMainWindowViewModel;
      aMainWindowViewModel.Start();
    }
  }
}
VB
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Data
Imports System.Windows.Documents
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Imaging
Imports System.Windows.Navigation
Imports System.Windows.Shapes
Namespace WpfApplication1
 ''' <summary>
 ''' Interaction logic for MainWindow.xaml
 ''' </summary>
 Partial Public Class MainWindow
   Inherits Window
   Public Sub New()
      InitializeComponent()
     AddHandler Loaded, AddressOf MainWindow_Loaded
   End Sub
   Private Sub MainWindow_Loaded(ByVal sender As Object, _
     ByVal e As RoutedEventArgs)
     Dim aMainWindowViewModel As New MainWindowViewModel()
     Me.DataContext = aMainWindowViewModel
      aMainWindowViewModel.Start()
   End Sub
 End Class
End Namespace

Note that we did not have to know that the view’s Loaded event handler had to be a RoutedEventHandler, etc.; we simply typed in “this.Loaded +=”, and then pressed the TAB key a couple of times to let Visual Studio set up an appropriate stub. We then filled in the code representing the actions we wanted to take place.

Now let’s create that MainWindowViewModel class. Add a new class by that name to the WpfApplication1 project and flesh out its code to look like this:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SysComp = System.ComponentModel;
using DomainModel;
using IdeaBlade.EntityModel;
namespace WpfApplication1 {
 class MainWindowViewModel : SysComp.INotifyPropertyChanged {
   #region Constructor
   public MainWindowViewModel() {
    }
   #endregion Constructor
   #region Methods
   public void Start() {
      FirstSample();
    }
   public void FirstSample() {
      StringBuilder aStringBuilder =
       new StringBuilder("Started FirstSample()...\n");
      var customersQuery =
        from cust in _mgr.Customers
        where cust.ContactTitle == "Sales Representative"
        orderby cust.CompanyName
        select cust;
      aStringBuilder.Append(string.Format(
       "Retrieved {0} customers\n",
      customersQuery.ToList().Count));
     foreach (Customer aCustomer in customersQuery) {
        aStringBuilder.Append(string.Format("Customer: {0}\n",
          aCustomer.CompanyName));
      }
      Output = aStringBuilder.ToString();
    }
   #endregion Methods
   #region Data Bound Properties
   public string Output {
     get {
       return _output;
      }
     set {
        _output = value;
        RaisePropertyChanged("Output");
      }
    }
   #endregion Data Bound Properties
   #region INotifyPropertyChanged
   public event SysComp.PropertyChangedEventHandler PropertyChanged
      = delegate { };
   protected void RaisePropertyChanged(string propertyName) {
      PropertyChanged(this, new SysComp.PropertyChangedEventArgs(
        propertyName));
    }
   #endregion
   #region Private Fields
   private NorthwindIBEntityManager _mgr =
     new NorthwindIBEntityManager();
   string _output = "";
   #endregion Private Fields
 }
}
VB
Imports Microsoft.VisualBasic
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports DomainModel
Imports IdeaBlade.EntityModel
Imports System.ComponentModel

Namespace WpfApplication1
 Friend Class MainWindowViewModel
   Implements INotifyPropertyChanged
#Region "Constructor"
   Public Sub New()
   End Sub
#End Region ' Constructor
#Region "Methods"
   Public Sub Start()
      FirstSample()
   End Sub
   Public Sub FirstSample()
     Dim aStringBuilder As New StringBuilder( _
       "Started FirstSample()..." & vbLf)
     Dim customersQuery = From cust In _mgr.Customers
        Where cust.ContactTitle = "Sales Representative"
        Order By cust.CompanyName
       Select cust
      aStringBuilder.Append(String.Format("Retrieved {0} customers" _
        & vbLf, customersQuery.ToList().Count))
     For Each aCustomer As Customer In customersQuery
        aStringBuilder.Append(String.Format("Customer: {0}" _
          & vbLf, aCustomer.CompanyName))
     Next aCustomer
      Output = aStringBuilder.ToString()
   End Sub
#End Region ' Methods
#Region "Data Bound Properties"
   Public Property Output() As String
     Get
       Return _output
     End Get
     Set(ByVal value As String)
        _output = value
        RaisePropertyChanged("Output")
     End Set
   End Property
#End Region ' Data Bound Properties

#Region "INotifyPropertyChanged"
   Public Event PropertyChanged As  _
      PropertyChangedEventHandler _
       Implements INotifyPropertyChanged.PropertyChanged
   Protected Sub RaisePropertyChanged(ByVal propertyName As String)
     RaiseEvent PropertyChanged(Me, _
       New PropertyChangedEventArgs(propertyName))
   End Sub
#End Region
#Region "Private Fields"
   Private _mgr As New NorthwindIBEntityManager()
   Private _output As String = ""
#End Region ' Private Fields
 End Class
End Namespace

Note a few things about our ViewModel:

  1. We made the class implement the INotifyPropertyChanged from the .NET namespace System.Component model. That interface requires the definition of an event name “PropertyChanged”, to be raised by an instance of this (MainWindowViewModel) class whenever the value of one of its public properties changes.
  2. We’ve defined exactly one such public property – a string property named “Output” – which when set will cause the PropertyChanged event to be fired. Most WPF and Silverlight user interface controls listen for this event and respond to it by refreshing themselves with current data from the source object to which they are bound. In our app, the TextBlock on MainWindow will get refreshed automatically whenever the value of Output is changed.

Beyond that, our view model, when the Start() method is run, simply submits a LINQ query requesting Customers who meet some condition; then uses that query to retrieve, first a count of such Customers, and then the Customer objects themselves. With each operation it writes text to the Output property.

Run the app

We’re almost ready to go, but we must make a couple of tweaks to the client-side App.config file to run the application initially in a single tier (or in a client-server configuration if your database is located elsewhere). Once we’ve tested in a single tier, we’ll switch to n-tier to show how simply that can be accomplished. The client-side App.config for the single-tier or client-server app looks like this:

XML
<?xml version="1.0"?>
<configuration>
    <configSections>
        <section name="ideablade.configuration" type=
           "IdeaBlade.Core.Configuration.IdeaBladeSection, IdeaBlade.Core"/>
    </configSections>
    <ideablade.configuration version="6.00"
        xmlns="http://schemas.ideablade.com/2010/IdeaBladeConfig">
        <logging logFile="DebugLog.xml"/>
        <objectServer remoteBaseURL="http://localhost"
            serverPort="9009" serviceName="EntityService.svc" >
            <clientSettings isDistributed="true" />
        </objectServer>
       <!-- Additional configuration can be added to override defaults.
See the sample config files in the Learning Resources for more information.
-->

    </ideablade.configuration>
</configuration>

Change the line:

XML<clientSettings isDistributed="true"/>

to read:

XML<clientSettings isDistributed="false"/>

Then, at the same level as the <ideablade.configuration> section, add a <connectionStrings> section as shown below. You can find the <connectionString> section you need in the copy of App.Config in the DomainModel project, where it was written by the Entity Data Model designer when you built the model. Simply copy it from there and paste it into the App.config for the client project (WpfApplication1).

The completed App.config should look like this:

XML
<?xml version="1.0"?>
<configuration>
    <configSections>
        <section name="ideablade.configuration" type=
          "IdeaBlade.Core.Configuration.IdeaBladeSection, IdeaBlade.Core"/>
    </configSections>
    <ideablade.configuration version="6.00"
        xmlns="http://schemas.ideablade.com/2010/IdeaBladeConfig">
        <logging logFile="DebugLog.xml"/>
        <objectServer remoteBaseURL="http://localhost"
            serverPort="9009" serviceName="EntityService.svc" >
            <clientSettings isDistributed="false" />
        </objectServer>
       <!-- Additional configuration can be added to override defaults.
See the sample config files in the Learning Resources for more information.
-->

    </ideablade.configuration>
    <connectionStrings>
        <add name="NorthwindIBEntityManager" connectionString=
         "metadata=res://*/NorthwindIBModel.csdl|res:
             //*/NorthwindIBModel.ssdl|res://*/NorthwindIBModel.msl;
             provider=System.Data.SqlClient;provider connection string=
             &quot;Data Source=.;Initial Catalog=NorthwindIB;
             Integrated Security=True;MultipleActiveResultSets=
             True&quot;"
providerName="System.Data.EntityClient" />
    </connectionStrings>
</configuration>

Now we’re ready to run our app. A few seconds after launch, you should see the following display:

Picture41.png

Add some business logic

So far, so good, but now let’s see how we add custom logic to our business model. As we previously pointed out, the code that currently comprises the business model – all stored in the file NorthwindIBModel.IB.Designer.cs -- is owned entirely by DevForce and will be overwritten by DevForce whenever you change the content of the Entity Data Model (.edmx) file. That makes it a very bad place to put custom code! Where such code should be put is in partial class files that extend the existing model.

You can code such files yourself from scratch, but you can also have the code generator write starter versions for you. To do that:

  1. Open the Entity Data Model in the designer
  2. View the Properties panel in Visual Studio
  3. Click on empty white space in the designer window to see the properties of the Conceptual Entity Model.
  4. In the DevForce Code Generation section, find the property Generate Developer Classes, and set its value to true.
  5. Save the updated model (and respond to the Security Warning about the text template, if it appears, by clicking <OK>).

You will now see one new file in the DomainModel project for each of the entities that you included in your model, as at right.

Let’s look at the contents of the Customer.cs file:

output_html_536ac0a4.png

C#
using System;
using System.Linq;
using IbEm = IdeaBlade.EntityModel;
using IdeaBlade.Core;
namespace DomainModel {
 // The IdeaBlade DevForce Object Mapping Tool generates this class once
 // and will not overwrite any changes you make. You can place your custom
 // application-specific business logic in this file.
 // Generated: 3/31/2010 6:17:32 PM
 //
 // This partial class is the companion to the class regenerated by the
 // Object Mapping tool whenever the model changes.
 public partial class Customer : IbEm.Entity {
  }
}
VB
Imports System
Imports System.Linq
Imports IbEm = IdeaBlade.EntityModel
Imports IdeaBlade.Core
'The IdeaBlade DevForce Object Mapping Tool generates this class once
'and will not overwrite any changes you make. You can place your custom
'application-specific business logic in this file.
'Generated: 7/13/2010 11:54:13 AM
'
'This partial class is the companion to the class regenerated by the
'Object Mapping tool whenever the model changes.
Partial Public Class Product
 Inherits IbEm.Entity
End Class

That’s pretty unimposing. But since it’s a partial class it can be enhanced to extend the generated model almost without limit.

For example, let’s add a static Create() factory method so we can create and initialize new Customers in a standard way throughout our application.

First we add a statement to make items in the DomainModel namespace available within the class:

C#
using DomainModel;
VB
Imports DomainModel

Then we add the Create() method:

C#
static Customer Create(string companyName) {
  Customer newCustomer = new Customer();
  newCustomer.CustomerID = System.Guid.NewGuid();
  newCustomer.CompanyName = companyName;
  newCustomer.EntityAspect.AddToManager();
 return newCustomer;
}
VB
Private Shared Function Create(ByVal companyName As String) As Customer
 Dim newCustomer As New Customer()
  newCustomer.CustomerID = System.Guid.NewGuid()
  newCustomer.CompanyName = companyName
  newCustomer.EntityAspect.AddToManager()
 Return newCustomer
End Function

Our Create() method requires a company name. It instantiates a new Customer, and because the Customer type has a GUID primary key, the code assigns a primary key value. It then assigns the companyName value passed to the method to the corresponding property on the new entity, and then – very importantly – puts the new entity into the local cache and under an EntityManager’s control. It then returns a reference to the new entity to the calling method.

You can add other custom methods, and custom properties, as needed and as desired. But suppose what you want to do is to override the behavior of a generated property. You can’t override or shadow the property because it’s defined in the same class you’re working in. (Remember, you’re in a partial class that is extending the existing, generated partial class.)

So how do you accomplish what you need? The answer is to use a property interceptor. This is a bit of code that you provide and decorate with attributes that describe to DevForce your intentions for it. It is then called by DevForce at an appropriate point in a Get or Set process to alter the result of that operation. For example, here’s an interceptor that converts the value of Customer.Company to its uppercase equivalent during a Get operation:

C#
[AfterGet(EntityPropertyNames.CompanyName)]
public String UppercaseLastName(String companyName) {
 if (!String.IsNullOrEmpty(companyName)) {
   return companyName.ToUpper();
  } else {
   return String.Empty;
  }
}
VB
<AfterGet(EntityPropertyNames.CompanyName)> _
Public Function UppercaseLastName(ByVal companyName _
 As String) As String
 If Not String.IsNullOrEmpty(companyName) Then
   Return companyName.ToUpper()
 Else
   Return String.Empty
 End If
End Function

If this interceptor is defined within the Customer class, DevForce assumes it is meant to apply to Customers; and because we have said so in the [AfterGet] attribute with which we decorated our interceptor method, DevForce knows the interceptor is meant to be applied only to the CompanyName property.

Run the application again and you’ll see the result of adding the AfterGet interceptor:

output_html_m76dee3bc.png

You can define interceptors to be as general as you need: for example, you can provide an interceptor method that will apply to every property of Customer; or even to every property of every entity in your model! You can define interceptors that jump into the middle of a Get before a value is obtained from the entity instance (which you could use, for example, to prevent certain data from being made available to unauthorized users); after the value is obtained from the business object but before it is delivered to the requestor (as in the interceptor above); before a proposed new value is pushed into the business object for which it is targetted; and after it is so pushed. You can alter the process of getting and setting business object property values in any way you need to do it.

One final note on the developer classes: recall that we got DevForce to generate starter partial classes for extending the entities in our business model by setting the Generate Developer Classes property on the ConceptualEntityModel to true. Even though we haven’t set that value back to false, we’re in no danger of losing the customizations we just made to Customer.cs. DevForce will never overwrite an existing developer partial class.

Run in an N-tier configuration

Now we’re ready for the last step, running the application in multiple tiers. We created this application using the DevForce N-Tier WPF Application template, and we’ve got a web application project in our solution which we haven’t used yet. The web application project hosts the DevForce Business Object Server, and is almost ready to go, with a few more changes to the config files.

Client-side App.Config. We toggled the isDistributed flag off earlier to show the application running in a single tier. Let’s turn that flag back on now:

XML<clientSettings isDistributed="true"

This flag, along with the objectServer settings indicating the URL, port and name of the BOS, will tell the client application where the BOS is located. The BOS in the web application project here is by default also listening at this address, so no additional wiring is required.

XML
<objectServer remoteBaseURL="http://localhost" serverPort="9009"
    serviceName="EntityService.svc" >
    <clientSettings isDistributed="true" />
</objectServer>

Server-side Web.Config. We need to ensure the connectionStrings section is available here in the web.config, since data access will now be performed here on the EntityServer and not in the client application. We previously copied the connectionStrings from the Domain Model app.config into the executable project’s app.config; we can now copy (or move if you prefer) this information into the web.config. We don’t need any other changes in the web.config, so it should look something like this when done:

XML
<?xml version="1.0"?>
<configuration>
    <configSections>
        <section name="ideablade.configuration" type=
           "IdeaBlade.Core.Configuration.IdeaBladeSection, IdeaBlade.Core"/>
    </configSections>
    <connectionStrings>
        <add name="NorthwindIBEntityManager" connectionString=
            "metadata=res://*/NorthwindIBModel.csdl|res:
             //*/NorthwindIBModel.ssdl|res://*/NorthwindIBModel.msl;
             provider=System.Data.SqlClient;provider connection string=
             &quot;Data Source=.;Initial Catalog=NorthwindIB;
             Integrated Security=True;MultipleActiveResultSets=
             True&quot;"
providerName="System.Data.EntityClient" />
    </connectionStrings>
    <ideablade.configuration version="6.00"
        xmlns="http://schemas.ideablade.com/2010/IdeaBladeConfig" >
        <logging logFile="log\DebugLog.xml"/>
       <!-- Additional configuration can be added to override defaults.
See the sample config files in the Learning Resources for more information.
-->

    </ideablade.configuration>
</configuration>

With these changes we’re ready to fire up the application in n-tier! One more thing before hitting F5, double-check that the web project will start up automatically. The “Always Start When Debugging” flag should be True, to ensure the ASP.NET Development Server starts when the client project does.

Picture18.png

Now build and run, and you’ll see … the same application window you saw before. But there’s a difference this time, the application is now running in multiple tiers – the WPF client application is now communicating with the EntityServer, which is now responsible for communicating with the data tier. The EntityServer here is running on the same machine (localhost), but you can now deploy it to another machine when you’re ready. Deployment is a complex topic, covered separately, but you’ve now created an application which can run in either a single tier or n-tier at the flip of a switch!

Monitor activity

What is actually happening as we run the applications? When is it asking for data? What does the SQL look like?

SQL Profiler

We can always monitor activity on the SQL Server using SQL Profiler. Here we assume SQL Server 2008.

Launch Microsoft SQL Server Management Studio.

Select “Tools, SQL Server Profiler” from the menu.

Select “File , New Trace ..” from the Profiler menu and connect to your database server

Click [Run] on the [Trace Properties] dialog.

Return to Visual Studio and re-run the application [F5]

The trace window fills, showing us exactly how we’re hitting the database.

Picture12.png

DevForce Tracing

As your application runs DevForce generates trace messages containing information (and warnings) about its actions. These messages will help you diagnose and debug problems should they occur. You can also use the DebugFns.WriteLine and TraceFns.WriteLine methods to send your own debug and trace messages to DevForce.

By default DevForce writes these messages to a trace log file named DebugLog.xml in the executable directory.

Open Windows Explorer.

Navigate to the ..\bin\debug directory under the executable’s directory.

Launch DebugLog.xml.

The log appears in a browser window.

output_html_m4fa215d2.png

Each row speaks of some event during the life of the last application run. You’ll see database access events among other event occurring from the start of the application until it shuts down.

You can launch the DebugLog while the application is running and refresh the browser from time to time to see how the log is progressing as you move through the application.

DevForce TraceViewer

DevForce also supplies a sample utility, the TraceViewer, which comes in WinForms and WPF flavors. The TraceViewer affords a friendlier and more dynamic look at logged activity as it provides a “live” view of trace activity for your running application. You can launch the TraceViewer from the IdeaBlade DevForce/Tools menu. It can also be linked directly into your application. 

Created by DevForce on June 18, 2010 11:31

This wiki is licensed under a Creative Commons 2.0 license. XWiki Enterprise 3.2 - Documentation. Copyright © 2015 IdeaBlade