October 2007 - Posts

Exporting a SQL Server Reporting Services 2005 (SSRS) Report Directly to PDF/Excel is a handy way of generating high quality reports without being stuck to using the ReportViewer interface. Sometimes the ReportViewer interface is an unnecessary step, but other times the ReportViewer won't render correctly even though the underlying report is correct. This is especially true when your audience might use Firefox or Safari (or anything other than IE), since the ReportViewer control almost never outputs a readable report. Of course it would be nice to just have a button on your page that generates a PDF or Excel file in any browser, and uses a SSRS back-end to do all of the report creating and heavy lifting.

The following code will show how to export such a report, including the passing of an arbitrary number of custom parameters. Note that the identity of the application pool that your website runs under will need to have at least "browser" access to the folder containing the report you want to display. This is usually pretty simple if both the IIS and SSRS server are within the same domain, but it might be tricky if this is not the case.

Microsoft.Reporting.WebForms.ReportViewer rview = new Microsoft.Reporting.WebForms.ReportViewer();
//Web Address of your report server (ex: http://rserver/reportserver)
 
rview.ServerReport.ReportServerUrl = new Uri(WebConfigurationManager.AppSettings[”ReportServer”]);
 
System.Collections.Generic.List<Microsoft.Reporting.WebForms.ReportParameter> paramList = new System.Collections.Generic.List<Microsoft.Reporting.WebForms.ReportParameter>();
 
paramList.Add(new Microsoft.Reporting.WebForms.ReportParameter(”Param1″, “Value1″));
paramList.Add(new Microsoft.Reporting.WebForms.ReportParameter(”Param2″, “Value2″));
 
rview.ServerReport.ReportPath = “/ReportFolder/ReportName”;
rview.ServerReport.SetParameters(paramList);
 
string mimeType, encoding, extension, deviceInfo;
string[] streamids;
Microsoft.Reporting.WebForms.Warning[] warnings;
string format = “PDF”; //Desired format goes here (PDF, Excel, or Image)
 
deviceInfo =
“<DeviceInfo>” +
“<SimplePageHeaders>True</SimplePageHeaders>” +
“</DeviceInfo>”;
 
byte[] bytes = rview.ServerReport.Render(format, deviceInfo, out mimeType, out encoding, out extension, out streamids, out warnings);
 
Response.Clear();
 
if (format == “PDF”)
{
Response.ContentType = “application/pdf”;
Response.AddHeader(”Content-disposition”, “filename=output.pdf”);
}
else if (format == “Excel”)
{
Response.ContentType = “application/excel”;
Response.AddHeader(”Content-disposition”, “filename=output.xls”);
}
 
Response.OutputStream.Write(bytes, 0, bytes.Length);
Response.OutputStream.Flush();
Response.OutputStream.Close();
Response.Flush();
Response.Close();

One potential issue that you might run into upon deploying your project is that your application server may not have the ReportViewer DLLs that are needed. You have two options in this case. The first is to copy the three Microsoft.ReportViewer.*.dll's (ReportViewer.Common.dll, ReportViewer.ProcessingObjectModel.dll, and ReportViewer.WebForms.dll) from your development computer into the BIN folder of your application server (or into the GAC). The second option (though I have not verified it), is to install the SSRS redistributable on the application server (http://www.microsoft.com/downloads/details.aspx?familyid=8a166cac-758d-45c8-b637-dd7726e61367&displaylang=en).

Check out http://www.microsoft.com/sql/technologies/reporting/default.mspx for good SSRS resources, including some nice learning tools and report packs.

When I was converting untyped data values from a SQL database into a custom Business class, I realized that I have to take a special precaution when casting possibly null values. If you try to cast DBNull to some type, you will get an InvalidCastException. When thinking about a solution, I remembered the as operator and decided to do some investigating. Consider the following example:

object objstr = DBNull.Value; 
string str1 = (string)objstr; //Cast throws an Exception 
string str2 = objstr as string; //No exception is thrown, and str2 == null

The as operator lends very well to using the ?? for writing compact code. Here's a little syntactic sugar:

if ( objstr == DBNull.Value )
{
 strResult = "Default";
}
else
{
 strResult = (string)objstr;
}
 
//Is equivalent to
 
strResult = objstr as string ?? "Default";

So it turns out that the as operator is just like the cast operator except that it yields null on conversion failure instead of throwing an exception. Looking at a MSDN C# Programmer's Reference page http://msdn2.microsoft.com/en-us/library/cscsdfbt(vs.71).aspx confirmed this, and even offered up the following explanation.

expression as type
 
//is equivalent to
 
expression is type ? (type)expression : (type)null

