Archive

Archive for the ‘C# 3.0’ Category

My first real WPF and Blend 2 application

October 21, 2008 Comments off

I’ve been familiar with WPF since December 2006 and the release of C# 3.0, and I’ve had Expression Suite installed for almost a year. While I’ve toyed with it here and there, I have never devoted the time and effort necessary to really begin to become proficient.

However, since last week’s Silverlight 2.0 release, I have been burying myself again in learning new technologies. I finally decided to take the plunge, so I installed Silverlight 2.0, the Visual Studio updates, and upgraded to Expression Suite 2 SP1. I spent a couple of days going through ScottGu’s tutorials and some others on Silverlight.net. I followed along and built the samples, some in Visual Studio and some in Blend.

Most of these have been around for a while, so I’m not claiming any kind of leading edge stuff here. What I wanted to do was to share some of the insights I’ve had in attempting this project.

What’s the big deal

XAML marks a sea change in how user interfaces are developed. The end result of XAML is still .NET objects, and as such they can be created and managed programmaticaly, but the ability to simply describe what you want is very attractive. For a long time, I did not like XAML itself, and one of my reasons for putting off learning WPF was that I wanted to wait until something like Blend made all XAML interaction obsolete. After diving in for a few days, I no longer feel that way, but I’ll share more about that later.

What really makes all this so attractive is the ability to do basically whatever I can imagine for an interface. More importantly, I can do it without a ton of hand coded control drawing, something I never liked doing in the first place. The behavior of a control truly is separate from its presentation, and the presentation can be altered or replaced in any number of ways. In other words, you can achieve some pretty cool effects with a reasonably small effort.

Visual Studio or Blend?

I’ve been having an ongoing discussion with a friend of mine at RVNUG about the usefulness of writing WPF applications within Visual Studio. Having seen some demos and had some training on Blend, I was staunchly in the Blend corner on this one. While you can drag and drop controls in Visual Studio, it takes a lot of hands on XAML coding to get anything more than a rudimentary window up and running. As I mentioned previously, after seeing some XAML presentations I really wanted to avoid that as much as possible, which is what makes Blend so intriguing.

But, having gone through ScottGu’s Silverlight tutorial and building an application in Visual Studio, I have a better appreciation for it now. One thing I do like about using Visual Studio is that it is keyboard centric. As a classic Midrange developer I have always shied away from using the mouse as much as possible, so it appeals to my keyboard-philia. And of course, Intellisense is still the killer feature and makes it much more palatable. Also, as a seasoned and grizzled web developer who still likes the occasional dip into VI and Notepad, it only took a little time with XAML to feel comfortable with what was going on. Anyone familiar with XHTML and CSS should find XAML completely doable. It is, of course, a lot more complex, with numerous options and quirks, but it is still familiar territory.

Now, all that being said, I still prefer Blend 5 to 1 over Visual Studio for Visual XAML development. I have only run into a few things that I couldn’t accomplish easily through Blend, and I’m new enough to it that I still chalk it up to just not knowing the tool well enough. Applying and developing styles is still one of these areas: I so far have not figured out how to do them in Blend, so I revert to XAML editing.

Which brings me to my question of the day: “Should I use Visual Studio or Blend?”  The answer is a resounding “both!” OK, I’m sure you saw that one coming, but let me explain my position. If you are a developer, there is no question that you are going to use Visual Studio. After all, it is our bread and butter, and all the real code will still be developed in our beloved IDE. But designing serious WPF solutions in Visual Studio would be far too painful, even with great Intellisense support. There are simply too many options to have to code them by hand.

It reminds me of my first Windows application: a Java Swing application that I wrote in Wordpad. Believe me, the pain of that experience made me instantly recognize the value of Visual Studio and is largely responsible for my shift to Microsoft technologies. On the same order, as soon as I saw Blend I knew that this was the tool I needed to design good WPF applications. So, for layout and Visual Tree management, use Blend. When you find a problem that you think you must solve using XAML editing, switch over to Visual Studio and take advantage of Intellisense, which Blend does not have.

Quirks

A couple of things so far have jumped out at me. While Blend and Visual Studio do a pretty good job of keeping each other in synch, there are a couple of irregularities.

The first real problem I had was in adding existing projects to my solution in Visual Studio. I started my solution in Visual Studio and then opened it up in Blend to work on the design. Later in the same session, I went back to Visual Studio and added several projects. Now that I had some CLR objects to work with, I wanted to try Data Binding, so I followed one of the online tutorials but no joy. No matter what I did, I could not get Blend to find the objects. Every time I tried, I received a slew of “file could not be located” errors. Finally, I restarted Blend and when I opened my solution, there the missing objects were.

Second, there have been several times when I’m not sure that I am being properly prompted to reload. I could be imagining it, but I feel pretty strongly that I have made changes in one without being prompted by the other to reload them. Perhaps this is just a matter of timing Saves.

Conclusion

Well, I don’t really have any as of yet. I do think that a lot of developers are going to struggle against the designer learning curve, yours truly included. But I think in the long run we will be much better off. So far, I am pleased with my efforts. I like the combination of resources the two applications provide me, and I amd getting more comfortable in deciding which to use for certain scenarios.

I’m not quite ready for a tutorial series, but I will try to share some of my learning with you all as I go. In the meantime, give it a try yourself and let us know what you think. Happy Coding!

Categories: .NET 3.5, Blend, C# 3.0, Expression, WPF

Using Object Initializers on Dictionary

March 13, 2008 2 comments

I’m still going strong on the ASP.NET MVC development, and I wanted to experiment with HtmlHelper.Select rather than hard coding specific values into my View. To that end, I am building a supporting class that will retain static properties (which I’m sure will be cached once I figure out how to do that). Some of these properties are lists of information, like product types and prices, that could be fed from a database table. Others may be static lists with the potential to become database driven, like state codes and names. Ultimately, where they come from does not matter, but in my coding today I came across something interesting.

To begin this process, I decided to populate a static Dictionary<string, string> with a list of codes and values. This is a small list, so for initial testing I just wanted to hard code the values. It has become a habit for me to use Object Initializers whenever possible, so naturally this seemed a perfect fit. But let me ask you: have you ever tried this? I have to admit that at first I was a little stumped. I first tried to build a method tree (a concept that really doesn’t exist in C#):

BusinessTypes = new Dictionary<string, string>()
.Add("APP", "Appraisal")
.Add("GOV", "Government")
.Add("INS", "Insurance")
.Add("LAW", "Legal")
.Add("LEN", "Lending")
.Add("REL", "Real Estate")
.Add("RES", "Research")
.Add("OTH", "Other");

Even though I didn’t really expect this to work, I thought it would be interesting to see what happened. [This could be a good candidate for a future language enhancement.] Naturally it failed, but in an unexpected fashion. The first call to .Add passed the compiler, but the second call to .Add threw the following error:

Operator ‘.’ cannot be applied to operand of type ‘void’

I also tried adding the method call inside the Initializer block, but since you can’t put method calls inside of Initializer blocks I pretty much knew that wouldn’t work either.

So I looked at it some more and asked myself “what is the Dictionary looking for?” My answer was “a KeyValuePair”, so I tried that next:

BusinessTypes = new Dictionary()
{
    new payday us fast cash loan,fast cash payday loancash till payday loan,cash loan payday,advance cash cash loan payday paydayloan oneclickcash paydaycash fast loan paydaycash advance loan illinois,5 advance cash illinois loanfull pay video pokerbest video pokerinternet casino craps,internet casino,internet casino gambling gamefree online casino gamblingduces wild video pokercasino link online suggestno deposit bonus online casino,free online gambling casino bonus,online casino bonusplay blackjack online freefree video poker gamescraps free online play,casino craps free gambling online,free online crapsfree online casino,online casino,top online casinoplaying blackjackplay free casinofree casino cash bonus,casino cash,free cash casinofree video poker gamereal money backgammonplay roulette onlinefree casino moneytriple play video pokerfree roulette,free roulette download,play free rouletteroulette gambling,best gambling online roulette,online roulette gamblingplay black jack online freeonline gambling casino,gambling casino online bonus,free online casino gamblingcasino download,free online casino download,download online casino gamemicrogaming casino bonusbaccarat card game,baccarat game,baccarat the internet casino gamecasino no deposit bonus code,online casino sign up bonus,casino bonusvideo poker for winnersvideo poker strategy,free video poker,video pokerkeno gameplay slots,play for fun online slots,free slots play for freecraps rulesplay free slots,free slots,free online slots gameinternational online casinovideo poker machineshand held video pokercraps gambling strategy,craps gambling game,gambling crapsbest casino bonus888 black jackfree online slots no download,no download free slots game,free slots no downloadblack jack downloadplay casino game onlinetournament backgammon888 poker,888 poker info,888 poker tournamentbest online casino bonus KeyValuePair<string, string>("APP", "Appraisal"),
    new KeyValuePair<string, string>("GOV", "Government"),
    new KeyValuePair<string, string>("INS", "Insurance"),
    new KeyValuePair<string, string>("LAW", "Legal"),
    new KeyValuePair<string, string>("LEN", "Lending"),
    new KeyValuePair<string, string>("REL", "Real Estate"),
    new KeyValuePair<string, string>("RES", "Research"),
    new KeyValuePair<string, string>("OTH", "Other")
};

That also threw an error:

No overload for method ‘Add’ takes ’1′ arguments

But the error pointed me back to my original idea: somehow I needed to use Add(), but how? And then it dawned on me: what the message is really telling me is that I was already using Add(), I just didn’t realize it! Since we use curly braces to denote Initializers, I decided to try that within the context of the parent Initializer to send parmameters to the Add method:

BusinessTypes = new Dictionary<string, string>()
{
    {"APP", "Appraisal"},
    {"GOV", "Government"},
    {"INS", "Insurance"},
    {"LAW", "Legal"},
    {"LEN", "Lending"},
    {"REL", "Real Estate"},
    {"RES", "Research"},
    {"OTH", "Other"}
};

This compiles fine and now I have successfully used an Object Initializer on a Dictionary<TKey, TValue> object. I was curious about why this works, so I hit the Google pavement and found this article on MSDN from October of 2006. In it, the author has this to say:

Our resolution to this is to refine our understanding of collection initializers a little bit. The list you provide is not a ?list of elements to add?, but a ?list of sets of arguments to Add methods?. If an entry in the list consists of multiple arguments to an Add method, these are enclosed in { curly braces }. This is actually immensely useful. For example, it allows you to Add key/value pairs to a dictionary, something we have had a number of requests for as a separate feature.

From previous text in the article, we find that any IEnumerable with a public Add method will behave this way. As such, we should be able to use this construct in almost all the generic Collection classes.

This is definitely useful, and yes I should have searched for a solution before I spent the entire 3 minutes trying to figure it out. But sometimes, I just like solving the puzzle.

Categories: .NET 3.5, C# 3.0

ASP.NET MVC Preview 2 Released

March 6, 2008 Comments off

In another case of DGT (Darn Good Timing), in my inbox this morning was my copy of theToques 3320 baixar toques para celular Gratis, toques. Developer Fusion Community Newsletter, and the first item was this announcement. Being a very recent fan of the project, I immediately downloaded and installed the new Preview.? Here are the Release Notes. There are a few breaking changes, mostly object renames, but a little refactoring should take care of the bulk of it.

I spent some time yesterday playing around with it, and I am very happy with what I see. I plan to spend most of today reading Scott Guthrie’s excellent Tutorials.

Trudging through ASP.NET

February 19, 2008 Comments off

I just wanted to post a quick update on what’s going on in my world this week.? I posted last week about my Authorize.Net efforts (which I updated this morning).? That work is the precursor to finally learning ASP.NET, one of my New Year’s Resolutions.? I have to admit that I have always assumed that learning ASP.NET would be no big deal.? I have developed web sites in PHP, JSP and Servlets, and even RPG CGI, so what could be so hard about ASP.NET?

Well, the short answer is that it is not hard, however I find myself constantly frustrated because I feel like I have to relearn all the basics.? Seemingly simple matters take me a lot of time and investigation to get working.? I’m sure that if I was learning from scratch this would be no problem, but in my case I am hobbled by too much knowledge going in.? In other words, my brain keeps getting in the way.

So I am trudging through books, websites, and training materials.? Currently, I am reading through Sitepoint.com’s “Build your own ASP.NET 2.0 Web Site“, which appears to be the simplest material I have on hand.? I’m also planning on spending some time going through some beginners videos at asp.net.? And if all that fails, I’ll consider ordering again from TotalTraining.com. I know one day soon I’ll look back on this and wonder what all the fuss was about, but for now I must simply “Soldier On!”

Upgrade your C# Skills part 5 – LINQ to XML

January 31, 2008 7 comments

I am working on a project that requires a configuration file that can be edited or even replaced by the user. Looking at the requirements, what I really need is a series of “records” of the same layout, similar to a database table. Naturally, given the current state of technology, an XML file is the obvious choice. So I decided that now would be a good time to investigate LINQ to XML.

Understanding LINQ to XML

First of all, unlike other LINQ topics, this one is not really query oriented. LINQ to XML (also known as XLINQ) is primarily about reading and writing XML files. Once read into program variables, you can use LINQ to Objects technologies to perform query functions.

First, the XLINQ classes are not part of the standard System.Linq namespace. Instead, you will need to include System.Xml.Linq. This namespace contains the XML classes that make XLINQ worthwhile, and we’ll review a few of these as we go along.

Second, this article will be a far cry from exhaustive on the subject: there are a ton of additional features and topics that I will not cover. Chief among the reasons for this is that I am far from an XML expert. I consider myself a typical developer where XML is concerned: my programs need to find and consume information stored in an XML format. I may even have occasion to update or write XML content. Otherwise, XML holds no particular glamor for me.

Reading an XML File

Most articles I have read on this topic begin with using XLINQ to create XML structures and files. My first task was to read an existing XML file, so that is where I will begin.

Here is the XML data I will be using for this article:


<LayoutItems Class="">
  <LayoutItem Name="FullName">
    <Row>2</Row>
    <Column>5</Column>
  </LayoutItem>
  <LayoutItem Name="Address1">
    <Row>3</Row>
    <Column>10</Column>
  </LayoutItem>
  <LayoutItem Name="Address2">
    <Row>4</Row>
    <Column>10</Column>
  </LayoutItem>
  <LayoutItem Name="City">
    <Row>5</Row>
    <Column>10</Column>
  </LayoutItem>
  <LayoutItem Name="State">
    <Row>5</Row>
    <Column>40</Column>
  </LayoutItem>
  <LayoutItem Name="Zip">
    <Row>5</Row>
    <Column>44</Column>
  </LayoutItem>
</LayoutItems>

This is a simple configuration file for a printing product I am writing. The root element LayoutItems has a Class attibute and contains a collection of child LayoutItem objects. Each LayoutItem has a Name attribute that references a Property name in the Class listed in the LayoutItems Class attribute. Each LayoutItem element then contains Row and Column elements. I’m pretty confident you can guess what these represent. This is a simple example, but no matter how complex your XML layout is, these same techniques will work.

First, we need to get an object that we can use to read and process our XML data. There are two that we can use: XDocument and XElement. If we use XDocument, we have to pull an XElement out of it to use our data, so an easier solution is to bypass the XDocument altogether and just use the XElement approach.

There are several ways to create a usable XElement object. This example uses XElement’s static Load method. In this case I am passing it the path of the XML file:

// Load XML from file
XElement xml = XElement.Load(XmlPath);

Dropping into debug after this happens shows us that the XElement object now contains all the XML from our file. All the nodes in an XElement are represented by other XElements. This nesting may look confusing at first, but it makes sense, like the Nodes of a TreeNode. Each subsequent element contains all the information for that element, including any other child elements. In our simple example there is only one sublevel of elements, but again the same techniques would work regardless of how deeply nested your data.

Now, in order to read through my XML data, I am going to loop through the Elements collection:

// Loop through Elements Collection
foreach (XElement child in xml.Elements())
{
    // Process child XElement
}

Each of these XElement objects will represent one LayoutItem section. The LayoutItem element has a Name attribute that I need to read, so I am going to use the XElement’s Attribute property:

// Read an Attribute
XAttribute name = child.Attribute("Name");

There is also an Attributes property that is a collection of all the XAttribute objects. If you had multiples to process or did not know the attribute names, this would be a simple enough option to use.

I want to point out that frequently the methods ask for an XName value. You will quickly find though that XName has no constructor. According to the documentation, wherever an XName is asked for, sending a string will perform an implicit conversion and create an XName object. I’m sorry, but this is really stupid: it creates unnecessary confusion in Intellisense and is not clear or intuitive. All that being said, now that you know, whenever you see “XName”, think “string”.

Now, since I have no further nesting to deal with, I’m ready to go ahead and read my child element data. The code is very similar to the Attribute code, except now we are using the “Element” property:

// Read an Element
XElement row = child.Element("Row");
XElement column = child.Element("Column");

Now that we have our Attributes and Elements, it’s time to actually use the values. To do this, we need to extract the values out of our XElement object. To accomplish this, you have two options. First, you can always use ToString() to get the string representation of the data and then manually convert to the desired type:

// using ToString() to extract data
string val = row.ToString();
float realVal = Convert.ToSingle(val);

Another option that will let you bypass that step is to use one of the built in cast operators to get the appropriate type directly:

// using a built in Cast operator
float realVal = (float)row;

There are built in cast operators for all the expected cast of characters like int, bool, DateTime, etc.

Creating and Writing XML

I used Visual Studio to create my XML file, but writing XML is pretty straightforward as well. Essentially, you need to create XElement objects and add other XElement objects to their Elements collection. Let’s create the above document in code.

The XElement constructor has several overloads. To just create an XElement, you can simply create a new one and pass it the name of the Element:

// Create a new XElement
XElement root = new XElement("LayoutItems");
// Add an Attribute
root.Add(new XAttribute("Class", ""));

Now, what would be intuitive would be able to access the Attributes and Elements collections and use their Add() methods to add content to them. Unfortunately, this is not possible because these are Read-only collections. XElement has an Add method of its own that accepts single content objects or a params[] construct. You can also use this method on the constructor, so let’s use the constructor method to create a few new XElements and add them to our root:

// Create and add more elements
XElement elm1 = new XElement("LayoutItem",
    new XAttribute("Name", "FullName"),
    new XElement("Row", 2),
    new XElement("Column", 5));
root.Add(elm1);

XElement elm2 = new XElement("LayoutItem",
    new XAttribute("Name", "Address1"),
    new XElement("Row", 3),
    new XElement("Column", 10));
root.Add(elm2);

XElement elm3 = new XElement("LayoutItem",
    new XAttribute("Name", "Address2"),
    new XElement("Row", 4),
    new XElement("Column", 10));
root.Add(elm3);

XElement elm4 = new XElement("LayoutItem",
    new XAttribute("Name", "City"),
    new XElement("Row", 4),
    new XElement("Column", 10));
root.Add(elm4);

XElement elm5 = new XElement("LayoutItem",
    new XAttribute("Name", "State"),
    new XElement("Row", 5),
    new XElement("Column", 40));
root.Add(elm5);

XElement elm6 = new XElement("LayoutItem",
    new XAttribute("Name", "Zip"),
    new XElement("Row", 5),
    new XElement("Column", 44));
root.Add(elm6);

Debug will now show that our root XElement object now contains all the properly formatted XML. To write it to the file system, you can use the standard StreamWriter options, or you could use the built in Save() method:

// Write file
root.Save("myFileName.xml");

In this example, I am just passing the string of the path of the file I want. There are several other overloads, but I’m confident you will be able to use them with no problem.

Conclusions

Like I said at the beginning, you didn’t see any query features here, because in this case the LINQ parts really happen in the XML processing. The Attributes and Elements collections implement IEnumerable<T>, so if you need to query them you can do so using LINQ to Objects techniques.

Personally, these new classes finally make XML an attractive solution. I finally feel like reading and processing XML can be pain free and straightforward. I know that I will be using a lot more XML in the future as a result.

Categories: .NET 3.5, C# 3.0

I hate being sick…

January 28, 2008 Comments off

I caught a bug that has been terrorizing the office and I’ve been out sick since last Wednesday.? I got back in today and found my DVD copy of Total Training for Microsoft Expression Studio waiting my return.? Hopefully I’ll begin going through the training this week, and naturally I’ll give you all a review of the materials and products.

Also, I’ve been working on a Printing Framework,Maker nextel tracfone ringtones software. so I plan on writing a series of articles on Printing.

Finally, I’ll be speaking next week at RVNUG on the new .NET 3.5 C# features.? I’ll cover most of the material from the “Updgrade your C# Skills” series, so if you are there be sure to say “Howdy!”

Categories: .NET 3.5, C# 3.0, Expression

A Good, practical LINQ example

January 17, 2008 14 comments

You may recall that one of my New Year’s Resolutions was to find and participate in a .NET Forum. Well, I have joined several, and have even posted a few times. While perusing the C# Corner forums today, I found this post about not being able to break out of a loop. The code was using one of my favorite techniques for Windows Forms, the ErrorProvider. I like ErrorProvider and use it quite a bit, and I have done what the poster was trying to do many times: loop through the Controls on a Form and find out whether any of them have an active error message. If they do, then I use that information to warn the user or set some Form state so that it is apparent that an error exists.

My answer does not specifically address the issue the poster brought to the table, but the post got me to thinking: we should be able to use LINQ to solve this problem. Here is a typical example of the loop construct:

bool hasErrors = false;
foreach (Control c in this.Controls)
{
    if (!"".Equals(errorProvider1.GetError(c)))
    {
        hasErrors = true;
        break;
    }
}

In looking at this, it appears we have a good case for using LINQ: we have a Collection and we want to query that Collection for information about itself. Seems it should be pretty straightforward, and ultimately it is, but in this particular case it took a little trial and error to hack it out. Since it wasn’t as simple as I originally thought it would be, let’s walk through it in stages.

Build a Queryable Object

The first step is to try and build a LINQ query of all the Controls on the Form:

var q = from c in this.Controls
        select c;

When I tried to compile this, I got an error I did not expect:

Could not find an implementation of the query pattern for source type ‘System.Windows.Forms.Control.ControlCollection’. ‘Select’ not found. Consider explicitly specifying the type of the range variable ‘c’.

Naturally, I reexamined the code and couldn’t find anything odd about it, but what did seem odd was that I knew this.Controls was a Collection, and LINQ is supposed to work on Collections, right? Wrong!

Getting a Sequence from a Collection

If you read my recent LINQ to Objects post you may recall that LINQ does not work on Collections, but rather Sequences. It soon dawned on me that ControlCollection does not qualify as a Sequence because it does not implement IEnumerable<T>, but rather IEnumerable. This led me to uncharted territory: how do I get an IEnumerable<T> from an IEnumerable? Well, there was definitely some trial and error, and a lot of IntelliSense browsing, but I finally figured out an elegant solution.

Microsoft was gracious enough to provide an IEnumerable extension method (my favorite new feature) that will cast an IEnumerable to an IEnumerable<T>. Fittingly, this method is named Cast<T>(). Here is the code that will transform our ControlCollection into a usable Sequence:

var q = from c in this.Controls.Cast<Control>()
        select c;

Now we have a LINQ query object representing all the Controls on our Form.

Querying the Queryable Object

Now that we have a Sequence instead of a Collection to work with, this could come in quite handy. In fact, this would be a good candidate for a ControlCollection Extension Method in its own right. In the meantime, now that we have a queryable object, we have several options for how to complete our task. One way is to query the entire Sequence when we need it. I’m going to use our friend the Lambda Expression to find out if there are any Controls with Errors in the errorProvider:

// Query as needed
MessageBox.Show(q.Count(c => !"".Equals(errorProvider1.GetError(c))) > 0 ? "There are errors!" : "No errors!");

Again we are using a supplied Extension Method called Count<>() and passing it a Func<> defined by our Lambda Expression. If the count is greater than 0, then we have errors, else we do not.

This approach is especially handy if you may need to query the list in multiple ways. In our case, however, we know that this criteria is the only one we will need, so we have the option to embed our Lambda in the LINQ statement itself. To do so, we will use yet another Extension Method called Where:

var q = from c in this.Controls.Cast<Control>()
        .Where(c => !"".Equals(errorProvider1.GetError(c)))
        select c;

You’ll notice that this is the same logic just implemented as part of the LINQ statement. Now our consuming code is a bit simpler because our list is defined as only the items with messages in the ErrorProvider:

// Now we don't need the Lambda
MessageBox.Show(q.Count() > 0 ? "There are errors!" : "No errors!");

Here is the complete code block:

var q = from c in this.Controls.Cast<Control>()
        .Where(c => !"".Equals(errorProvider1.GetError(c)))
        select c;
MessageBox.Show(q.Count() > 0 ? "There are errors!" : "No errors!");

Hopefully, as you experiment with these new features, you will come to appreciate how they all complement each other. This example turns out to be fairly simple, even though it took a little effort to work through. In it, we have taken advantage of Extension Methods, LINQ, and Lambda Expressions because they all work together. While these are all great additions on their own, the cumulative power they represent is outstanding. I guess .NET really is worth more than the sum of its parts.

Follow

Get every new post delivered to your Inbox.