As far as I can see there are three (oops! four then and now a 5th) types of Custom Page:
- Custom Pages Part 1 - Standard Custom Page based on an existing PageTemplate and customised in the DynamicData\CustomPages folder.
- Custom Pages Part 2 - A completely Custom Page again in the DynamicData\CustomPages folder.
- Custom Pages Part 3 - Standard ASP.Net Page with Dynamic Data features added to take advantage of the FieldTemplates.
- Custom Pages Part 4 - A DetailsView and a GridView using Validation Groups
- Custom Pages Part 5 - I18N? Internationalisation Custom Page
When answering this thread here LCID: how to scaffold language dependent fields from separate tables... by Zoltán Lantos from Hungary, I knocked together this CustomPage and thought it was worth blogging about and so add a 5th post the my Three post series on Custom Pages.
Figure 1 – the model
This works by filtering the LCID column by the users current culture, so there is a row in ProductDetails for each culture/language.
The requirements for this little project were:
- Admin to be able to see in a tabbed like layout all the ProductDetails foreach culture.
- The normal user to be able to see their ProductDetails in their own culture.
The FieldTemplates
Here are the FieldTemplates for editing and displaying the records culture here.
<%@ Control Language="C#" CodeFile="LCID_Edit.ascx.cs" Inherits="LCID_EditField" %> <asp:DropDownList runat="server" ID="DropDownList1" CssClass="droplist" ondatabound="DropDownList1_DataBound"> </asp:DropDownList>
using System; using System.Collections.Specialized; using System.Globalization; using System.Linq; using System.Web.UI; using System.Web.UI.WebControls; public partial class LCID_EditField : System.Web.DynamicData.FieldTemplateUserControl { protected override void OnDataBinding(EventArgs e) { base.OnDataBinding(e); // use linq to get a list of cultures to display in the drop down list var cultures = from c in CultureInfo.GetCultures(CultureTypes.NeutralCultures) select new { Lcid = c.TwoLetterISOLanguageName, Name = c.TwoLetterISOLanguageName + " - " + c.EnglishName }; // setup the drop down list DropDownList1.DataSource = cultures; DropDownList1.DataValueField = "Lcid"; DropDownList1.DataTextField = "Name"; DropDownList1.DataBind(); } protected void DropDownList1_DataBound(object sender, EventArgs e) { // check the FieldValueString is not null if (String.IsNullOrEmpty(FieldValueString)) { // set it to the culture of the client session DropDownList1.SelectedValue = CultureInfo.CurrentCulture.TwoLetterISOLanguageName; } else { // set the drop down list to the current vlaue DropDownList1.SelectedValue = FieldValueString; } } protected override void ExtractValues(IOrderedDictionary dictionary) { // get selected value dictionary[Column.Name] = ConvertEditedValue(DropDownList1.SelectedValue); } public override Control DataControl { get { return DropDownList1; } } }
Listing 2 – LCID_Edit.ascx.cs code behind file
As you can see from the code above all we are doing is populating a DropDownList with all the cultures from CultureInfo.GetCultures of type CultureTypes.NeutralCultures using Linq to get them in an anonymous type.
And here is the much simpler LCID.ascx FieldTemplate:
protected override void OnDataBinding(EventArgs e) { base.OnDataBinding(e); // use linq to get a list of cultures to display in the drop down list var culture = CultureInfo.GetCultures(CultureTypes.NeutralCultures).SingleOrDefault(c => c.TwoLetterISOLanguageName == FieldValueString); Label1.Text = culture.TwoLetterISOLanguageName + " - " + culture.EnglishName; }
Listing 3 – LCID.ascx FieldTemplate OnDataBinding event handler
As you can see all the LCID.ascx is is a Label and the OnDataBinding event handler.
The Metadata and Partial Classes
I’m just going to paste them here and give detailed explanation as we go alongusing System; using System.Collections.Generic; using System.Linq; using System.Web; using System.ComponentModel; using System.ComponentModel.DataAnnotations; public partial class ProductsDataContext {
// Implement the InsertProductDetail to do insert validation partial void InsertProductDetail(ProductDetail instance) { var DC = new ProductsDataContext(); if (DC.ProductDetails.SingleOrDefault(pc => (pc.ProductId == instance.ProductId && pc.LCID == instance.LCID)) != null) {
// Throw an exception if a match is found throw new ValidationException("Duplicate culture per Product is not permitted"); } else { // finally send this to the DB this.ExecuteDynamicInsert(instance); } } } [MetadataType(typeof(ProductDetailMD))] public partial class ProductDetail { public class ProductDetailMD { [UIHint("LCID")] public object LCID { get; set; } } }
Listing 4 – Metadata and Partial classes
As you can see we two parts to the ProductsMD.cs file; the first part check for duplicate languages/culture on a product and the second part adds the UIHint to the LCID column.
The Products Edit page
Here’s a snapshot of the finished page:
Figure 1 - Screen shot of the finished page
There are three part to the page;
- A DetailsView for the Product
- A ListView for the “tab” control (it could be styled to look like tabs using css if you wanted to)
- A FromView and GridvView for the ProductDetails
1. A DetailsView for the Product
Is strait forward enough and I started with a normal Edit.aspx Page Template and then added all the other components.
2. A ListView for the “tab” control
To do this at first I thought of using the Ajax Toolkit Tab control and embedding it in a ListView but that was a no go as you could not break apart the individual part between different ListView templates. So I finally opted for a ListView with LinkButtons as Select commands, each row only has a Select button in it.
3. A FromView and GridvView for the ProductDetails
The FormView is used for inserting and the GridView is used for editing and displaying ProductDetails.
So here’s the code for the page and the code behind.
<%@ Page Language="C#" MasterPageFile="~/Site.master" CodeFile="Edit.aspx.cs" Inherits="Edit" %> <asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server"> <asp:DynamicDataManager runat="server" ID="DynamicDataManager1" AutoLoadForeignKeys="true" /> <h2>Edit entry from table <%= mtProducts.DisplayName %></h2> <asp:ScriptManagerProxy runat="server" ID="ScriptManagerProxy1" /> <asp:UpdatePanel runat="server" ID="UpdatePanel1"> <ContentTemplate> <asp:ValidationSummary runat="server" ID="ValidationSummary1" EnableClientScript="true" HeaderText="List of validation errors" /> <asp:DynamicValidator runat="server" ID="DetailsViewValidator" ControlToValidate="DetailsView1" Display="None" /> <asp:DetailsView runat="server" ID="DetailsView1" DataSourceID="ldsProducts" DefaultMode="Edit" AutoGenerateEditButton="True" OnItemCommand="DetailsView1_ItemCommand" OnItemUpdated="DetailsView1_ItemUpdated" CssClass="detailstable" FieldHeaderStyle-CssClass="bold" AutoGenerateRows="False" DataKeyNames="Id"> <FieldHeaderStyle CssClass="bold" /> <Fields> <asp:DynamicField DataField="Code" /> <asp:DynamicField DataField="Display" /> <asp:DynamicField DataField="Inserted" /> </Fields> </asp:DetailsView> <asp:LinqDataSource runat="server" ID="ldsProducts" ContextTypeName="ProductsDataContext" TableName="Products" EnableUpdate="True"> <WhereParameters> <asp:DynamicQueryStringParameter /> </WhereParameters> </asp:LinqDataSource> <h2>Cultures</h2> <asp:ListView runat="server" ID="lvLanguages" DataSourceID="ldsLanguages" DataKeyNames="LCID" OnItemCommand="lvLanguages_ItemCommand"> <LayoutTemplate> <div> <span id="itemPlaceHolder" runat="server"></span> <asp:LinkButton runat="server" ID="lvLanguagesInsert" CommandName="InsertProductDetail" CommandArgument="LCID" CausesValidation="false"> add new </asp:LinkButton> <asp:Label runat="server" ID="InsertLabel" Visible="false"> <strong>add new</strong> </asp:Label> </div> </LayoutTemplate> <ItemTemplate> <asp:LinkButton runat="server" Text='<%# Eval("LCID") %>' CommandName="Select" CausesValidation="false"> </asp:LinkButton> </ItemTemplate> <SelectedItemTemplate> <strong><%# Eval("LCID") %></strong> </SelectedItemTemplate> </asp:ListView> <asp:LinqDataSource runat="server" ID="ldsLanguages" ContextTypeName="ProductsDataContext" TableName="ProductDetails" Where="ProductId == @ProductId" GroupBy="LCID" Select="new (key as LCID, it as ProductDetails)" OrderGroupsBy="key"> <WhereParameters> <asp:ControlParameter ControlID="DetailsView1" Name="ProductId" PropertyName="SelectedValue" Type="Int32" /> </WhereParameters> </asp:LinqDataSource> <br /><br /> <asp:DynamicValidator runat="server" ID="GridViewDynamicValidator" ControlToValidate="GridView1" Display="None" /> <asp:GridView runat="server" ID="GridView1" AutoGenerateColumns="False" CssClass="gridview" DataKeyNames="Id" DataSourceID="ldsProductDetails" OnRowDeleted="GridView1_RowDeleted"> <Columns> <asp:CommandField ShowEditButton="True" ShowDeleteButton="True" /> <asp:DynamicField DataField="LCID" /> <asp:DynamicField DataField="Type" /> <asp:DynamicField DataField="Price" /> <asp:DynamicField DataField="Description" /> </Columns> </asp:GridView> <asp:DynamicValidator runat="server" ID="ProductDetailDynamicValidator" ControlToValidate="fvProductDetail" ValidationGroup="ProductDetailsList_Insert" Display="None" /> <asp:FormView runat="server" ID="fvProductDetail" CssClass="gridview" DefaultMode="Insert" Visible="false" DataSourceID="ldsProductDetails" OnItemInserted="fvProductDetail_ItemInserted" onitemcommand="fvProductDetail_ItemCommand"> <InsertItemTemplate> <thead> <tr> <th> </th> <th> LCID </th> <th> Type </th> <th> Price </th> <th> Description </th> </tr> </thead> <tbody> <tr> <td> <asp:LinkButton runat="server" ID="InsertLinkButton" CommandName="Insert"> Insert </asp:LinkButton> <asp:LinkButton runat="server" ID="CancelLinkButton" CausesValidation="false" CommandName="Cancel"> Cancel </asp:LinkButton> </td> <td> <asp:DynamicControl ID="DynamicControl1" DataField="LCID" Mode="Insert" runat="server" /> </td> <td> <asp:DynamicControl ID="DynamicControl2" DataField="Type" Mode="Insert" runat="server" /> </td> <td> <asp:DynamicControl ID="DynamicControl3" DataField="Price" Mode="Insert" runat="server" /> </td> <td> <asp:DynamicControl ID="DynamicControl4" DataField="Description" Mode="Insert" runat="server" /> </td> </tr> </tbody> </InsertItemTemplate> </asp:FormView> <asp:LinqDataSource runat="server" ID="ldsProductDetails" ContextTypeName="ProductsDataContext" TableName="ProductDetails" Where="LCID == @LCID && ProductId == @ProductId" EnableDelete="True" EnableInsert="True" EnableUpdate="True" OnInserting="ldsProductDetails_Inserting"> <WhereParameters> <asp:ControlParameter ControlID="lvLanguages" Name="LCID" PropertyName="SelectedValue" Type="String" /> <asp:ControlParameter ControlID="DetailsView1" Name="ProductId" PropertyName="SelectedValue" Type="Int32" /> </WhereParameters> </asp:LinqDataSource> </ContentTemplate> </asp:UpdatePanel> </asp:Content>
Listing 5 – Edit.aspx Products page
using System; using System.Web.DynamicData; using System.Web.UI.WebControls; public partial class Edit : System.Web.UI.Page { protected MetaTable mtProducts; protected MetaTable mtProductDetails; protected void Page_Init(object sender, EventArgs e) { DynamicDataManager1.RegisterControl(DetailsView1, true); DynamicDataManager1.RegisterControl(GridView1); DynamicDataManager1.RegisterControl(fvProductDetail); } protected void Page_Load(object sender, EventArgs e) { mtProducts = ldsProducts.GetTable(); mtProductDetails = ldsProductDetails.GetTable(); Title = mtProducts.DisplayName; // set the first item to be selected lvLanguages.SelectedIndex = 0; } protected void DetailsView1_ItemCommand(object sender, DetailsViewCommandEventArgs e) { // redirect to the List page when cancel clicked if (e.CommandName == DataControlCommands.CancelCommandName) { Response.Redirect(mtProducts.ListActionPath); } } protected void DetailsView1_ItemUpdated(object sender, DetailsViewUpdatedEventArgs e) { // redirect to the List page when update clicked if (e.Exception == null || e.ExceptionHandled) { Response.Redirect(mtProducts.ListActionPath); } } protected void lvLanguages_ItemCommand(object sender, ListViewCommandEventArgs e) { // toggel the view between Insert and Edit/Display var insertButton = (LinkButton)lvLanguages.FindControl("lvLanguagesInsert"); var insertLabel = (Label)lvLanguages.FindControl("InsertLabel"); if (e.CommandName == "InsertProductDetail") { GridView1.Visible = false; lvLanguages.SelectedIndex = -1; fvProductDetail.Visible = true; insertButton.Visible = false; insertLabel.Visible = true; } if (e.CommandName == "Select") { GridView1.Visible = true; lvLanguages.SelectedIndex = 0; fvProductDetail.Visible = false; insertButton.Visible = true; insertLabel.Visible = false; } } protected void GridView1_RowDeleted(object sender, GridViewDeletedEventArgs e) { // when an item is deleted the redirect back // to the same page with the current id if (e.Exception == null || e.ExceptionHandled) { var s = DetailsView1.DataKey.Value.ToString(); var s1 = mtProducts.GetActionPath(PageAction.Edit) + @"?Id=" + s; Response.Redirect(s1); } } protected void fvProductDetail_ItemInserted(object sender, FormViewInsertedEventArgs e) { // when an item is inserted the redirect back // to the same page with the current id if (e.Exception == null || e.ExceptionHandled) { var s = DetailsView1.DataKey.Value.ToString(); var s1 = mtProducts.GetActionPath(PageAction.Edit) + @"?Id=" + s; Response.Redirect(s1); } } protected void fvProductDetail_ItemCommand(object sender, FormViewCommandEventArgs e) { // on the cancel button being presed return the page normal state var insertButton = (LinkButton)lvLanguages.FindControl("lvLanguagesInsert"); var insertLabel = (Label)lvLanguages.FindControl("InsertLabel"); if (e.CommandName == "Cancel") { GridView1.Visible = true; lvLanguages.SelectedIndex = 0; fvProductDetail.Visible = false; insertButton.Visible = true; insertLabel.Visible = false; } } protected void ldsProductDetails_Inserting(object sender, LinqDataSourceInsertEventArgs e) { // add the ProductId from the DetailsView ((ProductDetail)e.NewObject).ProductId = (int)DetailsView1.DataKey.Value; } }
Listing 6 – Edit.aspx.cs code behind for ProductDetails
The comments should explain what is happening.
Thanks for reading
UPDATE
I’ve just had a thought and decided to add it here :D
I’ve re done the ListView Tabs and added some tool tips:
<asp:ListView runat="server" ID="lvLanguages" DataSourceID="ldsLanguages" DataKeyNames="LCID" OnItemCommand="lvLanguages_ItemCommand"> <LayoutTemplate> <div> <span id="itemPlaceHolder" runat="server"></span> <asp:LinkButton runat="server" ID="lvLanguagesInsert" ToolTip="Insert new Product Decription" CommandName="InsertProductDetail" CommandArgument="LCID" CausesValidation="false"> add new </asp:LinkButton> <asp:Label runat="server" ID="InsertLabel" ToolTip="Insert new Product Decription" Visible="false"> <strong>add new</strong> </asp:Label> </div> </LayoutTemplate> <ItemTemplate> <asp:LinkButton runat="server" Text='<%# Eval("LCID") %>' CommandName="Select" ToolTip='<%# Eval("Name") %>' CausesValidation="false"> </asp:LinkButton> </ItemTemplate> <SelectedItemTemplate> <strong title='<%# Eval("Name") %>'><%# Eval("LCID") %></strong> </SelectedItemTemplate> </asp:ListView> <asp:LinqDataSource runat="server" ID="ldsLanguages" OnSelecting="ldsLanguages_Selecting"> <WhereParameters> </WhereParameters> </asp:LinqDataSource>
Listing 7 – Update ListView and associated LinqDataSource
protected void ldsLanguages_Selecting(object sender, LinqDataSourceSelectEventArgs e) { var DC = new ProductsDataContext(); var productCultures = from pdc in DC.ProductDetails where pdc.ProductId == (int)DetailsView1.DataKey.Value select pdc; var cultureDetails = from pd in productCultures.ToArray() join pc in CultureInfo.GetCultures(CultureTypes.NeutralCultures) on pd.LCID equals pc.TwoLetterISOLanguageName select new { LCID = pd.LCID, Name = pc.EnglishName }; e.Result = cultureDetails; }
Listing 8 – Selecting event handler for the ldsLanguages LinqDataSource
What I’ve done is taken the data from the SQL tables and merged it with the CultureInfo to produce a list of cultures plus their English names. Note I’ve removed the WHERE parameters etc from the LinqDataSource and added the OnSelecting event handler.
Yet another neat use of Linq Yes I could have done a separate article on this but I though it fitted well here.
1 comment:
Steve. good stuff, and I appreciate your responses on the asp.net forum. I'm wondering if you can point me in a good direction.
I'm having a hard time getting the ListView to do inserts using EntityDataSource Dynamic Data. I have been unable to find any documentation, and I've been struggling with this for days. Any Ideas. I posted this at the forum here http://forums.asp.net/p/1311101/2581836.aspx#2581836
Many Thanks,
Jeff
Post a Comment