DotNetNuke: ObjectDataSource for GridView not being found - asp.net

As a follow-up to a previous question about GridView and DotNetNuke, I'm having a little more trouble getting things to act correctly. Right now I have a simple GridView in my ascx file and I bind the data to the GridView in my .cs file like so:
DiscoveryController objDiscoverys = new DiscoveryController();
List<DiscoveryInfo> lstDiscoveries = objDiscoverys.GetDiscoverys(ModuleId);
grdDiscoverys.DataSource = lstDiscoveries;
grdDiscoverys.DataBind();
This works. However, I have seen an alternate method in a tutorial which instead defines an <asp:ObjectDataSource> in the controller, and this seems to allow the Designer to do more intelligent things, such as add functioning Delete buttons through a checkbox. Later on in the tutorial, I see inline editing being done as well, which is functionality I desire.
So I decided to give it a shot. To wit:
<asp:ObjectDataSource ID="objDataSource" runat="server" TypeName="MyCompany.Modules.Discovery.DiscoveryController" />
As my dll file in the bin folder is named MyCompany.Modules.Discovery (which matches the assembly name and default namespace I have set up in my C# project), this makes perfect sense. As the tutorial says, I then used the Designer to attempt to bind the Data Source to the GridView.
However, I get an error message that it can't be loaded. The namespace names and class name match up, and I can clearly bind it from the codebehind, so what gives?
EDIT: A follow up. After some experimentation, I have discovered that while the Designer cannot see my module, the .ascx template itself can. Putting this in my .ascx file seems to work...for the most part:
<asp:ObjectDataSource ID="objDataSource" runat="server" TypeName="MyCompany.Modules.Discovery.DiscoveryController" SelectMethod="GetDiscoverys" UpdateMethod="UpdateDiscovery" DeleteMethod="DeleteDiscovery">
<SelectParameters>
<asp:QueryStringParameter Name="ModuleId" QueryStringField="mid" />
</SelectParameters>
<UpdateParameters>
<asp:QueryStringParameter Name="ModuleId" QueryStringField="mid" />
</UpdateParameters>
<DeleteParameters>
<asp:QueryStringParameter Name="ModuleId" QueryStringField="mid" />
</DeleteParameters>
</asp:ObjectDataSource>
<asp:GridView ID="grdDiscoverys" runat="server" DataSourceID="objDataSource" EnableModelValidation="True" AutoGenerateColumns="false" AutoGenerateEditButton="true" AutoGenerateDeleteButton="true" DataKeyNames="ItemId">
<Columns>
<asp:BoundField DataField="ItemId" HeaderText="#" ReadOnly="true" />
<asp:BoundField DataField="Title" HeaderText="Title" />
<asp:BoundField DataField="Image" HeaderText="Image URL" />
<asp:BoundField DataField="Link" HeaderText="Link" />
</Columns>
</asp:GridView>
Fantastic...it looks like I've mirrored most of the functionality of what the Designer would have added...except for one teensy little thing. The automatic update doesn't update.
More specifically, I get this message when I try to update a field:
ObjectDataSource 'objDataSource' could not find a non-generic method 'UpdateDiscovery' that has parameters: ModuleId, Title, Image, Link, ItemId.
Of course it doesn't work! The method signature goes like this:
public void UpdateDiscovery(DiscoveryInfo objDiscovery)
At this point, I'm so close to getting something working that I can taste it, and DAL-be-damned I'm about to change the function so it takes those five exact params instead of a data object. However, the tutorial that I referenced above seemed to somehow convince the automatic update to pass a data object, so I'm kind of curious to know how it got away with it.

Change your object data source declaration to match your update method:
public void UpdateDiscovery(DiscoveryInfo objDiscovery)
Use single parameter in update parameter declaration:<UpdateParameters>
<asp:Parameter Name="objDiscovery" />
</UpdateParameters>
use object data source's updating event to create an object from the existing control set and assign it to parameter's default value.
Good luck

Related

Is it possible to use a control in a different content tag as a parameter?

I currently have two Web Controls, one being a CheckBoxList, and the other a GridView. The GridView's data configuration has a parameter in it's query which is linked to the selected values from the CheckBoxList. However, when the two controls are in separate content tags like so:
<asp:Content ID="ListPanel" runat="server" ContentPlaceHolderID="LeftContent">
<h3>Pick Info Here</h3>
<asp:CheckBoxList ID="cbList1" runat="server"
DataSourceID="TestDataSource"
DataTextField="St" DataValueField="St" RepeatColumns="2">
</asp:CheckBoxList>
</asp:Content>
<asp:Content ID="ResultsPanel" runat="server" ContentPlaceHolderID="RightContent">
<asp:GridView ID="gView1" runat="server"
AllowPaging="True" AllowSorting="True"
AutoGenerateColumns="False" DataSourceID="TestDS1">
<Columns>
...
</Columns>
</asp:GridView>
<asp:SqlDataSource ID="TestDS1" runat="server"
...
SelectCommand="SELECT ST FROM [Table] where ST = ?">
<SelectParameters>
<asp:ControlParameter ControlID="cbList1" Name="?"
PropertyName="SelectedValue" />
</SelectParameters>
</asp:SqlDataSource>
</asp:Content>
It works fine when I put both controls in the same <asp:Content> tag, but things call apart with an error of System.InvalidOperationException: Could not find control 'cbList1' in ControlParameter '?'. Is there a way to keep the controls separate, or do they have to be inside the same content tag?
Seems a little krufty but you could put a HiddenControl in the ResultPanel and then set it's value to that of cbList1.SelectedValue on page load.
I know you can access controls regardless of the Content they are in, from code behind but apparently you can't from within the Content itself on the aspx side.
Apparently this can be done by prefixing the ContentPlaceHolderID to the ControlID of the parameter entry. In my case, the code would be:
SelectParameters>
<asp:ControlParameter ControlID="LeftContent:cbList1" Name="?"
PropertyName="SelectedValue" />
</SelectParameters>
since the Control the parameter reads from is in the LeftContent tag.
Some years later... I had the same problem with a gridview and a control parameter. In the source-code of the rendered page, you can find the exact "path" to the element. In my case it was not only gv1$txtEmployeeName, but gv1$ctl02$txtEmplyeeName.
I guess, ctl02 means EditItemTemplate, because including ctl02$ solved my problem.

Automatically rebind controls on ASP.NET postback

Is there a flag or property value I can set to have my controls re-bind on every page load, instead of just the initial one? I'm still very new to ASP.NET, so I would like to do it properly (if such a way exists) before resorting to the first thing that "works".
I am working on a simple WebForms page, which boils down to a few SQLDataSource-bound Repeaters; for example:
<asp:Repeater ID="ExampleRepeater" runat="server" DataSourceID="ExampleDataSource"
OnItemDataBound="ExampleRepeater_ItemDataBound">
<ItemTemplate>
<asp:Label ID="DataboundControlID" runat="server" Text='<%# Eval("ExampleColumnName")%>' />
</ItemTemplate>
</asp:Repeater>
<asp:SqlDataSource ID="ExampleDataSource" runat="server" ConnectionString="example_connection_string"
SelectCommand="ExampleStoredProcedure" SelectCommandType="StoredProcedure">
<SelectParameters>
<asp:Parameter DefaultValue="exampleValue" Name="parameter1" Type="String" />
</SelectParameters>
</asp:SqlDataSource>
After the user has interacted with my page, they can initiate a postback which will change the state of the database. I'd like the postback'ed page to reflect the changes to the database. Please let me know if I can clarify my situation any further.
After your processing i.e. updating database you can re-bind the Repeater by calling DataBind method like:
ExampleRepeater.DataBind();
In my experience, some controls do, others don't. E.g. the standard ASP.NET controls from Microsoft always worked for me automatically, whereas the ASPxGridView from DevExpress needs a manual rebind on every postback/callback.

What are some ASP.NET GridView performance improvement opportunities?

I have an ASP.NET application that is fairly basic. It queries some data and displays the data in a GridView (0 - 2000 or so records possible). I've been trying to find some ways to make it zippier, best practices, etc. as it seems to be a little sluggish while the GridView is being rendered. I've seen some threads on utilizing CSS vs. setting all the styles directly on the GridView, but I'm not sure how this would look.
This is what the GridView looks like right now...
<asp:GridView ID="gvResults" runat="server" DataKeyNames="ORDNO" AutoGenerateColumns="False"
CellPadding="4" ForeColor="#333333" OnSelectedIndexChanged="gvResults_SelectedIndexChanged"
Width="100%" OnRowDataBound="gvResults_RowDataBound" meta:resourcekey="gvResultsResource1">
<AlternatingRowStyle BackColor="White" ForeColor="#284775" />
<Columns>
<asp:BoundField DataField="CSTNO" HeaderText="CUST" meta:resourcekey="BoundFieldResource1" />
<asp:BoundField DataField="ORDNO" HeaderText="RMA NUMBER" meta:resourcekey="BoundFieldResource2" />
<asp:BoundField DataField="CSTORD" HeaderText="CUST PO NUMBER" meta:resourcekey="BoundFieldResource3" />
<asp:BoundField DataField="ORDDTE" HeaderText="ORDER DATE" meta:resourcekey="BoundFieldResource4" />
<asp:BoundField DataField="INVDTE" HeaderText="INVOICE DATE" HtmlEncode="False" meta:resourcekey="BoundFieldResource5" />
<asp:CommandField ShowSelectButton="true" ButtonType="Link" meta:resourcekey="CommandFieldResource1" />
</Columns>
<EditRowStyle BackColor="#999999" />
<EmptyDataTemplate>
<span style="color: Red;">
<asp:Literal ID="litErrorNoRMAFound" runat="server" EnableViewState="False" meta:resourcekey="litErrorNoRMATagsFoundResource1"
OnInit="litErrorNoRMAFound_Init"></asp:Literal>
</span>
</EmptyDataTemplate>
<FooterStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
<HeaderStyle BackColor="#3494CC" Font-Bold="True" ForeColor="White" HorizontalAlign="Left" />
<PagerStyle BackColor="#284775" ForeColor="White" HorizontalAlign="Center" />
<RowStyle BackColor="#F7F6F3" ForeColor="#333333" />
<SelectedRowStyle BackColor="#E2DED6" Font-Bold="True" ForeColor="#333333" />
</asp:GridView>
Thanks in advance, for any of your ideas/comments.
EDIT
Requirements do not allow for paging of the data. I am also looking for specific information on CSS utilization and the GridView...
A few ideas:
Eliminate viewstate if possible.
If you're using IIS6 or better, consider enabling compression.
Enable paging on your GridView to keep request times down.
Make sure your deployed production solution is release-compiled with no rogue debugging or tracing directives
Don't use the GridView. If you want control, use the Repeater.
If you're not using paging (as mentioned in a comment), consider going to a DataList or even a Repeater to reduce the overhead of the object itself.
Enable paging will work ;)
Is viewstate enabled? That could be a lot of hidden data.
You could try using server-side viewstates. It puts a bit more load on the server, but page loading / updating should be much faster.
Using CSS should reduce the size of the markup.
http://www.jigar.net/howdoi/viewhtmlcontent197.aspx shows how to style the gridview with CSS.
You can replace the entire way the grid renders with the CSS Control Adapters, which should help reduce the markup size further.
You'll want to enable paging in order to avoid a situation where you are rendering hundreds of records to the page. In essence, you'll use the Page Controls in order to move through records rather than the scroll bar.
A related option that can help a lot is to use an ObjectDataSource that retrieves only those records that the grid wants to display. This uses the new ROW_NUMBER() construct in SQL Server. Here is an example of an ObjectDataSource method that retrieves a specific range of records. It uses my DAL but you should be able to get a pretty clear idea:
[DataObjectMethodAttribute(DataObjectMethodType.Select, true)]
public List<SelectClassData> GetList(string sortType, int startRowIndex, int maximumRows)
{
if (!BusinessUtilities.SQLSafe(sortType))
throw new Exception("Illegal value in request (unsafe).");
startRowIndex++;
int EndRow = startRowIndex + maximumRows;
using (BSDIQuery qry = new BSDIQuery())
{
if (sortType.Length == 0)
sortType = "Title";
return
qry.Command(
"With OrderedClassEvent as (Select ClassID, Title, StartDate, EndDate, ROW_NUMBER() OVER (ORDER BY " + sortType + ") as RowNum From ClassEvent)")
.Append(" Select ClassID, Title, StartDate, EndDate From OrderedClassEvent Where RowNum Between #StartRow AND ").ParamVal(startRowIndex)
.Append("#EndRow ").ParamVal(EndRow)
.Append("Order By RowNum")
.ReturnList<SelectClassData>();
}
}
With respect to using CSS, etc. that will not make the grid zippier but it will permit you to define the Grid attributes (e.g. FooterStyle, HeaderStyle, etc.) in a "Skin" file that you'll tuck away in an theme directory. It is optional but you could also define the styles for each attribute using CSS. The skin file will show a "template" GridView with the CssClass for each attribute. When you refer create your grid, you'll refer to the Skin file and then just leave out all of the attribute definitions found in that file (the gridview won't need them because it knows what to do based on the skin). This is especially great if you want to have all of your grids look the same.
You answered it yourself - zip it to make it zippier. Use a compression http handler.
You could output the first X items, then when the page has loaded fetch the next X items and so forth. This would probably make the page seem snappier for the end user.
You should also investigate what exacly makes the page slow, also check if the database query could be optimized using indexes or something.
As pagination is not allowed, I definitely explore to reduce total payload of this grid:
Using repeater control and build native TABLE to render this grid instead of datagrid control - this way I will have complete control over payload. Something like:
Using 100% CSS style instead of controls attributes.
Disabling the viewstate (?)
Note that performance issue is less relating to asp.net here and more to the amount of data transfer from server to client machine.
Disable viewstate if it is possible.
Use Repeater instead of grid and use
your own html, that would decrease
the rendered mark up
Try to use stored procedure
If you have any methods which getting
some data from other tables in your
db. Try to use view

Multiple DataKeyNames in a GridView

I have a GridView populated from an ObjectDataSource with two items in its DataKeyNames field. One is the primary key, ID, the other is a category field (the category field is used to add header rows to delineate categories).
Displaying works fine, but I'm trying to create a Delete action. The object's delete method only needs the ID field and in the ObjectDataSource even if I define the method as only needing an ID field, .net complains because it is looking for a method which has both the fields defined in DataKeyNames.
It works if I add a parameter for the category to the delete method, but it's annoying to have a parameter defined that isn't used for anything.
Can I configure the ObjectDataSource and GridView objects to have two values for DataKeyNames but specific which would should be passed to which methods?
The (simplified) definitions for the two objects are:
<asp:ObjectDataSource ID="ObjDS1" runat="server" SelectMethod="getAllItems"
TypeName="Items" DeleteMethod="deleteItem">
<DeleteParameters>
<asp:Parameter Name="ID" Type="Int32" />
<!-- This shouldn't be necessary: -->
<asp:Parameter Name="Category" Type="String" />
</DeleteParameters>
</asp:ObjectDataSource>
<asp:GridView ID="gvJItems" runat="server" AutoGenerateColumns="False" DataKeyNames="ID,Category"
DataSourceID="ObjDS1">
<Columns>
<asp:BoundField DataField="ID" Visible="false" HeaderText="ID" />
<asp:BoundField DataField="Name" HeaderText="Name" ItemStyle-Width="85%"/>
<asp:TemplateField>
<ItemTemplate>
<asp:LinkButton ID="lbDelete" Runat="server"
OnClientClick="return confirm('Are you sure you want to delete this?');"
CommandName="Delete">Delete</asp:LinkButton>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
All of the DataKey values will always be passed to the Delete method because the fields named by DataKeyNames are intended to be the field or fields that uniquely identify that item in the GridView. They're often used for more than that to easily maintain extra fields on a per-row basis in ViewState as you are here, but doing so has the side-effect that you're seeing with the ObjectDataSource methods.
To answer your original question, no, you can't have Category be in DataKeyNames without having it be passed to the Delete method. You could maintain the value in ViewState by putting the value in a hidden input in your Template Column, though. It would be more work to get that value out than to ignore the parameter to the Delete method, though.
I had the same problem with my gridview with an object datasource, how I handled this situation : I use StoredProcs in the SQL end. I added one more parameter into the store procedure, though I did not need that parameter. It worked fine
You can remove them using the ObjectDataSource updating/deleting the event
void ODS_Updating(Object sender, ObjectDataSourceMethodEventArgs e)
{
e.InputParameters.Remove("Type_ID");
e.InputParameters.Remove("Document_ID");
e.InputParameters.Remove("State_ID");
}
I am using the DataKeys for look-up purposes but I don't need them in my update procedure.
When using the default declarative delete method, GridView will pass both values to the ObjectDataSource.
If you really don't like the extra parameter, an alternative is to cancel the declarative method and use your own one (you can remove the unnecessary parameter there), but it requires more code.

Dynamic Columns and Data Sources with .NET Grid View

I am working on a multi-purpose page and rather than adding multiple grids to the same page we wanted to use a single GridView to the page, and on Page_Init add the needed columns, and set the respective DataSourceID.
So to do this, we have something like the following in the aspx, the codebehind in the Page_Init is very simple adding a few columns then setting the DataSourceID property of the GridView.
ASPX:
<asp:GridView ID="gvDisplay" runat="server" AutoGenerateColumns="false" CellPadding="5"
width="100%" AllowPaging="true" PageSize="200" DataSourceID="wuProcessLogDataSource">
<RowStyle CssClass="RowStyle" />
<AlternatingRowStyle CssClass="AlternatingRowStyle" />
<HeaderStyle CssClass="HeaderStyle" />
</asp:GridView>
<asp:ObjectDataSource id="wuProcessLogDataSource" runat="server" EnablePaging="True"
SelectMethod="GetWUProcessLog" TypeName="Project.Objects.WUProcessLogDal"
SelectCountMethod="GetWUProcessLogTotalRecords">
<SelectParameters>
<asp:QueryStringParameter QueryStringField="w" DefaultValue="0" Name="workunitId" />
</SelectParameters>
</asp:ObjectDataSource>
The object data source is there and working as the first page load triggers without any issues at all. However, as soon as you click on a page button, the grid disappears from the page? Any ideas?
I would just use a DataGrid but it doesn't have the desired dynamic display abilities for the HyperLinkColumn.
It sounds like you're doing something like
If (!Page.IsPostBack)
{
//create + add columns - set datasource etc
}
If that's the case - then you need to remove the check and always generate the columns (I'd also suggest disabling viewstate for the datagrid)
try the page load event instead of page init

Resources