I'm currently working on a little dummy application to get my head around the new (awesome) model binding in WebForms. I've succesfully listed data both in a ListView and a FormView, however I'm stuck on updating the data.
I have a very simple FormView control:
<asp:FormView runat="server" ID="formViewOrderDetail"
ItemType="ModelBindingDummy.Models.Order" DataKeyNames="Id"
SelectMethod="GetOrder" UpdateMethod="UpdateOrder">
<ItemTemplate>
<p>Supplier Order Number: <%#: Item.SupplierOrderNumber %></p>
<asp:LinkButton Text="Edit" runat="server" ID="linkEdit"
CommandName="Edit" />
</ItemTemplate>
<EditItemTemplate>
<p>Supplier Order Number:
<asp:TextBox runat="server" ID="SupplierOrderNumber"
Text='<%#: Item.SupplierOrderNumber %>' />
</p>
<asp:LinkButton Text="Cancel" runat="server" CommandName="Cancel" />
<asp:LinkButton Text="Save" runat="server" CommandName="Update" />
</EditItemTemplate>
</asp:FormView>
My UpdateOrder method looks like this:
public void UpdateOrder(int id)
{
ModelBindingDummy.Models.Order item = GetOrder(id);
if (item == null)
{
ModelState.AddModelError("", String.Format("Item with id {0} was not found", id));
return;
}
TryUpdateModel(item);
//All data in the app is in-memory, so no need to actually update
//the item as it is only a reference
}
Now my problem is that the TryUpdateModel doesn't work. It simply ignores the value in the TextBox SupplierOrderNumber
I've tried adding a parameter to the update method, [Control] string supplierOrderNumber, and while that works, I'd really like to see a way to automatically map that value.
I've also seen solutions using the <asp:DynamicEntity /> control, but that seems somewhat overkill.
So my question is: Is it possible to get the TryUpdateModel method to map the TextBox value, or do I need to go all the way with the DynamicEntities control, or are there any other fancy solutions?
Thanks!
You need to use BindItem.SupplierOrderNumber instead of Item.SupplierOrderNumber.
You need to use DataItem.SupplierOrderNumber instead of Item.SupplierOrderNumber
There is another question like this out there with a better explanation but i can't find it.
Related
I can't figure out how to use model binding in web forms to populate a child collection of a view model. Suppose I have the following:
public class MeasurementViewModel
{
public string Name {get;set;}
public int MeasurementTypeId {get;set;}
public double Value {get;set;}
}
public class ExperimentViewModel
{
public bool ExperimentAorB {get;set;}
public List<MeasurementViewModel> Measurements {get;set;}
}
So there are two types of experiments, A or B, each of which require a different set of measurements. I'd like to do this all on one page so that the user selects the experiment type, and is then presented with the list of measurements to enter via simple text boxes. This is simple in MVC, but I'm stuck with web forms where it's not clear how to do this.
<asp:DropDownList ID="ddExperiment" runat="server" CssClass="form-control" AutoPostBack="true"
DataValueField="Id" DataTextField="Description"
SelectMethod="ddPartCode_GetData" />
<asp:FormView ID="Entry" RenderOuterTable="false" runat="server" DefaultMode="Insert" Visible="<%# !string.IsNullOrEmpty(ddExperiment.SelectedValue) %>"
ItemType="ExperimentViewModel" SelectMethod="Entry_GetItem" InsertMethod="Entry_InsertItem">
<EditItemTemplate>
<asp:TextBox ID="txtName" Text="<%# BindItem.Name %>" runat="server" />
<!-- ??? -->
</EditItemTemplate>
</asp:FormView>
The insert method of the FormView is like this:
public void Entry_InsertItem(ExperimentViewModel item)
I've tried various markup in the ??? section, like a ListView with a SelectMethod with this signature:
public IEnumerable<MeasurementViewModel> Measurements_GetData(
[Control("ddExperiment")] int experimentType) ...
That works to display the proper form, but on postback ExperimentViewModel.Measurements is always null. I've also tried insert methods on the list view and a repeater to no avail.
I can obviously do this manually, but I figured there must be a standard way to do this using model binding. Obviously I have to define a control that actually binds to the ExperimentViewModel.Measurements, but I'm not sure how to do that in this setup.
You can bind a child ListView by using DataSource="<%# Item.Measurements %>. Here is an example:
<asp:FormView ID="FormView" runat="server" RenderOuterTable="false" ItemType="ExperimentViewModel" DataKeyNames="Id" DefaultMode="Edit" SelectMethod="FormView_GetItem" UpdateMethod="FormView_UpdateItem">
<EmptyDataTemplate>
The record you requested does not exist.
</EmptyDataTemplate>
<EditItemTemplate>
<asp:ListView ID="ListView" runat="server" ItemType="MeasurementViewModel" DataKeyNames="Id" DataSource="<%# Item.Measurements %>">
...
</asp:ListView>
</EditItemTemplate>
</asp:FormView>
I'm not positive that the values will be populated on postback. You may have to repopulate the model in the insert method. I'm not sure exactly what you are trying to do in the child template. Hope this helps.
I have a gridview which has a few rows (each with a unique rowId), and each line has a FileUpload control, now everything works okay with FileUpload.
(my uploaded file database image can be seen below)
I have the download button, which also works okay, however I want to make this button invisible if no file exists for the corresponding row.
Nothing proper comes to my mind.
My button and FileUpload control:
<asp:TemplateField HeaderText="BatchList">
<EditItemTemplate>
<asp:ImageButton ID="ibt_Download" runat="server" src="Images/Download.png" CommandName="Download" CommandArgument='<%# Container.DataItemIndex %>' ></asp:ImageButton>
</EditItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="UploadBatchList">
<HeaderTemplate>
<asp:Label ID="lbl_Header" ToolTip="Upload Batch List" runat="server" Text="UBL"></asp:Label>
</HeaderTemplate>
<EditItemTemplate>
<asp:FileUpload ID="fu_UploadBatchList" runat="server" />
<asp:Button ID="btn_Upload" runat="server" Text="Upload" OnClick="btn_Upload_Click" />
</EditItemTemplate>
</asp:TemplateField>
This is how it looks on my gridView
When gridview is first created the green dots must not be visible if a file has been uploaded before.
My file database:
You can check some property of the data item (DocName in your case) if it contains a value (it might not work when copy-pasted, I'm a little bit improvising):
<asp:Button ID="btn_Upload" runat="server"
Text="Upload"
Visible='<% DataBinder.Eval(Container.DataItem, "DocName") == null %>
OnClick="btn_Upload_Click" />
Or you can create a function that will evaluate the visibility. See Mastering ASP.NET DataBinding for more.
Ok, this should be really easy, but I just don't have enough experience.
I need to throw a GridView on a WebForm and populate with a List, where Template is my class that has ID, Name, CreatedOn, etc... properties.
The GridView needs to display each Template Name as a link. The link should point to TemplateEdit.aspx page, with the following URL: TemplateEdit.aspx?ID={ID of Template}.
I also need a Delete link (preferably an image link), that should popup a Yes/No delete confirmation dialog.
I've actually done this before in 2005 or so, but I simply can't remember anymore.
Here's how you do it (borrowed the code from here to save some typing)
<asp:TemplateField HeaderText="Statement" SortExpression="Statement">
<ItemTemplate>
<asp:HyperLink ID="Link1" runat="server" NavigateUrl='<%# Bind("ID", "~/TemplateEdit.aspx?ID={0}") %>' Text="The Best Link"></asp:HyperLink >
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField>
<ItemTemplate>
<asp:ImageButton ID="DeleteButton" Runat="server" ImageUrl="~/images/delete.gif" OnClientClick="return confirm('Are you sure you want to delete this?');" ToolTip="Delete" CommandName="Delete" />
</ItemTemplate>
</asp:TemplateField>
didn't actually test it, but looks like it should work.
I'm missing something here...
I need to access the values of the controls on the item that is leaving editmode, thus firing ItemUpdating.
I found how to get the key value so I know which record in the database I have to update. The problem is that I can't seem to access the values of the controls on the editing row.
the EditTemplate contains a few checkboxes, a dropdown and textbox. I need to access these values so I can update the record.
When looking at the eventargs, nothing shows.
I think I'm overlooking something crucial here, any help would be handy.
You can also use "ListView.EditItem.FindControl("X")" to access control directly.
Protected Sub ListView_ItemUpdating(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.ListViewUpdateEventArgs) Handles ListView.ItemUpdating
Dim DropDownListddl As DropDownList = ListView.EditItem.FindControl("DropDownListddl")
Lblwarning.Text = DropDownListddl.SelectedValue
End Sub
There are two ways I can think to do this.
The first way would be to use a datasource that supports an update command and using two way binding to up date the values. The following snipped uses two way binding to populate the name and student fields and it will also update them for you.
<EditItemTemplate>
<tr style="background-color: #FFCC66;color: #000080;">
<td>
<asp:Button ID="UpdateButton" runat="server" CommandName="Update"
Text="Update" />
<asp:Button ID="CancelButton" runat="server" CommandName="Cancel"
Text="Cancel" />
</td>
<td>
<asp:TextBox ID="nameTextBox" runat="server" Text='<%# Bind("name") %>' />
</td>
<td>
<asp:TextBox ID="studentTextBox" runat="server" Text='<%# Bind("student") %>' />
</td>
</tr>
</EditItemTemplate>
If you are not using a datasource that supports this you can do one other thing.
Notice how the update button has a command called "Update". You can use this to fetch the control values you want by handling the list views ItemCommand Event.
protected void ListView1_ItemCommand(object sender, ListViewCommandEventArgs e)
{
if (e.CommandName == "Update")
{
TextBox box = (TextBox)e.Item.FindControl("nameTextBox");
string name = box.Text;
}
}
You can find any control in the editied item by simply calling find control and passing it the control's ID, and don't forget the cast.
Hope this helps
I'm setting up a User Control driven by a XML configuration. It is easier to explain by example. Take a look at the following configuration snippet:
<node>
<text lbl="Text:"/>
<checkbox lbl="Check me:" checked="true"/>
</node>
What I'm trying to achieve to translate that snippet into a single text box and a checkbox control. Of course, had the snippet contained more nodes more controls would be generated automatically.
Give the iterative nature of the task, I have chosen to use Repeater. Within it I have placed two (well more, see bellow) Controls, one CheckBox and one Editbox. In order to choose which control get activate, I used an inline switch command, checking the name of the current configuration node.
Sadly, that doesn't work. The problem lies in the fact that the switch is being run during rendering time, long after data binding had happened. That alone would not be a problem, was not for the fact that a configuration node might offer the needed info to data bind. Consider what would happen if the check box control will try to bind to the text node in the snippet above, desperately looking for it's "checked" attribute.
Any ideas how to make this possible?
Thanks,
Boaz
Here is my current code:
Here is my code (which runs on a more complex syntax than the one above):
<asp:Repeater ID="settingRepeater" runat="server">
<ItemTemplate>
<%
switch (((XmlNode)Page.GetDataItem()).LocalName)
{
case "text":
%>
<asp:Label ID="settingsLabel" CssClass="editlabel" Text='<%# XPath("#lbl") %>' runat="server" />
<asp:TextBox ID="settingsLabelText" Text='<%# SettingsNode.SelectSingleNode(XPath("#xpath").ToString()).InnerText %>'
runat="server" AutoPostBack="true" Columns='<%# XmlUtils.OptReadInt((XmlNode)Page.GetDataItem(),"#width",20) %>'
/>
<% break;
case "checkbox":
%>
<asp:CheckBox ID="settingsCheckBox" Text='<%# XPath("#lbl") %>' runat="server"
Checked='<%# ((XmlElement)SettingsNode.SelectSingleNode(XPath("#xpath").ToString())).HasAttribute(XPath("#att").ToString()) %>'
/>
<% break;
} %>
</ItemTemplate>
</asp:Repeater>
One weekend later, here is what I came with as a solution. My main goal was to find something that will both work and allow you to keep specifying the exact content of the Item Template in markup. Doing things from code would work but can still be cumbersome.
The code should be straight forward to follow, but the gist of the matter lies in two parts.
The first is the usage of the Repeater item created event to filter out unwanted parts of the template.
The second is to store decisions made in ViewState in order to recreate the page during post back. The later is crucial as you'll notice that I used the Item.DataItem . During post backs, control recreation happens much earlier in the page life cycle. When the ItemCreate fires, the DataItem is null.
Here is my solution:
Control markup
<asp:Repeater ID="settingRepeater" runat="server"
onitemcreated="settingRepeater_ItemCreated"
>
<ItemTemplate>
<asp:PlaceHolder ID="text" runat="server">
<asp:Label ID="settingsLabel" CssClass="editlabel" Text='<%# XPath("#lbl") %>' runat="server" />
<asp:TextBox ID="settingsLabelText" runat="server"
Text='<%# SettingsNode.SelectSingleNode(XPath("#xpath").ToString()).InnerText %>'
Columns='<%# XmlUtils.OptReadInt((XmlNode)Page.GetDataItem(),"#width",20) %>'
/>
</asp:PlaceHolder>
<asp:PlaceHolder ID="att_adder" runat="server">
<asp:CheckBox ID="settingsAttAdder" Text='<%# XPath("#lbl") %>' runat="server"
Checked='<%# ((XmlElement)SettingsNode.SelectSingleNode(XPath("#xpath").ToString())).HasAttribute(XPath("#att").ToString()) %>'
/>
</asp:PlaceHolder>
</ItemTemplate>
</asp:Repeater>
Note: for extra ease I added the PlaceHolder control to group things and make the decision of which controls to remove easier.
Code behind
The code bellow is built on the notion that every repeater item is of a type. The type is extracted from the configuration xml. In my specific scenario, I could make that type to a single control by means of ID. This could be easily modified if needed.
protected List<string> repeaterItemTypes
{
get
{
List<string> ret = (List<string>)ViewState["repeaterItemTypes"];
if (ret == null)
{
ret = new List<string>();
ViewState["repeaterItemTypes"] = ret;
}
return ret;
}
}
protected void settingRepeater_ItemCreated(object sender, RepeaterItemEventArgs e)
{
string type;
if (e.Item.DataItem != null)
{
// data binding mode..
type = ((XmlNode)e.Item.DataItem).LocalName;
int i = e.Item.ItemIndex;
if (i == repeaterItemTypes.Count)
repeaterItemTypes.Add(type);
else
repeaterItemTypes.Insert(e.Item.ItemIndex, type);
}
else
{
// restoring from ViewState
type = repeaterItemTypes[e.Item.ItemIndex];
}
for (int i = e.Item.Controls.Count - 1; i >= 0; i--)
{
if (e.Item.Controls[i].ID != type) e.Item.Controls.RemoveAt(i);
}
}
You need something that looks more like this:
<ItemTemplate>
<%# GetContent(Page.GetDataItem()) %>
</ItemTemplate>
And then have all your controls generated in the code-behind.