Caching issue when re-generating pdf files

In our recent project, I met an issue regarding PDF cache, our web application will display the PDFs which generated by another web application, however the provide application could re-generate the PDF due to business process, and the consumer web application always wants to get the latest version of the PDF. The reality is the IIS somehow cache the PDF file, so after the first display, no matter the PDF changed on the back end, the consumer web application always show the first launched pdf which is really annoying.

Solution: Basically, there are quite a few methods to resolve this issue. As the root reason is IE cached the PDF, so you can ask the clients to clear the cache, or turn off IIS cache on the server side (go to ‘Output Caching’, add a new cache rule with file extension ‘.aspx’, tick both ‘User-mode caching’ and ‘Kernel-mode caching’ then under both options, select ‘Prevent all caching’).

But all in all, I prefer a lite solution which won’t involve server/client change. The idea is adding a time stamp at the end of url, so IE browser will treat as a new url so that won’t bother cache to display. The only trick is most likely the pdf link is on a hyperlink field or something like that, but the link will generate during the page load phase, so how to change the time stamp at the time when the client click is an issue.


   <asp:GridView ID="mygridview" runat="server" AutoGenerateColumns="False"
        DataKeyNames="id" Width="100%" SkinID="Professional" CssClass="my-gridview"
        GridLines="None" EnableModelValidation="True">
        <AlternatingRowStyle CssClass="aladdin-gridview-altrow" />
        <Columns>
            <asp:BoundField DataField="id" HeaderText="ID">
                <HeaderStyle HorizontalAlign="Left" />
                <ItemStyle ForeColor="#999999" HorizontalAlign="Left" Width="80px" />
            </asp:BoundField>
           
            <asp:BoundField DataField="DisplayDate" DataFormatString="{0:MMMM d, yyyy}" HeaderText=" My Date"    ItemStyle-HorizontalAlign="Center">
                <HeaderStyle HorizontalAlign="Left" />
                <ItemStyle HorizontalAlign="Left" Width="300px"></ItemStyle>
            </asp:BoundField>

            <asp:BoundField DataField="DisplayValue" HeaderText="Legislature" ItemStyle-HorizontalAlign="Center">
                <HeaderStyle HorizontalAlign="Left" />
                <ItemStyle HorizontalAlign="Left"></ItemStyle>
            </asp:BoundField>
      
            <asp:HyperLinkField  DataNavigateUrlFields="id" Target="_blank" 
                DataNavigateUrlFormatString = "mypdf-fromanotherapp{0}.pdf"
                Text="<img title='Print Session' border='0' src='images/sm-pdf.gif' />"   >
                  <ItemStyle Width="80px" HorizontalAlign="Center" VerticalAlign="Top" />
             </asp:HyperLinkField>
   
        </Columns>
        <HeaderStyle CssClass="my-gridview-header" />
        <SelectedRowStyle CssClass="my-gridview-selectedrow" />
    </asp:GridView>

solution a: direct the link to a new page(display in new window/tab) with query parameters, in the page load event, add time stamp and display the PDF in this new page.
In aspx page, should code like below

<a href="<%# "pdfdisplay.aspx?id=" + DataBinder.Eval(Container.DataItem, ("ID")) %>" target="_blank">%></a>

In aspx.cs, should code like below

protected void Page_Load(object sender, EventArgs e)
{
    string strID = Request.QueryString["id"];
    string newurl = String.Format("{0}mypdf_from_anotherAPP{1}.pdf?dt={2}", sitepath,strID,             DateTime.Now.Ticks.ToString()); //This will result in yourFile.pdf?dt=2839238293
    Response.Redirect(strURL);
}

solution b: use javascript to generate a url with timestamp whenever the link being clicked.

Aspx page:

<script type="text/javascript" language="javascript">
         function openPDFPopup(strOpen) {
             var mydt = new Date();
             timestr = mydt.getTime().toString();
             strOpen =strOpen+ '?t=' + timestr;
             var win = window.open(strOpen, '_blank');
             win.focus();
         }
