Skip to main content

Matt Goebel's Blog

Go Search
Home
  

Random thoughts and experiences on .NET, SmartClient/ClickOnce development, best practices and management.
‘Dynamic’ Expression Predicates in LINQ

Before I dive into the meat of this entry I would like to make the comment that if you haven't used LINQ yet, it's amazing. Assuming that you are reading this article because you do use LINQ and are trying to build a dynamic where clause or the such, you are aware of this.

The project uses LINQ to SQL and we're taking effort to stay consistent by using LINQ whenever possible. (Which so far is all the time we're finding out as LINQ hasn't let us down yet) The problem was a fairly simple and mundane. A user can select one or many States from a list and a second list is populated with all the cities in the states that the user selected. In theory, or by using a simple SQL query, this would be pretty simple. A quick loop and StringBuilder would be all that is needed to dynamically build the needed query string to return all the cities for the selected states. Since we were sticking to LINQ though, we had to find a different way…the LINQ way.

Initially, I thought this would be a piece of cake, mainly because everything thus far using LINQ was. After some initial searches on the Internet I was turning up only solutions that seemed far too complicated, and didn't really fit in with the sexiness of LINQ. One particular solution that kept coming up (could have been due to my search terms) was O'Reilly's (Joseph Albahari) solution discussed in the book "C# 3.0 In a Nutshell". This PredicateBuilder solution would have probably worked, but like I mentioned, and you can see, it wasn't as simple as I had become accustom to with LINQ…there had to be a better way.

My next logical (or at least in my brain :P ) was to simply put a LINQ statement into a loop, evaluate it for each selected state and concatenate the returned list of cities along the way. There are a couple key things wrong with this approach I won't get into, but again it wasn't fitting into the simple and easy mantra of LINQ….there still had to be a better way.

Well it turns out there is. I have to credit Adam Woods, a coworker, for figuring out the final solution, but it is so simple I had to share as it demonstrates the cool power of LINQ. LINQ has the ability to include outside List, not the List or data source you are "querying" off of, but additional List. The below code snippet demonstrates what I'm talking about.

//Gets all selected states from the lstStates listbox

List<string> states = new List<string>();

 

foreach (ListItem item in lstStates.Items)

{

    if (item.Selected)

{

    states.Add(item.Value);

}

}

 

//Gets all Cities from the database where the LocationStateID is one of the selected states from above.

var cities = from c in db.Cities

where states.Contains(c.LocationStateID.ToString())

orderby c.LocationStateID

select c.LocationCity;

 

lstCities.DataSource = cities;

This snippet demonstrates when you would need to build a dynamic WHERE clause with a bunch of OR evaluations. (If you are familiar with LINQ you wouldn't have to extrapolate this concept far to build a WHERE clause with AND and OR evaluations dynamically.) It also demonstrates the powerful ability LINQ has, as well as breaks down some mental walls on how you think about building LINQ statements and where LINQ can be used. Hopefully this helps or enlightens.

Setting up ADO.NET Data Services (a.k.a. “Astoria”)

Recently I started educating myself more on the emerging ADO.NET Data Services Framework. In doing so I, of course, had to setup an environment to play with the new framework. If I read the entire download page for the ADO.NET Entity Framework Tools I would have caught all the needed prerequisites but since I simply clicked to download I missed the following requirements.

  • Supported editions of Visual Studio 2008 RTM
  • XML Editor QFE – VS2008 Extension (VS90-KB945282.exe)
  • ADO.NET Entity Framework Beta 3 – Found HERE

I wasn't taking notes while going through the install process but if my memory serves me right there is an order that they need to be installed in and I believe that order is as follows.

  1. VS 2008, obviously…
  2. XML Editor QFE
  3. ADO.NET Entity Framework Beta 3
  4. ADO.NET Entity Framework Tools
  5. Finally, to get the ADO.NET Data Services Framework, ASP.NET 3.5 Extensions Preview

Why this information isn't on the ASP.NET 3.5 Extensions Preview page I don't know.

The ADO.NET Data Services Framework is included as part of the .NET 3.5 SP1 Beta 1 and Visual Studio 2008 SP1 Beta 1. So if you are using both Beta's you shouldn't have to jump through the hurdles above. More details and information about the framework can be found using the two links below. They've both been very helpful.

http://astoria.mslivelabs.com/

http://blogs.msdn.com/astoriateam/

Quick Wizard Functionality

I've recently come across 3 projects that needed 'Wizard-like' functionality. What I mean by this is that there was a need to guide the user through a process of either data entry or questions to get to an end result. This is pretty common functionality but there are numerous ways to implement, and the effectiveness of a certain implementations is sometimes questionable. I'd like to offer a walkthrough on one of these approaches I've been using recently to at least offer a jump off point to someone that could be solving the same issue.

Prerequisites: (What I'm using and assuming you know…)

  • VS 2008 (VS 2005 would be fine as well)
  • Infragistics v7.2 or later 3rd party controls
    • There are additional features in the Infragistics version of the TabControl control that I take advantage of. This functionality is still possible with the generic, "out of box", .NET TabControl control, but use of the Infragistics control makes life easier.

In a new Windows Forms Application, on the Form, add the UltraTabControl from the Toolbox. Set the 'Style' property to "Wizard". This removes the classic "tab" look from the tab control. We'll be able to use button navigation now that is typically associated with a wizard. Following widely accepted design, I place three buttons the "Shared controls page" of the UltraTabControl, "Cancel" on the bottom-left, "Back" and "Next"/"Finish" on the bottom-right.

Since the Back button won't be needed on the first tab/step of the wizard I set the Visible property to false. Next I implement the events for the Back and Next buttons.

private void cmdNext_Click(object sender, EventArgs e)

{

if (tbcWizard.SelectedTab.Index == tbcWizard.Tabs.Count - 1)

{

// Save/Final button was clicked (last step)

return; // May or may not be needed, depends on above

}

 

// Optional, implement if you need to do validation on a step or process

// information before reaching the end of the wizard

switch (tbcWizard.SelectedTab.Index)

{

case 0: // Step 1

break;

case 1: // Step 2

break;

case 2: // Step 3...

break;

default:

break;

}

 

if (tbcWizard.SelectedTab.Index < tbcWizard.Tabs.Count - 1)

{

// Move to the next step

tbcWizard.SelectedTab = tbcWizard.Tabs[tbcWizard.SelectedTab.Index + 1];

 

// Optional, in this case there is a label on the Shared page that is

// updated with the current tab/steps text...helpful for the user

lblStepHeader.Text = tbcWizard.SelectedTab.Text;

 

// Check if the tab is the last one, change "Next" text to "Finish"

if (tbcWizard.SelectedTab.Index == tbcWizard.Tabs.Count - 1)

cmdNext.Text = "Finish";

else

cmdNext.Text = "Next";

 

// Check if the tab is the first one, disable the "Back" button

if (tbcWizard.SelectedTab.Index == 0)

cmdBack.Visible = false;

else

cmdBack.Visible = true;

}

}

private void cmdBack_Click(object sender, EventArgs e)

{

if (tbcWizard.SelectedTab.Index == 0)

return;

 

// Move to the previous step

tbcWizard.SelectedTab = tbcWizard.Tabs[tbcWizard.SelectedTab.Index - 1];

 

// Optional, in this case there is a label on the Shared page that is

// updated with the current tab/steps text...helpful for the user

lblStepHeader.Text = tbcWizard.SelectedTab.Text;

 

// Check if the tab is the last one, change "Next" text to "Finish"

if (tbcWizard.SelectedTab.Index == tbcWizard.Tabs.Count - 1)

cmdNext.Text = "Finish";

else

cmdNext.Text = "Next";

 

// Check if the tab is the first one, disable the "Back" button

if (tbcWizard.SelectedTab.Index == 0)

cmdBack.Visible = false;

else

cmdBack.Visible = true;

}

The inline comments do a decent job of explaining what this simple chunk of code is doing. I'd be more than happy to provide a more robust and functioning solution if you use Infragistics controls and are interested. Please leave a comment and I'll be happy to send it along. So while very simple, this approach has been effective and efficient in all my latest needs to implement a wizard. Enjoy!

Indy DevCares

Tomorrow at the Indianapolis Microsoft office I will be presenting the May (even though it is in June) DevCares with a coworker, Neal Schneider. The details and material that will be covered can be found at the below link.

http://www.msdnevents.com/devcares/

File Associations for a ClickOnce Application

This bit of information came across my email yesterday and thought I would share. In my opinion this is a huge step forward for ClickOnce deployment. Hopefully VS2008 SP1 is full of these little goodies. =) The BETA is available now to play with.

How to: Create File Associations For a ClickOnce Application

VS 2008 Report Viewer Redistributable

In a recent post I provided a link to the Report Viewer redistributable and referenced another article that showed how to "hack" the EXE. The link points to the VS 2005 redistributable and so here is the link for the VS 2008 redistributable if you are running into issues with the older one.

http://www.microsoft.com/downloads/details.aspx?FamilyID=CC96C246-61E5-4D9E-BB5F-416D75A1B9EF&displaylang=en

Report Viewer – “More than one data set” Error

As the previous post suggest I'm currently spending some of my time around implementing the Reporting Services Report Viewer into a smart client application that was recently migrated from .NET 1.1 to 2.0. Yesterday we ran into an issue that didn't seem to have much information about it on-line. It was easy to diagnose and fix once taking a look under the covers, but for those weekend warriors of coding I hope you find this helpful.

The error you get when compiling is "More than one data set, data region, or grouping in the report has the name 'Assembly Name'. Data set, data region, and grouping names must be unique within a report." and is due to duplicate data sets in the .rdlc file you created for your report. In my case this occurred when I was using an object data source.

By right-clicking the .rdlc file in the Solution Explorer, selecting Open With, and then XML Editor you'll be able to see the XML that makes up the report you are designing. By collapsing the "DataSet" elements found within the "DataSets" element you'll quickly see the duplicates. By expanding the elements back out, scrolling down and finding the "rd:ObjectDataSourceType" element you'll see what is going on.

In the .NET 1.1 project we had used the AssemblyInfo.cs file to assign a version number of 1.0.*, the default if I remember correctly. Since we were not using the version number for anything and actually controlled versioning a different way this was never changed.

Therefore, each compile created a new ObjectDataSourceType because the version number kept incrementing. The quick and easy way to fix is to make the version number static and remove the duplicate dataset. So not very tricky or hard, but incase you were getting stumped I hope this helps out.

Report Viewer Redistributable Hack

I recently was able to get the approval to move an old project's reporting "engine" from a hacked together combination of grids, print documents and drawing to using Reporting Services' Report Viewer. The idea, as this is only one change in a set, is to get the application slimmed down to only requiring the .NET 2.0+ Framework. By removing other dependencies (in this case by moving to Reporting Services we're able to get rid of a 3rd party PDF driver) the end goal is someday to be able to move this application to ClickOnce deployment.

Today I ran into an issue with a user testing an application that uses the Report Viewer. There were DLLs missing, DLLs that I thought and assumed were installed by the .NET 2.0 Framework. After some digging around I found that this is fairly common, or at least it seems to be by the number of forum postings about the matter. There is a redistributable for the Report Viewer, but it drops files in the GAC and since that requires Admin rights this wasn't good news.

After some digging around I stumbled upon a great blog post by a Dennis J. Bottjer, a ASP.NET MVP. Below are the steps he laid out to get around needing Admin rights while still being able to deploy using ClickOnce.

The following steps were performed by co-worker John Arcidiacono and myself [Dennis J. Bottjer] to successfully solve the challenge described above.

  1. Download Report Viewer Redistributable
  2. Use favorite Zip Utility to extract the MSI.exe to a folder of your choice
  3. Find the file ReportV1.cab in extract folder from step #2
  4. Use favorite Zip Utility to extract ReportV1.cab to a folder of your choice
  5. Open the new folder from step 4 and find 4 files
  6. Rename: FL_Microsoft_ReportViewer_Common_dll_117718_____X86.3643236F_FC70_11D3_A536_0090278A1BB8 To Microsoft.ReportViewer.Common.dll
  7. Rename: FL_Microsoft_ReportViewer_ProcessingObject_125592_____X86.3643236F_FC70_11D3_A536_0090278A1BB8 To Microsoft.ReportViewer.ProcessingObjectModel.dll
  8. Rename: FL_Microsoft_ReportViewer_WebForms_dll_117720_____X86.3643236F_FC70_11D3_A536_0090278A1BB8 To Microsoft.ReportViewer.WebForms.dll
  9. Rename: FL_Microsoft_ReportViewer_WinForms_dll_117722_____X86.3643236F_FC70_11D3_A536_0090278A1BB8 To Microsoft.ReportViewer.WinForms.dll
  10. Copy these dlls to your smart client project and reference them
  11. Now they will be part of the Smart Client's Build Output and Click Once Deployment
Using a Smart Client Approach – Case Study #2

Client/Setting: Large Manufacturer / Sales-Marketing Group

Primary Issues:

  • Existing solution (disconnected windows form application, written pre-.NET days) was lagging behind the competition in terms of usability, cost to support and maintain on a quarterly basis. New CDs were shipped out to all users regardless of use once a quarter with new data at a tremendous cost.
  • Users of the application work on construction sites, mine sites, hospitals and other areas where a Internet connection is unreliable.

Solution Goal: Provide a solution that is both low cost to support, maintain quarterly with new data, and supports disconnected work.

Thought Process: This solution architecture is a bit of a no brainer due to the requirement of supporting a disconnected work environment. Adding to that was the need to be competitive with other sales/marketing tools the competition was providing. Two ways to do that was with superior information (client responsibility) and a design that is intuitive, easy to use. Finally, we had to reduce CD distribution cost so it made sense to incorporate a function to allow the application to pull data updates from the Internet when it was connected.

Solution: Starting with .NET 1.1 (2.0 wasn't available at the time this application was started, it has since been migrated to 2.0) we used the Updater Application Blocks provided by Microsoft to create a Windows Form application that had auto-update capabilities. This enabled the application to have a rich user interface, performance, run disconnected and receive updates when available and connected.

Result: We are now entering the 3rd generation of the application so the continued investment by the client is a good sign that things are working. Since starting the application in .NET 1.1, with MSDE, and supporting only English we've come a long way and haven't had to reship CDs yet update to any of the following. The application is now running on .NET 2.0, SQL Server Express, and now supports 7 languages including Chinese. The next phase has us working on moving the application towards ClickOnce deployment to further reduce support cost by reducing dependencies and eliminating CDs all together.

Using a Smart Client Approach - Case Study #1

I got a comment recently off the "Why Use a Smart Client" posts that asked for some examples of when a Smart Client approach was used, and why. I plan on doing three of these mini case studies, each outlining a different recent project where Smart Client architecture was used. My goal is to keep these brief, but also tie them back to the points made in the "Why Use a Smart Client" post.

Client/Setting: Financial/Brokerage Institution

Primary Issue: Low user adoption of a recently implemented MS CRM system.

  • Field personnel's work was being made more difficult by having to enter new activities through the CRM interface. The process ranged from 14 – 23 mouse clicks and multiple screen refreshes. Adding to the difficultly, activities could be created while on the phone with the client or in between calls when time is tight.
  • Daily entry of activities could range into the dozens.
  • CRM is data driven, without user adoption CRM losses much of its value.

Solution Goal: Reduce mouse clicks required to enter CRM activities and increase user adoption.

  • Use a custom written tool that interfaces with the CRM API.
  • Solution had to fit in client's existing environment, primarily dominated by Microsoft technologies.

Thought Process: With user adoption the key point I wanted to get rid of the numerous clicks and the pesky screen refreshes. A web application utilizing a host of AJAX and JavaScript seemed to be overkill and also didn't fit into the budget. I also wanted to incorporate user friendly features like MRU (most recently used) list, instantly available data entry ("lives" as a notify icon when "closed"), and ability to temporarily cache data to boost search performance. To tie this back to the previous post I was utilizing three advantages of a smart client approach; Rich UI and UX, Ease of Development and Increased Performance.

Solution: Single form, Windows application, deployed via a combination of SMS and ClickOnce giving the end user a "ClickZero" experience and requiring zero interaction from IT (Desktop support team) to keep the client updated on a forward moving basis. The smart client interacts with a combination of custom web services and CRM API web services to transport and consume only the needed data, increasing search/lookup performance. Furthermore, MRU list were added to dropdown list allowing the user to quickly select their most common choices without having to wait for a web service call or server latency. We integrated the smart client application into the CRM toolbar and CRM Outlook toolbar as well so that access to the application was as convenient and instant as possible due to the fact that the application was already running in memory. Finally, the UI (layout and graphics) was skinned to match that of CRM so the user was in a familiar environment regardless if they used the CRM default forms of the application.

Result: Overall, the client couldn't have been happier...unless maybe if we forgot to send the bill. : ) The business side was very pleased with the rich responsive user interface and immensely reduced complexity to enter an activity. The IT side was pleased as well at the end. There was initial friction because the application was their first smart client and was almost killed because of that. After explanation and some small prototypes though they came around. In the end IT was able to centrally control the deployments and updates, cheaply maintain moving forward, reduce needed network traffic and server load, and satisfy the need of their business customer. I guess what speaks loudest is that the client is looking to move the application and concept forward to not only allow quick entry of activities, but also contacts, accounts and a number of other commonly used CRM entities.

1 - 10 Next

 ‭(Hidden)‬ Admin Links