Raj Kaimal

Adding an expression based image in a client report definition file (RDLC)

In previous posts, I showed you how to create a report using Visual Studio 2010 and how to add a hyperlink to the report

In this post, I show you how to add an expression based image to each row of the report. This is similar to displaying a checkbox column for Boolean values.  A sample project is attached to the bottom of this post.

To start off, download the project we created earlier from here.  The report we created had a “Discontinued” column of type Boolean. We are going to change it to display an “available” icon or “unavailable” icon based on the “Discontinued” row value. 
 image
Load the project and double click on Products.rdlc. With the report design surface active, you will see the “Report Data” tool window. Right click on the Images folder and select “Add Image..”
image 
Add the available_icon.png and discontinued_icon.png images (the sample project at the end of this post has the icon png files)

image  
You can see the images we added in the “Report Data” tool window.
image 
Drag and drop the available_icon into the “Discontinued” column row (not the header)
image

We get a dialog box which allows us to set the image properties.
We will add an expression that specifies the image to display based the “Discontinued” value from the Product table. Click on the expression (fx) button.
 image

Add the following expression :
= IIf(Fields!Discontinued.Value = True, “discontinued_icon”, “available_icon”)
image 
Save and exit all dialog boxes.
In the report design surface, resize the column header and change the text from “Discontinued” to “In Production”.
image 

Change Display to “Original size”
(Optional) Right click on the image cell (not header) , go to “Image Properties..” and offset it by 5pt from the left. 
image

(Optional) Change the border color since it is not set by default for image columns.
image

We are done adding our image column!

Compile the application and run it. You will see that the “In Production” column has red ‘x’ icons for discontinued products.

image

Download the VS 2010 sample project


Other Posts

Posted by rajbk | 2 comment(s)

CascadingDropDown jQuery Plugin for ASP.NET MVC

Cascading Drop Down is a jQuery plug-in that can be used by a select list to get automatic population using AJAX. The plug-in and a sample ASP.NET MVC project are attached at the bottom of this post.

image 
Usage
The code below shows two select lists :

<select id="customerID" name="customerID">
  <option value="ALFKI">Maria Anders</option>
  <option value="ANATR">Ana Trujillo</option>
  <option value="ANTON">Antonio Moreno</option>
</select>
 
<select id="orderID" name="orderID">
</select>

The following code causes the second list to auto populate when a customer is selected in the first list.

$("#orderID").CascadingDropDown("#customerID", '/Sales/AsyncOrders');

Internally, an AJAX post is made to ‘/Sales/AsyncOrders’ with the post body containing  customerID=[selectedCustomerID]. This executes the action AsyncOrders on the SalesController with signature AsyncOrders(string customerID).  The AsyncOrders method returns JSON which is then used to populate the select list. The JSON format expected is shown below :

[{
    "Text": "John",
    "Value": "10326"
},
{
    "Text": "Jane",
    "Value": "10801"
}]


Details

$(targetID).CascadingDropDown(sourceID, actionPath, settings)

  • targetID
    The ID of the select list that will auto populate. 
  • sourceID
    The ID of the select list, which, on change, causes the targetID to auto populate.
  • actionPath
    The url to post to

Options

  • promptText
    Text for the first item in the select list
    Default : -- Select --
  • loadingText
    Optional text to display in the select list while it is being loaded.
    Default : Loading..
  • errorText
    Optional text to display if an error occurs while populating the list
    Default: Error loading data.
  • postData
    Data you want posted to the url in place of the default
    Example :

    postData: function () {
        return { prefix: $('#txtPrefix').val(), customerID: $('#customerID').val() };
    }
    will cause prefix=foo&customerID=bar to be sent as the POST body.
    Default: A text string obtained by calling serialize on the sourceID

  • onLoading (event)
    Raised before the list is populated.
  • onLoaded (event)
    Raised after the list is populated, The code below shows how to “animate” the  select list after load.

Example using custom options:

$("#orderID").CascadingDropDown("#customerID", '/Sales/AsyncOrders',
{
    promptText: '-- Pick an Order--',
    onLoading: function () {
        $(this).css("background-color", "#ff3");
    },
    onLoaded: function () {
        $(this).animate({ backgroundColor: '#ffffff' }, 300);
    }
});


To return JSON from our action method, we use the Json ActionResult passing in an IEnumerable<SelectListItem>.

public ActionResult AsyncOrders(string customerID)
{
    var orders = repository.GetOrders(customerID).ToList().Select(a => new SelectListItem()
    {
        Text = a.OrderDate.HasValue ? a.OrderDate.Value.ToString("MM/dd/yyyy") : "[ No Date ]",
        Value = a.OrderID.ToString(),
    });
    return Json(orders);
}


jQuery Plug-in
Now hosted on GitHub

Sample Project using VS 2010 RTM (updated 5/21/2010)


Comments and suggestions are welcome.

Other Posts

Posted by rajbk | 18 comment(s)
Filed under: , , , ,

Running a Silverlight application in the Google App Engine platform

This post shows you how to host a Silverlight application in the Google App Engine (GAE) platform. You deploy and host your Silverlight application on Google’s infrastructure by creating a configuration file and uploading it along with your application files.

I tested this by uploading an old demo of mine - the four stroke engine silverlight demo. It is currently being served by the GAE over here: http://fourstrokeengine.appspot.com/

The steps to run your Silverlight application in GAE are as follows:

Account Creation

Create an account at http://appengine.google.com/. You are allocated a free quota at signup.

Select “Create an Application”
image 
Verify your account by SMS
image 
Create your application by clicking on “Create an Application”
image 
Pick an application identifier on the next screen. The identifier has to be unique. You will use this identifier when uploading your application. The application you create will by default be accessible at [applicationidentifier].appspot.com. You can also use custom domains if needed (refer to the docs).

image 
Save your application.

Download SDK 

We will use the  Windows Launcher for Google App Engine tool to upload our apps (it is possible to do the same through command line). This is a GUI for creating, running and deploying applications. The launcher lets you test the app locally before deploying it to the GAE. This tool is available in the Google App Engine SDK.

The GUI is written in Python and therefore needs an installation of Python to run.
Download and install the Python Binaries from here: http://www.python.org/download/

Download and install the Google App Engine SDK from here: http://code.google.com/appengine/downloads.html

Run the GAE Launcher. Select Create New Application.
image 
On the next dialog, give your application a name (this must match the identifier we created earlier)
image
For Parent Directory, point to the directory containing your Silverlight files. Change the port if you want to. The port is used by the GAE local web server. The server is started if you choose to run the application locally for testing purposes. Hit Save.

Configure, Test and Upload

As shown below, the files I am interested in uploading for my Silverlight demo app are

  • The html page used to host the Silverlight control
  • The xap file containing the compiled Silverlight application
  • A favicon.ico file.

image 

We now create a configuration file for our application called app.yaml. The app.yaml file specifies how URL paths correspond to request handlers and static files.  We edit the file by selecting our app in the GUI and clicking “Edit”
image
The contents of file after editing is shown below (note that the contents of the file should be in plain text):

application: fourstrokeengine
version: 1
runtime: python
api_version: 1

handlers:
- url: /
  static_files: Default.html
  upload: Default.html
- url: /favicon.ico
  static_files: favicon.ico
  upload: favicon.ico
- url: /FourStrokeEngine.xap
  static_files: FourStrokeEngine.xap
  upload: FourStrokeEngine.xap
  mime_type: application/x-silverlight-app
- url: /.*
  static_files: Default.html
  upload: Default.html

We have listed URL patterns for our files, specified them as static files and specified a mime type for our xap file. The wild card URL at the end will match all URLs that are not found to our default page (you would normally include a html file that displays a 404 message).  To understand more about app.yaml, refer to this page. Save the file.

Run the application locally by selecting “Browse” in the GUI. A web server listening on the port you specified is started (8080 in my case). The app is loaded in your default web browser pointing to http://localhost:8080/. Make sure the application works as expected.

We are now ready to deploy. Click the “Deploy” icon. You will be prompted for your username and password. Hit OK. The files will get uploaded and you should get a dialog telling you to “close the window”.

We are done uploading our Silverlight application. Go to http://appengine.google.com/ and launch the application by clicking on the link in the “Current Version” column.

image 
You should be taken to a URL which points to your application running in Google’s infrastructure : http://fourstrokeengine.appspot.com/.

We are done deploying our application!

Clicking on the link in the Application column will take you to the Admin console where you can see stats related to system usage. 

To learn more about the Google Application Engine, go here: http://code.google.com/appengine/docs/whatisgoogleappengine.html

Posted by rajbk | 4 comment(s)
Filed under: , , ,

Pre-filtering and shaping OData feeds using WCF Data Services and the Entity Framework - Part 2

In the previous post, you saw how to create an OData feed and pre-filter the data. In this post, we will see how to shape the data. A sample project is attached at the bottom of this post.

Pre-filtering and shaping OData feeds using WCF Data Services and the Entity Framework - Part 1

Shaping the feed

The Product feed we created earlier returns too much information about our products.

image

Let’s change this so that only the following properties are returned – ProductID, ProductName, QuantityPerUnit, UnitPrice, UnitsInStock. We also want to return only Products that are not discontinued. 

Splitting the Entity
To shape our data according to the requirements above, we are going to split our Product Entity into two and expose one through the feed. The exposed entity will contain only the properties listed above. We will use the other Entity in our Query Interceptor to pre-filter the data so that discontinued products are not returned.