Creating a Custom Code Snippet is pretty easy to do -- you just fire up your favorite text editor, write some XML, and save the results in a *.snippet file. If you use Visual Studio as your text editor, you can even get some XML Intellisense, which is always nice. I am going to create a code snippet called rw.snippet, which will simply expand to Response.Write();, and leave your cursor inside the parenthesis (something I use far too often, along with Trace.Write(), for debugging).

To get started, create an empty text file in Notepad (or similar), and add the following lines to indicate that you want to write a custom code snippet.

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
</CodeSnippet>
</CodeSnippets>

Now save the file as rw.snippet (I would recommend saving it in your Visual Studio 2005\Code Snippets\[Language]\My Code Snippets folder) and close out of NotePad. Open the file in Visual Studio (note: since the file has the *.snippet extension, it should now be registered to open with Visual Studio by default). You should see the XML scheema information that you copied earlier, and you should get some next text highlighting as well.

Inside the <CodeSnippet> tag, you can add header information inside a <Header> tag, including a Title, Shortcut (the keyboard shortcut to be used), Description and more. You can use Intellisense to see all the possibilities. For now, I'll add the following information:

<Header>
<Title>rw</Title>
<Shortcut>rw</Shortcut>
<Description>Code snippet for generating Response.Write</Description>
<Author>Scott K</Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>

Next, you create the <Snippet> tag where the actual implementation will take place. Inside the snippet tag, you can define variables (literals/objects) to be filled in later (called Declarations) as well as the <Code> section containing your actual snippet content. The <Code> tag itself can define a few properties, like the declaration 'delimiter', 'kind' (to filter snippets based on code location), and 'language' (mainly helpful for organization) options. Inside the <Code> tag, you will create a <![CDATA[]]> section which will hold the snippet body as a plain string. The Snippet section for my Response.Write() snippet will look like the following:

<Snippet>
<Code Language="csharp">
<![CDATA[Response.Write($end$);]]>
</Code>
</Snippet>

Here, 'end' is a built-in declaration for the position the cursor will end up after expanding the snippet, and since we didn't change the delimiter is it defaulted to $. So the complete code for the rw.snippet file is:

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>rw</Title>
<Shortcut>rw</Shortcut>
<Description>Code snippet for Response.Write</Description>
<Author>Scott K</Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Code Language="csharp">
<![CDATA[Response.Write($end$);]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>

Once you have saved your file, inside Visual Studio click on Tools->Code Snippets Manager. Now choose the Import Option, navigate to your file save location and choose rw.snippet Choose where to put the snippet (I chose Visual C#) and click OK. Thats it! Now when you are typing in a normal coding environment, type rw and you will see the code snippet paper icon appear with rw in Intellisense. Press tab to expand out this snippet, and you will get Response.Write(); with your cursor ready for action inside the parenthesis.

Now the you have a working snippet, you may want to create a more complex snippet with custom declarations. All you have to do is add declarations to the <Snippet> tag and give them IDs, and then reference those IDs inside your CDATA[] block. For example, I will create a variable name declaration called var and then add it to the rw snippet I just created (disclaimer: this is just for demonstration purposes, obviously the following code would not be very helpful) which will print out a string. The declarations section would look like this:

<Declarations>
<Literal>
<ID>var</ID>
<ToolTip>Name for the string variable</ToolTip>
<Default>var</Default>
</Literal>
</Declarations>

And then the new CDATA[] section would look like this:

<![CDATA[string $var$ = "Hello World";
Response.Write($var$);
$end$]]>

When you expand that out as a code snippet and fill in the var name, you'll get a code block like this:

string helloString = "Hello World";
Response.Write(helloString);

That's really all there is to it -- for more advanced snippets you can look at the built in ones that Microsoft wrote, but this tutorial should give you all the tools you need to write some pretty advanced and time saving snippets.

There is a little used function of Visual Studio that will save you a lot of coding time: Code Snippets. Code Snippets are handy key shortcuts that expand into commonly used .NET constructs such as regions, constructor, loops (do/while/for/foreach), and try/catch blocks. To use a code snippet you can type in its 'shortcut' and in Intellisense you will see a little piece of paper next to the word indicating that it is indeed a code snippet. Then you press TAB twice, and the code snippet is expanded into the full construct, and depending on the snippet you may be directed to enter some pertinent information.

Let's try my favorite example: a property declaration with a backing field.

  1. Type in prop and then press TAB twice
  2. You will see an expanded property declaration with green fields, indicating that you need to supply this information.
  3. The first field to fill in will be highlighted in blue, which in this case is 'int'. Let's type 'string' to make a string property, but any .NET type will work.
  4. Now hit TAB again to go to the next field, which is the name of the private variable. Let's call it _stringProperty.
  5. Press TAB again to go to the public property name. Notice that it filled in the get/set values of the private variable for you. Now you can call the public string StringProperty, and press ENTER to indicate that you are finished (if you press TAB, you will cycle back through the customizable fields).

Now this saves me a lot of time since I can create properties rapidly instead of typing curly braces and get/set over and over. Other huge time savers include foreach/for, try, and cw (which does a Console.WriteLine()).

My development environment involves working on code that is not stored locally on my machine, but on a centralized development server on our network. I like working this way because it allows me to decouple my computer from IIS, and running the remote debugger (comes on the Visual Studio 2005 DVD) is really simple.

However, a problem arises when you try to run certain assemblies from a your remote development machine -- you get compilation errors telling you that an assembly can't be loaded because permission request problems. I first noticed this issue when I started using the ASP.NET AJAX (formerly Atlas) binary builds. You will get one of the following two error messages when you try to build your project:

1) Request for the permission of type 'System.Web.AspNetHostingPermission, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=xxxx' failed.

2) ASP.NET runtime error: Could not load file or assembly 'Microsoft.Practices.ObjectBuilder, Version=1.0.51205.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. Failed to grant minimum permission requests. [Note: The specific assembly information may differ depending on your project]

