February 2006 - Posts

Image in DetailsView part 2

Introduction

In my previous post, we were able to make our DetailsView display our images coming from the database. We were able to do so with the help of an auxilliary web form to display the image, sort of like an image handler. While there's nothing wrong with that technique it requires an extra page to be created just so we can display the image. We can do away with the image handler and let the page draw the image itself.

Post it to me pls

We are going to change the image url /uri of ImageField row from our default page(default.aspx):

DataImageUrlFormatString="~\DisplayPhoto.aspx?emp_no={0}">

to this:

DataImageUrlFormatString="~\Default.aspx?emp_no={0}">

        

The source will be the page itself; upon loading of the control(DetailsView) it will call itself, this timewith  the paramater(emp_no).

 

Then we're going to move the streaming of image or the drawing of the image to the codebehind of the page itself:

 

 

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        Dim empNo As String = Request.QueryString("emp_no")

        If Not empNo Is Nothing Then

            If empNo.Trim <> "" Then

                ShowImage(empNo)

            End If

        End If

    End Sub

 

    Private Sub ShowImage(ByVal empNo As String)

        Dim commandString As String = "SELECT Photo FROM HR_EmpPhoto WHERE Emp_No=" + empNo.ToString()

 

        Using myConn As New SqlConnection(ConfigurationManager.ConnectionStrings("HissDBConnectionString").ToString())

            Dim myCommand As New SqlCommand(commandString, myConn)

            myConn.Open()

            Dim myReader As SqlDataReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection)

 

            If myReader.Read() Then

                Dim imgBuffer As Byte() = CType(myReader.Item("Photo"), Byte())

                Response.ContentType = "image/jpeg"

                Response.BinaryWrite(imgBuffer)

            End If

            myConn.Close()

        End Using

 

    End Sub

 

That's all there is to it! You now have an DetailsView which displays images from a database. Pretty easy isn't it?


Notes

Improvements on the technique is to cache the images being displayed so that the application won't need to query for the image . Another thing to note should you use the method ExecuteScalar, it will give you the entire content of the data. While there's nothing wrong with that, getting a huge row size(uncommon for bitmaps) at one time will leave your user unsatisfied with the performance. What we can do is use ExecuteReader and pass the enum parameter SequentialAccess so that it will be given to us as a stream of bytes and use the GetBytes method to access the data into the buffer array.

 

With the said technique and a combination of GDI+ graphics we can integrated graphical reports to our ASP.NET application. Not just static images but dynamic one, like the ones in Cold Fusion were the report can be graphical and is even animated.


We'll continue with our endeavor into graphics using ASP.NET when we'll create our own CAPTCHA(TM) control using the same technique.

 

 

kick it on DotNetKicks.com
Posted by bonskijr | 6 comment(s)
Filed under:

Default Values

The Basics

In VB I just love optional parameters, it's a shame that C# doesn't have that feature(instead of overloading methods of different parameters.) In T-Sql we can simulate this optional parameters in our stored procedures by using default values:
PROC dbo.pr_GetInvoiceList (@PeriodMonth INT = 1, @PeriodYear INT = 2005)
AS
SELECT *
FROM InvoiceTable
WHERE PeriodMonth = @PeriodMonth AND PeriodYear =@PeriodYear


Then you can just execute your stored procedure sans the parameters:
EXEC pr_GetInvoiceList
The purpose of the default parameter values is to make sure our stored procedure will run even when there is no parameter assigned. Having said that however, the only time I find it is useful, is during testing.

An interesting application

I have used stored procs extensibly in my application, but I was kind of frustrated when a situation arises that not all parameters will be used for either an INSERT or UPDATE procedure. I use only a single stored proc to update for example any modification to our MedicalReports table. An update might contain just updating the last modified column or update both last modified column and approvedby column. I have to resort to client side updates to handle both scenario and find it cumbersome to maintain. Then I was toying the idea of default values, but instead of having actual values, I would instead default all the values to NULL.
PROC dbo.pr_UpdateMedRep(@RepId INT, @LastModBy VARCHAR(10)=NULL, @ApprovedBy VARCHAR(10)=NULL)

The body of our stored proc will be like this:

UPDATE MedicalReportsTable SET
LastModBy = ISNULL(@LastModBy, LastModBy),
ApprovedBy = ISNULL(@ApprovedBy, ApprovedBy)
WHERE MedicalReportsTable.RepId = @RepId


Now if I want to update only the LastModify column:
pr_UpDateMedRep 1, 'User1'

Updating only ApprovedBy column:
pr_UpDateMedRep 1, NULL, 'User2'

Without worrying that either column will be updated when it shouldn't be and I'm still using stored procedure to do so.

Posted by bonskijr | 13 comment(s)
Filed under:

Image in DetailsView

One of our forum members in MS Phil  has recently inquired if it's possible to display image from the database using a DetailsView control. Out of the box, it can't since the Imagefield rowfield only accepts a url for the image, but with the help of an auxilliary page we can.

Let's have our SqlDataSource setup first:

<asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:HissDBConnectionString %>"

SelectCommand="SELECT [Emp_No], [First_Name], [Last_Name] FROM [HR_EmployeeMaster] WHERE Company=@Company">

<SelectParameters>

<asp:ControlParameter Name="Company" ControlID="DropDownList1" PropertyName="SelectedValue" />

</SelectParameters>

</asp:SqlDataSource>

I have a select statement which will query all employees depending on their company which will be selected on my Dropdownlist control.

Then our DetailsView control:

<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False" DataKeyNames="Emp_No"

DataSourceID="SqlDataSource1" Height="50px" Width="125px" AllowPaging="True">

<Fields>

 

 <asp:BoundField DataField="Emp_No" HeaderText="Emp_No" ReadOnly="True" SortExpression="Emp_No" />

 <asp:BoundField DataField="First_Name" HeaderText="First_Name" SortExpression="First_Name" />

<asp:BoundField DataField="Last_Name" HeaderText="Last_Name" SortExpression="Last_Name" />

</Fields>

</asp:DetailsView>

Now that we have our DetailsView up and running, it's time to make our auxilliary page; whose function will be the placeholder of our streamed image. Create another web page, let's name it DisplayImage.aspx.  In our code-behind and upon the Page_Load event we typed the following:

VB.NET:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load


Dim EmpNo As String= Request.QueryString("emp_no")
Dim commandString As String = "SELECT Photo FROM HR_EmpPhoto WHERE Emp_No= @empNo "
Using
myConn As New SqlConnection(ConfigurationManager.ConnectionStrings("HissDBConnectionString").ToString())
Dim myCommand As SqlCommand()
Dim myReader As SqlDataReader

myConn.Open()
myCommand = myConn.CreateCommand()
myCommand.Parameters.Add("@empNo",SqlDbType.Varchar,10) .Value = EmpNo
myReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection)

 
If myReader.Read() Then

        Dim imgBuffer As Byte() = CType(myReader.Item("Photo"), Byte())

      Response.ContentType = "image/jpeg"
      Response.BinaryWrite(imgBuffer)

End If

   myConn.Close()

End Using

End Sub

 C#

protected void Page_Load(object sender, EventArgs e)

{

String empNo = Request.QueryString["emp_no"];
String commandString = "SELECT Photo FROM HR_EmpPhoto WHERE Emp_No=@empNo";

using (SqlConnection myConn = new SqlConnection (ConfigurationManager.ConnectionStrings["HissDBConnectionString"].ToString()))

{

    myConn.Open();
    SqlCommand
myCommand = myConn.CreateCommand();

    myCommand.Parameters.Add("@empNo",SqlDbType.Varchar,10) .Value = EmpNo

    SqlDataReader myReader = myCommand.EndExecuteReader(CommandBehavior.CloseConnection);

if (myReader.HasRows ()){
    byte[] imgBuffer = (byte[]) myReader["Photo"];
    Response.ContentType = "image/jpeg";
    Response.BinaryWrite (imgBuffer);
}

myConn.Close();

}

}

The query will try see if the there is a photo available for our employee. If it exists we then convert the content of our reader into a byte array so that we can pass it to our Response object's method BinaryWrite()  as an output stream. The content type should be properly configured so that the user agent(browser) knows how to deal with it, in case of our image, knows how to display it. However, I was testing it even with a wrong content type "image/gif" or no content type at all but the browser was able to display it correctly. Nevertheless, one should use the correct content type.

We can test our page by manually type at the address bar: http://localhost/ImageInDetailsView/DisplayImage.aspx?emp_no=7342. If the image is displaying then we proceed to add our ImageField row in our DetailsView.

Add the following as the first row of our DetailsView:

<asp:ImageField HeaderText = "Photo" DataImageUrlField="Emp_No"

DataImageUrlFormatString="~\DisplayImage.aspx?emp_no={0}">

<ControlStyle Width="150px" />

</asp:ImageField>

We are binding our DataImageUrlField to the Emp_No column from our datasource, from here we're going to get the parameter to pass to our auxilliary page. In our DataImageUrlFormatString we set it to our DisplayImage.aspx then pass in our querystring the parameter emp_no={0}, this tells the field to get the first column from our datasource so it depends on what column number you placed your bound column. We limit the size of our image to 150px only so that the display of picture is constant, no matter the size of the picture. The same technique can also be used in a GridView control.

That's all there is to it. Next time I'm going to show you how to do without the auxilliary page.

 UPDATED: Gavin noted that the code is open to sql-injection attack, so changed the code to a parameterized query. Thanks for pointing it out

Posted by bonskijr | 3 comment(s)
Filed under: