Saturday, February 09, 2008

I am, like to-oh-tally suscribed to the WebDevTools blog, dude! Here's why

[A New DevLife Post]

Saturday, February 09, 2008 11:57:35 AM (Eastern Standard Time, UTC-05:00)  #     |  Comments [0]  | 

I seem to frequently point people to the technical story of MySpace as they went through the painful evolution that eventually led them to have a serious ASP.NET website to manage what is one of the highest traffic websites in the world. Even if it's not ASP.NET that you  choose, it's a good lesson in planning ahead. Read more...

[A New DevLife Post]

Saturday, February 09, 2008 11:55:43 AM (Eastern Standard Time, UTC-05:00)  #     |  Comments [0]  | 
 Friday, February 08, 2008

Jessica Fosler has a very funny YouTube video of a Sesame Street Cookie Monster clone interacting with a "totally indestructable" machine.

She also has some great WPF UI posts that I need to dig into ...

Friday, February 08, 2008 10:03:02 PM (Eastern Standard Time, UTC-05:00)  #     |  Comments [0]  | 

I'll be doing a DotNetRocks show with Carl and Richard about the Entity Framework. Danny Simmons was on DNR last April so it's definitely time for an update!

 

Friday, February 08, 2008 6:49:28 PM (Eastern Standard Time, UTC-05:00)  #     |  Comments [1]  | 

Now that the Team System's Unit Testing tools are built in to VS2008 Pro, many of us will finally get a chance to take a look at it.

Luckily I know some VSTS experts who aren't too far away and we have Sarah Cameron coming down from Montreal to teach us all about how to use the built-in Unit Testing in VS2008 on Monday. We have a bunch of NUnit users in the group and they are also curious about how the Microsoft version stacks up. Interestingly this session is drawing out a lot of people who have never been to a VTdotNET meeting before! Go Sarah!

Read more at www.vtdotnet.org!

 
Date: 02/11/08
Speaker: Sarah Cameron (InCycle Software ) --
Location: VT Tech, Williston Campus
Topic: Unit Testing in Visual Studio 2008
VS2008 Pro now includes the Unit Testing features that were previously only available in the VSTS sku. Sarah Cameron, a Visual Studio Team System expert from InCycle Software (www.incyclesoftware.com), will demonstrate how to use and really benefit from Unit Testing in VS2008 using this built in tool..

Speaker Bio: Sarah Cameron is a consultant specializing in Visual Studio Team System, with experience on projects from inception to delivery following well-defined software processes. Sarah has been working as a consultant for Incycle Software (www.incyclesoftware.com) a Montreal based firm specializing in Team System consulting services. She has been assisting ISVs and larger corporations successfully plan and deploy Visual Studio Team System. She may be contacted at sarah.cameron@incyclesoftware.com

Friday, February 08, 2008 6:04:36 PM (Eastern Standard Time, UTC-05:00)  #     |  Comments [0]  | 
 Thursday, February 07, 2008

From Mad River's website this morning:

POWDER CENTRAL at Mad River Glen today as we picked up a solid 12" overnight. This on top of the 8-10" yesterday, on top of the 4-6" from the day before that so we will be approaching EPIC skiing conditions today on Gen. Stark Mountain. I was no math major but I think that like 2 feet in the past 3 days! It continues to dump snow vigorously and the best part is it should continue to do so all day with well over a foot total expected. Actually the best part really is that yet another big storm is winding up to wallop us again this weekend

