-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
URL Rewriting Module #16687
base: main
Are you sure you want to change the base?
URL Rewriting Module #16687
Changes from 27 commits
edcb175
ad39ce7
67ff75e
0c6761b
f8ec4e2
4661da6
49f3939
d7e6f61
171c170
0f2914a
3cbccfb
2eeefea
40bf27b
5a0a436
3d86a84
6d1f4b4
ff64d23
1735f9a
8080821
23546cb
060a19c
61e2ce7
397ffa0
2963137
7ffe421
806be17
8b23ca1
45d1547
8fb929f
78d7943
f5cd1f5
1f83775
342d69b
a1de614
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,29 @@ | ||||||||
using Microsoft.Extensions.Localization; | ||||||||
using OrchardCore.Navigation; | ||||||||
|
||||||||
namespace OrchardCore.UrlRewriting; | ||||||||
|
||||||||
public sealed class AdminMenu : AdminNavigationProvider | ||||||||
{ | ||||||||
public readonly IStringLocalizer S; | ||||||||
|
||||||||
public AdminMenu(IStringLocalizer<AdminMenu> stringLocalizer) | ||||||||
{ | ||||||||
S = stringLocalizer; | ||||||||
} | ||||||||
|
||||||||
protected override ValueTask BuildAsync(NavigationBuilder builder) | ||||||||
{ | ||||||||
builder | ||||||||
.Add(S["Configuration"], configuration => configuration | ||||||||
.Add(S["URL Rewriting"], S["URL Rewriting"].PrefixPosition(), rewriting => rewriting | ||||||||
.AddClass("urlrewriting").Id("urlrewriting") | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
.Permission(UrlRewritingPermissions.ManageUrlRewriting) | ||||||||
.Action("Index", "Admin", "OrchardCore.UrlRewriting") | ||||||||
.LocalNav() | ||||||||
) | ||||||||
); | ||||||||
|
||||||||
return ValueTask.CompletedTask; | ||||||||
} | ||||||||
} |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,200 @@ | ||||||||
using Microsoft.AspNetCore.Authorization; | ||||||||
using Microsoft.AspNetCore.Mvc; | ||||||||
using Microsoft.AspNetCore.Mvc.Localization; | ||||||||
using Microsoft.Extensions.Localization; | ||||||||
using OrchardCore.Admin; | ||||||||
using OrchardCore.DisplayManagement; | ||||||||
using OrchardCore.DisplayManagement.ModelBinding; | ||||||||
using OrchardCore.DisplayManagement.Notify; | ||||||||
using OrchardCore.Environment.Shell; | ||||||||
using OrchardCore.UrlRewriting.Helpers; | ||||||||
using OrchardCore.UrlRewriting.Models; | ||||||||
using OrchardCore.UrlRewriting.Services; | ||||||||
using OrchardCore.UrlRewriting.ViewModels; | ||||||||
|
||||||||
namespace OrchardCore.UrlRewriting.Controllers; | ||||||||
|
||||||||
[Admin("UrlRewriting/{action}/{id?}", "UrlRewriting{action}")] | ||||||||
public sealed class AdminController : Controller | ||||||||
{ | ||||||||
private readonly IAuthorizationService _authorizationService; | ||||||||
private readonly INotifier _notifier; | ||||||||
private readonly RewriteRulesStore _rewriteRulesStore; | ||||||||
private readonly IDisplayManager<RewriteRule> _rewriteRuleDisplayManager; | ||||||||
private readonly IUpdateModelAccessor _updateModelAccessor; | ||||||||
private readonly IShellReleaseManager _shellReleaseManager; | ||||||||
|
||||||||
internal readonly IStringLocalizer S; | ||||||||
internal readonly IHtmlLocalizer H; | ||||||||
|
||||||||
public AdminController( | ||||||||
IDisplayManager<RewriteRule> rewriteRuleDisplayManager, | ||||||||
IAuthorizationService authorizationService, | ||||||||
RewriteRulesStore rewriteRulesStore, | ||||||||
INotifier notifier, | ||||||||
IShellReleaseManager shellReleaseManager, | ||||||||
IStringLocalizer<AdminController> stringLocalizer, | ||||||||
IHtmlLocalizer<AdminController> htmlLocalizer, | ||||||||
IUpdateModelAccessor updateModelAccessor) | ||||||||
{ | ||||||||
_rewriteRuleDisplayManager = rewriteRuleDisplayManager; | ||||||||
_authorizationService = authorizationService; | ||||||||
_rewriteRulesStore = rewriteRulesStore; | ||||||||
_notifier = notifier; | ||||||||
_shellReleaseManager = shellReleaseManager; | ||||||||
_updateModelAccessor = updateModelAccessor; | ||||||||
S = stringLocalizer; | ||||||||
H = htmlLocalizer; | ||||||||
} | ||||||||
|
||||||||
public async Task<IActionResult> Index() | ||||||||
{ | ||||||||
if (!await _authorizationService.AuthorizeAsync(User, UrlRewritingPermissions.ManageUrlRewriting)) | ||||||||
{ | ||||||||
return Forbid(); | ||||||||
} | ||||||||
|
||||||||
var rules = await _rewriteRulesStore.GetRewriteRulesAsync(); | ||||||||
|
||||||||
var model = new RewriteRulesViewModel | ||||||||
{ | ||||||||
Rules = rules.Rules | ||||||||
.Select(BuildViewModel) | ||||||||
.ToList() | ||||||||
MikeAlhayek marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
}; | ||||||||
|
||||||||
return View(model); | ||||||||
} | ||||||||
|
||||||||
[Admin("UrlRewriting/CreateRule", nameof(CreateRule))] | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
public async Task<ActionResult> CreateRule() | ||||||||
MikeAlhayek marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
{ | ||||||||
if (!await _authorizationService.AuthorizeAsync(User, UrlRewritingPermissions.ManageUrlRewriting)) | ||||||||
{ | ||||||||
return Forbid(); | ||||||||
} | ||||||||
|
||||||||
var rule = new RewriteRule(); | ||||||||
|
||||||||
var shape = await _rewriteRuleDisplayManager.BuildEditorAsync(rule, updater: _updateModelAccessor.ModelUpdater, isNew: true); | ||||||||
|
||||||||
return View(shape); | ||||||||
} | ||||||||
|
||||||||
[HttpPost, ActionName(nameof(CreateRule))] | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
public async Task<ActionResult> CreateRulePOST() | ||||||||
MikeAlhayek marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
{ | ||||||||
if (!await _authorizationService.AuthorizeAsync(User, UrlRewritingPermissions.ManageUrlRewriting)) | ||||||||
{ | ||||||||
return Forbid(); | ||||||||
} | ||||||||
|
||||||||
var rule = new RewriteRule(); | ||||||||
|
||||||||
var shape = await _rewriteRuleDisplayManager.UpdateEditorAsync(rule, updater: _updateModelAccessor.ModelUpdater, isNew: true); | ||||||||
|
||||||||
if (!ModelState.IsValid) | ||||||||
{ | ||||||||
return View(shape); | ||||||||
} | ||||||||
|
||||||||
await _rewriteRulesStore.SaveAsync(rule); | ||||||||
|
||||||||
await _notifier.SuccessAsync(H["Rule created successfully."]); | ||||||||
|
||||||||
return RedirectToAction(nameof(Index)); | ||||||||
MikeAlhayek marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
} | ||||||||
|
||||||||
public async Task<ActionResult> EditRule(string id) | ||||||||
MikeAlhayek marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
{ | ||||||||
if (!await _authorizationService.AuthorizeAsync(User, UrlRewritingPermissions.ManageUrlRewriting)) | ||||||||
{ | ||||||||
return Forbid(); | ||||||||
} | ||||||||
|
||||||||
var currentRule = await _rewriteRulesStore.FindByIdAsync(id); | ||||||||
MikeAlhayek marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
||||||||
if (currentRule == null) | ||||||||
{ | ||||||||
return NotFound(); | ||||||||
} | ||||||||
|
||||||||
var shape = await _rewriteRuleDisplayManager.BuildEditorAsync(currentRule, updater: _updateModelAccessor.ModelUpdater, isNew: false); | ||||||||
|
||||||||
return View(shape); | ||||||||
} | ||||||||
|
||||||||
[HttpPost, ActionName(nameof(EditRule))] | ||||||||
MikeAlhayek marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
public async Task<ActionResult> EditRulePOST(string id) | ||||||||
{ | ||||||||
if (!await _authorizationService.AuthorizeAsync(User, UrlRewritingPermissions.ManageUrlRewriting)) | ||||||||
{ | ||||||||
return Forbid(); | ||||||||
} | ||||||||
|
||||||||
var currentRule = await _rewriteRulesStore.FindByIdAsync(id); | ||||||||
|
||||||||
if (currentRule == null) | ||||||||
{ | ||||||||
return NotFound(); | ||||||||
} | ||||||||
|
||||||||
var shape = await _rewriteRuleDisplayManager.UpdateEditorAsync(currentRule, updater: _updateModelAccessor.ModelUpdater, isNew: false); | ||||||||
|
||||||||
if (ModelState.IsValid) | ||||||||
{ | ||||||||
await _rewriteRulesStore.SaveAsync(currentRule); | ||||||||
|
||||||||
await _notifier.SuccessAsync(H["Rule updated successfully."]); | ||||||||
|
||||||||
return RedirectToAction(nameof(Index)); | ||||||||
} | ||||||||
|
||||||||
return View(shape); | ||||||||
} | ||||||||
|
||||||||
[HttpPost] | ||||||||
public async Task<IActionResult> DeleteRule(string id) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
{ | ||||||||
if (!await _authorizationService.AuthorizeAsync(User, UrlRewritingPermissions.ManageUrlRewriting)) | ||||||||
{ | ||||||||
return Forbid(); | ||||||||
} | ||||||||
|
||||||||
var currentRule = await _rewriteRulesStore.FindByIdAsync(id); | ||||||||
|
||||||||
if (currentRule == null) | ||||||||
{ | ||||||||
return NotFound(); | ||||||||
} | ||||||||
|
||||||||
await _rewriteRulesStore.DeleteAsync(currentRule); | ||||||||
|
||||||||
await _notifier.SuccessAsync(H["Rule deleted successfully."]); | ||||||||
|
||||||||
return RedirectToAction(nameof(Index)); | ||||||||
} | ||||||||
|
||||||||
[HttpPost] | ||||||||
public async Task<IActionResult> ReloadWebsite() | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would rather just reload the shell every time a new rule is added or a rule edited with change instead of adding an action to reload the site. This is how to do it in other places like recipes and features. We typically add a warning from the display driver about the release. |
||||||||
{ | ||||||||
if (!await _authorizationService.AuthorizeAsync(User, UrlRewritingPermissions.ManageUrlRewriting)) | ||||||||
{ | ||||||||
return Forbid(); | ||||||||
} | ||||||||
|
||||||||
_shellReleaseManager.RequestRelease(); | ||||||||
|
||||||||
return RedirectToAction(nameof(Index)); | ||||||||
} | ||||||||
|
||||||||
|
||||||||
private static RewriteRuleViewModel BuildViewModel(RewriteRule rule) | ||||||||
{ | ||||||||
var viewModel = new RewriteRuleViewModel(); | ||||||||
|
||||||||
viewModel.FromModel(rule); | ||||||||
|
||||||||
return viewModel; | ||||||||
} | ||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
using Microsoft.AspNetCore.Authorization; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.Extensions.Localization; | ||
using OrchardCore.DisplayManagement.Handlers; | ||
using OrchardCore.DisplayManagement.Views; | ||
using OrchardCore.UrlRewriting.Models; | ||
using OrchardCore.UrlRewriting.ViewModels; | ||
using OrchardCore.Mvc.ModelBinding; | ||
using Microsoft.AspNetCore.Mvc.Rendering; | ||
using OrchardCore.UrlRewriting.Services; | ||
using OrchardCore.UrlRewriting.Rules; | ||
using OrchardCore.UrlRewriting.Helpers; | ||
|
||
namespace OrchardCore.UrlRewriting.Drivers; | ||
|
||
internal sealed class RewriteRuleDisplayDriver : DisplayDriver<RewriteRule> | ||
MikeAlhayek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
private readonly IAuthorizationService _authorizationService; | ||
private readonly IHttpContextAccessor _httpContextAccessor; | ||
private readonly RewriteRulesStore _rewriteRulesStore; | ||
|
||
internal readonly IStringLocalizer S; | ||
|
||
public RewriteRuleDisplayDriver( | ||
IAuthorizationService authorizationService, | ||
IHttpContextAccessor httpContextAccessor, | ||
RewriteRulesStore rewriteRulesStore, | ||
IStringLocalizer<RewriteRuleDisplayDriver> stringLocalizer) | ||
{ | ||
_authorizationService = authorizationService; | ||
_httpContextAccessor = httpContextAccessor; | ||
_rewriteRulesStore = rewriteRulesStore; | ||
S = stringLocalizer; | ||
} | ||
|
||
public override async Task<IDisplayResult> EditAsync(RewriteRule rule, BuildEditorContext context) | ||
{ | ||
if (!await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, UrlRewritingPermissions.ManageUrlRewriting)) | ||
{ | ||
return null; | ||
} | ||
|
||
return Initialize<RewriteRuleViewModel>("RewriteRuleFields_Edit", viewModel => BuildViewModel(rule, viewModel, context.IsNew)) | ||
.Location("Content:1"); | ||
} | ||
|
||
public override async Task<IDisplayResult> UpdateAsync(RewriteRule rule, UpdateEditorContext context) | ||
{ | ||
if (!await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, UrlRewritingPermissions.ManageUrlRewriting)) | ||
{ | ||
return null; | ||
} | ||
|
||
var model = new RewriteRuleViewModel(); | ||
|
||
await context.Updater.TryUpdateModelAsync(model, Prefix); | ||
|
||
var rules = await _rewriteRulesStore.LoadRewriteRulesAsync(); | ||
|
||
if (string.IsNullOrWhiteSpace(model.Name)) | ||
{ | ||
context.Updater.ModelState.AddModelError(Prefix, nameof(RewriteRuleViewModel.Name), "The rule name is required."); | ||
} | ||
else if (context.IsNew && rules.Rules.Any(x => string.Equals(x.Name, model.Name, StringComparison.OrdinalIgnoreCase))) | ||
{ | ||
context.Updater.ModelState.AddModelError(Prefix, nameof(RewriteRuleViewModel.Name), "The rule name already exists."); | ||
} | ||
if (string.IsNullOrWhiteSpace(model.Pattern)) | ||
MikeAlhayek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
context.Updater.ModelState.AddModelError(Prefix, nameof(RewriteRuleViewModel.Pattern), "The url match pattern is required."); | ||
} | ||
if (model.RuleAction == RuleAction.Rewrite && string.IsNullOrWhiteSpace(model.RewriteAction.RewriteUrl)) | ||
MikeAlhayek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
context.Updater.ModelState.AddModelError(Prefix, "RewriteAction.RewriteUrl", "The rewrite url substitition is required."); | ||
} | ||
if (model.RuleAction == RuleAction.Redirect && string.IsNullOrWhiteSpace(model.RedirectAction.RedirectUrl)) | ||
MikeAlhayek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
context.Updater.ModelState.AddModelError(Prefix, "RedirectAction.RedirectUrl", "The redirect url substitition is required."); | ||
} | ||
|
||
if (context.Updater.ModelState.IsValid) | ||
{ | ||
if (ApacheRuleValidator.ValidateRule(model, out var message) == false) | ||
MikeAlhayek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
context.Updater.ModelState.AddModelError(Prefix, "RuleError", S["Rule error: {0}", message]); | ||
} | ||
} | ||
|
||
rule.Name = model.Name; | ||
rule.Pattern = model.Pattern; | ||
rule.Substitution = model.RuleAction == RuleAction.Rewrite ? model.RewriteAction.RewriteUrl : model.RedirectAction.RedirectUrl; | ||
rule.Flags = ApacheRules.FlagsFromViewModel(model); | ||
|
||
return await EditAsync(rule, context); | ||
} | ||
|
||
private void BuildViewModel(RewriteRule model, RewriteRuleViewModel viewModel, bool isNew) | ||
{ | ||
viewModel.FromModel(model); | ||
|
||
viewModel.IsNew = isNew; | ||
|
||
viewModel.AvailableActions.Add(new SelectListItem() { Text = S["Rewrite"], Value = ((int)RuleAction.Rewrite).ToString() }); | ||
viewModel.AvailableActions.Add(new SelectListItem() { Text = S["Redirect"], Value = ((int)RuleAction.Redirect).ToString() }); | ||
|
||
viewModel.RedirectAction.AvailableRedirectTypes.Add(new SelectListItem() { Text = S["Moved Permanently (301)"], Value = ((int)RedirectType.MovedPermanently301).ToString() }); | ||
viewModel.RedirectAction.AvailableRedirectTypes.Add(new SelectListItem() { Text = S["Found (302)"], Value = ((int)RedirectType.Found302).ToString() }); | ||
viewModel.RedirectAction.AvailableRedirectTypes.Add(new SelectListItem() { Text = S["Temporary Redirect (307)"], Value = ((int)RedirectType.TemporaryRedirect307).ToString() }); | ||
viewModel.RedirectAction.AvailableRedirectTypes.Add(new SelectListItem() { Text = S["Pernament Redirect (308)"], Value = ((int)RedirectType.PernamentRedirect308).ToString() }); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.