Posso especificar um local personalizado para “pesquisar visualizações” na ASP.NET MVC?

105

Tenho o seguinte layout para meu projeto mvc:

  • / Controladores
    • / Demo
    • / Demo / DemoArea1Controller
    • / Demo / DemoArea2Controller
    • etc ...
  • /Visualizações
    • / Demo
    • /Demo/DemoArea1/Index.aspx
    • /Demo/DemoArea2/Index.aspx

No entanto, quando tenho isso para DemoArea1Controller:

public class DemoArea1Controller : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}

Recebo o erro "O 'índice' da visualização ou seu mestre não foi encontrado", com os locais de pesquisa usuais.

Como posso especificar os controladores na pesquisa de namespace "Demo" na subpasta de exibição "Demo"?

Daniel Schaffer
fonte
Aqui está outra amostra de um ViewEngine simples do aplicativo MVC Commerce de Rob Connery: View Engine Code E o código Global.asax.cs para definir o ViewEngine: Global.asax.cs Espero que isso ajude.
Robert Dean,

Respostas:

121

Você pode facilmente estender o WebFormViewEngine para especificar todos os locais que deseja examinar:

public class CustomViewEngine : WebFormViewEngine
{
    public CustomViewEngine()
    {
        var viewLocations =  new[] {  
            "~/Views/{1}/{0}.aspx",  
            "~/Views/{1}/{0}.ascx",  
            "~/Views/Shared/{0}.aspx",  
            "~/Views/Shared/{0}.ascx",  
            "~/AnotherPath/Views/{0}.ascx"
            // etc
        };

        this.PartialViewLocationFormats = viewLocations;
        this.ViewLocationFormats = viewLocations;
    }
}

Certifique-se de registrar o mecanismo de visualização, modificando o método Application_Start em seu Global.asax.cs

protected void Application_Start()
{
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new CustomViewEngine());
}
Sam Wessel
fonte
Como você pode acessar o caminho de uma página mestra a partir de uma página mestra aninhada? Como na configuração do layout da página mestra aninhada para pesquisar nos caminhos do CustomViewEngine
Drahcir
6
Não é melhor pular a limpeza dos motores já registrados e apenas adicionar o novo e viewLocations terá apenas os novos?
Prasanna de
3
Implementações sem ViewEngines.Engines.Clear (); Tudo funciona bem. Se você quiser usar * .cshtml, você deve herdar de RazorViewEngine
KregHEk
Existe alguma maneira de vincular as opções "adicionar visualização" e "ir para visualização" dos controladores aos novos locais de visualização? Estou usando o Visual Studio 2012
Neville Nazerane
Conforme mencionado por @Prasanna, não há necessidade de limpar os motores existentes para adicionar novos locais, veja esta resposta para mais detalhes.
Hooman Bahreini
44

Agora no MVC 6 você pode implementar a IViewLocationExpanderinterface sem mexer com os motores de visualização:

public class MyViewLocationExpander : IViewLocationExpander
{
    public void PopulateValues(ViewLocationExpanderContext context) {}

    public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
    {
        return new[]
        {
            "/AnotherPath/Views/{1}/{0}.cshtml",
            "/AnotherPath/Views/Shared/{0}.cshtml"
        }; // add `.Union(viewLocations)` to add default locations
    }
}

onde {0}é o nome da visualização de destino, {1}- nome do controlador e {2}- nome da área.

Você pode retornar sua própria lista de locais, mesclá-la com o padrão viewLocations( .Union(viewLocations)) ou apenas alterá-los ( viewLocations.Select(path => "/AnotherPath" + path)).

Para registrar seu expansor de localização de visualização personalizada no MVC, adicione as próximas linhas ao ConfigureServicesmétodo no Startup.csarquivo:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<RazorViewEngineOptions>(options =>
    {
        options.ViewLocationExpanders.Add(new MyViewLocationExpander());
    });
}
Whyleee
fonte
3
Eu gostaria de poder votar 10 votos. É exatamente o que é necessário no Asp.net 5 / MVC 6. Lindo. Muito útil no meu caso (e em outros) quando você deseja agrupar áreas em superáreas para sites maiores ou agrupamentos lógicos.
desenhou em
A parte Startup.cs deve ser: services.Configure <RazorViewEngineOptions> Vai neste método: public void ConfigureServices (IServiceCollection services)
OrangeKing89
42

Na verdade, há um método muito mais fácil do que codificar os caminhos em seu construtor. Abaixo está um exemplo de extensão do mecanismo Razor para adicionar novos caminhos. Uma coisa que não tenho certeza é se os caminhos que você adicionar aqui serão armazenados em cache:

public class ExtendedRazorViewEngine : RazorViewEngine
{
    public void AddViewLocationFormat(string paths)
    {
        List<string> existingPaths = new List<string>(ViewLocationFormats);
        existingPaths.Add(paths);

        ViewLocationFormats = existingPaths.ToArray();
    }

    public void AddPartialViewLocationFormat(string paths)
    {
        List<string> existingPaths = new List<string>(PartialViewLocationFormats);
        existingPaths.Add(paths);

        PartialViewLocationFormats = existingPaths.ToArray();
    }
}

E seu Global.asax.cs

protected void Application_Start()
{
    ViewEngines.Engines.Clear();

    ExtendedRazorViewEngine engine = new ExtendedRazorViewEngine();
    engine.AddViewLocationFormat("~/MyThemes/{1}/{0}.cshtml");
    engine.AddViewLocationFormat("~/MyThemes/{1}/{0}.vbhtml");

    // Add a shared location too, as the lines above are controller specific
    engine.AddPartialViewLocationFormat("~/MyThemes/{0}.cshtml");
    engine.AddPartialViewLocationFormat("~/MyThemes/{0}.vbhtml");

    ViewEngines.Engines.Add(engine);

    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
}

Uma coisa a ser observada: seu local personalizado precisará do arquivo ViewStart.cshtml em sua raiz.

Chris S
fonte
23

Se você deseja apenas adicionar novos caminhos, pode adicionar aos mecanismos de visualização padrão e poupar algumas linhas de código:

ViewEngines.Engines.Clear();
var razorEngine = new RazorViewEngine();
razorEngine.MasterLocationFormats = razorEngine.MasterLocationFormats
      .Concat(new[] { 
          "~/custom/path/{0}.cshtml" 
      }).ToArray();

razorEngine.PartialViewLocationFormats = razorEngine.PartialViewLocationFormats
      .Concat(new[] { 
          "~/custom/path/{1}/{0}.cshtml",   // {1} = controller name
          "~/custom/path/Shared/{0}.cshtml" 
      }).ToArray();

ViewEngines.Engines.Add(razorEngine);

O mesmo se aplica a WebFormEngine

Marcelo De Zen
fonte
2
Para Visualizações: use razorEngine.ViewLocationFormats.
Aldentev
13

Em vez de subclassificar o RazorViewEngine, ou substituí-lo imediatamente, você pode apenas alterar a propriedade PartialViewLocationFormats existente do RazorViewEngine. Este código vai em Application_Start:

System.Web.Mvc.RazorViewEngine rve = (RazorViewEngine)ViewEngines.Engines
  .Where(e=>e.GetType()==typeof(RazorViewEngine))
  .FirstOrDefault();

string[] additionalPartialViewLocations = new[] { 
  "~/Views/[YourCustomPathHere]"
};

if(rve!=null)
{
  rve.PartialViewLocationFormats = rve.PartialViewLocationFormats
    .Union( additionalPartialViewLocations )
    .ToArray();
}
Simon Giles
fonte
2
Isso funcionou para mim, com a exceção de que o tipo de motor de barbear era 'FixedRazorViewEngine' em vez de 'RazorViewEngine'. Além disso, lanço uma exceção se o mecanismo não for encontrado, pois impede que meu aplicativo seja inicializado com êxito.
Rob
3

Da última vez que verifiquei, isso requer que você crie seu próprio ViewEngine. Não sei se eles tornaram mais fácil no RC1.

A abordagem básica que usei antes do primeiro RC foi, em meu próprio ViewEngine, dividir o namespace do controlador e procurar por pastas que correspondessem às partes.

EDITAR:

Voltei e encontrei o código. Aqui está a ideia geral.

public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName)
{
    string ns = controllerContext.Controller.GetType().Namespace;
    string controller = controllerContext.Controller.GetType().Name.Replace("Controller", "");

    //try to find the view
    string rel = "~/Views/" +
        (
            ns == baseControllerNamespace ? "" :
            ns.Substring(baseControllerNamespace.Length + 1).Replace(".", "/") + "/"
        )
        + controller;
    string[] pathsToSearch = new string[]{
        rel+"/"+viewName+".aspx",
        rel+"/"+viewName+".ascx"
    };

    string viewPath = null;
    foreach (var path in pathsToSearch)
    {
        if (this.VirtualPathProvider.FileExists(path))
        {
            viewPath = path;
            break;
        }
    }

    if (viewPath != null)
    {
        string masterPath = null;

        //try find the master
        if (!string.IsNullOrEmpty(masterName))
        {

            string[] masterPathsToSearch = new string[]{
                rel+"/"+masterName+".master",
                "~/Views/"+ controller +"/"+ masterName+".master",
                "~/Views/Shared/"+ masterName+".master"
            };


            foreach (var path in masterPathsToSearch)
            {
                if (this.VirtualPathProvider.FileExists(path))
                {
                    masterPath = path;
                    break;
                }
            }
        }

        if (string.IsNullOrEmpty(masterName) || masterPath != null)
        {
            return new ViewEngineResult(
                this.CreateView(controllerContext, viewPath, masterPath), this);
        }
    }

    //try default implementation
    var result = base.FindView(controllerContext, viewName, masterName);
    if (result.View == null)
    {
        //add the location searched
        return new ViewEngineResult(pathsToSearch);
    }
    return result;
}
Joel
fonte
1
Na verdade, é muito mais fácil. Subclasse WebFormsViewEngine e, em seguida, apenas adicione à matriz de caminhos que já procura em seu construtor.
Craig Stuntz
Bom saber. A última vez que precisei modificar essa coleção, não foi possível dessa maneira.
Joel
Vale a pena mencionar que você precisa definir a variável "baseControllerNamespace" para o namespace do seu controlador de base (por exemplo, "Project.Controllers"), mas de outra forma fez exatamente o que eu precisava, 7 anos após ser postado.
protótipo 14
3

Experimente algo assim:

private static void RegisterViewEngines(ICollection<IViewEngine> engines)
{
    engines.Add(new WebFormViewEngine
    {
        MasterLocationFormats = new[] {"~/App/Views/Admin/{0}.master"},
        PartialViewLocationFormats = new[] {"~/App/Views/Admin//{1}/{0}.ascx"},
        ViewLocationFormats = new[] {"~/App/Views/Admin//{1}/{0}.aspx"}
    });
}

protected void Application_Start()
{
    RegisterViewEngines(ViewEngines.Engines);
}
Vitaliy Ulantikov
fonte
3

Nota: para ASP.NET MVC 2, eles têm caminhos de localização adicionais que você precisará definir para visualizações em 'Áreas'.

 AreaViewLocationFormats
 AreaPartialViewLocationFormats
 AreaMasterLocationFormats

A criação de um mecanismo de visualização para uma área é descrita no blog de Phil .

Nota: Este é para a versão prévia 1, portanto, está sujeito a alterações.

Simon_Weaver
fonte
1

A maioria das respostas aqui, limpe os locais existentes chamando ViewEngines.Engines.Clear()e, em seguida, adicione-os novamente ... não há necessidade de fazer isso.

Podemos simplesmente adicionar os novos locais aos existentes, conforme mostrado abaixo:

// note that the base class is RazorViewEngine, NOT WebFormViewEngine
public class ExpandedViewEngine : RazorViewEngine
{
    public ExpandedViewEngine()
    {
        var customViewSubfolders = new[] 
        {
            // {1} is conroller name, {0} is action name
            "~/Areas/AreaName/Views/Subfolder1/{1}/{0}.cshtml",
            "~/Areas/AreaName/Views/Subfolder1/Shared/{0}.cshtml"
        };

        var customPartialViewSubfolders = new[] 
        {
            "~/Areas/MyAreaName/Views/Subfolder1/{1}/Partials/{0}.cshtml",
            "~/Areas/MyAreaName/Views/Subfolder1/Shared/Partials/{0}.cshtml"
        };

        ViewLocationFormats = ViewLocationFormats.Union(customViewSubfolders).ToArray();
        PartialViewLocationFormats = PartialViewLocationFormats.Union(customPartialViewSubfolders).ToArray();

        // use the following if you want to extend the master locations
        // MasterLocationFormats = MasterLocationFormats.Union(new[] { "new master location" }).ToArray();   
    }
}

Agora você pode configurar seu projeto para usar o acima RazorViewEngineem Global.asax:

protected void Application_Start()
{
    ViewEngines.Engines.Add(new ExpandedViewEngine());
    // more configurations
}

Veja este tutorial para mais informações.

Hooman Bahreini
fonte