views:

1228

answers:

5

I have a number of similarly structured FormViews. In an effort to avoid duplicate markup, I've created a master page that contains the FormView, and placed a ContentPlaceHolder inside the FormView. The specific databound controls - which are the only thing that change from page to page - are then on the page that uses that master page.

So I have a master page that looks something like this:

<%@ master ... %>
...
<form runat=server>
...
   <asp:formview runat="server" ... >
      <edititemtemplate>
         ... Lots of common markup ...
         <asp:contentplaceholder id='FormRows' runat='server' />
         ... Lots more common markup ...
       </edititemtemplate>
    </asp:formview>
...
</form>

and a page using that master page that looks something like this:

<%@ page masterpagefile="Form.Master" ... %>
<asp:content contentplaceholderid="FormRows" runat="server" >
   ...
   <p>
     Field One: 
     <asp:textbox runat=server text='<%#Bind("Field1")%>' id='Field1' />
   </p>
   <p>
     Field Two: 
     <asp:textbox runat=server text='<%#Bind("Field2")%>' id='Field2' />
   </p>
   ...
</asp:content>

With an existing record, the FormView sees through to the databound controls (Field1, etc) and populates them with the correct data. But when inserting or updating, it doesn't see them, and they're not included in the insert or update. In the FormView_ItemInserting event, e.Values is empty; likewise in the FormView_ItemUpdating event, e.NewValues is empty.

So:

  1. Is there a way to provoke the FormView on the master page to see through to the databound controls inside the ContentPlaceholder?

  2. Failing that, is there a straightforward way of identifying controls that are databound with <%#Bind(...)%> so that I can add them manually to the values bag?

A: 

Where do you have server form tag? May be in content place holder insted of master page, so your values not send to server page after submit

pocheptsov
No, the form tag is on the master page.
Herb Caudill
+2  A: 

There are a couple of things that come to mind why this setup will not work and may lead to more code than markup.

If you have a datasource defined in the master page it will not handle the different data bound controls from each page without adding more logic to the master page to change the query etc.

All form views will be coupled together increasing the complexity of changes down the road

I would go with separate pages for each FormView reducing the complexity of code, debugging and the ability to change

Just my two cents

Jon
I concur with Jon.
Mufasa
A: 

You might be able to do something like this...

Define an interface for your "data pages" that has a method signature that returns a bindable data source..

public interface IFormViewChild {
   IEnumerable GetFormData();
}

Then you can have your "data pages" implement that interface...

public class ChildDataPage : Page, IDataPage {
   public IEnumerable GetFormData() {
      // code to return stuff here
   }
}

Finally, in your masterpage's Load() event...

if (Page is IFormViewChild) {
   myFormViewControl.DataSource = ((IFormViewChild)Page).GetFormData();
   myFormViewControl.DataBind();
}

Please keep in mind that this is all psudo code typed directly into this web form editor.. so it's probably wrong. But it might not be :)

datacop
The problem is not, strictly speaking, getting the FormView to databind through the master page boundary. Databinding works - in one direction at least (displaying data). Where it has trouble is getting the values back out of the controls databound with <%#Bind(...)%>.
Herb Caudill
A: 

I think this will prove difficult, if not possible; in fact I'm surprised that the databinding works at all!

You may want to try a different method of encapsulating your FormView control.

You could try placing the FormView control in an .ascx control with a PlaceHolder where you now have the ContentPlaceHolder.

Then on each ASPX page, you could have a mirror ASCX page that contains the filler for the placeholder. You could give them the same names (Page1.aspx uses Page1.ascx) or set up a naming convention like Page1-Content.ascx, so that your FormView ascx would figure out what it's filler control is named, use Page.LoadControl() to load the control by path, and plug that content in during the Init phase.

Now, your content controls have the advantage of being able to have public properties, so you could bind to those public properties, and have the properties shuttle the data to and from the appropriate server controls in the filler .ascx file.

Unfortunately it's double the files (because of the ASPX and ASCX required for each page) but fairly work-unintensive compared to the alternative (duplicating all that code)

Of course, you haven't told us what all your common markup is, but your common markup could go into a CommonMarkupHeader.ascx and CommonMarkupFooter.ascx as well and included on each page's unique FormView.

David
Thanks - I don't think I'll go that route, but you've understood the problem and given it some thought. I'll outline my current solution in a separate response.
Herb Caudill
A: 

Here's a provisional solution - not elegant, but it works. In the code-behind for Form.Master I have something along these lines:

    Private Sub FormView1_ItemInserting(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.FormViewInsertEventArgs) Handles FormView1.ItemInserting
        ManuallyAddValues(e.Values)
    End Sub

    Private Sub FormView1_ItemUpdating(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.FormViewUpdateEventArgs) Handles FormView1.ItemUpdating
        ManuallyAddValues(e.NewValues)
    End Sub

    Private Sub ManuallyAddValues(ByRef Values As IOrderedDictionary)
        For Each Field As Core.Field In FormView1.DataSourceControl.IncludedFields
            If Values(Field.Name) Is Nothing Then
                Dim DataboundControl As Control = FormView1.FindControl("FormRows").FindControl(Field.Name)
                Values.Add(Field.Name, GetValue(DataboundControl))
            End If
        Next
    End Sub

This isn't so elegant because

  1. I have to know the names of all databound controls
  2. This relies on the assumption that the ID of each control matches the fieldname
  3. The 'GetValue' function (not included here) is a clumsy solution: it checks for various types (textbox, dropdownlist, checkbox, etc.) and gets the string value from the appropriate property (textbox.text, dropdownlist.selectedvalue, checkbox.checked, etc.).

I'd still love to at least have a way of knowing what's bound with the '<%#Bind("Foo")%>' syntax and getting that information directly.

Herb Caudill