Flutter页面传值的几种方式

今天来聊聊Flutter页面传值的几种方式:

  • InheritWidget
  • Notification
  • Eventbus

(当前Flutter版本:2.0.4)

InheritWidget

如果看过Provider的源码的同学都知道,Provider跨组件传值的原理就是根据系统提供的InheritWidget实现的,让我们来看一下这个组件。
InheritWidget是一个抽象类,我们写一个保存用户信息的类UserInfoInheritWidget继承于InheritWidget:

class UserInfoInheritWidget extends InheritedWidget {

  UserInfoBean userInfoBean;
  UserInfoInheritWidget({Key key, this.userInfoBean, Widget child}) : super (child: child);

  static UserInfoWidget of(BuildContext context){
    return context.dependOnInheritedWidgetOfExactType<UserInfoWidget>();
  }

  @override
  bool updateShouldNotify(UserInfoInheritWidget oldWidget) {
    return oldWidget.userInfoBean != userInfoBean;
  }
}

我们在这里面定义了一个静态方法:of,并且传入了一个context,根据context获取当前类,拿到当前类中的UserInfoBean,其实获取主题数据也是根据InheritWidget这种方式获取Theme.of(context),关于of方法后面重点讲一下,updateShouldNotify是刷新机制,什么时候刷新数据

还有一个用户信息的实体:

class UserInfoBean {
  String name;
  String address;
  UserInfoBean({this.name, this.address});
}

我们做两个页面,第一个页面显示用户信息,还有一个按钮,点击按钮跳转到第二个页面,同样也是显示用户信息:

class Page19PassByValue extends StatefulWidget {
  @override
  _Page19PassByValueState createState() => _Page19PassByValueState();
}

class _Page19PassByValueState extends State<Page19PassByValue> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('PassByValue'),
      ),
      body: DefaultTextStyle(
        style: TextStyle(fontSize: 30, color: Colors.black),
        child: Column(
          children: [
            Text(UserInfoWidget.of(context)!.userInfoBean.name),
            Text(UserInfoWidget.of(context)!.userInfoBean.address),
            SizedBox(height: 40),
            TextButton(
              child: Text('点击跳转'),
              onPressed: (){
                Navigator.of(context).push(CupertinoPageRoute(builder: (context){
                  return DetailPage();
                }));
              },
            )
          ],
        ),
      ),
    );
  }
}
class DetailPage extends StatefulWidget {
  @override
  _DetailPageState createState() => _DetailPageState();
}

class _DetailPageState extends State<DetailPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Detail'),
      ),
      body: DefaultTextStyle(
        style: TextStyle(fontSize: 30, color: Colors.black),
        child: Center(
          child: Column(
            children: [
              Text(UserInfoWidget.of(context).userInfoBean.name),
              Text(UserInfoWidget.of(context).userInfoBean.address),
              TextButton(
                    onPressed: () {
                      setState(() {
                        UserInfoWidget.of(context)!.updateBean('wf123','address123');
                      });
                    },
                    child: Text('点击修改'))
            ],
          ),
        ),
      )
    );
  }
}

由于我们这里是跨组件传值,需要把UserInfoWidget放在MaterialApp的上层,并给UserInfoBean一个初始值:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return UserInfoWidget(
      userInfoBean: UserInfoBean(name: 'wf', address: 'address'),
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: MyHomePage(title: 'Flutter Demo Home Page'),
      ),
    );
  }
}

这样就实现了一个跨组件传值,但是还有个问题,我们给UserInfoWidget赋值的时候是在最顶层,在真实业务场景中,如果我们把UserInfo的赋值放在MaterialApp上面,这时候我们还没拿到用户数据呢,所以就要有一个可以更新UserInfo的方法,并且修改后立即刷新,我们可以借助setState,把我们上面定义的UserInfoWidget改个名字然后封装在StatefulWidget 中:

class _UserInfoInheritWidget extends InheritedWidget {

  UserInfoBean userInfoBean;
  Function update;
  _UserInfoInheritWidget({Key key, this.userInfoBean, this.update, Widget child}) : super (child: child);

  updateBean(String name, String address){
    update(name, address);
  }

  @override
  bool updateShouldNotify(_UserInfoInheritWidget oldWidget) {
    return oldWidget.userInfoBean != userInfoBean;
  }
}

class UserInfoWidget extends StatefulWidget {
  UserInfoBean userInfoBean;
  Widget child;
  UserInfoWidget({Key key, this.userInfoBean, this.child}) : super (key: key);

  static _UserInfoInheritWidget of(BuildContext context){
    return context.dependOnInheritedWidgetOfExactType<_UserInfoInheritWidget>();
  }
  @override
  State<StatefulWidget> createState() => _UserInfoState();
}

class _UserInfoState extends State <UserInfoWidget> {

  _update(String name, String address){
    UserInfoBean bean = UserInfoBean(name: name, address: address);
    widget.userInfoBean = bean;
    setState(() {});
  }
  @override
  Widget build(BuildContext context) {
    return _UserInfoInheritWidget(
      child: widget.child,
      userInfoBean: widget.userInfoBean,
      update: _update,
    );
  }
}

上面把继承自InheritWidget的类改了一个名字:_UserInfoInheritWidget,对外只暴露用StatefulWidget封装过的UserInfoWidget,向_UserInfoInheritWidget传入了包含setState的更新数据方法,更新数据的时候通过UserInfoWidget.of(context)获取到继承于InheritWidget的_UserInfoInheritWidget类,调用updateBean方法实际上就调用了包含setState的方法,所以做到了数据更新和页面刷新

下面重点说一下UserInfoWidget.of(context)是如何获取到继承于InheritWidget类的对象的,通过查看类似的方法:Theme.of(context)发现是根据dependOnInheritedWidgetOfExactType,于是我们也照着它的样子获取到了_UserInfoInheritWidget,点到dependOnInheritedWidgetOfExactType源码中看一下,发现跳转到了BuildContext中定义了这个方法:

  T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object? aspect });

了解Widget、Element、RenderObject三只之间关系的同学都知道,其实context是Element的一个实例,BuildContext的注释也提到了这一点:

我们可以在Element中找到这个方法的实现:

@override
  T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
    if (ancestor != null) {
      assert(ancestor is InheritedElement);
      return dependOnInheritedElement(ancestor, aspect: aspect) as T;
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }

_inheritedWidgets是从哪来的,我们搜索一下在Element中发现

void _updateInheritance() {
    assert(_lifecycleState == _ElementLifecycle.active);
    _inheritedWidgets = _parent?._inheritedWidgets;
  }

再看一下_updateInheritance方法是什么时候调用的

@mustCallSuper
  void mount(Element? parent, dynamic newSlot) {
    ...
    ...省略无关代码
    _parent = parent;
    _slot = newSlot;
    _lifecycleState = _ElementLifecycle.active;
    _depth = _parent != null ? _parent!.depth + 1 : 1;
    if (parent != null) // Only assign ownership if the parent is non-null
      _owner = parent.owner;
    final Key? key = widget.key;
    if (key is GlobalKey) {
      key._register(this);
    }
    _updateInheritance();//这里调用了一次
  }

还有:

@mustCallSuper
  void activate() {
    ...
    ...已省略无关代码
    final bool hadDependencies = (_dependencies != null && _dependencies!.isNotEmpty) || _hadUnsatisfiedDependencies;
    _lifecycleState = _ElementLifecycle.active;
    _dependencies?.clear();
    _hadUnsatisfiedDependencies = false;
    _updateInheritance();//这里又调用了一次
    if (_dirty)
      owner!.scheduleBuildFor(this);
    if (hadDependencies)
      didChangeDependencies();
  }

