Click here to Skip to main content
15,882,113 members
Articles / Web Development / ASP.NET

An Innovative Architecture to Develop .NET Business Web Forms (1) - Overview

Rate me:
Please Sign up or sign in to vote.
4.00/5 (12 votes)
11 Mar 2010GPL38 min read 179.4K   44   9
The series of articles introduces an innovative architecture to develop business web forms in enterprise software development which is better performance, higher productivity, more configurability and easier maintainability than traditional either ASP.NET or MVC development.

Introduction

The series of articles introduces an innovative architecture to develop business web forms in enterprise software development which is better performance, higher productivity, more configurability and easier maintainability than traditional either ASP.NET or MVC development. This is the first article to introduce the facing problems on traditional ASP.NET and MVC development and a new solution to improve them. Then I give a code snippet on developing a product management web form in the new solution.

Problems

When we develop a web form in ASP.NET or MVC, we need work on the following items:

  1. Create view template and code behind (controller)
  2. Style adjustment
  3. Write event handlers for all actions
  4. Control visibility of web controls for different actions manually
  5. Permission integration manually
  6. Hard to write unit tests (not for MVC)
  7. Something else

With all of this stuff, the source code of developed web forms are very complex usually by mixed logics of webcontrols' visibility control, business logics, style management, permission check, etc. The web forms are less productive, bug-ness and hard maintainable.

New Solution

RapidWebDev solution implements a new UI framework based on ASP.NET which is designed to solve the facing problems. The principle of design is to abstract requirement of web forms for usual business scenarios and define XML schema to configure web forms so that user behavior, style, permission are all managed by the framework. And some interfaces of the framework are required to be implemented which are callback for user behaviors. E.G. there is a query panel with filters configured. When a user clicks query button in query panel, method Query of the implementation to interface IDynamicPage will be invoked with query parameters.

With this design,

  1. web forms are configurable and maintainable
  2. code complexity is decreased obviously
  3. implementations are reusable and testable
  4. consolidate UI style by the framework

So, the roadmap of introduction to RapidWebDev UI framework in this article is to introduce:

  1. web form structure/layout
  2. communication between components in a web form
  3. what is required to develop a web form
  4. a sample product management application
  5. how permission works

This article doesn't introduce the detail implementation of the architecture but focusing on high level innovative points. I will introduce the details in later articles in the series.

Web Form Structure

We abstract generic panel types for usual business scenarios - "query panel" to set query filters; "grid panel" to display queried records; "detail panel" to create/update/view/delete a single record; "button panel" to configure buttons for custom actions; "aggregate panels" for bulk operate multiple records selected in grid, etc.

Let's go through some usual business scenarios in enterprise software.

  • in a product management web form, some users can create a draft product, approve products, export or forward them. There should be a "query panel" for users to setup filters, a "grid panel" to display queried products in list, a "detail panel" to create/update/view a single product detail information, an approve panel ("aggregate panel") to approve selected products in the grid and a forward panel ("aggregate panel") to forward selected products to someone else by mail, etc.
  • in a stock-in product web form, users can scan product number in a scanning panel ("detail panel" with a product number input textbox) and get scanned products displayed in a temporary "grid panel". Users can submit multiple selected scanned products in grid and get a stock-in sheet ("aggregate panel").
  • in a news management web form, users can setup new query filters in "query panel" and get them displayed in "grid panel". Users can add a new news or select a exist news in grid to update, delete or view detail information in "detail panel".

Let's preview these panel types as the following screenshot.

Query, Button and Grid panel

Image 1

Detail panel

Image 2

Aggregate panel

Image 3

Communication b/w Web Form Panels

The communication b/w panels is described as the following workflow chart:

Image 4

Query Panel

When a user clicks Query button in query panel, the query panel grabs all query filters and sends an asynchronous request to web server and pull records rendered into grid panel.

Grid Panel

Display queried records. There are three buttons that can be configured for each row in the grid, they're Edit, View and Delete. When the user clicks Edit/View button in a row, the detail panel is shown up. In Edit mode, the grid refreshes automatically after the user saves the editing record successfully. The grid supports column resizing, show/hide column, sorting, paging, row preview automatically.

