Замена старых ссылок контроллера на новые
Делаю свой pet проект для ознакомления с asp.net core mvc. Первоначально был контроллер User который отвечал за работу с пользователем (регистрация, авторизация, смена пароля и выход) и был метод Login который отвечал за вход. В один момент мне захотелось изменить имя контроллера с User на Account, а имя метода с Login на Sigin и пошла замена ссылок везде где только можно. В общем всплыли у меня 2 проблемы:
- Как быть если необходимо поменять имя контроллера или метода, то тогда искать во всём проекте старые имена и менять их на новые (View, код, класс
Startupпри настройкеCookieAuthenticationOptions) - Как быть, если проект разросся до >=10 контроллеров и в каждом из них >=5 методов
HttpGetи в голове всех их уже не упомнишь и нужно один из них вызвать.
Вот как я решил свои проблемы
public static class LinksConstants
{
public static class Account
{
public const String Controller = "Account";
public const String SignUpActionName = "SignUp";
public const String SignInActionName = "SignIn";
public const String ChangePasswordActionName = "ChangePassword";
public const String SignOutActionName = "SignOut";
public static readonly String SignUpLink;
public static readonly String SignInLink;
public static readonly String ChangePasswordLink;
public static readonly String SignOutLink;
static Account()
{
String mask = "/{0}/{1}";
SignUpLink = String.Format(mask , Controller , SignUpActionName);
SignInLink = String.Format(mask , Controller , SignInActionName);
ChangePasswordLink = String.Format(mask , Controller , ChangePasswordActionName);
SignOutLink = String.Format(mask , Controller , SignOutActionName);
}
}
}
public class AccountController : Controller
{
[HttpGet, AllowAnonymous, ActionName(LinksConstants.Account.SignInActionName)]
public async Task<IActionResult> SignInView([FromServices] SignInManager signInManager ,
[FromQuery] String returnUrl = null)
{
//do work
}
}
<div class="clearfix">
<a class="float-right" asp-route-returnUrl="@Model.ReturnUrl" [email protected] [email protected]>Create account</a>
</div>
Как у Вас, в реальных проектах решаются такие проблемы?
Модель
public sealed class SignInViewModel
{
#region Fields
public static readonly SignInViewModel Empty;
[Required]
[DataType(DataType.EmailAddress)]
[EmailAddress]
[Display(Name = "Email")]
public String Email { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
[Remote(action: LinksConstants.RemoteValidation.ValidationPasswordActionName , controller: LinksConstants.RemoteValidation.Controller)]
public String Password { get; set; }
[ScaffoldColumn(false), ValidateNever]
public String ReturnUrl { get; set; }
#endregion Fields
static SignInViewModel()
{
Empty = new SignInViewModel()
{
ReturnUrl = "/"
};
}
public async Task ValidationOnServer(ModelStateDictionary modelState , UserManager userManager)
{
UserEntity user = await userManager.FindByEmailAsync(Email);
if (user is null || !await userManager.CheckPasswordAsync(user , Password))
{
String message = "Invalid email or password";
modelState.TryAddModelError(nameof(Email) , message);
modelState.TryAddModelError(nameof(Password) , message);
}
}
}
View
@using WebContacts.Models.Account;
@model SignInViewModel
@section Scripts
{
<partial name="_ValidationScriptsPartial" />
}
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<form id="account" method="post" asp-route-returnUrl="@Model.ReturnUrl">
<div class="container py-0 ">
<div class="row d-flex justify-content-center align-items-center ">
<div class="col-12 col-md-8 col-lg-6 col-xl-5">
<div class="card shadow-2-strong" style="border-radius: 1rem;">
<div class="card-body p-5 text-center">
<h3 class="mb-5">Sign in</h3>
<div class="form-outline mb-4">
<input class="form-control form-control-lg" asp-for="@Model.Email" />
<label class="form-label" asp-for="@Model.Email"></label>
<span class="text-danger" asp-validation-for="@Model.Email"></span>
</div>
<div class="form-outline mb-4">
<input class="form-control form-control-lg" asp-for="@Model.Password" />
<label class="form-label" asp-for="@Model.Password"></label>
<span class="text-danger" asp-validation-for="@Model.Password"></span>
</div>
<button class="btn btn-primary btn-lg btn-block" type="submit">Login</button>
<div class="clearfix">
<a class="float-right" asp-route-returnUrl="@Model.ReturnUrl" [email protected] [email protected]>Create account</a>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</body>
</html>
Ответы (1 шт):
Кратко: вам нужна автоматическая замена имён классов-контроллеров и имён методов в них.
Идеально для этого подходит nameof.
Единственное, что нужно сделать, принять именование контроллеров без суффикса, т. е. в виде Account, а не AccountController.
Соглашения о наименованиях позволяют это делать: What is a Controller?.
В tag helper пишем
<a asp-controller="@nameof(Account)" asp-action="@nameof(Account.SignInView)">Create account</a>
и всё прекрасно работает.
После чего можно без проблем делать переименования методов и классов.
Осознал: это решает не все проблемы. Остаётся вопрос автоматического переименования View и ViewModel.
С вьюхами наверняка можно что-то намутить с помощью IViewLocationExpander interface.
В реальных проектах я пока не встречал такого, но в книге Modern API Design with ASP.NET Core 2, Fanie Reynders, есть пример, как задать собственные соглашения по именованию контроллеров с помощью IApplicationModelConvention.
По аналогии можно и собственные соглашения по именованию View сделать.
Остаются ViewModel. Тут наверняка на помощь могут прийти Source Generators.
Вот только боюсь, если всё это реализовать, вас потом проклянут те, кто будут поддерживать проект.
Ещё бы я посоветовал посмотреть в сторону Vertical Slice Architecture. Отказаться от традиционного размещения: контроллеры в папку Controllers, вьюхи в папку Views, модели в папку Models.
Вместо этого группировать их по назначению: в папке Account располагаются вместе контроллер, модель, вьюха и прочее, что относится к аккаунту. Тогда переименование станет намного проще: не нужно метаться по разным папкам, вспоминая, где ещё что-то забыто.
Однако, и это полностью не решит проблему, т. к. иногда бывает необходимо сделать редирект из одного контроллера в другой. Тогда при изменении имени в одной папке придётся менять его и в другой. Но всё же это редкость.