后台默认提供了 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>
最终效果: