mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
Merge
--HG-- branch : dev
This commit is contained in:
@@ -21,6 +21,10 @@ namespace Orchard.Core.Feeds.Services {
|
|||||||
dynamic Shape { get; set; }
|
dynamic Shape { get; set; }
|
||||||
|
|
||||||
public void OnResultExecuting(ResultExecutingContext filterContext) {
|
public void OnResultExecuting(ResultExecutingContext filterContext) {
|
||||||
|
// should only run on a full view rendering result
|
||||||
|
if (!(filterContext.Result is ViewResult))
|
||||||
|
return;
|
||||||
|
|
||||||
var layout = _workContextAccessor.GetContext(filterContext).Layout;
|
var layout = _workContextAccessor.GetContext(filterContext).Layout;
|
||||||
var feed = Shape.Feed()
|
var feed = Shape.Feed()
|
||||||
.FeedManager(_feedManager);
|
.FeedManager(_feedManager);
|
||||||
|
@@ -72,8 +72,15 @@ namespace Orchard.Core.Routable.Handlers {
|
|||||||
public class RoutePartHandlerBase : ContentHandlerBase {
|
public class RoutePartHandlerBase : ContentHandlerBase {
|
||||||
public override void GetContentItemMetadata(GetContentItemMetadataContext context) {
|
public override void GetContentItemMetadata(GetContentItemMetadataContext context) {
|
||||||
var routable = context.ContentItem.As<RoutePart>();
|
var routable = context.ContentItem.As<RoutePart>();
|
||||||
if (routable != null) {
|
|
||||||
context.Metadata.DisplayText = routable.Title;
|
if (routable == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
context.Metadata.DisplayText = routable.Title;
|
||||||
|
|
||||||
|
// set the display route values if it hasn't been set or only has been set by the Contents module.
|
||||||
|
// allows other modules to set their own display. probably not common enough to warrant some priority implemntation
|
||||||
|
if (context.Metadata.DisplayRouteValues == null || context.Metadata.DisplayRouteValues["Area"] as string == "Contents") {
|
||||||
context.Metadata.DisplayRouteValues = new RouteValueDictionary {
|
context.Metadata.DisplayRouteValues = new RouteValueDictionary {
|
||||||
{"Area", "Routable"},
|
{"Area", "Routable"},
|
||||||
{"Controller", "Item"},
|
{"Controller", "Item"},
|
||||||
|
@@ -10,13 +10,16 @@ namespace Orchard.Core.Routable.Services {
|
|||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public class RoutableHomePageProvider : IHomePageProvider {
|
public class RoutableHomePageProvider : IHomePageProvider {
|
||||||
private readonly IContentManager _contentManager;
|
private readonly IContentManager _contentManager;
|
||||||
|
private readonly IWorkContextAccessor _workContextAccessor;
|
||||||
public const string Name = "RoutableHomePageProvider";
|
public const string Name = "RoutableHomePageProvider";
|
||||||
|
|
||||||
public RoutableHomePageProvider(
|
public RoutableHomePageProvider(
|
||||||
IOrchardServices services,
|
IOrchardServices services,
|
||||||
IContentManager contentManager,
|
IContentManager contentManager,
|
||||||
IShapeFactory shapeFactory) {
|
IShapeFactory shapeFactory,
|
||||||
|
IWorkContextAccessor workContextAccessor) {
|
||||||
_contentManager = contentManager;
|
_contentManager = contentManager;
|
||||||
|
_workContextAccessor = workContextAccessor;
|
||||||
Services = services;
|
Services = services;
|
||||||
T = NullLocalizer.Instance;
|
T = NullLocalizer.Instance;
|
||||||
Shape = shapeFactory;
|
Shape = shapeFactory;
|
||||||
@@ -39,11 +42,13 @@ namespace Orchard.Core.Routable.Services {
|
|||||||
if (contentItem == null || !contentItem.Is<RoutePart>())
|
if (contentItem == null || !contentItem.Is<RoutePart>())
|
||||||
return new HttpNotFoundResult();
|
return new HttpNotFoundResult();
|
||||||
|
|
||||||
var model = _contentManager.BuildDisplay(contentItem);
|
// get the display metadata for the home page item
|
||||||
|
var displayRouteValues = _contentManager.GetItemMetadata(contentItem).DisplayRouteValues;
|
||||||
|
|
||||||
return new ViewResult {
|
var model = Shape.ViewModel(RouteValues: displayRouteValues);
|
||||||
|
return new PartialViewResult {
|
||||||
ViewName = "Routable.HomePage",
|
ViewName = "Routable.HomePage",
|
||||||
ViewData = new ViewDataDictionary<dynamic>(model)
|
ViewData = new ViewDataDictionary<object>(model)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1 +1,4 @@
|
|||||||
@Display(Model)
|
@{
|
||||||
|
RouteValueDictionary routeValues = Model.RouteValues;
|
||||||
|
Html.RenderAction(routeValues["action"] as string, routeValues["controller"] as string, routeValues);
|
||||||
|
}
|
@@ -3,6 +3,7 @@ using JetBrains.Annotations;
|
|||||||
using Orchard.Blogs.Models;
|
using Orchard.Blogs.Models;
|
||||||
using Orchard.ContentManagement;
|
using Orchard.ContentManagement;
|
||||||
using Orchard.ContentManagement.Handlers;
|
using Orchard.ContentManagement.Handlers;
|
||||||
|
using Orchard.Core.Routable.Models;
|
||||||
using Orchard.Data;
|
using Orchard.Data;
|
||||||
|
|
||||||
namespace Orchard.Blogs.Handlers {
|
namespace Orchard.Blogs.Handlers {
|
||||||
@@ -23,6 +24,12 @@ namespace Orchard.Blogs.Handlers {
|
|||||||
if (blog == null)
|
if (blog == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
context.Metadata.DisplayRouteValues = new RouteValueDictionary {
|
||||||
|
{"Area", "Orchard.Blogs"},
|
||||||
|
{"Controller", "Blog"},
|
||||||
|
{"Action", "Item"},
|
||||||
|
{"blogSlug", blog.As<RoutePart>().Slug}
|
||||||
|
};
|
||||||
context.Metadata.CreateRouteValues = new RouteValueDictionary {
|
context.Metadata.CreateRouteValues = new RouteValueDictionary {
|
||||||
{"Area", "Orchard.Blogs"},
|
{"Area", "Orchard.Blogs"},
|
||||||
{"Controller", "BlogAdmin"},
|
{"Controller", "BlogAdmin"},
|
||||||
|
@@ -246,13 +246,13 @@ namespace Orchard.Setup.Services {
|
|||||||
layerInitializer.CreateDefaultLayers();
|
layerInitializer.CreateDefaultLayers();
|
||||||
|
|
||||||
// add a layer for the homepage
|
// add a layer for the homepage
|
||||||
var homepageLayer = contentManager.Create("Layer");
|
var homepageLayer = contentManager.Create("Layer", VersionOptions.Draft);
|
||||||
homepageLayer.As<LayerPart>().Name = "TheHomepage";
|
homepageLayer.As<LayerPart>().Name = "TheHomepage";
|
||||||
homepageLayer.As<LayerPart>().LayerRule = "url \"~/\"";
|
homepageLayer.As<LayerPart>().LayerRule = "url \"~/\"";
|
||||||
contentManager.Publish(homepageLayer);
|
contentManager.Publish(homepageLayer);
|
||||||
|
|
||||||
// and three more for the tripel...really need this elsewhere...
|
// and three more for the tripel...really need this elsewhere...
|
||||||
var tripelFirst = contentManager.Create("HtmlWidget");
|
var tripelFirst = contentManager.Create("HtmlWidget", VersionOptions.Draft);
|
||||||
tripelFirst.As<WidgetPart>().LayerPart = homepageLayer.As<LayerPart>();
|
tripelFirst.As<WidgetPart>().LayerPart = homepageLayer.As<LayerPart>();
|
||||||
tripelFirst.As<WidgetPart>().Title = T("First Leader Aside").Text;
|
tripelFirst.As<WidgetPart>().Title = T("First Leader Aside").Text;
|
||||||
tripelFirst.As<WidgetPart>().Zone = "TripelFirst";
|
tripelFirst.As<WidgetPart>().Zone = "TripelFirst";
|
||||||
@@ -260,7 +260,7 @@ namespace Orchard.Setup.Services {
|
|||||||
tripelFirst.As<BodyPart>().Text = "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur a nibh ut tortor dapibus vestibulum. Aliquam vel sem nibh. Suspendisse vel condimentum tellus.</p>";
|
tripelFirst.As<BodyPart>().Text = "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur a nibh ut tortor dapibus vestibulum. Aliquam vel sem nibh. Suspendisse vel condimentum tellus.</p>";
|
||||||
contentManager.Publish(tripelFirst);
|
contentManager.Publish(tripelFirst);
|
||||||
|
|
||||||
var tripelSecond = contentManager.Create("HtmlWidget");
|
var tripelSecond = contentManager.Create("HtmlWidget", VersionOptions.Draft);
|
||||||
tripelSecond.As<WidgetPart>().LayerPart = homepageLayer.As<LayerPart>();
|
tripelSecond.As<WidgetPart>().LayerPart = homepageLayer.As<LayerPart>();
|
||||||
tripelSecond.As<WidgetPart>().Title = T("Second Leader Aside").Text;
|
tripelSecond.As<WidgetPart>().Title = T("Second Leader Aside").Text;
|
||||||
tripelSecond.As<WidgetPart>().Zone = "TripelSecond";
|
tripelSecond.As<WidgetPart>().Zone = "TripelSecond";
|
||||||
@@ -268,7 +268,7 @@ namespace Orchard.Setup.Services {
|
|||||||
tripelSecond.As<BodyPart>().Text = "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur a nibh ut tortor dapibus vestibulum. Aliquam vel sem nibh. Suspendisse vel condimentum tellus.</p>";
|
tripelSecond.As<BodyPart>().Text = "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur a nibh ut tortor dapibus vestibulum. Aliquam vel sem nibh. Suspendisse vel condimentum tellus.</p>";
|
||||||
contentManager.Publish(tripelSecond);
|
contentManager.Publish(tripelSecond);
|
||||||
|
|
||||||
var tripelThird = contentManager.Create("HtmlWidget");
|
var tripelThird = contentManager.Create("HtmlWidget", VersionOptions.Draft);
|
||||||
tripelThird.As<WidgetPart>().LayerPart = homepageLayer.As<LayerPart>();
|
tripelThird.As<WidgetPart>().LayerPart = homepageLayer.As<LayerPart>();
|
||||||
tripelThird.As<WidgetPart>().Title = T("Third Leader Aside").Text;
|
tripelThird.As<WidgetPart>().Title = T("Third Leader Aside").Text;
|
||||||
tripelThird.As<WidgetPart>().Zone = "TripelThird";
|
tripelThird.As<WidgetPart>().Zone = "TripelThird";
|
||||||
@@ -278,8 +278,10 @@ namespace Orchard.Setup.Services {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create a welcome page that's promoted to the home page
|
// create a welcome page that's promoted to the home page
|
||||||
var page = contentManager.Create("Page");
|
var page = contentManager.Create("Page", VersionOptions.Draft);
|
||||||
page.As<RoutePart>().Title = T("Welcome to Orchard!").Text;
|
page.As<RoutePart>().Title = T("Welcome to Orchard!").Text;
|
||||||
|
page.As<RoutePart>().Path = "";
|
||||||
|
page.As<RoutePart>().Slug = "";
|
||||||
page.As<BodyPart>().Text = string.Format(CultureInfo.CurrentCulture, "<p>You’ve successfully setup your Orchard Site and this is the homepage of your new site. Here are a few things you can look at to get familiar with the application. Once you feel confident you don’t need this anymore, you can <a href=\"Admin/Contents/Edit/{0}\">remove this by going into editing mode</a> and replacing it with whatever you want.</p><p>First things first - You’ll probably want to <a href=\"Admin/Settings\">manage your settings</a> and configure Orchard to your liking. After that, you can head over to <a href=\"Admin/Themes\">manage themes to change or install new themes</a> and really make it your own. Once you’re happy with a look and feel, it’s time for some content. You can start creating new custom content types or start with some built-in ones by <a href=\"Admin/Pages/Create\">adding a page</a>, <a href=\"Admin/Blogs/Create\">creating a blog</a> or <a href=\"Admin/Navigation\">managing your menus.</a></p><p>Finally, Orchard has been designed to be extended. It comes with a few built-in modules such as pages and blogs or themes. If you’re looking to add additional functionality, you can do so by creating your own module or installing a new one that someone has made. Modules are created by other users of Orchard just like you so if you feel up to it, <a href=\"http://www.orchardproject.net/\">please consider participating</a>. XOXO – The Orchard Team </p>", page.Id);
|
page.As<BodyPart>().Text = string.Format(CultureInfo.CurrentCulture, "<p>You’ve successfully setup your Orchard Site and this is the homepage of your new site. Here are a few things you can look at to get familiar with the application. Once you feel confident you don’t need this anymore, you can <a href=\"Admin/Contents/Edit/{0}\">remove this by going into editing mode</a> and replacing it with whatever you want.</p><p>First things first - You’ll probably want to <a href=\"Admin/Settings\">manage your settings</a> and configure Orchard to your liking. After that, you can head over to <a href=\"Admin/Themes\">manage themes to change or install new themes</a> and really make it your own. Once you’re happy with a look and feel, it’s time for some content. You can start creating new custom content types or start with some built-in ones by <a href=\"Admin/Pages/Create\">adding a page</a>, <a href=\"Admin/Blogs/Create\">creating a blog</a> or <a href=\"Admin/Navigation\">managing your menus.</a></p><p>Finally, Orchard has been designed to be extended. It comes with a few built-in modules such as pages and blogs or themes. If you’re looking to add additional functionality, you can do so by creating your own module or installing a new one that someone has made. Modules are created by other users of Orchard just like you so if you feel up to it, <a href=\"http://www.orchardproject.net/\">please consider participating</a>. XOXO – The Orchard Team </p>", page.Id);
|
||||||
|
|
||||||
contentManager.Publish(page);
|
contentManager.Publish(page);
|
||||||
|
@@ -12,6 +12,7 @@ using Orchard.Users.Services;
|
|||||||
using Orchard.Users.ViewModels;
|
using Orchard.Users.ViewModels;
|
||||||
using Orchard.ContentManagement;
|
using Orchard.ContentManagement;
|
||||||
using Orchard.Users.Models;
|
using Orchard.Users.Models;
|
||||||
|
using Orchard.UI.Notify;
|
||||||
|
|
||||||
namespace Orchard.Users.Controllers {
|
namespace Orchard.Users.Controllers {
|
||||||
[HandleError, Themed]
|
[HandleError, Themed]
|
||||||
@@ -119,8 +120,8 @@ namespace Orchard.Users.Controllers {
|
|||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
if ( user.As<UserPart>().EmailStatus == UserStatus.Pending ) {
|
if ( user.As<UserPart>().EmailStatus == UserStatus.Pending ) {
|
||||||
string challengeToken = _membershipService.GetEncryptedChallengeToken(user.As<UserPart>());
|
string challengeToken = _userService.GetNonce(user.As<UserPart>());
|
||||||
_membershipService.SendChallengeEmail(user.As<UserPart>(), Url.AbsoluteAction(() => Url.Action("ChallengeEmail", "Account", new { Area = "Orchard.Users", token = challengeToken })));
|
_userService.SendChallengeEmail(user.As<UserPart>(), Url.AbsoluteAction(() => Url.Action("ChallengeEmail", "Account", new { Area = "Orchard.Users", token = challengeToken })));
|
||||||
|
|
||||||
return RedirectToAction("ChallengeEmailSent");
|
return RedirectToAction("ChallengeEmailSent");
|
||||||
}
|
}
|
||||||
@@ -141,6 +142,36 @@ namespace Orchard.Users.Controllers {
|
|||||||
return Register();
|
return Register();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ActionResult LostPassword() {
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public ActionResult LostPassword(string username) {
|
||||||
|
|
||||||
|
if(String.IsNullOrWhiteSpace(username)){
|
||||||
|
_orchardServices.Notifier.Error(T("Invalid username or E-mail"));
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
_userService.SendLostPasswordEmail(username, nonce => Url.AbsoluteAction(() => Url.Action("ValidateLostPassword", "Account", new { Area = "Orchard.Users", nonce = nonce })));
|
||||||
|
|
||||||
|
_orchardServices.Notifier.Information(T("Check your e-mail for the confirmation link."));
|
||||||
|
|
||||||
|
return RedirectToAction("LogOn");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionResult ValidateLostPassword(string nonce) {
|
||||||
|
IUser user;
|
||||||
|
if (null != (user = _userService.ValidateLostPassword(nonce))) {
|
||||||
|
_authenticationService.SignIn(user, false);
|
||||||
|
return RedirectToAction("ChangePassword");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new RedirectResult("~/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public ActionResult ChangePassword() {
|
public ActionResult ChangePassword() {
|
||||||
ViewData["PasswordLength"] = MinPasswordLength;
|
ViewData["PasswordLength"] = MinPasswordLength;
|
||||||
@@ -150,32 +181,23 @@ namespace Orchard.Users.Controllers {
|
|||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
|
|
||||||
Justification = "Exceptions result in password not being changed.")]
|
|
||||||
public ActionResult ChangePassword(string currentPassword, string newPassword, string confirmPassword) {
|
public ActionResult ChangePassword(string currentPassword, string newPassword, string confirmPassword) {
|
||||||
ViewData["PasswordLength"] = MinPasswordLength;
|
ViewData["PasswordLength"] = MinPasswordLength;
|
||||||
|
|
||||||
if (!ValidateChangePassword(currentPassword, newPassword, confirmPassword)) {
|
if (newPassword == null || newPassword.Length < MinPasswordLength) {
|
||||||
|
ModelState.AddModelError("newPassword", T("You must specify a new password of {0} or more characters.", MinPasswordLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!String.Equals(newPassword, confirmPassword, StringComparison.Ordinal)) {
|
||||||
|
ModelState.AddModelError("_FORM", T("The new password and confirmation password do not match."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ModelState.IsValid) {
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
_membershipService.SetPassword(_orchardServices.WorkContext.CurrentUser, newPassword);
|
||||||
var validated = _membershipService.ValidateUser(User.Identity.Name, currentPassword);
|
return RedirectToAction("ChangePasswordSuccess");
|
||||||
|
|
||||||
if (validated != null) {
|
|
||||||
_membershipService.SetPassword(validated, newPassword);
|
|
||||||
return RedirectToAction("ChangePasswordSuccess");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ModelState.AddModelError("_FORM",
|
|
||||||
T("The current password is incorrect or the new password is invalid."));
|
|
||||||
return ChangePassword();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
ModelState.AddModelError("_FORM", T("The current password is incorrect or the new password is invalid."));
|
|
||||||
return ChangePassword();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ActionResult RegistrationPending() {
|
public ActionResult RegistrationPending() {
|
||||||
@@ -199,7 +221,7 @@ namespace Orchard.Users.Controllers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ActionResult ChallengeEmail(string token) {
|
public ActionResult ChallengeEmail(string token) {
|
||||||
var user = _membershipService.ValidateChallengeToken(token);
|
var user = _userService.ValidateChallenge(token);
|
||||||
|
|
||||||
if ( user != null ) {
|
if ( user != null ) {
|
||||||
_authenticationService.SignIn(user, false /* createPersistentCookie */);
|
_authenticationService.SignIn(user, false /* createPersistentCookie */);
|
||||||
@@ -217,21 +239,6 @@ namespace Orchard.Users.Controllers {
|
|||||||
|
|
||||||
#region Validation Methods
|
#region Validation Methods
|
||||||
|
|
||||||
private bool ValidateChangePassword(string currentPassword, string newPassword, string confirmPassword) {
|
|
||||||
if (String.IsNullOrEmpty(currentPassword)) {
|
|
||||||
ModelState.AddModelError("currentPassword", T("You must specify a current password."));
|
|
||||||
}
|
|
||||||
if (newPassword == null || newPassword.Length < MinPasswordLength) {
|
|
||||||
ModelState.AddModelError("newPassword", T("You must specify a new password of {0} or more characters.", MinPasswordLength));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!String.Equals(newPassword, confirmPassword, StringComparison.Ordinal)) {
|
|
||||||
ModelState.AddModelError("_FORM", T("The new password and confirmation password do not match."));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ModelState.IsValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IUser ValidateLogOn(string userNameOrEmail, string password) {
|
private IUser ValidateLogOn(string userNameOrEmail, string password) {
|
||||||
bool validate = true;
|
bool validate = true;
|
||||||
|
|
||||||
|
@@ -197,8 +197,8 @@ namespace Orchard.Users.Controllers {
|
|||||||
var user = Services.ContentManager.Get(id);
|
var user = Services.ContentManager.Get(id);
|
||||||
|
|
||||||
if ( user != null ) {
|
if ( user != null ) {
|
||||||
string challengeToken = _membershipService.GetEncryptedChallengeToken(user.As<UserPart>());
|
string challengeToken = _userService.GetNonce(user.As<UserPart>());
|
||||||
_membershipService.SendChallengeEmail(user.As<UserPart>(), Url.AbsoluteAction(() => Url.Action("ChallengeEmail", "Account", new {Area = "Orchard.Users", token = challengeToken})));
|
_userService.SendChallengeEmail(user.As<UserPart>(), Url.AbsoluteAction(() => Url.Action("ChallengeEmail", "Account", new {Area = "Orchard.Users", token = challengeToken})));
|
||||||
}
|
}
|
||||||
|
|
||||||
Services.Notifier.Information(T("Challenge email sent"));
|
Services.Notifier.Information(T("Challenge email sent"));
|
||||||
|
@@ -29,11 +29,16 @@ namespace Orchard.Users.Handlers {
|
|||||||
context.MailMessage.Body = T("The following user account needs to be moderated: {0}", recipient.UserName).Text;
|
context.MailMessage.Body = T("The following user account needs to be moderated: {0}", recipient.UserName).Text;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( context.Type == MessageTypes.Validation ) {
|
if (context.Type == MessageTypes.Validation) {
|
||||||
context.MailMessage.Subject = T("User account validation").Text;
|
context.MailMessage.Subject = T("User account validation").Text;
|
||||||
context.MailMessage.Body = T("Dear {0}, please <a href=\"{1}\">click here</a> to validate you email address.", recipient.UserName, context.Properties["ChallengeUrl"]).Text;
|
context.MailMessage.Body = T("Dear {0}, please <a href=\"{1}\">click here</a> to validate you email address.", recipient.UserName, context.Properties["ChallengeUrl"]).Text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.Type == MessageTypes.LostPassword) {
|
||||||
|
context.MailMessage.Subject = T("Lost password").Text;
|
||||||
|
context.MailMessage.Body = T("Dear {0}, please <a href=\"{1}\">click here</a> to change your password.", recipient.UserName, context.Properties["LostPasswordUrl"]).Text;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Sent(MessageContext context) {
|
public void Sent(MessageContext context) {
|
||||||
|
@@ -7,6 +7,7 @@ namespace Orchard.Users.Models {
|
|||||||
public static class MessageTypes {
|
public static class MessageTypes {
|
||||||
public const string Moderation = "ORCHARD_USERS_MODERATION";
|
public const string Moderation = "ORCHARD_USERS_MODERATION";
|
||||||
public const string Validation = "ORCHARD_USERS_VALIDATION";
|
public const string Validation = "ORCHARD_USERS_VALIDATION";
|
||||||
|
public const string LostPassword = "ORCHARD_USERS_RESETPASSWORD";
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -123,6 +123,9 @@
|
|||||||
<Content Include="Views\EditorTemplates\Parts\User.Edit.cshtml" />
|
<Content Include="Views\EditorTemplates\Parts\User.Edit.cshtml" />
|
||||||
<Content Include="Views\EditorTemplates\Parts\User.Create.cshtml" />
|
<Content Include="Views\EditorTemplates\Parts\User.Create.cshtml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Views\Account\LostPassword.cshtml" />
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
|
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
|
@@ -1,6 +1,16 @@
|
|||||||
|
using Orchard.Security;
|
||||||
|
using System;
|
||||||
namespace Orchard.Users.Services {
|
namespace Orchard.Users.Services {
|
||||||
public interface IUserService : IDependency {
|
public interface IUserService : IDependency {
|
||||||
string VerifyUserUnicity(string userName, string email);
|
string VerifyUserUnicity(string userName, string email);
|
||||||
string VerifyUserUnicity(int id, string userName, string email);
|
string VerifyUserUnicity(int id, string userName, string email);
|
||||||
|
|
||||||
|
void SendChallengeEmail(IUser user, string url);
|
||||||
|
IUser ValidateChallenge(string challengeToken);
|
||||||
|
|
||||||
|
bool SendLostPasswordEmail(string usernameOrEmail, Func<string, string> createUrl);
|
||||||
|
IUser ValidateLostPassword(string nonce);
|
||||||
|
|
||||||
|
string GetNonce(IUser user);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -14,6 +14,7 @@ using Orchard.Users.Events;
|
|||||||
using Orchard.Users.Models;
|
using Orchard.Users.Models;
|
||||||
using Orchard.Messaging.Services;
|
using Orchard.Messaging.Services;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Orchard.Services;
|
||||||
|
|
||||||
namespace Orchard.Users.Services {
|
namespace Orchard.Users.Services {
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
@@ -22,11 +23,13 @@ namespace Orchard.Users.Services {
|
|||||||
private readonly IOrchardServices _orchardServices;
|
private readonly IOrchardServices _orchardServices;
|
||||||
private readonly IMessageManager _messageManager;
|
private readonly IMessageManager _messageManager;
|
||||||
private readonly IEnumerable<IUserEventHandler> _userEventHandlers;
|
private readonly IEnumerable<IUserEventHandler> _userEventHandlers;
|
||||||
|
private readonly IClock _clock;
|
||||||
|
|
||||||
public MembershipService(IOrchardServices orchardServices, IMessageManager messageManager, IEnumerable<IUserEventHandler> userEventHandlers) {
|
public MembershipService(IOrchardServices orchardServices, IMessageManager messageManager, IEnumerable<IUserEventHandler> userEventHandlers, IClock clock) {
|
||||||
_orchardServices = orchardServices;
|
_orchardServices = orchardServices;
|
||||||
_messageManager = messageManager;
|
_messageManager = messageManager;
|
||||||
_userEventHandlers = userEventHandlers;
|
_userEventHandlers = userEventHandlers;
|
||||||
|
_clock = clock;
|
||||||
Logger = NullLogger.Instance;
|
Logger = NullLogger.Instance;
|
||||||
T = NullLocalizer.Instance;
|
T = NullLocalizer.Instance;
|
||||||
}
|
}
|
||||||
@@ -87,54 +90,6 @@ namespace Orchard.Users.Services {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendChallengeEmail(IUser user, string url) {
|
|
||||||
_messageManager.Send(user.ContentItem.Record, MessageTypes.Validation, "email", new Dictionary<string, string> { { "ChallengeUrl", url } });
|
|
||||||
}
|
|
||||||
|
|
||||||
public IUser ValidateChallengeToken(string challengeToken) {
|
|
||||||
string username;
|
|
||||||
DateTime validateByUtc;
|
|
||||||
|
|
||||||
if(!DecryptChallengeToken(challengeToken, out username, out validateByUtc)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( validateByUtc < DateTime.UtcNow )
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var user = GetUser(username);
|
|
||||||
if ( user == null )
|
|
||||||
return null;
|
|
||||||
|
|
||||||
user.As<UserPart>().EmailStatus = UserStatus.Approved;
|
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetEncryptedChallengeToken(IUser user) {
|
|
||||||
var challengeToken = new XElement("Token", new XAttribute("username", user.UserName), new XAttribute("validate-by-utc", DateTime.UtcNow.Add(DelayToValidate).ToString(CultureInfo.InvariantCulture))).ToString();
|
|
||||||
var data = Encoding.UTF8.GetBytes(challengeToken);
|
|
||||||
return MachineKey.Encode(data, MachineKeyProtection.All);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool DecryptChallengeToken(string challengeToken, out string username, out DateTime validateByUtc) {
|
|
||||||
username = null;
|
|
||||||
validateByUtc = DateTime.UtcNow;
|
|
||||||
|
|
||||||
try {
|
|
||||||
var data = MachineKey.Decode(challengeToken, MachineKeyProtection.All);
|
|
||||||
var xml = Encoding.UTF8.GetString(data);
|
|
||||||
var element = XElement.Parse(xml);
|
|
||||||
username = element.Attribute("username").Value;
|
|
||||||
validateByUtc = DateTime.Parse(element.Attribute("validate-by-utc").Value, CultureInfo.InvariantCulture);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public IUser GetUser(string username) {
|
public IUser GetUser(string username) {
|
||||||
var lowerName = username == null ? "" : username.ToLower();
|
var lowerName = username == null ? "" : username.ToLower();
|
||||||
|
|
||||||
|
@@ -1,17 +1,33 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Orchard.Logging;
|
using Orchard.Logging;
|
||||||
using Orchard.ContentManagement;
|
using Orchard.ContentManagement;
|
||||||
using Orchard.Users.Models;
|
using Orchard.Users.Models;
|
||||||
|
using Orchard.Security;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using Orchard.Services;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
using System.Web.Security;
|
||||||
|
using Orchard.Messaging.Services;
|
||||||
|
|
||||||
namespace Orchard.Users.Services {
|
namespace Orchard.Users.Services {
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public class UserService : IUserService {
|
public class UserService : IUserService {
|
||||||
private readonly IContentManager _contentManager;
|
private static readonly TimeSpan DelayToValidate = new TimeSpan(7, 0, 0, 0); // one week to validate email
|
||||||
|
|
||||||
public UserService(IContentManager contentManager) {
|
private readonly IContentManager _contentManager;
|
||||||
|
private readonly IMembershipService _membershipService;
|
||||||
|
private readonly IClock _clock;
|
||||||
|
private readonly IMessageManager _messageManager;
|
||||||
|
|
||||||
|
public UserService(IContentManager contentManager, IMembershipService membershipService, IClock clock, IMessageManager messageManager) {
|
||||||
_contentManager = contentManager;
|
_contentManager = contentManager;
|
||||||
|
_membershipService = membershipService;
|
||||||
|
_clock = clock;
|
||||||
|
_messageManager = messageManager;
|
||||||
Logger = NullLogger.Instance;
|
Logger = NullLogger.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,5 +62,86 @@ namespace Orchard.Users.Services {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetNonce(IUser user) {
|
||||||
|
var challengeToken = new XElement("Token", new XAttribute("username", user.UserName), new XAttribute("validate-by-utc", _clock.UtcNow.Add(DelayToValidate).ToString(CultureInfo.InvariantCulture))).ToString();
|
||||||
|
var data = Encoding.UTF8.GetBytes(challengeToken);
|
||||||
|
return MachineKey.Encode(data, MachineKeyProtection.All);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DecryptNonce(string challengeToken, out string username, out DateTime validateByUtc) {
|
||||||
|
username = null;
|
||||||
|
validateByUtc = _clock.UtcNow;
|
||||||
|
|
||||||
|
try {
|
||||||
|
var data = MachineKey.Decode(challengeToken, MachineKeyProtection.All);
|
||||||
|
var xml = Encoding.UTF8.GetString(data);
|
||||||
|
var element = XElement.Parse(xml);
|
||||||
|
username = element.Attribute("username").Value;
|
||||||
|
validateByUtc = DateTime.Parse(element.Attribute("validate-by-utc").Value, CultureInfo.InvariantCulture);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public IUser ValidateChallenge(string nonce) {
|
||||||
|
string username;
|
||||||
|
DateTime validateByUtc;
|
||||||
|
|
||||||
|
if (!DecryptNonce(nonce, out username, out validateByUtc)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validateByUtc < _clock.UtcNow)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var user = _membershipService.GetUser(username);
|
||||||
|
if (user == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
user.As<UserPart>().EmailStatus = UserStatus.Approved;
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendChallengeEmail(IUser user, string url) {
|
||||||
|
_messageManager.Send(user.ContentItem.Record, MessageTypes.Validation, "email", new Dictionary<string, string> { { "ChallengeUrl", url } });
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SendLostPasswordEmail(string usernameOrEmail, Func<string, string> createUrl) {
|
||||||
|
var lowerName = usernameOrEmail.ToLower();
|
||||||
|
var user = _contentManager.Query<UserPart, UserPartRecord>().Where(u => u.NormalizedUserName == lowerName || u.Email == lowerName).List().FirstOrDefault();
|
||||||
|
|
||||||
|
if (user != null) {
|
||||||
|
string nonce = GetNonce(user);
|
||||||
|
string url = createUrl(nonce);
|
||||||
|
|
||||||
|
_messageManager.Send(user.ContentItem.Record, MessageTypes.LostPassword, "email", new Dictionary<string, string> { { "LostPasswordUrl", url } });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IUser ValidateLostPassword(string nonce) {
|
||||||
|
string username;
|
||||||
|
DateTime validateByUtc;
|
||||||
|
|
||||||
|
if (!DecryptNonce(nonce, out username, out validateByUtc)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validateByUtc < _clock.UtcNow)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var user = _membershipService.GetUser(username);
|
||||||
|
if (user == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -6,11 +6,6 @@
|
|||||||
@using (Html.BeginFormAntiForgeryPost()) {
|
@using (Html.BeginFormAntiForgeryPost()) {
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>@T("Account Information")</legend>
|
<legend>@T("Account Information")</legend>
|
||||||
<div>
|
|
||||||
<label for="currentPassword">@T("Current password:")</label>
|
|
||||||
@Html.Password("currentPassword")
|
|
||||||
@Html.ValidationMessage("currentPassword")
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<label for="newPassword">@T("New password:")</label>
|
<label for="newPassword">@T("New password:")</label>
|
||||||
@Html.Password("newPassword")
|
@Html.Password("newPassword")
|
||||||
|
@@ -6,10 +6,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<h1 class="page-title">@Html.TitleForPage(Model.Title)</h1>
|
<h1 class="page-title">@Html.TitleForPage(Model.Title)</h1>
|
||||||
<p>@T("Please enter your username and password.") @if(userCanRegister) { <text> </text> @Html.ActionLink("Register", "Register") @T(" if you don't have an account.") }</p>
|
<p>
|
||||||
|
@T("Please enter your username and password.")
|
||||||
|
@if(userCanRegister) { @Html.ActionLink(T("Register").Text, "Register") @T(" if you don't have an account.") }
|
||||||
|
@Html.ActionLink(T("Lost your Password?").Text, "LostPassword")
|
||||||
|
</p>
|
||||||
@Html.ValidationSummary(T("Login was unsuccessful. Please correct the errors and try again.").ToString())
|
@Html.ValidationSummary(T("Login was unsuccessful. Please correct the errors and try again.").ToString())
|
||||||
|
|
||||||
@using (Html.BeginFormAntiForgeryPost(Url.Action("LogOn", new {ReturnUrl = Request.QueryString["ReturnUrl"]}))) {
|
@using (Html.BeginFormAntiForgeryPost(Url.Action("LogOn", new { ReturnUrl = Request.QueryString["ReturnUrl"] }))) {
|
||||||
<fieldset class="login-form group">
|
<fieldset class="login-form group">
|
||||||
<legend>@T("Account Information")</legend>
|
<legend>@T("Account Information")</legend>
|
||||||
<ol>
|
<ol>
|
||||||
|
@@ -0,0 +1,15 @@
|
|||||||
|
<h1>@Html.TitleForPage(T("Lost Password").ToString())</h1>
|
||||||
|
<p>@T("Please enter your username or email address. You will receive a link to create a new password via email.")</p>
|
||||||
|
@using (Html.BeginFormAntiForgeryPost()) {
|
||||||
|
<fieldset>
|
||||||
|
<legend>@T("Account Information")</legend>
|
||||||
|
<div>
|
||||||
|
<label for="username">@T("Username or E-mail:")</label>
|
||||||
|
@Html.TextBox("username")
|
||||||
|
@Html.ValidationMessage("username")
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button class="primaryAction" type="submit">@T("Send Request")</button>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
}
|
@@ -9,13 +9,10 @@ namespace Orchard.Mvc.Html {
|
|||||||
/// <see cref="http://en.wikipedia.org/wiki/Harry_Houdini"/>
|
/// <see cref="http://en.wikipedia.org/wiki/Harry_Houdini"/>
|
||||||
/// <returns>himself</returns>
|
/// <returns>himself</returns>
|
||||||
public static TService Resolve<TService>(this HtmlHelper html) {
|
public static TService Resolve<TService>(this HtmlHelper html) {
|
||||||
var workContextAccessor = html.ViewContext.RouteData.DataTokens["IWorkContextAccessor"] as IWorkContextAccessor;
|
var workContext = html.ViewContext.RequestContext.GetWorkContext();
|
||||||
if (workContextAccessor == null)
|
|
||||||
throw new ApplicationException("Unable to resolve");
|
|
||||||
|
|
||||||
var workContext = workContextAccessor.GetContext(html.ViewContext.HttpContext);
|
|
||||||
if (workContext == null)
|
if (workContext == null)
|
||||||
throw new ApplicationException("Unable to resolve");
|
return default(TService);
|
||||||
|
|
||||||
return workContext.Resolve<TService>();
|
return workContext.Resolve<TService>();
|
||||||
}
|
}
|
||||||
|
@@ -8,8 +8,5 @@
|
|||||||
IUser ValidateUser(string userNameOrEmail, string password);
|
IUser ValidateUser(string userNameOrEmail, string password);
|
||||||
void SetPassword(IUser user, string password);
|
void SetPassword(IUser user, string password);
|
||||||
|
|
||||||
IUser ValidateChallengeToken(string challengeToken);
|
|
||||||
void SendChallengeEmail(IUser user, string url);
|
|
||||||
string GetEncryptedChallengeToken(IUser user);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,6 +21,10 @@ namespace Orchard.UI.Navigation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void OnResultExecuting(ResultExecutingContext filterContext) {
|
public void OnResultExecuting(ResultExecutingContext filterContext) {
|
||||||
|
// should only run on a full view rendering result
|
||||||
|
if (!(filterContext.Result is ViewResult))
|
||||||
|
return;
|
||||||
|
|
||||||
var workContext = _workContextAccessor.GetContext(filterContext);
|
var workContext = _workContextAccessor.GetContext(filterContext);
|
||||||
|
|
||||||
var menuName = "main";
|
var menuName = "main";
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Web.Mvc;
|
using System.Web.Mvc;
|
||||||
using Orchard.DisplayManagement;
|
using Orchard.DisplayManagement;
|
||||||
using Orchard.Mvc.Filters;
|
using Orchard.Mvc.Filters;
|
||||||
@@ -20,6 +19,10 @@ namespace Orchard.UI.Resources {
|
|||||||
|
|
||||||
|
|
||||||
public void OnResultExecuting(ResultExecutingContext filterContext) {
|
public void OnResultExecuting(ResultExecutingContext filterContext) {
|
||||||
|
// should only run on a full view rendering result
|
||||||
|
if (!(filterContext.Result is ViewResult))
|
||||||
|
return;
|
||||||
|
|
||||||
var ctx = _workContextAccessor.GetContext();
|
var ctx = _workContextAccessor.GetContext();
|
||||||
var head = ctx.Layout.Head;
|
var head = ctx.Layout.Head;
|
||||||
var tail = ctx.Layout.Tail;
|
var tail = ctx.Layout.Tail;
|
||||||
|
@@ -10,27 +10,48 @@ namespace Orchard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static WorkContext GetWorkContext(this RequestContext requestContext) {
|
public static WorkContext GetWorkContext(this RequestContext requestContext) {
|
||||||
if (requestContext == null) {
|
if (requestContext == null)
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
var routeData = requestContext.RouteData;
|
var routeData = requestContext.RouteData;
|
||||||
object value;
|
if (routeData == null || routeData.DataTokens == null)
|
||||||
if (routeData == null ||
|
|
||||||
routeData.DataTokens == null ||
|
|
||||||
!routeData.DataTokens.TryGetValue("IWorkContextAccessor", out value) ||
|
|
||||||
!(value is IWorkContextAccessor)) {
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
object workContextValue;
|
||||||
|
if (!routeData.DataTokens.TryGetValue("IWorkContextAccessor", out workContextValue)) {
|
||||||
|
workContextValue = FindWorkContextInParent(routeData);
|
||||||
}
|
}
|
||||||
|
|
||||||
var workContextAccessor = (IWorkContextAccessor)value;
|
if (!(workContextValue is IWorkContextAccessor))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var workContextAccessor = (IWorkContextAccessor)workContextValue;
|
||||||
return workContextAccessor.GetContext(requestContext.HttpContext);
|
return workContextAccessor.GetContext(requestContext.HttpContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static WorkContext GetWorkContext(this ControllerContext controllerContext) {
|
private static object FindWorkContextInParent(RouteData routeData) {
|
||||||
if (controllerContext == null) {
|
object parentViewContextValue;
|
||||||
|
if (!routeData.DataTokens.TryGetValue("ParentActionViewContext", out parentViewContextValue)
|
||||||
|
|| !(parentViewContextValue is ViewContext)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var parentRouteData = ((ViewContext)parentViewContextValue).RouteData;
|
||||||
|
if (parentRouteData == null || parentRouteData.DataTokens == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
object workContextValue;
|
||||||
|
if (!parentRouteData.DataTokens.TryGetValue("IWorkContextAccessor", out workContextValue)) {
|
||||||
|
workContextValue = FindWorkContextInParent(parentRouteData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return workContextValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WorkContext GetWorkContext(this ControllerContext controllerContext) {
|
||||||
|
if (controllerContext == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
return WorkContextExtensions.GetWorkContext(controllerContext.RequestContext);
|
return WorkContextExtensions.GetWorkContext(controllerContext.RequestContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user