这是一个Orchard-Modules的入门教程。在这个教程里,我们将开发两个功能页面分别用于数据录入与数据展示。
完成上述简单功能开发,我们一共需要6个步骤。分别为:
上面6个步骤可能不太好理解。在这里,我们把他们转换从MVC中的概念让我们更好理解。
Module | 项目模块 |
Model | 实体层 |
Controller、View | Controller、View |
Route | Route |
Services | 服务层 |
Admin Menu | 后台管理 |
有点概念后,我们就开始吧!
第一步我们需要利用Orchard的代码生成工具Code Generation 来生成Module项目文件。如果对这个命令还不是很熟悉,我们可以在这里先进行了解。
创建:
codegen module XiaoGuang.HelloWorld
使用上述就完成了一个HelloWorld Module的创建。
*关于Module的名称,建议使用系统模块.功能名来命名。
修改清单文件Module.txt:
这个文件用于描述Module信息与依赖关系。因为本次只是一个简单示例,不深入讲解。
Name: XiaoGuang.HelloWorld
AntiForgery: enabled
Author: 互联网新手
Website: http://curd.cnblogs.com
Version: 1.0
OrchardVersion: 1.0
Description: XiaoGuang.HelloWorld演示模块。
Features:
XiaoGuang.HelloWorld:
Description: XiaoGuang.HelloWorld演示模块。
启用:
管理后台->Modules->找到[XiaoGuang.HelloWorld]->点击Enable 或 命令行:feature enable XiaoGuang.HelloWorld
不少朋友开发完Module后,输入了注册路由的地址。发现始终无法看到效果。实际上是Module默认为非启用状态导致。
Models目录下新增TestRecord.cs 文件。新增,代码如下:
namespace XiaoGuang.HelloWorld.Models
{
public class TestRecord
{
public virtual int Id { get ; set ; }
public virtual string Content { get ; set ; }
}
}
注:如果后续运行提示没有持久化的问题。是因为实体类必须放在命名空间为Models或Records结尾下。
这属于Orchard默认规范,详见: https://orchard.codeplex.com/discussions/267968
codegen datamigration XiaoGuang.HelloWorld
上述语句创建一个实体迁移。并生成如下代码,完成Record对应表创建过程。
public class Migrations : DataMigrationImpl {
public int Create() {
// Creating table TestRecord
SchemaBuilder.CreateTable( "TestRecord" , table => table
.Column( "Id" , DbType .Int32, column => column.PrimaryKey().Identity())
.Column( "Content" , DbType .String)
);
return 1;
}
}
新建文件:
ITestService:
public interface ITestService :Orchard. IDependency {
TestRecord GetTest();
TestRecord UpdateTest( string content);
}
TestService:
public class TestService : ITestService {
private readonly IRepository < TestRecord > _testRepository;
public TestService( IRepository < TestRecord > testRepository) {
_testRepository = testRepository;
}
public TestRecord GetTest() {
return _testRepository.Table.FirstOrDefault();
}
public TestRecord UpdateTest( string content) {
var result = GetTest();
if (result == null ) {
result = new TestRecord {Content = content};
_testRepository.Create(result);
}
else {
result.Content = content;
_testRepository.Update(result);
}
return result;
}
}
上面的代码的重点是IRepository ,由Orchard封装。实现了实体的增、删、改、查功能。
Controller:
public class AdminController : Controller {
public IOrchardServices Services { get ; set ; }
public ITestService TestService { get ; set ; }
public AdminController( IOrchardServices services, ITestService testService) {
Services = services;
T = NullLocalizer .Instance;
TestService = testService;
}
public Localizer T { get ; set ; }
public ActionResult Update( string content) {
TestService.UpdateTest(content);
return RedirectToAction( "Index" , "Home" );
}
}
这里充分体现了依赖注入的好处。只需要构造函数传递接口就可以了。框架自动实例。也易于单测。
View:
@model XiaoGuang.HelloWorld.Models. TestRecord
@{
Layout .Title = T ( "TestUpdate" ).ToString();
}
@ using ( Html .BeginFormAntiForgeryPost( Url .Action( "Update" , "Admin" ))) {
@ Html .AntiForgeryToken()
< div class ="form-horizontal">
@ Html .ValidationSummary( true , "" , new {@class = "text-danger" })
< div class ="form-group">
@ Html .LabelFor(model => model. Content , htmlAttributes: new {@class = "control-label col-md-2" })
< div class ="col-md-10">
@ Html .EditorFor(model => model. Content , new {htmlAttributes = new {@class = "form-control" }})
@ Html .ValidationMessageFor(model => model. Content , "" , new {@class = "text-danger" })
</ div >
</ div >
< div class ="form-group">
< div class ="col-md-offset-2 col-md-10">
< input type ="submit" value ="Create" class ="btn btn-default"/>
</ div >
</ div >
</ div >
}
新增Routes.cs文件。返回指定的路由。
public class Routes : IRouteProvider
{
public IEnumerable < RouteDescriptor > GetRoutes() {
yield return new RouteDescriptor {
Route = new Route ( "MyHelloWorld" , new RouteValueDictionary {
{ "area" , "XiaoGuang.HelloWorld" },
{ "action" , "Index" },
{ "controller" , "Home" }
}, new RouteValueDictionary (), new RouteValueDictionary {
{ "area" , "XiaoGuang.HelloWorld" }
}, new MvcRouteHandler ())
};
yield return new RouteDescriptor
{
Route = new Route ( "admin/XiaoGuang.HelloWorld/Update" , new RouteValueDictionary {
{ "area" , "XiaoGuang.HelloWorld" },
{ "action" , "Update" },
{ "controller" , "Admin" }
}, new RouteValueDictionary (), new RouteValueDictionary {
{ "area" , "XiaoGuang.HelloWorld" }
}, new MvcRouteHandler ())
};
}
public void GetRoutes( ICollection < RouteDescriptor > routes) {
foreach ( var route in GetRoutes()) {
routes.Add(route);
}
}
}
该功能用于产生一个后台导航菜单,定位到管理页面。相信代码直接读就可以理解。需要继承于 INavigationProvider。
public class AdminMenu : INavigationProvider {
public string MenuName => "admin" ;
public Localizer T { get ; set ; }
public void GetNavigation( NavigationBuilder builder) {
builder.AddImageSet( "helloworld" ).Add(T( "HelloWorld" ), "5" , item => {
item.Action( "Index" , "Admin" , new {area = "XiaoGuang.HelloWorld" });
});
}
}
需要特殊说明一下。 public string MenuName => "admin" ;
这段代码是固定值,注意指的大小写。具体原因搜索下INavigationProvider相关引用就知道了。我可是不只一次入坑了。