Go to the design surface for the Entity Model and make a copy of the Product entity. A “Product1” Entity gets created.
image 
Rename Product1 to ProductDetail.
Right click on the Product entity and select “Add Association”
image

Make a one to one association between Product and ProductDetails.

image 
Keep only the properties we wish to expose on the Product entity and delete all other properties on it (see diagram below). You delete a property on an Entity by right clicking on the property and selecting “delete”.

Keep the ProductID on the ProductDetail. Delete any other property on the ProductDetail entity that is already present in the Product entity. Your design surface should look like below:
image  
Mapping Entity to Database Tables
Right click on “ProductDetail” and go to “Table Mapping”
image 
Add a mapping to the “Products” table in the Mapping Details.
image 
After mapping ProductDetail, you should see the following.
image 

Add a referential constraint.
Lets add a referential constraint which is similar to a referential integrity constraint in SQL. Double click on the Association between the Entities and add the constraint with “Principal” set to “Product”.
image


Let us review what we did so far.

  • We made a copy of the Product entity and called it ProductDetail
  • We created a one to one association between these entities
  • Excluding the ProductID, we made sure properties were not duplicated between these entities 
  • We added a ProductDetail entity to Products table mapping (Entity to Database).
  • We added a referential constraint between the entities.

Lets build our project. We get the following error:

”'NortwindODataFeed.Product' does not contain a definition for 'Discontinued' and no extension method 'Discontinued' accepting a first argument of type 'NortwindODataFeed.Product' could be found …"

The reason for this error is because our Product Entity no longer has a “Discontinued” property. We “moved” it to the ProductDetail entity since we want our Product Entity to contain only properties that will be exposed by our feed. Since we have a one to one association between the entities, we can easily rewrite our Query Interceptor like so:

[QueryInterceptor("Products")]
public Expression<Func<Product, bool>> OnReadProducts()
{
    return o => o.ProductDetail.Discontinued == false;
}

Similarly, all “hidden” properties of the Product table are available to us internally (through the ProductDetail Entity) for any additional logic we wish to implement.

Compile the project and view the feed. We see that the feed returns only the properties that were part of the requirement.
image 

To see the data in JSON format, you have to create a request with the following request header (easy to do with jQuery)
Accept: application/json, text/javascript, */*

The response will look like this:

{
"d" : {
"results": [
{
"__metadata": {
"uri": "http://localhost.:2576/DataService.svc/Products(1)", "type": "NorthwindModel.Product"
}, "ProductID": 1, "ProductName": "Chai", "QuantityPerUnit": "10 boxes x 20 bags", "UnitPrice": "18.0000", "UnitsInStock": 39
}, {
"__metadata": {
"uri": "http://localhost.:2576/DataService.svc/Products(2)", "type": "NorthwindModel.Product"
}, "ProductID": 2, "ProductName": "Chang", "QuantityPerUnit": "24 - 12 oz bottles", "UnitPrice": "19.0000", "UnitsInStock": 17
}, {
...
...

If anyone has the $format operation working, please post a comment. It was not working for me at the time of writing this. 

We have successfully pre-filtered our data to expose only products that have not been discontinued and shaped our data so that only certain properties of the Entity are exposed. Note that there are several other ways you could implement this like creating a QueryView, Stored Procedure or DefiningQuery.

You have seen how easy it is to create an OData feed, shape the data and pre-filter it by hardly writing any code of your own.

For more details on OData, check out the blog of one of the most passionate Architects I have ever met, Pablo Castro – the Architect of Aristoria WCF Data Services.
In addition, watch his MIX 2010 presentation titled “OData: There's a Feed for That” here.


Download Sample Project for VS 2010 RTM.

Make sure you have the right Entity Connection String. Depending on how you have your server/user set up, you may have to include the Catalog name like so:

…\sqlexpress;Initial Catalog=Northwind;Integrated Security=True;MultipleActiveResultSets=True&quot;" …


Tip: To debug your feed, add config.UseVerboseErrors in the InitializeService method.
Pop Quiz: What happens when we add this config.SetEntitySetAccessRule("ProductDetails", EntitySetRights.AllRead); ?

Posted by rajbk | 6 comment(s)

Pre-filtering and shaping OData feeds using WCF Data Services and the Entity Framework - Part 1

The Open Data Protocol, referred to as OData, is a new data-sharing standard that breaks down silos and fosters an interoperative ecosystem for data consumers (clients) and producers (services) that is far more powerful than currently possible. It enables more applications to make sense of a broader set of data, and helps every data service and client add value to the whole ecosystem. WCF Data Services (previously known as ADO.NET Data Services), then, was the first Microsoft technology to support the Open Data Protocol in Visual Studio 2008 SP1. It provides developers with client libraries for .NET, Silverlight, AJAX, PHP and Java. Microsoft now also supports OData in SQL Server 2008 R2, Windows Azure Storage, Excel 2010 (through PowerPivot), and SharePoint 2010. Many other other applications in the works. *

This post walks you through how to create an OData feed, define a shape for the data and pre-filter the data using Visual Studio 2010, WCF Data Services and the Entity Framework. A sample project is attached at the bottom of Part 2 of this post.

Pre-filtering and shaping OData feeds using WCF Data Services and the Entity Framework - Part 2

Create the Web Application
File –› New –› Project, Select “ASP.NET Empty Web Application”

image

Add the Entity Data Model
Right click on the Web Application in the Solution Explorer and select “Add New Item..”
Select “ADO.NET Entity Data Model” under "Data”. Name the Model “Northwind” and click “Add”.

image 
In the “Choose Model Contents”, select “Generate Model From Database” and click “Next”

image 
Define a connection to your database containing the Northwind database in the next screen.

image

We are going to expose the Products table through our OData feed. Select “Products” in the “Choose your Database Object” screen.

 image

Click “Finish”. We are done creating our Entity Data Model. Save the Northwind.edmx file created.

Add the WCF Data Service
Right click on the Web Application in the Solution Explorer and select “Add New Item..”
Select “WCF Data Service” from the list and call the service “DataService” (creative, huh?). Click “Add”.
image 
Enable Access to the Data Service
Open the DataService.svc.cs class. The class is well commented and instructs us on the next steps.

public class DataService : DataService< /* TODO: put your data source class name here */ >
{
    // This method is called only once to initialize service-wide policies.
    public static void InitializeService(DataServiceConfiguration config)
    {
        // TODO: set rules to indicate which entity sets and service operations are visible, updatable, etc.
        // Examples:
        // config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead);
        // config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All);
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
    }
}

