Thursday, August 7, 2008

Using ListView and LINQ to display multi-level relationships



This is cool! Assume we have a database that has a three-level relationship. We have a Product, Install, and Document table. Products have installations and installations have related documents for them. This means our Document table has a InstallId and our Install table has a ProductId. This is all standard relationship stuff so hopefully you are following this.

Now assume we want to use LINQ and the ListView web control to display hierarchical data and we want full control over the HTML generated (that's why we use the ListView control). The display will look like:

Product P1
  Installation I1
    Document D1
    Document D2
  Installation I2
    Document D3
Product P2
...

First, use a standard LINQ-to-SQL class in Visual Studio to create your data context object. Next, create a three-level set of ListView objects. Here's the cool part...are you ready for this?

Binding your data: Each ListView needs to be bound to a LINQ IQueryable data source. The outer most ListView, Product, can be just linked to the Products (remember the generated data context adds the plural name to the table name) like:

MultiLevelDataContext db = new MultiLevelDataContext();
IEnumerable<Product> products = db.Products;
lvProduct.DataSource = products;
lvProduct.DataBind();



If you do this in code-behind, that's all you are going to do there. The other two bindings are done in the ListView declaration itself.

The Install and Document nested ListView components just need to have their DataSource property set to the property of its outer ListView object. Remember that when the LINQ-to-SQL code was generated, it automatically added properties for relationship data. For example, the Product class has a property called Installs. The Install class has a property called Documents. These properties basically end up being IQueryable data sources. We simply use a standard Eval() binding statement in the DataSource to connect things up. This makes it incredibly easy to bind the related data into a web control like a three-level ListView structure.

Below is what is all ends up looking like. Two things to mention. Most of the aspx is table formatting. Secondly, notice the DataSource statements in the two nested ListView controls.

Code Behind (cs)

protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
MultiLevelDataContext db = new MultiLevelDataContext();
IEnumerable<Product> products = db.Products;
lvProduct.DataSource = products;
lvProduct.DataBind();
}
}



Web Page (aspx)

<asp:ListView ID="lvProduct" runat="server">
<LayoutTemplate>
<table cellpadding="3" cellspacing="0" border="1" style="width: 100%; background-color: Silver;">
<tr runat="server" id="itemPlaceholder" />
</table>
</LayoutTemplate>
<ItemTemplate>
<tr>
<td>Product:
<%# Eval("Name") %>
</td>
</tr>
<asp:ListView ID="lvInstall" runat="server" DataSource='<%# Eval("Installs") %>'>
<LayoutTemplate>
<tr>
<td>
<table cellpadding="3" cellspacing="0" border="1" style="width: 100%; background-color: Aqua;">
<tr runat="server" id="itemPlaceholder" />
</table>
</td>
</tr>
</LayoutTemplate>
<ItemTemplate>
<tr>
<td>Version:
<%# Eval("Version") %>
</td>
<td>Release Date:
<%# Eval("ReleaseDate") %>
</td>
</tr>
<asp:ListView ID="lvDocuments" runat="server" DataSource='<%# Eval("Documents") %>'>
<LayoutTemplate>
<tr>
<td colspan="2">
<table cellpadding="3" cellspacing="0" border="1" style="width: 100%; background-color: Lime;">
<tr runat="server" id="itemPlaceholder" />
</table>
</td>
</tr>
</LayoutTemplate>
<ItemTemplate>
<tr valign="top">
<td>
<%# Eval("Name") %>
</td>
<td>
<%# Eval("Description") %>
</td>
</tr>
</ItemTemplate>
</asp:ListView>
</ItemTemplate>
</asp:ListView>
</ItemTemplate>
</asp:ListView>

Monday, August 4, 2008

Synchronizing LINQ-to-SQL with database schema

The initial release of the LINQ-to-SQL designer support in VS 2008 doesn't have a good way of keeping changes in the database schema synchronized with the dbml (designer) data. I've really only found two products so far that claim to do this for you.

Huagati DBML Tools
http://www.huagati.com/dbmltools/

Database Restyle by Perpetuum Software
http://www.perpetuumsoft.com/Product.aspx?lang=en&pid=55

I have not personally tried either of these yet.

Wednesday, July 30, 2008

Capturing and viewing WCF SOAP messages

When using WCF it is sometimes required to examine the incoming and outgoing SOAP messages. You can do this by modifying your WCF configuration file, then viewing the messages using a utility installed alongside Visual Studio.