从上面代码我们可以看到每个页面的Element都会通过_parent向下级传递父级信息,而我们的UserInfoWidget就保存在_parent中的_inheritedWidgets集合中:Map<Type, InheritedElement>? _inheritedWidgets;,当_inheritedWidgets在页面树中向下传递的时候,如果当前Widget是InheritWidget,在当前Widget对应的Element中先看_parent传过来的_inheritedWidgets是否为空,如果为空就新建一个集合,把自己存到这个集合中,以当前的类型作为key(这也是为什么调用of方法中的context.dependOnInheritedWidgetOfExactType方法为什么要传当前类型的原因),从_inheritedWidgets集合中去取值;如果不为空直接把自己存进去,这就是of的原理了。

Notification

上面讲的InheritWidget一般是根部组建向子级组件传值,Notification是从子级组件向父级组件传值,下面我们来看一下它的用法

class Page19PassByValue extends StatefulWidget {
  @override
  _Page19PassByValueState createState() => _Page19PassByValueState();
}

class _Page19PassByValueState extends State<Page19PassByValue> {
  UserInfoBean userInfoBean = UserInfoBean(name: 'wf', address: 'address');

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('PassByValue'),
      ),
      body: Center(
        child: NotificationListener<MyNotification>(
          onNotification: (MyNotification data) {
            userInfoBean = data.userInfoBean;
            setState(() {});
            ///这里需要返回一个bool值,true表示阻止事件继续向上传递,false表示事件可以继续向上传递到父级组件
            return true;
          },
          child: Builder(
          ///这里用了一个Builder包装了一下,为的是能取到
          ///NotificationListener的context
            builder: (context) {
              return Column(
                children: [
                  Text(userInfoBean.name),
                  Text(userInfoBean.address),
                  Container(
                    child: FlatButton(
                      child: Text('点击传值'),
                      onPressed: () {
                        MyNotification(userInfoBean: UserInfoBean(name: 'wf123', address: 'address123')).dispatch(context);
                      },
                    ),
                  )
                ],
              );
            },
          ),
        ),
      ),
    );
  }
}
///Notification是一个抽象类,
///使用Notification需要自定义一个class继承Notification
class MyNotification extends Notification {
  UserInfoBean userInfoBean;
  MyNotification({this.userInfoBean}) : super();
}

我们到源码中看一下这个dispatch方法:

void dispatch(BuildContext target) {
    // The `target` may be null if the subtree the notification is supposed to be
    // dispatched in is in the process of being disposed.
    target?.visitAncestorElements(visitAncestor);
  }

target就是我们传进来的context,也就是调用了BuildContext的visitAncestorElements方法,并且把visitAncestor方法作为一个参数传过去,visitAncestor方法返回一个bool值:

  @protected
  @mustCallSuper
  bool visitAncestor(Element element) {
    if (element is StatelessElement) {
      final StatelessWidget widget = element.widget;
      if (widget is NotificationListener<Notification>) {
        if (widget._dispatch(this, element)) // that function checks the type dynamically
          return false;
      }
    }
    return true;
  }

我们进入Element内部看一下visitAncestorElements方法的实现:

@override
  void visitAncestorElements(bool visitor(Element element)) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    Element? ancestor = _parent;
    while (ancestor != null && visitor(ancestor))
      ancestor = ancestor._parent;
  }

当有父级节点,并且visitor方法返回true的时候执行while循环,visitor是Notification类传进来的方法,回过头再看visitor方法的实现,当Element向visitor方法传递的ancestor是NotificationListener类的情况下,再判断widget._dispatch方法,而widget._dispatch方法:

final NotificationListenerCallback<T>? onNotification;

  bool _dispatch(Notification notification, Element element) {
    if (onNotification != null && notification is T) {
      final bool result = onNotification!(notification);
      return result == true; // so that null and false have the same effect
    }
    return false;
  }

就是我们在外面写的onNotification方法的实现,我们在外面实现的onNotification方法返回true(即阻止事件继续向上传递),上面的while循环主要是为了执行我们onNotification里面的方法.