These errors occur because your remote development environment does not have enough permission to compile the ASP.NET AJAX (or other) DLLs on your system [For a nice explanation of Code Access Security, see http://www.15seconds.com/issue/040121.htm]. In order to give your remote development environment permission, you need to configure the Code Groups inside of the .NET Framework 2.0 Configuration MMC to allow your local intranet to be fully trusted by your machine. To do this, follow these steps on your development (local) computer:

  1. Go to Control Panel-->Administrative Tools on your computer.
  2. Click on .NET Framework 2.0 Configuration.
  3. Drill down to My Computer --> Runtime Security Policy --> Machine --> Code Groups --> All Code --> LocalIntranet Zone.
  4. Right click on LocalIntranet Zone, and choose Properties.
  5. Click on the Permission Set Tab and change the Permission Set to “Full Trust”.
  6. Hit Apply and OK and close the .NET 2.0 Framework Configuration window.
  7. Restart Visual Studio

Now you should be able to build, debug and run your project with your ASP.NET AJAX DLLs included in the BIN folder. Happy Coding!

PS: I have heard that adding the problem DLLs to your GAC (on your machine and the remote development machine) will fix the problem, but I have not verified this. I did not want to do this in my environment because we work with different versions of AJAX and other DLLs and I want to keep different versions by project.

If you use BLL objects with ObjectDataSources to populate your GridViews, DropDownLists, etc, the ObjectDataSource tries to instantiate your BLL object using a parameterless constructor. If you don't have a parameterless constructor (I usually don't) available, you'll get and error like:

No parameterless constructor defined for this object.

Description: An un-handled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. Exception Details: System.MissingMethodException: No parameterless constructor defined for this object.

The way to get around this problem is to handle the ObjectDataSource's "OnObjectCreating" property and instantiate the object with any necessary parameters, and then assign that object to the e.ObjectInstance property. A quick and easy sample is below:

protected void objBLLClass_ObjectCreating(object sender, ObjectDataSourceEventArgs e)

{

BLLClass bClass = new BLLClass(param1, param2);

e.ObjectInstance = bClass;

}

-- If you aren't using ObjectsDataSources yet, I highly recommend it for creating powerful, multi-tiered applications. There is a great intro-level series on 3-Tier Data Access at the ASP.NET main site. Once you understand the .NET 2.0 data model, there are many ways to customize the default objects to create really powerful applications! --

After coding a .NET 2.0 project in VS 2005, I built and ran the project successfully with no compiler errors or warnings. However, once i added a Web Deployment Project and tried to compile it, it gave me the error "aspnet_compiler.exe exited with code 1". After doing some searching, I was able to find (more or less) the problem by running the aspnet_compiler manually with the error stack. The steps I followed are summarized below:

   

  • Drop into the Visual Studion 2005 Command Prompt
  • Run the aspnet_compiler.exe with the errorstack flag on your project (where Z:\PROJECT is the location of your project)
    • For Example: prompt> aspnet_compiler.exe -errorstack -v /PROJECT -p Z:\PROJECT
  • Look for the problem in the output-- in my case it was [HttpParseException]: Unexpected end of file looking for </form> tag
  • Since it doesn't give you a specific line number, search through your ASPX files and try to find the error (I searched for <form>).

After fixing the problem, re-run the complier to ensure the problem is solved. After that, you will be able to compiler your Web Deployment Project.

More Posts