Disons que j'ai un contrôleur qui utilise le routage basé sur des attributs pour gérer une URL demandée de/admin/product comme ceci:
[Route("admin/[controller]")]
public class ProductController: Controller {
// GET: /admin/product
[Route("")]
public IActionResult Index() {
return View();
}
}
Supposons maintenant que je souhaite conserver mes vues organisées dans une structure de dossiers qui reflète approximativement les chemins d'URL auxquels ils sont liés. J'aimerais donc que la vue de ce contrôleur soit située ici:
/Views/Admin/Product.cshtml
Pour aller plus loin, si j'avais un contrôleur comme celui-ci:
[Route("admin/marketing/[controller]")]
public class PromoCodeListController: Controller {
// GET: /admin/marketing/promocodelist
[Route("")]
public IActionResult Index() {
return View();
}
}
J'aimerais que le framework recherche automatiquement sa vue ici:
Views/Admin/Marketing/PromoCodeList.cshtml
Idéalement, l'approche pour informer le cadre de l'emplacement de la vue fonctionnerait de manière générale sur la base des informations de route basées sur les attributs, quel que soit le nombre de segments d'URL impliqués (c'est-à-dire la profondeur d'imbrication).
Comment puis-je demander au framework Core MVC (j'utilise actuellement RC1) de rechercher la vue du contrôleur dans un tel emplacement?
Vous pouvez développer les emplacements où le moteur de vue recherche les vues en implémentant un expanseur d'emplacement de vue. Voici quelques exemples de code pour illustrer l'approche:
public class ViewLocationExpander: IViewLocationExpander {
/// <summary>
/// Used to specify the locations that the view engine should search to
/// locate views.
/// </summary>
/// <param name="context"></param>
/// <param name="viewLocations"></param>
/// <returns></returns>
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations) {
//{2} is area, {1} is controller,{0} is the action
string[] locations = new string[] { "/Views/{2}/{1}/{0}.cshtml"};
return locations.Union(viewLocations); //Add mvc default locations after ours
}
public void PopulateValues(ViewLocationExpanderContext context) {
context.Values["customviewlocation"] = nameof(ViewLocationExpander);
}
}
Ensuite, dans la méthode ConfigureServices(IServiceCollection services)
du fichier startup.cs, ajoutez le code suivant pour l'enregistrer auprès du conteneur IoC. Faites cela juste après services.AddMvc();
services.Configure<RazorViewEngineOptions>(options => {
options.ViewLocationExpanders.Add(new ViewLocationExpander());
});
Vous avez maintenant un moyen d'ajouter la structure de répertoires personnalisée de votre choix à la liste des emplacements où le moteur de vue recherche les vues et les vues partielles. Ajoutez-le simplement au locations
string[]
. En outre, vous pouvez placer un fichier _ViewImports.cshtml
Dans le même répertoire ou dans n'importe quel répertoire parent et il sera trouvé et fusionné avec vos vues situées dans cette nouvelle structure de répertoires.
Mise à jour:
Une bonne chose à propos de cette approche est qu'elle offre plus de flexibilité que l'approche introduite plus tard dans ASP.NET Core 2 (Merci @BrianMacKay pour avoir documenté la nouvelle approche). Ainsi, par exemple, cette approche ViewLocationExpander permet non seulement de spécifier une hiérarchie de chemins pour rechercher des vues et des zones, mais également des dispositions et des composants de vue. Vous avez également accès à l'intégralité de ActionContext
pour déterminer ce qu'est une route appropriée. Cela offre beaucoup de flexibilité et de puissance. Ainsi, par exemple, si vous souhaitez déterminer l'emplacement de vue approprié en évaluant le chemin de la demande en cours, vous pouvez accéder au chemin de la demande en cours via context.ActionContext.HttpContext.Request.Path
.
Bonne nouvelle ... Dans ASP.NET Core 2. *, vous n'avez plus besoin d'un ViewEngine personnalisé ni même de ExpandViewLocations.
Utilisation du package OdeToCode.AddFeatureFolders
C'est la manière la plus simple ... K. Scott Allen a un paquet de pépites pour vous à OdeToCode.AddFeatureFolders qui est propre et inclut un support optionnel pour les zones. Github: https://github.com/OdeToCode/AddFeatureFolders
Installez le package, et c'est aussi simple que:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddFeatureFolders();
...
}
...
}
[~ # ~] bricolage [~ # ~]
Utilisez-le si vous avez besoin d'un contrôle extrêmement précis sur la structure de votre dossier, ou si vous n'êtes pas autorisé/ne voulez pas prendre la dépendance pour une raison quelconque. C'est aussi assez facile, bien que peut-être plus encombrant que le paquet de pépites ci-dessus:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.Configure<RazorViewEngineOptions>(o =>
{
// {2} is area, {1} is controller,{0} is the action
o.ViewLocationFormats.Clear();
o.ViewLocationFormats.Add("/Controllers/{1}/Views/{0}" + RazorViewEngine.ViewExtension);
o.ViewLocationFormats.Add("/Controllers/Shared/Views/{0}" + RazorViewEngine.ViewExtension);
// Untested. You could remove this if you don't care about areas.
o.AreaViewLocationFormats.Clear();
o.AreaViewLocationFormats.Add("/Areas/{2}/Controllers/{1}/Views/{0}" + RazorViewEngine.ViewExtension);
o.AreaViewLocationFormats.Add("/Areas/{2}/Controllers/Shared/Views/{0}" + RazorViewEngine.ViewExtension);
o.AreaViewLocationFormats.Add("/Areas/Shared/Views/{0}" + RazorViewEngine.ViewExtension);
});
...
}
...
}
Et c'est tout! Aucune classe spéciale requise.
Traiter avec Resharper/Rider
Astuce bonus: si vous utilisez ReSharper, vous remarquerez peut-être qu'à certains endroits, ReSharper ne peut pas trouver vos vues et vous donne des avertissements ennuyeux. Pour contourner cela, tirez dans le package Resharper.Annotations et dans votre startup.cs (ou n'importe où ailleurs vraiment) ajoutez l'un de ces attributs pour chacun de vos emplacements d'affichage:
[Assembly: AspMvcViewLocationFormat("/Controllers/{1}/Views/{0}.cshtml")]
[Assembly: AspMvcViewLocationFormat("/Controllers/Shared/Views/{0}.cshtml")]
[Assembly: AspMvcViewLocationFormat("/Areas/{2}/Controllers/{1}/Views/{0}.cshtml")]
[Assembly: AspMvcViewLocationFormat("/Controllers/Shared/Views/{0}.cshtml")]
J'espère que cela épargnera à certaines personnes les heures de frustration que je viens de vivre. :)
Dans .net core, vous pouvez spécifier le chemin d'accès complet à la vue.
return View("~/Views/booking/checkout.cshtml", checkoutRequest);
Vous allez avoir besoin d'un RazorviewEngine
personnalisé pour celui-ci.
Tout d'abord, le moteur:
public class CustomEngine : RazorViewEngine
{
private readonly string[] _customAreaFormats = new string[]
{
"/Views/{2}/{1}/{0}.cshtml"
};
public CustomEngine(
IRazorPageFactory pageFactory,
IRazorViewFactory viewFactory,
IOptions<RazorViewEngineOptions> optionsAccessor,
IViewLocationCache viewLocationCache)
: base(pageFactory, viewFactory, optionsAccessor, viewLocationCache)
{
}
public override IEnumerable<string> AreaViewLocationFormats =>
_customAreaFormats.Concat(base.AreaViewLocationFormats);
}
Cela créera un format de zone supplémentaire, qui correspond au cas d'utilisation de {areaName}/{controller}/{view}
.
Ensuite, enregistrez le moteur dans la méthode ConfigureServices
de Startup.cs
classe:
public void ConfigureServices(IServiceCollection services)
{
// Add custom engine (must be BEFORE services.AddMvc() call)
services.AddSingleton<IRazorViewEngine, CustomEngine>();
// Add framework services.
services.AddMvc();
}
Troisièmement, ajoutez le routage de zone à vos routes MVC, dans la méthode Configure
:
app.UseMvc(routes =>
{
// add area routes
routes.MapRoute(name: "areaRoute",
template: "{area:exists}/{controller}/{action}",
defaults: new { controller = "Home", action = "Index" });
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Enfin, changez votre classe ProductController
pour utiliser le AreaAttribute
:
[Area("admin")]
public class ProductController : Controller
{
public IActionResult Index()
{
return View();
}
}
Maintenant, votre structure d'application peut ressembler à ceci:
Donc, après avoir creusé, je pense avoir trouvé le problème sur un autre stackoverflow. J'ai eu le même problème, et lors de la copie dans le fichier ViewImports de la section non-zone, les liens ont commencé à fonctionner comme prévu.
Comme on le voit ici: l'aide de balise d'ancrage MVC Asp.Net core 2.0 ne fonctionne pas
L'autre solution était de copier au niveau de la vue:@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers