Using a Modal Popup to Modify LINQ GridViews
LINQ queries are great for connecting to and displaying data in GridViews without much effort. They do lack; however, in some of the GridView update functionality if you are spanning between multiple tables.
When you try to Update, using a GridView’s normal Edit/Update commands, a LinqDataSource, you’ll recieve:
“LinqDataSource does not support the Select property when the Delete, Insert or Update operations are enabled.”
Scott Guthrie touched on this a bit in his recent LinqDataSource article and suggested either using a DetailsView or other means to modify it. Instead of that, I decided to place those fields in a ModalPopupExtender.
NOTE: This project is using Visual Studio 2008 with the AjaxControlToolkit for v3.5. For a copy of this solution, here. The solution includes the correct AjaxControlToolkit.dll and the Northwind database used for the examples.
UPDATE 4/12/2008: New version here in response to comments to this post.
To begin, I modified the LinqDataSource’s Selecting method to match what ScottGu used (for simplicity of getting to a LINQ expression):
protected void ProductsDataSource_Selecting(object sender,
LinqDataSourceSelectEventArgs e)
{
var products = db.Products
.Select(i => new {
i.ProductID,
i.ProductName,
i.UnitsInStock,
i.UnitPrice,
NumOrders = i.Order_Details.Count,
Revenue = i.Order_Details.Sum(o =>
o.Quantity * o.UnitPrice),
Potential = i.UnitsInStock * i.UnitPrice});
e.Result = products;
}
This provides us with a result set of the simple data (ProductId, ProductName, UnitsInStock, and UnitPrice) and two computed fields (NumOrders, Revenue, and Potential). I added the potential revenue field for a bit of fun—it’s a computed field based on values we can change (UnitsInStock and UnitPrice).
We prepare our GridView accordingly.
<asp:LinqDataSource ID=”ProductsDataSource” runat=”server”
ContextTypeName=”NorthwindDataContext”
onselecting=”ProductsDataSource_Selecting” />
<asp:GridView ID=”ProductsGridView” runat=”server”
DataSourceID=”ProductsDataSource”
OnRowDataBound=”ProductsGridView_RowDataBound”
OnRowCommand=”ProductsGridView_RowCommand”>
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:LinkButton ID=”ModifyButton”
runat=”server” Text=”Modify” CommandName=”Modify” />
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField=”ProductID”
HeaderText=”Product ID” SortExpression=”ProductID” />
<asp:BoundField DataField=”ProductName”
HeaderText=”Product Name” SortExpression=”ProductName” />
<asp:BoundField DataField=”UnitsInStock”
HeaderText=”Units In Stock” SortExpression=”UnitsInStock” />
<asp:BoundField DataField=”UnitPrice”
HeaderText=”Unit Price” DataFormatString=”{0:c}”
SortExpression=”UnitPrice” />
<asp:BoundField DataField=”NumOrders”
HeaderText=”Num. Orders” SortExpression=”NumOrders” />
<asp:BoundField DataField=”Revenue”
HeaderText=”Total Revenue” SortExpression=”Revenue”
DataFormatString=”{0:c}” />
<asp:BoundField DataField=”Potential”
HeaderText=”Potential Revenue” SortExpression=”Potential”
DataFormatString=”{0:c}” />
</Columns>
</asp:GridView>
(Click image to see in new window)
We’ll come back to that Modify LinkButton later—that’ll be how we’ll start the ModalPopupExtender.
You notice that there are two methods associated with our GridView: RowDataBound and RowCommand. RowDataBound will allow us to preslug in the ProductId into the CommandArgument so that RowCommand can use it later:
protected void ProductsGridView_RowDataBound(object sender,
GridViewRowEventArgs e)
{
// For each edit button, place the ProductId in the CommandArgument
// so we can access it later.
LinkButton lb = e.Row.FindControl(“ModifyButton”) as LinkButton;
if (lb != null)
{
lb.CommandArgument = e.Row.Cells[1].Text;
}
}
Next, we’ll need to add our ModalPopupExtender in. Because we’re wanting to vary our content for each row of the GridView, we’ll want to manually call the MPE after selecting and populating our panel. We can do this by creating an invisible control to for the TargetControlId.
<asp:Button ID=”HiddenButton” runat=”server”
Style=”display: none;“ />
<ajaxToolkit:ModalPopupExtender ID=”mpe” runat=”server”
BackgroundCssClass=”ModalBackground”
CancelControlID=”CancelPopupButton”
PopupControlID=”UpdateRecordPanel”
TargetControlID=”HiddenButton” />
<asp:Panel ID=”UpdateRecordPanel” runat=”server”
CssClass=”PopupPanel” Style=”display: none;“>
<h2>Update Record</h2>
<p>
Record:
<asp:Label ID=”ProductId” runat=”server” /><br />
Product Name:
<asp:TextBox ID=”ProductName” runat=”server” /><br />
Units in Stock:
<asp:TextBox ID=”UnitsInStock” runat=”server” /><br />
Unit Price:
<asp:TextBox ID=”UnitPrice” runat=”server” /><br />
</p>
<p>
<asp:Button ID=”OkPopupButton” runat=”server” Text=”Update”
OnClick=”OkPopupButton_Click” />
<asp:Button ID=”CancelPopupButton” runat=”server” Text=”Cancel” />
</p>
</asp:Panel>
To fire our MPE, we’ll use that RowCommand we specified earlier. The RowCommand will do three things:
-
Collect the row’s CommandArgument (which is the ProductId),
-
LINQ up a new Product and preslug the UpdateRecordPanel’s fields with the data,
-
Find the MPE and .Show() it.
Since our fields in our Product object match, we could reflect through; however, for simplicity, we’ll just assign them for now.
Below you’ll see the RowCommand method as well as a little Helper method to query up the Product objects.
protected void ProductsGridView_RowCommand(object sender,
GridViewCommandEventArgs e)
{
if (e.CommandName == “Modify”)
{
// Fill the pop-up’s edit boxes with the
// information from the Product object.
Product record =
SelectRecord(Convert.ToInt32(e.CommandArgument));
ProductId.Text =
record.ProductID.ToString();
ProductName.Text =
record.ProductName.ToString();
UnitsInStock.Text =
record.UnitsInStock.Value.ToString();
UnitPrice.Text =
record.UnitPrice.Value.ToString();
// Now we want to “Show” our ModalPopupExtender,
// so find the control and .Show it.
ModalPopupExtender mpe =
up1.ContentTemplateContainer.FindControl(“mpe”)
as ModalPopupExtender;
mpe.Show();
}
}
private Product SelectRecord(int productId)
{
// Helper method to fetch a Product
// object based on a specific ID.
Product record = db.Products
.Where(i => i.ProductID ==
Convert.ToInt32(productId)).Single();
return record;
}
Note the .Value for those fields that can be nullable.
Now that we’ve populated and displayed our UpdateRecordPanel, we need to capture the changes and save the back to the data source. This is where inline editing fails on our GridView, but we’ll have no problem calling a simple SubmitChanges() on the data source here.
protected void OkPopupButton_Click(object sender, EventArgs e)
{
// This method is called by our Popup’s OK button.
// By not specifying a OkControlID on the MPE, we can use
// this method instead.
UpdateRecord();
}
public void UpdateRecord()
{
// Fetch our record based on the ProductId (since it’s read-only)
// and update according to what’s been entered in the box.
Product record =
SelectRecord(Convert.ToInt32(ProductId.Text));
record.ProductName =
ProductName.Text.ToString();
record.UnitsInStock =
short.Parse(UnitsInStock.Text);
record.UnitPrice =
decimal.Parse(UnitPrice.Text);
// Save the changes back through LINQ.
db.SubmitChanges();
// Rebind the GridView to pick up the new changes.
ProductsGridView.DataBind();
}
Updating the Product Name is easy enough, but what about those computed fields? Looking above, we have 25 Chai in stock at $25. Let’s change that to 30 in stock. Our ModalPopup panel looks like:
When calling UpdateRecord() the GridView’s Potential Revenue updates accordingly to match the Units in Stock and Unit Price.
The code isn’t perfect, but it does give one option to updating LINQ data sources without using the built-in GridView Updates. At this point, personally, I don’t see a lot of value in the actual LinqDataSource object—you just as easily retrieved this data in a method using a query and never messed with the object itself. I suppose it’s personal preference and flexibility.
Now, with all this talk of Chai tea, I need to go make some. Yum. =)
Hi!
Need help!
Do you know how to convert this to Vb?
private Product SelectRecord(int productId)
{
// Helper method to fetch a Product
// object based on a specific ID.
Product record = db.Products
.Where(i => i.ProductID ==
Convert.ToInt32(productId)).Single();
return record;
}
thanks in advance,
Ed
@edcon
Private Function SelectRecord(ByVal productId As Integer) As Product
‘ Helper method to fetch a Product
‘ object based on a specific ID.
Product record = db.Products.Where(i => i.ProductID = Convert.ToInt32(productId)).Single()
Return record
End Function
You really helped me, David!
Thank you and more power !
@edcon-
Not a problem. You make a good point, I tend to do most everything in C# since that’s what I use all day. I’ll try to mix it up more and put some VB code up and/or see if I can do something fancy using some of the online converters. 🙂
Have a good one!
-dl
Hi Dave,
I noticed when I used your codes and made to VB, the Update button fires when hitting the refresh button in IE. Do you encounter this behavior?
thank you,
-edcon
Hmm, no, I haven’t had that trouble. Would you mind packaging up a sample of your implementation and emailing it to me? tiredstudent@gmail.com
-dl
Hi Dave,
As per this below link, redirecting to the same page will resolve the issue but I’m not sure if this the right way to prevent the button being re-postback when hitting F5 or refresh in IE.
http://forums.asp.net/t/1151989.aspx
This issue might be out of topic… my apology.
I sent the code already. Just let me know.
I really appreciate your response.
edcon
Excellent work! I was looking all over for a solution in the ASP.NET forums, but had no luck. I have posted my solution there using your technique.
http://forums.asp.net/p/1224034/2200052.aspx
Hi David
I try to do the same thing.
I don’t know where can I find the ModalPopupExtender?
what is up1? Could u tell me ?
ModalPopupExtender mpe =up1.ContentTemplateContainer.FindControl(“mpe”) as ModalPopupExtender;
Do u have the sample file ? Could u mail me that?
Thanks
Reg
@Reg-
up1 is the updatePanel control. Since our GridView is enclosed in an updatePanel (which has a ContentTemplate for controls), we have to dig into that control to find our ModalPopupExtender.
There used to be a solution attached to this blog post, but appears it was misplaced when our ftp server crashed–I’ll work up something and post it both to this page and the comments today.
@Reg-
I reworked the above code and have posted and example here.
I didn’t use the LinqDataSource, instead simply bound it in code-behind. If you wish to use the LinqDataSource, simply add the LinqDataSource object onto the page, go to it’s events and add a _Selecting event, then move the BindProductsGrid method from Page_Load to the _Selecting event of the LinqDataSource.
In my opinion, LinqDataSource is rarely, if ever, needed–a conclusion I’ve held to since I wrote this article originally.
Hope this helps!
Hey,
Thanks for the code. My client wanted a way to change the text color and size of text inside the gridview. The gridview was being used to organize content that was used on his website. I used your code and instead of using textboxes, I used fckEditors so that he could wysiwyg the heck outta the text and reinsert it back into the gridview. Thanks!
@bebandit-
Great to hear; thanks!
Hey this one is excellent.
I was looking for this and you made it look so simple and so self explanatory..
Thank you so much
Cheers ‘
Ali
Hello!
Does someone know how to adapt this very excellent LINQ-Example to ImageButton?
I tried…
ImageButton modifyButton = e.Row.FindControl("ModifyButton") as ImageButton;
and
Thanks!
@Maci-
Is your ImageButton control trying to kick off the MPE? It looks like your second code block got cutoff. 😦
Good work. I’ve used It in my solution, and I’ts works fine. I’ve made some changes because I’ve used ListView. Also i’ve stored unique key in HiddenField control. In future I want to use UpdateProgress while modal popup waited to be shown and while data to be updated in ListView.
This article helped me to made my website more pretty and userfrendly. Thanks!
hi David is there any chance of you sending me this in vb as i have looked through the current source code and am confused between the linqdatasource and code behind
I, unfortunately, don’t have this in VB. What seems to be throwing you off?
The LINQDataSource is like any other DataSource object. You could use a SqlDataSource or roll your own via IEnumerable, List, etc. Those types should be the same in VB as they are in C#.
Thanks for the reply i understand the code better now i have tried to convert it over to vb just with a sql connection but when the page loads the gridview doesnt display could i send you the source code for you to have a look at for me??
thanks
Yeah, email it to drlongnecker@gmail.com and I’ll take a look. 🙂