</script>

            <asp:TemplateField >                     
                <ItemTemplate>
                    <a href="javascript:openPDFPopup('<%# String.Format("{0}mypdf-fromanotherapp{1}.pdf",ConfigurationManager.AppSettings("PDFSite"),Eval("id"))%>')">
                    <img src='images/sm-pdf.gif' border=0px/> </a>                                    
                </ItemTemplate>

                <ItemStyle Width="80px" HorizontalAlign="Center" VerticalAlign="Top" />                
            </asp:TemplateField>

Asp.net Url path and absolute path

There are quiet few cases that we need to find the file absolute path on the storage and the url path which can be referenced. The following is the the summary.

The test.pdf file is located at “c:\Inetpub\VirtualWebsite\Subfolderpath\test.pdf”
The url should be: http://servername/VirtualWebsite/Subfolderpath/test.pdf

 'http://servername/VirtualWebsite/Subfolderpath/default.aspx
 Dim strPathAndQuery As String = HttpContext.Current.Request.Url.PathAndQuery

 'http://servername/
 Dim strUrl As String = HttpContext.Current.Request.Url.AbsoluteUri.Replace(strPathAndQuery, "/")

 'http:// or https://
 Dim protocolpath As String = HttpContext.Current.Request.Url.Scheme + "://"

 'servername or domainname
 Dim apphost As String = HttpContext.Current.Request.Url.Authority

 '/subfolder
 Dim apppath As String = HttpContext.Current.Request.ApplicationPath

 'the whold url path
 pdffilepath = protocolpath + apphost + apppath + "subfolderpath/test.pdf"  

'"c:\Inetpub\VirtualWebsite\Subfolderpath\test.pdf"
 Server.MapPath("~\downloads\") + filename

Configure Report Viewer Control on Web Server

Recently our project need to use report viewer control to display SSRS report in a web project. There is no any issues in development phase but when we try to deploy the web application to the IIS server, it gave use the following error.

There are two options to resolve this issue.

Solution 1: Install reportviewer distribution package on the web server.
If you installed your VS2010 on the local machine, then check the following local path:
\Program Files\Microsoft SDKs\Windows\v7.0A\Bootstrapper\Packages\ReportViewer, there should be a file called ReportViewer.exe which can distributed 4 report viewer dlls to the Server’s GAC and register them as well, then your server pages can be displayed no problem.

Solution 2: Configure Web.config.
Sometimes, due to the security issue, your server may not allow to install those application distribution packages. You can config the web.config to achieve the same result.

Step 0: In the web project. Change the reference “Microsoft.ReportViewer.WebForms” property “CopyLocal” to “True”

Step 1: add the following declaration in the aspx page

<@ Register assembly="Microsoft.ReportViewer.WebForms, Version=10.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxx" namespace="Microsoft.Reporting.WebForms" tagprefix="rsweb" %>"

Step 2: add the following two lines into the <assemblies> section

      <add assembly="Microsoft.ReportViewer.WebForms, Version=10.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxx" />
        <add assembly="Microsoft.ReportViewer.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxxx" />
        

Step 3: add the following lines into the <httpHandler> section


  <add path="Reserved.ReportViewerWebControl.axd" verb="*" type="Microsoft.Reporting.WebForms.HttpHandler, Microsoft.ReportViewer.WebForms, Version=10.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxxxx"
        validate="false" />

However, when you added you will find a problem, because there are two httpHandlers actually, one is under tag, the other is under tag, so where you should added or both need to be filled in. The tricky is the system.web section is for configuring IIS 6.0, while the system.webserver version is used to configure IIS 7.0. IIS 7.0 includes a new ASP.NET pipeline and some configuration differences, hence the extra config sections. If you’re running IIS 7.0 in integrated mode only, you shouldn’t need to add the handlers to both sections. Adding it to system.web as well is a fallback for IIS 7.0 operating in classic mode.

Validation for data entry on the web page

Sometimes we need the validation for the data entry from the front web pages, the following is a few ways to implement

Solution 1: Asp.net

<BDP:BDPLite ID="BDPLiteDateA" runat="server" />
<asp:RequiredFieldValidator ID="RequiredFieldValidatorDateA" runat="server" ErrorMessage=" * Required" ControlToValidate="BDPLiteDateA" Display="Dynamic">
</asp:RequiredFieldValidator> &nbsp;<br>

<asp:CustomValidator ID="CustomValidatorDateA" runat="server" ErrorMessage="* Date is not in the range of the XXXXXX" ControlToValidate ="BDPLiteDateA" Display ="Dynamic" OnServerValidate="CustomValidatorDateAbsent_ServerValidate">
</asp:CustomValidator>
Protected Sub CustomValidatorDateReceived_ServerValidate(ByVal sender As Object, ByVal args As ServerValidateEventArgs)
        Dim initialdate As New Date(1990, 1, 1)
        If (Date.Compare(CDate(args.Value), initialdate) > 0) Then
            args.IsValid = True
        ElseIf BDPLiteDateA.IsNull And BDPLiteDateA.IsDate Then
            args.IsValid = True
        Else
            args.IsValid = False
        End If
End Sub

Also it can use the following code to display more make sense error message to the client

Protected Sub CustomValidatorDateA_ServerValidate(ByVal sender As Object, ByVal args As ServerValidateEventArgs)
        If Controller.VerifyDateA(CDate(args.Value), XXXID) Then
            args.IsValid = True
        Else
            args.IsValid = False
        End If

        Dim C As CustomValidator = CType(sender, CustomValidator)
        C.ErrorMessage = Controller.getDateRangeString(Me.XXXID)
End Sub

Solution II: Javascript

Aspx page:

<asp:TextBox ID="txtXXXX" Width="80px" Text='’
onchange=”javascript_regcheck(this);” AutoPostBack=”false” TabIndex=”20″ runat=”server” />

Javascript function:

function regcheckTimeSeconds(txtbox) {
    var s = txtbox.value
    //var reg = new RegExp("^((([0]?[1-9]|1[0-2])(:|\.)[0-5][0-9]((:|\.)[0-5][0-9])?( )?(AM|am|aM|Am|PM|pm|pM|Pm))|(([0]?[0-9]|1[0-9]|2[0-3])(:|\.)[0-5][0-9]((:|\.)[0-5][0-9])?))$");
    //var reg = new RegExp("^((([0]?[1-9]|1[0-2])(:|\.)[0-5][0-9]((:|\.)[0-5][0-9])?( )?(AM|am|aM|Am|PM|pm|pM|Pm))|(([0]?[0-9]|1[0-9]|2[0-3])(:|\.)[0-5][0-9]((:|\.)[0-5][0-9])?))$");

    // var reg = new RegExp("^([1-9]|1[0-2]|0[1-9]){1}(:[0-5][0-9](\:[0-5][0-9]\s?[aApP][mM])){1}$");
    var reg = new RegExp("^(1[0-2]|[1-9]):[0-5][0-9]:[0-5][0-9][ ]?[aApP][mM]$");


    if ((/^\s*$/)
        .test(s)) {} else {
        if (String(s)
            .search(reg) == -1) {
            txtbox.style.color = "red";
            alert("The time format is incorrect.\r\n\r\nThe correct format is:\r\nHH:MM:SS am/pm\r\n\r\nFor example:\r\n11:41:55 pm");
        } else {
            txtbox.style.color = "black";
        }
    }
}

This url shows all date regular expressions:
http://www.regexlib.com/Search.aspx?k=&c=5&m=-1&ps=100&AspxAutoDetectCookieSupport=1

Maybe shouldn’t include in this topic, the following code shows asp.net regular expression usage…..

Public Shared Function CheckDateByRegularExpression(ByVal xxxdatestring As String) As Integer
   ' the time format should be 12-SEP-2012
   ' 0: not empty string, correct time format  
   ' -1: not emtpy string, wrong date format 
   ' 1: empty string  
   If String.IsNullOrEmpty(xxxdatestring) Then
            Return 1
   Else  
      Dim reg As New Regex("^((31(?! (FEB|APR|JUN|SEP|NOV)))|((30|29)(?! FEB))|(29(?= FEB (((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00)))))|(0?[1-9])|1\d|2[0-8])-(JAN|FEB|MAR|MAY|APR|JUL|JUN|AUG|OCT|SEP|NOV|DEC)-((1[6-9]|[2-9]\d)\d{2})$")
      If reg.IsMatch(xxxdatestring) Then
         Return 0
       Else
          Return -1
       End If

    End If
End Function

Group rows in Gridview

You may want to show some grouped rows with header in gridview and display as the following

I didn’t post the code how to retrieve the dataset or object collection and bind to gridview as the data source, only show the two major part code which can implement the solution.

Step 1. Override the gridview render event

protected override void Render(HtmlTextWriter writer)
{
    string lastSubCategory = String.Empty;
    Table gridTable = (Table)gvProducts.Controls[0];
    foreach (GridViewRow gvr in gvProducts.Rows)
    {
        HiddenField hfSubCategory = gvr.FindControl("hfSubCategory") as
                                    HiddenField;
        string currSubCategory = hfSubCategory.Value;
        if (lastSubCategory.CompareTo(currSubCategory) != 0)
        {
            int rowIndex = gridTable.Rows.GetRowIndex(gvr);
            // Add new group header row
            GridViewRow headerRow = new GridViewRow(rowIndex, rowIndex,
                DataControlRowType.DataRow, DataControlRowState.Normal);
            TableCell headerCell = new TableCell();
            headerCell.ColumnSpan = gvProducts.Columns.Count;
            headerCell.Text = string.Format("{0}:{1}", "SubCategory",
                                            currSubCategory);
            headerCell.CssClass = "GroupHeaderRowStyle";
            // Add header Cell to header Row, and header Row to gridTable
            headerRow.Cells.Add(headerCell);
            gridTable.Controls.AddAt(rowIndex, headerRow);
            // Update lastValue
            lastSubCategory = currSubCategory;
        }
    }
    base.Render(writer);
}

Step 2. create a hidden field “hfSubCategory” in the aspx page


<asp:gridview id="gvProducts"
  autogeneratecolumns="False"
  emptydatatext="No data available."
  GridLines="None"
  runat="server" DataKeyNames="ProductID"
  CssClass="GridStyle">
  <AlternatingRowStyle CssClass="AlternatingRowStyle" />
  <HeaderStyle CssClass="ColumnHeaderStyle" />
<Columns>
    <asp:BoundField DataField="ProductID" HeaderText="Product ID">
        <ItemStyle Width="200px"/>
    </asp:BoundField>
    <asp:BoundField DataField="Name" HeaderText="Product Name”>
        <HeaderStyle HorizontalAlign="Left"/>
        <ItemStyle Width="200px" HorizontalAlign="Left"/>
    </asp:BoundField>
    <asp:BoundField DataField="ProductNumber" HeaderText="Product Number" />
    <asp:BoundField HeaderText="Price"
            DataField="ListPrice"
            DataFormatString="{0:c}">
        <ItemStyle HorizontalAlign="Right"></ItemStyle>
    </asp:BoundField>
   <asp:TemplateField>
        <ItemTemplate>
            <asp:HiddenField ID="hfSubCategory" runat="server"
                             Value='<%#Eval("SubCategoryName")%>' />
        </ItemTemplate>
   </asp:TemplateField>
</Columns>
</asp:gridview>

Javascript integreted with GridView rowdatabound event

Javascript code is as below. The second hightlighten part also shows how to set a value to the label, if you just use property “value”, it won’t work.

function CheckGridList(maxnumber, currentcheckboxid){
  var count = 0;
  var leftitems = 0;
  for (i=0; i < document.forms[0].elements.length; i++){           
     if (document.forms[0].elements[i].type == 'checkbox') 
       {
        if (document.forms[0].elements[i].checked == true){                                   
            count++;
            if (count > maxnumber){
        //document.forms[0].elements[i].checked = false;                                               
            document.getElementById(currentcheckboxid).checked = false;                       
            break;
            }
         }
        }
     }
  if (count > maxnumber)
    {
      alert("Only 5 items can be selected at one time.");
      return false;
    }
   else {
         //leftitems = maxnumber - count;
         document.getElementById("lbl_alert").innerHTML = (maxnumber - count).toString() + " items remaining for selecting.";
         return true;
          }
    }

The first highlight get parameter from the function argument currentcheckboxid, but how to pass the current check box client id to the javascript?


Private Sub gridview_RowDataBound(sender As Object, e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles gridview.RowDataBound
        Dim ck As CheckBox
        Try
            If e.Row.RowType = DataControlRowType.DataRow Then
                ck = DirectCast(e.Row.FindControl("ckselected"), CheckBox)
                ck.Attributes.Add("OnClick", "CheckGridList(" & SurveyController.getMaxItemNumber & ", '" & ck.ClientID & "')")
            End If
        Catch ex As Exception
            Throw

        End Try
    End Sub

Be cautious the single quote and double quote using, if you just give the id value without single quote, the javascript will pass an object as the parameter instead of the id value.

Pass parameter from asp.net to javascript.

Recently we have a requirement to put a checkbox in every gridview row, but only maximum rows allow to be selected, if you select more, the javascript need to alert you. The thing is the maximum number should get from web.config and shouldn’t be hard coded, so how pass the backend asp.net argument to the front javascript? There is a few ways to pass, but the following is working for me than others, for example using code block

Backend aspx.vb page:

    Private Sub gridview_RowDataBound(sender As Object, e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles gdvw_Survey.RowDataBound
        Dim ck As CheckBox
        Try
            If e.Row.RowType = DataControlRowType.DataRow Then
                ck = DirectCast(e.Row.FindControl("ckselected"), CheckBox)
                ck.Attributes.Add("OnClick", "CheckGridList(" & DataController.getMaxNumber & ")")
            End If
        Catch ex As Exception
            Throw
        End Try
    End Sub

Frontend page:

javascript:

  <script type="text/javascript">
        function CheckGridList(maxnumber)
               {      
                var count = 0;
                for (i=0; i < document.forms[0].elements.length; i++)
                    {           
                        if (document.forms[0].elements[i].type == 'checkbox') 
                        {
                            if (document.forms[0].elements[i].checked == true)
                            {                                   
                            count++; 
                            if (count > maxnumber)
                                {
                                    document.forms[0].elements[i].checked = false;                       
                                    break;
                                }
                            }
                        }
                    }
                    if (count > maxnumber)
                    {
                        alert("Only 5 items can be selected at one time.");
                        return false;
                    }
                    else { return true;}
                }
 </script>

                 <asp:GridView ID="gridview" runat="server" CellPadding="4" GridLines="None" AutoGenerateColumns="False" DataKeyNames="ID"  Width = "100%">
                       <Columns>

                            <asp:BoundField DataField="ID" HeaderText="" ReadOnly="True" Visible="False"/>  

                            <asp:BoundField DataField="Order" HeaderText="Number"  >
                                <ItemStyle Font-Size="Small" HorizontalAlign="Center" Font-Names="Arial" />
                                <HeaderStyle Font-Size="Small" HorizontalAlign="Center" Font-Names="Arial"/>
                            </asp:BoundField> 

                            <asp:TemplateField>
                                <ItemTemplate>
                                    <asp:CheckBox ID="ck" runat="server" Checked="false" />
                                </ItemTemplate>
                            </asp:TemplateField>
                    

                            <asp:BoundField DataField="Name" HeaderText="Name" ItemStyle-Width = "90%" >
                                <ItemStyle Font-Size="Small" HorizontalAlign="Left"   Font-Names="Arial"/>
                                <HeaderStyle Font-Size="Small" HorizontalAlign="Center" Font-Names="Arial" />
                            </asp:BoundField>
                                                       

                          
                        </Columns>
                        <FooterStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
                        <RowStyle BackColor="#F7F6F3" ForeColor="#333333" />          
                        <SelectedRowStyle BackColor="#E2DED6" Font-Bold="True" ForeColor="#333333" />
                        <PagerStyle BackColor="#284775" ForeColor="White" HorizontalAlign="Center" />
                        <HeaderStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
                        <AlternatingRowStyle BackColor="White" ForeColor="#284775" />
                        <EmptyDataTemplate>
                            <asp:CheckBox ID="ckladjust" runat="server" Checked="True" />
                        </EmptyDataTemplate>
                    </asp:GridView>