欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

ASP.NET Core Razor Pages 初探

程序员文章站 2022-10-04 08:43:57
最近新建 Asp.net Core MVC 项目的时候不小心选错了个模板,发现了一种新的项目模板。它使用cshtml视图模板,但是没有Controller文件夹。后来才发现这是ASP.NET Core框架新推出的Razor Pages技术。 什么是Razor Pages “Razor Pages 使 ......

最近新建 asp.net core mvc 项目的时候不小心选错了个模板,发现了一种新的项目模板。它使用cshtml视图模板,但是没有controller文件夹。后来才发现这是asp.net core框架新推出的razor pages技术。

什么是razor pages

“razor pages 使编码更加简单更加富有生产力”这是微软说的==!。razor pages 简化了传统的mvc模式,仅仅使用视图跟模型来完成网页的渲染跟业务逻辑的处理。模型里包含了数据跟方法,通过绑定技术跟视图建立联系,这就有点像服务端的绑定技术。下面使用一个标准的crud示例来演示razor pages的开发,并且简单的探索一下它是如何工作的。

新建razor pages项目

在visual studio中新建razor pages项目。
ASP.NET Core Razor Pages 初探
项目结构
ASP.NET Core Razor Pages 初探
新建项目的目录结构比mvc项目简单。它没有controllers目录,pages有点像mvc项目的views目录,里面存放了cshtml模板。随便点开一个cshtml文件,发现它都包含了一个cs文件。这是跟mvc项目最大的不同,这个结构让人回忆起那古老的webform技术,o(╥﹏╥)o 。
ASP.NET Core Razor Pages 初探

新建razor page

我们模拟开发一个学生管理系统。一共包含4个页面:列表页面、新增页面、修改页面、删除页面。首先我们新建一个列表页面。
在pages目录下面新建student目录。在student目录下新建4个razor page名叫:list、add、update、delete。
ASP.NET Core Razor Pages 初探
建好后目录结构是这样:
ASP.NET Core Razor Pages 初探

模拟数据访问仓储

由于这是个演示项目,所以我们使用静态变量来简单模拟下数据持久。
在项目下新建一个data目录,在目录下新建student实体类:

    public class student
    {
        public int id { get; set; }
        public string name { get; set; }

        public string class { get; set; }

        public int age { get; set; }

        public string sex { get; set; }
    }

在data目录下新建istudentrepository跟studentrepository类:

    public interface istudentrepository
    {
        list<student> list();

        student get(int id);

        bool add(student student);

        bool update(student student);

        bool delete(int id);
    }

    public class studentrepository : istudentrepository
    {
        private static list<student> students = new list<student> {
                new student{ id=1, name="小红", age=10, class="1班", sex="女"},
                new student{ id=2, name="小明", age=11, class="2班", sex="男"},
                new student{ id=3, name="小强", age=12, class="3班", sex="男"}
        };

        public bool add(student student)
        {
            students.add(student);

            return true;
        }

        public bool delete(int id)
        {
            var stu = students.firstordefault(s => s.id == id);
            if (stu != null)
            {
                students.remove(stu);
            }

            return true;
        }

        public student get(int id)
        {
            return students.firstordefault(s=>s.id == id);
        }

        public list<student> list()
        {
            return students;
        }

        public bool update(student student)
        {
            var stu = students.firstordefault(s=>s.id == student.id);
            if (stu != null)
            {
                students.remove(stu);
            }

            students.add(student);
            return true;
        }
    }

我们新建了一个irepository接口,里面有几个基本的crud的方法。然后新建一个实现类,并且使用静态变量保存数据,模拟数据持久化。
当然还得在di容器中注册一下:

  public void configureservices(iservicecollection services)
        {
            services.addrazorpages();
            //注册repository
            services.addscoped<istudentrepository, studentrepository>();
        }

实现列表(student/list)页面

列表页面用来展现所有的学生信息。
修改listmodel类:

    public class listmodel : pagemodel
    {
        private readonly istudentrepository _studentrepository;
        public list<student> students { get; set; }
        public listmodel(istudentrepository studentrepository) 
        {
            _studentrepository = studentrepository;
        }

        public void onget()
        {
            students = _studentrepository.list();
        }
    }

修改list.cshtml模板:

@page
@model razorpagecrud.listmodel
@{
    viewdata["title"] = "list";
}

<h1>list</h1>

<p>
    <a class="btn btn-primary" asp-page="add">add</a>
</p>
<table class="table">
    <tr>
        <th>id</th>
        <th>name</th>
        <th>age</th>
        <th>class</th>
        <th>sex</th>
        <th></th>
    </tr>
    @foreach (var student in model.students)
    {
        <tr>
            <td>@student.id</td>
            <td>@student.name</td>
            <td>@student.age</td>
            <td>@student.class</td>
            <td>@student.sex</td>
            <td>
                <a class="btn btn-primary" asp-page="update" asp-route-id="@student.id">update</a>
                <a class="btn btn-danger" href="/student/delete?id=@student.id" >delete</a>
            </td>
        </tr>
    }