The easiest way configure your configuration file is to use the Visual Studio menu,
Tools | WCF Service Configuration Editor. Use the utility to open up your WCF configuration file (the place where all your WCF endpoints are defined). Next, access the Diagnostics section to enable logging for incoming and outgoing SOAP messages (Message Logging in the tree view). There are various options in the right pane of the display. If you want to see the entire SOAP messages, be sure to enable the LogEntireMessage option in the Message Logging section (it took me a while to figure out this one).

Perform a File | Save and now your WCF configuration should be ready to go. When you run your WCF application, a new xxxx.svclog file will be created in the root directory of your Visual Studio project (you can change this via the WCF configuration file).

To view the message log file, use the SvcTraceViewer.exe program from Microsoft. It’s normally located in \Program Files\Microsoft SDKs\Windows\v6.0A\Bin. Use the File | Open to find the xxxx.svclog file. There are several different ways to view your data. I like the "Messages" view the best.

When you are done with your diagnostics, you can use the WCF Service Configuration Editor to disable logging.

Monday, July 28, 2008

PowerShell to encrypt / decrypt app.config sections

Here is a PS script called AppConfigCrypto.ps1 that allows you to encrypt and decrypt sections of an appConfig. Be aware that once a config is encrypted, you can't just copy it from machine to machine since the encryption is done via the default machine key. You should be able to get around this by importing your own keys and modifying the script below. If you don't import a user specified key, then you will have to encrypt on the machine where the application will execute.

Here's the PS script:

param(
[string]$sectionName,
[string]$exePath="app.config",
[switch]$encrypt,
[switch]$decrypt)

function CallExit($msg)
{
$msg
Usage
exit
}

function OKExit($msg)
{
$msg
exit
}

function Usage
{
"Usage: ./AppConfigCrypto.ps1 sectionName exePath [-encrypt | -decrypt]"
}

# check params
if ($sectionName.Trim().Length -eq 0) { CallExit("%You must pass a section name (e.g. appSettings, ConnectionStrings)") }
if ($encrypt -eq $false -and $decrypt -eq $false) { CallExit("%Must specify -encrypt or -decrypt") }
if ($encrypt -ne $false -and $decrypt -ne $false) { CallExit("%Must specify either -encrypt or -decrypt") }

# load the config
$config = [System.Configuration.ConfigurationManager]::OpenExeConfiguration((Resolve-Path $exePath))

# make sure section exists and is readable
$section = $config.GetSection($sectionName)
if ($null -eq $section) { CallExit("%$sectionName section not found") }
if ($section.IsReadOnly()) { CallExit("%$sectionName is read-only") }

if ($encrypt)
{
if ($section.SectionInformation.IsProtected -eq $true) { OKExit("%Section already encrypted") }
"Encrypting $sectionName . . ."
$section.SectionInformation.ProtectSection("RsaProtectedConfigurationProvider")
}
elseif ($decrypt)
{
if ($section.SectionInformation.IsProtected -eq $false) { OKExit("%Section already decrypted") }
"Decrypting $sectionName . . ."
$section.SectionInformation.UnprotectSection()
}

# save section
$section.SectionInformation.ForceSave = $true
$config.Save()

Tuesday, May 20, 2008

Update: Clear Visual Studio "Recent Project" entries

I had posted last October how to clear the recent project/file list in Visual Studio via a register cleanup. Recently I found a really nice VS add-in to do this. Very nice for VS 2008.

http://www.csharper.net/blog/visual_studio_2008_add_in_compatibility.aspx

Wednesday, May 14, 2008

LINQ cheat sheets for deferred and non-deferred

Here are cheat sheets for the LINQ deferred and non-deferred extension methods. Included are the page number references of where these are found in Joseph Rattz's book, Pro LINQ by Apress.

Format is legal, 14" x 8.5"

Click here

Aggregation: LINQ and SQL XML fields

I'm thinking ahead about the possibility of having to store some XML snippets in a database XML field. How can we store XML data in the database and use LINQ to easily retrieve it...and report on it. There are lots of examples out there, but I want to look at a more difficult scenario, one involving a variable XML data and aggregating that data from multiple records.

I've dreamed up a scenario based on a survey to collect the opinions of partners. When a partner takes the survey, there could be 1 to n questions on various opinions. I say n since the number of questions could change over time (plus I want to model variable XML data for this example).

For each partner, we decide to store the opinions in a single database XML field. For this example, I use the values "Choice X", but in reality it might be something like "Favor debit cards" or "Want e-mail rewards". Here is an example of what might be stored in the XML field for a single partner record, for a single survey:

