Skip to main content

FormView Oddities and Using EditItemTemplate to Insert

Every now and then I come across those weird, annoying issues with ASP.NET which very few people seem to have had, and no one seems to have an answer.  It's especially frustrating because I can spend hours (I've spent 4 hours today tracking this one down) trying to figure out what's going on.  Maybe one of the gifts of being a programmer comes the ability to spend 4 hours researching, testing, and focusing on one problem.

In any case, we use FormViews pretty regularly in our ASP.NET applications.  For those who don't know, a FormView is very similar to the DataGrid and GridView controls.  You can place your form fields in either an ItemTemplate, EditItemTemplate or IntertItemTemplate.  This lets you bind the FormView to a DataSource and simple use Bind() to populate the values and update them back into the database.  We've been able to create pages with no, or very, very little code in the code behind using this method, and after a bit of a learning curve I definitely think this is the way to go for building sites.

Our only complaint is that you essentially duplicate form fields for each template.  For example, you may want to make the user name field read-only in the EditItemTemplate, but editable in the InsertItemTmplate. 

We (well, my wife anyway. . . Why are women always smarter than men?) came up with a solution where you can use the EditItemTemplate for both updates and inserts.  Essentially the SelectCommand will always return a row, even if the primary key value passed in is null/0.

So, we'll have a FormView for adding and editing builders which looks like this:

FormView

With the aspx code having:

   1:      <asp:FormView ID="fvContactInfo" runat="server" DataSourceID="odsContact" DataKeyNames="BusinessID,UserName"


   2:          DefaultMode="Edit" Width="100%">


   3:          <EditItemTemplate>


   4:              <%-- Business Name --%>


   5:              <div class="inputRow">


   6:                  <asp:Label ID="lblBusinessName" runat="server" Text="Business Name" CssClass="inputLabel"


   7:                      AssociatedControlID="txbBusinessName" />


   8:              </div>


   9:              <div class="inputRow">


  10:                  <asp:TextBox ID="txbBusinessName" runat="server" MaxLength="150" Text='<%#Bind("BusinessName") %>'


  11:                      CssClass="inputBox oneColumn" />


  12:                  <asp:RequiredFieldValidator ID="rqvBusinessName" ValidationGroup="CreateUserWizard1"


  13:                      runat="server" ControlToValidate="txbBusinessName" ErrorMessage="Business Name is required."


  14:                      Text="*" ToolTip="Business Name is required." />


  15:              </div>


  16:              <%-- Buttons --%>


  17:              <div class="inputRow" id="rowButtons" runat="server">


  18:                  <asp:Button ID="btnSave" runat="server" Text="Save" CommandName="Update" />


  19:                  <asp:Button ID="btnBack" runat="server" Visible="false" Text="Go back" />


  20:              </div>


  21:          </EditItemTemplate>


  22:      </asp:FormView>


  23:   


  24:  <%-- Data Source --%>


  25:  <asp:ObjectDataSource ID="odsContact" runat="server" SelectMethod="GetData" TypeName="BCDatasetTableAdapters.BusinessTableAdapter"


  26:      UpdateMethod="Update">


  27:      <SelectParameters>


  28:          <asp:Parameter Name="UserName" Type="String" />


  29:      </SelectParameters>


  30:      <UpdateParameters>


  31:          <asp:Parameter Name="BusinessName" Type="String" />


  32:          <asp:Parameter Name="UserName" Type="String" />


  33:          <asp:Parameter Name="BusinessID" Type="Int32" />


  34:      </UpdateParameters>


  35:  </asp:ObjectDataSource>




And a simple SQL procedure:




   1:  CREATE PROCEDURE [dbo].[sp_bc_BusinessSelect]


   2:  (


   3:      @UserName nvarchar(256)


   4:  )


   5:  AS


   6:      SET NOCOUNT ON;


   7:  BEGIN


   8:      IF @UserName is null 


   9:          BEGIN    


  10:              SELECT 


  11:                  TOP 1


  12:                  -1 BusinessID, 


  13:                  null BusinessName, 


  14:                  '' UserName


  15:              FROM


  16:                  bc_Business


  17:          END


  18:      ELSE


  19:          BEGIN


  20:              SELECT     


  21:                  TOP 1 


  22:                  IsNull(bc_Business.BusinessID, -1) BusinessID, 


  23:                  bc_Business.BusinessName


  24:                  bc_Business.UserName


  25:              FROM         


  26:                  bc_Business 


  27:              WHERE 


  28:                  UserName = @UserName


  29:              ORDER BY 


  30:                  BusinessName


  31:          END


  32:  END




While this is pretty straightforward, there is one problem which came up today.  When there are no builders at all in the builder table, the stored procedure will not return any rows (including the blank row your FormView expects).



That's really due to the fact that in lines 15 and 16 I specify a FROM table, though it's always possible that table is empty.  A better solution is to rewrite the query to look like this:




IF @UserName is null 


        BEGIN    


            SELECT 


                TOP 1


                -1 BusinessID, 


                null BusinessName, 


                '' UserName


        END


    ELSE



What's actually interesting is how the FormView handles the child controls in the Edit template when no data is returned.  When you attempt to run the following code:




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


   2:   


   3:      odsContact.SelectParameters("UserName").DefaultValue = UserName


   4:      CType(fvContactInfo.FindControl("lblBusinessName"), Label).Text = "Enter new business name here:"


   5:   


   6:  End Sub




You would expect line 4 to return either a reference to the label or, at worst, Nothing for the control, even if you place it in the FormViews DataBound or Unload event.



You actually receive an error that the FormView itself fvContactInfo threw a NullReferenceException.  Whenever you are binding a FormView to have the Edit mode handle both inserts and updates.



Accounting for the return of a blank row though, this is a great method to reduce the need to duplicate form fields in all templates.  The same template above which used Edit, Item and Insert templates would look similar to the following:




   1:  <asp:FormView ID="fvContactInfo" runat="server" DataSourceID="odsContact" DataKeyNames="BusinessID"


   2:      DefaultMode="Edit" Width="100%">


   3:      <EditItemTemplate>


   4:          Business Name:


   5:          <asp:TextBox ID="BusinessNameTextBox" runat="server" Text='<%# Bind("BusinessName") %>'>


   6:          </asp:TextBox><br />


   7:          User Name:


   8:          <asp:Label ID="UserNameLabel" runat="server" Text='<%# Bind("UserName") %>'>


   9:          </asp:Label><br />


  10:          <asp:LinkButton ID="UpdateButton" runat="server" CausesValidation="True" CommandName="Update"


  11:              Text="Update">


  12:          </asp:LinkButton>


  13:          <asp:LinkButton ID="UpdateCancelButton" runat="server" CausesValidation="False" CommandName="Cancel"


  14:              Text="Cancel">


  15:          </asp:LinkButton>


  16:      </EditItemTemplate>


  17:      <InsertItemTemplate>


  18:          Business Name:


  19:          <asp:TextBox ID="BusinessNameTextBox" runat="server" Text='<%# Bind("BusinessName") %>'>


  20:          </asp:TextBox><br />


  21:          User Name:


  22:          <asp:TextBox ID="UserNameTextBox" runat="server" Text='<%# Bind("User Name") %>'>


  23:          </asp:TextBox><br />


  24:          <asp:LinkButton ID="InsertButton" runat="server" CausesValidation="True" CommandName="Insert"


  25:              Text="Insert">


  26:          </asp:LinkButton>


  27:          <asp:LinkButton ID="InsertCancelButton" runat="server" CausesValidation="False" CommandName="Cancel"


  28:              Text="Cancel">


  29:          </asp:LinkButton>


  30:      </InsertItemTemplate>


  31:      <ItemTemplate>


  32:          BusinessName:


  33:          <asp:Label ID="BusinessNameLabel" runat="server" Text='<%# Bind("BusinessName") %>'>


  34:          </asp:Label><br />


  35:          UserName:


  36:          <asp:Label ID="UserNameLabel" runat="server" Text='<%# Bind("UserName") %>'></asp:Label><br />


  37:          <asp:LinkButton ID="EditButton" runat="server" CausesValidation="False" CommandName="Edit"


  38:              Text="Edit">


  39:          </asp:LinkButton>


  40:      </ItemTemplate>


  41:  </asp:FormView>




Which quickly becomes a nightmare to update.  Have fun!



Peace,

+Tom

Comments

Anonymous said…
Nerd. :)
ChrisS said…
Brother:
Good example, but u need a code formatter. Check out CopySourceAsHTML. That is the bomb for lifting your junk from VS.

Keep it fresh, I'm a reader!

Popular posts from this blog

Using an Array of Objects in C++

 I've been programming for years (over 35 at this point, which is crazy  to think about). My career right now is much more Software Architecture, and much less Software Developer, but I still get some time to write out GraphQL APIs in TypeScript, Vue 3 UIs, GitLab pipelines, and just generally making "big" decisions and helping make them a reality. It's nice every now and then to come across different articles and ideas that get me to remember life in college when I was using C++. Who would have thought C++ was the "hot new thing" right now (though I suppose it's more like Rust and Go, both great languages as well). One of the things I find frustrating with most technical posts is where they focus on the "how do I build an app" and not so much on "how do I do this one slightly useful thing". I figured I'd throw one together what was front of mind, using user attributes for permissions (i.e., Attribute Based Access Control - ABAC) ...

Red-Gate SQL Compare

Every now and then I come across a program that becomes so ingrained in my daily work that I hardly know how I'd get by without it.  I'll probably break down a couple over the next few days, but for database work, I have never found anything as good as Red Gate's SQL Compare and SQL Data Compare .  Essentially these tools let you compare two SQL Server databases (all objects, users, permissions, functions, diagrams, anything) and update changes to whichever database you want.  This is amazingly useful for deploying database changes to a test or production environment (do it to production with ridiculous care, even though it will generate a SQL Script for you and run all updates in one transaction), and making sure everything is synchronized. For releases we can just generate the compare script, confirm that the changes match the updates we want to go out, and store it all in one place with the release details.  This is true for both the structure and the d...

Kids Activities

I find myself often in a situation where it's some morning, I have the kids for the afternoon, and I'm not sure what to do with them. We could go to a movie, or play Legos, but living near Washington, DC, I want the kids to love the museums as much as I do, or to see what else is going on. This Sunday, while my wife was travelling, I took the kids to the Chocolate Festival in Old Town Fairfax. I didn't even know there wad an Old Town Fairfax, much less a chocolate festival. It was okay overall, but the best was seeing any type of chocolate you could imagine, and letting the kids pick something for themselves and their teacher. For finding cheap or free stuff going on nearby with the kids, I have to say About.com has consistently been the best. I tried si.edu (the Smithsonian Website) which is also good, but a little hard to navigate, partly because they have so much going on. At About I did a search of what to do with my kids this weekend, and a bunch of items came ...