</table>

listmodel类混合了mvc的controller跟model的概念。它本身可以认为是mvc里面的那个model,它包含的数据可以被razor试图引擎使用,用来生成html,比如它的students属性;但是它又包含方法,可以用来处理业务逻辑,这个方法可以认为是controller中的action。方法通过特殊的前缀来跟前端的请求做绑定,比如onget方法就是对get请求作出响应,onpost则是对post请求作出响应。
运行一下并且访问/student/list:
ASP.NET Core Razor Pages 初探
列表页面可以正常运行了。

使用asp-page进行页面间导航

列表页面上有几个按钮,比如新增、删除等,点击的时候希望跳转至不同的页面,可以使用asp-page属性来实现。asp-page属性不是html自带的属性,显然这是razor pages为我们提供的。

<p>
    <a class="btn btn-primary" asp-page="add">add</a>
</p>

上面的代码在a元素上添加了asp-page="add",表示点击这个a连接会跳转至同级目录的add页面。html页面之间的导航不管框架怎么封装无非就是url之间的跳转。显然这里asp-page最后会翻译成一个url,看看生成的页面源码:

<a class="btn btn-primary" href="/student/add">add</a>

跟我们想的一样,最后asp-page被翻译成了href="/student/add"。

使用asp-route-xxx进行传参

页面间光导航还不够,更多的时候我们还需要进行页面间的传参。比如我们的更新按钮,需要跳转至update页面并且传递一个id过去。

<a class="btn btn-primary" asp-page="update" asp-route-id="@student.id">update</a>

我们使用asp-route-id来进行传参。像这里的a元素进行传参,无非是放到url的querystring上。让我们看一下生成的html源码:

<a class="btn btn-primary" href="/student/update?id=2">update</a>

不出所料最后id作为querystring被组装到了url上。
上面演示了razor pages的导航跟传参,使用了几个框架内置的属性,但其实我们根本可以不用这些东西就可以完成,使用标准的html方式来完成,比如删除按钮:

<a class="btn btn-danger" href="/student/delete?id=@student.id" >delete</a>

上面的写法完全可以工作,并且更加清晰明了,谁看了都知道是啥意思。
小小的吐槽下微软:像asp-page这种封装我是不太喜欢的,因为它掩盖了html、http工作的本质原理。这样会造成很多同学知道使用asp-page怎么写,但是换个框架就不知道怎么搞了。我见过号称精通asp.net的同学,但是对html、特别是对http一无所知。当你了解了真相后,甭管你用什么技术,看起来其实都是一样的,都是套路。

实现新增(student/add)页面

新增页面提供几个输入框输入学生信息,并且可以提交到后台。
修改addmodel类:

   public class addmodel : pagemodel
   {
       private readonly istudentrepository _studentrepository;
       public addmodel(istudentrepository studentrepository)
       {
           _studentrepository = studentrepository;
       }
       public void onget()
       {
       }

       [bindproperty]
       public student student { get; set; }

       public iactionresult onpostsave()
       {
           _studentrepository.add(student);
           return redirecttopage("list");
       }
   }

修改add.cshtml页面

@page
@model razorpagecrud.addmodel
@{
    viewdata["title"] = "add";
}

<h1>add</h1>

<form method="post">
    <div class="form-group">
        <label>id</label>
        <input type="number" asp-for="student.id" class="form-control" />
    </div>
    <div class="form-group">
        <label>name</label>
        <input type="text" asp-for="student.name" class="form-control" />
    </div>
    <div class="form-group">
        <label>age</label>
        <input type="number" asp-for="student.age" class="form-control" />
    </div>
    <div class="form-group">
        <label>class</label>
        <input type="text" asp-for="student.class" class="form-control" />
    </div>
    <div class="form-group">
        <label>sex</label>
        <input type="text" asp-for="student.sex" class="form-control" />
    </div>
    <div class="form-group">
        <button type="submit" class="btn btn-primary" asp-page-handler="save">save</button>
        <a asp-page="list" class="btn btn-dark">cancel</a>
    </div>
</form>

add页面使用一个form表单作为容器,里面的文本框使用asp-for跟model的student属性建立联系。
运行一下:
ASP.NET Core Razor Pages 初探
asp-for会把关联的属性字段的值作为input元素的value的值,会把关联的属性名+字段的名称作为input元素的name属性的值。看看生成的html源码:

<input type="text" class="form-control" id="student_name" name="student.name" value="">

使用asp-page-handler来映射模型方法

我们的save是一次post提交,显然我们需要一个后台方法来接受这次请求并处理它。使用asp-page-handler="save"可以跟模型的onpostsave方法做映射。onpost前缀表示对post请求做响应,这又有点像webapi。那么asp-page-handler为什么能映射模型的方法呢?继续看看生成的源码:

<button type="submit" class="btn btn-primary" formaction="/student/add?handler=save">save</button>

看到这里就明白了。最后生成的button上有个formaction属性,值为/student/add?handler=save。formaction相当于在form元素上指定action属性的提交地址,并且在url上附带了一个参数handler=save,这样后台就能查找具体要执行哪个方法了。不过据我的经验formaction属性存在浏览器兼容问题。