Replace the comment that starts with “/* TODO:” with “NorthwindEntities” (the entity container name of the Model we created earlier). 

WCF Data Services is initially locked down by default, FTW! No data is exposed without you explicitly setting it. You have explicitly specify which Entity sets you wish to expose and what rights are allowed by using the SetEntitySetAccessRule. The SetServiceOperationAccessRule on the other hand sets rules for a specified operation.

Let us define an access rule to expose the Products Entity we created earlier. We use the EnititySetRights.AllRead since we want to give read only access. Our modified code is shown below.

public class DataService : DataService<NorthwindEntities>
{
    public static void InitializeService(DataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule("Products", EntitySetRights.AllRead);
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
    }
}

We are done setting up our ODataFeed! Compile your project. Right click on DataService.svc and select “View in Browser” to see the OData feed.

To view the feed in IE, you must make sure that "Feed Reading View" is turned off. You set this under Tools -› Internet Options -› Content tab.

image 
If you navigate to “Products”, you should see the Products feed. Note also that URIs are case sensitive. ie. Products work but products doesn’t.

image 

Filtering our data
OData has a set of system query operations you can use to perform common operations against data exposed by the model. For example, to see only Products in CategoryID 2, we can use the following request:

/DataService.svc/Products?$filter=CategoryID eq 2

At the time of this writing, supported operations are $orderby, $top, $skip, $filter, $expand, $format†, $select, $inlinecount.

Pre-filtering our data using Query Interceptors
The Product feed currently returns all Products. We want to change that so that it contains only Products that have not been discontinued.

WCF introduces the concept of interceptors which allows us to inject custom validation/policy logic into the request/response pipeline of a WCF data service.

We will use a QueryInterceptor to pre-filter the data so that it returns only Products that are not discontinued. To create a QueryInterceptor, write a method that returns an Expression<Func<T, bool>> and mark it with the QueryInterceptor attribute as shown below.

[QueryInterceptor("Products")]
public Expression<Func<Product, bool>> OnReadProducts()
{
    return o => o.Discontinued == false;
}

Viewing the feed after compilation will only show products that have not been discontinued. We also confirm this by looking at the WHERE clause in the SQL generated by the entity framework.

SELECT 
[Extent1].[ProductID] AS [ProductID], 
...
... 
[Extent1].[Discontinued] AS [Discontinued]
FROM [dbo].[Products] AS [Extent1]
WHERE 0 = [Extent1].[Discontinued]

Other examples of Query/Change interceptors can be seen here including an example to filter data based on the identity of the authenticated user.

We are done pre-filtering our data. In the next part of this post, we will see how to shape our data.

Pre-filtering and shaping OData feeds using WCF Data Services and the Entity Framework - Part 2

Foot Notes

* http://msdn.microsoft.com/en-us/data/aa937697.aspx

† $format did not work for me. The way to get a Json response is to include the following in the  request header “Accept: application/json, text/javascript, */*” when making the request. This is easily done with most JavaScript libraries.

Posted by rajbk | 4 comment(s)

Adding a hyperlink in a client report definition file (RDLC)

This post shows you how to add a hyperlink to your RDLC report. In a previous post, I showed you how to create an RDLC report.

We have been given the requirement to the report we created earlier, the Northwind Product report, to add a column that will contain hyperlinks which are unique per row.  The URLs will be RESTful with the ProductID at the end.

Clicking on the URL will take them to a website like so: http://localhost/products/3  where 3 is the primary key of the product row clicked on.

To start off, open the RDLC and add a new column to the product table.

image 
Add text to the header (Details) and row (Product Website).

image
Right click on the row (not header) and select “TextBox properties”

image

Select Action – Go to URL. You could hard code a URL here but what we need is a URL that changes based on the ProductID.

image 
Click on the expression button (fx)

image
The expression builder gives you access to several functions and constants including the fields in your dataset. See this reference for more details: Common Expressions for ReportViewer Reports.

Add the following expression:

= "http://localhost/products/" & Fields!ProductID.Value

Click OK to exit the Expression Builder.

The report will not render because hyperlinks are disabled by default in the ReportViewer control. To enable it, add the following in your page load event (where rvProducts is the ID of your ReportViewerControl):

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        rvProducts.LocalReport.EnableHyperlinks = true;
    }
}

We want our links to open in a new window so set the HyperLinkTarget property of the ReportViewer control to “_blank”

image 
We are done adding hyperlinks to our report. Clicking on the links for each product pops open a new windows. The URL has the ProductID added at the end.

image

Enjoy!

Posted by rajbk | 5 comment(s)
Filed under: , , ,

Pages in IE render differently when served through the ASP.NET Development server and Production Server

You see differences in the way IE renders your web application locally on the ASP.NET Development server compared to your production server. Comparing the response from both servers including response headers and CSS show no difference.

The issue may occur because of a setting in IE. In IE, go to Tools –> Compatibility ViewSettings.
image

The checkbox “Display intranet sites in Compatibility View” turned on forces IE8 to display the web application content in a way similar to how Internet Explorer 7 handles standards mode web pages. Since your local web server is considered to be in the intranet zone, IE uses “Compatibility View” to render your pages.

image

While you could uncheck this setting in or propagate the change to all developers through group policy settings, a different way is described below.

To force IE to mimic the behavior of a certain version of IE when rendering the pages, you use the meta element  to include a “X-UA-Compatible” http-equiv header in  your web page or have it sent as part of the header by adding it to your web.config file. The values are listed below:

<meta http-equiv="X-UA-Compatible" content="IE=4">   <!-- IE5 mode -->
<meta http-equiv="X-UA-Compatible" content="IE=7.5"> <!-- IE7 mode -->
<meta http-equiv="X-UA-Compatible" content="IE=100"> <!-- IE8 mode -->
<meta http-equiv="X-UA-Compatible" content="IE=a">   <!-- IE5 mode --> 

This value can also be set in web.config like so:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
   <system.webServer>
      <httpProtocol>
         <customHeaders>
            <clear />
            <add name="X-UA-Compatible" value="IE=EmulateIE7" />
         </customHeaders>
      </httpProtocol>
   </system.webServer>
</configuration>


The setting can added in the IIS metabase as described here.

Similarly, you can do the same in Apache by adding the directive in httpd.conf

<Location /store>
   Header set X-UA-Compatible “IE=EmulateIE7”
</Location>

Even though it can be done on a site level, I recommend you do it on a per application level to avoid confusing the developer.

References
Defining Document Compatibility
Implementing the META Switch on IIS
Implementing the META Switch on Apache

Posted by rajbk | 7 comment(s)
Filed under: , ,

Running ASP.NET Webforms and ASP.NET MVC side by side

One of the nice things about ASP.NET MVC and its older brother ASP.NET WebForms is that they are both built on top of the ASP.NET runtime environment. The advantage of this is that, you can still run them side by side even though MVC and WebForms are different frameworks.

Another point to note is that with the release of the ASP.NET routing in .NET 3.5 SP1, we are able to create SEO friendly URLs that do not map to specific files on disk. The routing is part of the core runtime environment and therefore can be used by both WebForms and MVC.

To run both frameworks side by side, we could easily create a separate folder in your MVC project for all our WebForm files and be good to go. What this post shows you instead, is how to have an MVC application with WebForm pages  that both use a common master page and common routing for SEO friendly URLs. 

A sample project that shows WebForms and MVC running side by side is attached at the bottom of this post.

So why would we want to run WebForms and MVC in the same project? 

WebForms come with a lot of server controls that provide a lot of rich functionality. One example is the ReportViewer control. Using this control and client report definition files (RDLC), we can create rich interactive reports (with charting controls). I show you how to use the ReportViewer control in a WebForm project here :  Creating an ASP.NET report using Visual Studio 2010. We can create even more advanced reports by using SQL reporting services that can also be rendered by the ReportViewer control.

Moving along, consider the sample MVC application I blogged about titled : ASP.NET MVC Paging/Sorting/Filtering using the MVCContrib Grid and Pager. Assume you were given the requirement to add a UI to the MVC application where users could interact with a report and be given the option to export the report to Excel, PDF or Word. How do you go about doing it?  

This is a perfect scenario to use the ReportViewer control and RDLCs. As you saw in the post on creating the ASP.NET report, the ReportViewer control is a Web Control and is designed to be run in a WebForm project with dependencies on, amongst others, a ScriptManager control and the beloved Viewstate

image

Since MVC and WebForm both run under the same runtime, the easiest thing to is to add the WebForm application files (index.aspx, rdlc, related class files) into our MVC project. We will copy the files over from the WebForm project into the MVC project.

Create a new folder in our MVC application called CommonReports. Add the index.aspx and rdlc file from the Webform project

image 
Right click on the Index.aspx file and convert it to a web application. This will add the index.aspx.designer.cs file (this step is not required if you are manually adding a WebForm aspx file into the MVC project).
image  
Verify that all the type names for the ObjectDataSources in code behind to point to the correct ProductRepository and fix any compiler errors. Right click on Index.aspx and select “View in browser”. You should see a screen like the one below:

image 
There are two issues with our page. It does not use our site master page and the URL is not SEO friendly.

Common Master Page

The easiest way to use master pages with both MVC and WebForm pages is to have a common master page that each inherits from as shown below.

image

The reason for this is most WebForm controls require them to be inside a Form control and require ControlState or ViewState. ViewMasterPages used in MVC, on the other hand, are designed to be used with content pages that derive from ViewPage with Viewstate turned off. By having a separate master page for MVC and WebForm that inherit from the Root master page,, we can set properties that are specific to each. For example, in the Webform master, we can turn on ViewState, add a form tag etc.

Another point worth noting is that if you set a WebForm page to use a MVC site master page, you may run into errors like the following:

A ViewMasterPage can be used only with content pages that derive from ViewPage or ViewPage<TViewItem>

or

Control 'MainContent_MyButton' of type 'Button' must be placed inside a form tag with runat=server.

Since the ViewMasterPage inherits from MasterPage as seen below, we make our Root.master inherit from MasterPage, MVC.master inherit from ViewMasterPage and Webform.master inherits from MasterPage.
image
We define the attributes on the master pages like so:

  • Root.master
    <%@ Master Inherits="System.Web.UI.MasterPage"  … %>
  • MVC.master
    <%@ Master MasterPageFile="~/Views/Shared/Root.Master" Inherits="System.Web.Mvc.ViewMasterPage" … %>
  • WebForm.master
    <%@ Master MasterPageFile="~/Views/Shared/Root.Master" Inherits="NorthwindSales.Views.Shared.Webform" %>
    Code behind:
    public partial class Webform : System.Web.UI.MasterPage {}

We make changes to our reports aspx file to use the Webform.master. See the source of the master pages in the sample project for a better understanding of how they are connected.

SEO friendly links

We want to create SEO friendly links that point to our report. A request to /Reports/Products should render the report located in ~/CommonReports/Products.aspx. Simillarly to support future reports, a request to /Reports/Sales should render a report in ~/CommonReports/Sales.aspx.

Lets start by renaming our index.aspx file to Products.aspx to be consistent with our routing criteria above.

image

As mentioned earlier, since routing is part of the core runtime environment, we ca easily create a custom route for our reports by adding an entry in Global.asax.

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
    //Custom route for reports
    routes.MapPageRoute(
     "ReportRoute",                         // Route name
     "Reports/{reportname}",                // URL
     "~/CommonReports/{reportname}.aspx"   // File
     );
 
 
    routes.MapRoute(
        "Default",                          // Route name
        "{controller}/{action}/{id}",       // URL with parameters
        new
        {
            controller = "Home",
            action = "Index",
            id = UrlParameter.Optional
        }                                   // Parameter defaults
    );
}

With our custom route in place, a request to Reports/Employees will render the page at ~/CommonReports/Employees.aspx. We make this custom route the first entry since the routing system walks the table from top to bottom, and the first route to match wins. Note that it is highly recommended that you write unit tests for your routes to ensure that the mappings you defined are correct.

Common Menu Structure
The master page in our original MVC project had a menu structure like so:

<ul id="menu">
    <li>
        <%=Html.ActionLink("Home", "Index", "Home") %></li>
    <li>
        <%=Html.ActionLink("Products", "Index", "Products") %></li>
    <li>
        <%=Html.ActionLink("Help", "Help", "Home") %></li>
</ul>

We want this menu structure to be common to all pages/views and hence should reside in Root.master. Unfortunately the Html.ActionLink helpers will not work since Root.master inherits from MasterPage which does not have the helper methods available.

The quickest way to resolve this issue is to use RouteUrl expressions. Using  RouteUrl expressions, we can programmatically generate URLs that are based on route definitions. By specifying parameter values and a route name if required, we get back a URL string that corresponds to a matching route.
We move our menu structure to Root.master and change it to use RouteUrl expressions:

<ul id="menu">
    <li>
        <asp:HyperLink ID="hypHome" runat="server" NavigateUrl="<%$RouteUrl:routename=default,controller=home,action=index%>">Home</asp:HyperLink></li>
    <li>
        <asp:HyperLink ID="hypProducts" runat="server" NavigateUrl="<%$RouteUrl:routename=default,controller=products,action=index%>">Products</asp:HyperLink></li>
    <li>
        <asp:HyperLink ID="hypReport" runat="server" NavigateUrl="<%$RouteUrl:routename=ReportRoute,reportname=products%>">Product Report</asp:HyperLink></li>
    <li>
        <asp:HyperLink ID="hypHelp" runat="server" NavigateUrl="<%$RouteUrl:routename=default,controller=home,action=help%>">Help</asp:HyperLink></li>
</ul>

We are done adding the common navigation to our application.

The application now uses a common theme, routing and navigation structure.

Conclusion
We have seen how to do the following through this post

  • Add a WebForm page from a WebForm project to an existing ASP.NET MVC application
  • Use a common master page for both WebForm and MVC pages
  • Use routing for SEO friendly links
  • Use a common menu structure for both WebForm and MVC.

The sample project is attached below.
Version: VS 2010 RTM
Remember to change your connection string to point to your Northwind database

-

Posted by rajbk | 14 comment(s)
Filed under: , , ,

Creating an ASP.NET report using Visual Studio 2010 - Part 3

We continue building our report in this three part series.

Creating an ASP.NET report using Visual Studio 2010 - Part 1
Creating an ASP.NET report using Visual Studio 2010 - Part 2

Adding the ReportViewer control and filter drop downs.

Open the source code for index.aspx and add a ScriptManager control. This control is required for the ReportViewer control. Add a DropDownList for the categories and suppliers. Add the ReportViewer control. The markup after these steps is shown below.
<div>
    <asp:ScriptManager ID="smScriptManager" runat="server">
    </asp:ScriptManager>
    <div id="searchFilter">
        Filter by: Category :
        <asp:DropDownList ID="ddlCategories" runat="server" />
        and Supplier :
        <asp:DropDownList ID="ddlSuppliers" runat="server" />
    </div>
    <rsweb:ReportViewer ID="rvProducts" runat="server">
    </rsweb:ReportViewer>
</div>

The design view for index.aspx is shown below. The dropdowns will display the categories and suppliers in the database. The report will be filtered by the selections in the dropdowns. You will see how to do this in the next steps.
image 
Attaching the RDLC to the ReportViewer control by clicking on the top right of the control, going to Report Viewer tasks and selecting Products.rdlc.  
image
Resize the ReportViewer control by dragging at the bottom right corner. I set mine to 800px x 500px. You can set this value in source view also.

image

Defining the data sources.


We will now define the Data Source used to populate the report. Go back to the “ReportViewer Tasks” and select “Choose Data Sources”
image
Select a “New data source..”
image

Select “Object” and name your Data Source ID “odsProducts”

image 
In the next screen, choose “ProductRepository” as your business object.
image
Choose “GetProductsProjected” in the next screen.

image 
The method requires a SupplierID and CategoryID. We will have the data source use the selected values of the drop down lists we defined earlier. Set the parameter source to be of type “Control” and set the ControlIDs to be ddlSuppliers and ddlCategories respectively. Your screen will look like this:
image
We are now going to define the data source for our drop downs. Select the ddlCategory drop down and pick “Choose Data Source”.

image
Pick “Object” and give it an id “odsCategories”
image 
In the next screen, choose “ProductRepository”
image
Select the GetCategories() method in the next screen.
image 
Select “CategoryName” and “CategoryID” in the next screen. We are done defining the data source for the Category drop down.
image
Perform the same steps for the Suppliers drop down.

image
image
image 
Select each dropdown and set the AppendDataBoundItems to true and AutoPostback to true.
image   
The AppendDataBoundItems is needed because we are going to insert an “All“ list item with a value of empty. This will be the first item in each drop down list. Go to each drop down and add this list item markup as shown below.
image

Double click on each drop down in the designer and add the following code in the code behind. This along with the “Autopostback= true” attribute refreshes the report anytime the selection in the drop down is changed.

protected void ddlCategories_SelectedIndexChanged(object sender, EventArgs e)
{
    rvProducts.LocalReport.Refresh();
}
 
protected void ddlSuppliers_SelectedIndexChanged(object sender, EventArgs e)
{
    rvProducts.LocalReport.Refresh();
}

Compile your report and run the page. You should see the report rendered. Note that the tool bar in the ReportViewer control gives you a couple of options including the ability to export the data to Excel, PDF or word.

 image

Conclusion

Through this three part series, we did the following:

  • Created a data layer for use by our RDLC.
  • Created an RDLC using the report wizard and define a dataset for the report.
  • Used the report design surface to design our report including adding a chart.
  • Used the ReportViewer control to attach the RDLC.
  • Connected our ReportWiewer to a data source and take parameter values from the drop downlists.
  • Used AutoPostBack to refresh the reports when the dropdown selection was changed.

RDLCs allow you to create interactive reports including drill downs and grouping. For even more advanced reports you can use Microsoft® SQL Server™ Reporting Services with RDLs. With RDLs, the report is rendered on the report server instead of the web server. Another nice thing about RDLs is that you can define a parameter list for the report and it gets rendered automatically for you. RDLCs and RDLs both have their advantages and its best to compare them and choose the right one for your requirements.

Download VS2010 RTM Sample project


Related posts
How to render client report definition files (.rdlc) directly to the Response stream without preview 
Rendering an RDLC directly to the Response stream in ASP.NET MVC

Alfred Borden: Are you watching closely?

Creating an ASP.NET report using Visual Studio 2010 - Part 2


Creating the Client Report Definition file (RDLC)

Add a folder called “RDLC”. This will hold our RDLC report.

image 
Right click on the RDLC folder, select “Add new item..” and add an “RDLC” name of “Products”. We will use the “Report Wizard” to walk us through the steps of creating the RDLC.
image 
In the next dialog, give the dataset a name called “ProductDataSet”. Change the data source to “NorthwindReports.DAL” and select “ProductRepository(GetProductsProjected)”.
The “Data Source” may show up empty. To get it populated, make sure your project is compiled and there is an index.aspx file in the root folder. This may be a bug.
 
The fields that are returned from the method are shown on the right. Click next.
image 
Drag and drop the ProductName, CategoryName, UnitPrice and Discontinued into the Values container. Note that you can create much more complex grouping using this UI. Click Next.

image 
Most of the selections on this screen are grayed out because we did not choose a grouping in the previous screen. Click next.
image
Choose a style for your report. Click next.
image
The report graphic design surface is now visible. Right click on the report and add a page header and page footer.
image
With the report design surface active, drag and drop a TextBox from the tool box to the page header. Drag one more textbox to the page header. We will use the text boxes to add some header text as shown in the next figure.

image
You can change the font size and other properties of the textboxes using the formatting tool bar (marked in red). You can also resize the columns by moving your cursor in between columns and dragging.
image

Adding Expressions


Add two more text boxes to the page footer. We will use these to add the time the report was generated and page numbers. Right click on the first textbox in the page footer and select “Expression”.
image
Add the following expression for the print date (note the = sign at the left of the expression in the dialog below)
image

"© Northwind Traders " & Format(Now(),"MM/dd/yyyy hh:mm tt")
Right click on the second text box and add the following for the page count.
 
Globals.PageNumber & " of " & Globals.TotalPages
Formatting the page footer is complete.
 
We are now going to format the “Unit Price” column so it displays the number in currency format.  Right click on the [UnitPrice] column (not header) and select “Text Box Properties..”

image
Under “Number”, select “Currency”. Hit OK.
image

Adding a chart

With the design surface active, go to the toolbox and drag and drop a chart control. You will need to move the product list table down first to make space for the chart contorl. The document can also be resized by dragging on the corner or at the page header/footer separator.

image
In the next dialog, pick the first chart type. This can be changed later if needed. Click OK. The chart gets added to the design surface.
image 
Click on the blue bars in the chart (not legend). This will bring up drop locations for dropping the fields. Drag and drop the UnitPrice and CategoryName into the top (y axis) and bottom (x axis) as shown below. This will give us the total unit prices for a given category. That is the best I could come up with as far as what report to render, sorry :-) Delete the legend area to get more screen estate.
image
Resize the chart to your liking. Change the header, x axis and y axis text by double clicking on those areas.

image
We made it this far. Let’s impress the client by adding a gradient to the bar graph :-) Right click on the blue bar and select “Series properties”.
image

Under “Fill”, add a color and secondary color and select the Gradient style.
image

We are done designing our report. In the next section you will see how to add the report to the report viewer control, bind to the data and make it refresh when the filter criteria are changed.

 

Creating an ASP.NET report using Visual Studio 2010 - Part 3

 

Other Posts

Posted by rajbk | 8 comment(s)
Filed under: , , , ,
More Posts Next page »