Force File Download

One handy feature that browser’s offer is the ability to automatically open know file types in their respective program. There are however times when you want to force the user to download the file, you want the Save As dialog box to appear to the user.

In order to avoid the default behavior and stop file from being open automatically you can use the content-disposition header. This is an HTTP header, and is not asp.net specific.

Content-disposition: attachment; filename=fname.ext

To do this using asp.net resources you can use the Response object to set the header like this:

Response.AppendHeader(“Content-disposition”, “attachment; filename=” + fileName);

It’s very simple and very handy. I wrote a working example below:

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

    protected void Button1_Click(object sender, EventArgs e)
    {
        string fileName = "MyFile.doc";
        string filePath = Server.MapPath("MyFiles/"+fileName);
        Response.AppendHeader("Content-disposition", "attachment; filename=" + fileName);
        Response.ContentType = "Application/msword";
        Response.WriteFile(filePath);
        Response.End();
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Button ID="Button1" runat="server" Text="Download" 
            onclick="Button1_Click" />
    </div>
    </form>
</body>
</html>

 

 

Exposing events in Web User Controls

Web User Controls are a way encapsulating code that would have to be repeated in various pages, they are definitely a huge help.

VisualStudio makes so simple to create these user controls that even novice users can do it. When the page doesn’t need to interact with the user control it is really simple, and there’s nothing to it but putting the controls you want in the user control. However the controls that are inside the user control are isolated from the Page and you can’t use it’s events. This post will show you how easy it is to overcome this issue.

Let’s say we want to create a simple control with a DropDownList, a Button and a Label. When the button is clicked the selected value in the DropDownList is copied to the label. Here’s the code for it:

<%@ Control Language="C#" ClassName="MyUserControl" %>

<script runat="server">

    protected void ButtonCopy_Click(object sender, EventArgs e)
    {
        LabelText.Text = DropDownListNames.SelectedValue;
    }
</script>
<asp:DropDownList ID="DropDownListNames" runat="server">
    <asp:ListItem>Elisa</asp:ListItem>
    <asp:ListItem>Gabriel</asp:ListItem>
    <asp:ListItem>Rafaela</asp:ListItem>
</asp:DropDownList>
<br />
<br />
<asp:Button ID="ButtonCopy" runat="server" Text="Copy SelectedValue to Label" 
    onclick="ButtonCopy_Click" />
<br />
<br />
<asp:Label ID="LabelText" runat="server" Text="Label" Font-Size="X-Large"></asp:Label>

This is simple enough, right? All you have to do is place this control in any page and this behavior is going to be replicated. What I notice that most novice programmers miss is if you need the control to send some kind of information to the page.

Say you want to have a label in your page showing the time that the Copy button of the user control is clicked. It would be easy if you could access the button click event of the button inside the user control but unfortunately you can’t. What you want is to make the event of the inner button accessible to outside the control, this is done creating a new event exposing the event you want.


<%@ Control Language="C#" ClassName="MyUserControl" %>

<script runat="server">

    //this is the event that will be exposed
    public event EventHandler ButtonClick;

    protected void ButtonCopy_Click(object sender, EventArgs e)
    {
        LabelText.Text = DropDownListNames.SelectedValue;

        //this tests if the event has been subscribed by any method
        if (ButtonClick != null)
        {
            //fires the event passing the same arguments of the button
            //click event
            ButtonClick(sender, e);
        }
    }
</script>
<asp:DropDownList ID="DropDownListNames" runat="server">
    <asp:ListItem>Elisa</asp:ListItem>
    <asp:ListItem>Gabriel</asp:ListItem>
    <asp:ListItem>Rafaela</asp:ListItem>
</asp:DropDownList>
<br />
<br />
<asp:Button ID="ButtonCopy" runat="server" Text="Copy SelectedValue to Label" 
    onclick="ButtonCopy_Click" />
<br />
<br />
<asp:Label ID="LabelText" runat="server" Text="Label" Font-Size="X-Large"></asp:Label>

Now you are able to put the control in a page and use it’s event, like in the following example:

<%@ Page Language="C#" %>

<%@ Register Src="MyUserControl.ascx" TagName="MyUserControl" TagPrefix="uc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
    protected void MyUserControl1_ButtonClick(object sender, EventArgs e)
    {
        LabelTime.Text = DateTime.Now.ToString();
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <!-- Notice here that I use ButtonClick event I just created in the user control -->
        <!-- Also notice that I didn't misspell the event, the On is added by a VisualStudio convention   -->
        <uc1:MyUserControl ID="MyUserControl1" runat="server" OnButtonClick="MyUserControl1_ButtonClick" />
        <br />
        <asp:Label ID="LabelTime" runat="server" Text="Label" Font-Bold="True" 
            Font-Italic="True" ForeColor="#FF3300"></asp:Label>
    </div>
    </form>
</body>
</html>

You could also subscribe to the event in the code behind just like you do with asp.net controls. Here is the code for it:

<%@ Page Language="C#" %>

<%@ Register Src="MyUserControl.ascx" TagName="MyUserControl" TagPrefix="uc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
    public void Page_Load(object sender, EventArgs e)
    {
        MyUserControl1.ButtonClick += MyUserControl1_ButtonClick;
    }

    protected void MyUserControl1_ButtonClick(object sender, EventArgs e)
    {
        LabelTime.Text = DateTime.Now.ToString();
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <!-- Notice here that I use ButtonClick event I just created in the user control -->
        <!-- Also notice that I didn't misspell the event, the On is added by a VisualStudio convention   -->
        <uc1:MyUserControl ID="MyUserControl1" runat="server" />
        <br />
        <asp:Label ID="LabelTime" runat="server" Text="Label" Font-Bold="True" 
            Font-Italic="True" ForeColor="#FF3300"></asp:Label>
    </div>
    </form>
</body>
</html>

I wrote this post because I helped a few people in the asp.net forums and I hope it can help other people that are beginning to write web user controls. Happy coding guys.

Top 10 Answerer on Asp.Net Forum

Today I made it to the Top 10 Answerers of the asp.net forums for the first time and I’m really happy with it.

I just hope it’s not too geeky of me to be celebrating this… My wife seems to think so!

I like the forums because they always give me new challenges to help me learn more. I get problems from people all over the world and it makes me learn a lot of things that I wouldn’t on my day to day job. If that makes me a geek so be it.

GridView: Select last inserted record

The other day a responded a post on the asp.net forums about this so I think it might be useful to others.

Imagine if you have a GridView using pagination. When you insert a new record using a DetailsView or a FormView you want this new record to be automatically selected on the GridView, even if it’s not at the same page (of the GridView) that you currently have selected.

//returns a collection of records, including the one you just inserted
DataRowCollection drc = Dal.GetData();

//RecordID is the id of the record you just inserted, 
//you need to store this somewhere when you insert the record
DataRow dr = drc.Find(RecordID);

//having found the datarow of the record you need to know it's position(index) in the results
int index = drc.IndexOf(dr);

//Get the page the record will appear on
int page = index / GridView1.PageSize;

//If you're not already on the right page, set the PageIndex to the correct page
if (GridView1.PageIndex != page)
{
     GridView1.PageIndex = page;
}

 //gets the index of the record in the page
GridView1.SelectedIndex = index - (page* GridView1.PageSize);

 

Cache using Dynamic LINQ and Generics

You know those tables in your systems that are almost static (never change) by are consulted thousands of times a day? Well, the other day at work some asked me if we could avoid all this trips to the database.

The solution I came up with creates a cache at our BLL (Business Logic Layer) avoiding DAL layer to be called. First of all our base architecture is based on the principles on the asp.net tutorial Creating a Business Logic Layer. This article basically creates something very similar to a proxy pattern before the DAL which gives me the perfect place to intercept any calls to the DAL and consult my cache.

To avoid the trip to the database, when the BLL is instantiated I do one query to the database which returns all the records in the table. The resulting DataTable is stored in my cache class which is declared as static in the class. This will make possible that the cache is populated only once. All subsequent queries will fetch the results from the Cache instead of making a trip to the database.

The cache table is very simple. It stores a DataTable and performs filters in it using LINQ syntax. I had other options but since I was starting to work with LINQ at the time it seemed like a good choice. For this code I use the Dynamic LINQ which I mentioned in my previous post.

In order to work with TypedDataSets (which was a requirement for me) I create the Cache class to accept the DataTable and DataRow types.

public class CacheDataTable<T, S>  where T : DataTable where S : DataRow
    {
        private T cacheDataTable;

        //stores the DataTable that will be used for future consults
        public CacheDataTable(T dataTable)
        {
            this.cacheDataTable = dataTable;
        }

        //returns a DataTable filtered by the expression
        public T GetData(string expression, params object[] values)
        {
            IEnumerable<S> tmp = ((IEnumerable<S>)cacheDataTable).AsQueryable().Where(expression, values);
            return getDataTablePopulated(tmp);
        }

        //returns all the records
        public T GetData()
        {
            return getDataTablePopulated((IEnumerable<S>)cacheDataTable);
        }

        //takes an enumeration and creates a new DataTable
        private T getDataTablePopulated(IEnumerable<S> rows)
        {
            T dt = Activator.CreateInstance<T>();
            foreach (S row in rows)
            {
                dt.ImportRow(row);
            }
            return dt;
        }
    }

After having the Cache class defined you can use it in the BLL like this:

public class CountryBLL
{
        CountryDAL dal;

        //the cache is static so that it is shared among all instances from the CountryBLL class
        private static CacheDataTable<DataSet1.CountryDataTable, DataSet1.CountryRow> cache;

        public TBG_CountryBLL()
        {
            dal = new CountryDAL();
            //tests if the cache class has not been created yet. Only happens at the first access
            if (cache == null)
            {
                cache = new CacheDataTable<DataSet1.CountryDataTable, DataSet1.CountryRow>(dal.GetData());
            }
        }

        public DataSet1.CountryDataTable GetData()
        {
            //gets all records from the cache
            return cache.GetData();
        }

        public DataSet1.CountryDataTable GetDataByPK(decimal id)
        {
            return cache.GetData("id == @0", id);
        }
}

Coded like this the CountryBLL will only cause a database connection at its first instantiation. This approach should only be used with tables which are static (or almost). If one record is inserted in this table it would demand the application to be restarted so that the static cache variable would be unloaded and created again. There are ways around this but it is not the objective of this post.

Hope this will help someone :-)