¿Existe una vista en ASP.NET MVC?

95

¿Es posible determinar si existe un nombre de vista específico desde dentro de un controlador antes de renderizar la vista?

Tengo el requisito de determinar dinámicamente el nombre de la vista a representar. Si existe una vista con ese nombre, entonces necesito renderizar esa vista. Si no hay vista por el nombre personalizado, entonces necesito representar una vista predeterminada.

Me gustaría hacer algo similar al siguiente código dentro de mi 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
fuente
14
Simplemente leyendo el título de esto, parece una pregunta filosófica muy profunda.

Respuestas:

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

Para aquellos que buscan un método de extensión de copiar / pegar:

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
fuente
2
Probablemente esto sea mejor. No sabía que había un método FindView fuera de la colección ViewEngines.
Lance Harper
1
Pero, ¿cómo verificar si la vista existe para otro controlador?
SOReader
Una especie de aparte: uno de nuestros ingenieros (desde que se mudó) construyó un motor de vista personalizado (llamado MultiTenantViewEngine, para que tenga una idea de su propósito) que implementa FindView para lanzar una HttpException (404) si no puede encontrar el dado ver. ¿Es esta una buena práctica? No tengo idea. Pero no se sorprendería si hubiera otras implementaciones como esa. Dado que no conocerá el funcionamiento interno del motor de visualización a medida que se ejecuta este código, es posible que desee lanzar un catch {return false; } alrededor de este cachorro, solo para estar seguro.
Brian Colavito
1
@SOReader, no he probado pero IController controller = new HomeController (); y luego controller.ControllerContext le dará lo que puede pasar a los métodos findview.
Vishal Sharma
Gracias por esta respuesta. Me ayudó en otro problema. Necesitaba verificar si mi vista es parcial o no y como el nombre de todos mis parciales comienza con subrayado, ahora puedo trabajar con mi solución verificando si "result.View! = Null"
Deise Vicentin
19

¿Qué tal si prueba algo como lo siguiente asumiendo que está utilizando un solo motor de visualización?

bool viewExists = ViewEngines.Engines[0].FindView(ControllerContext, "ViewName", "MasterName", false) != null;
Lance Harper
fuente
parece que este se publicó 3 minutos antes de la respuesta aceptada y, sin embargo, ¡¿no hay amor ?! +1 de mi parte.
Trevor de Koekkoek
@TrevordeKoekkoek ... hmmm ... + 1
Vishal Sharma
8

Aquí hay otra forma [no necesariamente recomendada] de hacerlo

 try
 {
     @Html.Partial("Category/SearchPanel/" + Model.CategoryKey)
 }
 catch (InvalidOperationException) { }
Simon_Weaver
fuente
esto es para probar la existencia de una vista parcial dentro de un archivo .cshtml. no es realmente una respuesta para esta pregunta, sino otra pregunta que los enlaces aquí se cerraron incorrectamente, así que dejo mi respuesta aquí
Simon_Weaver
2
Esto fue realmente acertado para mi uso, ya que estaba buscando una forma de usar una vista parcial específica de la cultura. Así que llamé a esto con el nombre de vista específico de la cultura y luego llamé a la vista predeterminada dentro de la captura. Y yo estaba haciendo esto en una función de utilidad, así que no tenía acceso a la ControllerContextque las FindViewnecesidades de método.
asombro
2

Si desea reutilizar esto en varias acciones del controlador, basándose en la solución proporcionada por Dave, puede definir un resultado de vista personalizado de la siguiente manera:

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

    ...
}

Luego, en su acción, simplemente devuelva una instancia de su vista personalizada:

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

Mis 2 centavos.

tynar
fuente
1

En asp.net core 2.x, la ViewEnginespropiedad ya no existe, así que tenemos que usar el ICompositeViewEngineservicio. Esta es una variante de la respuesta aceptada usando la inyección de dependencia:

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 los curiosos: La interfaz base IViewEngineno está registrada como un servicio por lo que debemos inyectar en su ICompositeViewEnginelugar. Sin FindView()embargo, el método lo proporciona IViewEnginepara que la variable miembro pueda usar la interfaz base.

idilov
fuente
0

Aquí se explica cómo hacerlo en Razor para Core 2.2, etc. Tenga en cuenta que la llamada es "GetView", no "Find View".

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