Wednesday, June 21, 2006

ASP.NET 2.0 Deployment : Installer for updating Web.Config settings during Setup - Part II

In Part I of the article we saw how Visual Studio 2005 Web Deployment Projects help in deployment of ASP.NET 2.0 applications. Let us now look at how we can make use of this add-on in building an MSI that also sets the configuration settings.

The first step is to add an installer class in your web project. This installer class will be used in setting the configuration settings in web.config. The following steps will discuss on adding the installer class:

1. Right click on your web project, and then click Add New Item.
2. Under Templates, click Class, and then give the name of the class as InstallerClass.cs.
3. This will get added in the App_Code folder in your web application.
4. Right click on the Web Project, and then click Add Reference. Add the following references to the Web project:
System.Configuration.Install
System.Configuration
System.Windows.Forms
5. Open InstallerClass.cs, and then replace it with the following code:

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Configuration.Install;
using System.ComponentModel;
using System.Collections;
using System.Windows.Forms;
using System.Xml;
///
/// Summary description for InstallerClass
///

[RunInstaller(true)]
public class InstallerClass : System.Configuration.Install.Installer
{
private System.ComponentModel.Container components = null;
public InstallerClass()
{
components = new System.ComponentModel.Container();
}
protected override void OnBeforeInstall(IDictionary stateSaver)
{
string server = this.Context.Parameters["DATASOURCE"];
string userId = this.Context.Parameters["USERID"];
string password = this.Context.Parameters["PASSWORD"];
string newConnectionString = "User Id=" + userId + ";Password= " + password + ";Data Source =" + server + ";";
string xmlConfigFile = "";
xmlConfigFile = Context.Parameters["INSTALLDIR"] + "web.config";
UpdateConfigFile(xmlConfigFile, newConnectionString, "connectionString");
}
public void UpdateConfigFile(string filepath, string newValue, string keyName)
{
XmlReaderSettings xmlReaderSettings = null;
XmlDocument doc = null;
XmlReader xmlReader = null;
XmlWriterSettings xmlWriterSettings = null;
XmlWriter writer = null;
try
{
xmlReaderSettings = new XmlReaderSettings();
doc = new XmlDocument();
xmlReader = XmlReader.Create(filepath, xmlReaderSettings);
doc.Load(xmlReader);
xmlReaderSettings.CloseInput = true;
foreach (XmlNode node in doc.DocumentElement.ChildNodes)
{
if (node.Name == "connectionStrings")
foreach (XmlNode childNode in node.ChildNodes)
{
if (childNode.Name == "add" && childNode.Attributes["connectionString"] != null && childNode.Attributes["name"].Value == keyName)
{
string connStr = childNode.Attributes["connectionString"].Value.ToString();
string newConnStr = newValue;
childNode.Attributes["connectionString"].Value = newConnStr;
}
}
}

xmlReader.Close();
xmlWriterSettings = new XmlWriterSettings();
xmlWriterSettings.Indent = true;
xmlWriterSettings.CloseOutput = true;
writer = XmlWriter.Create(filepath, xmlWriterSettings);
doc.Save(writer);
}
catch (Exception)
{
//Handle exeptions
}
finally
{
writer.Close();
}
}
}

6. Save the file and build your web project.

In Part I of the article we discussed on how to add a Web deployment. Make sure the Web Deployment Project is added to the Web project and the settings are done as detailed in Part I of this article. Rebuild this Web Deployment project also.

The next step is to build an MSI. The following steps explain the same:

1. Right click on the solution node in Solution Explorer, point to Add, and then click New Project.
2. Select Setup and Deployment under Other Project Types from the Project Types box.
3. Select Web Setup Project under Templates, and then give a name MyNewWebSiteSetup.
4. Right click MyNewWebSiteSetup on Solution Explorer, point to Add, and then click Project Output.
5. From the Project dropdown, choose (i.e. the Web Deployment project that you have added and compiled). You will find that Precompiled Web Outputs from (Active) is added to your Setup project.
6. Right click MyNewWebSiteSetup on Solution Explorer, point to Add, and then click Assembly.
7. From the Browse tab, browse to the folder where your Web Deployment Project is present. Under the MyNewWebSite_deploy -> Debug -> Bin path you will find the single assembly of the Web Project that was created, named MyNewWebSiteAssembly
(refer Point 5 in Part I of the article).
8. Right click MyNewWebSiteSetup on Solution Explorer, point to View, and then click User Interface.

