Existe um modo de exibição no ASP.NET MVC?

95

É possível determinar se um nome de visão específico existe de dentro de um controlador antes de renderizar a visão?

Tenho um requisito para determinar dinamicamente o nome da vista a ser renderizada. Se houver uma visualização com esse nome, preciso renderizá-la. Se não houver visualização com o nome personalizado, preciso renderizar uma visualização padrão.

Eu gostaria de fazer algo semelhante ao seguinte código no meu controlador:

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
fonte
14
Só de ler o título disso, parece uma questão filosófica muito profunda.

Respostas:

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

Para quem procura um método de extensão copiar / colar:

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
fonte
2
Provavelmente é melhor. Eu não sabia que havia um método FindView fora da coleção ViewEngines em si.
Lance Harper
1
Mas como verificar se a view existe para outro controlador?
SOReader de
Um pouco à parte: um de nossos engenheiros (desde então) construiu um mecanismo de visualização personalizado (chamado MultiTenantViewEngine, para que você tenha uma noção de seu propósito) que implementa FindView para lançar uma HttpException (404) se não conseguir encontrar o dado Visão. Esta é uma boa prática? Eu não faço ideia. Mas não ficaria surpreso se houver outras implementações como essa. Como você não conhecerá o funcionamento interno do mecanismo de visualização à medida que este código é executado, você pode desejar lançar um catch {return false; } em torno deste cachorrinho, só para garantir.
Brian Colavito
1
@SOReader, eu hvnt testado, mas, controlador IController = new HomeController (); e então controller.ControllerContext fornecerá o que você pode passar para os métodos findview.
Vishal Sharma
Obrigado por esta resposta. Isso me ajudou em outro problema. Eu precisava verificar se minha visão é parcial ou não e como o nome de todos os parciais começa com sublinhado agora posso trabalhar com minha solução verificando se "result.View! = Null"
Deise Vicentin
19

Que tal tentar algo como o seguinte, supondo que você esteja usando apenas um mecanismo de visualização:

bool viewExists = ViewEngines.Engines[0].FindView(ControllerContext, "ViewName", "MasterName", false) != null;
Lance Harper
fonte
parece que esse aqui foi postado 3 minutos antes da resposta aceita e mesmo assim sem amor? +1 de mim.
Trevor de Koekkoek
@TrevordeKoekkoek ... hmmm ... + 1
Vishal Sharma
8

Aqui está outra maneira [não necessariamente recomendada] de fazer isso

 try
 {
     @Html.Partial("Category/SearchPanel/" + Model.CategoryKey)
 }
 catch (InvalidOperationException) { }
Simon_Weaver
fonte
isso é para testar a existência de uma visualização parcial em um arquivo .cshtml. não é realmente uma resposta para esta pergunta, mas outra pergunta que links aqui foi fechada incorretamente, então estou deixando minha resposta aqui
Simon_Weaver
2
Isso foi realmente adequado para o meu uso, já que eu estava procurando uma maneira de usar uma visão parcial específica da cultura. Então, chamei isso apenas com o nome de exibição específico da cultura e, em seguida, chamei a exibição padrão dentro da captura. E eu estava fazendo isso em uma função de utilidade, então não tive acesso ao ControllerContextconforme o FindViewmétodo precisa.
admirável
2

Se quiser reutilizar isso em várias ações do controlador, com base na solução fornecida por Dave, você pode definir um resultado de visualização personalizado da seguinte maneira:

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);
    }

    ...
}

Então, em sua ação, basta retornar uma instância de sua visualização personalizada:

public ActionResult Index()
{ 
    return new CustomViewResult();
}
DSO
fonte
1
ViewEngines.Engines.FindView(ViewContext.Controller.ControllerContext, "View Name").View != null

Meus 2 centavos.

tynar
fonte
1

No asp.net core 2.x o ViewEngines propriedade não existe mais, então temos que usar o ICompositeViewEngineserviço. Esta é uma variante da resposta aceita usando injeção de dependência:

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() ...
}

Para os curiosos: A interface base IViewEnginenão está registrada como um serviço, então devemos injetar em seu ICompositeViewEnginelugar. O FindView()método, entretanto, é fornecido por, IViewEngineportanto, a variável de membro pode usar a interface base.

idilov
fonte
0

Veja como fazer isso no Razor for Core 2.2 etc. Observe que a chamada é "GetView", não "Find View)

@using Microsoft.AspNetCore.Mvc.ViewEngines
@inject ICompositeViewEngine Engine
...
@if (Engine.GetView(scriptName, scriptName, isMainPage: false).Success) 
{
    @await Html.PartialAsync(scriptName)
}
philw
fonte