net mvc
行业动态 更新时间:2023-04-04 21:21:36
光圈是什么意思-解压文件下载
![net mvc](/uploads/image/0817.jpg)
2023年4月4日发(作者:天龙百宝箱)
如何创建的控制器(controller)与视图(view)。
如何使用EntityFrameworkcode-first范例来创建一个新的数据库。
如果要创建一个3的工程时,首先运行VisualWebDeveloper2010Express
(本教程中简称“VisualWebDeveloper”),并且在起始页(startpage)中选择“新建项
VisualWebDeveloper是一个集成开发环境,你可以使用它来进行各种应用程序的开发。
在VisualWebDeveloper的菜单的下面有一个工具条,可以直接点击工具条中的各个工具
按钮来进行各种操作,也可以直接点击菜单中的各个菜单项来进行各种操作,此处我们点击
图1-1VisualWebDeveloper2010Express中的起始页
你可以使用VisualBasic或VisualC#作为开发语言来创建应用程序。在本教程中,
选择C#来作为开发语言。点击“新建项目”菜单项后,在打开的“新建项目”对话框中,
双击左边的“VisualC#”使其成为展开状态,然后点击“Web”,点击右边的“
3Web应用程序”,然后在下方的名称文本框中填入应用程序的名称,在本教程中命名为
图1-2在新建项目对话框中选择3应用程序并为应用程序命名
在接下来打开的“新3项目”对话框中,点击选中“Internet应用程
序”,在“视图引擎”下拉框中保持默认的“Razor”选项不作修改(Razor视图是
MVC3种新增的一种十分重要的视图类型,使用它可以使得Web应用程序的开发变得更加方
点击确定按钮,VisualWebDeveloper会为你所创建的项目提供一个默
认模板,这样的话你就拥有了一个可以立刻运行的应用程序。默认的模板中提供的是一个很
简单的显示“欢迎使用!”文字的应用程序,你可以以此作为你的开发起点。
图1-4VisualWebDeveloper提供了一个默认的应用程序模板
点击“调试”菜单中的“启动调试”菜单项(该菜单项的快捷键为F5),VisualWeb
Developer将启动一个内置的服务器,并且在该服务器中打开当前Web应用程序的主页,如
请注意该页面在浏览器中的地址为“http://localhost:4423/”。其中“localhost”
代表了本机上你刚刚创建的Web应用程序的临时网站地址,4423代表了VisualWeb
Developer使用的一个随机端口,每次调试的时候,VisualWebDeveloper都会使用这个端
口来作为内置服务器的端口号。在各计算机上,该端口号都是不相同的,因为该端口号是
在这个模板应用程序的页面的右上角,提供了两个按钮与一个“登录”链接,点击“登
录”链接,页面跳转到登录页面,点击“主页”按钮,页面返回到主页,点击“关于”按钮,
接下来,让我们开始逐步将这个默认的应用程序修改为我们所要的应用程序,在这个过
程中逐步了解3的有关知识。首先,让我们关闭浏览器并开始代码的修改工作。
MVC的全称为model-view-controller(模型-视图-控制器)。MVC是一种开发应用程序
的模式,这个模式已经具有了很好的框架架构,并且十分容易维护。使用MVC开发出来的应
控制器(Controller):控制器类处理客户端向Web应用程序发出的请求,获取数据,
模型(Model):模型类代表了应用程序的数据,这些数据通常具有一个数据验证逻
视图(View):视图类是Web应用程序中用来生成并显示HTML格式的服务器端对客
在本教程中,将全面介绍这些概念,并且向你展示如何利用它们来搭建一个应用程序。
首先,让我们来创建一个控制器(controller)类。在解决方案资源管理器中,鼠标右击
Controllers文件夹,并且点击添加-〉控制器,如图2-1所示。
在弹出的“添加控制器”对话框中,将控制器命名为“HelloWorldController”,然后
观察解决方案资源管理器中新增加了一个文件,名字为,并
修改打开的文件,在HelloWorldController类中,创建如
代码清单2-1中所示的两个方法,控制器将返回一个HTML格式的字符串。
publicclassHelloWorldController:Controller
//GET:/HelloWorld/Welcome/
return"这是我的Welcome方法...";
在这个修改后的HelloWorldController控制器中,第一个方法名为Index。现在让我
们从浏览器中调用该方法。运行应用程序(按F5键或Ctrl+F5键),在打开的浏览器中的地
址栏后面,添加“HelloWorld”路径(譬如,在我的计算机上,浏览器中地址为
http://localhost:4423/HelloWorld),画面显示如图2-4所示。由于在Index方法中,直
接返回了一个HTML格式的字符串,所以在浏览器中将该字符串显示出来。
图2-4HelloWorldController控制器中Index方法的运行结果
在中,可以根据浏览器中的输入地址来调用不同的控制器或控制七种不同
的方法。的默认的映射逻辑使用如下所示的格式来决定应该调用什么控制器或
/[Controller]/[ActionName]/[Parameters]
URL地址的第一部分决定调用哪个控制器类,所以“/HelloWorld”映射到
HelloWorldController控制器类。第二部分决定调用控制器中的哪个方法。所以
“/HelloWorld/Index”将会调用HelloWorldController控制器类的Index方法。由于
Index方法是控制器类的默认方法(可以另外指定控制器类的默认方法),所以也可只输入
在浏览器的地址栏中,输入“http://localhost:xxxx/HelloWorld/Welcome”,将会调
用HelloWorldController控制器类的Welcome方法,该方法返回“这是我的Welcome方
法...”文字,所以浏览器中显示该文字,如图2-5所示。
图2-5HelloWorldController控制器中Welcome方法的运行结果
接下来,让我们修改Welcome方法,以便在URL地址栏中可以传递一些参数给该方法(例
如:/HelloWorld/Welcome?name=Scott&numtimes=4)。修改后的代码如下所示。注意这里我
们使用了C#的可选参数,当URL地址中没有使用numtimes参数时,该参数被默认设定为1。
publicstringWelcome(stringname,intnumTimes=1)
code("Hello"+name+",NumTimesis:"+numTimes);
“http://localhost:xxxx/HelloWorld/Welcome?name=Scott&numtimes=4”,运行结果显示
如图2-6所示。浏览器自动将URL地址栏中的参数映射成Welcome方法中的传入参数。
到现在为止,我们展示了MVC中的“VC”(视图与控制器)部分的工作机制,控制器返
回HTML字符串。很显然大多数情况下你不想让控制器直接返回HTML字符串,因为那样的话
编码起来就太麻烦了。所以我们需要使用不同的视图模板文件来帮助生成HTML格式的页面
文件,在下一节中让我们来看一下如何在3中使用视图。
在本节中我们修改HelloWorldController类,以便使用视图来向客户端展示HTML格式
我们使用3中新增的Razor视图引擎来创建视图。Razor视图模板文件的后缀
名为.cshtml,它提供了一种简洁的方式来创建HTML输出流。Razor视图大大减少了在书写
视图模板文件时所需要输入的字符,提供了一个最快捷,最简便的编码方式。
这里,我们在HelloWorldController类的Index方法中添加使用一个视图。在修改前
的Index方法中返回一个字符串,我们修改这个方法来使它返回一个视图,代码如下所示。
publicActionResultIndex()
这段代码表示Index方法使用一个视图模板来在浏览器中生成HTML格式的页面文件。
接着,让我们来添加一个Index方法所使用的视图模板。在Index方法中点击鼠标右键,然
后点击“添加视图”,将会弹出一个“添加视图”对话框。
在该对话框中,不做任何修改,直接点击添加按钮,观察解决方案资源管理器中,在
MvcMovie项目下的Views文件夹下创建了一个HelloWorld文件夹,并且在该文件夹中创建
了一个文件,同时该文件呈打开状态,如图3-3所示。
让我们在该文件中追加一些文字,代码如代码清单3-1所示。
运行应用程序,输入地址“http://localhost:xxxx/HelloWorld”。由于在Index方法
中并没有做任何事情,只是简单地一行代码—“returnView()”,该行代码表示我们使用一
个视图模板文件来在浏览器中展示响应结果。因为我们并没有显式指定使用哪个视图模板文
件,所以使用了默认的Views文件夹下的HelloWorld文件夹下的视图模板文
件。该视图模板文件中只有简单的两行文字,在浏览器中的显示结果如图3-4所示。
看上去还不错,但是请注意,该网页的标题为“首页”,但是网页中的大标题文字却为
首先,让我们修改页面大标题中的“我的MVC应用程序”文字。这段文字是所有页面中
的公共大标题,在这个应用程序中,虽然所有页面中都显示了这个共同的大标题,但只有一
处地方对其进行了设置。打开解决方案资源管理器中Views文件夹下的Shared文件夹下的
_文件。该文件被称为布局页面,位于公有文件夹Shared下,被所有其他网
布局模板页允许你统一在一个地方指定整个Web应用程序或Web网站的所有HTML页面
的布局方法。请注意文件底部的“@RenderBody()”代码行。@RenderBody()是一个占位符,
代表了所有你创建出来的实际应用的视图页面,在这里统一指定。将布局模板文件中的“我
的MVC应用程序”修改为“我的MVCMovie应用程序”。代码如下所示。
运行应用程序,注意网页中的大标题被修改为“我的MVCMovie应用程序”。点击“关
于”链接,你可以看见“关于”页面中的大标题也被修改为“我的MVCMovie应用程序”。
由此可以看出一旦修改了布局页面中的某处地方,该修改将会被应用到所有页面中。
@Link("主页","Index","Home") @Link("关于","About","Home") 打开Views文件夹下的HelloWorld文件夹下的文件。这里我们修改两处
地方:首先,修改浏览器中的标题,然后修改
标签中的小标题文字。修改后代码如代码
ViewBag对象的Title属性代表了显示该页面时的浏览器中的标题文字。让我们回头看
一下布局模板文件,在该文件的
区段中的
标签中使用了这个值来作为浏览器中的网页标题。同时,通过这种方法,你可以很容易地在你的视图模板文件与布局模板文件
运行应用程序,在地址栏中输入“http://localhost:xxxx/HelloWorld”,注意浏览器
中的网页标题,页面中的小标题文字都变为修改后的标题文字(如果没有发生变化的话,则
可能你的网页被缓存住了,可以按Ctrl+F5键来在重新刷新页面时取消缓存)。
同时也请注意_文件中的占位符中的内容被替换成了视图
模板中的内容,所以浏览器中展示的是一个单一的HTML文件。浏览器中的运行结果如图3-7
此处,我们的数据(“这是我的第一个视图模板”文字)是被直接书写在文件中的,也
就是说我们使用到了MVC应用程序的“V”(视图View)与“C”(控制器Controller)。接下
来,我们讲解一下如何创建一个数据库并从该数据库中获取模型数据。
在我们使用数据库并介绍模型之前,首先我们介绍一下如何将控制器中的信息传递给视
图。浏览器接收到一个URL请求后,将会调用控制器类来进行响应。你可以在控制器类中进
行对接收到的页面参数进行处理的代码,你可以在控制器类中书写从数据库中获取数据的代
码,你也可以在控制器类中书写代码来决定返回给客户端什么格式的响应文件。控制器可以
利用视图模板文件来生成HTML格式的响应文件并显示在浏览器中。
控制器类负责提供视图模板文件在生成HTML格式的响应文件时所需要的任何数据或对
象。一个视图模板文件不应该执行任何业务逻辑,也不应该直接和数据库进行交互。它只能
和控制器类进行交互,获取控制器类所提供给它的数据,这样可以使你的代码更加清晰,容
现在在我们的应用程序中,HelloWorldController控制器类中的Welcome方法带有两
个参数—name与numTimes,Welcome方法直接向浏览器输出这两个参数的参数值。这里,我
们修改该方法使其不再直接输出数据,而是使用一个视图模板。该视图模板将生成一个动态
的响应流,这意味着我们需要将数据从控制器类传递给视图以便利用该数据来生成该响应
流。我们在该控制器类中将视图模板所需要的数据送入一个ViewBag对象中,该对象可以被
打开文件,修改Welcome方法,在该方法中为ViewBag对象
添加一个Message属性与NumTimes属性,并且将属性值分别设定为经过处理后的name参数
值与numTimes参数值。ViewBag对象是一个动态对象,你可以为它添加任何属性并赋上属
性值。在未赋值之前该属性是不生效的,直到你赋值为止。修改后的
publicclassHelloWorldController:Controller
publicActionResultIndex()
//GET:/HelloWorld/Welcome/
publicActionResultWelcome(stringname,intnumTimes=1)
现在ViewBag对象中已经包含了数据,它将被自动传递给视图。
接下来,我们需要创建一个Welcome视图模板。在“调试”菜单中,点击“生成
MvcMovie”将应用程序进行编译,如图3-8所示。
接下来,在Welcome方法中点击鼠标右键,然后点击“添加视图”,弹出对话框如图
在该对话框中不做任何修改,直接点击添加按钮,View文件夹下的HelloWorld文件假
种自动被创建了一个文件,打开该文件,在
元素下添加代码,让浏览
器显示URL地址中传入的name参数中设定的文字,显示次数等于URL地址中传入的numTimes
参数中设定的次数。修改后的文件中的代码如代码清单3-5所示。
“http://localhost:xx/HelloWorld/Welcome?name=Scott&numtimes=4”,该地址栏中的页
面参数将会自动传递给控制器。控制器将会把这些参数值放入ViewBag对象中并且传递给视
这里,我们使用了模型“M”的一种方式,但是不是数据库的方式。在下一节中,我们
将创建一个数据库,并且介绍如何对该数据库中的数据进行处理。
在本节中我们将追加一些类来管理数据库中的电影。这些类将成为我们的MVC应用程序
我们将使用一个.NETFramework的被称之为“EntityFramework”的数据访问技术来
定义这些模型类,并使用这些类来进行操作。EntityFramework(通常被简称为“EF”)支
持一个被称之为“code-first”的开发范例。Code-first允许你通过书写一些简单的类来
创建模型对象。你可以通过访问这些类的方式来访问数据库,这是一种非常方便快捷的开发
我们可以利用NuGet包管理器(安装3时会自动安装)来把EFCodeFirst
类库添加到我们的MvcMovie工程中。这个类库使得我们可以直接使用code-first。点击“工
具”菜单下的“LibraryPackageManager”子菜单下的“AddLibraryPackage
点击“AddLibraryPackageReference”菜单选项后,将会弹出一个对话框,标题为
“AddLibraryPackageReference”,如图4-2所示。
图4-2“AddLibraryPackageReference”对话框
默认状态下,左边的“All”选项处于选择状态。因为还没有安装任何包,所以右边面
板中显示“找不到任何项”。点击左边面板中的“online”选项,NuGet包管理器将会在服
服务器上有几百个当前能够获取的包,现在我们只关注EFCodeFirst包。在右上角的搜
索输入框中输入“EFCode”。在检索结果中,选择EFCodeFirst包,并且点击Install按钮
点击了install按钮后,会弹出一个接受许可证窗口,如图4-5所示,在这个窗口中必
须要点击“IAccept”按钮,接受许可证条款,安装才能继续进行。
安装完毕后,点击close按钮。我们的MvcMovie工程中会自动加载EntityFramework
图4-6安装完毕后EntityFramework程序集被自动加载
在解决方案资源管理器中,鼠标右击Models文件夹,点击“添加”菜单下的“类”,
点击“类”菜单项后,会弹出“添加新项”对话框,在该对话框中将类名命名为
然后点击添加按钮,观察解决方案资源管理器中,Models文件夹下添加了一个
类定义文件,并且该文件呈打开状态,如图4-9所示。
publicstringTitle{get;set;}
publicDateTimeReleaseDate{get;set;}
publicstringGenre{get;set;}
publicdecimalPrice{get;set;}
我们将利用Movie类来代表数据库中的movie(电影)。每一个Movie对象的实例对应于
数据表中的一行,Movie类中的每一个属性被映射到数据表的每一列。
在同一个文件中,追加如下所示的MovieDBContext类。
publicclassMovieDBContext:DbContext
publicDbSetMovies{get;set;}
MovieDBContext类代表了EntityFramework中的movie数据库的上下文对象,用来处
理数据的存取与更新。MovieDBContext对象继承了EntityFramework中的DbContext基础
类。为了能够引用DbContext类,你需要在文件的头部追加如下所示的using语
publicstringTitle{get;set;}
publicDateTimeReleaseDate{get;set;}
publicstringGenre{get;set;}
publicdecimalPrice{get;set;}
publicclassMovieDBContext:DbContext
publicDbSetMovies{get;set;}
如果要从数据库中存取数据,类似以上所示的代码是必须要写的。在下一节中,我们将
要创建一个新的MoviesController类,用来显示数据库中的数据,并且允许用户创建一个
在本节中,我们将要创建一个新的MoviesController类,并且书写代码来获取数据库
鼠标右击Controllers文件夹,点击“添加”菜单下的“控制器”菜单项,将会弹出一
在该对话框中将控制其命名为MoviesController,然后点击添加按钮,该对话框被关
闭。观察解决方案资源管理器中,Controllers文件夹下增加了一个名为
的文件,并且呈打开状态。让我们更新MoviesController类中的Index
这里需要注意的是,为了引用我们前面创建的MovieDBContext类,你需要在文件头部
修改MoviesController类中的代码为代码清单5-1中所示代码。
代码清单5-1MoviesController类中的完整代码
publicclassMoviesController:Controller
MovieDBContextdb=newMovieDBContext();
publicActionResultIndex()
eDate>newDateTime(1984,6,1)
这段代码实施了一个LINQ查询来获取1984年夏天之后发行的所有电影。我们还需要一
个视图模板来显示这个电影清单,所以在Index方法内点击鼠标右键,然后点击“添加视
由于这里我们需要将一个Movie类传递给视图,所以在“添加视图”对话框中,与本教
程中前几次在该对话框中之行的操作有所不同,前几次我们都是直接点击添加按钮来创建一
个空白的视图模板,但是这一次我们想让VisualWebDeveloper为我们自动创建一个具有
一些默认处理的强类型的视图,所以我们勾选“创建强类型视图”复选框,在模型类下拉框
中选择“Movie()”(如果模型类中不存在这个类,请先点击调试菜单下的
“生成MvcMovie”生成该类),在支架模板下拉框中选择“List”,最后勾选“引用脚本”
点击添加按钮,VisualWebDeveloper自动生成一个视图,并且自动在视图文件中添
加显示电影清单所需要的代码。这里,我们首先用与前面修改HelloWorld控制器所用的视
图中的标题同样的方法来修改这个Movies控制器所用视图中的标题。
代码清单5-2为修改后的这个视图中的完整代码。在这段代码中,我们将releaseDate
(发行日期)属性的格式化字符串从原来的“{0:g}”修改为“{0:d}”(长日期修改为短
日期),将Price(票价)属性的格式化字符串从原来的“{0:F}”修改为“{0:c}”(float
代码清单5-2Movies控制器所用视图中的完整代码
@foreach(variteminModel){
@Link("编辑","Edit",new{id=})| @Link("查看明细","Details",new{id=})| @Link("删除","Delete",new{id=}) |
在本教程的前文中,我们介绍了一个控制器可以使用ViewBag对象来将数据或对象传递
到视图模板中。ViewBag是一个动态对象,它提供了一种便利的,后期绑定的方法来将信息
也提供了一种利用强类型的方法来将数据或对象传递到视图模板中。这种
强类型的方法为你的编码过程提供了很丰富的编辑时的智能输入提示信息与非常好的编译
时的检查。接下来我们将结合这种方法与我们的Movies控制器(MoviesController)与视
请注意在我们的MoviesController控制器的Index方法中,我们在调用View()方法时
publicclassMoviesController:Controller
MovieDBContextdb=newMovieDBContext();
publicActionResultIndex()
eDate>newDateTime(1984,6,1)
请注意如下这一行代码表示将一个movies列表从控制器传递到了视图中。
通过在视图模板文件的头部使用@model语句,视图模板可以识别传入的参数中的对象
类型是否该视图模板所需要的对象类型。请记住当我们在创建这个Movies控制器所使用的
模板时,我们在“添加视图”对话框中勾选了“创建强类型视图”复选框,在模型类下拉框
中选择了“Movie()”,在支架模板下拉框中选择了“List”。所以Visual
WebDeveloper自动在我们的视图模板文件的第一行中添加了如下所示的语句。
@model关键字允许我们在视图模板中直接访问在控制器类中通过使用强类型的“模
型”而传递过来的Movie类的列表。例如,在我们的视图模板中,我们可以
通过foreach语句来遍历这个强类型的模型,访问其中的每一个Movie对象。代码如下所示。
@foreach(variteminModel){
@Link("编辑","Edit",new{id=})| @Link("查看明细","Details",new{id=})| @Link("删除","Delete",new{id=}) |
因为这里的“模型”是强类型的(IEnumerable),所以在循环遍历时“模型”
中的每一个项目(“item”)也是一个强类型的Movie对象,可以直接访问该对象的每一个
属性。同时这也意味着我们可以在编译时检查我们的代码,同时在书写代码时也可以使用代
图5-3可以使用强类型“模型”所带来的智能输入提示信息
我们在本节前面创建了一个MovieDBContext类,用来连接数据库,并将数据库中的记
录映射到Movie对象。你也许会问一个问题,怎样定义数据库连接?接下来我们通过在
打开应用程序根目录下的文件(请注意不是Views文件夹下的
connectionString="datasource=.SQLEXPRESS;Integrated
Security=SSPI;AttachDBFilename=|DataDirectory|;
connectionString="DataSource=.SQLEXPRESS;
InitialCatalog=Movies;PersistSecurityInfo=True;
UserID=aaa;Password=aaaaaaa"
connectionString属性的值表示我们想要使用SQLServerExpress的一个本地实例中
的Movies数据库。当你安装VisualWebDeveloperExpress的时候,安装过程中也会同时
自动在你的计算机中安装SQLServerExpress,你可以利用它来进行有关数据库的管理工
运行应用程序,在浏览器中输入“http://localhost:xxxx/Movies”,浏览器中将会显
EFcode-first如果发现使用我们提供的连接字符串而连接到的数据库中没有
“Movies”数据库,它将自动为我们创建一个。你可以在类似“C:Program
RESSMSSQLDATA”这样的SQLServer的安装目录
另外请注意,在本教程的前面部分中,我们采用如下所示的代码创建了一个Movie模型。
publicstringTitle{get;set;}
publicDateTimeReleaseDate{get;set;}
publicstringGenre{get;set;}
publicdecimalPrice{get;set;}
publicclassMovieDBContext:DbContext
publicDbSetMovies{get;set;}
如您所见,当我们第一次使用MoviesController控制器类来访问MovieDBContext所指
向的实例时,EntityFramework可以自动为你创建一个新的Movies数据库,并且将
MovieDBContext类的Movies属性映射到一个新的Movies表,并且自动将它创建。这个表
中的每一行被映射到一个新的Movie类的实例,Movies表的每一列被映射到Movie类的一
你可以使用SQLServerManagementStudio来查看使用模型创建出来的数据库与数据
在Windows的开始菜单中打开SQLServerManagementStudio,并且连接到
图5-6使用SQLServerManagementStudio连接数据库
点击“连接”按钮进行连接,查看数据库,可以看见Movies数据库与数据表已被创建,
鼠标右击Movies数据表,并且点击“设计”,如图5-8所示。
你可以看见Movies表中各字段的属性,其中ID字段被自动设定为自增长主键,如图
这里请注意Movies表中各字段是如何映射到Movie类中各属性上的。EntityFramework
code-fist自动在你创建的Movie类的基础上创建了这张Movies数据表。
你现在已经可以访问数据库中的Movies数据表,并且有了一个简单的页面来显示这个
表中的内容。在下一节,我们将增加一个追加数据的方法和一个追加数据的视图,并且向数
在本节中我们将要在数据库中追加并保存一些数据。我们将要创建一个表单以及一些表
单输入控件,用来输入数据信息。当用户提交表单时将把这些用户输入的信息保存在数据库
中。我们可以通过在浏览器中输入“http://localhost:xx/Movies/Create”这个URL地址
首先,我们需要在我们的MoviesController类中追加一个Create方法,该方法返回一
个视图,该视图中包含了用户输入信息时所要用到的表单。
publicActionResultCreate()
现在让我们来实现这个Create方法中所要返回的视图,我们将在这个视图中向用户显
示追加数据时所需要用到的表单。在Create方法中点击鼠标右键,并点击上下文菜单中的
在“添加视图”对话框中选择“创建强类型视图”,将模型类指定为“Movie”,在支
点击添加按钮,Views文件夹下的Movies文件夹中将会自动添加一个名为
“”的视图模板文件。因为你在支架模板中选择了“Create”,所以支架模板
会在该视图模板文件中自动生成一些默认代码。打开该文件进行查看,在该文件中已经自动
创建了一个HTML表单,以及一段用来显示校验时错误信息的文字。VisualWebDeveloper
架模板自动生成的创建数据所用视图中的代码如代码清单6-1所示。
代码清单6-1支架模板自动生成的创建数据所用视图中的代码
@tionMessageFor(model=>eDate)
@Link("BacktoList","Index")
这段代码中使用了几个HTML帮助器的方法来帮助简化HTML标签的书写方法。
or帮助器用于显示字段名(”Title”,”ReleaseDate”,”Genre”或
元素。tionMessageFor帮助器用于显示一个针对属性的校验信息。请注意我们
的视图模板的顶部有一个“@”的声明,该声明将我们的视图
运行应用程序,在浏览器中输入“http://localhost:xx/Movies/Create”,浏览器中显
在书写中文网站或中文Web应用程序的时候,可以将代码清单6-1中的代码修改为代码
@tionMessageFor(model=>eDate)
修改完毕后重新运行程序,然后访问“http://localhost:xx/Movies/Create”这个地址,
鼠标右击浏览器中显示的该页面,点击“显示源代码”,显示出来的源代码如代码清单
6-3所示。(为了突出本节内容,只显示与本节中相关部分)。
代码清单6-3追加电影信息画面在浏览器中的HTML代码
data-valmsg-replace="true">
data-val-required="TheReleaseDatefieldisrequired."
id="ReleaseDate"name="ReleaseDate"type="text"value=""/>
data-valmsg-for="ReleaseDate"
data-valmsg-replace="true">
data-valmsg-replace="true"/>
data-val-number="ThefieldPricemustbeanumber."
data-val-required="ThePricefieldisrequired."id="Price"
name="Price"type="text"value=""/>
data-valmsg-replace="true">
从这段代码中可以看出,表单的action属性被设置为“/Movies/Create”,当点击追
加按钮时会把表单中各文本框中的数据提交到服务器端进行保存。
到此为止,我们已经实现了显示追加数据所用表单的所需代码。我们的下一步是编写代
码来进行表单提交到服务器后的处理。我们将要获取用户在数据库中输入的信息并且将它们
为了实现这一处理,我们需要在MoviesController类中追加第二个Create方法。这个
Create方法具有一个[HttpPost]属性,它意味着我们将要用它来处理提交到
“/Movies/Create”这个URL地址的请求。另外,所有提交到“/Movies/Create”这个URL
地址的非POST的请求(即GET请求)将被第一个Create方法进行处理,即简单地返回一个
代码清单6-4中所示代码为MoviesController类中的两个Create方法的全部代码。
代码清单6-4MoviesController类中的两个Create方法的全部代码
publicActionResultCreate()
publicActionResultCreate(MovienewMovie)
returnRedirectToAction("Index");
之前我们介绍了可以自动地将一个URL地址中的查询字符串中的参数(例
如:传递到“/HelloWorld/Welcome?name=Scott&numTimes=5”)作为一个方法的参数传递
到方法中。同样地,除了传递查询字符串中的参数之外,也可以用这种方法来
提交后的表单参数可以作为一个独立的参数传递到一个方法中。例如,
framework可以将我们提交的表单中的控件值作为参数传递到具有HttpPost属性的Create
publicActionResultCreate(stringtitle,DateTimereleaseDate,stringgenre,
提交的表单值也可以被映射到一个复合的,具有属性的对象(譬如我们的Movie类),
并且作为一个单一的参数传递到一个方法中。在代码清单6-4中我们使用的就是这个方法。
请注意Create方法是怎样作为一个参数来接收Movie对象的。
publicActionResultCreate(MovienewMovie)
returnRedirectToAction("Index");
d属性用来检查提交的表单中的数据是否能够被用来创建一个
Movie对象。如果数据是有效的,我们的代码将把提交上来的这个Movie类追加在
MoviesDBContext对象的实例中的Movies集合中。调用我们的MoviesDBContext对象
实例的SaveChanges方法将把这个Movie对象保存在数据库中。保存数据完毕后,代码把
画面重定向到MoviesController类的Index方法中,浏览器中将会打开电影清单显示画面,
如果提交的值是无效的,将会返回到电影信息追加画面中,并且在表单的各输入控件中
显示各自的提交的值。各输入控件的tionMessageFor帮助器将会显示对应的
运行应用程序,在浏览器中输入“http://localhost:xx/Movies/Create”,在表单中
输入一条电影信息,然后点击追加按钮,如图6-4所示。
点击追加按钮进行提交,表单中输入的这条电影信息将会保存到数据库中,保存后浏览
器中将打开电影清单画面,并且将这条追加的电影显示在清单中,如图6-5所示。
你可能已经注意到了在这个电影清单画面中将刚才追加的电影票价显示成了10元,而
不是用户输入的9.99元,这是因为当前该数据表中Decimal类型的默认精度只能识别与处
理整数值,并且自动将小数部分四舍五入。关于如何解决这个问题,我们将在下一节中对模
现在我们已经有了一个Web应用程序的雏形,我们可以在数据库中追加数据,显示数据。
代码清单6-5是现在这个MoviesController类的完整代码。
代码清单6-5MoviesController类的完整代码
publicclassMoviesController:Controller
MovieDBContextdb=newMovieDBContext();
publicActionResultIndex()
eDate>newDateTime(1984,6,1)
publicActionResultCreate()
publicActionResultCreate(MovienewMovie)
returnRedirectToAction("Index");
在下一节,我们将介绍如何为我们的模型添加附加的属性,如何在映射后的数据库中定
3快速入门-第七节在Movie(电影)模型与数据表中添
在本节中我们将要对我们的模型类进行修改,同时介绍如何在3中根据这
7.1在我们的Movie模型中添加一个Rating(电影等级)属性
首先,我们在现存的Movie类中添加一个附加的“Rating”属性。打开文件,
在Movie类中添加一个Rating属性,如下所示。
publicstringRating{get;set;}
现在完整的Movie类的代码如代码清单7-1所示。
publicstringTitle{get;set;}
publicDateTimeReleaseDate{get;set;}
publicstringGenre{get;set;}
publicdecimalPrice{get;set;}
publicstringRating{get;set;}
点击“调试”菜单下的“生成MvcMovie”,重新编译应用程序。
现在我们已经将我们的模型进行了更新,让我们同样地修改我们的Views文件夹下的
Movies文件夹下的文件与这两个视图模板文件,在视图中添
首先打开文件,在内容为“票价”(对应Price属性)的
元素后面追加“ 电影等级 | ”列标题(对应Rating属性)。在显示Price属性内容的td元 素后面追加一个 元素,用来显示Rating属性的内容。进行了这两个修改后的@foreach(variteminModel){ @Link("编辑","Edit",new{id=})| @Link("查看明细","Details",new{id=})| @Link("删除","Delete",new{id=}) |
接下来打开文件,在表单底部追加如下所示的标签。它将显示为一个文 现在我们已经将应用程序修改完毕,在Movie模型中添加了一个Rating属性。 现在让我们重新运行应用程序,打开“http://localhost:xx/Movies”这个URL地址, 这时,浏览器会显示一个应用程序出错画面。如图7-1所示。 导致这个问题发生的原因是因为在我们的应用程序中,更新后的Movie模型类与我们实 际连接的数据库的结构并不统一(Movies数据表中并没有Rating列)。 在默认情况下,当你使用EFcode-first自动创建数据库时(就好像本教程中前面所介 绍过的那样),EFcode-first会自动在数据库中追加数据表来使得数据库的结构与它自动 生成的模型类保持同步。如果不同步,EF将会抛出一个错误。这使得在开发程序时对于错 误的跟踪会变得更加容易,否则你只能在运行时发现这个错误。同步检查特性正是引起以上 1.让EntityFramework自动删除当前数据库,并在新的模型类的基础上重新创建该数 据库。这种方法在使用一个测试数据库时对于开发来说是十分方便的,因为它允许你 快速地同步修改模型与数据库。但缺点是你将丢失现存库中的数据(所以请不要将这 2.修改数据库中的数据表的结构来使之与模型相匹配。这个方法的好处是可以让你保 留表中的数据。你可以手工实现这一操作,或创建一个数据表修改脚本来实现这一操 在本教程中,我们使用第一种方法,在任何模型发生了改变的情况下让EFcode-first 现在我们来修改我们的应用程序,使得我们的应用程序中如果任何模型发生了改变,都 将自动删除与重建当前模型所使用的数据库。(请使用一个开发测试用的数据库来实践这一 操作。用在实际使用的数据库中将造成库中数据的丢失)。 在解决方案资源管理器中,鼠标右击Modes文件夹,选择“添加”,然后点击“类”, 在“添加新项”对话框中,将类名定义为“MovieIntializer”,然后点击添加按钮添 代码清单7-2MovieIntializer类的完整代码 publicclassMovieIntializer: DropCreateDatabaseIfModelChanges protectedoverridevoidSeed(MovieDBContextcontext) ReleaseDate=("2011-1-11"), ReleaseDate=("2011-2-23"), 使用这个MovieInitializer类之后,一旦我们的模型类发生改变后,我们的模型类所映 射的数据库都会被自动重建。代码中使用了Seed方法来指定任何重建数据库的时候想要默 认追加到某张数据表中的数据。这为将一些示例数据添加到数据表中的操作提供了一个有用 的方法,而不需要重建了数据库之后再手工到数据表中添加示例数据。 现在我们已经定义好了我们的MovieInitializer类,接下来我们想在整个工程中使用 这个类,这样每次在运行我们的应用程序的时候会自动检查当前我们的模型类结构是否与数 据库结构不一致,如果不一致的时候就自动重建该数据库,并且追加MovieInitializer类 打开我们的MvcMovies工程的根目录下的文件,如图7-3所示。 文件中定义了当前工程所使用到的Application(应用程序)主类,包含了 一个Application_Start()事件处理器,当第一次运行我们的应用程序时会触发这个事件。 让我们在文件头部追加两个有用的声明。第一个声明引用EntityFramework命名空间, 第二个声明引用我们的MovieInitializer类所存在的命名空间。这两句声明的代码如下所 接下来寻找到Application_Start方法,在该方法的开头追加一个 protectedvoidApplication_Start() tializer(newMovieInitializer()); RegisterGlobalFilters(s); 我们追加的tializer方法将会在实际使用的数据库中的结构与我们 的Movie模型类所映射的数据库结构不匹配时自动重建该数据库,并且自动添加 MovieInitializer类中所指定的默认数据。 重新运行我们的应用程序,并在浏览器中输入“http://localhost:xx/Movies”。当我 们的应用程序启动的时候,会自动觉察出我们的模型类结构与数据库的结构不再匹配,于是 重建该数据库使之相匹配,并且自动追加默认数据,浏览器中显示结果如图7-4所示。 图7-4修改了模型类结构并追加默认数据后浏览器中的显示结果 点击追加按钮后,新追加的包括Rating(电影等级)字段的数据能正常追加并在电影清 图7-6包括Rating(电影等级)字段的数据能被正常追加与显示 在第六节追加数据的时候我们遗留下来一个问题,就是我们在追加数据的时候,票价 (Price)字段中输入的是9.99元,但是电影清单显示画面中该数据的票价字段显示为10 这个问题发生的原因是因为当EFcode-first在创建数据表的时候,如果字段为 Decimal类型,则使用默认的精度(18:0),从而使得9.99元被四舍五入成为10元。现在我 们想要将这个默认的精度修改为(18:2),从而使得数据表中的票价字段能够存储小数点后的 两位数字。可喜的是EFcode-first允许你很容易地重载这个定义模型如何向数据库中存取 数据的映射规则。你可以利用这个重载机制来重载EFcode-first中默认的类型定义以及数 为了改变我们的票价(Price)字段在数据表中的精度,打开Models文件夹下的 文件。追加一句引用onfiguration的语句,代码如下所示。 在我们现在的MovieDBContext类中重载OnModelCreating方法,代码如下所示。 publicclassMovieDBContext:DbContext publicDbSetMovies{get;set;} protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder) ().Property(p=>).HasPrecision(18,2); OnModelCreating方法可以被用来重载与定制规定我们的模型类如何与我们的数据表 进行映射的映射规则。代码中使用了EF的ModelBuilderAPI来定义我们的Movie对象的票 publicstringTitle{get;set;} publicDateTimeReleaseDate{get;set;} publicstringGenre{get;set;} publicdecimalPrice{get;set;} publicstringRating{get;set;} publicclassMovieDBContext:DbContext publicDbSetMovies{get;set;} protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder) 现在让我们重新运行我们的应用程序,并且在浏览器中输入 “http://localhost:xx/Movies”。当应用程序启动的时候,EFcode-first将会再次察觉我们的 模型类中的结构与数据表的结构不再匹配,然后自动重建数据表与新的模型类的结构进行匹 重新创建一个新的电影(Movie)数据,在票价(Price)字段中输入9.99。请注意 数据表中保存后显示在电影清单画面中的该条数据的票价(Price)字段中现在也显示为 在本节中我们介绍了如何快速调整你的模型对象,同时在改变模型类的结构时自动将 数据表中的结构保持同步。我们也介绍了如何预先在自动重建的数据表中追加默认数据,它 使得你可以快速在数据表中追加一些测试数据。在下一节中,我们将介绍如何在我们的模型 类中加入我们自定义的数据有效性校验规则,从而强制实现一些业务规则。 本节介绍如何在我们的Movie(电影)模型中添加一些验证规则,同时确认当用户使用我 们的应用程序创建或编辑电影信息时将使用这些验证规则对用户输入的信息进行检查。 在中,有一条作为核心的原则,就是DRY(“Don’tRepeatYourself, 中文意思为:不要让开发者重复做同样的事情)原则。提倡让开发者“一处定 义、处处可用”。这样可以减少开发者的代码编写量,同时也更加便于代码的维护。 与EFcode-first提供的默认验证规则就是一个实现DRY原则的很好的例 子。你也可以在模型类中显式地追加一个验证规则,然后在整个应用程序中都使用这个验证 现在让我们来看一下怎样在我们的应用程序中追加一些验证规则。 notations命名空间的using语句,代码如下所示。 这个notations命名空间是.NETFramework中的一个 命名空间。它提供了很多内建的验证规则,你可以对任何类或属性显式指定这些验证规则。 现在让我们来修改Movie类,增加一些内建的Required(必须输入),StringLength(输 入字符长度)与Range(输入范围)验证规则,代码如代码清单8-1所示。 [Required(ErrorMessage="必须输入标题")] publicstringTitle{get;set;} [Required(ErrorMessage="必须输入发行日期")] publicDateTimeReleaseDate{get;set;} [Required(ErrorMessage="必须指定种类")] publicstringGenre{get;set;} [Required(ErrorMessage="必须输入票价")] [Range(1,100,ErrorMessage="票价必须在1元到100元之间")] publicdecimalPrice{get;set;} [StringLength(5,ErrorMessage="最多允许输入五个字符")] publicstringRating{get;set;} 上述这些验证属性指定了我们想要强加给模型中各属性的验证规则。Required属性表示 必须要指定一个属性值,在上例中,一个有效的电影信息必须含有标题,发行日期,种类与 票价信息。Range属性表示属性值必须在一段范围之间。StringLength属性表示一个字符 EFcode-first在将一条数据保存到数据库中之前首先使用你对模型类指定的验证规则 来对这条数据进行有效性检查。例如,在以下代码中,当程序调用SaveChanges方法时将抛 出一个异常(也称例外),因为数据并不满足Movie属性的必须输入条件,同时票价属性的 MovieDBContextdb=newMovieDBContext(); 通过EntityFramework来自动实现验证规则检查可以让我们的应用程序变得更强健。 它也确保我们不会由于忘了实施数据验证而使得一些无效数据保存到数据库中。 [Required(ErrorMessage="必须输入标题")] publicstringTitle{get;set;} [Required(ErrorMessage="必须输入发行日期")] publicDateTimeReleaseDate{get;set;} [Required(ErrorMessage="必须指定种类")] publicstringGenre{get;set;} [Required(ErrorMessage="必须输入票价")] [Range(1,100,ErrorMessage="票价必须在1元到100元之间")] publicdecimalPrice{get;set;} [StringLength(5,ErrorMessage="最多允许输入五个字符")] publicstringRating{get;set;} publicclassMovieDBContext:DbContext publicDbSetMovies{get;set;} protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder) 现在让我们运行我们的应用程序,并在地址栏中输入“http://localhost:xx/Movies”。 在电影清单画面中点击追加按钮打开追加电影画面。在该画面中的表单中填入一些无效 请注意表单自动使用了一个背景颜色来高亮显示包含了无效数据的文本框,并且在每个 文本框的旁边显示验证错误信息。使用的错误信息文字正是我们在前面代码中所指定的验证 错误的错误信息文字。这个验证错误既可以由客户端引发(使用JavaScript脚本),也可以 由服务器端引发(当用户禁止使用JavaScript脚本时)。 这种处理方法是非常不错的,因为我们不再需要为了显示错误信息文字而在 MoviesController类或视图文件中书写不必要的代码。我们之前创建的控 制器与视图将自动实施验证规则与显示验证错误信息文字。 8.4在Create视图(追加电影视图)与Create方法内部是如何实现验证的 也许有的读者会问,既然我们没有追加任何显示错误信息提示的代码,那么我们的控制 器或视图内部是如何生成这个显示错误信息提示的画面的。首先我们将MovieController 类中的代码显示如下,在我们在Movie类中追加了验证规则后,我们并没有修改这个类中的 publicActionResultCreate() publicActionResultCreate(MovienewMovie) returnRedirectToAction("Index"); 第一个方法返回追加电影视图。在第二个方法中对追加电影视图中的表单的提交进行处 理。该方法中的d属性用来判断是否提交的电影数据中包含有任何没有 通过数据验证的无效数据。如果存在无效数据,Create方法重新返回追加电影视图。如果 我们之前创建的使用支架模板的视图模板中的代码显示如下,在首次打 开追加电影视图与数据没有通过验证时,Create方法中返回的视图都是使用的这个视图模 @tionMessageFor(model=>eDate) 请注意在这段代码中使用了许多For帮助器来为Movie类的每个属性输出 tionMessageFor帮助器。这两个帮助器将与从控制器传入的模型类的对象实例 (在本示例中为Movie对象的一个实例)结合起来,自动寻找指定给模型的各个验证属性, 这种验证体制的好处是在于控制器和Create视图(追加电影视图)事先都即不知道实 际指定的验证规则,也不知道将会显示什么验证错误信息。验证规则和错误信息只在Movie 如果我们之后想要改变验证规则,我们也只要在一处地方进行改变就可以了。我们不用 担心整个应用程序中存在验证规则不统一的问题,所有的验证规则都可以集中在一处地方进 行指定,然后在整个应用程序中使用这些验证规则。这将使我们的代码更加清晰明确,更加 具有可读性、可维护性与可移植性。这将意味着我们的代码是真正符合DRY原则(一处指定, 在下一节中,作为结尾部分,我们将介绍如何修改与删除数据,同时介绍如何显示一条 首先,让我们来看一下如何实现一条数据的明细信息视图。为了更好地体会这一功能, 首先我们在前文所述的电影清单视图(Views文件夹下面的Movies文件夹下面的 文件)中删除电影清单中的种类、票价、电影等级字段,使其代码如代码清 @foreach(variteminModel){ @Link("编辑","Edit",new{id=})| @Link("查看明细","Details",new{id=})| @Link("删除","Delete",new{id=}) |
重新运行该应用程序,在浏览器中输入地址“http://localhost:xx/Movies”,浏览器 现在电影清单画面中就只显示每条数据的电影名称与发行日期了,如果像查看该条数据 的详细信息,需要点击每条数据的“查看明细”链接,将画面导航到明细数据画面,在该画 面中查看这条数据的明细信息。当一条数据的细节信息比较多,而我们只想在该数据的列举 清单中显示该数据的几个摘要信息,通过点击链接或按钮的操作来查看数据的细节信息时这 接下来让我们来追加这个明细数据视图。首先打开Movie控制器,追加一个返回明细数 publicActionResultDetails(intid) returnRedirectToAction("Index"); returnView("Details",movie); code-first通过使用Find方法来让一条数据的寻找变得非常容易。这个方法的一个非 常重要的安全特性就是我们可以确保我们寻找的是一条可以被映射为Movie对象的数据。为 什么这种做法可以确保安全性呢?举个例子来说,一个黑客可以将 “http://localhost:xxxx/Movies/Details/1”地址修改为 “http://localhost:xxxx/Movies/Details/12345”,如果数据库中没有这条id为12345 的数据,根据以上代码所示,作为寻找结果的Movie对象将被设定为null,浏览器将重新返 在Details方法中点击鼠标右键,选择“添加视图”,依然勾选“创建强类型视图”, 模型类选择Movie,在支架模板中选择“Details”(明细数据),如图9-2所示。 如果要创建中文网站或应用程序,则将默认生成的文件中有关英文文 字修改为中文,修改完毕后该文件中的代码如代码清单9-2中所示。 @Link("编辑","Edit",new{id=})| 重新运行应用程序,在电影清单画面中点击某个电影的“查看明细”链接,浏览器显示 接下来,让我们来看一下如何实现一个用来修改数据的视图。 首先打开Movie控制器,追加一个返回数据修改视图的Edit方法与一个对该视图中的 publicActionResultEdit(intid) returnRedirectToAction("Index"); publicActionResultEdit(Moviemodel) returnRedirectToAction("Details",new{id=}); elError("","修改失败,请查看详细错误信息。"); 这两个Edit方法中,第一个方法将在用户点击外部画面的“编辑”链接时被调用,用来在浏 览器中显示数据修改视图,并且在该视图中显示用户选择编辑的数据。第二个Edit方法前 面带有一个[HttpPost]标记,负责将修改数据视图中提交的表单数据绑定到一个用模型创建 出来的Movie对象实例之上(当用户在表单中完成数据修改并点击保存按钮的时候进行提 交),UpdateModel(movie)方法将调用模型拷贝器,该模型拷贝器将修改后的数据(使用 model参数,该参数指向一个各属性值为编辑后数据的Movie对象实例)拷贝到数据库中(即 为数据的保存过程)。在保存数据的过程中如果发生任何错误而导致保存失败的话,则画面 接下来让我们来追加该数据修改视图、在Edit方法中点击鼠标右键,选择“添加视 图”,依然勾选“创建强类型视图”,模型类选择Movie,在支架模板中选择“Edit”(修 如果要创建中文网站或应用程序,则将默认生成的文件中有关英文文字修 改为中文,修改完毕后该文件中的代码如代码清单9-3中所示。 @tionMessageFor(model=>eDate) 重新运行应用程序,在电影清单画面中点击某个电影的“编辑”链接,浏览器显示画面 在该视图中修改选中的数据,然后点击保存按钮,浏览器将修改后的数据显示在明细数 接下来,让我们来看一下如何实现一个用来删除数据的视图。 首先打开Movie控制器,追加一个返回数据修改视图的Edit方法与一个对该视图中的 publicActionResultDelete(intid) returnRedirectToAction("Index"); publicRedirectToRouteResultDelete(intid,FormCollectioncollection) returnRedirectToAction("Index"); 这里请注意第一个没有[HttpPost]标记的Delete方法并不会将数据删除,因为如果通 过GET请求而删除(或者追加、修改)删除数据的话都会打开一个安全漏洞。 接下来让我们来追加该数据删除视图、在Delete方法中点击鼠标右键,选择“添加视 图”,依然勾选“创建强类型视图”,模型类选择Movie,在支架模板中选择“Delete”(删 如果要创建中文网站或应用程序,则将默认生成的文件中有关英文文字 修改为中文,修改完毕后该文件中的代码如代码清单9-3中所示。 在电影清单画面中点击一条数据的删除按钮,浏览器打开数据删除视图,如图9-8所示。 点击删除按钮,该条数据将被删除,浏览器中返回显示电影清单画面。 最后,我们来回顾一下本教程中所讲述的内容。本教程中首先讲述了如何创建控制器、 视图、如何将控制器中的数据传递给视图。然后我们设计并创建了一个数据模型。code-first 根据模型在指定的数据库服务器中创建了一个数据库。我们可以从这个数据库中获取数据并 显示在一个HTML表格中。然后我们追加了一个添加数据所用的视图。接下来我们通过添加 一个数据列(也称字段)的方式来改变数据表,同时修改了数据清单画面与数据追加视图来 显示这个新追加的数据列。然后我们通过使用DataAnnotations命名空间,为数据模型标注 属性的方式来追加了一些数据验证规则。这些数据验证即可以在客户端实现,也可以在服务 器端实现。最后,我们添加代码与视图模板来创建了数据的修改视图,删除视图与明细数据 接下来,我鼓励你继续看笔者即将发表的“MVC音乐商店”这篇连载教程,来进一步了
本文发布于:2023-04-04 21:21:35,感谢您对本站的认可!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签: net mvc
智慧餐饮开发|APP开发|盲盒商城源码
| |
发布评论