So after a lifetime of learning how to ski on hard packed snow and ice (we're talking eastern skiing), I'm now having to learn how to ski in real powder. My skis are too skinny, that's for sure. But if I find powder that is a little fluffier, I'm turning like a champ. If it's too heavy I unexpectedly revert to the snowplow I learned when I was 6 years old.Well, at least it's a really soft landing when you fall. When my ego and my energy level fail me, I can always fall back on that old excuse: "ummm, I have to go home and get back to work!"

Thursday, February 07, 2008 10:10:58 PM (Eastern Standard Time, UTC-05:00)  #     |  Comments [0]  | 

From TEKsystems 

 

These jobs are getting listed on thingamajob.com, but not all of them seem to be there yet. Here's a direct link to TEKsystem's listings.

 

Title                 Software Developer

Location:         Stowe

Number of openings:  3

Responsibilities: 

Software Developers provide the technical expertise to create enhancements, code corrections, and new functionality.   Software Developers fulfill both the support and development programming roles and work in teams with other developers, support, QA and management when appropriate.

 

Requirements

·      Minimum of 3 years experience developing and supporting software applications in a Windows environment.

·      Four year college degree or equivalent experience – Bachelor’s degree desired.

·      Able to learn and master new technology quickly and improve personal performance through continual self-study.

·      Desire to provide outstanding products and customer service.

·      Able to work on multiple projects simultaneously and to cope with diversity and complexity in a high-pressure, rapidly changing environment.

·      Strong interpersonal and team relationship skills and able to work well individually or as part of a team.

·      Demonstrated understanding of the following: Windows 2000, XP, and 2003 Server in conjunction with software development.

·      Experience utilizing one or more of the following:  SQL Server 2000/2005, .NET.

·      Ability to maintain high degree of confidentiality concerning development products.

·      Detailed knowledge of one or more SMS software products helpful (i.e., two or more years of installation, product management, or systems support experience) or able to thoroughly learn SMS software products.

·      Demonstrated technical writing ability and basic email skills.

·      Extremely detail oriented and dedicated to follow-through in all work with a focus on project quality, completeness, and thoroughness.

 

 

Title                 Senior Software Developer

Location:         Stowe

Number of openings:   2

Responsibilities: 

Senior Software Developers provide the technical expertise and leadership to create technical architectures and designs, enhancements, code corrections, and new functionality.   Senior Software Developers fulfill both the support and development programming roles and work in teams with other developers, support, QA and management when appropriate.

 

Requirements

·      Minimum of 5-7 years experience developing and supporting web based applications in a Windows environment.

·      Four year college degree or equivalent experience – Bachelor’s degree desired.

·      Able to learn and master new technology quickly and improve personal performance through continual self-study.

·      Desire to provide outstanding products and customer service.

·      Able to work on multiple projects simultaneously and to cope with diversity and complexity in a high-pressure, rapidly changing environment.

·      Strong interpersonal and team relationship skills and able to work well individually or as part of a team.

·      Demonstrated understanding of the following: Windows 2000, XP, and 2003 Server in conjunction with software development.

·      Experience utilizing one or more of the following: SQL Server 2000/2005, .NET.

·      Ability to maintain high degree of confidentiality concerning development products.

·      Detailed knowledge of one or more SMS software products helpful (i.e., two or more years of installation, product management, or systems support experience) or able to thoroughly learn SMS software products.

·      Demonstrated technical writing ability and basic email skills.

·      Extremely detail oriented and dedicated to follow-through in all work with a focus on project quality, completeness, and thoroughness.

 

Contact

      Alicia Ferraro

aferraro@teksystems.com
TEKsystems - Boston, MA
111 Speen Street
Suite 520

Framingham, MA  01701

Thursday, February 07, 2008 9:39:14 PM (Eastern Standard Time, UTC-05:00)  #     |  Comments [0]  | 

One of the drawbacks with the partial postbacks in AJAX is that you can't go forward or back in your web browser to different states of the page created by the partial postbacks. Nor can you create a shortcut to one of the views.

The ASP.NET 3.5 Extensions has functionality in there to enable these scenarios. It's pretty simple to pull off thanks to the new tools.

Jonathan Carter has a great post on how to use this. It's just #1 of more to come so keep tuned. Jonathan is a new to Microsoft's Visual Studio Developer Platform and Evangelism team with the dream job of writing and speaking about all the cool things you can do in VS. You can tell by his post that he will be a great resource for us to learn new features.

Thursday, February 07, 2008 3:00:40 PM (Eastern Standard Time, UTC-05:00)  #     |  Comments [0]  | 

I mentioned a few days ago that Matthieu Mezil will be presenting on Entity Framework at TechDays 2008 in Paris next week.

There are more at this conference!

  • LINQ & Entity Framework: Fabrice Marguerie & Sebastien Ros
  • ADO.NET Data Service: Mitsu FUruta & Pierre Lagarde
  • There are also a bunch of LINQ talks - too many to list, in fact!

And there are more EF talks upcoming at other conferences as well!  (Including my own, of course ;-))

DevWeek in London March 10-14:
 "Understanding the ADO.NET Entity Framework" (Mike Taulty)
 "Patterns of use with the new Entity Framework" (James Winters)
This looks like a great conference filled with a huge list of rock star speakers, although, oddly, it's all men. I wonder if that will impact female attendance (either positively, thanks to all those good looking, brainy guys or negatively, if they are afraid they will feel out of place.)