使用bindpropertyattribute进行参数绑定

光能映射后台方法还不够,我们还需要把前端的数据提交到后台,并且拿到它。这里可以使用bindpropertyattribute来自动完成提交的表单数据跟模型属性之间的映射。这样我们的方法可以是无参的方法。

        [bindproperty]
        public student student { get; set; }

看到这里突然有种mvvm模式的既视感了。虽然不是实时的双向绑定,但是也实现了简单的前后端绑定技术。另外提一句既然我们前端的数据是通过表单提交,那么跟mvc一样,使用fromformattribute其实一样可以进行参数绑定的。

public iactionresult onpostsave([fromform] stuend student)

这有获取表单数据毫无问题。

在后台方法进行页面导航

当保存成功后需要使页面跳转到列表页面,可以使用redirecttopage等方法进行跳转,onpostsave方法的返回值类型也改成iactionresult,这就非常mvc了,跟action方法一模一样的套路。

public iactionresult onpostsave()
        {
            _studentrepository.add(student);
            return redirecttopage("list");
        }

修改编辑(student/update)页面

修改,删除页面就没什么好多讲的了,使用前面的知识点轻松就能实现。
修改cshtml模板:

@page
@model razorpagecrud.updatemodel
@{
    viewdata["title"] = "update";
}

<h1>update</h1>

<form method="post">
    <div class="form-group">
        <label>id</label>
        <input type="number" asp-for="student.id" class="form-control" />
    </div>
    <div class="form-group">
        <label>name</label>
        <input type="text" asp-for="student.name" class="form-control" />
    </div>
    <div class="form-group">
        <label>age</label>
        <input type="number" asp-for="student.age" class="form-control" />
    </div>
    <div class="form-group">
        <label>class</label>
        <input type="text" asp-for="student.class" class="form-control" />
    </div>
    <div class="form-group">
        <label>sex</label>
        <input type="text" asp-for="student.sex" class="form-control" />
    </div>
    <div class="form-group">
        <button type="submit" class="btn btn-primary" asp-page-handler="edit">save</button>
        <a asp-page="list" class="btn btn-dark">cancel</a>
    </div>
</form>

修改updatemodel类:

    public class updatemodel : pagemodel
    {
        private readonly istudentrepository _studentrepository;
        public updatemodel(istudentrepository studentrepository)
        {
            _studentrepository = studentrepository;
        }
        public void onget(int id)
        {
            student = _studentrepository.get(id);
        }

        [bindproperty]
        public student student { get; set; }

        public iactionresult onpostedit()
        {
            _studentrepository.update(student);

            return redirecttopage("list");
        }
    }

运行一下:
ASP.NET Core Razor Pages 初探

修改删除(student/delete)页面

删除页面跟前面一样没什么好多讲的了,使用前面的知识点轻松就能实现。
修改delete.cshtml模板:

@page
@model razorpagecrud.deletemodel
@{
    viewdata["title"] = "delete";
}

<h1>delete</h1>
<h2 class="text-danger">
    确定删除?
</h2>
<form method="post">
    <div class="form-group">
        id: @model.student.id
    </div>
    <div class="form-group">
        name:@model.student.name
    </div>
    <div class="form-group">
        age: @model.student.age
    </div>
    <div class="form-group">
        class: @model.student.class
    </div>
    <div class="form-group">
        sex: @model.student.sex
    </div>
    <div class="form-group">
        <button type="submit" class="btn btn-primary" asp-page-handler="delete" asp-route-id="@model.student.id">delete</button>
        <a asp-page="list" class="btn btn-dark">cancel</a>
    </div>
</form>

修改deletemodel类:

     public class deletemodel : pagemodel
    {
        private readonly istudentrepository _studentrepository;
        public deletemodel(istudentrepository studentrepository)
        {
            _studentrepository = studentrepository;
        }

        public void onget(int id)
        {
            student = _studentrepository.get(id);
        }

        public student student { get; set; }

        public iactionresult onpostdelete(int id)
        {
            _studentrepository.delete(id);

            return redirecttopage("list");
        }
    }  

运行一下:
ASP.NET Core Razor Pages 初探

总结

通过上的简单示例,对razor pages有了大概的了解。razor pages本质上对mvc模式的简化,后台模型聚合了controller跟model的的概念。并且提供了一些内置html属性实现绑定技术。有人说razor pages是webform的继任者,我倒不觉得。个人觉得它更像是mvc/mvvm的一种混合。[bindproperty]有点像wpf里的依赖属性,onpostxxx方法就像是command命令;又或者[bindproperty]像vue的data属性上的字段,onpostxxx像methods里的方法;又或者整个model像极了angularjs的$scope,混合了数据跟方法。只是razor pages毕竟是服务端渲染,不能进行实时双向绑定而已。最后,说实话通过简单的体验,razor pages开发模式跟mvc模式相比并未有什么特殊的优点,不知道后续发展会如何。