Microsoft Blazor——快速开发与SQL Forms开源平台Platz.SqlForms

目录

1.创建演示项目

1.1 DemoSqlForms.App

1.2 Platz.SqlForms NuGet包

1.3数据库项目

2.设置演示数据库

3. SqlForms动态页面

3.1 CourseEditForm和CourseEdit.razor页面

3.2 CourseListForm和CourseList.razor页面

3.3 StudentListForm和StudentList.razor页面

3.4 StudentEditForm和StudentEdit.razor页面

4. Platz.ObjectBuilder

4.1代码生成

4.2 EnrollmentListForm和EnrollmentList.razor页面

4.3登记编辑和删除

5.总结

5.1下一步

附录

设置演示数据库

SchoolContext

DbInitializer

连接字符串

Program.cs

Startup.cs


当您需要为客户构建有效的原型时,或者您的公司没有用于企业发展的预算时,您别无选择,并且需要使用一些捷径和生活技巧,通常是低代码或无代码方法。在这篇文章中,我提出了一种有趣的方法,它关于如何使用开源库Platz.SqlForms快速开发Blazor UISqlForms将提供SPA用户体验,并且无需您进行任何编码即可与数据库进行通信,您所要做的就是定义UI表单,为要作为UI控件显示的EF实体提供流畅的符号定义。

  • Github下载完整的解决方案

1.创建演示项目

1.1 DemoSqlForms.App

让我们首先使用Visual Studio 2019“创建新项目链接创建Blazor Server App .NET 5.0项目DemoSqlForms.App

Microsoft Blazor——快速开发与SQL Forms开源平台Platz.SqlForms_第1张图片

然后找到Blazor App模板,选择它,然后单击下一步按钮。

Microsoft Blazor——快速开发与SQL Forms开源平台Platz.SqlForms_第2张图片

在下一个屏幕上,指定项目名称:DemoSqlForms.App和解决方案名称:DemoSqlForms,然后单击创建按钮。

现在选择.NET 5.0 ”Blazor Server App模板,然后单击创建按钮。

Microsoft Blazor——快速开发与SQL Forms开源平台Platz.SqlForms_第3张图片

Visual Studio将使用项目创建解决方案。

我想花一些时间删除示例页面(CounterFetchData)及其相关代码,但这不是必需的。

1.2 Platz.SqlForms NuGet

现在,我们需要添加Platz.SqlForms NuGet软件包,右键单击解决方案项目,然后单击Manage NuGet Packages…菜单,然后在浏览选项卡中键入Platz搜索模式,您将看到Platz软件包。选择Platz.SqlForms并单击安装按钮。

Microsoft Blazor——快速开发与SQL Forms开源平台Platz.SqlForms_第4张图片

安装后,您将看到一个带有简单说明的readme.txt文件,请遵循它们。

重要的步骤是在ConfigureServices 方法中添加Platz.SqlForms初始化逻辑:

services.AddPlatzSqlForms();

1.3数据库项目

为了演示如何使用Platz.SqlForms,我们将需要创建一个数据库项目。

右键单击DemoSqlForms解决方案(解决方案资源管理器中的第一行),单击添加,然后单击新建项目

添加新项目向导中,找到类库(.NET Core模板,将其选中并单击下一步

Microsoft Blazor——快速开发与SQL Forms开源平台Platz.SqlForms_第5张图片

在项目名称中键入DemoSqlForms.Database,然后单击创建

Visual Studio将创建一个新的类库项目并将其添加到解决方案中。

我们需要确保目标框架是.NET 5.0,右键单击项目DemoSqlForms.Database,然后单击Properties

Microsoft Blazor——快速开发与SQL Forms开源平台Platz.SqlForms_第6张图片

选择目标框架.NET 5.0以保存您的更改。

2.设置演示数据库

您可以在本文的附录中看到如何设置演示数据库——它与我们演示的方法无关,并且许多人都知道如何使用Entity Framework,因此,我不想在此花费您的时间。

我只应该说,对于此演示,我们需要SchoolContext数据库上下文以及以下带有一些测试数据的实体:

Microsoft Blazor——快速开发与SQL Forms开源平台Platz.SqlForms_第7张图片

3. SqlForms动态页面

SqlForms的主要思想是为开发人员提供一个工具,使他们能够以C#类型安全的方式定义UI。具有Entity Framework实体或您自己的POCO对象意味着您可以定义要显示的特定属性,要使用的UI控件,使其成为强制性或可选性来提交,以及附加业务规则以验证输入。

3.1 CourseEditFormCourseEdit.razor页面

让我们从Course]实体开始,向DemoSqlForms.App项目添加一个新文件夹Forms” ,然后创建一个CourseEditForm类。

using DemoSqlForms.Database.Model;
using Platz.SqlForms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DemoSqlForms.App.Forms
{
    public class CourseEditForm : DynamicEditFormBase
    {
        protected override void Define(DynamicFormBuilder builder)
        {
            builder.Entity(e =>
            {
                e.Property(p => p.CourseID).IsPrimaryKey().IsUnique(); 

                e.Property(p => p.Title).IsRequired();

                e.Property(p => p.Credits).IsRequired();

                e.DialogButton(ButtonActionTypes.Cancel).DialogButton
                              (ButtonActionTypes.Submit);

                e.DialogButtonNavigation("CourseList", ButtonActionTypes.Cancel, 
                  ButtonActionTypes.Delete, ButtonActionTypes.Submit);
            });
        }
    }
}

您可以看到[CourseEditForm]继承自具有类型参数[SchoolContext][DynamicEditFormBase]——这就是我们告诉SqlForms引擎DbContext要使用的方式。

我们重写Define]方法并在其中提供表单定义。

代码builder.Entity]指定Course]类型参数,因此我们通知SqlForms引擎使用哪个实体。

现在我们需要指定如何显示每个属性:

e.Property(p => p.CourseID).IsPrimaryKey().IsUnique(); 

这意味着CourseID是一个主键,并且具有唯一性约束。IsRequired()表示如果此属性的值为空,则不会提交表单。

方法DialogButton用于指定要显示的按钮。

方法DialogButtonNavigation用于将导航操作分配给一组按钮。因此,下一行...

e.DialogButtonNavigation("CourseList", ButtonActionTypes.Cancel, 
                          ButtonActionTypes.Delete, ButtonActionTypes.Submit); 

表示单击取消删除提交按钮时,应用程序将重定向到/CourseList链接。

可以在项目Wiki页面上找到Form Definition的完整规范:

  • Platz.SqlForms编程参考·ProCodersPtyLtd/MasterDetailsDataEntry Wikigithub.com

现在,在定义表单后,我们可以将新的razor页面添加到Pages文件夹CourseEdit.razor

@page "/CourseEdit/{CourseId:int}"
@page "/CourseEdit"

Course Edit

@code { [Parameter] public int CourseId { get; set; } }

组件需要指向表单定义CourseEditFormTForm参数,以及映射到页面参数CourseEdit的实体Id

现在,如果您运行该应用程序并将其/CourseEdit添加到浏览器路径,您将看到从定义呈现的编辑页面。因为我们没有提供Id值,所以它将在数据库中创建一个新Course记录。

如果你点击提交,你会看到,验证了CourseIDTitle*失败。

Microsoft Blazor——快速开发与SQL Forms开源平台Platz.SqlForms_第8张图片

因为CourseID是主键,但不是自动递增的,所以您可以指定任何整数值,除了0和已经使用的整数值外,对于自动递增的主键,输入始终是只读的。

如果使用值(100, C#, 4) 填充表单,然后单击Submit,则表单将在数据库中创建新记录并重定向到/CourseList,该记录尚未实现。

3.2 CourseListFormCourseList.razor页面

列表形式的定义有些不同,但是我们使用类似的方法BTW,这是我们在Entity Framework实体定义中发现的,请查看SchoolContext.cs这段代码:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity(entity =>
    {
        entity.HasOne(d => d.Course)
            .WithMany(p => p.Enrollments)
            .HasForeignKey(d => d.CourseID)
            .OnDelete(DeleteBehavior.Restrict)
            .HasConstraintName("FK_Enrollment_Course");

        entity.HasOne(d => d.Student)
            .WithMany(p => p.Enrollments)
            .HasForeignKey(d => d.StudentID)
            .OnDelete(DeleteBehavior.Restrict)
            .HasConstraintName("FK_Enrollment_Student");
    });
}

因此,课程列表表单将如下所示:

using DemoSqlForms.Database.Model;
using Platz.SqlForms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DemoSqlForms.App.Forms
{
    public class CourseListForm : DataServiceBase
    {
        protected override void Define(DataServiceFormBuilder builder)
        {
            builder.Entity(e =>
            {
                e.ExcludeAll();

                e.Property(p => p.CourseID).IsPrimaryKey();

                e.Property(p => p.Title);

                e.Property(p => p.Credits);

                // Parameter {0} is always PrimaryKey, parameters {1} and above - Filter Keys
                // {0} = AddressId {1} = CustomerId
                e.ContextButton("Edit", "CourseEdit/{0}").ContextButton
                               ("Delete", "CourseDelete/{0}");

                e.DialogButton("CourseEdit/0", ButtonActionTypes.Add);
            });

            builder.SetListMethod(GetCourseList);
        }

        public List GetCourseList(params object[] parameters)
        {
            using (var db = GetDbContext())
            {
                var query =
                    from s in db.Course
                    select new Course
                    {
                        CourseID = s.CourseID,
                        Title = s.Title,
                        Credits = s.Credits
                    };

                var result = query.ToList();
                return result;
            }
        }
    }
}

现在类CourseListForm开始从DataServiceBase继承,我们再次需要重写放置表单定义的Define方法。

首先,我们用e.ExcludeAll();从定义中删除所有属性,我们在不想显示所有内容时执行此操作。

其次,我们指定要按看到顺序显示的所有列。

接下来,我们在一行中定义上下文菜单:

e.ContextButton("Edit", "CourseEdit/{0}").ContextButton("Delete", "CourseDelete/{0}"); 

我们在此处提供按钮的文本和导航链接。链接部分{0}是记录主键的占位符,当用户在某行上单击此按钮时,主键值将从行中提取并放置到占位符,例如对于主键值17,我们将获得产生的导航链接CourseEdit/17 ”

然后,我们使用DialogButton来显示带有链接CourseEdit/0添加按钮,并且0表示执行了编辑页面以创建新记录。

最后,我们需要指定返回数据以显示在页面上的方法(SetListMethod)。GetCourseList使用LINQ从数据库返回所有课程。

定义就绪后,我们可以添加razor页面:

@page "/CourseList"

Courses

@code { }

我们使用FormDataServiceListComponent并将我们的定义设置为TForm参数。

我们还需要在Shared文件夹中进行修改NavMenu.razor,并将CourseList页面包含在左侧菜单中,另外,我还包含了指向StudentList页面的链接,我们将在下一步中实现它。


如果立即运行该应用程序,您将看到:

Microsoft Blazor——快速开发与SQL Forms开源平台Platz.SqlForms_第9张图片

如果单击课程列表,您将看到:

Microsoft Blazor——快速开发与SQL Forms开源平台Platz.SqlForms_第10张图片

您可以使用添加按钮将更多课程添加到数据库中,也可以使用Actions上下文菜单来编辑记录。

如果添加CourseDelete.razor页面,我们也可以删除课程记录。

@page "/CourseDelete/{CourseId:int}"

Delete Course

@code { [Parameter] public int CourseId { get; set; } }

该页面具有路由@page "/CourseDelete/{CourseId:int},并且重复使用了CourseEditForm我们提供的ForDelete="true",并且此参数告诉SqlForms表单应该是只读的,并包含一个“ Delete按钮。

Microsoft Blazor——快速开发与SQL Forms开源平台Platz.SqlForms_第11张图片

如您所见,所有insertupdatedelete操作都是由SqlForms完成的,我们只需要创建查询以选择课程记录即可。

3.3 StudentListFormStudentList.razor页面

学生名单的Form定义与CourseList非常相似。

using DemoSqlForms.Database.Model;
using Platz.SqlForms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DemoSqlForms.App.Forms
{
    public class StudentListForm : DataServiceBase
    {
        protected override void Define(DataServiceFormBuilder builder)
        {
            builder.Entity(e =>
            {
                e.ExcludeAll();

                e.Property(p => p.ID).IsPrimaryKey();

                e.Property(p => p.FirstMidName);

                e.Property(p => p.LastName);

                e.Property(p => p.EnrollmentDate).Format("dd-MMM-yyyy");

                e.Property(p => p.EnrollmentCount);

                // Parameter {0} is always PrimaryKey, parameters {1} and above - Filter Keys
                // {0} = AddressId {1} = CustomerId
                e.ContextButton("Edit", "StudentEdit/{0}").ContextButton
                ("Delete", "StudentDelete/{0}").ContextButton
                ("Enrollments", "EnrollmentList/{0}");

                e.DialogButton("StudentEdit/0", ButtonActionTypes.Add);
            });

            builder.SetListMethod(GetStudentList);
        }

        public class StudentDetails : Student
        {
            public int EnrollmentCount { get; set; }
        }

        public List GetStudentList(params object[] parameters)
        {
            using (var db = GetDbContext())
            {
                var query =
                    from s in db.Student
                    select new StudentDetails
                    {
                        ID = s.ID,
                        FirstMidName = s.FirstMidName,
                        LastName = s.LastName,
                        EnrollmentDate = s.EnrollmentDate,
                        EnrollmentCount = (db.Enrollment.Where
                                          (e => e.StudentID == s.ID).Count())
                    };

                var result = query.ToList();
                return result;
            }
        }
    }
}

请注意Format("dd-MMM-yyyy")指定如何显示EnrollmentDate属性的格式。

另外,有时您需要显示比实体更多的列,然后我们需要创建一个业务对象——一个将包含所有必需属性的类。我创建了一个继承自Student的所有属性的StudentDetails类,并且还添加了该EnrollmentCount属性。

GetStudentList返回所有学生数据,并计算每个student的入学人数。

razor页面如下所示:

@page "/StudentList"

Students

@code { }

如果运行该应用程序并单击学生列表菜单项,则将看到:

Microsoft Blazor——快速开发与SQL Forms开源平台Platz.SqlForms_第12张图片

为了使添加、编辑、删除工作,我们需要添加StudentEditForm

3.4 StudentEditFormStudentEdit.razor页面

StudentEditForm定义非常相似于CourseEditForm,但是我添加了业务规则,以便在输入或编辑新student规则时进行其他验证。

using DemoSqlForms.Database.Model;
using Platz.SqlForms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DemoSqlForms.App.Forms
{
    public class StudentEditForm : DynamicEditFormBase
    {
        protected override void Define(DynamicFormBuilder builder)
        {
            builder.Entity(e =>
            {
                e.Property(p => p.ID).IsReadOnly();

                e.Property(p => p.FirstMidName).IsRequired();

                e.Property(p => p.LastName).IsRequired();

                e.Property(p => p.EnrollmentDate).Rule
                (DefaultDate, FormRuleTriggers.Create).Rule(CheckDate);

                e.DialogButton(ButtonActionTypes.Cancel).DialogButton
                (ButtonActionTypes.Validate).DialogButton(ButtonActionTypes.Submit);

                e.DialogButtonNavigation("StudentList", ButtonActionTypes.Cancel, 
                                         ButtonActionTypes.Delete, ButtonActionTypes.Submit);
            });
        }

        public FormRuleResult DefaultDate(Student model)
        {
            model.EnrollmentDate = new DateTime(DateTime.Now.Year, 9, 1);
            return null;
        }

        public FormRuleResult CheckDate(Student model)
        {
            if (model.EnrollmentDate < new DateTime(2015, 1, 1))
            {
                return new FormRuleResult("EnrollmentDate is incorrect");
            }

            return null;
        }
    }
}

规则Rule(DefaultDate, FormRuleTriggers.Create)表明,当创建新的学生记录时,将执行该DefaultDate方法,该方法设置EnrollmentDate为当年的01-Sep

EnrollmentDate属性更改或提交表单后,将执行规则CheckDate。当输入的值早于201511日时,此规则将触发验证错误。

StudentEdit.razor 页面像往常一样非常简单:

@page "/StudentEdit/{Id:int}"
@page "/StudentEdit"

Student Edit

@code { [Parameter] public int Id { get; set; } }

如果现在运行该应用程序,请选择学生列表页面,然后单击添加按钮。您可以使用默认规则和验证规则。

Microsoft Blazor——快速开发与SQL Forms开源平台Platz.SqlForms_第13张图片

对于删除功能,我们需要添加StudentDelete.razor页面。

@page "/StudentDelete/{Id:int}"

Delete Student

@code { [Parameter] public int Id { get; set; } }

运行应用程序时,删除页面将如下所示:

Microsoft Blazor——快速开发与SQL Forms开源平台Platz.SqlForms_第14张图片

现在我们需要创建注册页面,我想演示如何简化列表表单的创建。

4. Platz.ObjectBuilder

Platz.ObjectBuilder 可用于可视化地构建具有联接、子查询、条件的复杂LINQ查询,并为查询和查询返回的业务对象生成C#代码。

为了展示如何使用Platz.ObjectBuilder,我们需要创建另一个目标框架为.NET 5.0Blazor Server应用程序,并将其命名为DemoSqlForms.ObjectBuilder.App

然后,我们需要安装Platz.ObjectBuilder NuGet软件包,并按照readm.txt文件中的说明进行操作。

要使用SchoolContext,我们需要向项目添加一个项目引用DemoSqlForms.Database,并将连接字符串添加到appsettings.json文件中。