9. Under the Install node, right click on Start and then click Add Dialog.
10. From the Add Dialog pop up choose Textboxes (A) and then click OK.
11. Right click Textboxes (A) node and then click Properties Window.
12. In the Properties window, set the following properties:
BannerText - Configuration Settings

BodyText- Please provide values for Configuration Settings
Edit1Label-DataSource:
Edit1Property- DATASOURCE
Edit2Label-UserId:
Edit2Property- USERID

Edit3Label-Password:
Edit3Property- PASSWORD

Edit4Visible-False

13. After setting the above properties, close the Properties window.
14. Right click MyNewWebSiteSetup on Solution Explorer, point to View, and then click Custom Actions.
15. On the left pane, right click Install node under Custom Actions, and then click Add Custom Action.
16. In the Look in dropdown list in Select Item in Project dialog, select Web Application Folder.
17. Click the Add File button and then choose the single assembly dll that is present under the MyNewWebSite_deploy -> Debug -> Bin -> MyNewWebSiteAssembly.dll.

Note : If you have not set any path when adding, it would be present in C:\Documents and Settings\ My Documents\Visual Studio 2005\Projects\ GridViewWeb\ MyNewWebSite_deploy\ Debug\bin\ MyNewWebSiteAssembly.dll.

18. Right click MyNewWebSiteAssembly.dll on the left pane, and then click Properties Window.

19. In the Properties Window, set the CustomActionData property to the following:
/datasource=[DATASOURCE] / userid=[USERID] /password=[PASSWORD] /INSTALLDIR="[TARGETDIR]\"

These are precisely the properties we use in the InstallerClass.

20. Right click MyNewWebSiteSetup on Solution Explorer, and then click Build.
21. You can test the installation of MSI using the Install option when you right click MyNewWebSiteSetup on Solution Explorer.

You will find that you can set the connection string properties at runtime during installation of your Web Application.

22. Set your project in Release mode, and then take a build again to take the final installable.

While picking up the single assembly dll was straight forward in ASP.NET 1.1, in ASP.NET 2.0, this has been made possible with the Visual Studio Web Deployment Project Add on. This makes adding the Custom Actions possible.

Wednesday, June 07, 2006

ASP.NET 2.0 - Custom Paging and Custom Sorting for GridViews

Paging and sorting GridViews are the most sought after functionalities in most of the Web applications.When it comes to performance, for datacentric applications its very important to implement the custom paging/sorting mechanism instead of the
default paging/sorting for gridviews. This is because the page becomes heavily loaded when thousands of records are fetched at a strech from the
database.

To overcome this, we must implement a "Custom paging" mechanism which brings record only "on-demand". This article discusses how this can be
implemented in ASP.NET 2.0 with zero lines of code in presentation layer.

1. The first step is to write a stored proc, GetSortedItems that accepts these parameters and returns a resultset.

a. SortExpression
b. StartRow
c. EndRow

For Example, if the parameter values are ("ItemId desc", 5,10), which means the rows 5 to 10 have to be retrieved, with ItemId sorted in descending
order.

2. Another stored proc must be written, say GetItemsCount that returns the count of records that is returned from the resultset.

3. Next step is to call the stored proc in the Data Access layer, and return a Collection object.

The following is an example of a DAL method using ODP.NET of Oracle:

public QueryList GetSortedQueries(string sort, int startRowIndex, int maximumRows, int x, string y)
{
DataSet dsQueryList = null;
OdpHelper adoHelper = new OdpHelper();
QueryEntity queryEntity = null;
QueryList queryList = null;
string connectionString = ConfigurationManager.ConnectionStrings["connectionString"].ConnectionString;
IDataParameter[] param = null;
param = adoHelper.GetDataParameters(4);
param[0] = adoHelper.GetRefCurParameter("p_cursor");
param[0].Direction = ParameterDirection.Output;
param[1] = adoHelper.GetParameter("P_SortExp",
DbType.String,
1000,
ParameterDirection.Input
);
param[1].Value = sort;
param[2] = adoHelper.GetParameter("BV_STARTROW",
DbType.Int32,
4,
ParameterDirection.Input
);
param[2].Value = startRowIndex;
param[3] = adoHelper.GetParameter("BV_ENDROW",DbType.Int32,4,ParameterDirection.Input);
int lastRow = startRowIndex + maximumRows;
param[3].Value = lastRow;
dsQueryList = adoHelper.ExecuteDataset(connectionString,"TestCustomPagingWithSorting",param);
queryList = new QueryList();
//loops through the dataset rows to load displaycontrols retrieved into the list
if (dsQueryList.Tables.Count > 0)
{
if (dsQueryList.Tables[0].Rows.Count > 0)
{
foreach (DataRow drQueryEntity in dsQueryList.Tables[0].Rows)
{
queryEntity = new QueryEntity();
if (drQueryEntity["queryid"] != DBNull.Value)
{
queryEntity.QueryId = Convert.ToInt32(drQueryEntity["queryid"].ToString());
}
if (drQueryEntity["querytypeid"] != DBNull.Value)
{
queryEntity.QueryTypeId = Convert.ToInt32(drQueryEntity["querytypeid"].ToString());
}
queryList.Add(queryEntity);
}
}
}
return queryList;
}


4. In the Presentation layer, all we have to do is to add an ObjectDataSource control and set the following properties:
  • SelectMethod property to the GetSortedQueryList method - either from Data Access Layer or Business Facade
  • TypeName property to the Data Access Layer or Business Facade class that has the specific method.
  • SortParameterName property to the parameter name in which the Sort expression is passed.
  • SelectCountMethod is the method in the Data Access Layer or Business Facade class that returns the count of records.
  • The <SelectParameters> are the individual parameters that are present passed to the SelectMethod and SelectCountMethod.
  • If the GridView has to be bound on triggering of an event, just do not specify the DataSourceID="ObjectDataSource1" in GridView, and add these
    two lines of code in the event.
    GridView1.DataSourceID = "ObjectDataSource1";
    GridView1.DataBind();
Note: The method signature of SelectCountMethod must necessarily contain all parameters of SelectMethod, even though these may not be used.

Ex:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="GetSortedQueryList"
SortParameterName="sort" TypeName="CustomPagingService.CustomPagingService" SelectCountMethod="GetQueryListCount"
EnablePaging="True" >
<SelectParameters>
<asp:Parameter DefaultValue="Asc" Name="sort" Type="String" />
<asp:Parameter DefaultValue="1" Name="startRowIndex" Type="Int32" />
<asp:Parameter DefaultValue="10" Name="maximumRows" Type="Int32" />
<asp:Parameter DefaultValue="10" Name="x" Type="Int32" />
<asp:Parameter DefaultValue="" Name="y" Type="String" />
</SelectParameters>
</asp:ObjectDataSource>




<asp:GridView ID="GridView2" DataKeyNames="QueryId" runat="server" AllowPaging="True"
AllowSorting="true" AutoGenerateColumns="false"
PageSize="5">
<Columns>
<asp:BoundField DataField="QueryId" ReadOnly="true" HeaderText="QueryId" SortExpression="QueryId" />
<asp:BoundField DataField="QueryTypeId" ReadOnly="true" HeaderText="QueryTypeId"
SortExpression="QueryTypeId" />
</Columns>
<PagerSettings FirstPageText="First" LastPageText="Last" Mode="NumericFirstLast" />
</asp:GridView>


Thats it. Just compile and run your page. You will find that by just setting properties with no line of code in the presentation layer, Custom paging and sorting functionalites are achieved.