There don't seem to be any EF talks at VSLive for San Francisco or Orlando. Too bad.

Developer Summit 2008 April 9-11 in Stockholm
   Full Day Workshop on Entity Framework (by me :-) Yay, I get to go to Sweden!)
   ADO.NET Data Services (by WCF & Web Services guru: Christian Weyer)
   Advanced Entity Framework: EDM in the Enterprise (by me)

DevConnections (April 20 - 23 Orlando) has a whole Data Access track now!
   Full Day Workshop on Entity Framework (by me) on April 20th
   During the conference Proper:
   On Microsoft Day, 2 EF sessions, an Astoria Session and a guidance session on choosing from the myriad data access tools
  On the third party days, 2 EF sessions by John Papa, 1 by me and 1 by Kathleen Dollard (and additional ADO.NET sessions as well!). I'll also be doing a session on ADO.NET Data Services in the ASP Connections track.

DevTeach (May 12-15 Toronto)
   Intro to Entity Framework (Barry Gervin)
   An Advanced EF session and a data access guidance session by me

If you know of any others, send me a note and I'll list it!

Thursday, February 07, 2008 2:40:01 PM (Eastern Standard Time, UTC-05:00)  #     |  Comments [0]  | 
 Tuesday, February 05, 2008

One of the major accomplishments of EF Beta3 was that the performance of materializing objects through Object Services (that means with Linq to Entities or with Entity SQL) was improved significantly. So significantly that  they are almost comparable with querying through EntityClient which streams data into a datareader.

I did a number of experiments that I hadn't blogged about yet but did ask on the forums [EF: Best way to compare apples to apples when comparing perf of materialized objects vs datareader?] where I learned that ViewGeneration was the most time consuming part of the query process.

In the end, I had found this when performing the same query with LINQ to Entities, with EntityClient and with ADO.NET after separating the ViewGeneration time out in order to do fair comparisons. You can use EDMGen to pre-compile a query so that the expense of ViewGeneration becomes negligible at runtime.

  • ViewGeneration: ranges around 1110 milliseconds
  • Iterate through Materialized Objects: 14 to 15 ms
  • Iterate EDM shaped data through datareader: 17 to 18 ms
  • Iterated sqlclient datareader (classic ado.net): about 4 ms

BUT! Even better, Brian Dawson has written an extensive blog post about performance in Entity Framework: Exploring the Performance of the ADO.NET Entity Framework - Part 1. The post is filled with charts and graphs and is very enlightening. Brian even has a pie chart showing ViewGeneration as 56% of the time for performing a query.

The post digs very deeply into the query and object materialization pipeline, which, to someone like me, is dangerously close to pillow talk! ;-)

And it's only Part 1!

Tuesday, February 05, 2008 10:10:12 AM (Eastern Standard Time, UTC-05:00)  #     |  Comments [0]  | 

Matthieu Mezil is presenting on Entity Framework and LINQ (and VSTO) at next week's first (?) .NET event in Paris: TechDays 2008.

I wanted to see what else was going on and found the website (by BrainSonic) and boy is this a fantastic Silverlight implementation. Even if you don't know French, I highly recommend checking out the website, if for no other reason than to be inspired!

This is just but a small tidbit. After searching for Mezil, it spun (literally) these three items on to the calendar.

There's more, but I'll let you go play with it yourself.

Matthieu has been building up his EF expertise as well as using the forums as a means to challenge himself to learning more and sharing what he's learned.

Tuesday, February 05, 2008 9:22:00 AM (Eastern Standard Time, UTC-05:00)  #     |  Comments [5]  | 
 Monday, February 04, 2008

Congrats to Jim Wooley, Fabrice Marguerie and Steve Eichert! And THANKS for writing it. It's been a long haul! I have really been anticipating this book!

Monday, February 04, 2008 10:20:54 PM (Eastern Standard Time, UTC-05:00)  #     |  Comments [1]  | 

I am part of United's mileage plus program and thanks to the way-too-much travelling I do, am a premier member. (I would have to fly 50,000 a year to get to the next level and I cannot imagine travelling that much more!)

I just received an email alerting me that United is going to start charging $25 for a second checked bag. This doesn't apply to me because I have "status" with my miles. With the rising cost of fuel, I suppose it isn't too surprising and possibly an interesting way to avoid raising airfares across the board significantly. This way, you just pay a little extra if you are loading more stuff onto the plane.

But geeze, what's next, airfare based on weight?

Monday, February 04, 2008 6:02:56 PM (Eastern Standard Time, UTC-05:00)  #     |  Comments [0]  | 
 Saturday, February 02, 2008

Burlington geeks from the itty bitty little market that can get 120 peope to who up at an MSDN event :-), will have to plan a road trip if they want to attend an official Launch event for VS2008, SQL SErver 2008 and Windows 2008 Server. There will be one in Boston on March 18 and one in Albany on May 23.

However, we will have two INETA/Microsoft/PASS sponsored launch events through the VTdotNET and VTSQL. VTSQL will have a "SQL Sever 2008 launch party" meeting on April 7th and VTdotNET's VS2008 Launch will be on April 14th. While we can't promise a copy of all three products to every attendee, hopefully we'll have licenses to give away. Fortunately, in December we were extremely lucky to have a VS2008 Install Fest where over 40 licenses to VS2008 Pro were given away to Vermont.NET User Group members, thanks to Chris Bowen.

Saturday, February 02, 2008 2:03:02 PM (Eastern Standard Time, UTC-05:00)  #     |  Comments [0]  | 

At VTdotNET's last meeting, we were happy to have Jeff McWherter share with us his hard-earned lessons about improving performance in asp.net apps. Jeff spends a lot of time dealing with this in his job and it was very obvious that he wasn't just sharing something he read about, but his very adept experience.

He started out by posing what may seem a redundant question, but is a really good way of getting people to focus on the issues. He asked for someone to explain the difference between web site performance and web site scalability.

These were the two topics he focused on as he delved into a number of performance bottlenecks as well as issues which prevent websites from being able to scale out. Jeff used a variety of performance measurement tools in the process which was very educational. Sure beats waiting for the server to come to it's knees as an indication that some changes might need to be made! Then of course he showed us lots of ways, some very simple, some more complex, to solve the problems displayed by the tools.

Often people focus on their specific asp.net code and don't consider out of scope processes that may be causing the problems such as a database query or a file download. I was happy that he made sure people were aware of SQLProfiler!

One of the tips that really hit home for me, because I was dealing with this problem, was if you have file download/upload functions in your application, split those off to another process, whether you can do that on another server or just in another app on your web server (e.g a web service). It's not just the time involved (which can be helped also by doing this asynchronously) but the resources involved. If multiple people are uploading or downloading at the same time, this could really pose a problem.

In these days where many of us are scrambling to learn the new technologies that are coming down the pipe, it is a huge benefit to have someone show us how to get more out of the tools that we are working with today with lessons that will apply to the tools of the future.

The day after the meeting, Jeff and his wife (who had spent a fun few days with us at our house) headed off to Smuggler's Notch to do some ice climbing and then were going to be on vacation for a while after that visiting friends around the Northeast.

I think he's back in Michigan now, so I'll be sure to get his list of resources from him and up on the VTdotNET site.

Saturday, February 02, 2008 9:25:02 AM (Eastern Standard Time, UTC-05:00)  #     |  Comments [2]  | 
 Friday, February 01, 2008

I change my mind daily about going to MIX.

I WANT TO GO TO MIX. The registrations are dwindling and it will soon be sold out.

But I don't want to deal with this:

SOUTH BURLINGTON -- Today's ice storm has caused numerous delays and cancellations this afternoon at Burlington International Airport, airport officials said.

Between about 3 and 5 p.m., there are expected to have been two delayed and two canceled arrivals, and another four or five delays and two canceled departures from the airport, said airport Facilities Manager Brian Searles.

"Right now, it's really all about where flights are coming from," said Searles. "We're operational here."

Searles said a continuing ice-build up could force the airport to close at some point this evening.

People should call the the airlines for more information on their flight, Searles said.

[source today's burlington free press]

This article doesn't even mention the winds that have been howling the past 6 hours. It's not much fun to be in a plane that is landing in high winds on ice covered runways. Been there done that.

And then there's always the fun at O'Hare. Last April we sat onthe runway for 2 hours when we landed at O'Hare, got on another plane and sat there for 3 hours before we took off. Bah!

Friday, February 01, 2008 9:56:02 PM (Eastern Standard Time, UTC-05:00)  #     |  Comments [1]  | 

In my post about rewriting Brad Abram's MVC + Entity Framework example in VB, I pointed out a better way to query a collection of objects and return their entity references (eg, query products and bring along the category information) without writing a scary query filled with references, checks for nulls, etc.

But there was something bothering me about the query.

I went from this (Brad's)

List<Product> products =TheProducts.Where(c => c.Category.CategoryName == category).ToList();
//prepare the view by explicitly loading the categories  
products.FindAll(p => p.Category == null).ForEach(p => p.CategoryReference.Load()); 

To this

     var _prod = Northwind.Categories.
              Where(c => c.CategoryName == id).
              OrderBy(c => c.CategoryName).
              Select(c => c.Products).
              First().ToList();

Which gives the same effect of ending up with a set of products and being able to drill into product.category.categoryname, etc.

But that query represents something that Entity Framework is not supposed to do, and I asked Danny Simmons about it.

Entity Framework will not perform actions that you did not request. I selected only products in my projection, yet I also got back categories. It was convenient, but it was against the promise of EF. I didn't ask for categories.

In the end, a bug was filed, because indeed, that shouldn't be happening.

However, it's still VERY easy to explicitly tell EF to bring along a collection (eg if I query categories I can say "and give me those products while your at it) or an entity ref (query products and ask for the category) by using Include.

Here's the same query for the MVC view even simpler.

    var _products = Northwind.Products.Include("Category").
        Where(p => p.Category.CategoryID == 1).ToList();

There are other ways to achieve this as well, but for this scenario, this is the most streamlined (as far as I know ;-)).
Friday, February 01, 2008 9:08:15 AM (Eastern Standard Time, UTC-05:00)  #     |  Comments [4]  | 

The date's been picked for the next New England Code Camp!

More details to come - watch Chris Bowen's blog!

Friday, February 01, 2008 8:05:22 AM (Eastern Standard Time, UTC-05:00)  #     |  Comments [0]  | 
 Thursday, January 31, 2008

I followed the great walkthrough on Brad's blog showing how to use MVC together with Entity Framework. But I did it my own way - in VB, using a different database and trying to write more effective EF queries. I have a simple solution that renders these views from the AdventureWorksLT database:

Start with a list of customers who have orders in the system (that's less than 10% of the full customer list)

Then I can click on a customer and see a list of their orders

 

Then drill in to see the order details. (The Edit button is not implemented yet, in case you were wondering!)

 

So here is what's significantly different from Brad's walkthrough.

  • My EDM is created from AdventureWorksLT.
  • The relationship from AW's SalesOrderHeaders to Customer is the same as the relationship from Northwind's Products to Category. Therefore, where he use Products, I use SalesOrderHeaders and  where he uses Categories, I use Customers.
  • One of the keys for getting data easily to a view is that we need to send ONE object (and not an anoymous type) to the view. Yet what we really desire in the case of the List of Order (which also has the Customer Name) and the list of details (which also has data from the order and the customer) is an object graph.

So on the 2nd page, I need to pass in an set of orders with their related Customer entity so that I can have access to the customer name.

On the 3rd page, I need to pass in a set of order details with their related Order entiteis AND the order's related customer.

Brad achieves this by starting with the desired entity and then using entity references and some scary looking LINQ to Entities queries.

List<Product> products =TheProducts.Where(c => c.Category.CategoryName == category).ToList();
//prepare the view by explicitly loading the categories  
products.FindAll(p => p.Category == null).ForEach(p => p.CategoryReference.Load()); 

Only because I've spent a lot of time with LINQ to Entities, do I happen to know a little trick.

If I start with the "parent", i.e. category and query it's property collection (i.e. products), when I return the property collection, the "parent" entity is still attached.

So taking Brad's query, I can get the same effect with this query (still in C#):

     var _prod = Northwind.Categories.
              Where(c => c.CategoryName == id).
              OrderBy(c => c.CategoryName).
              Select(c => c.Products).
              First().ToList(); 

(Update: turns out this only works because of a bug which will get fixed - see THIS POST for an even better way!)

This returns a list of products that belong to the category. However I do not have to do any extra loading to get to Product.Category.CategoryName. Beasue my query began with the Category, it's already there. (I learned this by trial and error by the way.)

Therefore, in my SalesOrderController (my versoin of his productController), the List ControllerAction code is a little different.

I use the same type of querying to get the order details.

Another thing that I spent some time thinking about and asking about was about the ObjectContext. In a web app you want an ojbectcontext to be as short-lived as possible. I notice that Brad was instantiating in the class level declarations. This is okay because in the background, MVC  instantiates  the class for each ControllerAction and then disposes it when it's finished. It doesn't hang around waiting for another method call. (This is one of the key premises of MVC. As Kevin Hoffman explained to me, it works in "short bursts" long enough to get something out to the browser. I have much to learn!)

Brad uses the CategoryName as the basis for the view creation so that he gets a pretty URL http://host/products/list/Beverages.

I'm still seeing if there is a way around this, but I don't like querying on a string like this. I like my keys! So I'm passing in CustomerID and SalesOrderID and my urls aren't as pretty.

Here is what my controller looks like and you can see my queries that populate the Customer List, the customer's order list and the order's detail list.

Imports System.Web.Mvc
Imports System.Linq
Imports MvcApplication.awModel
Namespace MvcApplication.Controllers 

  Public Class SalesOrdersController
    Inherits System.Web.Mvc.Controller
    <ControllerAction()> _
    Sub Index()
      REM Add Action Logic here
    End Sub
    'example URL:http://localhost:xxxx/SalesOrders/Customers
    <ControllerAction()> _
    Public Sub Customers()
      Using aw = New awEntities
        Dim _customers = aw.Customers. _
Where(Function(c) c.SalesOrderHeaders.Any). _
OrderBy(Function(c) c.CompanyName).ToList RenderView("Customers", _customers) End Using End Sub
    'example URL:http://localhost:xxxx/SalesOrders/List/[CustomerID]
    <ControllerAction()> _
    Public Sub List(ByVal id As String)
      Using aw = New awEntities
        Dim _salesorders = (From cust In aw.Customers _
                          Where cust.CustomerID = id _
                          Select cust.SalesOrderHeaders).FirstorDefault.ToList
        RenderView("SalesOrdersbyCustomer", _salesorders)
      End Using
    End Sub
    'example URL:http://localhost:xxxx/SalesOrders/List/#### (order number)
    <ControllerAction()> _
    Public Sub Detail(ByVal id As String)
      Using aw = New awEntities
        Dim _order = (From ord In aw.SalesOrderHeaders.Include("SalesOrderDetails.Product") _
                     Where ord.SalesOrderID = id _
                     Select ord).First
        Dim _order2 = (From cust In aw.Customers.Include("SalesOrderHeaders.SalesOrderDetails.Product") _
                   Where cust.SalesOrderHeaders.Any(Function(so As SalesOrderHeader) so.SalesOrderID = id) _
                   Select cust.SalesOrderHeaders).First.ToList.First
        RenderView("SalesOrder", _order2)
      End Using
    End Sub 

  End Class
End Namespace 

The markup is not really different from Brad's since I can drill from the ViewData into my references (Customer, Product, SalesOrder) thanks to my queries (which make me feel so clever!)

The last page just uses tables to do the trick.

<asp:Content ID="Content1" ContentPlaceHolderID="MainContentPlaceHolder" runat="server">
<h2><%=ViewData.Customer.CompanyName%></h2>
<h3>
<%=String.Format("Sales Order #: {0}", ViewData.SalesOrderNumber)%>
<%=String.Format("Order Date: {0:d}", ViewData.OrderDate)%>
<%=String.Format("Order Total: {0:C2}", ViewData.TotalDue)%>
</h3>
 <table>
 <tr><td style="width: 225px">Product</td><td style="width: 154px">Quantity</td>
   <td style="width: 256px">Line Item Total</td></tr>
            <% For Each detail As awModel.SalesOrderDetail In ViewData.SalesOrderDetails%>
            <tr>
               <td style="width: 225px"><%=detail.Product.Name%></td>  
               <td align="center" style="width: 154px"><%=detail.OrderQty%></td>
               <td style="width: 256px"> <%=String.Format("{0:C2}", detail.LineTotal)%></td>
            </tr>
        <% Next%>
    </table>
</asp:Content>

As an aside, this was my first time really playing with client side code in VS2008 and I am enamored of all the intellisense in there!

Thursday, January 31, 2008 3:03:52 PM (Eastern Standard Time, UTC-05:00)  #     |  Comments [2]  | 

In the TabletPC SDKs and in WPF it's very easy to take an ink image and save it to an image format - BMP, JPG, etc.

Then came Silverlight and the InkPresenter and naturally I wanted to do the same. But it wasn't so easy.

Silverlight itself isn't bogged down with that functionality. So you first need to get the XAML representation of the Ink and send it to a service where either the TabletPC SDK or the WPF APIs are available. Even then you are not home free because the Silverlight ink is not quite the same as either of the other two. So you then need to extract data from the XAML representation of the Silverlight ink and create a new object for whichever API you choose.

This was all done in Silverlight 1.0. I haven't pulled this into Silverlight 1.1/2.0 yet, but it should all be the same and you still have to do the conversion on the server side. THe only difference of course, is the javascript needs to be converted to .NET code on the client side.

Even then, there is still some more trickery because there is something strange with using the width from the Silverlight object and I spent hours just experimenting with getting the proportions to display properly in the image. I also spent a lot of time struggling with the colors because the javascript output of the color values doesn't line up with what WPF wants. You'll see in the code comments all of the conversions going on.

Once I had all of that worked out (and this represents hours of effort) there were still some issues. Luckily, Stefan Wick, who is the ultimate guru on this topic and finally has a blog - hoorah!, was able to set me straight (and trim some  of my code down significantly).

I hadn't thought much of this nor, apparently had anyone else, until someone recently emailed asking me how I did it so that he can use it as part of a solution in a competition. (I hope that the requirements of the competition don't say anything about original work!), so I thought I would blog the steps.

1) Convert the InkPresenter data to XAML . This does two things. FIrst it enables you to serialize it and pass it to a web service and secondly, it is the lowest reasonable common denominator for sharing between different objects. This javascript code comes from Gavin Gear.

This javascript code reads through the StrokeCollection property of an InkPresenter and builds up a string of xml that is the XAML representation of the StrokeCollection. You could also take the resulting string and pass it to CreateFromXAML to recreate the Silverlight StrokeCollection object.

  if (strokeCollection.Count>0)
  { 
   var xaml = "<StrokeCollection>";
   if (strokeCollection != null)
   {
    for (var i = 0; i < strokeCollection.Count; i++)
    {
        var stroke = strokeCollection.GetItem(i);
        if (stroke.Name>"")
          xaml += "<Stroke Name='" + stroke.Name + "'><Stroke.DrawingAttributes>";
        else
          xaml += "<Stroke><Stroke.DrawingAttributes>";
        xaml += "<DrawingAttributes ";
        xaml += "Color='" + BrowserColorConverter(stroke.DrawingAttributes.Color) + "' ";
        xaml += "OutlineColor='" + convertColorToHexString(stroke.DrawingAttributes.OutlineColor) + "' ";
        xaml += "Width='" + stroke.DrawingAttributes.Width + "' ";
        xaml += "Height='" + stroke.DrawingAttributes.Height + "' ";
        xaml += "/></Stroke.DrawingAttributes>";
        xaml += "<Stroke.StylusPoints>";
        for (var j = 0; j < stroke.StylusPoints.Count; j++)
        {
            var stylusPoint = stroke.StylusPoints.GetItem(j);
            xaml += "<StylusPoint X='" + roundToTwoDecimalPlaces(stylusPoint.X) + "' Y='" +
roundToTwoDecimalPlaces(stylusPoint.Y) + "' />"; } xaml += "</Stroke.StylusPoints></Stroke>"; } } xaml += "</StrokeCollection>";

 

2) Pass this string to a web service method that will do the following to it