Button Panel

Configure custom buttons in web form that each button is assigned with a command argument. When a button is clicked, the aggregate panel with the same command argument will be displayed. But there are three command arguments that are predefined which do not relate to aggregate panels. They're Add, Print and DownloadToExcel. A blank detail panel is displayed when Add button is clicked. Print and DownloadToExcel button is used to print or export all grid records (include in other pagination) to Excel without writing any code.

Detail Panel

Create/update/view a single record. Grid panel refreshes automatically after the user saves in detail panel.

Aggregate Panel

Any custom operations. Grid panel refreshes automatically after the user saves in aggregate panel.

What're Required to Develop a Web Form

XML configuration and implementation of interface IDynamicPage is required for a web form. In XML configuration, we should configure query filters, grid fields, buttons, detail panel and aggregate panels. Don't worry about dynamic data in static XML configuration. XML configuration allows to configure callback processors.

When we need detail panel in a web form, we need to develop an ascx template without code behind and implements interface IDetailPanel. The ascx template is used to render the web controls in detail panel as the following screenshot. The IDetailPanel implementation integrates the ascx template for business logics.

The aggregate panel development is the same to detail panel.

A Sample Product Management Application

We have a requirement of a product management web form as follows:

  1. Create a new product
  2. Edit/view an existed product
  3. Delete a single product
  4. Bulk delete multiple selected products
  5. Print queried products
  6. Export queried product to Excel
  7. Show the product changing logs when edit/view a product

Step 1

Implement dynamic page interface which is used for querying products and deleting a single product. You see in the method Query, actually we don't assemble query expression. The argument "parameter" passed from the framework can be converted to LinqPredicate directly. The framework not only uses query panel XML configuration to render UI but also assemble query expression. So when we have requirement to change query filters, we only need to change the XML configuration without compilation.

C#
/// <summary>
/// Dynamic page to manage products
/// </summary>
public class ProductDynamicPage : DynamicPage
{
    private IAuthenticationContext authenticationContext = 
	SpringContext.Current.GetObject<IAuthenticationContext>();

    /// <summary>
    /// Query products by parameters.
    /// </summary>
    /// <param name="parameter"></param>
    /// <returns></returns>
    public override QueryResults Query(QueryParameter parameter)
    {
        using (ProductManagementDataContext ctx = 
		DataContextFactory.Create<ProductManagementDataContext>())
        {
            IQueryable<Product> q = from p in ctx.Products 
                                    where p.ApplicationId == 
				authenticationContext.ApplicationId 
                                    select p;

            LinqPredicate predicate = parameter.Expressions.Compile();
            if (predicate != null && !string.IsNullOrEmpty(predicate.Expression))
                q = q.Where(predicate.Expression, predicate.Parameters);

            if (parameter.SortExpression != null)
                q = q.OrderBy(parameter.SortExpression.Compile());

            int recordCount = q.Count();
            var results = q.Skip(parameter.PageIndex * 
		parameter.PageSize).Take(parameter.PageSize).ToList();
            return new QueryResults(recordCount, results);
        }
    }

    /// <summary>
    /// Delete the product by id.
    /// </summary>
    /// <param name="entityId"></param>
    public override void Delete(string entityId)
    {
        Guid productId = new Guid(entityId);
        using (TransactionScope transactionScope = new TransactionScope())
        using (ProductManagementDataContext ctx = 
		DataContextFactory.Create<ProductManagementDataContext>())
        {
            ctx.ProductLogs.Delete(log => log.ProductId == productId);
            ctx.Products.Delete(p => p.Id == productId);
            ctx.SubmitChanges();
            transactionScope.Complete();
        }
    }
}

Step 2

Create an ascx template for product detail panel. The template is used to render the form to add/update/view a product. The ASP.NET server control will be bound to the member declaration of detail panel implementation class automatically by ID.

ASP.NET
<ajax:TabContainer ID="TabContainer" runat="server">
    <ajax:TabPanel ID="TabPanelProduct" HeaderText="Product" runat="server">
        <ContentTemplate>                        
            <table cellpadding="0" cellspacing="0" class="table6col">
                <tr>
                    <td class="c1" nowrap="nowrap">Category: </td>
                    <td class="c2">
                        <My:ComboBox ID="DropDownListCategory" Mode="Local" 
			Editable="true" ForceSelection="true" runat="server" />
                        <label for="<%= this.DropDownListCategory.ClientID %>" 
			class="required">*</label>
                    </td>
                    <td class="c1" nowrap="nowrap">Name: </td>
                    <td class="c2">
                        <My:TextBox ID="TextBoxName" CssClass="textboxShort" 
			MaxLength="256" runat="server" />
                        <label for="<%= this.TextBoxName.ClientID %>" 
			class="required">*</label>
                    </td>
                    <td class="c1" nowrap="nowrap">Number: </td>
                    <td class="c2">
                        <My:TextBox ID="TextBoxNumber" 
			CssClass="textboxShort" MaxLength="32" runat="server" />
                        <label for="<%= this.TextBoxNumber.ClientID %>" 
			class="required">*</label>
                    </td>
                </tr>
                <tr>
                    <td class="c1" nowrap="nowrap">Manufactory: </td>
                    <td class="span" colspan="5">
                        <My:TextBox ID="TextBoxManufactory" CssClass="textarea" 
			MaxLength="256" Width="92.7%" runat="server" />
                    </td>
                </tr>
                <tr>
                    <td class="c1" nowrap="nowrap">Description: </td>
                    <td class="span" colspan="5">
                        <My:TextBox ID="TextBoxDescription" CssClass="textarea" 
			Width="92.7%" runat="server" />
                    </td>
                </tr>
                    
                <My:ExtensionDataForm ID="ProductExtensionDataForm" runat="server" />
                
                <asp:PlaceHolder ID="PlaceHolderOperateContext" Visible="false" 
			runat="server">
                    <tr>
                        <td colspan="6"><hr /></td>
                    </tr>
                    <tr>
                        <td class="c1" nowrap="true">Created On: </td>
                        <td class="c2">
                            <My:TextBox ID="TextBoxCreatedOn" 
				CssClass="textboxShort readonly" 
				ReadOnly="true" runat="server" />
                        </td>
                        <td class="c1" nowrap="true">Created By: </td>
                        <td class="span" colspan="3">
                            <My:UserLink ID="UserLinkCreatedBy" runat="server" />
                        </td>
                    </tr>
                    <tr>
                        <td class="c1" nowrap="true">Updated On: </td>
                        <td class="c2">
                            <My:TextBox ID="TextBoxLastUpdatedOn" 
				CssClass="textboxShort readonly" ReadOnly="true" 
				runat="server" />
                        </td>
                        <td class="c1" nowrap="true">Updated By: </td>
                        <td class="span" colspan="3">
                            <My:UserLink ID="UserLinkLastUpdatedBy" runat="server" />
                        </td>
                    </tr>
                    </tr>
                </asp:PlaceHolder>
            </table>
        </ContentTemplate>
    </ajax:TabPanel>
    <ajax:TabPanel ID="TabPanelProductLogs" HeaderText="Logs" Visible="false" 
		runat="server">
        <ContentTemplate>
            <table cellpadding="2" cellspacing="0" border="1" style="width:100%; 
		border-collapse:separate">
                <asp:Repeater ID="RepeaterProductLogs" runat="server">
                    <HeaderTemplate>
                        <tr>
                            <th style="width: 60px; padding:2px; 
				background-color: Gray; color: White">
                                Number
                            </th>
                            <th style="padding:2px; background-color: Gray; 
				color: White">Body</th>
                            <th style="width: 120px; padding:2px; 
				background-color: Gray; color: White">
                                User
                            </th>
                            <th style="width: 170px; padding:2px; 
				background-color: Gray; color: White">
                                Logged On
                            </th>
                        </tr>
                    </HeaderTemplate>
                    <ItemTemplate>
                        <tr>
                            <td style="text-align:center; padding:2px">
                                <%# Container.ItemIndex + 1 %>
                            </td>
                            <td style="padding:2px">
                                <%# DataBinder.Eval(Container.DataItem, "Body") %>
                            </td>
                            <td style="padding:2px">
                                <%# UserLink.BuildUserLink(
                                    DataBinder.Eval(Container.DataItem, 
					"LoggedBy").ToString())%>
                            </td>
                            <td style="padding:2px">
                                <%# DataBinder.Eval(Container.DataItem, "LoggedOn") %>
                            </td>
                        </tr>
                    </ItemTemplate>
                </asp:Repeater>
            </table>
        </ContentTemplate>
    </ajax:TabPanel>
