.NET Core 的内容处处可见,刷爆全球各大社区,所以,老周相信各位大伙伴已经看得不少了,故而,老周不考虑一个个知识点地去写,那样会成为年度最大的屁话,何况官方文档也很详尽。老周主要扯一下大伙伴们在入门的时候可能会疑惑的内容。

ASP.NET Core 可以在一个项目中混合使用 Web Pages 和 MVC ,这是老周最希望的,因为这样会变得更灵活。Web Pages 类似于我们过去的 Web 开发方式,以页面为单位,此模型侧重于功能划分。而 MVC 侧重于数据,有什么样的数据模型就有什么样的 Controller,有什么样的 Controller 就会对应什么样的 Action ,而 Action 又会有对应的 UI,即 View。所以说 MVC 是以数据为核心的。

如果两者可以同时使用,那在我的项目中,可能有些内容以功能为重点,而另一些内容是以数据为中心的,这样可以灵活地交替使用,因此,老周向来最喜欢空项目模板,因为空的什么都没有,什么都没有才能做到什么都有。大概,老庄所说的“无”,与佛家所说的“空”,就是这样的。

Web Pages 和 MVC 可以一起用,是因为它们的配置方法是一样的,在 Startup 类中,有两个约定的方法。

ConfigureServices 方法是告诉应用程序我要用到哪些功能,Service 是用来扩展的,你自己也可以编写各种功能,然后添加到 services 集合中就好了。不管是 W  eb Pages 还是 MVC ,都是添加这一行代码

        public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}

估计大家会发现,除了 AddMvc 方法外,还有一个 AddMvcCore 方法,你一定会有疑问,这两个家伙一家吗?于是,你会尝试一下把 AddMvc 换成 AddMvcCore ,然后运行时你会发现找不到视图。

带 Core 结尾的方法,只添加核心的功能,并非 MVC 所需的必备功能,此方法也许更适合 Web API,但即便我们写的是 API 项目,我们也极少用这个方法,所以,在实际开发中,你可以直接无视 AddMvcCore 方法。

那么,这哥儿俩到底有啥不同呢。咱们不妨看看源代码。AddMvcCore 主要添加了以下功能。

            //
// Options
//
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcCoreMvcOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IPostConfigureOptions<MvcOptions>, MvcOptionsConfigureCompatibilityOptions>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<ApiBehaviorOptions>, ApiBehaviorOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<RouteOptions>, MvcCoreRouteOptionsSetup>()); //
// Action Discovery
//
// These are consumed only when creating action descriptors, then they can be deallocated services.TryAddEnumerable(
ServiceDescriptor.Transient<IApplicationModelProvider, DefaultApplicationModelProvider>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IApplicationModelProvider, ApiBehaviorApplicationModelProvider>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IActionDescriptorProvider, ControllerActionDescriptorProvider>()); services.TryAddSingleton<IActionDescriptorCollectionProvider, ActionDescriptorCollectionProvider>(); //
// Action Selection
//
services.TryAddSingleton<IActionSelector, ActionSelector>();
services.TryAddSingleton<ActionConstraintCache>(); // Will be cached by the DefaultActionSelector
services.TryAddEnumerable(
ServiceDescriptor.Transient<IActionConstraintProvider, DefaultActionConstraintProvider>()); //
// Controller Factory
//
// This has a cache, so it needs to be a singleton
services.TryAddSingleton<IControllerFactory, DefaultControllerFactory>(); // Will be cached by the DefaultControllerFactory
services.TryAddTransient<IControllerActivator, DefaultControllerActivator>(); services.TryAddSingleton<IControllerFactoryProvider, ControllerFactoryProvider>();
services.TryAddSingleton<IControllerActivatorProvider, ControllerActivatorProvider>();
services.TryAddEnumerable(
ServiceDescriptor.Transient<IControllerPropertyActivator, DefaultControllerPropertyActivator>()); //
// Action Invoker
//
// The IActionInvokerFactory is cachable
services.TryAddSingleton<IActionInvokerFactory, ActionInvokerFactory>();
services.TryAddEnumerable(
ServiceDescriptor.Transient<IActionInvokerProvider, ControllerActionInvokerProvider>()); // These are stateless
services.TryAddSingleton<ControllerActionInvokerCache>();
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IFilterProvider, DefaultFilterProvider>()); //
// Request body limit filters
//
services.TryAddTransient<RequestSizeLimitFilter>();
services.TryAddTransient<DisableRequestSizeLimitFilter>();
services.TryAddTransient<RequestFormLimitsFilter>(); // Error description
services.TryAddSingleton<IErrorDescriptionFactory, DefaultErrorDescriptorFactory>(); //
// ModelBinding, Validation
//
// The DefaultModelMetadataProvider does significant caching and should be a singleton.
services.TryAddSingleton<IModelMetadataProvider, DefaultModelMetadataProvider>();
services.TryAdd(ServiceDescriptor.Transient<ICompositeMetadataDetailsProvider>(s =>
{
var options = s.GetRequiredService<IOptions<MvcOptions>>().Value;
return new DefaultCompositeMetadataDetailsProvider(options.ModelMetadataDetailsProviders);
}));
services.TryAddSingleton<IModelBinderFactory, ModelBinderFactory>();
services.TryAddSingleton<IObjectModelValidator>(s =>
{
var options = s.GetRequiredService<IOptions<MvcOptions>>().Value;
var metadataProvider = s.GetRequiredService<IModelMetadataProvider>();
return new DefaultObjectValidator(metadataProvider, options.ModelValidatorProviders);
});
services.TryAddSingleton<ClientValidatorCache>();
services.TryAddSingleton<ParameterBinder>(s =>
{
var options = s.GetRequiredService<IOptions<MvcOptions>>().Value;
var loggerFactory = s.GetRequiredService<ILoggerFactory>();
var metadataProvider = s.GetRequiredService<IModelMetadataProvider>();
var modelBinderFactory = s.GetRequiredService<IModelBinderFactory>();
var modelValidatorProvider = new CompositeModelValidatorProvider(options.ModelValidatorProviders);
return new ParameterBinder(metadataProvider, modelBinderFactory, modelValidatorProvider, loggerFactory);
}); //
// Random Infrastructure
//
services.TryAddSingleton<MvcMarkerService, MvcMarkerService>();
services.TryAddSingleton<ITypeActivatorCache, TypeActivatorCache>();
services.TryAddSingleton<IUrlHelperFactory, UrlHelperFactory>();
services.TryAddSingleton<IHttpRequestStreamReaderFactory, MemoryPoolHttpRequestStreamReaderFactory>();
services.TryAddSingleton<IHttpResponseStreamWriterFactory, MemoryPoolHttpResponseStreamWriterFactory>();
services.TryAddSingleton(ArrayPool<byte>.Shared);
services.TryAddSingleton(ArrayPool<char>.Shared);
services.TryAddSingleton<OutputFormatterSelector, DefaultOutputFormatterSelector>();
services.TryAddSingleton<IActionResultExecutor<ObjectResult>, ObjectResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<PhysicalFileResult>, PhysicalFileResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<VirtualFileResult>, VirtualFileResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<FileStreamResult>, FileStreamResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<FileContentResult>, FileContentResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<RedirectResult>, RedirectResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<LocalRedirectResult>, LocalRedirectResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<RedirectToActionResult>, RedirectToActionResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<RedirectToRouteResult>, RedirectToRouteResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<RedirectToPageResult>, RedirectToPageResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<ContentResult>, ContentResultExecutor>(); //
// Route Handlers
//
services.TryAddSingleton<MvcRouteHandler>(); // Only one per app
services.TryAddTransient<MvcAttributeRouteHandler>(); // Many per app //
// Middleware pipeline filter related
//
services.TryAddSingleton<MiddlewareFilterConfigurationProvider>();
// This maintains a cache of middleware pipelines, so it needs to be a singleton
services.TryAddSingleton<MiddlewareFilterBuilder>();