3) Create a WPF InkObject from the XAML. Now that I'm in the web service, I can use .NET code. Phew. Note that I did this before I knew how to use LINQ to XML so I struggled through XPath to get this. Watch for an upcoming MSDN Mag article that will have updated code.

      private static StrokeCollection InkObjectfromXAML(XmlNode StrokeColl)
      {
          StrokeCollection objStrokes = new StrokeCollection();
          XmlNodeList strokeElements =
          StrokeColl.SelectNodes("Stroke");
          foreach (XmlNode strokeNodeElement in strokeElements)
          { 

              //step 1: create a new stroke from the stylus point elements in the XAML
              XmlNodeList stylusPointElements =
                  strokeNodeElement.SelectNodes("./Stroke.StylusPoints/StylusPoint");
              XmlNode drawAttribs = strokeNodeElement.SelectSingleNode("./Stroke.DrawingAttributes");
              //points node is sent to GetStrokePOints method to convert to a type 
//that can be used by the new stroke
System.Windows.Input.StylusPointCollection strokeData = GetStrokePoints(stylusPointElements); Stroke newstroke = new Stroke(strokeData); //step 2: grab color metadata about stroke from the xaml //color is a hex value //the stroke object requires a System.Windows.Media.Color type //following code performs the conversion string mycolor = drawAttribs.FirstChild.Attributes["Color"].Value; System.Drawing.Color drwColor = System.Drawing.ColorTranslator.FromHtml(mycolor); //build the new color from the a,r,g,b values of the drawing.color System.Windows.Media.Color newColor = new System.Windows.Media.Color(); newColor.A = drwColor.A; newColor.R = drwColor.R; newColor.G = drwColor.G; newColor.B = drwColor.B; //Step 3: extract width data from xaml, convert to int int myIntWidth; bool parseSuccess = int.TryParse(drawAttribs.FirstChild.Attributes["Width"].Value,
out myIntWidth); //Step 4: apply width & color to stroke //some really wierd unexplainable transformations that I had to get
              // around until the final images looked right.
if (myIntWidth == 3) newstroke.DrawingAttributes.Width = 1.5; else newstroke.DrawingAttributes.Width = 2; newstroke.DrawingAttributes.Color = newColor; //Step 5: add stroke to the stroke collection objStrokes.Add(newstroke); } return objStrokes; }
    //This method (called from the method above, is an abstraciton of some sample code from Microsoft
     private static System.Windows.Input.StylusPointCollection GetStrokePoints(XmlNodeList stylusPointElements)
        {
System.Windows.Input.StylusPointCollection pointData = new System.Windows.Input.StylusPointCollection();

            //The object requires HiMetric point values, create multiplier for conversion
            double pixelToHimetricMultiplier = (2540d / 96d) / 100;

            foreach (XmlNode stylusPointElement in stylusPointElements)
            {
                string xStr = stylusPointElement.Attributes["X"].Value;
                string yStr = stylusPointElement.Attributes["Y"].Value;

                //x and y are in pixels, we need to multiply them to get them into HIMETRIC
                //space, which is what the InkAnalyzerBase expects
                int xInHimetric = (int)(System.Convert.ToDouble(xStr) * pixelToHimetricMultiplier);
                int yInHimetric = (int)(System.Convert.ToDouble(yStr) * pixelToHimetricMultiplier);
                pointData.Add(new System.Windows.Input.StylusPoint(xInHimetric, yInHimetric));
            }

            return pointData;
        }
Now we have an inkObject that WPF will be happy with!

4) Convert WPF Ink to PNG format bytes This is with a BIG thanks to Stefan - we need to start a separate thread to do the conversion from WPF Ink ojbect to PNG. That conversion happens inside the thread. Also, thank to his deep understanding of the ink object, Stefan was able to accomplish in a much smaller amount of code what I had achieved in about 3 times as much code. I was definitely doing loop-dee-loops, but it was the best I could come up at the time.