</ajax:TabContainer>

Step 3

Implement detail panel interface which is used for add/update/view a single product. The references of data members with custom attribute Binding are resolved by the framework from ascx template automatically. The data member name should be the same as the server control ID in the template. The argument parentControlPath of Binding(string parentControlPath) indicates which ASP.NET ITemplate control includes the control.

C#
/// <summary>
/// Detail panel page for products.
/// </summary>
public class ProductDetailPanel : DetailPanelPage
{
    #region Binding Controls

    [Binding("TabContainer/TabPanelProduct")]
    protected DropDownList DropDownListCategory;
    [Binding("TabContainer/TabPanelProduct")]
    protected TextBox TextBoxName;
    [Binding("TabContainer/TabPanelProduct")]
    protected TextBox TextBoxNumber;
    [Binding("TabContainer/TabPanelProduct")]
    protected TextBox TextBoxManufactory;
    [Binding("TabContainer/TabPanelProduct")]
    protected TextBox TextBoxDescription;
    [Binding("TabContainer/TabPanelProduct")]
    protected ExtensionDataForm ProductExtensionDataForm;

    [Binding("TabContainer")]
    protected AjaxControlToolkit.TabPanel TabPanelProductLogs;
    [Binding("TabContainer/TabPanelProduct")]
    protected PlaceHolder PlaceHolderOperateContext;
    [Binding("TabContainer/TabPanelProduct")]
    protected TextBox TextBoxCreatedOn;
    [Binding("TabContainer/TabPanelProduct")]
    protected UserLink UserLinkCreatedBy;
    [Binding("TabContainer/TabPanelProduct")]
    protected TextBox TextBoxLastUpdatedOn;
    [Binding("TabContainer/TabPanelProduct")]
    protected UserLink UserLinkLastUpdatedBy;

    [Binding("TabContainer/TabPanelProductLogs")]
    protected Repeater RepeaterProductLogs;

    #endregion

    private IConcreteDataApi concreteDataApi = 
	SpringContext.Current.GetObject<IConcreteDataApi>();
    private IMetadataApi metadataApi = SpringContext.Current.GetObject<IMetadataApi>();
    private IAuthenticationContext authenticationContext = 
	SpringContext.Current.GetObject<IAuthenticationContext>();

    /// <summary>
    /// Load product categories into dropdown control when the form is loaded.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public override void OnLoad(IRequestHandler sender, DetailPanelPageEventArgs e)
    {
        if (!sender.IsPostBack)
        {
            var productCategories = concreteDataApi.FindAllByType("ProductCategory")
                .Where(c => c.DeleteStatus == DeleteStatus.NotDeleted)
                .OrderBy(c => c.Name);

            this.DropDownListCategory.Items.Clear();
            this.DropDownListCategory.Items.Add("");
            this.DropDownListCategory.SelectedIndex = 0;

            foreach (ConcreteDataObject productCategory in productCategories)
                this.DropDownListCategory.Items.Add
		(new ListItem(productCategory.Name, productCategory.Id.ToString()));
        }

        IObjectMetadata productMetadata = metadataApi.GetType("Product");
        if (ProductExtensionDataForm != null)
            this.ProductExtensionDataForm.CreateDataInputForm(productMetadata.Id);
    }

    /// <summary>
    /// Load an existed product into UI.
    /// </summary>
    /// <param name="entityId"></param>
    public override void LoadWritableEntity(string entityId)
    {
        Guid productId = new Guid(entityId);
        using (ProductManagementDataContext ctx = 
		DataContextFactory.Create<ProductManagementDataContext>())
        {
            Product p = ctx.Products.FirstOrDefault(product => 
		product.Id == productId && product.ApplicationId == 
		authenticationContext.ApplicationId);
            if (p == null)
                throw new ValidationException("The product id doesn't exist.");

            // load hardwired properties.
            this.TextBoxName.Text = p.Name;
            this.TextBoxNumber.Text = p.Number;
            this.DropDownListCategory.SelectedValue = p.CategoryId.ToString();
            this.TextBoxDescription.Text = p.Description;
            this.TextBoxManufactory.Text = p.Manufactory;

            // load dynamic properties.
            this.ProductExtensionDataForm.SetControlValuesFromObjectProperties(p);

            this.PlaceHolderOperateContext.Visible = true;
            this.UserLinkCreatedBy.UserId = p.CreatedBy.ToString();
            this.TextBoxCreatedOn.Text = 
		LocalizedDateTime.ToDateTimeString(p.CreatedOn);

            if (p.LastUpdatedBy.HasValue)
                this.UserLinkLastUpdatedBy.UserId = p.LastUpdatedBy.ToString();

            if (p.LastUpdatedOn.HasValue)
                this.TextBoxLastUpdatedOn.Text = 
		LocalizedDateTime.ToDateTimeString(p.LastUpdatedOn.Value);

            // bind the product logs.
            this.TabPanelProductLogs.Visible = true;
            this.RepeaterProductLogs.DataSource = 
		p.ProductLogs.OrderBy(l => l.LoggedOn).ToList();
            this.RepeaterProductLogs.DataBind();
        }
    }

    /// <summary>
    /// Create a new product.
    /// </summary>
    /// <returns></returns>
    public override string Create()
    {
        using (ProductManagementDataContext ctx = 
		DataContextFactory.Create<ProductManagementDataContext>())
        {
            this.Validate(ctx, null);

            IObjectMetadata productMetadata = metadataApi.GetType("Product");
            Product p = new Product
            {
                Name = this.TextBoxName.Text,
                Number = this.TextBoxNumber.Text,
                CategoryId = new Guid(this.DropDownListCategory.SelectedValue),
                ApplicationId = authenticationContext.ApplicationId,
                Description = this.TextBoxDescription.Text,
                Manufactory = this.TextBoxManufactory.Text,
                CreatedBy = authenticationContext.User.UserId,
                CreatedOn = DateTime.Now,
                ExtensionDataTypeId = productMetadata != null ? 
				productMetadata.Id : Guid.Empty
            };

            if (ProductExtensionDataForm != null)
                this.ProductExtensionDataForm.SetObjectPropertiesFromControlValues(p);
            ctx.Products.InsertOnSubmit(p);

            ProductLog productLog = new ProductLog
            {
                Product = p,
                Body = "The product is created",
                LoggedBy = authenticationContext.User.UserId,
                LoggedOn = DateTime.Now
            };

            ctx.ProductLogs.InsertOnSubmit(productLog);
            ctx.SubmitChanges();

            return p.Id.ToString();
        }
    }

    /// <summary>
    /// Update an existed product.
    /// </summary>
    /// <param name="entityId"></param>
    public override void Update(string entityId)
    {
        Guid productId = new Guid(entityId);
        using (ProductManagementDataContext ctx = 
		DataContextFactory.Create<ProductManagementDataContext>())
        {
            this.Validate(ctx, productId);

            Product p = ctx.Products.FirstOrDefault(product => product.Id == productId);
            if (p == null)
                throw new ValidationException("The product id doesn't exist.");

            p.Name = this.TextBoxName.Text;
            p.Number = this.TextBoxNumber.Text;
            p.CategoryId = new Guid(this.DropDownListCategory.SelectedValue);
            p.Description = this.TextBoxDescription.Text;
            p.Manufactory = this.TextBoxManufactory.Text;
            p.LastUpdatedBy = authenticationContext.User.UserId;
            p.LastUpdatedOn = DateTime.Now;

            if (this.ProductExtensionDataForm != null)
                this.ProductExtensionDataForm.SetObjectPropertiesFromControlValues(p);

            ProductLog productLog = new ProductLog
            {
                Product = p,
                Body = "The product is modified...",
                LoggedBy = authenticationContext.User.UserId,
                LoggedOn = DateTime.Now
            };

            ctx.ProductLogs.InsertOnSubmit(productLog);
            ctx.SubmitChanges();
        }
    }

    private void Validate(ProductManagementDataContext ctx, Guid? productId)
    {
        using (ValidationScope validation = new ValidationScope())
        {
            if (string.IsNullOrEmpty(this.DropDownListCategory.SelectedValue))
                validation.Error("The product category should not be empty.");

            if (string.IsNullOrEmpty(this.TextBoxName.Text))
                validation.Error("The product name should not be empty.");

            if (string.IsNullOrEmpty(this.TextBoxNumber.Text))
                validation.Error("The product number should not be empty.");

            Guid productIdValue = productId.HasValue ? productId.Value : Guid.NewGuid();
            if (ctx.Products.Count(p => p.Id != productIdValue && 
			p.Name == this.TextBoxName.Text) > 0)
                validation.Error(@"The product name ""{0}"" does exist.", 
			this.TextBoxName.Text);

            if (ctx.Products.Count(p => p.Id != productIdValue && 
			p.Number == this.TextBoxNumber.Text) > 0)
                validation.Error(@"The product number ""{0}"" does exist.", 
			this.TextBoxNumber.Text);
        }
    }
} 

Step 4

Create another ascx template for bulk deletion. The content of the template is quite simple which is used to display a confirmation message.

ASP.NET
Are you sure to bulk delete the selected 
<asp:Label ID="LabelProductCount" ForeColor="Red" runat="server" /> products?

Step 5

Implement aggregate panel for bulk deleting multiple products. When the method Save is invoked, the selected products in grid will be passed in automatically by the framework. So the implementation is only focused on operations against the passing entity IDs.

C#
/// <summary>
/// Aggregate panel for multiple products bulk deletion.
/// </summary>
public class ProductBulkDeleteAggregatePanel : AggregatePanelPage
{
    private IAuthenticationContext authenticationContext = 
	SpringContext.Current.GetObject<IAuthenticationContext>();

    [Binding]
    protected Label LabelProductCount;

    /// <summary>
    /// Display count of deleting products when the page is loaded.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public override void OnLoad(IRequestHandler sender, AggregatePanelPageEventArgs e)
    {
        this.LabelProductCount.Text = e.EntityIdEnumerable.Count().ToString();
    }

    /// <summary>
    /// Bulk delete products by enumerable ids.
    /// </summary>
    /// <param name="commandArgument"></param>
    /// <param name="entityIdEnumerable"></param>
    public override void Save(string commandArgument, 
	IEnumerable<string> entityIdEnumerable)
    {
        if (!string.Equals("BulkDelete", commandArgument, 
		StringComparison.OrdinalIgnoreCase)) return;

        using(TransactionScope transactionScope = new TransactionScope())
        using (ProductManagementDataContext ctx = 
	DataContextFactory.Create<ProductManagementDataContext>())
        {
            Guid[] productIdArray = entityIdEnumerable.Select
		(entityId => new Guid(entityId)).ToArray();
            IEnumerable<Product> products = ctx.Products.Where
		(p => productIdArray.Contains(p.Id) && 
		p.ApplicationId == authenticationContext.ApplicationId);
            ctx.Products.DeleteAllOnSubmit(products);
            ctx.SubmitChanges();
            transactionScope.Complete();
        }
    }
}

Step 6

XML configure product management web form. The ObjectId is an unique key of a web form which is used to access the web form as URI: ~/[ObjectId]/DynamicPage.svc.

XML
<?xml version="1.0" encoding="utf-8" ?>
<Page xmlns="http://www.rapidwebdev.org/schemas/dynamicpage" 
    ObjectId="ProductManagement" 
    Type="ProductManagement.Web.ProductDynamicPage, ProductManagement">
    <Title>Product Management</Title>
    <PermissionValue>ProductManagement</PermissionValue>
    <Panels>
        <QueryPanel HeaderText="Query">
            <TextBox FieldName="Name" Label="Name: " />
            <TextBox FieldName="Number" Label="Number: " />
            <ComboBox FieldName="CategoryId" 
            Label="Category: " 
                Editable="false" 
                ForceSelection="true" 
                FieldValueType="System.Guid">
                <DynamicDataSource TextField="Name" 
                    ValueField="Id"
                    Url="/Services/ConcreteDataService.svc/json/
		    FindByKeyword?concreteDataType=ProductCategory&amp;limit=50" />
            </ComboBox>
        </QueryPanel>
        
        <ButtonPanel ButtonAlignment="Left">
            <Button CommandArgument="New" Type="Button"  Text="Add" />
            <Button CommandArgument="BulkDelete" Type="Button" Text="Bulk Delete">
                <GridSelectionRequired WarningMessage=
			"Please select the deleting products." />
            </Button>
            <Button CommandArgument="Print" Type="Button" Text="Print" />
            <Button CommandArgument="DownloadExcel" Type="Button" Text="Export Excel" />
        </ButtonPanel>
        
        <GridViewPanel HeaderText="Query Results"
            EntityName="Product"
            EnabledCheckBoxField="true"
            PageSize="25"
            PrimaryKeyFieldName="Id"
            DefaultSortField="LastUpdatedOn"
            DefaultSortDirection="DESC">
            <ViewButton />
            <EditButton />
            <DeleteButton />
            <Fields>
                <Field FieldName="Number" HeaderText="Number" />
                <Field FieldName="Name" HeaderText="Name" />
                <Field FieldName="CategoryId" HeaderText="Category" Width="80">
                    <Transform-Callback Type=
			"RapidWebDev.Platform.Web.DynamicPage.
			GridViewFieldValueTransformCallback.ShowConcreteDataName, 
			RapidWebDev.Platform"/>
                </Field>
                <Field FieldName="Manufactory" HeaderText="Manufactory" />
                <Field FieldName="LastUpdatedBy" HeaderText="Updated By" Align="Center">
                    <Transform-Callback Type="RapidWebDev.Platform.Web.DynamicPage.
			GridViewFieldValueTransformCallback.ShowUserDisplayName, 
			RapidWebDev.Platform"/>
                </Field>
                <Field FieldName="LastUpdatedOn" HeaderText="Updated On" Align="Center" 
			Width="150" />
                <RowView FieldName="Description" />
            </Fields>
        </GridViewPanel>
        
        <DetailPanel HeaderText="Product Detail" 
		ShowMessageAfterSavedSuccessfully="false">
            <Type>ProductManagement.Web.ProductDetailPanel, ProductManagement</Type>
            <SkinPath>~/Templates/ProductManagement/Product.ascx</SkinPath>
            <SaveAndAddAnotherButton IsFormDefaultButton="true" />
            <SaveAndCloseButton />
            <CancelButton />
        </DetailPanel>
        
        <AggregatePanel HeaderText="Product Bulk Deletion Confirmation" 
            CommandArgument="BulkDelete" 
            ShowMessageAfterSavedSuccessfully="true">
            <Type>ProductManagement.Web.ProductBulkDeleteAggregatePanel, 
		ProductManagement</Type>
            <SkinPath>~/Templates/ProductManagement/ProductBulkDeleteAggregatePanel.ascx
	   </SkinPath>
            <SaveButton />
            <CancelButton />
        </AggregatePanel>
    </Panels>
</Page>

Finally, the web form works in the URL: http://[host]:[port]/ProductManagement/DynamicPage.svc. There is an existing article introducing the details on implementing a whole product management system in RapidWebDev.

How Permission Works