现在让我们修改Index.razor页面。

@page "/"
@using Platz.ObjectBuilder

右键单击DemoSqlForms.ObjectBuilder.App项目,然后选择调试,然后选择启动新实例

您将看到该应用程序,它使我们可以直观地构建查询。

选择注册实体,然后选择Course实体。您将看到两个对象已添加到From面板。现在,在选择面板中,为e.StudentID列输入@p1Filter。您应该看到一个查询窗口,如下所示:

Microsoft Blazor——快速开发与SQL Forms开源平台Platz.SqlForms_第15张图片

现在,在设置面板上单击,并在查询返回类型名称控件中输入EnrollmentDetails” ,然后单击保存,然后关闭应用程序。

我们创建了查询定义,该定义保存为json文件,位于文件夹DemoSqlForms.ObjectBuilder.App\StoreData中。我们可以使用t4模板从此json定义生成代码。

4.1代码生成

让我们回到DemoSqlForms.App项目。如果打开项目文件夹Platz.Config.Link,则将看到CopyMe.PlatzDataService.tt.txt文件。双击此文件,选择所有代码()并将其复制到剪贴板()。

现在,在Forms文件夹中,创建一个子文件夹DataServices

DataServices文件夹中,创建一个名为SchoolDataService.tt的文件,然后粘贴剪贴板中的内容()。

您需要更改第12行,以指向DemoSqlForms.ObjectBuilder.App项目中保存了查询的StoreData文件夹:

<#      var JsonStorePath = @"DemoSqlForms.ObjectBuilder.App\StoreData"; #>

现在,当您保存文件时,Visual Studio将为您生成代码并将其放置在SchoolDataService.cs中。

// ******************************************************************************************
// This code is auto generated by Platz.ObjectBuilder template, 
// any changes made to this code will be lost
// ******************************************************************************************
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Platz.SqlForms;
using DemoSqlForms.Database.Model;

namespace Default
{
    #region Interface 

    public partial interface IMyDataService
    {
        List GetEnrollmentDetailsList(params object[] parameters);
    }

    #endregion

    #region Data Service 

    public partial class MyDataService : DataServiceBase, IMyDataService
    {
        public List GetEnrollmentDetailsList(params object[] parameters)
        {
            var p1 = (Int32)parameters[0];

            using (var db = GetDbContext())
            {
                var query =
                    from c in db.Course 
                    join e in db.Enrollment on c.CourseID equals e.CourseID
                    where e.StudentID == p1
                    select new EnrollmentDetails
                    {
                        EnrollmentID = e.EnrollmentID,
                        CourseID = e.CourseID,
                        Grade = e.Grade,
                        StudentID = e.StudentID,
                        Credits = c.Credits,
                        Title = c.Title,
                    };

                var result = query.ToList();
                return result;
            }
        }
    }

    #endregion

    #region Entities

    public partial class EnrollmentDetails
    {
        public Int32 EnrollmentID { get; set; }
        public Int32 CourseID { get; set; }
        public Grade? Grade { get; set; }
        public Int32 StudentID { get; set; }
        public Int32 Credits { get; set; }
        public String Title { get; set; }
    }

    #endregion
}

生成的文件包含EnrollmentDetails业务对象类和MyDataService:: GetEnrollmentDetailsList方法,该类和方法返回EnrollmentCourse实体的联接数据。它还接受参数p1,并且该StudentID字段将过滤数据。

4.2 EnrollmentListFormEnrollmentList.razor页面

现在我们添加EnrollmentListForm代码:

using Default;
using Platz.SqlForms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DemoSqlForms.App.Forms
{
    public class EnrollmentListForm : MyDataService
    {
        protected override void Define(DataServiceFormBuilder builder)
        {
            builder.Entity(e =>
            {
                e.ExcludeAll();

                e.Property(p => p.EnrollmentID).IsPrimaryKey();

                e.Property(p => p.StudentID).IsFilter().IsReadOnly();

                e.Property(p => p.CourseID);

                e.Property(p => p.Grade);

                e.Property(p => p.Title);

                e.Property(p => p.Credits);

                // Parameter {0} is always PrimaryKey, parameters {1} and above - Filter Keys
                // {0} = EnrollmentID {1} = StudentID
                e.ContextButton("Edit", "EnrollmentEdit/{0}/{1}").ContextButton
                               ("Delete", "EnrollmentDelete/{0}/{1}");

                e.DialogButton("StudentList", ButtonActionTypes.Custom, "Back");

                e.DialogButton("EnrollmentEdit/0/{1}", ButtonActionTypes.Add);
            });

            builder.SetListMethod(GetEnrollmentDetailsList);
        }
    }
}

我们从生成的MyDataService继承类EnrollmentListForm,并使用SetListMethod指定生成的GetEnrollmentDetailsList

我们照常定义了属性,但是导航链接现在具有两个占位符:EnrollmentEdit/{0}/{1}EnrollmentDelete/{0}/{1}

原因是EnrollmentListFormStudentListForm的附属形式。当我们选择学生,点击Enrollments上下文菜单按钮,我们需要向EnrollmentListForm提供StudentID主键,这个StudentID将作为{1}占位符被传播到EnrollmentEditForm,但{0}是保留给EnrollmentEditForm主键EnrollmentID的。

EnrollmentList.razor 页面将如下所示:

@page "/EnrollmentList/{StudentId:int}"

Student Enrollments

@code { [Parameter] public int StudentId { get; set; } }

页面路由现在接受StudentId参数,我们使用ServiceParameters提供StudentIdFormDataServiceListComponent。该引擎将使用ServiceParameters生成导航链接,以填充占位符{1}及以上的占位符。

我们还添加了FormDynamicEditComponent以显示所有字段都设置为只读的字段的StudentHeaderForm

using DemoSqlForms.Database.Model;
using Platz.SqlForms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DemoSqlForms.App.Forms
{
    public class StudentHeaderForm : DynamicEditFormBase
    {
        protected override void Define(DynamicFormBuilder builder)
        {
            builder.Entity(e =>
            {
                e.ExcludeAll();

                e.Property(p => p.ID).IsReadOnly();

                e.Property(p => p.FirstMidName).IsReadOnly();

                e.Property(p => p.LastName).IsReadOnly();
            });
        }
    }
}

如果我们现在运行该应用程序,然后在学生列表选择一个学生,然后单击注册上下文菜单按钮,我们将看到:

Microsoft Blazor——快速开发与SQL Forms开源平台Platz.SqlForms_第16张图片

您可以在下面的headerenrollments表中看到学生只读详细信息。

如果单击返回按钮,我们将返回到学生列表页面。

4.3登记编辑和删除

最后一步是创建EnrollmentEditForm定义,就像我们之前所做的一样简单。

using DemoSqlForms.Database.Model;
using Platz.SqlForms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DemoSqlForms.App.Forms
{
    public class EnrollmentEditForm : DynamicEditFormBase
    {
        protected override void Define(DynamicFormBuilder builder)
        {
            builder.Entity(e =>
            {
                e.Property(p => p.EnrollmentID).IsPrimaryKey().IsReadOnly();

                e.Property(p => p.StudentID).IsFilter().IsHidden();

                e.Property(p => p.CourseID).IsRequired().Dropdown().Set
                          (c => c.CourseID, c => c.Title);

                e.Property(p => p.Grade).IsRequired().Rule
                (DefaultGrade, FormRuleTriggers.Create).Dropdown().Set(g => g, g => g);

                e.DialogButton(ButtonActionTypes.Cancel).DialogButton
                              (ButtonActionTypes.Submit);

                // {0} always reserved for Primary Key (EnrollmentID in this case) 
                // but EnrollmentList accepts StudentId as parameter
                e.DialogButtonNavigation("EnrollmentList/{1}", 
                ButtonActionTypes.Cancel, ButtonActionTypes.Delete, ButtonActionTypes.Submit);
            });
        }

        public FormRuleResult DefaultGrade(Enrollment model)
        {
            model.Grade = Grade.A;
            return null;
        }
    }
}

在这里,我们使用了Dropdown定义。对于CourseID属性,我们使用Course实体,并指定[value]将是Course.CourseID[name]将是Course.Title。对于Grade属性,我们指定Grade enum,并且下拉列表中的[value][name]将具有Grade枚举项(ABC等)

然后,我们需要为Edit添加razor页面。

@page "/EnrollmentEdit/{EnrollmentId:int}/{StudentId:int}"

Student Enrollment Edit

@code { [Parameter] public int EnrollmentId { get; set; } [Parameter] public int StudentId { get; set; } }

而对于Delete

@page "/EnrollmentDelete/{EnrollmentId:int}/{StudentId:int}"

Student Enrollment Delete

@code { [Parameter] public int EnrollmentId { get; set; } [Parameter] public int StudentId { get; set; } }

在这两个页面中,我们都显示StudentHeaderFormheader,并在ServiceParameters中提供StudentId

现在,该应用程序可以进行测试了,单击学生注册编辑操作,您将看到:

Microsoft Blazor——快速开发与SQL Forms开源平台Platz.SqlForms_第17张图片

如果单击删除操作,将显示此页面:

Microsoft Blazor——快速开发与SQL Forms开源平台Platz.SqlForms_第18张图片

SqlForms引擎将使用我们提供的表单定义来执行InsertUpdateDelete所有数据库操作。

5.总结

在本文中,我们演示了一种使用C#中的类型安全定义来构建Blazor UI应用程序的方法。对于使用Platz.SqlForms进行原型或低预算应用程序的开发人员而言,该技术可以节省大量时间。

这种方法有几个优点:

  • 中级或初级开发人员可以轻松使用它,不需要任何前端体验
  • 代码将被很好地组织,并且仅在业务规则中允许业务逻辑
  • 业务逻辑可以轻松地进行单元测试
  • 生成的代码库要小得多,并且不需要昂贵的维护
  • 可以在可视工具中生成复杂的查询和业务对象

但是,有一些缺点:

  • SqlForms动态组件具有局限性,无法生成所需的任何UI
  • 不支持复合主键
  • 目前只有一个bootstrap演示文稿

我们还考虑了可以节省大量时间来定义业务对象并将其映射到LINQ查询结果的Platz.ObjectBuilder工具。尽管对象生成器目前不支持复杂的查询,但我们演示了一个概念,该概念涉及t4模板如何使用可视化工具输出来生成不需要维护的代码:任何时候您需要更改内容时,只需进行修改即可查询并重新生成代码。

该项目Platz.SqlForms是开源的,由Pro Coders团队开发。

您可以在Github上找到所有详细信息

要提交错误或功能请求,请使用以下链接:

Issues · ProCodersPtyLtd/MasterDetailsDataEntry (github.com)

5.1下一步

我的下一篇文章将介绍SqlForms内联编辑。

附录

设置演示数据库

您可以在此处详细了解如何设置实体框架模型优先数据库: 

教程:在ASP.NET MVC Web应用程序中开始使用EF Core 微软文档

SchoolContext

我们在DemoSqlForms.Database项目中创建一个文件夹Model ”,并添加SchoolContext.cs文件。

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Configuration;
using Microsoft.Extensions.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DemoSqlForms.Database.Model
{
    public class SchoolContext : DbContext
    {
        public SchoolContext() 
        {
        }

        public SchoolContext(DbContextOptions options) : base(options)
        {
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                IConfigurationRoot configuration = 
                   new ConfigurationBuilder().AddJsonFile
                           ("appsettings.json", optional: false).Build();
                optionsBuilder.UseSqlServer
                   (configuration.GetConnectionString("DefaultConnection"));
            }
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity(entity =>
            {
                entity.HasOne(d => d.Course)
                    .WithMany(p => p.Enrollments)
                    .HasForeignKey(d => d.CourseID)
                    .OnDelete(DeleteBehavior.Restrict)
                    .HasConstraintName("FK_Enrollment_Course");

                entity.HasOne(d => d.Student)
                    .WithMany(p => p.Enrollments)
                    .HasForeignKey(d => d.StudentID)
                    .OnDelete(DeleteBehavior.Restrict)
                    .HasConstraintName("FK_Enrollment_Student");
            });
        }

        public DbSet Course { get; set; }
        public DbSet Enrollment { get; set; }
        public DbSet Student { get; set; }
    }

    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

        public ICollection Enrollments { get; set; }
    }

    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }

    public class Student
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        public DateTime EnrollmentDate { get; set; }

        public ICollection Enrollments { get; set; }
    }
}

我将简要提及该文件包含我们的演示数据库的实体框架DbContext和实体。SchoolContextappsettings.json中读取连接字符串,我们将其添加到DemoSqlForms.App项目中。

实体看起来像:

Microsoft Blazor——快速开发与SQL Forms开源平台Platz.SqlForms_第19张图片

DbInitializer

要使用测试数据初始化数据库,我们添加DbInitializer.cs文件。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DemoSqlForms.Database.Model
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            context.Database.EnsureCreated();

            // Look for any students.
            if (context.Student.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
            new Student{FirstMidName="Carson",LastName="Alexander",
                        EnrollmentDate=DateTime.Parse("2005-09-01")},
            new Student{FirstMidName="Meredith",LastName="Alonso",
                        EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Arturo",LastName="Anand",
                        EnrollmentDate=DateTime.Parse("2003-09-01")},
            new Student{FirstMidName="Gytis",LastName="Barzdukas",
                        EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Yan",LastName="Li",
                        EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Peggy",LastName="Justice",
                        EnrollmentDate=DateTime.Parse("2001-09-01")},
            new Student{FirstMidName="Laura",LastName="Norman",
                        EnrollmentDate=DateTime.Parse("2003-09-01")},
            new Student{FirstMidName="Nino",LastName="Olivetto",
                        EnrollmentDate=DateTime.Parse("2005-09-01")}
            };
            foreach (Student s in students)
            {
                context.Student.Add(s);
            }
            context.SaveChanges();

            var courses = new Course[]
            {
            new Course{CourseID=1050,Title="Chemistry",Credits=3},
            new Course{CourseID=4022,Title="Microeconomics",Credits=3},
            new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
            new Course{CourseID=1045,Title="Calculus",Credits=4},
            new Course{CourseID=3141,Title="Trigonometry",Credits=4},
            new Course{CourseID=2021,Title="Composition",Credits=3},
            new Course{CourseID=2042,Title="Literature",Credits=4}
            };
            foreach (Course c in courses)
            {
                context.Course.Add(c);
            }
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
            new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
            new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
            new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
            new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
            new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
            new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
            new Enrollment{StudentID=3,CourseID=1050},
            new Enrollment{StudentID=4,CourseID=1050},
            new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
            new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
            new Enrollment{StudentID=6,CourseID=1045},
            new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };
            foreach (Enrollment e in enrollments)
            {
                context.Enrollment.Add(e);
            }
            context.SaveChanges();
        }
    }
}

现在我们需要对DemoSqlForms.App项目进行更改。

连接字符串

将连接字符串添加到appsettings.json,文件将如下所示:

{
  "ConnectionStrings": {

    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;
     Database=DemoSqlForms1;Trusted_Connection=True;MultipleActiveResultSets=true"
  },

  "Logging": {

    "LogLevel": {

      "Default": "Information",

      "Microsoft": "Warning",

      "Microsoft.Hosting.Lifetime": "Information"
    }
  },

  "AllowedHosts": "*"
}

连接字符串指定SQL Server LocalDBLocalDBSQL Server Express数据库引擎的轻量级版本,旨在用于应用程序开发,而不用于生产。LocalDB按需启动并以用户模式运行,因此没有复杂的配置。默认情况下,在C:/Users/目录中LocalDB创建.mdf DB文件。

Program.cs

在文件Program.cs,我们删除以下行:

CreateHostBuilder(args).Build().Run(); 

并添加创建数据库逻辑,代码将如下所示:

using DemoSqlForms.Database.Model;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DemoSqlForms.App
{
    public class Program
    {
        public static void Main(string[] args)
        {
            //CreateHostBuilder(args).Build().Run();
            var host = CreateHostBuilder(args).Build();

            CreateDbIfNotExists(host);

            host.Run();
        }

        private static void CreateDbIfNotExists(IHost host)
        {
            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                try
                {
                    var context = services.GetRequiredService();
                    DbInitializer.Initialize(context);
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService>();
                    logger.LogError(ex, "An error occurred creating the DB.");
                }
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup();
                });
    }
}

方法CreateDbIfNotExists只需执行DbInitializer ,即可在首次运行时创建数据库并填充测试数据。

Startup.cs

ConfigureServices方法中,我们需要添加DbContext初始化逻辑

services.AddDbContext
(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

services.AddDatabaseDeveloperPageExceptionFilter();

我们已经添加了Platz.SqlForms初始化逻辑:

services.AddPlatzSqlForms();

该代码将如下所示:

using DemoSqlForms.Database.Model;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Platz.SqlForms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DemoSqlForms.App
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. 
        // Use this method to add services to the container.
        // For more information on how to configure your application, 
        // visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            services.AddServerSideBlazor();

            services.AddDbContext(options => options.UseSqlServer
                     (Configuration.GetConnectionString("DefaultConnection")));
            services.AddDatabaseDeveloperPageExceptionFilter();
            services.AddPlatzSqlForms();
        }

        // This method gets called by the runtime. Use this method to configure 
        // the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. 
                // You may want to change this for production scenarios, 
                // see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");
            });
        }
    }
}

https://www.codeproject.com/Articles/5291832/Microsoft-Blazor-Rapid-Development-with-SQL-Forms

你可能感兴趣的:(ASP.NET,CORE,web前端,架构及框架,Platz.SqlForms,Blazor,sql,forms)