【ASP.NET Core】MVC中自定义视图的查找位置

.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。

原文地址:https://www.cnblogs.com/tcjiaan/p/8412827.html

时间: 2024-08-03 06:16:13

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

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 views and layout pages 修改视图和布局页 2.Change the title and menu link in the layout file 在布局文件中修改标题与菜单 3.Passing Data from the Controller to the View 从控制器向视图

asp.net core mvc中如何把二级域名绑定到特定的控制器上

由于公司的工作安排,一直在研究其他技术,所以一直没时间更新博客,今天终于可以停下手头的事情,写一些新内容了. 应用场景:企业门户网站会根据内容不同,设置不同的板块,如新浪有体育,娱乐频道,等等.有的情况下需要给不同的板块设置不同的二级域名,如新浪体育sports.sina.com.cn. 在asp.net core mvc中,如果要实现板块的效果,可能会给不同的板块建立不同的控制器(当然也有其他的技术,这里不讨论实现方式的好坏),在这种情况下,如何给控制器绑定上独有的二级域名,比如体育频道对应的

ASP.NET Core MVC中Controller的Action如何直接使用Response.Body的Stream流输出数据

在ASP.NET Core MVC中,我们有时候需要在Controller的Action中直接输出数据到Response.Body这个Stream流中,例如如果我们要输出一个很大的文件到客户端浏览器让用户下载,那么在Controller的Action中用Response.Body这个Stream流,来逐步发送文件数据到客户端浏览器是最好的办法. 但是我今天在ASP.NET Core MVC的Controller的Action中使用Response.Body输出数据到客户端浏览器的时候遇到了个问题

ASP.NET Core MVC中URL和数据模型的匹配

Http GET方法 首先我们来看看GET方法的Http请求,URL参数和ASP.NET Core MVC中Controller的Action方法参数匹配情况. 我定义一个UserController,其中有一个只接受GET请求的Action方法GetDataInPage public class UserController : Controller { [HttpGet] public IActionResult GetDataInPage(string languageCode, int

Asp.Net Core MVC控制器和视图之间传值

一.Core MVC中控制器和视图之间传值方式和Asp.Net中非常类似 1.弱类型数据:ViewData,ViewBag 2.强类型数据:@model 二.代码 实例  1.ViewData public IActionResult Index() { //测试的 时候在Cummuty2017的最新版中右键添加视图生成的 视图文件的编码 为ANSI对于中文显示乱码 //使用ViewData 实现 控制器 和 视图之间传值 /* * 1.ViewData指定键值对的方式设置或读取 数据 * 2.

在ASP.NET Core MVC中构建简单 Web Api

Getting Started 在 ASP.NET Core MVC 框架中,ASP.NET 团队为我们提供了一整套的用于构建一个 Web 中的各种部分所需的套件,那么有些时候我们只需要做一个简单的 Web Api 程序怎么办呢? 在 GitHub 中的 ASP.NET Core MVC 源码里面,我们只要关注 Microsoft.AspNetCore.Mvc 这个包,那么除了这个包之外它还包含这些: Microsoft.AspNetCore.Mvc.ApiExplorer Microsoft.

c# – Asp.Net Core MVC中Request.IsAjaxRequest()在哪里?

要了解有关新的令人兴奋的Asp.Net-5框架的更多信息,我正在使用最新发布的Visual Studio 2015 CTP-6来构建一个Web应用程序. 大多数事情看起来真的很有希望,但我似乎找不到Request.IsAjaxRequest() – 一个在旧的MVC项目中经常使用的功能. 有没有更好的方法来做到这一点 – 这使得他们删除这种方法 – 或者是“隐藏”在别的地方? 感谢任何建议,在哪里找到它或做什么改为! 我有点困惑,因为标题提到了MVC 5. 搜索Ajax in the MVC6

三分钟学会在ASP.NET Core MVC 中使用Cookie

一.Cookie是什么? 我的朋友问我cookie是什么,用来干什么的,可是我居然无法清楚明白简短地向其阐述cookie,这不禁让我陷入了沉思:为什么我无法解释清楚,我对学习的方法产生了怀疑!所以我们在学习一个东西的时候,一定要做到知其然知其所以然. HTTP协议本身是无状态的.什么是无状态呢,即服务器无法判断用户身份.Cookie实际上是一小段的文本信息).客户端向服务器发起请求,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie.客户端浏览器会把Cooki

ASP.NET Core MVC 中实现中英文切换

哈喽..大家好 很久没有更新了,今天就来一篇最近开发用到的功能,那就是中英文切换,这个实际上也不是高大上,先说一下原理,在.NET Core框架中给我们提供了全球化的类,叫做Localization,其官方的文档地址传送门. 在我的项目中,我是这样操作的,你想用别的方式,也可以看文档自己去搞.这个已经不是什么新鲜的东西了,只是网上的实现有些问题,不容易明白. 我们无需任何Nuget包,因为它是在 Microsoft.AspNetCore.Mvc.Localization 中,那么我们直接在.NE