Orchard模块开发全接触8:改造后台

时间:2023-03-08 16:45:12

后台默认提供了 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;
}

这个时候,我们的后台,就会变成这样了:

Orchard模块开发全接触8:改造后台

二:后台控制器

现在,添加后台的控制器。

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">&nbsp;&darr;</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模块开发全接触8:改造后台

Orchard模块开发全接触8:改造后台