Orchard模块开发全接触8:改造后台
后台默认提供了 Content 的管理,但是,所有的内容类型揉杂在一起,而我们需要更深度的定制一些功能,比如,我们只想管理订单,又比如,我们需要对注册用户进行管理。本篇的内容包括:
1:自定义 admin menu;
2:使用 content query;
3:使用 paging utilities;
4:创建 list 与 edit;
一:扩展 Admin Menu
首先我们必须要实现 INavigationProvider 接口,我们创建 AdminMenu.cs:
using Orchard.Environment;
using Orchard.Localization;
using Orchard.UI.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Routing;namespace TMinji.Shop
{
public class AdminMenu : INavigationProvider
{
private readonly Work<RequestContext> _requestContextAccessor;public string MenuName
{
get { return "admin"; }
}public AdminMenu(Work<RequestContext> requestContextAccessor)
{
_requestContextAccessor = requestContextAccessor;
T = NullLocalizer.Instance;
}private Localizer T { get; set; }
public void GetNavigation(NavigationBuilder builder)
{var requestContext = _requestContextAccessor.Value;
var idValue = (string)requestContext.RouteData.Values["id"];
var id = 0;if (!string.IsNullOrEmpty(idValue))
{
int.TryParse(idValue, out id);
}builder
// Image set
.AddImageSet("webshop")// "Webshop"
.Add(item => item.Caption(T("Webshop"))
.Position("2")
.LinkToFirstChild(false)// "Customers"
.Add(subItem => subItem
.Caption(T("Customers"))
.Position("2.1")
.Action(new RouteValueDictionary
{
{"area", "TMinji.Shop"},
{"controller", "CustomerAdmin"},
{"action", "Index"}
}).Add(T("Details"), i => i.Action("Edit", "CustomerAdmin", new { id }).LocalNav())
.Add(T("Addresses"), i => i.Action("ListAddresses", "CustomerAdmin", new { id }).LocalNav())
.Add(T("Orders"), i => i.Action("ListOrders", "CustomerAdmin", new { id }).LocalNav())
)// "Orders"
.Add(subItem => subItem
.Caption(T("Orders"))
.Position("2.2")
.Action(new RouteValueDictionary
{
{"area", "TMinji.Shop"},
{"controller", "OrderAdmin"},
{"action", "Index"}
})
)
);
}
}}
代码中,指示我们需要加两个资源,一个是 menu.webshop.png,一个是 menu.webshop-admin.css,这是需要我们手动添加的,
Styles/menu.webshop-admin.css:
.navicon-webshop {
background-image:url('../images/menu.webshop.png') !important;
}
.navicon-webshop:hover {
background-position:0 -30px !important;
}
这个时候,我们的后台,就会变成这样了:
二:后台控制器
现在,添加后台的控制器。
Controllers/CustomerAdminController.cs:
using Orchard.ContentManagement;
using Orchard.DisplayManagement;
using Orchard.Localization;
using Orchard.Settings;
using Orchard.UI.Admin;
using Orchard.UI.Navigation;
using Orchard.UI.Notify;
using Orchard.Users.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Mvc;
using TMinji.Shop.Services;
using TMinji.Shop.ViewModels;
using TMinji.Shop.Helpers;namespace TMinji.Shop.Controllers
{
[Admin]
public class CustomerAdminController : Controller, IUpdateModel
{
private dynamic Shape { get; set; }
private Localizer T { get; set; }
private readonly ICustomerService _customerService;
private readonly ISiteService _siteService;
private readonly IContentManager _contentManager;
private readonly INotifier _notifier;
private readonly IOrderService _orderService;public CustomerAdminController
(
ICustomerService customerService,
IShapeFactory shapeFactory,
ISiteService siteService,
IContentManager contentManager,
INotifier notifier,
IOrderService orderService
)
{
Shape = shapeFactory;
T = NullLocalizer.Instance;
_customerService = customerService;
_siteService = siteService;
_contentManager = contentManager;
_notifier = notifier;
_orderService = orderService;
}public ActionResult Index(PagerParameters pagerParameters, CustomersSearchVM search)
{// Create a basic query that selects all customer content items, joined with the UserPartRecord table
var customerQuery = _customerService.GetCustomers().Join<UserPartRecord>().List();// If the user specified a search expression, update the query with a filter
if (!string.IsNullOrWhiteSpace(search.Expression))
{var expression = search.Expression.Trim();
customerQuery = from customer in customerQuery
where
customer.FirstName.Contains(expression, StringComparison.InvariantCultureIgnoreCase) ||
customer.LastName.Contains(expression, StringComparison.InvariantCultureIgnoreCase) ||
customer.As<UserPart>().Email.Contains(expression)
select customer;
}// Project the query into a list of customer shapes
var customersProjection = from customer in customerQuery
select Shape.Customer
(
Id: customer.Id,
FirstName: customer.FirstName,
LastName: customer.LastName,
Email: customer.As<UserPart>().Email,
CreatedAt: customer.CreatedAt
);// The pager is used to apply paging on the query and to create a PagerShape
var pager = new Pager(_siteService.GetSiteSettings(), pagerParameters.Page, pagerParameters.PageSize);// Apply paging
var customers = customersProjection.Skip(pager.GetStartIndex()).Take(pager.PageSize);// Construct a Pager shape
var pagerShape = Shape.Pager(pager).TotalItemCount(customerQuery.Count());// Create the viewmodel
var model = new CustomersIndexVM(customers, search, pagerShape);return View(model);
}public ActionResult Edit(int id)
{
var customer = _customerService.GetCustomer(id);
var model = _contentManager.BuildEditor(customer);// Casting to avoid invalid (under medium trust) reflection over the protected View method and force a static invocation.
return View((object)model);
}[HttpPost, ActionName("Edit")]
public ActionResult EditPOST(int id)
{
var customer = _customerService.GetCustomer(id);
var model = _contentManager.UpdateEditor(customer, this);if (!ModelState.IsValid)
return View(model);_notifier.Add(NotifyType.Information, T("Your customer has been saved"));
return RedirectToAction("Edit", new { id });
}public ActionResult ListAddresses(int id)
{
var addresses = _customerService.GetAddresses(id).ToArray();
return View(addresses);
}public ActionResult ListOrders(int id)
{
var orders = _orderService.GetOrders(id).ToArray();
return View(orders);
}bool IUpdateModel.TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties)
{
return TryUpdateModel(model, prefix, includeProperties, excludeProperties);
}void IUpdateModel.AddModelError(string key, LocalizedString errorMessage)
{
ModelState.AddModelError(key, errorMessage.Text);
}
}
}
Views/CustomerAdmin/Index.cshtml:
@model TMinji.Shop.ViewModels.CustomersIndexVM
@{
Script.Require("ShapesBase");
Layout.Title = T("Customers").ToString();
}@using (Html.BeginForm("Index", "CustomerAdmin", FormMethod.Get))
{
<fieldset class="bulk-actions">
<label for="search">@T("Search:")</label>
@Html.TextBoxFor(m => m.Search.Expression)
<button type="submit">@T("Search")</button>
<a href="@Url.Action("Index")">@T("Clear")</a>
</fieldset>
}
<fieldset>
<table class="items" summary="@T("This is a table of the customers in your application")">
<colgroup>
<col id="Col1" />
<col id="Col2" />
<col id="Col3" />
<col id="Col4" />
<col id="Col5" />
<col id="Col6" />
</colgroup>
<thead>
<tr>
<th scope="col"> ↓</th>
<th scope="col">@T("FirstName")</th>
<th scope="col">@T("LastName")</th>
<th scope="col">@T("Email")</th>
<th scope="col">@T("Created")</th>
<th scope="col">@T("Actions")</th>
</tr>
</thead>
@foreach (var customer in Model.Customers)
{
<tr>
<td>@customer.Id</td>
<td>@customer.FirstName</td>
<td>@customer.LastName</td>
<td>@customer.Email</td>
<td>@customer.CreatedAt</td>
<td>
<div>
<a href="@Url.Action("Edit", new {customer.Id})" title="@T("Edit")">@T("Edit")</a>@T(" | ")
<a href="@Url.Action("List", "AddressAdmin", new {customerId = customer.Id})" title="@T("Addresses")">@T("Addresses")</a>@T(" | ")
<a href="@Url.Action("Delete", new {customer.Id, returnUrl = Request.Url.PathAndQuery})">@T("Delete")</a>
</div>
</td>
</tr>
}
</table>
@Display(Model.Pager)
</fieldset>
Services/ICustomerService.cs:
public interface ICustomerService : IDependency
{
CustomerPart CreateCustomer(string email, string password);
AddressPart GetAddress(int customerId, string addressType);
AddressPart CreateAddress(int customerId, string addressType);IContentQuery<CustomerPart> GetCustomers();
CustomerPart GetCustomer(int id);
IEnumerable<AddressPart> GetAddresses(int customerId);
AddressPart GetAddress(int id);
}
Services/CustomerService.cs:
using Orchard;
using Orchard.ContentManagement;
using Orchard.Security;
using Orchard.Services;
using Orchard.Users.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TMinji.Shop.Models;namespace TMinji.Shop.Services
{
public class CustomerService : ICustomerService
{
private readonly IOrchardServices _orchardServices;
private readonly IMembershipService _membershipService;
private readonly IClock _clock;public CustomerService(IOrchardServices orchardServices, IMembershipService membershipService, IClock clock)
{
_orchardServices = orchardServices;
_membershipService = membershipService;
_clock = clock;
}public CustomerPart CreateCustomer(string email, string password)
{
// New up a new content item of type "Customer"
var customer = _orchardServices.ContentManager.New("Customer");// Cast the customer to a UserPart
var userPart = customer.As<UserPart>();// Cast the customer to a CustomerPart
var customerPart = customer.As<CustomerPart>();// Set some properties of the customer content item (via UserPart and CustomerPart)
userPart.UserName = email;
userPart.Email = email;
userPart.NormalizedUserName = email.ToLowerInvariant();
userPart.Record.HashAlgorithm = "SHA1";
userPart.Record.RegistrationStatus = UserStatus.Approved;
userPart.Record.EmailStatus = UserStatus.Approved;// Use IClock to get the current date instead of using DateTime.Now (see http://skywalkersoftwaredevelopment.net/orchard-development/api/iclock)
customerPart.CreatedAt = _clock.UtcNow;// Use Ochard's MembershipService to set the password of our new user
_membershipService.SetPassword(userPart, password);// Store the new user into the database
_orchardServices.ContentManager.Create(customer);return customerPart;
}public AddressPart GetAddress(int customerId, string addressType)
{
return _orchardServices.ContentManager.Query<AddressPart, AddressPartRecord>().Where(x => x.CustomerId == customerId && x.Type == addressType).List().FirstOrDefault();
}public AddressPart CreateAddress(int customerId, string addressType)
{
return _orchardServices.ContentManager.Create<AddressPart>("Address", x =>
{
x.Type = addressType;
x.CustomerId = customerId;
});
}public IContentQuery<CustomerPart> GetCustomers()
{
return _orchardServices.ContentManager.Query<CustomerPart, CustomerPartRecord>();
}public CustomerPart GetCustomer(int id)
{
return _orchardServices.ContentManager.Get<CustomerPart>(id);
}public IEnumerable<AddressPart> GetAddresses(int customerId)
{
return _orchardServices.ContentManager.Query<AddressPart, AddressPartRecord>().Where(x => x.CustomerId == customerId).List();
}public AddressPart GetAddress(int id)
{
return _orchardServices.ContentManager.Get<AddressPart>(id);
}
}}
Helpers/StringExtensions.cs:
public static class StringExtensions
{
public static string TrimSafe(this string s)
{
return s == null ? string.Empty : s.Trim();
}public static bool Contains(this string source, string value, StringComparison comparison)
{
return source.IndexOf(value, comparison) >= 0;
}
}
ViewModels/CustomersIndexVM.cs:
public class CustomersIndexVM
{
public IList<dynamic> Customers { get; set; }
public dynamic Pager { get; set; }
public CustomersSearchVM Search { get; set; }public CustomersIndexVM()
{
Search = new CustomersSearchVM();
}public CustomersIndexVM(IEnumerable<dynamic> customers, CustomersSearchVM search, dynamic pager)
{
Customers = customers.ToArray();
Search = search;
Pager = pager;
}
}
ViewModels/CustomersSearchVM.cs:
public class CustomersSearchVM
{
public string Expression { get; set; }
}
Views/CustomerAdmin/Edit.cshtml:
@{
Layout.Title = "Edit Customer";
}@using (Html.BeginFormAntiForgeryPost())
{
@Display(Model)
}
Drivers/CustomerDriver.cs:
protected override DriverResult Editor(CustomerPart part, IUpdateModel updater, dynamic shapeHelper)
{
updater.TryUpdateModel(part, Prefix, null, null);var user = part.User;
updater.TryUpdateModel(user, Prefix, new[] { "Email" }, null);return Editor(part, shapeHelper);
}
Views/EditorTemplates/Parts/Customer.cshtml:
@using System.Web.Mvc.Html
@model TMinji.Shop.Models.CustomerPart
@{
var user = Model.User;
}
<fieldset>
<div class="editor-label">@Html.LabelFor(x => x.Title)</div>
<div class="editor-field">@Html.TextBoxFor(x => x.Title, new { @class = "text" })</div><div class="editor-label">@Html.LabelFor(x => x.FirstName)</div>
<div class="editor-field">
@Html.TextBoxFor(x => x.FirstName, new { @class = "large text" })
@Html.ValidationMessageFor(x => x.FirstName)
</div><div class="editor-label">@Html.LabelFor(x => x.LastName)</div>
<div class="editor-field">
@Html.TextBoxFor(x => x.LastName, new { @class = "large text" })
@Html.ValidationMessageFor(x => x.LastName)
</div><div class="editor-label">@Html.LabelFor(x => user.Email)</div>
<div class="editor-field">
@Html.TextBoxFor(x => user.Email, new { @class = "large text" })
@Html.ValidationMessageFor(x => user.Email)
</div>
</fieldset>
最终效果:
Orchard模块开发全接触8:改造后台的更多相关文章
- Orchard模块开发全接触1:起步
在<http://www.cnblogs.com/luminji/p/3831281.html>中简单介绍了 Orchard 的模块开发,接下来,我们需要做个更复杂的例子,Orchard ...
- Orchard模块开发全接触7:订单与支付之Event Bus
在这部分,我们要完成的工作有: 1:将购物车内的商品变成真正的订单: 2:理解 父子及一对多关系: 3:写一个针对 Event Bus 的扩展点: 4:实现一个针对该扩展点的模拟的 支付服务: 一:创 ...
- Orchard模块开发全接触4:深度改造前台
这里,我们需要做一些事情,这些事情意味着深度改造前台: 1:为商品增加 添加到购物车 按钮,点击后功能实现: 2:商品排序: 3:购物车预览,以及添加 结算 按钮: 4:一个显式 购物车中有*个 商品 ...
- Orchard模块开发全接触5:深度改造前台第二部分
在这一部分,我们继续完善我们的购物车,我们要做以下一些事情: 1:完成 shoppingcart.cshtml: 2:让用户可以更新数量及从购物车删除商品: 3:创建一个 widget,在上面可以看到 ...
- Orchard模块开发全接触3:分类的实现及内容呈现(Display)
一:分类用现有技术怎么实现? 实际就是创建 Query 和 Projection,如果不知道怎么做,参考:Orchard之在前台显式一个属于自己的列表(在这篇里,还进行了稍稍拓展),当然,基础的知道, ...
- Orchard模块开发全接触2:新建 ProductPart
一:创建 Part 1:项目引用 Orchard.Framework: 2:创建 Models 文件夹: 3:在 Models 文件夹下创建类 ProductPartRecord,如下: public ...
- Orchard模块开发全接触6:自定义用户注册
我们都知道 Orchard 的用户注册相当简单,现在,我们需要一个自定义的用户注册,现在,开始吧. 一:定义实体 Models/CustomerPartRecord.cs: public class ...
- Orchard 模块开发学习笔记 (1)
创建模块 首先,打开Bin目录下的Orchard.exe 等到出现orchard>后, 看看命令列表中是否存在 codegen module 如果不存在,则需要先执行:feature enabl ...
- Odoo9.0模块开发全流程
构建Odoo模块 模块组成 业务对象 业务对象声明为Python类, 由Odoo自己主动加载. 数据文件 XML或CSV文件格式, 在当中声明了元数据(视图或工作流).配置数据(模块參数).演示数据等 ...
随机推荐
- PhoneGap学习地址 / PhoneGap API介绍:Events
http://blog.csdn.net/phonegapcn 事件类型: backbutton deviceready menubutton pause resume searchbutton on ...
- gym 101986
A - Secret of Chocolate Poles 队友写的. 好像水水的. //#pragma GCC optimize(2) //#pragma GCC optimize(3) //#pr ...
- Java之路(一) 一切皆对象
Java语言假设我们只进行面向对象的程序设计,即在开始用Java进行设计前,我们需要将思想切换到面向对象的世界中. 1.用引用操纵对象 每种编程语言都有自己操纵内存中元素的方式.是直接操纵元素还是用某 ...
- CSS3选择器01—CSS2.1部分选择器
这篇文章主要用于存储CSS以及CSS3的选择器部分知识,以便日后查阅及记忆. 该内容分为两部分,第一部分为css选择器的一些基本知识.第二部分为CSS3新增加的选择器. 在开始之前,先简单介绍一下选择 ...
- 统计Mongo数组中相同对象的属性之和
统计Mongo数组中相同对象的属性之和 需求 需要统计app端用户的行为,按天分表,存入mongo.每次用户进行操作的时候,将数据存入app本地,下次用户启动的时候,提交存入mongo,删除app本地 ...
- 干货: 可视化项目实战经验分享,轻松玩转 Bokeh (建议收藏)
作者 | Will Koehrsen 翻译 | Lemon 译文出品 | Python数据之道 (ID:PyDataRoad) 本文通过一个项目案例,详细的介绍了如何从 Bokeh 基础到构建 Bok ...
- Java 多线程 - synchronize 关键字
目录 Java 多线程 - synchronize 关键字 Java 多线程 - synchronize 关键字 学习自 http://cmsblogs.com/?p=2071 https://www ...
- windows下elasticsearch启动
windows下启动elasticsearch,依赖于配置好JAVA_HOME D:\Program Files\Java\jdk1.7.0_71 命令行启动elasticsearch.bat即可实现 ...
- 【WIN10】移植opencc到WIN10-UWP,實現自己的繁簡轉換工具
花了週末兩天時間,將opencc移植成WIN10-UWP可用的庫,並完成自己的繁簡轉換工具. 我的繁簡轉換工具下載地址為:https://www.microsoft.com/store/apps/9n ...
- ==与equals与hashCode的区别联系。
前言:对于引用类型的变量,它涉及到两块内存,一块是堆中的内存,用于存放new出来的对象(就是对象的具体内容):另一块是栈内存,用来存放变量在堆内存中的地址. 1,“==” 判断两个变量是否是同一个变量 ...