7/31/2010
LifeCycle Solutions - Home ( the software development blog )
 

<July 2009>
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

Subscribe to this feed:

RSS 2.0 | Atom 1.0 |CDF






Friday, July 24, 2009

imageimage

This is just a quick and pretty obvious tip, but worth posting I think.  We recently rolled out a SharePoint solution for a customer and as an afterthought I added a Wiki called 'About this Site' to the home site.  As I identified areas in training that they might need help remembering, I added pages to the wiki with very short steps to do various tasks.  In addition, I showed them how to add their own articles.  Each took just a few minutes to add, and in the end, they had easily accessible documentation on their particular SharePoint install.  I wondered if they'd use it, but I was pleasantly surprised when I logged in and saw 'Last Modified by:' wasn't me!  SharePoint can be pretty daunting to new users, and even old ones.  By adding documentation using the customer's name and terminology familiar to the customer, we made the site much more approachable.

As a bonus, I was able to export the wiki as a template and can now re-use it elsewhere, though I'll need to take care to change the customer name everywhere as needed.  I may look into putting one of these up publicly and building up a generic reusable wiki templates to install as starting places for various common SharePoint scenarios.

This idea extends to non-SharePoint apps as well.  I'm trying this concept to document some ASP.NET MVC apps for customers.  Those haven't rolled out yet, but the customer seems to like the idea so far.

Posted by Daniel Root

Tuesday, July 14, 2009

It's been a long time coming, but Azure's pricing model is now available

Windows Azure:

  • Compute @ $0.12 / hour
  • Storage @ $0.15 / GB stored
  • Storage Transactions @ $0.01 / 10K

SQL Azure:

  • Web Edition – Up to 1 GB relational database @ $9.99
  • Business Edition – Up to 10 GB relational database @ $99.99

.NET Services:

  • Messages @ $0.15/100K message operations , including Service Bus messages and Access Control tokens

Bandwidth across all three services will be charged at $0.10 in / $0.15 out / GB

This translates to a minimum of about $90 to $100 for a small single-server forms-on-data app in the cloud. This is very competitive pricing compared to Amazon E3 windows instances, which run about $1/hour and up, and comparable to low-end dedicated server hosting plans.  In addition, Microsoft is announcing initiatives to enable private cloud services via System Center and public cloud services by licensing and other efforts aimed at hosting services like MaximumASP.

Posted by Daniel Root

Monday, June 08, 2009

Frequently, I'll have add-on reports that we don't necessarily want to deploy to a full Reporting Services installation.  I don't need the scheduling or other features of Reporting Services and would rather just run them in-application and return the result.  To this end, I've written a "WebReportRender" class that takes an .rdlc and renders it as a PDF to the browser.  In WebForms, this meant some goo to render the Http headers to trigger a file download in the browser. I've wrapped all that nicely and it's served me well the past few years. Now,  ASP.NET MVC adds a nice FileContentResult which wraps some of this goo for you.  To reuse WebReportRender in  ASP.NET MVC, I had to add methods to just output the byte array without the Http Headers:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Mail;
using System.Text;
using System.Web;
 
using Microsoft.Reporting.WebForms;
 
 
namespace LifeCycle.Reporting
{
    /// <summary>
    /// Assists in executing and rendering Reporting Service reports.
    /// </summary>
    public class WebReportRenderer : IDisposable
    {
        #region Private fields
        private string fullReportPath;
        private string downloadFileName;
        private LocalReport reportInstance;
        private MemoryStream reportMemoryStream;
        private string reportMimeType;
        #endregion
        #region Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="WebReportRenderer"/> class.
        /// </summary>
        /// <param name="reportPath">The report path.</param>
        /// <param name="downloadFileName">Name of the download file.</param>
        public WebReportRenderer(string reportPath, string downloadFileName)
        {
            if (HttpContext.Current == null) throw new InvalidOperationException("This class is only for use from web applications.");
 
            this.downloadFileName = downloadFileName;
            fullReportPath = HttpContext.Current.Server.MapPath(reportPath);
            using (System.IO.FileStream reportFile = new System.IO.FileStream(fullReportPath, System.IO.FileMode.Open, FileAccess.Read))
            {
                reportInstance = new LocalReport();
                reportInstance.LoadReportDefinition(reportFile);
            }
        }
        #endregion
        #region Properties
        /// <summary>
        /// Gets the report instance.
        /// </summary>
        /// <value>The report instance.</value>
        public LocalReport ReportInstance
        {
            get
            {
                return reportInstance;
            }
        }
        #endregion
        #region Public Methods
        /// <summary>
        /// Renders the current ReportInstance to the user's browser as a PDF download.
        /// </summary>
        /// <param name="pdfDeviceInfoSettings">The PDF device info settings (see http://msdn2.microsoft.com/en-us/library/ms154682.aspx).</param>
        /// <returns></returns>
        public Warning[] RenderToBrowserPDF(string pdfDeviceInfoSettings)
        {
            CreateStreamCallback callback = new Microsoft.Reporting.WebForms.CreateStreamCallback(CreateWebBrowserStream);
            Warning[] warnings;
            HttpContext.Current.Response.ContentType = "application/octet-stream";
            HttpContext.Current.Response.AddHeader("Content-Disposition", "attachment; filename=\"" + downloadFileName + "\"");
            reportInstance.Render("PDF", null, callback, out warnings);
            return warnings;
        }
        /// <summary>
        /// Renders the current ReportInstance to the user's browser as a PDF download.
        /// </summary>
        /// <returns></returns>
        public Warning[] RenderToBrowserPDF()
        {
            return RenderToBrowserPDF(null);
        }
 
        /// <summary>
        /// Renders the current ReportInstance to an email with a file attachment containing the report.
        /// </summary>
        /// <param name="pdfDeviceInfoSettings">The PDF device info settings (see http://msdn2.microsoft.com/en-us/library/ms154682.aspx).</param>
        /// <param name="toAddress">To address.</param>
        /// <param name="fromAddress">From address.</param>
        /// <param name="subject">The subject.</param>
        /// <param name="body">The body.</param>
        /// <returns></returns>
        public Warning[] RenderToEmailPDF(string pdfDeviceInfoSettings, string toAddress, string fromAddress, string subject, string body)
        {
            CreateStreamCallback callback = CreateMemoryStream;
            Warning[] warnings;
            reportInstance.Render("PDF", pdfDeviceInfoSettings, callback, out warnings);
            reportMemoryStream.Seek(0, SeekOrigin.Begin);
 
            var client = new SmtpClient();
            using (var message = new MailMessage(fromAddress, toAddress, subject, body))
            using (var attachment = new Attachment(reportMemoryStream, reportMimeType))
            {
                attachment.Name = downloadFileName;
                message.Attachments.Add(attachment);
                client.Send(message);
            }
            return warnings;
        }
 
 
        public byte[] RenderToBytesPDF()
        {
            string mimeType;
            string encoding;
            string fileNameExtension;
            string[] streams;
            Warning[] warnings;
            return reportInstance.Render("PDF", null, out mimeType, out encoding, out fileNameExtension, out streams,
                                         out warnings);
        }
 
        public byte[] RenderToBytesExcel()
        {
            string mimeType;
            string encoding;
            string fileNameExtension;
            string[] streams;
            Warning[] warnings;
            return reportInstance.Render("EXCEL", null, out mimeType, out encoding, out fileNameExtension, out streams,
                                         out warnings);
        }
 
        /// <summary>
        /// Renders the current ReportInstance to an email with a file attachment containing the report.
        /// </summary>        
        /// <param name="toAddress">To address.</param>
        /// <param name="fromAddress">From address.</param>
        /// <param name="subject">The subject.</param>
        /// <param name="body">The body.</param>
        /// <returns></returns>
        public Warning[] RenderToEmailPDF(string toAddress, string fromAddress, string subject, string body)
        {
            return RenderToEmailPDF(null, toAddress, fromAddress, subject, body);
        }
        #endregion
        #region Private Methods
        /// <summary>
        /// Formats the current response output and returns the output stream suitable for rendering to the browser as a file download.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="extension">The extension.</param>
        /// <param name="encoding">The encoding.</param>
        /// <param name="mimeType">Type of the MIME.</param>
        /// <param name="willSeek">if set to <c>true</c> [will seek].</param>
        /// <returns></returns>
        private Stream CreateWebBrowserStream(string name, string extension, System.Text.Encoding encoding, string mimeType, bool willSeek)
        {
 
            return HttpContext.Current.Response.OutputStream;
        }
 
        /// <summary>
        /// Creates a memory stream that can be used by the report when rendering to an email attachment.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="extension">The extension.</param>
        /// <param name="encoding">The encoding.</param>
        /// <param name="mimeType">Type of the MIME.</param>
        /// <param name="willSeek">if set to <c>true</c> [will seek].</param>
        /// <returns></returns>
        private Stream CreateMemoryStream(string name, string extension, System.Text.Encoding encoding, string mimeType, bool willSeek)
        {
            reportMemoryStream = new MemoryStream();
            reportMimeType = mimeType;
            return reportMemoryStream;
        }
        #endregion
        #region IDisposable Members
 
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            if (this.reportInstance != null) this.reportInstance.Dispose();
            if (this.reportMemoryStream != null) this.reportMemoryStream.Dispose();
        }
 
        #endregion
 
       
    }   
 
 
}

Using WebReportRenderer from ASP.NET MVC is simple:

byte[] result;
using (var renderer = new WebReportRenderer(@"~\Report.rdlc", "Report.pdf"))
{
       var adapter = new ReportTableAdapter();                
       renderer.ReportInstance.DataSources.Add(new ReportDataSource("ReportDataSet_Report", adapter.GetData()));
       ReportParameter p0 = new ReportParameter("p0", someValue);
       renderer.ReportInstance.SetParameters(new[] {p0});
       result = renderer.RenderToBytesPDF();
}
return File(result,"application/pdf", "Report.pdf");
 

This isn't something you'd use for high volume reports, or if you need advanced delivery or scheduling capabilities.  But for one-off reports that only a few people will ever use, it works pretty well.

Posted by Daniel Root

Tuesday, May 05, 2009

We've been cranking out a couple of ASP.NET MVC applications for a client in the past few weeks.   I really feel the framework has helped us achieve greater separation of concerns and testability in these line-of-business type apps.  I recently went back to a Web Forms app, and was interested that my MVC work had really helped inform that as well.  I found myself taking better care of my rendered HTML and separating logic much better.  It's been fun to work with, but there was a little learning curve, so below are a few rules of thumb I picked up along the way.  As the title suggests, these are not rules per-se, but things that can tend to help in ASP.NET MVC development.

#1 - Favor smaller controllers over larger ones  - Our first controller attempt lumped several concepts into one class and wound up with about 20 or so action methods on one class- all only tangentially related.  This made the default routes wordy and non-intuitive, and the "one big controller" difficult to debug and read.  In future projects, and when refactoring theses, we broke the app into more narrowly-scoped controllers.  For example prefer an "EmployeeVacationController" over a catch-all "EmployeeController" with action methods for Vacation and other Employee "stuff".  This rule would really apply to most custom-written classes- Keep It Simple and limit classes to a Single Responsibility!

#2 - Consider using ViewModel classes - Most MVC examples show directly using a model class, such as a LINQ-to-SQL or Entity Framework class.  The Visual Studio wiring for MVC even steers you into this concept with it's default "Add View" code-generation, which lets you quickly gen up views based on a single model class.  However, in real-world-apps you often need more than just a single table's data to build out a page.  Some examples get around this by stuffing secondary data into ViewData, but a better way to do this is to create a "roll-up" class to contain properties for _everything_ your view will need.  This has the added benefits of being more strongly-typed, supporting intellisense, being testable, and defining exactly what a view needs.  Here's an example:

public class TerminalIndexViewModel
{
    public Terminal TerminalInfo { get; set; }
 
    public IEnumerable<FuelUsage> LatestUsage { get; set; }
    public IEnumerable<FuelDelivery> LatestDeliveries { get; set; }
    public InventorySummary CurrentInventorySummary { get; set; }
}

This does mean the default code-gen isn't as useful- for example, it wouldn't gen up anything useful for an action that returned the above class.  You can get around this by creating your own code-gen in T4, or temporarily using the "main" model type (ie 'TerminalInfo') to generate the page and tweaking to to work with your ViewModel class.  This is an emerging pattern for Silverlight and WPF as well.  More on ViewModels.

#3 - Consider separating application logic into classes separate from the controller.  As an earlier post of mine demonstrates, it's easy to begin thinking of controllers as just business logic classes.  But they're not.  They are classes whose purpose is to coordinate sending and receiving model data to/from the views to business logic.  For all but the most simple forms-on-data apps, this means the controller should call out to another class to do things like query a data layer or perform calculations.  In these apps, we've had luck using a Visitor pattern.  The controller creates the visitor instance and has it "visit" the model to perform some complex business-specific calculations.  These visitors are extremely testable, since they are not at all concerned with data access or UI wiring.

#4 - Set up client-side 'conventions' using JQuery.  Instead of wiring up individual page elements in each view, establish some 'conventions' for your app's client-side behavior using a little jQuery in script referenced from your site.master.  For example this snippet will set up rules for how we want textboxes, dates, delete buttons, and messageboxes to behave accross the entire app:

$(document).ready(function() {
          $(".initialfocus").focus();
          $(".shortdate").datepicker();
          $(".longdatetime").datepicker();
          $(".print").click(function() { window.print(); });
          $(".delete").click(function() { return confirm("This record will be deleted.  Are you sure you want to continue? Click 'Ok' to delete this record or 'Cancel' to stay on this page."); });
          $(".messagebox")
              .animate({ opacity: 1.0 }, 5000)
              .fadeOut("slow"); 
      });

With the above in site.master, setting the initial focus in any view is just a matter of setting the css tag:

<input type="textbox" class="initialfocus"/>

  I blogged about this previously, and it's made the app's client-side behavior very consistent.

Posted by Daniel Root

Tuesday, March 24, 2009

image Ages ago, NDoc- a jDoc clone- was the best free .NET documentation-generating application in town.  For various reasons, that project shut down, and Microsoft released it's internal documenting system, Sandcastle, to the public.  However Sandcastle by itself is non-trivial to understand and implement.  It's designed to gen up the entire MSDN documentation site, so it can be overwhelming for creating docs for your small line-of-business app.

Enter Sandcastle Help File Builder.  This gem brings back the simplicity and open source spirit of NDoc, and adds some functionality from Sandcastle to provide a great documenting solution for small or large projects. I'd checked it out in earlier versions, but it's come a long way. It even has a console app, which can easily be integrated into your continuous integration process.   In addition, you can add documents other than the typical xml-doc and build a true compiled help that a developer may actually use.

Here's how we set ours up:

  • A solution has a _FileReferences project for file-based dll references and other non-compiled code.  (Build is false for all Build configurations).  This project contains:
    • a Help folder that  contains .htm files and the compiled .chm file
    • a .shfbproj file that manages the documentation project.
  • In the .shfbproj:
    • A site map has been added to build the table of contents.  It has nodes pointing to each .htm file.
    • The solutions' projects have been added as Documentation Sources.
  • An automated process downloads the solution from source control, checks out the .shfbproj and .chm, runs the console app to generate a new .chm, and checks everything back in.

This way, the documentation is constantly updated and available in source control alongside the project!

Posted by Daniel Root

Friday, March 20, 2009

I came across this run-time error message while trying to run a web app project from Visual Studio 2008:

"Failed to generate a user instance of SQL Server due to a failure in starting the process for the user instance. The connection will be closed."

The app has a connectionString pointing to a SQL Server 2005 Express instance, so I checked to make sure the instance service was running and it was.  As any good computer shade tree mechanic would do, I restarted the service hoping for the best, but nothing changed.

So as always, Google to the rescue.  Joe Stagner in this blog entry describes what he did to fix the problem when he experienced it.  I deleted the corresponding folder under username and it worked!  If I had more time I'd try to dig some more for an explanation on why this happens and why the fix works, but we'll leave that exercise for another day.  For now I'm just glad it works.

Standard disclaimer: make sure you have data backed up before you go deleting things willy-nilly.  This fix worked for Joe Stagner and it worked for me, but there's no guarantee it won't fubar your SQL Server Express instance.

Update: Depending on your version of Windows, this folder may not be under 'Documents and Settings', but rather: C:\Users\USERNAME\AppData\Local\Microsoft\Microsoft SQL Server Data\SQLEXPRESS  Again, Your Mileage May Vary.

Posted by Daniel Root

Wednesday, March 18, 2009

Mix09 is happening this week.  This is one of Microsoft's larger developers conferences and sure to drop a few new tools, features, and plans.  Below are some cool new technologies I'm watching.  This post will be updated as I hear about interesting news, so check back from time to time.

  • Of course, as already mentioned, MVC v1 is now live!
  • Expression Web SuperPreview - effortlessly test browser compatibility in Expression Web.  I hope we get this for Visual Studio soon!
  • Silverlight 3 Beta 1 - Hardware GPU acceleration. New codecs, including AAC. Write your own codecs. IIS Media Services - static and LIVE streaming video support easily installed from Web Platform Installer (see below).  3D perspective support for any control.  Improved fonts.  Multitouch support. Support for running outside-of-browser on Windows and Mac in secure sandbox, with offline-awareness and automatic updates.  New Eclipse IDE tools for developing on Mac.  The download size - including all these new features - is actually smaller than Silverlight 2 (4.4M)!  Beta today, shipping RTM later this year.
  • Expression Blend 3 - design Silverlight 3 UIs.  Supports the new features, and has "SketchFlow" for sketching prototype page flows, transitions, and layouts, then generating documentation and actual Silverlight code.  Very cool, and also something VS needs!
  • Web Platform Installer and Web App Gallery - Make it easier to install framework and related MS features, plus popular applications that run in IIS.  They just called it "an App Store for the Web Server". Some apps included: DotNetNuke, Umbraco.
  • Azure commercial release this year.  Update this week supports PHP & CGI, has ADO.NET support that works with ADO EF, NHibernate, etc. similar to your traditional MS SQL apps.   It also supports geo-locating your applications and storage to enable edge content delivery, geo-load balancing, and other scenarios.  No pricing released yet. New SDK and Visual Studio Tools CTP available.
  • New Virtual Earth Silverlight SDK.
Posted by Daniel Root

I'm sure this will be old news in about an hour, but MVC is now live! Download here.  LifeCycle was an early adopter on this - our first production MVC app was deployed for testing at the client last week. The result was a clean separation of concerns and a light, clean standards-compliant web UI, with just enough jQuery goodness. 

Posted by Daniel Root

Friday, March 06, 2009

Need to generate create scripts for your SQL Express database?  Here's a little batch I've used several times recently to do just that:

@echo off
SET DatabaseFileName=DbName
 
ECHO Generating code for %DatabaseFileName%
ECHO Generating create script 'CreateDatabaseSchema.sql'
 
"C:\Program Files\Microsoft SQL Server\90\Tools\Publishing\SqlPubWiz.exe" script -C "Data Source=.\SQLEXPRESS;AttachDbFilename=%CD%\%DatabaseFileName%.mdf;Integrated Security=True;User Instance=True"  -noschemaqualify -schemaonly -nodropexisting -f CreateDatabaseSchema.sql

To use, just drop in a .bat next to the .mdf, change DbName and run.  It will output 'CreateDatabaseSchema.sql containing create script for all of the tables and stored procs in the database. 

Posted by Daniel Root

Thursday, February 26, 2009

I've recently started playing with NDepend again.  This tool is a sort of data mining utility for your code.  Feed it your assemblies and it can tell you almost anything about them.  Using a custom query language with a SQL-like syntax, you can dig into all sorts of metrics and useful information. Out of the box, it ships with tons of useful queries for common code problems. 

I ran some of our code through it and got dinged on some of the code complexity queries:

 image

In this case, I had several huge methods that needed to be broken up and optimized.  Thanks to NDepend's prodding, I spent some time refactoring using ReSharper, and whittled those down to 0s:

image

That's helpful, but not nearly everything that NDepend can do.  For example, the code I'm testing with is involved in some multithreading, so with a little digging through the docs, I came up with this query to show all methods that change state (besides property and event setters), but don't use any locks:

WARN IF Count < 0 IN SELECT METHODS WHERE
!IsDirectlyUsing "System.Threading.Monitor" AND ( ChangesObjectState OR ChangesTypeState ) AND
!IsConstructor AND !IsClassConstructor AND !IsPropertySetter AND !IsEventAdder AND !IsEventRemover

This doesn't guarantee thread-safe code, but it definitely helps drill down to potential problem areas.

Another area I can see us using this in is to provide metrics for our customers.  Generally they are not concerned with things like Cyclomatic Complexity, but some simple numbers may be useful.  We use FinalBuilder, so it's possible we could run NDepend's console utility to roll the numbers into some XML and publish to their customer wiki.  For example, here's a quick query to spit out the total number of tests for a project:

SELECT METHODS FROM ASSEMBLIES "TestAssemblyName" WHERE HasAttribute "NUnit.Framework.TestAttribute"

Once we get a customer set up with these metrics, we'll post more about how to automate that.  Until then, go check out NDepend!

Posted by Daniel Root

© 2006 LifeCycle Solutions, LLC | All Rights Reserved