As we have seen in XML configuration, there is a segment PermissionValue for the web form. Actually there are many permission values derived from the value. E.G. "ProductManagement" is configured for the web form. "ProductManagement.Add" is the permission for adding a new product. "ProductManagement.Update" is the permission for editing an existed product. "ProductManagement.[CommandArgument]" is the permission for special button and aggregate panel with that command argument. The permission is intelligently controlled by the framework so that developers don't need to do anything.

There is an interface RapidWebDev.UI.IPermissionBridge which is used for the UI framework to integrate with external systems. The method HasPermission is invoked by the framework to check whether the current user having the permission on a behavior. The framework checks permission on both rendering UI interactive elements and callback UI implementation.

C#
/// <summary>
/// Returns true if the current user has any permissions in specified permission.
/// </summary>
/// <param name="permissionValue">Permission value.</param>
/// <returns>Returns true if the current user has any 
/// permissions in specified permission.</returns>
bool HasPermission(string permissionValue);

Finally

I will introduce the detail of RapidWebDev UI framework in later articles of the series. UI framework is only an important component of RapidWebDev solution, but not all.

What's RapidWebDev

Website: http://www.rapidwebdev.org

RapidWebDev is an infrastructure helps engineers to develop enterprise software solutions in Microsoft .NET easily and productively. It consists of an extendable and maintainable web system architecture with a suite of generic business model, APIs and services as fundamental functionalities needed in development for almost all business solutions. So when engineers develop solutions in RapidWebDev, they can have a lot of reusable and ready things then they can more focus on business logics implementation. In practice, we can save more than 50% time on developing a high quality and performance business solution than traditional ASP.NET development.

Related Topics

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
President TCWH
China China
I've worked as a software architect and developer based on Microsoft .NET framework and web technology since 2002, having rich experience on SaaS, multiple-tier web system and website development. I've devoted into open source project development since 2003, and have created 3 main projects including DataQuicker (ORM), ExcelQuicker (Excel Report Generator), and RapidWebDev (Enterprise-level CMS)

I worked in BenQ (8 mo), Bleum (4 mo), Newegg (1 mo), Microsoft (3 yr) and Autodesk (5 yr) before 2012. Now I own a startup company in Shanghai China to deliver the exceptional results to global internet users by leveraging the power of Internet and the local excellence.

Comments and Discussions

 
Question如何让在QueryPanel里的ComboBox关联一个数据库,让数据库中的数据作为ComboBox的子项,点击即可选择 Pin
xaq69388826512-Jan-11 5:10
xaq69388826512-Jan-11 5:10 
GeneralMy vote of 1 Pin
MR_SAM_PIPER15-Mar-10 13:54
MR_SAM_PIPER15-Mar-10 13:54 
General你这个东西和我之前写的很像,不知道是谁先谁后。 Pin
reborn_zhang8-Mar-10 5:57
reborn_zhang8-Mar-10 5:57 
GeneralRe: 你这个东西和我之前写的很像,不知道是谁先谁后。 Pin
Eunge8-Mar-10 15:49
Eunge8-Mar-10 15:49 
GeneralRe: 你这个东西和我之前写的很像,不知道是谁先谁后。 Pin
xaq69388826512-Jan-11 5:02
xaq69388826512-Jan-11 5:02 
hello ,不知道怎么在QueryPanel定义一个ComboBox,然后在初始化时让他关联到一个DataBase,然后把数据库中读取的值作为ComboBox的Items,Thanks for your help ,很着急,最好能把详细的步骤发给我,谢谢,我的信箱xaq693888265@126.com
General[My vote of 2] Not getting the value Pin
paul.vencill8-Mar-10 3:35
paul.vencill8-Mar-10 3:35 
GeneralRe: [My vote of 2] Not getting the value [modified] Pin
Eunge8-Mar-10 4:37
Eunge8-Mar-10 4:37 
GeneralRe: [My vote of 2] Not getting the value [modified] Pin
paul.vencill11-Mar-10 1:40
paul.vencill11-Mar-10 1:40 
GeneralPerfect! Pin
Dave Watt4-Mar-10 20:19
Dave Watt4-Mar-10 20:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.