问题

关于自己写的ASP.NET MVC控件找不到视图的问题?

回答
这几天在捣鼓ASP.NET MVC,想着自己写点儿东西,造点儿轮子,提高开发效率。结果,刚写好一个自定义的控件,准备在视图里调用一下,就傻眼了——MVC告诉我找不到这个视图。折腾了大半天,才算是把这个问题给捋明白了。

首先,得搞清楚MVC里“控件”和“视图”是怎么回事。我们自己写的这个“控件”,其实本质上就是一块封装好的逻辑,它接收一些数据,然后返回一段HTML片段。在ASP.NET MVC里,这个逻辑通常会放在一个Controller的方法里,而那个返回的HTML片段,就由一个View来负责生成。所以,当你遇到“找不到视图”的问题时,最根本的原因就是MVC框架找不到和你的“控件”逻辑相匹配的那个View文件。

那MVC是怎么找到View的呢?这背后有一套很严谨的查找机制。当你在Controller里执行一个Action方法,并且这个方法返回一个`ViewResult`(或者其子类,比如`PartialViewResult`,这在我们自定义控件的场景下很常见),MVC就会去一系列预设好的位置去寻找对应的View文件。

这些预设好的位置,通常包括:

1. Views/[ControllerName]/[ActionName].cshtml (或 .vbhtml):这是最直接的,MVC会优先在这个Controller专属的文件夹下去找和当前Action同名的View文件。比如,如果你的Controller叫`MyCustomControlsController`,Action叫`RenderMyControl`,MVC就会先找`Views/MyCustomControls/RenderMyControl.cshtml`。
2. Views/Shared/[ActionName].cshtml (或 .vbhtml):如果在一个Controller专属的文件夹里没找到,MVC就会去全局共享的`Views/Shared`文件夹里找。这适合那些可以在多个Controller里复用的View。
3. Views/[ControllerName]/_ViewStart.cshtml (或 _ViewImports.cshtml):虽然不是直接找View文件,但这些文件会影响到View的查找和渲染过程,比如定义了布局页,或者导入了一些常用的命名空间。

现在,把我们自己的“控件”逻辑放进Controller,我们通常会写一个Action方法,这个方法会返回一个`PartialViewResult`,因为我们写的控件往往是嵌入到其他视图中的一部分,而不是一个完整的页面。

举个例子,我写了一个简单的“评价控件”,它需要显示星级和评论内容。我可能在 `ControlsController` 里写了这样一个Action:

```csharp
public class ControlsController : Controller
{
public PartialViewResult RenderStarRating(int rating, string comment)
{
// 这里可能会做一些数据的准备工作,
// 比如从数据库加载评论的详细信息
ViewBag.Rating = rating;
ViewBag.Comment = comment;
return PartialView(); // 关键在这里!
}
}
```

然后,我想在我的`ProductDetail.cshtml`视图里这样调用它:

```html
@Html.Action("RenderStarRating", "Controls", new { rating = 4, comment = "非常满意!" })
```

问题来了,当我运行到这里的时候,MVC找不到`RenderStarRating`对应的View。按照上面说的查找规则,它会先去 `Views/Controls/RenderStarRating.cshtml` 找。如果这个文件不存在,它会去 `Views/Shared/RenderStarRating.cshtml` 找。

那么,为什么MVC找不到呢?

有几种常见的情况:

View文件确实不存在:最直接的原因,你可能根本没创建`RenderStarRating.cshtml`这个文件。
View文件位置不对:你可能把`RenderStarRating.cshtml`放在了其他地方,比如项目根目录的`Controls`文件夹下,或者`Views/Shared`文件夹里,但MVC的默认查找路径没有覆盖到。
文件名拼写错误:View文件的名字必须严格匹配Action方法的名字,哪怕是一个字母的差别,MVC也认不出来。
PartialView()的参数问题:`PartialView()`方法有一个重载,可以接受View的名称作为参数。例如 `PartialView("MyCustomRatingView")`。如果你的View文件名不是`RenderStarRating.cshtml`,而是`MyCustomRatingView.cshtml`,那么你就必须在Action里明确指定这个名字。如果调用`PartialView()`时没有参数,MVC就会自动使用当前Action方法的名称来查找View。
MVC的View查找路径配置:虽然比较少见,但如果你对MVC的View查找路径做了自定义配置(例如通过`RazorViewEngine`的`ViewLocationFormats`属性),那么MVC的查找逻辑就会按照你的配置来。如果你的自定义配置有问题,也可能导致找不到。
Area问题:如果你的控件Controller被放在了一个Area里(比如`Areas/Admin/Controllers/ControlsController`),那么MVC的查找路径会变得更复杂,它会优先在`Areas/[AreaName]/Views/[ControllerName]/`目录下查找。你必须确保View文件放在了正确的Area目录结构下,比如`Areas/Admin/Views/Controls/RenderStarRating.cshtml`。

解决办法就是:

1. 创建正确的View文件:在你的项目目录结构下,找到 `Views/Controls/` 文件夹(或者 `Views/Shared/` 文件夹),然后创建一个名为 `RenderStarRating.cshtml` 的文件。
2. 编写View内容:在 `RenderStarRating.cshtml` 文件中,你需要写生成HTML的代码。由于我们通过`ViewBag`传递了数据,所以可以在View里这样访问:

```html

Rating: @ViewBag.Rating / 5

@ViewBag.Comment


@ 这里可以放星级图标,比如 Font Awesome 或者自定义的图片 @

@for (int i = 0; i < (int)ViewBag.Rating; i++) { }
@for (int i = 0; i < 5 (int)ViewBag.Rating; i++) { }


```
3. 检查Controller Action的返回:确保你的Controller Action `RenderStarRating` 返回的是 `PartialViewResult`,并且如果没有传递View名称参数,就意味着MVC会尝试查找同名的View文件。
4. 处理特殊情况(如Area):如果你的Controller在Area里,务必确认View文件的路径也遵循了Area的结构。
5. 明确指定View名称:如果你的View文件名和Action名不一致,或者你想把它放在一个非标准位置,最简单的办法就是在Action中明确指定:

```csharp
public PartialViewResult RenderStarRating(int rating, string comment)
{
ViewBag.Rating = rating;
ViewBag.Comment = comment;
// 明确指定View的名称和它所在的文件夹(相对于Views目录)
// 如果View在Views/Controls/RatingView.cshtml
return PartialView("RatingView");
// 如果View在Views/Shared/MySharedRating.cshtml
// return PartialView("~/Views/Shared/MySharedRating.cshtml");
}
```
这样,MVC就知道该去哪里找View了。

总而言之,当你遇到“找不到视图”的问题时,别慌,仔细检查一下你预期的View文件是否存在,它的名字对不对,以及它所在的位置是否符合MVC默认的查找规则。通常情况下,只要把View文件放到正确的位置,或者在Controller Action中明确指定View的路径,就能迎刃而解。自己封装控件,就是为了方便复用,而View的正确加载是这个复用流程的关键一环。

网友意见

user avatar

.Net MVC里的View应该是运行时编译的,所以你的控件项目生产的dll应该是不包含控件的View的,问题可能就是出在这里。

我还没有进行试验所以不能确定,不过如果控件项目预编译控件的View的话,应该是一种可行的办法。

-- 参考 --

ASP.NET MVC5 视图预编译 How to Load Views from Assembly in MVC
user avatar

@Blueve 的答案中的第二个链接看起来的确可以解决问题,原理其实是重写了虚拟文件系统(VirtualPathProvider)来获取文件内容,这样一来即使视图文件放在哪里都无所谓了。好处是改动范围极小,副作用也很小,但坏处是视图还是在运行时编译的,应该尽可能想办法在编译DLL的时候就把视图编译了。



2015年来补充一下,

我很高兴的在最新vNext版本的ASP.NET MVC中发现了嵌入式资源的虚拟路径提供程序(VirtualPathProvider),这说明这个问题有望在vNext的新版本中彻底得到解决。

类似的话题

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有