DevForce has been designed from the ground up by experienced enterprise application developers with several very important goals in mind:
- Make it (following Albert Einstein’s famous advice) “as simple as possible, but not simpler.”
- Support (and encourage) “separation of concerns” in the architecture: your business model (and all business logic) is entirely separate from your user interface, so you can reuse it across more than one user interface, and more than one application.
- “Don’t fence me in.” You build the application you want to build, with the functionality and user interface you need. We make that much easier for you, and we make sure your end product is truly industrial strength -- but we don’t take over, and we don’t get in your way. Not in the beginning, and – even more importantly – not later on, after you’ve invested so much into your development effort that changing course is no longer an option, however badly you wish you could do it.
So what’s it like to build an application with DevForce Classic? Let’s take a walk through the four simple steps:
- Create your data model from the database
- Add your business logic
- Auto-populate your User Interface from the data model
- N-tier deployment
Create your Data Model from the Database
You start by launching the DevForce Object Mapper from the Tools menu in Visual Studio, and indicating which database you want to use.[1]
Download DevForce
Next, select in the Object Mapper which database tables will serve as the basis for your business objects. After configuring these business objects, the Object Mapper will generate the base classes for your business logic and you will be able to add custom logic to the subclass.
Rename any of the business object classes if you like: at right we’re preparing to rename the business object based on the database’s OrderSummary table simply to “Order”:
You can rename any of the properties of your objects as well. You’re not stuck with the names used in your back-end data sources, which may reflect all kinds of naming conventions and practices that don’t work for you. The object model is your world, and you’re in charge!
DevForce also generates relation properties into your business classes. These properties reflect foreign key relationships discovered on the source database:
In the code you subsequently write against the model, you’ll be able to retrieve the line items for your Order simply by referencing the DevForce-generated OrderDetails
property of an Order: e.g., myOrder.OrderDetails
.
There’s much more you can do in the Object Mapper, including requesting that it automatically generate validation logic to enforce the constraints on your data that are imposed by the database schema: for example, allowing or disallowing nulls, enforcing column widths on strings, and so forth. But it doesn’t have to get any more complicated than you need it to be, and you can introduce detail incrementally. The Object Mapper round trips, so you can return to it throughout your application development cycle to add, remove, or modify your objects.
You can see more detail on the object mapping process by viewing the instructional videos posted at
http://www.ideablade.com/DevForceClassic/DevForceClassic_videos.html
For now, let’s just save our work, and the DevForce Object Mapper will generate the .NET code for our object model:
Download DevForce
Add your Business Logic
Now you have a business object model: a place and structure for introducing your custom business logic. If you look closely at the generated files above, you’ll see that the Employee class – and indeed, each of the business object classes – has two generated parts. We call these the “developer class” (which is in the Employee.cs file) and the “generated” or “DataRow” class (in the EmployeeDataRow.cs file). The developer class is where you’ll put all of your custom business logic; whereas the “DataRow” class contains all of the basic properties and functionality of your business entity.
The developer class file (Employee.cs), once created, will be left alone by the Object Mapper. You make your changes and enhancements there, and the Object Mapper leaves them alone. The “DataRow” class file, by contrast, is entirely the property of the Object Mapper, and will be regenerated whenever you make changes to your model in the Object Mapper. This two-part structure is the key to your ability to “round trip” with the Object Mapper throughout the development cycle.
Let’s add a bit of business logic to our Employee
class. With comments stripped out, here’s what it looks like immediately after being generated:
using System;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using IdeaBlade.Persistence;
using IdeaBlade.Rdb;
using IdeaBlade.Persistence.Rdb;
using IdeaBlade.Util;
namespace Model {
[Serializable]
public sealed partial class Employee : EmployeeDataRow {
#region Constructors -- DO NOT MODIFY
private Employee() : this(null) {}
public Employee(DataRowBuilder pRowBuilder)
: base(pRowBuilder) {
}
#endregion
#region Suggested Customizations
#endregion
}
#region EntityPropertyDescriptors.EmployeePropertyDescriptor
#endregion
}
We’ll add some custom properties:
using System;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using IdeaBlade.Persistence;
using IdeaBlade.Rdb;
using IdeaBlade.Persistence.Rdb;
using IdeaBlade.Util;
namespace Model {
[Serializable]
public sealed partial class Employee : EmployeeDataRow {
#region Constructors -- DO NOT MODIFY
private Employee() : this(null) {}
public Employee(DataRowBuilder pRowBuilder)
: base(pRowBuilder) {
}
#endregion
#region Suggested Customizations
#endregion
#region Create Method
public static Employee Create(PersistenceManager pPersMgr,
String pFirstName, String pLastName, DateTime? pBirthDate) {
Employee aEmployee = (Employee)pPersMgr.CreateEntity(typeof(Employee));
pPersMgr.GenerateId(aEmployee, Employee.IdEntityColumn);
aEmployee.AddToManager();
aEmployee.FirstName = pFirstName;
aEmployee.LastName = pLastName;
aEmployee.BirthDate = pBirthDate;
return aEmployee;
}
#endregion
#region Properties
public override String LastName {
get { return base.LastName.ToUpper(); }
set { base.LastName = value; }
}
public String LastFirst {
get {
String aLastFirst = this.LastName + ", " + this.FirstName;
if (aLastFirst == ", ") aLastFirst = "(Not specified)";
return aLastFirst;
}
}
public int Age {
get {
if (null == BirthDate) return 0;
DateTime oBirthDate = (DateTime)this.BirthDate;
DateTime oToday = DateTime.Today;
int oAge = oToday.Year - oBirthDate.Year;
if (oBirthDate.AddYears(oAge) > oToday) oAge--;
if (oAge < 0) return 0;
else return oAge;
}
}
public double TotalOrderRevenue {
get {
double revenue = 0;
foreach (Order aOrder in this.Orders) {
foreach (OrderDetail aOrderDetail in aOrder.OrderDetails) {
revenue += aOrderDetail.Quantity * Convert.ToDouble(aOrderDetail.UnitPrice) *
aOrderDetail.Discount;
}
}
return revenue;
}
}
#endregion
}
#region EntityPropertyDescriptors.EmployeePropertyDescriptor
#endregion
}
We’ve added a Create()
method to ensure that new instances of Employees
get created with the minimum required data, and also get put under the control of the DevForce PersistenceManager
. (The PersistenceManager
maintains a sophisticated and performance-enhancing client-side cache, and also handles all saving and data retrieval operations in your app. We’ll talk more about that in the N-Tier Deployment section.)
We’ve also added the custom properties LastFirst
, Age
, and TotalOrderRevenue
, and overridden the default definition of the LastName
property. In doing the latter, we’ve taken control of a property that was generated into the DataRow
class by the Object Mapper (and will be again, whenever we make changes there). We’ve done so in a manner that ensures that our changes will never get overwritten.
Your custom business logic can get as complex and powerful as you need it to be. DevForce, for example, has a sophisticated “Verification” system that assists you in modeling complex data validation logic, including cross-property and even cross-object validations.
You can also implement security not only to prevent unauthorized users from accessing your data, but also to grant selective access to data and UI facilities based on a user’s security privileges. DevForce includes a Login Manager as well as server-side methods that permit you to intercept data retrieval and save requests so you can determine, based on the user, whether to allow them as requested, allow them after modification, or disallow them altogether.
Again, you don’t have to worry about this dimension of your application when you’re still trying to get the basic functionality up and running. The structure for adding security is already in place, via DevForce, from day one. When you get ready to make use of it, it’s there.
Auto-Populate your User Interface from the Data Model
Now we have a domain model and we’re ready to start building our user interface. The model doesn’t have to be “finished”; rather, you’re much more likely to work iteratively back and forth between model and UI, just as, in the model, you work back and forth between the Object Mapper and your custom business logic.
Let’s build a Master-Detail form to display data about our Employees and their sales orders. We start by adding a Windows Application (WinForms) project to our solution. We’ll name it “UI”:
Our UI project will need to use the business objects we’ve defined in the Model assembly, so we add a reference to that assembly now.
We’ll configure a few basic properties of the form in Visual Studio and then drag two BindingSources
and a BindingNavigator
onto the form.
Next, we’ll drag two DevForce UI components, a ControlBindingManager
and a DataGridViewBindingManager
, on to the form. The ControlBindingManager
will manage data bindings between properties of an Employee and controls such as TextBoxes, DatePickers, ComboBoxes, and the like; we’ll rename it mEmployeeControlBindingManager. The DataGridViewBindingManager
will manage data bindings between the properties of Order objects and columns of a .NET DataGridView grid control; so we’ll rename it to mOrdersDataGridViewBindingManager. (DevForce also provides BindingManagers for the Developers Express XtraGrid and the Infragistics UltraGrid controls.)
Now we’re ready to populate our form with controls and configure the data bindings. The DevForce BindingManagers include visual designers that make this easy. Let’s start with the ControlBindingManager
and set up the form to display information about an Employee.
First select the mEmployeeControlBindingManager in the component tray and click the “smart tag” in its upper-right-hand corner. This brings up a menu, from which we’ll choose the “AutoPopulate” option:
The BindingManager
needs to know to what business object type it will be configuring data bindings, so we specify the Employee type in the Model assembly:
Having told the BindingManager
designer the business object type, it displays a grid showing the properties on an Employee that are available for data binding. We simply drag the ones we want into the grid area of the dialog, and the designer selects appropriate controls based upon the data type and meta data for each property:
Download DevForce
We can override the designer’s control selections if we wish…
…but this is seldom necessary if we’ve told it which control suite (.NET, DevExpress, or Infragistics) is our preference:
When selecting properties for binding, we can drill into the details of related objects for our target properties and specify a “property path” that traverses the object model. At left we’ve selected the Photo property of the Employee’s Manager. That relationship originates from the database, where it is defined on the tables. The Object Mapper discovered it and generated the Manager property on the Employee type, all automatically.
We can also drag the Manager
property in its entirety into our data bindings grid. The designer selects a ComboBox for the property, which with a tiny bit of configuration will display the name of the current Employee’s manager in a list of all possible managers. The ComboBox can then be used easily and intuitively to reassign an Employee to a different Manager, when the need arises.
Here’s our completed visual specification for data bindings in the designer:
When we click the <OK> button, the designer populates our form and generates code to configure the data bindings. We adjust the position of a few of the controls to get a form that looks like this:
We’re almost ready to use our form at this point, but first let’s configure a DataGridView to display the Orders for which our Employee has acted as Sales Rep. We start with the mOrdersDataGridViewBindingManager
in the component tray, choosing Configure DataBindings.
This time we want to create bindings to an Order rather than an Employee:
As before, we select the properties we want displayed:
Click OK, reposition and resize the grid, and voila, it’s ready for use with all data bindings configured:
We still need to connect our BindingSources
to the BindingNavigator
and BindingManagers
, and write a little code to retrieve the desired employees. We could do further configuration through the Visual Studio Winforms designer, but it’s easier to see the whole picture if we do the rest of our work in the forms “code behind”. Here’s what the completed form code looks like:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Model;
using IdeaBlade.Persistence;
namespace UI {
public partial class EmployeeForm : Form {
public EmployeeForm() {
InitializeComponent();
}
private void EmployeeForm_Load(object sender, EventArgs e) {
ConfigureBindingSources();
ConfigureBindingNavigator();
ConfigureBindingManagers();
LoadSupportingData();
LoadMainData();
}
private void ConfigureBindingSources() {
mEmployeesBindingSource.DataSource = mEmployees;
mOrdersBindingSource.DataSource = mOrders;
mManagersBindingSource.DataSource = mManagers;
mEmployeesBindingSource.CurrentChanged +=
new EventHandler(mEmployeesBindingSource_CurrentChanged);
}
void mEmployeesBindingSource_CurrentChanged(object sender, EventArgs e) {
mOrders.ReplaceRange(CurrentEmployee.Orders);
}
private void ConfigureBindingNavigator() {
mEmployeesBindingNavigator.BindingSource = mEmployeesBindingSource;
}
private void ConfigureBindingManagers() {
mEmployeeControlBindingManager.BindingSource = mEmployeesBindingSource;
mOrdersDataGridViewBindingManager.BindingSource = mOrdersBindingSource;
}
private void LoadSupportingData() {
mManagers.ReplaceRange(mPersistenceManager.GetEntities<Employee>());
mManagers.ApplySort("LastName", ListSortDirection.Ascending, true);
}
private void LoadMainData() {
mEmployees.ReplaceRange(mPersistenceManager.GetEntities<Employee>());
}
public Employee CurrentEmployee {
get {
return (Employee)mEmployeesBindingSource.Current;
}
}
#region Private Fields
PersistenceManager mPersistenceManager = PersistenceManager.DefaultManager;
EntityList<Employee> mEmployees = new EntityList<Employee>();
EntityList<Order> mOrders = new EntityList<Order>();
EntityList<Employee> mManagers = new EntityList<Employee>();
#endregion Private Fields
}
}
The five methods called from the Form_Load
handler constitute the roadmap to show you the steps:
Method |
Action |
ConfigureBindingSources()
|
Assigns DevForce EntityLists as the DataSources for the .NET BindingSources used in the form. The DevForce EntityList<T> is a subclass of the .NET BindingList<T> that provides richer facilities for data binding.
Configures a CurrentChanged handler for the BindingSource that governs Employees. When the user moves to a new Employee, the handler loads the Orders EntityList with the new Employee’s Orders.
|
ConfigureBindingNavigator()
|
Links the BindingNavigator to the BindingSource that governs Employees
|
ConfigureBindingManagers()
|
Links the BindingManagers to the BindingSources that governs Employees and Orders.
|
LoadSupportingData()
|
Load data into the list used to populate the ComboBox that displays Managers
|
LoadMainData()
|
Loads the Employee data.
|
Here’s the completed form running:
It was easy to build, and it’s also ready to grow into something more complex. DevForce won’t paint you into a corner!
Download DevForce
N-Tier Deployment
So now you’ve built your app and need to deploy it n-tier. Did you remember to design and write it for n-tier operation? Without DevForce, creating an effective infrastructure for moving data from client to middle-tier to data-tier and back -- much less with consistently applied business logic and a client-side cache that eliminates redundant data retrievals -- is a big job requiring a tremendous amount of expertise and experience to get right. With DevForce, you get that expertise and experience in the package. There are still some things to think about, to be sure, but fundamentally your architecture is already prepared for n-tier deployment.
Let’s take the app we just built. Did we give any thought to n-tier operation? Not really. Can we nevertheless deploy the app pretty much as-is and expect it to work n-tier? Yes we can!
One of the important tasks is to determine what assemblies and other files need to be deployed on the client and which on the middle tier. To assist with that task, DevForce includes a handy little tool called the Deployment Test Tool. The Deployment Test Tool shows you the assemblies and executables for your n-tier app, and indicates which will need to be deployed where.
In addition to the application executables and dlls, a copy of the IdeaBlade.ibconfig configuration file is deployed to both the Client and Server folders. But the copies are not quite identical: instead, connection strings for the back-end databases and/or web services have been removed from the client-side configuration files. The client application now knows where and how to contact the middle-tier server, but has no information at all about how to connect to the back-end data sources. A stolen laptop can’t be used to compromise the database!
With no more work than we’ve just done, you’re ready to test your app in n-tier operation. You haven’t yet moved the server files to a separate machine, but by launching ServerConsole.exe you create an instance of the DevForce Business Object Server on your local machine. (When you’re done testing, you can also deploy the server as a Windows Service or under IIS.)
If you now launch UI.exe, you’ll run your client app in a separate process from the Business Object Server, and the two apps will immediately begin their cross-process dialog.
A debug log is automatically created to show you all important communications between the client and server apps.
When you’re ready to deploy to different machines, you’ll already have tested the n-tier operation of your app, conveniently on your own machine.
The Business Object Server can be deployed as a console application, as a Windows Service, or using IIS. If you do the latter, you can take advantage of multiple processing cores for scalability and SSL to encrypt all communications between the client and server.
Download DevForce
Conclusion
So there were our four simple steps. We created our data model from the database; added business logic to the model; created our UI with plenty of help from the DevForce UI designer tools; and deployed n-tier. No part of the process locks you into a style of application you don’t want: you’re in charge.
DevForce ships with plenty of documentation, code samples, and tutorials to help you address the full spectrum of development tasks; and a world-class team of engineers will help you over the rough spots. Try it today!