代码很长,看不懂也没关系,反正你知道它添加这么一堆核心功能。

我们再来看看 AddMvc 方法。

            var builder = services.AddMvcCore();

            builder.AddApiExplorer();
builder.AddAuthorization(); AddDefaultFrameworkParts(builder.PartManager); // Order added affects options setup order // Default framework order
builder.AddFormatterMappings();
builder.AddViews();
builder.AddRazorViewEngine();
builder.AddRazorPages();
builder.AddCacheTagHelper(); // +1 order
builder.AddDataAnnotations(); // +1 order // +10 order
builder.AddJsonFormatters(); builder.AddCors();

注意这句:

   var builder = services.AddMvcCore();

这说明,运行时是先调用 AddMvcCore 方法添加核心的功能后,再添加 MVC 所必备的其他功能。尤其是下面这几行,很重要。

            builder.AddViews();
builder.AddRazorViewEngine();
builder.AddRazorPages();

现在你明白为什么调用 AddMvcCore 方法后会找不到视图的原因了吧。

扯远了,咱们还是回到 Startup 类来,弄完 ConfigureServices 方法后,还要在 Configure 方法中 use 一下。

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
} app.UseMvc();
}

别以为在 services 上面 add 完后就能用,那是两回事,services 集合仅仅说明添加功能,并不代表启用功能,UseMvc 是告诉应用程序在接收到 HTTP 请求后用 MVC 方式进行处理,些时相关的功能才会以中间件的形式插入到 HTTP 处理管道中。

你可以把 HTTP 处理管道看作一个生产线,而 services 集合中添加的内容相当于采购,我生产过程用到锄头,你帮我买,我用到馒头,你帮我买,我用到铁钳,你帮我买。至于说你买来后怎么用,用多少,那是生产线上的事情了。

你可以把 ConfigureServices 方法看作是买菜,把 Configure 方法看作是下厨。

这里顺便废话一下,Startup 类你是可以改为其他名字的,比如叫 MyStart,然后在 Main 入口处改一下 UseStartup 就行了。

            WebHost.CreateDefaultBuilder(args)
.UseStartup<MyStart>()
.Build();

运行的时候,程序会优先查找 Startup 这个名字,如果找不到再找其他的,所以,这个类名没必要改,这样还能减少程序查找的成本,反正你改了名字也没什么实际意义的,还是按照约定来吧。ConfigureServices 方法和 Configure 方法你是不能改的,因为程序会通过反射来找这两个方法。

说了那么多,下面进入咱们主题,我们知道,默认的约会是把视图页面放到 /Views 目录下的,并且按照 Controller 的名字建立子目录,以 Action 的名字来命名页面文件。

比如,有个 Controller 叫 Home ,里面有个 Action 叫 Test,那么默认的视图应该是这样的。

  /Views
|--- /Home
|--- /Test.cshtml

注意文件与目录名是严格区分大小写的,如果 Controller 是 Demo,你的目录是 demo ,是找不到视图,尤其是在 Linux 等系统上运行时,更加要严格遵守大小写的规则。

有时候,老周会觉得这样的路径不爽,目录层次套得多,老周喜欢对页面文件这样命名:Controller-Action.cshtml。例如,Controller 叫 Home,其中一个 Action 叫 Index ,那么视图页的名字就是 Home-Index.cshtml。

那么,我们该怎么修改默认的视图查找位置呢。不急,先来看看人家默认的视图查找位置。在 Configure 方法中加入以下代码。

            //app.UseMvc();
app.Run(async context =>
{
// 取出选项实例
IOptions<RazorViewEngineOptions> razoropt = app.ApplicationServices.GetService<IOptions<RazorViewEngineOptions>>();
var locations = razoropt.Value.ViewLocationFormats;
StringBuilder strbd = new StringBuilder();
foreach (var item in locations)
{
strbd.AppendLine(item);
}
// 这一行不要少,少了会乱码
context.Response.ContentType = "text/plain;charset=utf-8";
await context.Response.WriteAsync($"视图的默认查找位置:\n{strbd}");
});

这里要注意一个代码约定,services 集合添加功能时,经常会附带各种选项类,而为了便于识别,选项类通常是以 Options 结尾,比如,上面代码中的 RazorViewEngineOptions。

还记得上面老周贴的源代码吗,在 AddMvc 方法中有这一句:

builder.AddRazorViewEngine();

这会使得 RazorViewEngineOptions 类的实例被加入到依赖注入的列表中,而 services 集合所添加的各种东东会合并到 app.ApplicationServices 属性上,所以,我们通过这个属性可以取出 RazorViewEngineOptions 实例,但是,你要记得:凡是选项配置类都是用 IOptions<TOptions> 泛型对象来包装,虽然它是个接口,其实现类型也许在这里。

依赖注入类型在注册时往往是以接口类型为 key ,这样一来我们无需考虑它有哪些实现类型,只要统一用 IOptions 接口就能获取对应的选项类实例。

所以你要记住这个约定,选项类都用 IOptiions<TOptions> 类型来包装,并且其 Value 属性中获取选项类的实例,这种约定也是为了区分类型的用途,因为所有类型都可以加入依赖注入列表中的,只有带 IOption 包装的才是选项类。

要自定义视图的查找方法,你不必要实现 IViewLocationExpander 接口,你只需要修改 RazorViewEngineOptions 类的以下三个属性即可:

1、PageViewLocationFormats:专用于 Web Pages 模型,定义查找 Razor 页面的查找位置。

2、AreaViewLocationFormats:定义带 area 的 MVC 模型的 View 页面位置。这个也许你有些陌生,一般 MVC 应用我们少加 area,它的作用可以将 MVC 模型进行分组,比如 admin 组中有 MVC,users 组中也有 MVC,只是前者不能随便访问。

3、ViewLocationFormats:这是咱们今天的重点,也是最常用的。用于定义视图的查找位置。

这些属性都是字符列表,可以动态增减。现在我们运行应用,看看上面的代码所输出的内容。

我们看到,默认主要查找两个目录,Views 和它的子目录 Shared。

这时候,你注意到,路径中有参数,{1} 表示 Controller 名称,{0} 表示 Action 名称。如果 Controller = Home, Action = Index,那么,查找的视图页就是 /Views/Home/Index.cshtml。

可能你又要问了,为什么参数 0 是 Action名,参数 1 是 Controller名呢,这顺序怎么是反过来的?对的,如果有 Area 的话,路径就可以变成 /{2}/Views/{1}/{0}.cshtml。

因为 Action 名是必须的,Controller 次之,Area 许多时候可以忽略,所以,Action 名字的参数位置是 0。有的视图页是不需要限定 Controller 名称的,比如以下这几个特殊页面:_Layout.cshtml、_ViewStart.cshtml、_ViewImports.cshtml。在查找这几个视图时,Action 名称直接就叫 ”_Layout“、”_ViewStart“、”_ViewImports“,不需要指定 Controller 的名字。

好了,知道上面这些原理,相信你也懂得怎么动手了,接下来,老周就以改为 /视图/Controller-Action.cshtml 为例。

在项目中新建两个目录,咱们来个中文名,就叫”控制器“和”视图“。

其实,Controller 类放哪儿都行,因为它们是代码,最终会参与编译的,我们要处理的主要是View。

我们写一个 DemoController 控制器,按照约定,就叫 DemoController,其实类名叫 Demo 也行的。

然后里面写一个简单的 Test 方法,作为 Action,直接返回与该 Action 关联的视图页。

    public class DemoController : Controller
{
public IActionResult Test()
{
return View();
}
}

接着,在 视图 目录下,加一个叫 Demo-Test.cshtml 的文件。注意大小写。

最后,很重要一步,就是在 Startup.ConfigureServices 方法中加入自定义的视图搜索路径。

        public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddRazorOptions(opt =>
{
opt.ViewLocationFormats.Clear();// 清空默认的列表
opt.ViewLocationFormats.Add("/视图/{0}" + RazorViewEngine.ViewExtension);
opt.ViewLocationFormats.Add("/视图/{1}-{0}" +
RazorViewEngine.ViewExtension);
});
}

这里为什么要加一条 /视图/{0}.cshtml 呢,前面说过了,有的特殊页面是只有 Action 的,如 _Layout.cshtml。RazorViewEngine.ViewExtension 是个静态字段,表示视图页的扩展名,其实就是 .cshtml,所以这里你完全可以直接写.cshtml。

这时候,运行程序,从 http://<your host>:<your port>/Demo/Test 访问,就能找到视图 Demo-Test.cshtml 了。输入 URL 时是不分大小写的,但是,在代码中查找视图时是区分大小写的。

但为了方便测试,我们在 UseMvc 时加个带默认值的路由。

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
} app.UseMvc(r => {
r.MapRoute("hehe", "{controller=Demo}/{action=Test}"
);
});

}

路由规则需要一个名字,这个名字有啥用,以后再告诉你。

此时,运行应用就很方便了,直接根 URL 上去就能看到视图了。

再补充一下问题,在 Program.cs 文件中,如果你调用的是默认的 CreateDefaultBuilder 方法是很好办的,因为它会为我们配置好一切。

        public static void Main(string[] args)
{
BuildWebHost(args).Run();
} public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();

但是,如果你自己改写了代码,比如这样。

        public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseStartup<Startup>()
.UseUrls("http://localhost:9999")
.Build();
host.Run();
}

ASP.NET Core 应用可以独立运行,Kestrel 是传说中的神兽,有了这只神兽,你可以跨平台独立运行。如果你只在 Windows 上独立,除了神兽外,你还可以用 HttpSys。这里我顺便指定了 URL ,端口是 9999。

运行后,把这个 URL 复制到浏览器可以进行访问。

但,你再也找不到视图了。

为什么呢?因为少了一句代码。你到 \bin 目录下看看,编译只生成了.dll,并没有复制页面和其他资源,而上面的代码执行后,默认是在这个 bin 下面找资源的,所以找不到了。

解决方法是加上这一句代码。

            var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseUrls("http://localhost:9999")
.Build();

加上这一句后,应用会自动处理当前目录的路径,调试阶段,它查找的是 VS 项目所在的目录,所以能找到视图。而在网站发布后,当前目录会自动变为 .dll 所在的目录,发布时会自动复制项目的资源。

好了,本文说到这里了,88。

【ASP.NET Core】MVC中自定义视图的查找位置的更多相关文章

  1. ASP.NET Core MVC 中自定义视图

    ASP.NET Core MVC 中的视图发现 ASP.NET Core MVC 中有提供了几个 View()的重载方法. 如果我们使用下面提供 View()的重载方法,它将查找与 Action 方法 ...

  2. 007.Adding a view to an ASP.NET Core MVC app -- 【在asp.net core mvc中添加视图】

    Adding a view to an ASP.NET Core MVC app 在asp.net core mvc中添加视图 2017-3-4 7 分钟阅读时长 本文内容 1.Changing vi ...

  3. asp.net core mvc中自定义ActionResult

    在GitHub上有个项目,本来是作为自己研究学习.net core的Demo,没想到很多同学在看,还给了很多星,所以觉得应该升成3.0,整理一下,写成博分享给学习.net core的同学们. 项目名称 ...

  4. 数据呈现到 ASP.NET Core MVC 中展示

    终于要将数据呈现到 ASP.NET Core MVC 中的 视图 上了 将数据从控制器传递到视图的三种方法 在 ASP.NET Core MVC 中,有 3 种方法可以将数据从控制器传递到视图: 使用 ...

  5. ASP.NET Core MVC 中的 [Controller] 和 [NonController]

    前言 我们知道,在 MVC 应用程序中,有一部分约定的内容.其中关于 Controller 的约定是这样的. 每个 Controller 类的名字以 Controller 结尾,并且放置在 Contr ...

  6. 006.Adding a controller to a ASP.NET Core MVC app with Visual Studio -- 【在asp.net core mvc 中添加一个控制器】

    Adding a controller to a ASP.NET Core MVC app with Visual Studio 在asp.net core mvc 中添加一个控制器 2017-2-2 ...

  7. 008.Adding a model to an ASP.NET Core MVC app --【在 asp.net core mvc 中添加一个model (模型)】

    Adding a model to an ASP.NET Core MVC app在 asp.net core mvc 中添加一个model (模型)2017-3-30 8 分钟阅读时长 本文内容1. ...

  8. ASP.NET MVC和ASP.NET Core MVC中获取当前URL/Controller/Action (转载)

    ASP.NET MVC 一.获取URL(ASP.NET通用): [1]获取完整url(协议名+域名+虚拟目录名+文件名+参数) string url=Request.Url.ToString(); [ ...

  9. ASP.NET Core MVC中Controller的Action,默认既支持HttpGet,又支持HttpPost

    我们知道ASP.NET Core MVC中Controller的Action上可以声明HttpGet和HttpPost特性标签,来限制可以访问Action的Http请求类型(GET.POST等). 那 ...

随机推荐

  1. 纯CSS3模拟星体旋转效果

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. React Native学习(一)——搭建开发环境

    第一次接触React Native,首先搭建环境,过程还算顺利,不过也遇到了些问题,这里简单记录下来.中文官网(http://reactnative.cn/docs/0.47/getting-star ...

  3. Offcanvas 自适应窗口示例

    <!DOCTYPE html><html lang="zh-CN"><head> <meta charset="UTF-8&qu ...

  4. Codeforces 833E Caramel Clouds

    E. Caramel Clouds time limit per test:3 seconds memory limit per test:256 megabytes input:standard i ...

  5. 2017 ECJTU ACM 程序设计竞赛

    大厦 Time Limit : 4000/2000ms (Java/Other)   Memory Limit : 65535/32768K (Java/Other) Total Submission ...

  6. JFinal极速开发框架使用笔记(三) 分析Model和ActiveRecord

    JFinal框架的一些新发现的用法: 在JFinal框架中,实体类并不需要设置属性,更不需要配置getset方法就可以很方便的操作数据库,如果需要设置或者获取属性,可以直接使用一下方式: User u ...

  7. [随笔] 简单操作解决Google chrome颜色显示不正常的情况

    最近在用Linuxmint 真的是极友好的桌面Linux啊,然后用最新的Linuxmint自带的Firefox浏览器上网,发现颜色都变成了红色黄色变绿色,以为是显卡的问题,搞了一阵,无果.果断换Goo ...

  8. nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address 

    http://blog.csdn.net/ownfire/article/details/7966645 今天在做LNMP的时候,启动nginx服务,无法开启,导致网页打不开.把服务从起一下发现提示错 ...

  9. As a Start - 毫厘之间,宇宙之外

    序 突然想聊聊多重宇宙——多重宇宙,这听上去像是一个科幻概念,但是严肃思考这一个问题时,将会带我们进入一个全新的格局和世界. 对宇宙学家而言,研究多重宇宙并不仅仅是为了猜测平行世界里某一历史事件是否有 ...

  10. CSS3 [attribute^=value] 选择器

    设置 class 属性值以 "test" 开头的所有 div 元素的背景色: div[class^="test"] { background:#ffff00; ...