65.9K
CodeProject 正在变化。 阅读更多。
Home

一种创新的.NET业务Web Forms开发架构(1)-概述

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (11投票s)

2010年3月5日

GPL3

8分钟阅读

viewsIcon

181036

本系列文章介绍了一种用于在企业软件开发中构建业务Web Forms的创新架构,与传统的ASP.NET或MVC开发相比,它具有更好的性能、更高的生产力、更强的可配置性和更易于维护性。

引言

本系列文章介绍了一种用于在企业软件开发中构建业务Web Forms的创新架构,与传统的ASP.NET或MVC开发相比,它具有更好的性能、更高的生产力、更强的可配置性和更易于维护性。这是第一篇文章,介绍了传统ASP.NET和MVC开发所面临的问题以及改进这些问题的新解决方案。然后,我将提供一个关于在新解决方案中开发产品管理Web Form的代码片段。

问题

当我们在ASP.NET或MVC中开发Web Form时,我们需要处理以下事项:

  1. 创建视图模板和代码隐藏(控制器)
  2. 样式调整
  3. 为所有操作编写事件处理程序
  4. 手动控制Web控件在不同操作下的可见性
  5. 手动集成权限
  6. 难以编写单元测试(MVC除外)
  7. 其他

有了这些内容,开发的Web Forms的源代码通常非常复杂,混合了Web控件可见性控制、业务逻辑、样式管理、权限检查等逻辑。Web Forms的生产力较低,容易出错且难以维护。

新解决方案

RapidWebDev解决方案实现了一个基于ASP.NET的新UI框架,旨在解决面临的问题。其设计原则是抽象出常规业务场景下Web Forms的需求,并定义XML模式来配置Web Forms,从而使用户行为、样式和权限都由框架管理。框架的某些接口需要实现,用于回调用户行为。例如,有一个带有过滤器配置的查询面板。当用户单击查询面板中的查询按钮时,将调用实现IDynamicPage接口的Query方法,并传入查询参数。

通过这种设计,

  1. Web Forms可配置且易于维护
  2. 代码复杂度明显降低
  3. 实现可重用且可测试
  4. 通过框架统一UI样式

因此,本文介绍RapidWebDev UI框架的路线图是介绍:

  1. Web Form结构/布局
  2. Web Form组件之间的通信
  3. 开发Web Form所需条件
  4. 一个产品管理示例应用程序
  5. 权限如何工作

本文不介绍架构的详细实现,而是侧重于高层次的创新点。我将在后续文章中详细介绍。

Web Form结构

我们为常规业务场景抽象出通用的面板类型 - “查询面板”用于设置查询过滤器;“网格面板”用于显示查询到的记录;“详细信息面板”用于创建/更新/查看/删除单个记录;“按钮面板”用于配置自定义操作按钮;“聚合面板”用于批量处理网格中选定的多个记录等。

让我们通过一些企业软件中的常见业务场景。

  • 在产品管理Web Form中,一些用户可以创建产品草稿、审批产品、导出或转发产品。应该有一个“查询面板”供用户设置过滤器,一个“网格面板”以列表形式显示查询到的产品,一个“详细信息面板”用于创建/更新/查看单个产品的详细信息,一个审批面板(“聚合面板”)用于审批网格中选定的产品,以及一个转发面板(“聚合面板”)用于通过邮件将选定的产品转发给其他人等。
  • 在入库产品Web Form中,用户可以在扫描面板(带有产品编号输入框的“详细信息面板”)中扫描产品编号,并在临时“网格面板”中显示扫描到的产品。用户可以提交网格中选定的多个扫描产品,并获得一个入库单(“聚合面板”)。
  • 在新闻管理Web Form中,用户可以在“查询面板”中设置新的查询过滤器,并在“网格面板”中显示它们。用户可以在“详细信息面板”中添加新新闻,或选择现有新闻进行更新、删除或查看详细信息。

让我们通过以下截图预览这些面板类型。

查询、按钮和网格面板

详细信息面板

聚合面板

Web Form面板之间的通信

面板之间的通信通过以下流程图描述:

查询面板

当用户单击查询面板中的*查询*按钮时,查询面板会收集所有查询过滤器,并向Web服务器发送异步请求,然后将记录拉取并渲染到网格面板。

网格面板

显示查询到的记录。网格中的每行都可以配置三个按钮:编辑、查看和删除。当用户单击行中的编辑/查看按钮时,将显示详细信息面板。在编辑模式下,用户成功保存编辑记录后,网格会自动刷新。网格支持列大小调整、显示/隐藏列、排序、分页、自动行预览。

按钮面板

在Web Form中配置自定义按钮,每个按钮都分配了一个命令参数。单击按钮时,将显示具有相同命令参数的聚合面板。但有三个预定义的命令参数不与聚合面板相关。它们是AddPrintDownloadToExcel。单击Add按钮时会显示一个空的详细信息面板。PrintDownloadToExcel按钮用于打印或将所有网格记录(包括其他分页)导出到Excel,而无需编写任何代码。

详细信息面板

创建/更新/查看单个记录。用户在详细信息面板中保存后,网格面板会自动刷新。

聚合面板

任何自定义操作。用户在聚合面板中保存后,网格面板会自动刷新。

开发Web Form所需条件

Web Form需要XML配置和实现IDynamicPage接口。在XML配置中,我们应配置查询过滤器、网格字段、按钮、详细信息面板和聚合面板。不要担心静态XML配置中的动态数据。XML配置允许配置回调处理器。

当Web Form中需要详细信息面板时,我们需要开发一个不带代码隐藏的ascx模板,并实现IDetailPanel接口。ascx模板用于渲染详细信息面板中的Web控件,如下截图所示。IDetailPanel实现将ascx模板集成到业务逻辑中。

聚合面板的开发与详细信息面板相同。

一个产品管理示例应用程序

我们有一个产品管理Web Form的要求如下:

  1. 创建新产品
  2. 编辑/查看现有产品
  3. 删除单个产品
  4. 批量删除多个选定的产品
  5. 打印查询到的产品
  6. 将查询到的产品导出到Excel
  7. 编辑/查看产品时显示产品更改日志

步骤 1

实现用于查询产品和删除单个产品的动态页面接口。您可以看到,在Query方法中,我们实际上并没有组合查询表达式。框架传递的“parameter”参数可以直接转换为LinqPredicate。框架不仅使用查询面板XML配置来渲染UI,还组合查询表达式。因此,当我们需要更改查询过滤器时,只需更改XML配置而无需编译。

/// <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();
        }
    }
}

第二步

为产品详细信息面板创建一个ascx模板。该模板用于渲染添加/更新/查看产品的表单。ASP.NET服务器控件将通过ID自动绑定到详细信息面板实现类的成员声明。

<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>

步骤 3

实现用于添加/更新/查看单个产品的详细信息面板接口。框架会自动从ascx模板解析具有自定义属性*Binding*的数据成员的引用。数据成员名应与模板中的服务器控件ID相同。*Binding(string parentControlPath)*的参数*parentControlPath*表示包含该控件的ASP.NET*ITemplate*控件。

/// <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);
        }
    }
} 

步骤 4

为批量删除创建另一个ascx模板。模板的内容非常简单,用于显示确认消息。

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

步骤 5

为批量删除多个产品实现聚合面板。当调用*Save*方法时,网格中选定的产品将由框架自动传递。因此,实现仅专注于对传递的实体ID执行操作。

/// <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();
        }
    }
}

步骤 6

XML配置产品管理Web Form。*ObjectId*是Web Form的唯一键,用于作为URI访问Web Form:~/[ObjectId]/DynamicPage.svc。

<?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&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>

最后,Web Form在URL:*http://[host]:[port]/ProductManagement/DynamicPage.svc*下工作。已有文章详细介绍了如何在RapidWebDev中实现完整的Product Management系统

权限如何工作

正如我们在XML配置中看到的,Web Form有一个*PermissionValue*段。实际上,有许多权限值从此值派生。例如,“ProductManagement”配置为Web Form。“ProductManagement.Add”是添加新产品的权限。“ProductManagement.Update”是编辑现有产品的权限。“ProductManagement.[CommandArgument]”是特殊按钮和具有该命令参数的聚合面板的权限。权限由框架智能控制,因此开发人员无需执行任何操作。

有一个名为RapidWebDev.UI.IPermissionBridge的接口,用于UI框架与外部系统集成。当框架调用*HasPermission*方法来检查当前用户是否具有某个行为的权限时,框架会检查渲染UI交互元素和回调UI实现的权限。

/// <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);

最后

我将在本系列的后续文章中详细介绍RapidWebDev UI框架。UI框架只是RapidWebDev解决方案的一个重要组成部分,但不是全部。

什么是RapidWebDev

网站:http://www.rapidwebdev.org

RapidWebDev是一个基础设施,可帮助工程师轻松高效地在Microsoft .NET中开发企业软件解决方案。它由一个可扩展且可维护的Web系统架构组成,并提供一套通用的业务模型、API和服务,作为几乎所有业务解决方案开发所需的基础功能。因此,当工程师在RapidWebDev中开发解决方案时,他们可以获得许多可重用的现成组件,从而可以更专注于业务逻辑的实现。在实践中,与传统的ASP.NET开发相比,我们可以节省50%以上的时间来开发高质量、高性能的业务解决方案。

相关主题

© . All rights reserved.