Level Extreme platform
Subscription
Corporate profile
Products & Services
Support
Legal
Français
Are we going backwards?
Message
From
30/11/2009 11:21:48
 
 
To
All
General information
Forum:
ASP.NET
Category:
Other
Title:
Are we going backwards?
Miscellaneous
Thread ID:
01436966
Message ID:
01436966
Views:
306
I've been spending a lot of time lately cramming to learn Silverlight programming, using the most current published products (VS .NET 2008, Silverlight 3 and Blend.) At the same time I've been doing ongoing maintenance and development in VFP/West Wind Web Connection. The objective in both "tracks" is to hoist an application onto the Web.

As I move forward on both tracks, I've become somewhat disillusioned with .NET/Silverlight development because of the amount of effort it takes to get the simplest things done, even if I'm using a framework (Ideablade) on top of .NET to make things a little bit easier.

Today I read an article talking about how Microsoft's top developers prefer hand-coding over visual design, and all of a sudden my pain was explained quite clearly (http://www.computerworld.com/s/article/9141465/Microsoft_s_top_developers_prefer_old_school_coding_methods) If Microsoft's top developers prefer creating applications with a TEXT EDITOR, it is no wonder that the .NET framework tends to lean towards carpal tunnel -inducing development methodology as well.

VS.NET 2010 (slated for release sometime next year) ***finally*** introduces visual design for XAML, but it still leaves a whole bunch of pain in the codebehind. While I don't fancy myself as a luddite, at times like this I seriously wonder if we are actually making much progress here with MS's programming paradigm. To illustrate the point, I have posted below code (XAML and C# codebehind) that is necessary to get a simple data query screen up and running. You don't need to try to understand the code, just take a look at the volume and the complexity, keeping in mind that the Ideablade framework I used with this application takes care of a whole lot of the heavy lifting to get data access going -- without it I would have had to write a whole bunch of additional complex code to create a data server between Silverlight and the data source. This simple screen has a few buttons to connect to the backend SQL server database, log in, fetch and filter data, save changes, and log out. It also has a grid displaying the results of the data fetches. Call me lazy, but I believe there has to be an easier way to do this.

And here (drum roll) is the code I had to write by hand (albeit with the help of Intellisense):

User interface handcoded with XAML
<UserControl x:Class="FirstSilverlightApp.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"             
    Width="Auto" Height="Auto">

  <Grid x:Name="LayoutRoot" Background="White">

    <Grid.Resources>
      <Style x:Key="grid" TargetType="data:DataGrid">
        <Setter Property="Margin" Value="10,10,10,10" />
        <Setter Property="Height" Value="400" />
        <Setter Property="Width" Value="700" />
      </Style>
      <Style x:Key="button" TargetType="Button">
        <Setter Property="Width" Value="80" />
        <Setter Property="Margin" Value="2" />
      </Style>
    </Grid.Resources>

    <StackPanel Grid.Row="0">
      <StackPanel Orientation="Horizontal" Margin="40,15,0,15" >
        <Button x:Name="btnConnect" Click="btnConnect_Click" Style="{StaticResource button}"  
                    Content="Connect"  />
        <Button x:Name="btnLogin" Click="btnLogin_Click" Style="{StaticResource button}"  
                    Content="Login" />
        <Button x:Name="btnFetch" Click="btnFetch_Click" Style="{StaticResource button}"  
                    Content="Fetch" />
        <Button x:Name="btnSave" Click="btnSave_Click" Style="{StaticResource button}"  
                    Content="Save" />
        <Button x:Name="btnLogout" Click="btnLogout_Click" Style="{StaticResource button}"  
                    Content="Logout" />
        <Button x:Name="btnReset" Click="btnReset_Click" Style="{StaticResource button}"  
                    Content="Reset" />

      </StackPanel>

      <StackPanel Orientation="Horizontal" Margin="40,15,0,15" >
        <TextBlock Margin="0,0,6,0">Query:</TextBlock>
        <ComboBox x:Name="queryCombo" MinWidth="30"/>
      </StackPanel>

      <data:DataGrid x:Name="dg" Style="{StaticResource grid}" 
                AutoGenerateColumns="False" HeadersVisibility="Column">
        <data:DataGrid.Columns>
          <data:DataGridTextColumn Binding="{Binding CustomerID}" Header="Id" IsReadOnly="True" />
          <data:DataGridTextColumn Binding="{Binding CompanyName}" Header="Company" />
          <data:DataGridTextColumn Binding="{Binding ContactName}" Header="Contact" />
          <data:DataGridTextColumn Binding="{Binding ContactTitle}" Header="Title" />
          <data:DataGridTextColumn Binding="{Binding Address}"  Header="Address"/>
        </data:DataGrid.Columns>
      </data:DataGrid>

      <Border BorderThickness="1" Margin="10" BorderBrush="Black">
        <TextBox x:Name="txtStatus" Width="300" Height="25" TextWrapping="Wrap" BorderThickness="0"
                   VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
                   HorizontalAlignment="Left" Margin="5"  IsReadOnly="True"  />
      </Border>

    </StackPanel>

  </Grid>
</UserControl>
Codebehind -- business rules and data access coded with C#
#region usings
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using IdeaBlade.EntityModel;

using DomainModel;

#endregion

namespace FirstSilverlightApp {

  public partial class Page : UserControl {

    #region ctor & load

    public Page() {
      InitializeComponent();
      Loaded += Page_Loaded;
    }

    void Page_Loaded(object sender, RoutedEventArgs e) {
      CreateEntityManager(); // the gateway to persistence
      InitializeQueries();
      Reset();
    }

    private void CreateEntityManager() {
      WriteMessage("Creating EntityManager ...");
      _entityManager = new DomainModelEntityManager(false);
    }


    #endregion

    #region Button Click Handlers

    private void btnConnect_Click(object sender, RoutedEventArgs e) {
      Connect(); // to the server
    }

    private void btnLogin_Click(object sender, RoutedEventArgs e) {
      Login(); // for security and identification
    }

    private void btnFetch_Click(object sender, RoutedEventArgs e) {
      Fetch(); // Get from the database
    }

    private void btnSave_Click(object sender, RoutedEventArgs e) {
      Save();
    }

    private void btnLogout_Click(object sender, RoutedEventArgs e) {
      Logout();
    }

    private void btnReset_Click(object sender, RoutedEventArgs e) {
      Reset();
    }
    #endregion

    #region Initializequeries

    private void InitializeQueries() {
      AddQueries();
      InitializeQueryCombo();
    }

    private void InitializeQueryCombo() {
      var qNames = _queries.Keys.ToList();
      queryCombo.ItemsSource = qNames;
      queryCombo.SelectedItem = qNames.FirstOrDefault();
      queryCombo.SelectionChanged += delegate { Fetch(); };
    }

    #endregion

    #region Fetch

    private void Fetch() {

      var query = GetQuery();
      if (null == query) {
        WriteMessage("No query selected; pick a query.");
        return;
      }

      WriteMessage("Fetching ...");

      _entityManager.ExecuteQueryAsync(
        query,
        args => {
          if (args.Error != null) {
            WriteMessage(args.Error.Message);
          } else {
            dg.ItemsSource = args.Result;
            ReportFetchCount(args.Result);
          }
        },
        null);
    }

    private IEntityQuery GetQuery() {
      var queryName = (string)queryCombo.SelectedItem;
      return queryName == null ? null : _queries[queryName];
    }

    private void ReportFetchCount(IEnumerable result) {
      WriteMessage(string.Format("Retrieved {0} customers", ((ICollection)result).Count));
    }

    #endregion

    #region Queries 

    private void AddQueries() {

      _queries = new Dictionary<string, IEntityQuery>();

      _queries.Add("Get all Customers", _entityManager.Customers);

      _queries.Add("Get Customers starting with A",
                  _entityManager.Customers
                    .Where(c => c.CompanyName.StartsWith("A")));

      _queries.Add("Get Customers with Orders in Oregon",
                  _entityManager.Customers
                    .Where(c => c.Orders.Any(o => o.ShipRegion == "OR")));

      _queries.Add("Get Customers starting with B from cache-only",
                  _entityManager.Customers
                    .Where(c => c.CompanyName.StartsWith("B"))
                    .With(QueryStrategy.CacheOnly));
    }

    #endregion

    #region Reset

    private void Reset() {

      // Start all over again ...

      btnLogin.IsEnabled = false;
      btnFetch.IsEnabled = false;
      btnLogout.IsEnabled = false;
      btnSave.IsEnabled = false;
      queryCombo.IsEnabled = false;

      dg.ItemsSource = null;

      _entityManager.Clear();
      if (_entityManager.IsLoggedIn) Logout();
      if (_entityManager.IsConnected) _entityManager.Disconnect();

      WriteMessage("Ready to connect ...");
    }

    #endregion

    #region Connect

    private void Connect() {

      _entityManager.ConnectAsync(
        args => {
          if (args.Error != null) {
            WriteMessage(args.Error.Message);
          } else {
            WriteMessage("Connected");
          }
          btnLogin.IsEnabled = args.IsCompleted;
          if (args.IsCompletedSynchronously) return;
        },
        null);

      if (!_entityManager.IsConnected) WriteMessage("Connecting ...");
    }

    #endregion

    #region Login

    private void Login() {
      var cred = new LoginCredential("demo", "demo", "demo");

      WriteMessage("Logging in ...");

      _entityManager.LoginAsync(
        cred,
        args => {
          if (args.Error != null) {
            WriteMessage(args.Error.Message);
          } else {
            WriteMessage("Logged in");
          }
          queryCombo.IsEnabled = args.IsCompleted;
          btnFetch.IsEnabled = args.IsCompleted;
          btnSave.IsEnabled = args.IsCompleted;
          btnLogout.IsEnabled = args.IsCompleted;
        },
        null);
    }

    #endregion

    #region Logout

    private void Logout() {
      WriteMessage("Logging out ...");

      _entityManager.LogoutAsync(
        args => {
          WriteMessage("Logged out");
          queryCombo.IsEnabled = false;
          btnFetch.IsEnabled = false;
          btnSave.IsEnabled = false;
        },
        null);
    }

    #endregion

    #region Save

    private void Save() {

      if (!_entityManager.HasChanges()) {
        WriteMessage("No changes to save.");
        return;
      }

      WriteMessage("Saving ...");

      _entityManager.SaveChangesAsync(
        args => {
          if (args.IsCompleted) {
            WriteMessage("Changes saved");
          } else {
            WriteMessage(args.Error.Message);
          }
        },
        null);
    }

    #endregion

    #region Misc

    private void WriteMessage(string msg) {
      txtStatus.Text = msg;
    }

    #endregion

    #region Fields

    private DomainModelEntityManager _entityManager;
    private Dictionary<string, IEntityQuery> _queries;

    #endregion
  }
}
Pertti Karjalainen
Product Manager
Northern Lights Software
Fairfax, CA USA
www.northernlightssoftware.com
Next
Reply
Map
View

Click here to load this message in the networking platform