So, I had a GridView with a TemplateField. The TemplateField contained links that were used for deleting rows. I had set up Membership and Roles, and I only wanted users in the "admin" role to see these delete links, so I wrapped them in LoginView. However, when I did this, the links quit working. Today, I spent several hours searching for a solution to this problem, and I found lots of forum posts where other developers were having the same trouble, but none of the suggestions that were posted worked for me. They did give me some clues in the right direction, though. I just needed to simmer in it a little while, and sure enough, while watching The Story Of Us with my wife, the answer came to me. This article explains the problem in detail and how I solved it.
Here was my broken code. I am leaving out code that isn't relevant to the issue.
<asp:GridView ID="GridView1" runat="server" DataKeyNames="record_id" DataSourceID="SqlDataSource1" >
<Columns>
<asp:TemplateField ShowHeader="False">
<ItemTemplate>
<asp:LoginView ID="deleteRecordLoginView" runat="server">
<RoleGroups>
<asp:RoleGroup Roles="admin">
<ContentTemplate>
<a href="#" name="myNewWindow" onclick="windowOpen('<%# Eval("record_id") %>', 'Delete')">Delete</a> <!-- I am injecting the key from my datasource into a javascript function I have defined on the page. That's not really relevant here. The underlying problem is having databinding syntax ie. Eval("record_id") inside a LoginView control. -->
</ContentTemplate>
</asp:RoleGroup>
</RoleGroups>
</asp:LoginView>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
As noted in the comment above, the underlying issue is that I have data-binding syntax within a LoginView control. Here are some examples of other attempts to do this that also resulted in the same problem:
One thing I got from reading those examples is that this problem stumps more people than myself. As I mentioned above, I tried several of the proposed solutions which didn't work for me for some reason, but they gave me some clues that led me to a solution.
Since the problem is that LoginView's apparently don't handle databinding syntax well, I decided to get rid of the LoginView.
<script runat="server">
protected void GridView1_Init(object sender, EventArgs e)
{
if (User.IsInRole("admin")) {GridView1.Columns[0].Visible = true;}
else {GridView1.Columns[0].Visible = false;}
}
</script>
<asp:GridView ID="GridView1" runat="server" DataKeyNames="record_id" DataSourceID="SqlDataSource1" OnInit="GridView1_Init">
<Columns>
<asp:TemplateField ShowHeader="False">
<ItemTemplate>
<a href="#" name="myNewWindow" onclick="windowOpen('<%# Eval("record_id") %>', 'Delete')">Delete</a>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
In place of the LoginView, I used code-behind on init of the GridView to check if the currently logged-in user is in the "admin" role. If they are, then I display the column containing my Delete button. If they aren't in the role, I hide the column. If you looked at the other examples, there were a couple of suggestions to do something similar at the row level on databound or rowcreated. I don't know why, but when I tried those solutions, they didn't work for me. The above code, however, worked, and in my mind, it is a simpler solution.
Another point that is perhaps worth considering is that, if you are using validation, you will need to set EnableViewState="true" in the Gridview. I found that out in a note that I read on MSDN here which says "The GridView control is re-created on postback based on the information that is stored in ViewState. If the GridView control includes a TemplateField or a CommandField with the CausesValidation property set to true, then the EnableViewState property must also be set to true to ensure that concurrent data operations, such as updates and deletes, apply to the appropriate row."