Saturday, August 21, 2010

Dynamically removing parameters from an ObjectDataSource and LinqDataSource within a ReportViewer LocalReport

If you've been using the ReportViewer in your web application, you've probably encountered having to deal with dynamically adding and removing parameters from a datasource to dynamically change your query statement in your report.  This is useful if you provide your users a filter for ad hoc reporting.  You could have radio buttons or drop downs on your report page to allow your users to filter search results.

If you are using Linq in your application, there's a LinqDataSource control that you can use to make calls using your DataContext object.  In the example below, my LinqDataSource object has a Where condition parameter:  StudentStatusID == @StudentStatusID


 <asp:LinqDataSource ID="ldsExamCompleted" runat="server" 
        ContextTypeName="MyDataContext" EntityTypeName="" 
        Select="new (StudentID, FirstName, LastName, ExamDate)" 
        TableName="Students" 
        
    
        Where="StudentStatusID == @StudentStatusID" 
        OrderBy="LastName">
        <WhereParameters>
            <asp:QueryStringParameter Name="StudentStatusID" QueryStringField="status" 
                Type="Int16" />
        </WhereParameters>
    </asp:LinqDataSource>


 For example on the aspx page we have an html dropdown named Status with 3 possible values:  Active (value=1), Inactive (value=2) and Both (value=0).  Each time we change the value on the dropdown, we pass the value as a querystring parameter.  Let's say we choose Both on the dropdown.  Our code behind would look like this.

            if (!IsPostBack)
            {
                ReportViewer1.LocalReport.DataSources.Clear();

                ParameterCollection examParams = ldsExamCompleted.WhereParameters;

                //if user chooses 'Both' on status dropdown
                if (Request.QueryString["status"] == "0"
                {

                    Parameter p = examParams["StudentStatusID"];
                    ldsExamCompleted.WhereParameters.Remove(p);
                }
            }

Here, we removed the where condition parameter "StudentStatus == @StudentStatusID" from our Linq select query and our DataContext object returns all results.  That simple.

So when should you use an ObjectDataSource in your ReportViewer?  An ObjectDataSource is ideal if you use  TableAdapters.  TableAdapters provide communication between your application and the database.  TableAdapters are automatically created when you add a DataSet to your project.  You pass parameters from your ObjectDataSource to your TableAdapter if the Select method on your TableAdapter expects parameters.   Below is a sample of an ObjectDataSource on the aspx page:

   <asp:ObjectDataSource ID="odsExamCompleted" runat="server" 
        OldValuesParameterFormatString="original_{0}" SelectMethod="GetDataByStatusID" 
        
        TypeName="dsStudentTableAdapters.vwExamResultsTableAdapter">
        <SelectParameters>
            <asp:QueryStringParameter Name="StudentStatusID" QueryStringField="status" 
                Type="Int16" />
        </SelectParameters>
    </asp:ObjectDataSource>



Unlike a LinqDataSource where you can remove Where Condition parameters such as "StudentStatusID == @StudentStatusID", an ObjectDataSource can only remove the Select parameter StudentStatusID.  So if you try to remove this parameter from your ObjectDataSource, it will error out since your TableAdapter expects this parameter on its where clause.  You will need to add another Select method to your TableAdapter if you need to have a parameterless query or one with additional parameters.  So on our TableAdapter, I defined 2 Select methods.  One named GetDataByStatusID which expects a @StudentStatusID parameter and one named GetData which doesn't expect any parameter. 


Let's say we want to achieve the same results as our LinqDataSource  example above, this is what we should do on our code behind:
           
               if (!IsPostBack)
            {
                ReportViewer1.LocalReport.DataSources.Clear();

                ParameterCollection examCompletedParams = odsExamCompleted.SelectParameters;

                //if user chooses 'Both' on status dropdown
                if (Request.QueryString["status"] == "0"
                {

                    Parameter p = examCompletedParams["StudentStatusID"];
                    odsExamCompleted.SelectParameters.Remove(p);

                    odsExamCompleted.SelectMethod = "GetData";
                }
            }

Hope this helps anyone implementing ad hoc reporting on their web applications.


Friday, August 20, 2010

How to secure a web folder in IIS 7 by denying IP addresses other than your own

I spent 3 days trying to think of a way to secure a web folder which contained sensitive files. I wanted public users to be able to upload files to that folder but i didn't want them to be able to view it. In other words, Write but no Read. The only way to view the file is within my application which the user needs to be authenticated using Forms Authentication (SqlMembershipProvider). Now there were different approaches on how to secure web folders. The 1st one is using ASP.NET authorization which looks like this on a web.config:

<location path="uploads">
     <system.web>
          <authorization>
               <allow users="myself"/>
               <deny users="*"></deny>
         </authorization>
     </system.web>
</location>

Sadly, this didn't work. Anyone could just type in the full url path to the file and view it without being authenticated.

The 2nd one is through IIS 7 URL Authorization which looks like this on a web.config:

<location path="uploads">
       <system.webServer>
             <security>
                  <authorization>
                          <add accessType="Allow" users="myself" />
                          <remove users="*" roles="" verbs="" />
                  </authorization>
             </security>
       </system.webServer>
</location>

Excitingly, this blocked all requests to the files I wanted to hide. Sadly, even my application wasn't able to access the files. IIS 7 URL Authorization doesn't care about any ASP.NET authenticated user.

The 3rd option was the one that worked for me. Using IIS 7 Url Rewrite, I created a rule denying requests from IP addresses that don't match my server's IP address. Just put a web.config file into the folder you want to secure and put the entry below. Just replace the IP address below with your server's IP.

<system.webServer>
      <rewrite>
            <rules>
                 <rule name="RequestBlockingRule1" patternSyntax="Wildcard"                   stopProcessing="true">
                        <match url="*" />
                                <conditions>
                                        <add input="{REMOTE_ADDR}" pattern="77.11.36.12" negate="true" />
                                </conditions>
                                <action type="CustomResponse" statusCode="403" statusReason="Forbidden: Access is denied." statusDescription="You do not have permission to view this directory or page using the credentials that you supplied." />
                  </rule>
           </rules>
      </rewrite>
</system.webServer>

Hope this helps someone out there.