<Opinions>
<Opinion>Choice 1
<Opinion>
<Opinion>Choice 2<Opinion>
<Opinion>Choice 5<Opinion>
</Opinions>

Don't get hung up on whether this is a correct way to store survey results...that's not the purpose here.

Notice that since certain opinions were not selected, they were not included in the XML (e.g. Choice 3-4 are missing). Assuming that the opinions are a simple set of checkboxes on a survey page, the database XML column value should be created like:

XElement results = new XElement("Opinions",
cblOpinion.Items.OfType<ListItem>()
.Where(o => o.Selected)
.Select(o => new XElement("Opinion", o.Text)));

These XML results are stored in the database. At some point we are going to have to report on the opinions of the partners. Assuming we can search the database to obtain partner records for a certain survey within a certain time period, we'd like to report on data like:

Choice 1, 378
Choice 2, 120
Choice 3, 629

I spent several iterations trying to figure out the best way to do this. In the end, as I suspected, it was easier that I thought. It wouldn't surprise me to have someone else find even an easier way as LINQ is a powerful language. To accomplish this the code below performs two steps, 1) get all survey opinions from the database, creating a sequence of XElement with all the opinions, and 2) create grouping sequence of all like-opinions. The example uses the "Linq To Sql" support in VS 2008.

// Step #1
// extract out all survey opinions from the DB and create one
// xml element
DbDataContext db = new DbDataContext();
XElement allOpinions = new XElement("Survey", db.Opinions
.Select(x => x.Opinions) // database column is called Opinions
.Select(x => x));

// Step #2
// group like opinions together (could have been done along with
// previous linq statement, but kept separate since may be other
// queries to perform on allOpinions
var groups = allOpinions.Descendants("Opinion")
.OfType<XElement>()
.GroupBy(x => x.Value); // e.g. "Choice 1"

I can output the result of #1 and #2 as:

// Output #1
Console.WriteLine(allOpinions);

// Output #2
foreach (IGrouping<string, XElement> g in groups)
{
Console.WriteLine("{0},{1}", g.Key, g.Count());
}

Monday, May 5, 2008

Visual Studio 2008 Poster (C# key bindings)

I took the Microsoft Visual Studio 2008 Key Bindings poster and formatted to fix a standard 30x20 poster. Also provided in JPG rather than PDF format in case anyone wants to re-scale it.

Visual Studio 2008 Key Bindings Poster

Sunday, May 4, 2008

CheckBoxList, Linq, and XML

I’ve been playing around with the new Linq stuff in .NET 3. I’m finding it’s possible to do things much easier with Linq, as well as with significantly less code. Recently I had to take all the selected items in an asp.net checkbox list (cblOpinion) and write them to an XML file (filePath). The code turned out to be a single statement:

new XElement("Opinions",
cblOpinion.Items.OfType()
.Where(o => o.Selected)
.Select(o => new XElement("Opinion", o.Text)))
.Save(filePath);

This created a nice XML file that looked like:



opinion a...
opinion b...
opinion c...
opinion d...


Pretty nifty!

Wednesday, April 23, 2008

Commands to increase performance of MS Server 2008

To increase performance of MS Server 2008...

netsh interface tcp set global autotuninglevel=disabled
netsh interface tcp set global rss=disabled


This must be run with admin privs.

Monday, April 21, 2008

Can't compile Linq after VS 2008 migration

I migrated a VS 2005 asp.net website (not using Linq) to VS 2008. Initially, the website compiled fine, but once I tried to add Linq statements, it would not compile.

I had already done the following to prepare for Linq use:

1) Added in the web.config so System.Core (where Linq lives) would be a reference (copied several of these from a virgin VS 2008 asp.net site)

2) Modified the site Build properties and targeted .NET 3.5

3) Added "using System.Linq"

But it still would not compile...all Linq statements were not recognized (even Intellisense worked). It was like I was still using the .NET 2.0 compiler.

When I looked back at the web.config from a virgin VS 2008 asp.net site, I realized I needed to also have the section. I copied this entire section into my web.config and it now compiles fine (with the .3.5 compiler).

It would have been nice if the conversion tool from VS 2005 to 2008 would have done this for me. I have the first official release of VS 2008.

Can't RDP? How to enable / disable virtual machine firewall for Azure VM

Oh no!  I accidentally blocked the RDP port on an Azure virtual machine which resulted in not being able to log into the VM anymore.  I did ...