LLBLGenProDataSource + asp.net FormView + programmatically filling dropdownlist = nightmare!!

Posts   
 
    
trevorg
User
Posts: 104
Joined: 15-Nov-2007
# Posted on: 18-Mar-2010 00:24:57   

This is more an issue with asp.net itself (the FormView specifically), but I was hoping someone might have some experience with this area and can offer some advice.

I have a UserControl, containing a FormView, containing a DropDownList. The FormView is bound to an LLBLGenProDataSource control. I don't want to use a separate LLBLGenProDataSource for each drowdonwlist on the FormView (I just prefer code whenever possible)....so I used existing code I had for loading the dropdownlists.

This resulted in my first nightmare: http://stackoverflow.com/questions/2435185/not-possible-to-load-dropdownlist-on-formview-from-code-behind

....which I eventually found a workaround for.

Then, I got an exception when trying to insert a new item into the FormView. If you look at my page_load code you'll see I apply a filter based on the pk value from the queryString. If it is not specified, then the LLBLGenProDataSource contains 0 items. If the FormView contains only textboxes, this is fine, and it behaves as expected (can successfully add a new item and save successfully.

However, if it includes DropDownLists, then (it seems) I have to append a new LLB entity [Me.llbDataSource.EntityCollection.Add(New MetricEntity)], otherwise when the ddl's are filled from frmEdit_ItemCreated, the ddl control does not yet exist (a "quirk" of the FormView). So fine, I made that fix. Then some more things went wrong and I fixed those.

So I now seem to be at the point of it working, but one last thing is going wrong:

    Private Sub cmdSave_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles cmdSave.Click
        If Me.frmEdit.CurrentMode = FormViewMode.Edit Then
            Me.frmEdit.UpdateItem(False)
        ElseIf Me.frmEdit.CurrentMode = FormViewMode.Insert Then
            Me.frmEdit.InsertItem(False)
            Response.Redirect("~/MetricDetail.aspx?MetricCode=" & Me.llbDataSource.EntityCollection(0).Fields("MetricCode").CurrentValue.ToString)
        End If
    End Sub

Me.llbDataSource.EntityCollection(0).Fields("MetricCode").CurrentValue.ToString is returning Nothing, although the data is in fact being inserted into the database. Why is the entity seemingly not populated with the data, yet it seems to have the ability to read it from somewhere and insert it??

So, getting to my point: 1. Why can't I read: Me.llbDataSource.EntityCollection(0).Fields("MetricCode").CurrentValue.ToString

  1. Does anyone reading this happen to have any experience with this scenario (loading DropDownLists on a FormView programatically)? The order of events under different scenarios is really confusing and I had to handle so many edge cases....am I perhaps simply doing something wrong? If I was to surrender and use separate LLBLGenProDataSource controls for each DropDownList, would that presumably work properly?

Here is all my code:

<llblgenpro:LLBLGenProDataSource ID="llbDataSource" runat="server" MaxNumberOfItemsToReturn="1" 
    DataContainerType="EntityCollection"  
    EntityCollectionTypeName="Talisman.CorpScorecard.Domain.CollectionClasses.MetricCollection, Talisman.CorpScorecard.Domain" 
    LivePersistence="True" ThrowExceptionOnIllegalFieldInput="true" />

<asp:FormView ID="frmEdit" DataKeyNames="MetricCode" runat="server"  
DefaultMode="Edit" DataSourceID="llbDataSource" Cellpadding="0" >
<EditItemTemplate>
    <fieldset>  
        <legend>Metric</legend>
    <ol>
        
        <li>
        <label for="MetricCode">Metric Code</label>
        <asp:textbox id="MetricCode" runat="server" Text='<%# Bind("MetricCode") %>' />
        </li>
        <li>
        <label for="MetricName">Metric Name</label>
        <asp:textbox id="MetricName" runat="server" Text='<%# Bind("MetricName") %>' />
        </li>
        <li>
        <label for="Definition">Definition</label>
        <asp:textbox id="Definition" runat="server" Text='<%# Bind("Definition") %>' />
        </li>
        <li>
        <label for="ParentMetricCode">Parent Metric</label>
        <asp:DropDownList ID="ParentMetricCode" runat="server" SelectedValue='<%# Bind("ParentMetricCode") %>' /> 
        </li>
        <li>
        <label for="OwnerUserName">Owner User Name</label>
        <asp:DropDownList ID="OwnerUserName" runat="server" SelectedValue='<%# Bind("OwnerUserName") %>' /> 
        </li>
        <li>
        <label for="IsConfidential">Confidential?</label>
        <asp:CheckBox ID="IsConfidential" Checked='<%# Bind("IsConfidential") %>' runat="server" />
        </li>
        <li>
        <label for="Comments">Comments</label>
        <asp:textbox id="Comments" runat="server" Text='<%# Bind("Comments") %>' />
        </li>
    </ol>
    </fieldset> 
</EditItemTemplate>
</asp:FormView>

<asp:Button runat="server" ID="cmdSave" Text="Save" />
Partial Public Class MetricEdit
    Inherits System.Web.UI.UserControl

    'TODO: Most of this should be moved to a subclass:
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        If IsPostBack Then Exit Sub
        'this label text fixing won't work until the html labels are changed to asp:label's
        'Dim allControls As System.Collections.Generic.List(Of Control) = Talisman.Core.Web.WebUtils.ChildControls(Me.frmEdit, GetType(Label))

        Dim qFilter As New PredicateExpression
        qFilter.AddWithAnd(MetricFields.MetricCode = Request.QueryString("MetricCode"))

        Me.Filter = qFilter
        Me.frmEdit.DataBind()
        ' If there are no items, OR, if the item is a newly created entity (from frmEdit_ItemCreated).....
        If Me.frmEdit.DataItemCount = 0 OrElse Me.llbDataSource.EntityCollection(0).IsNew Then
            If Request.QueryString(frmEdit.DataKeyNames(0)) <> "" Then Throw New System.Exception(String.Format("Record not found for {0} = {1}", frmEdit.DataKeyNames(0), Request.QueryString(frmEdit.DataKeyNames(0))))
            If frmEdit.CurrentMode <> FormViewMode.Insert Then Me.frmEdit.ChangeMode(FormViewMode.Insert)
            'LoadDropdowns()
        ElseIf frmEdit.DataItemCount = 1 Then
            Me.frmEdit.ChangeMode(FormViewMode.Edit)
        End If

    End Sub

    Private Sub frmEdit_ItemCreated(ByVal sender As Object, ByVal e As System.EventArgs) Handles frmEdit.ItemCreated
        If Me.llbDataSource.EntityCollection.Count = 0 Then
            Me.frmEdit.ChangeMode(FormViewMode.Insert)
            Me.llbDataSource.EntityCollection.Add(New MetricEntity)
            Me.frmEdit.DataBind()   ' This will re-invoke frmEdit_ItemCreated() now tih a New entity in the llbDataSource....ugh. (todo: template change)
        Else
            LoadDropdowns()
        End If
    End Sub


    Private Sub LoadDropdowns()
        WebUtils.BindListControl(Of MetricEntity)(Me.frmEdit, MetricFields.ParentMetricCode.Name, DataGateway.getMetricCollection, MetricFields.MetricCode, MetricFields.MetricName, True)
        WebUtils.BindListControl(Of SysUserEntity)(Me.frmEdit, MetricFields.OwnerUserName.Name, DataGateway.getSysUserCollection, SysUserFields.UserName, SysUserFields.UserFullName, True)
    End Sub

    Private Sub cmdSave_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles cmdSave.Click
        If Me.frmEdit.CurrentMode = FormViewMode.Edit Then
            Me.frmEdit.UpdateItem(False)
        ElseIf Me.frmEdit.CurrentMode = FormViewMode.Insert Then
            Me.frmEdit.InsertItem(False)
            Response.Redirect("~/MetricDetail.aspx?MetricCode=" & Me.llbDataSource.EntityCollection(0).Fields("MetricCode").CurrentValue.ToString)
        End If
    End Sub


        Public Overloads Shared Sub BindListControl(Of theLLBLgenEntityType As EntityBase) _
                            (ByVal formView As FormView, _
                             ByVal listControlName As String, _
                            ByVal theCollection As CollectionCore(Of theLLBLgenEntityType), _
                            ByVal DataValueField As EntityField, _
                            ByVal DataTextField As EntityField, _
                            ByVal IncludeEmptyItem As Boolean)

            If formView.CurrentMode = FormViewMode.Edit OrElse formView.CurrentMode = FormViewMode.Insert Then
                Dim theListcontrol As System.Web.UI.WebControls.ListControl
                theListcontrol = CType(formView.FindControl(listControlName), System.Web.UI.WebControls.ListControl)

                'If theListcontrol.Items Is Nothing Then theListcontrol.Items = New ListItemCollection

                If theListcontrol.Items.Count = 0 Then
                    For Each entity As theLLBLgenEntityType In theCollection
                        theListcontrol.Items.Add(New ListItem(entity.Fields(DataTextField.Name).CurrentValue.ToString, entity.Fields(DataValueField.Name).CurrentValue.ToString))
                    Next
                End If
                If IncludeEmptyItem Then theListcontrol.Items.Insert(0, New ListItem("", ""))
            End If
        End Sub


daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 18-Mar-2010 02:53:41   

I have faced that situation. The problem is that where you are trying to access such code is to late. What I do normally is (to simplify the explanation I show the code):

public partial class Controls_EditOrder : System.Web.UI.UserControl, IEditControl
{
    ...
    private string _pkValuesAfterInsert = string.Empty;
        ... 

protected void _OrderDS_PerformWork(object sender, PerformWorkEventArgs2 e)
{
    // as we're using a formview, there's just 1 entity in the UoW.
    OrderEntity entityToProcess = null;
    List<UnitOfWorkElement2> elementsToInsert = e.Uow.GetEntityElementsToInsert();
    if(elementsToInsert.Count > 0)
    {
        // it's an insert operation. grab the entity so we can determine the PK later on. 
        entityToProcess = (OrderEntity)elementsToInsert[0].Entity;
    }
    using(DataAccessAdapter adapter = new DataAccessAdapter())
    {
        e.Uow.Commit(adapter, true);
    }
    
    if(entityToProcess != null)
    {
        // store the PK values so a redirect can use these. 
        _pkValuesAfterInsert = "&OrderId=" + entityToProcess.OrderId;

    }
}
/// <summary>
/// Handles the ItemCommand event of the frmEditOrder control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Web.UI.WebControls.FormViewCommandEventArgs"/> instance containing the event data.</param>
protected void frmEditOrder_ItemCommand(object sender, FormViewCommandEventArgs e)
{
    if(!Page.IsValid)
    {
        return;
    }
    
    switch(e.CommandName)
    {
        ...
        case "InsertAndView":
            frmEditOrder.InsertItem(true);
            Response.Redirect("~/ViewExisting.aspx?EntityType=" + (int)EntityType.OrderEntity + _pkValuesAfterInsert);
            break;
        ...
    }
}

Hope it was clear. Please let us know if you made it.

David Elizondo | LLBLGen Support Team
trevorg
User
Posts: 104
Joined: 15-Nov-2007
# Posted on: 18-Mar-2010 17:30:33   

Well, I'm using LivePersistence="True" (which I think is what causes me to not have to use the _PerformWork....which I would rather avoid.)

I'm trying to find a pattern I can use everywhere that both: a) minimizes amount of code b) minimizes use of data controls, etc.

I want some form of two-way databinding, so I don't have to code a formLoad and formUnload function for every form. At the same time, I prefer to avoid using data controls whenever possible (for the automatic two-way databinding, this compromise is worth it)

As is, the code I have in here is getting way too messy anyways and is almost entirely devoted to working around the brutal behaviour of the FormView control. Longer term, I'm going to investigate whether something like AutoMapper http://www.codeplex.com/AutoMapper might be able to perform my form loading/unloading of webforms...I can't seem to find anyone who is using it for that, and figuring out how to write the layer to properly handle the various asp.net webform controls will surely take some time to figure out. (Anyone reading have any AutoMapper experience?)

In the short run I'd still like to be able to get this to work....I just can't understand why, after the:

frmEditOrder.InsertItem(true)

...I am unable to see the actual values in the entity, when I can see in the LLB logging that they are being inserted. If I could at least get that working I would be ok for now.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 19-Mar-2010 05:43:39   

It's just that there is too late in that method. Please try these events: EntityInserting EntityInserted EnttiyUpdating EntityUpdated

David Elizondo | LLBLGen Support Team
trevorg
User
Posts: 104
Joined: 15-Nov-2007
# Posted on: 19-Mar-2010 10:25:55   

Well....

It's just that there is too late in that method. Please try these events:
EntityInserting
EntityInserted
EnttiyUpdating
EntityUpdated 

I've "please try"d everything. I realize I ask a lot of questions on here, and I like to think when I finally figure out the way to do things I post the answer.

I have tried, seriously, I have really tried for hours. I know this is maybe (likely) not "your" responsibility, it is almost surely bugs in the MS FormView.

I'm just getting depressed is all. When I say I have a serious databinding problem and you say: check out:

EntityInserting
EntityInserted
EnttiyUpdating
EntityUpdated 

You know what, I l'm pretty sure I checked that out.

I've been a huge advocate of the LLBLGen framework for many years, but lately I seem to not be able to do the simplest things (which perhas most times is a result of the .Net Framework, in general)

Fuck it.

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 19-Mar-2010 11:20:07   
  1. Does anyone reading this happen to have any experience with this scenario (loading DropDownLists on a FormView programatically)?

I haven't taken this route before, but if you want us to help dig down on this, would you please attach a simple repro project so we can debug and trace this issue for you. Just a simple repro and better to be on Northwind. Most probably it has to do with the sequence of ASP.NET and databindig events.

The order of events under different scenarios is really confusing and I had to handle so many edge cases....am I perhaps simply doing something wrong? If I was to surrender and use separate LLBLGenProDataSource controls for each DropDownList, would that presumably work properly?

Sure, this route is the common one to take and it works smoothly enough that I wonder why did you avoid it in the first place.

trevorg
User
Posts: 104
Joined: 15-Nov-2007
# Posted on: 19-Mar-2010 18:37:56   

Sorry....getting frustrated. rage

The reason I'm trying to do it programatically is because I'm trying to write somewhat of an app framework where all these elements are filled by global functions driven by metadata, ie:

So, any given SysTable.EditScreen will have one FormView, and the DropDownLists will be populated according to SysTableColumn.IsDefaultPropertyForFK, and all sorts of other nice things like this that can naturally be derived from database relations. The default aspx UI is built from custom LLB templates. I could generate the data controls from within the template, but it is less flexible, plus, I only get "first time" generation, after I actually deploy a generated template, I can't regenerate (as layout is customized once deployed).

I think for now I am going to give up and just use individual data controls for each DDL, maybe I'll look into creating and binding them at runtime rather than filling the dropdowns programatically.