Czy widok istnieje w ASP.NET MVC?

95

Czy można określić, czy określona nazwa widoku istnieje z poziomu kontrolera przed renderowaniem widoku?

Mam wymaganie, aby dynamicznie określić nazwę widoku do renderowania. Jeśli istnieje widok o tej nazwie, muszę go wyrenderować. Jeśli nie ma widoku o niestandardowej nazwie, muszę renderować widok domyślny.

Chciałbym zrobić coś podobnego do następującego kodu w moim kontrolerze:

public ActionResult Index()
{
    var name = SomeMethodToGetViewName();

    // The 'ViewExists' method is what I've been unable to find.
    if (ViewExists(name))
    {
        retun View(name);
    }
    else
    {
        return View();
    }
}
Andrew Hanson
źródło
14
Samo przeczytanie tytułu tego wydaje się być bardzo głębokim pytaniem filozoficznym.

Odpowiedzi:

154
 private bool ViewExists(string name)
 {
     ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext, name, null);
     return (result.View != null);
 }

Dla tych, którzy szukają metody rozszerzenia kopiuj / wklej:

public static class ControllerExtensions
{
    public static bool ViewExists(this Controller controller, string name)
    {
        ViewEngineResult result = ViewEngines.Engines.FindView(controller.ControllerContext, name, null);
        return (result.View != null);
    }
}
Dave Cluderay
źródło
2
To jest prawdopodobnie lepsze. Nie wiedziałem, że istnieje metoda FindView z samej kolekcji ViewEngines.
Lance Harper
1
Ale jak sprawdzić, czy widok istnieje dla innego kontrolera?
SOReader
Coś na marginesie: jeden z naszych inżynierów (od czasu przeniesienia) zbudował niestandardowy silnik widoku (zwany MultiTenantViewEngine, dzięki czemu można zrozumieć jego cel), który implementuje FindView, aby zgłosić HttpException (404), jeśli nie może znaleźć danego widok. Czy to dobra praktyka? Nie mam pojęcia. Ale nie zdziwiłbym się, gdyby istniały inne takie implementacje. Ponieważ nie znasz wewnętrznego działania silnika widoku podczas wykonywania tego kodu, możesz chcieć rzucić catch {return false; } wokół tego szczeniaka, żeby być bezpiecznym.
Brian Colavito
1
@SOReader, hvnt testowałem, ale kontroler IController = new HomeController (); a następnie controller.ControllerContext da to, co możesz przekazać do metod findview.
Vishal Sharma
Dzięki za tę odpowiedź. Pomogło mi to w innym problemie. Musiałem sprawdzić, czy mój widok jest częściowy, czy nie, a ponieważ wszystkie nazwy moich stron podrzędnych zaczynają się od podkreślenia, mogę teraz pracować z moim rozwiązaniem, sprawdzając, czy "result.View! = Null"
Deise Vicentin
19

A co z wypróbowaniem czegoś podobnego do następującego, zakładając, że używasz tylko jednego silnika widoku:

bool viewExists = ViewEngines.Engines[0].FindView(ControllerContext, "ViewName", "MasterName", false) != null;
Lance Harper
źródło
wygląda na to, że ten został opublikowany 3 minuty przed zaakceptowaną odpowiedzią, ale nie ma miłości ?! +1 ode mnie.
Trevor de Koekkoek,
@TrevordeKoekkoek ... hmmm ... + 1
Vishal Sharma,
8

Oto inny (niekoniecznie zalecany) sposób na zrobienie tego

 try
 {
     @Html.Partial("Category/SearchPanel/" + Model.CategoryKey)
 }
 catch (InvalidOperationException) { }
Simon_Weaver
źródło
służy to do testowania istnienia częściowego widoku w pliku .cshtml. tak naprawdę nie jest to odpowiedź na to pytanie, ale inne pytanie, do którego linki tutaj zostały nieprawidłowo zamknięte, więc zostawiam swoją odpowiedź tutaj
Simon_Weaver
2
To było dla mnie trafne, ponieważ szukałem sposobu na wykorzystanie częściowego widoku specyficznego dla kultury. Więc nazwałem to po prostu nazwą widoku specyficzną dla kultury, a następnie wywołałem widok domyślny wewnątrz catch. I robił to w funkcji użyteczności, więc nie miałem dostępu do ControllerContextjak FindViewpotrzeby metod.
awe
2

Jeśli chcesz ponownie użyć tego w wielu akcjach kontrolera, opierając się na rozwiązaniu podanym przez Dave'a, możesz zdefiniować wynik widoku niestandardowego w następujący sposób:

public class CustomViewResult : ViewResult
{
    protected override ViewEngineResult FindView(ControllerContext context)
    {
        string name = SomeMethodToGetViewName();

        ViewEngineResult result = ViewEngines.Engines.FindView(context, name, null);

        if (result.View != null)
        {
            return result;
        }

        return base.FindView(context);
    }

    ...
}

Następnie w akcji po prostu zwróć instancję widoku niestandardowego:

public ActionResult Index()
{ 
    return new CustomViewResult();
}
DSO
źródło
1
ViewEngines.Engines.FindView(ViewContext.Controller.ControllerContext, "View Name").View != null

Moje 2 centy.

tynar
źródło
1

W asp.net core 2.x ViewEngineswłaściwość już nie istnieje, więc musimy skorzystać z ICompositeViewEngineusługi. Oto wariant akceptowanej odpowiedzi przy użyciu wstrzykiwania zależności:

public class DemoController : Controller
{
    private readonly IViewEngine _viewEngine;

    public DemoController(ICompositeViewEngine viewEngine)
    {
        _viewEngine = viewEngine;
    }

    private bool ViewExists(string name)
    {
        ViewEngineResult viewEngineResult = _viewEngine.FindView(ControllerContext, name, true);
        return viewEngineResult?.View != null;
    }

    public ActionResult Index() ...
}

Dla ciekawskich: interfejs podstawowy IViewEnginenie jest zarejestrowany jako usługa, więc ICompositeViewEnginezamiast tego musimy wstrzyknąć . FindView()Metoda jest jednak zapewnia IViewEnginewięc zmienny element może używać interfejsu bazową.

idiłow
źródło
0

Oto jak to zrobić w Razor dla Core 2.2 itp. Zwróć uwagę, że wywołanie to „GetView”, a nie „Find View”)

@using Microsoft.AspNetCore.Mvc.ViewEngines
@inject ICompositeViewEngine Engine
...
@if (Engine.GetView(scriptName, scriptName, isMainPage: false).Success) 
{
    @await Html.PartialAsync(scriptName)
}
philw
źródło