Monday, June 29, 2009

An iPhone Sketch a Day - Week 5

This post is part of an ongoing series about my daily sketches with a great little iPhone app called Brushes. You can watch my progress over on my Flickr Photo Set.

I've been back on schedule since my last post, the only exception being that the last two posts to Flickr are an as-yet unfinished piece.  This may seem like a stretch, but it's a more involved portrait and I think it still passes as a 'sketch a day'. Anyway I hope to wrap that up by Tuesday.  This week I tried to vary things up a little.  I finally braved the heat and mosquitos for a landscape out at the rez, and stood outside for a quick live sketch from the garden.  Still, I have to say that I just enjoy sitting down and zoning in with some random item the most, and think that ends up with the best product.  Anyway, below are the sketches from this week.  Enjoy!

I have a new idea I'll be starting soon, so check back soon.

image image  image
image image image
image  image image

How To Do Two Things At Once (In C#)

Right this second I'm watching a blogging video by Tim Ferriss and typing this up.   I can't say I'm writing well, or paying much attention to the video - humans tend to do better when focusing on one thing at a time.  Computers on the other hand, can do pretty well doing more than one thing at once, especially when they have more than one processor.  The term for 'doing more than one thing at a time' in code is multithreading, and can be a quite complex to tackle correctly.  But sometimes you just want to do a few things at the same time and come back when they are all complete. .NET helps greatly by providing high level classes that simplify working with multithreading.  In my case, I needed to make a SalesForce webservice call and update a database at the same time.  To do this, I used .NET's Action type:

Action<ContactFormData> beginSendToSalesForce = SendToSalesForce;
var asyncResult = beginSendToSalesForce.BeginInvoke(formData, null, null);
 
using (var context = new ContactFormDataContext())
{
   context.ContactFormDatas.InsertOnSubmit(formData);
   context.SubmitChanges();
}
SetContactFormCookie(formData);
if (this.HasCurrentResourceUrl) LogPageRequest(formData.ContactFormId);
beginSendToSalesForce.EndInvoke(asyncResult);

This works by first creating an Action instance, which you can think of as an object that represents a method call.  Then 'BeginInvoke' is called, kicking off the webservice call, but letting the method continue.  The database is updated, some other stuff done, and then finally we return to the Action instance, calling EndInvoke.  At this point, the app will wait for the webservice call to finish and re-throw any exception that occurs. 

It is important to note that the typical multithreading rules still apply- watch and try to avoid shared state, and if you're doing this in a windows app, don't update any UI elements from the asynchronous method.  But especially for cases where you need to do two only slightly related actions at once, this pattern works well!

Wednesday, June 24, 2009

Quick Mail Debug Page

I recently helped a client debug some mail issues in an ASP.NET application.  These issues can be particularly frustrating because there are many different points of failure ranging from users' mail client, spam filters, SMTP setup, and more.  In this particular case, no errors occurred in the application- smtpClient.Send exited successfully.  However mail was not being received.  Somewhere between SmtpClient and the users' Outlook, the mail was disappearing! After trying several different settings, I finally wrote this quick mail test page:

<%@ Page Language="C#" AutoEventWireup="true" %>
<%@ Import Namespace="System.Net.Mail"%>
<%@ Import Namespace="System.Net"%>
<script runat="server">
    protected void cmdSend1_Click(object sender, EventArgs e)
    {
        var client = new SmtpClient();
        client.Send(this.uiFrom1.Text, this.uiTo1.Text, "Test - " + DateTime.Now.ToString("g"), "This is a test from the MailTest.aspx form.");
    }
 
    protected void cmdSend2_Click(object sender, EventArgs e)
    {
        var client = new SmtpClient(this.uiServer.Text);
        client.Credentials = new NetworkCredential(this.uiUser.Text, this.uiPassword.Text);
        client.Send(this.uiFrom2.Text, this.uiTo2.Text, "Test - " + DateTime.Now.ToString("g"), "This is a test from the MailTest.aspx form.");
 
    }
    </script>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    
        <b>Send a Simple Test</b><br />
        Tests sending a simple message using the default settings from web.config<br />
        To Address<br />
        <asp:TextBox ID="uiTo1" runat="server"></asp:TextBox>
        <br />
        From Address<br />
        <asp:TextBox ID="uiFrom1" runat="server"></asp:TextBox>
        <br />
        <asp:Button ID="cmdSend1" runat="server" onclick="cmdSend1_Click" Text="Send" />
        <br />
        <br />
        <b>Test Mail Settings</b><br />
        Tests sending a message using settings other than the ones in web.config<br />
        To Address<br />
        <asp:TextBox ID="uiTo2" runat="server"></asp:TextBox>
        <br />
        From Address<br />
        <asp:TextBox ID="uiFrom2" runat="server"></asp:TextBox>
        <br />
        Server<br />
        <asp:TextBox ID="uiServer" runat="server"></asp:TextBox>
        <br />
        Username<br />
        <asp:TextBox ID="uiUser" runat="server"></asp:TextBox>
        <br />
        Password<br />
        <asp:TextBox ID="uiPassword" runat="server"></asp:TextBox>
        <br />
        <br />
        <asp:Button ID="cmdSend2" runat="server" onclick="cmdSend2_Click" Text="Send" />
        <br />
    
    </div>
    </form>
</body>
</html>


This let me quickly test both the configured mail settings, and other settings.  After a few tries, I discovered that the 'from' address being used was no longer valid (for reasons I still don't completely understand).  A little configuration change, and the app worked fine!

To use, simply copy the code above into a file named MailTest.aspx in the problem app.  Browse to it and it should display a form that lets you test the various mail settings. Depending on your app, this is much quicker than changing settings in web.config and running through your app over and over.

Tuesday, June 23, 2009

My Unit Testing Naming Conventions

There is plenty of information on unit testing and unit test patterns, and I am by no means an expert.  But as I've started doing more tests, I've started using some naming conventions I find useful.  Here's how my unit tests generally look:

[TestFixture]
public class NnnTests
{
    [Test]
    public void Should_Do_XYZ_If_ABC()
    {
        var target = new SomethingBeingTested();
        var actual = target.DoSomething();
        Assert.AreEqual(expected, actual);
    }
}

 

This illustrates a few conventions:

  • Name the test fixture SomethingTests, where Something is the main target class being tested.
  • Name each method in plain English with underscores, adding qualifiers if needed.  This is to avoid ambiguity if a word is legitimately camel-cased. For example in SomeMethodShouldReturnExpectedValue, it's not immediately clear that the method's name is "SomeMethod".  In SomeMethod_Should_Return_Expected_Value, this is not an issue.
  • Setup the test by setting a local variable named 'target'.
  • Get a result by calling a method on the target and setting a local variable named actual.
  • Assert one thing about the result.  This doesn't necessarily mean only one call to Assert.  Just that you look at the value of one narrowly scoped test.

I find that by following these, it keeps tests focused on a single test case and helps make them very readable.

Thursday, June 18, 2009

An iPhone Sketch a Day - Week 3 & 4

This post is part of an ongoing series about my daily sketches with a great little iPhone app called Brushes. You can watch my progress over on my Flickr Photo Set.

Well, my spotless painting streak ended at 2 and a half weeks.  With a busy long weekend of 12 o'clock bedtimes, followed by a cold, I managed to miss several paintings.  Still, I've done as much as I could and am hopefully getting back on track now.  A few friends have asked how exactly this works, and what better way to show than a video!

 

 

The video doesn't show all the zooming in/out and color choices, so it's obviously sped up a bit, but you can see the strokes.  I've started to recall a little bit of technique- things like working background to foreground and coming back in to edges, and so on.  I'm still trying to push myself to loosen up a  bit and go faster, but sometimes it's just too much fun to get lost in the little details of a peach or cherry.

image image image
image image image

Tuesday, June 9, 2009

An iPhone Sketch a Day - Week 2

This post is part of an ongoing series about my daily sketches with a great little iPhone app called Brushes.

Honestly, when I last blogged about my iPhone Sketch-a-Day, I secretly wondered if I would be able to keep up the pace.  I've been getting a baby room ready and generally finding little free time.  But somehow I've managed to squeeze in one a day since Monday, bringing my body of work to a whopping 15 sketches!  Moreover, I really think I'm learning stuff with this little experiment.  I feel my rusty eye-hand connection slowly coming back, and thinking about composition and subject matter more.  Color remains my favorite part of this app- I'll hold the phone up near my subject and slowly rock my finger back and forth to find exactly the right color.  I feel I'm gaining an eye for color that will hopefully transfer back to paint once I pick that back up.  There's plenty of room for improvement - I'd really like to speed things up and let go; get out of the highly detailed renderings and into a more painterly style.  But things seem to fall apart when I do (see 'Flowers', 'Lamppost').

I've also started posting to and commenting in the Brushes Gallery Flickr group.  I've subscribed in my Google Reader and have enjoyed several of the artists' work. 

At any rate, below are the sketches for this week.  If you want to see bigger images, comment/critique, or subscribe, check out my Flickr set!

Enjoy!

image image image
image image image
image image

Monday, June 8, 2009

How To Render Reporting Services Reports From ASP.NET MVC

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.