Note that I am not saving to an actual file here, just creating the bytes because my goal was to store that in a database.

   private static void ThreadforConverttoPNG()
   {
    Thread t = new Thread(new ThreadStart(ConverttoPNG));
    t.SetApartmentState(ApartmentState.STA);  

    // Start ThreadProc.  Note that on a uniprocessor, the new 
    // thread does not get any processor time until the main thread 
    // is preempted or yields.  Uncomment the Thread.Sleep that 
    // follows t.Start() to see the difference.
    t.Start();
   }
   private static void ConverttoPNG()
   {
    //I had originally achieved this with a LOT more code. This is Stefan's more trimmed down method

    //create temporary InkCanvas
    InkCanvas inkCanvas = new InkCanvas();
    inkCanvas.Strokes = strokes;
    //render InkCanvas to a RenderBitmapTarget
    Rect rect = inkCanvas.Strokes.GetBounds();
    RenderTargetBitmap rtb = new RenderTargetBitmap((int)rect.Right, 
(int)rect.Bottom, 96d, 96d, System.Windows.Media.PixelFormats.Default); rtb.Render(inkCanvas); //endcode as PNG BitmapEncoder pngEncoder = new PngBitmapEncoder(); pngEncoder.Frames.Add(BitmapFrame.Create(rtb)); //save to memory stream System.IO.MemoryStream ms = new System.IO.MemoryStream(); pngEncoder.Save(ms); ms.Close(); strokeBytes= ms.ToArray();    }

5) My next step was actually to store the bytes into a database. I wasn't actually saving out to a file. But to do that is simple. System.IO.File lets you create a new file on the fly from a byte array.

   System.IO.File.WriteAllBytes("C:\\myfile.png",strokeBytes); 

So, those are all of the pieces from converting a silverlight inkpresenter image to an image file.