总结一下:MyNotification执行dispatch方法,传递context,根据当前context向父级查找对应NotificationListener,并且执行NotificationListener里面的onNotification方法,返回true,则事件不再向上级传递,如果返回false则事件继续向上一个NotificationListener传递,并执行里面对应的方法。Notification主要用在同一个页面中,子级向父级传值,比较轻量级,不过如果我们用了Provider可能就就直接借助Provider传值了。

Eventbus

Eventbus用于两个不同的页面,可以跨多级页面传值,用法也比较简单,我创建了一个EventBusUtil来创建一个单例
import 'package:event_bus/event_bus.dart';

class EventBusUtil {
  static  EventBus ? _instance;
  static EventBus getInstance(){
    if (_instance == null) {
      _instance = EventBus();
    }
    return _instance!;
  }
}

在第一个页面监听:

class Page19PassByValue extends StatefulWidget {
  @override
  _Page19PassByValueState createState() => _Page19PassByValueState();
}

class _Page19PassByValueState extends State<Page19PassByValue> {
  UserInfoBean userInfoBean = UserInfoBean(name: 'wf', address: 'address');
  @override
  void initState() {
    super.initState();
    EventBusUtil.getInstance().on<UserInfoBean>().listen((event) {
      setState(() {
        userInfoBean = event;
      });
    });
  }

  @override
  void dispose() {
    super.dispose();
    //不用的时候记得关闭
    EventBusUtil.getInstance().destroy();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('PassByValue'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(userInfoBean.name),
            Text(userInfoBean.address),
            TextButton(onPressed: (){
              Navigator.of(context).push(CupertinoPageRoute(builder: (_){
                return EventBusDetailPage();
              }));
            }, child: Text('点击跳转'))

          ],
        ),
      ),
    );
  }
}

在第二个页面发送事件:

class EventBusDetailPage extends StatefulWidget {
  @override
  _EventBusDetailPageState createState() => _EventBusDetailPageState();
}

class _EventBusDetailPageState extends State<EventBusDetailPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('EventBusDetail'),
      ),
      body: Center(
        child: TextButton(onPressed: (){
          EventBusUtil.getInstance().fire(UserInfoBean(name: 'name EventBus', address: 'address EventBus'));
        }, child: Text('点击传值')),
      ),
    );
  }
}

我们看一下EventBus的源码,发现只有几十行代码,他的内部是创建了一个StreamController,通过StreamController来实现跨组件传值,我们也可以直接使用一下这个StreamController实现页面传值:

class Page19PassByValue extends StatefulWidget {
  @override
  _Page19PassByValueState createState() => _Page19PassByValueState();
}

StreamController controller = StreamController();

class _Page19PassByValueState extends State<Page19PassByValue> {

  //设置一个初始值
  UserInfoBean userInfoBean = UserInfoBean(name: 'wf', address: 'address');
  @override
  void initState() {
    super.initState();
    controller.stream.listen((event) {
      setState(() {
        userInfoBean = event;
      });
    });
  }

  @override
  void dispose() {
    super.dispose();
    //页面销毁的时候记得关闭
    controller.close();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('PassByValue'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(userInfoBean.name),
            Text(userInfoBean.address),
            TextButton(onPressed: (){
              Navigator.of(context).push(CupertinoPageRoute(builder: (_){
                return MyStreamControllerDetail();
              }));
            }, child: Text('点击跳转'))
          ],
        ),
      )
    );
  }
}
class MyStreamControllerDetail extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _MyStreamControllerDetailState();
  }
}
class _MyStreamControllerDetailState extends State <MyStreamControllerDetail> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('StreamController'),
      ),
      body: Center(
        child: TextButton(onPressed: (){
        //返回上个页面,会发现页面的数据已经变了
          controller.sink.add(UserInfoBean(name: 'StreamController pass name: 123', address: 'StreamController pass address 123'));
        }, child: Text('点击传值'),),
      ),
    );
  }
}

到此这篇关于Flutter页面传值的几种方式的文章就介绍到这了,更多相关Flutter页面传值内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • flutter 路由跳转的实现示例

    路由 做Android/iOS原生开发的时候,要打开一个新的页面,你得知道你的目标页面对象,然后初始化一个Intent或者ViewController,再通过startActivity或者pushViewController来推出一个新的页面,不能跟web一样,直接丢一个链接地址就跳转到新的页面.当然,可以自己去加一个中间层来实现这些功能. Flutter里面是原生支持路由的.Flutter的framework提供了路由跳转的实现.我们可以直接使用这些功能. Flutter路由介绍 Flutte

  • Flutter学习教程之Route跳转以及数据传递

    前言 我们知道移动应用页面跳转是非常重要的一部分,几乎我们的程序和用户打交道的就是页面,或者叫view,我们Android基本都是Activity和Fragment.而且Flutter当中叫做Route,它就是与用户打交道的页面.本文详细探索一下Flutter当中页面之间是怎么交互的. Route类似Android中Activity,所以Flutter中的页面跳转类似Android中Activity之间跳转,Intent携带传递的数据. 正文 页面跳转 我们现在看看Flutter中是怎么进行页面

  • Flutter路由的跳转、动画和传参详解(最简单)

    路由 做Android/iOS原生开发的时候,要打开一个新的页面,你得知道你的目标页面对象,然后初始化一个Intent或者ViewController,再通过startActivity或者pushViewController来推出一个新的页面,不能跟web一样,直接丢一个链接地址就跳转到新的页面.当然,可以自己去加一个中间层来实现这些功能. Flutter里面是原生支持路由的.Flutter的framework提供了路由跳转的实现.我们可以直接使用这些功能. Flutter路由介绍 Flutte

  • Flutter 使用Navigator进行局部跳转页面的方法

    Navigator组件使用的频率不是很高,但在一些场景下非常适用,比如局部表单多页填写.底部导航一直存在,每个tab各自导航场景. Navigator 是管理路由的控件,通常情况下直接使用Navigator.of(context)的方法来跳转页面,之所以可以直接使用Navigator.of(context)是因为在WidgetsApp中使用了此控件,应用程序的根控件通常是MaterialApp,MaterialApp包含WidgetsApp,所以可以直接使用Navigator的相关属性. Nav

  • Flutter中如何实现无Context跳转详解

    背景介绍 Navigator.of(context).push(MaterialPageRoute(builder: (context){ return DemoPage(); })); 在日常的项目开发中,我们一般push一个新页面是用上面的方法的,利用Navigator.of(context)来进行push或者pop操作. 缺点:这种情况是必须传context的,目的是为了利用Navigator.of(context)来获取到NavigatorState对象,然后才能进行push或者pop操

  • Flutter页面传值的几种方式

    今天来聊聊Flutter页面传值的几种方式: InheritWidget Notification Eventbus (当前Flutter版本:2.0.4) InheritWidget 如果看过Provider的源码的同学都知道,Provider跨组件传值的原理就是根据系统提供的InheritWidget实现的,让我们来看一下这个组件. InheritWidget是一个抽象类,我们写一个保存用户信息的类UserInfoInheritWidget继承于InheritWidget: class Us

  • Android Flutter实现搜索的三种方式详解

    目录 示例 1 :使用搜索表单创建全屏模式 编码 示例 2:AppBar 内的搜索字段(最常见于娱乐应用程序) 编码 示例 3:搜索字段和 SliverAppBar 编码 结论 示例 1 :使用搜索表单创建全屏模式 我们要构建的小应用程序有一个应用程序栏,右侧有一个搜索按钮.按下此按钮时,将出现一个全屏模式对话框.它不会突然跳出来,而是带有淡入淡出动画和幻灯片动画(从上到下).在圆形搜索字段旁边,有一个取消按钮,可用于关闭模式.在搜索字段下方,我们会显示一些搜索历史记录(您可以添加其他内容,如建

  • 分享Vue子组件接收父组件传值的3种方式

    目录 1.简单声明接收 2.接收数据的同时进行 类型限制 3.接收数据的同时对 数据类型.必要性.默认值 进行限制 父组件代码↓ <template>     <div>         <div>父组件</div>         <Student :name="name" :age="age"></Student>     </div> </template> <

  • vue父子组件动态传值的几种方式及注意问题详解

    1.vue父组件向子组件动态传值的两种方法 在一些项目需求中需要父组件向子组件动态传值,比如我这里的需求是,父组件动态通过axios获取返回的图片url数组然后传给子组件,上传图片的子组件拿到该数组后进行遍历并展示图片,因为有时候获取到的会是空,所以这里要考虑到动态获取. 方法有两种, vue父组件向子组件动态传值方法一: props传值,这里注意一个问题,传过来的值需要用watch监听并赋值,否则这里获取到的是空数组 父组件: <uploadImg :width="200" :

  • 详解ASP.NET 页面之间传值的几种方式

    开篇概述 对于任何一个初学者来说,页面之间传值可谓是必经之路,却又是他们的难点.其实,对大部分高手来说,未必不是难点. 回想2016年面试的将近300人中,有实习生,有应届毕业生,有1-3年经验的,有3-5年经验的,有5-10年经验的,对于所有的面试者,我几乎问了同一道题:"请说说你所知道的页面之间传值的几种形式和方法,并阐述他们的原理和过程",关于这道题,从大家的回答来看,结果并不是很理想,从种类上来说,大部分人回答5种左右,极少部分能回答8种,没有超过8种的,但从深度上来说,很少有

  • 详解SpringMVC注解版前台向后台传值的两种方式

    一.概述. 在很多企业的开法中常常用到SpringMVC+Spring+Hibernate(mybatis)这样的架构,SpringMVC相当于Struts是页面到Contorller直接的交互的框架也是界面把信息传输到Contorller层的一种架构,通过这个架构可以让我们把页面和Contorller层解耦,使得开发人员的分工更加明确. 二.代码演示. 1.首先配置SpringMVC环境. 1.1导入jar. 值得注意的是红色标记的commons-logging这个jar包一定得引入进去不然会

  • ASP.NET实现页面传值的几种方法小结

    这三种方法是:QueryString,Session和Server.Transfer. 通过URL链接地址传递  send.aspx:  复制代码 代码如下: protected void Button1_Click(object sender, EventArgs e)    {        Request.Redirect("Default2.aspx?username=honge");    } receive.aspx: 复制代码 代码如下: string username

  • c# WinForm 窗体之间传值的几种方式(小结)

    前言 小编最近维护一个Winfrom窗体,是项目中CS端的主窗体,很多子窗体需要从主窗体获取值,同时子窗体还需要给主窗体回传值,下面来给大家介绍一下. 正文 本文中以主窗体为frmMain,子窗体为frmGroup ,两窗体之间的传值来做示例. 方式一: 使用公共静态变量传值 主窗体frmMain中代码 public partial class frmMain : Form { //声明工位ID 为公共静态变量 public static string terminalID = "";

  • vue中兄弟组件传值的两种方式小结

    目录 一. bus总线传值的使用 二. 使用常规的传值:(子传父,父再传子) 总结 本demo主要是为了演示vue项目中兄弟组件之间的传值,这里我演示了两种方式: a. bus总线传值: b. 我自己一般把它当成常规的传值(其实也就是子组件A传父组件,父组件再传子组 件B) 下边开始本次demo的编写: 一. bus总线传值的使用 在项目中创建一个单独的eventBus.js文件 该js文件的内容很简单,就是暴露一个vue实例而已. 有人喜欢在main.js全局引入该js文件,我一般在需要使用到

  • ASP.NET MVC中Controller控制器向View视图传值的几种方式

    一.准备工作 创建一个ASP.NET MVC程序,然后在Models文件夹里面新添加Student实体类,用来模拟从Controller向View传递数据,Student类定义如下: using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MVCStudyDemo.Models { public class Student { public int ID { ge

随机推荐