ASP.NET Core教程-Routing(路由)
创始人
2024-02-05 17:56:43

更新记录
转载请注明出处:
2022年11月21日 发布。
2022年11月20日 从笔记迁移到博客。

Route Basic(路由基础)

路由说明

请求URL映射到控制器方法的过程,这个映射过程由路由规则定义。

路由功能

跟据预先配置的路由信息对客户端传来的请求进行路由映射,映射完成后再将请求传给对应的模块处理,比如:指定的Controller处理。 ASP.NET Core 提供创建路由及路由处理器的接口,要创建路由,首先要先添加路由中间件,然后配置路由相关的服务。

路由的类型

ASP.NET Core中两种路由

  • 常规路由
  • 特性路由

常规路由特性路由 对比:

  • 特性路由修饰在Controller和Action上,特性路由有更多的灵活性。

  • 常规路由一般用于设置HTML控制器的映射。

  • 特性路由一般用于设置RESTful API控制器的映射。

  • 常规路由和特性路由可以混用。

默认路由规则

在Startup.cs文件中的Startup类中Configure()方法中,可以看到UseMvcWithDefaultRoute()中间件,这是启动默认MVC路由的功能。这会给ASP.NET Core添加默认路由规则。

{controller}/{action}/{id?}
//默认映射规则
// {controller}映射到控制器
// {action}映射到控制器的方法
// {id?}映射到控制器方法的参数,称为模型绑

Configure()方法:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{app.UseMvcWithDefaultRoute();
}

如果需要自定义映射规则,可以使用UseEndpoints()中间件来设置端点,然后在端点中设置如何映射

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{app.UseMvcWithDefaultRoute();app.UseEndpoints(endpoints =>{endpoints.MapControllerRoute(name: "default",pattern: "{controller=Home}/{action=Index}/{id?}");});
}

Route Middleware(路由中间件)

UseRouting()中间件

使用UseRouting()中间件启用路由功能。然后使用UseEndpoints()中间件配置路由功能。

UseRouting注册了路由中间件,负责根据请求信息匹配路由定义。路由信息定义包含了路由的处理程序信息,这些信息会在UseEndpoints方法中预先注册生成。当一个请求到达时,路由中间件匹配到MvcEndpointInfo后,后续的中间件就可以根据这些信息进行处理。例如,身份认证中间件可以判断Endpoint对应的处理程序是否要求身份认证,进而进行认证操作。当这些中间件处理完毕后,如果请求未被返回,则由EndpointMiddleware直接调用Endpoint对应的处理程序,从而完成整个请求的处理。基于终结点路由的设计,理论上可以定义任意的路由匹配规则和处理程序。

image

实例:

public class Startup
{public void Configure(IApplicationBuilder app, IWebHostEnvironment env){//启用路由功能app.UseRouting();...//配置路由端点app.UseEndpoints(endpoints =>{//具体的路由配置项endpoints.MapControllers();});}
}

UseEndpoints()中间件

使用UseRouting()中间件启用路由功能。然后使用UseEndpoints()中间件配置路由功能。路由中间件UseEndpoints()是ASP.NET Core新增功能。
路由中间件UseEndpoints()的核心点是:EndpointRouting(终结点路由)。路由中间件UseEndpoints()会代替原有的路由规则和模板,可以跨越不同中间件系统,所以可以用在MVC、Razor Pages、Blazor、SignalR、gRPC中。推荐使用路由中间件UseEndpoints()来设置路由。

app.UseEndpoints(endpoints =>{//注册Web API Controllerendpoints.MapControllers();//注册MVC Controller模板 {controller=Home}/{action=Index}/{id?}endpoints.MapDefaultControllerRoute();//注册RazorPagesendpoints.MapRazorPages();//注册Blazor Hubendpoints.MapBlazorHub();//注册SignalR Hubendpoints.MapHub();//注册Grpc服务终结点endpoints.MapGrpcService();//注册健康检查endpoints.MapHealthChecks("/ready");});

实例:自定义配置

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{app.UseEndpoints(endpoints =>{endpoints.MapControllerRoute(//路由名称name: "default",//路由规则pattern: "{controller=Home}/{action=Index}/{id?}",//默认值(方式二)defaults: new{ controller = "Home", action = "Index" , id = 888 },//路由约束constraints: new{ id = new IntRouteConstraint() });endpoints.MapControllerRoute(name: "panda",pattern: "{Panda}/{action}/{id?}");});
}

实例:配置默认路由规则

public class Startup
{...public void Configure(IApplicationBuilder app, IWebHostEnvironment env){//启用路由功能app.UseRouting();...//配置路由端点app.UseEndpoints(endpoints =>{//配置默认的路由规则endpoints.MapControllerRoute("default", "{controller}/{action}/{id?}");});}
}

Rote Match(路由匹配)

URL 段变量

URL 段变量基本使用

在URL中可以直接使用固定的字符匹配,也称为静态段,也称为路由参数,比如:

/PandaController/PandaAction/PandaArgument

也可以使用称为段变量,使用指定的模式进行匹配

/{first}/{second}/{third}

实例:

app.UseEndpoints(endpoints =>
{endpoints.MapGet("{first}/{second}/{third}", async context =>{await context.Response.WriteAsync("开始-路由信息");foreach (var keyValuePair in context.Request.RouteValues){await context.Response.WriteAsync($"{keyValuePair.Key}-{keyValuePair.Value}\n");}await context.Response.WriteAsync("结束-路由信息");});
});

URL 段变量设置默认值

在段变量中直接指定默认值即可

{Key=defaultValue}

实例:

app.UseEndpoints(endpoints =>
{//设置映射endpoints.MapGet("/Panda/{Key=defaultValue}", async context =>{await context.Response.WriteAsync("Panda666");});
});

URL 段变量设置可选段

在段变量中加上?即可。

{Key?}

实例:

app.UseEndpoints(endpoints =>
{//设置映射endpoints.MapGet("/Panda/{Key?}", async context =>{await context.Response.WriteAsync("Panda666");});
});

默认情况下可选段匹配较短的URL路径。如果需要匹配更多的URL。可以加上catchall段。

app.UseEndpoints(endpoints =>
{//设置映射endpoints.MapGet("/Panda/{*catchall}", async context =>{await context.Response.WriteAsync("Panda666");});
});

路由约束(Routing constraints)

路由约束说明

路由约可以使用:常规路由的Constraints参数方式、行内简写方式。

常规路由的Constraints参数方式

使用constraints参数即可。配置起来比较麻烦,一般使用行内简写。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{app.UseEndpoints(endpoints =>{endpoints.MapControllerRoute(//路由名称name: "default",//路由规则pattern: "{controller=Home}/{action=Index}/{id?}",//默认值(方式二)defaults: new{ controller = "Home", action = "Index" , id = 888 },//路由约束constraints: new{ id = new IntRouteConstraint() });endpoints.MapControllerRoute(name: "panda",pattern: "{Panda}/{action}/{id?}");});
}

行内简写方式路由匹配约束

应用约束的方式是使用冒号(😃,在段变量名称后加上即可。可以组合多个约束进行处理路由约束。

/{first:int}/{second:bool}

官方文档:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-6.0#route-constraints

ConstraintDescription
alphaThis constraint matches the letters a to z (and is case-insensitive).
boolThis constraint matches true and false (and is case-insensitive).
datetimeThis constraint matches DateTime values, expressed in the nonlocalized invariant culture format.
decimalThis constraint matches decimal values, formatted in the nonlocalized invariant culture.
doubleThis constraint matches double values, formatted in the nonlocalized invariant culture.
fileThis constraint matches segments whose content represents a file name, in the form name.ext. The existence of the file is not validated.
floatThis constraint matches float values, formatted in the nonlocalized invariant culture. guid This constraint matches GUID values.
intThis constraint matches int values.
length(len)This constraint matches path segments that have the specified number of characters.
length(min, max)This constraint matches path segments whose length falls between the lower and upper values specified.
longThis constraint matches long values.
max(val)This constraint matches path segments that can be parsed to an int value that is less than or equal to the specified value.
maxlength(len)This constraint matches path segments whose length is equal to or less than the specified value.
min(val)This constraint matches path segments that can be parsed to an int value that is more than or equal to the specified value.
minlength(len)This constraint matches path segments whose length is equal to or more than the specified value. nonfile This constraint matches segments that do not represent a file name, i.e., values that would not be matched by the file constraint.
range(min, max)This constraint matches path segments that can be parsed to an int value that falls between the inclusive range specified.
regex(expression)This constraint applies a regular expression to match path segments.

实例:基本约束

app.UseEndpoints(endpoints =>
{//设置映射endpoints.MapGet("/Panda/{arg:bool}", async context =>{await context.Response.WriteAsync("Panda666");});
});

实例:组合约束

app.UseEndpoints(endpoints =>
{//设置映射endpoints.MapGet("/Panda/{arg:alpha:length(3)}", async context =>{await context.Response.WriteAsync("Panda666");});
});

实例:匹配正则表达式

app.UseEndpoints(endpoints =>
{//设置映射endpoints.MapGet("/Panda/{arg:regex(^panda|dog|cat$)}", async context =>{await context.Response.WriteAsync("Panda666");});
});

自定义路由约束(Custom constraints)

通过实现 IRouteConstraint 接口来实现自定义约束。然后通过 Match 方法来设置是否匹配路由。

using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;namespace PandaTest.RouteConstraint
{public class PandaRouteConstraint : IRouteConstraint{public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection){switch (values[routeKey]){case "v1":return true;case "v2":return true;}return false;}}
}

在服务中加入自定义路由约束

builder.Services.Configure(options => {options.ConstraintMap.Add("Panda", typeof(PandaRouteConstraint));
});

使用自定义约束

app.UseEndpoints(endpoints =>
{//设置映射endpoints.MapGet("/Test/{arg:Panda}", async context =>{await context.Response.WriteAsync("Matched");});
});

路由优先级

说明

默认情况下,特性路由的优先级高于常规路由。

常规路由按从上到下(优先级从高到低),先匹配则优先。

修改优先级-常规路由

路由中间件为每个路由分配一个分数。如果两条路由的分数相同,就会出现歧义。这是就要设置路由优先级。

比如:

app.UseEndpoints(endpoints => {endpoints.Map("{number:int}", async context => {await context.Response.WriteAsync("Routed to the int endpoint");});endpoints.Map("{number:double}", async context => {await context.Response.WriteAsync("Routed to the double endpoint");});
});

加入优先级后:

app.UseEndpoints(endpoints => {endpoints.Map("{number:int}", async context => {await context.Response.WriteAsync("Routed to the int endpoint");}).Add(b => ((RouteEndpointBuilder)b).Order = 1);endpoints.Map("{number:double}", async context => {await context.Response.WriteAsync("Routed to the double endpoint");}).Add(b => ((RouteEndpointBuilder)b).Order = 2);
});

修改优先级-特性路由

[Route("api/[Controller]")]
public class PandaController: Controller
{//Get api/[Controller]/{id}[HttpGet("{id:guid}")][HttpGet("{id:guid}", Order = -1)]public string Get(Guid id){}
}

Attribute routing(特性路由)

说明

使用[Route]特性修饰 Controller 以及 Controller 的方法,即可实现路由的功能。

注意:

  • 使用了特性路由特性修饰后,常规路由将不适用当前被修饰的控制器或方法。
  • 特性路由会被继承。

特性路由修饰Controller

[Route("")]         //任何URL
[Route("Panda")]    //Panda
[Route("Tiger")]    //Tiger
public class PandaController : Controller
{[Route("")]             //任何URL[Route("Panda")]        //Panda/[Route("Panda/Index")]  //Panda/Index[Route("Tiger/Index", Name = "PandaRoute")]  //Tiger/Index ,还可以像传统路由那样设置路由名称public ActionResult Index(){return View();}
}

特性路由修饰Controller的方法

public class PandaController : Controller
{[Route("")]             //任何URL[Route("Panda")]        //Panda/[Route("Panda/Index")]  //Panda/Indexpublic ActionResult Index(){return View();}
}

堆叠修饰

多个修饰器,可以实现,既可以用Panda访问,也可以用Tiger访问。

[Route("Panda")]    //Panda
[Route("Tiger")]    //Tiger
public class PandaController : Controller
{[Route("Index")]  			//Panda/Index 或者 Tiger/Indexpublic ActionResult Index(){return View();}
}

模板参数|模型绑定

[Route("Panda/Index/{id}")]
public ActionResult Index(int id)
{return View();
}

模板可选参数

[Route("Panda/Index/{id?}")]
public ActionResult Index(int? id)
{return View();
}

使用预定义标记实现自定义路由

在特性路由[Route]中使用[Controller]和[Action]即可实现自定义映射,而无需每个方法都去定义一遍路由。

using Microsoft.AspNetCore.Mvc;namespace WebApplication2.Controllers
{[Route("[controller]/[action]")]public class PandaController : Controller{[Route("")]public IActionResult Index(int? id){return View();}public IActionResult Detail(int? id){return View();}}
}

HTTP动词修饰

[Route("api/[Controller]")]
public class PandaController: Controller
{//Get api/[Controller][HttpGet]public IEnumerable Get(){}//Get api/[Controller]/{id}[HttpGet("{id:guid}")]public string Get(Guid id){}//Post api/[Controller][HttpPost]public void Post([FromBody]string value){}
}

自定义特性路由(Custom attribute routing)

引入using Microsoft.AspNetCore.Mvc.Routing;命名空间,然后继承 IRouteTemplateProvider 接口即可。

using System;
//引入命名空间
using Microsoft.AspNetCore.Mvc.Routing;namespace SampleAPI.CustomRouting
{//实现接口 IRouteTemplateProvider、Attributepublic class CustomOrdersRoute : Attribute, IRouteTemplateProvider{public string Template => "api/orders";public int? Order { get; set; }public string Name => "Orders_route";}
}

使用自定义特性路由

[CustomOrdersRoute]
[ApiController]
public class OrderController : ControllerBase
{

添加路由(常规路由)(Conventional routing)

最基本使用

使用UseRouting()中间件启用路由功能。然后使用UseEndpoints()中间件配置路由功能。路由中间件UseEndpoints()是ASP.NET Core新增功能。
路由中间件UseEndpoints()的核心点是:EndpointRouting(终结点路由)。路由中间件UseEndpoints()会代替原有的路由规则和模板,可以跨越不同中间件系统,所以可以用在MVC、Razor Pages、Blazor、SignalR、gRPC中。推荐使用路由中间件UseEndpoints()来设置路由。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{app.UseEndpoints(endpoints =>{endpoints.MapControllerRoute(//路由名称name: "default",//路由规则pattern: "{controller=Home}/{action=Index}/{id?}",//默认值(方式二)defaults: new{ controller = "Home", action = "Index" , id = 888 },//路由约束constraints: new{ id = new IntRouteConstraint() });endpoints.MapControllerRoute(name: "panda",pattern: "{Panda}/{action}/{id?}");});
}

添加普通路由

添加普通路由(固定字符串)

app.UseEndpoints(endpoints =>
{endpoints.MapGet("/Panda/", async context => {await context.Response.WriteAsync("Panda666");});
});

添加普通路由(路由参数)

app.UseEndpoints(endpoints =>
{endpoints.MapGet("/{arg1}/{arg2}", async context => {await context.Response.WriteAsync("Panda666");});
});

添加普通路由(路由参数)

app.UseEndpoints(endpoints =>
{endpoints.MapGet("/{arg1}/{arg2}/{arg3}", async context => {await context.Response.WriteAsync("Panda666");});
});

添加普通路由(不规则)

映射指定的文件名和文件后缀

app.UseEndpoints(endpoints =>
{//设置映射endpoints.MapGet("/file/{FileName}.{Extension}", async context =>{await context.Response.WriteAsync("Panda666");});
});

添加路由并获得路由信息

app.UseEndpoints(endpoints =>
{endpoints.MapGet("{first}/{second}/{third}", async context =>{await context.Response.WriteAsync("开始-路由信息");foreach (var keyValuePair in context.Request.RouteValues){await context.Response.WriteAsync($"{keyValuePair.Key}-{keyValuePair.Value}\n");}await context.Response.WriteAsync("结束-路由信息");});
});

添加路由并根据指定参数做出反应

app.UseEndpoints(endpoints =>
{endpoints.MapGet("{first}/{second}/{third}", async context =>{//获得指定的路由参数context.Request.RouteValues.TryGetValue("first", out object? value);string firstValue = value as string ?? "";//根据路由参数做出反应switch(firstValue){case "Panda":await context.Response.WriteAsync("Panda\n");break;case "Dog":await context.Response.WriteAsync("Dog\n");break;}});
});

添加路由并将路由匹配放入单独的类中

自定义路由处理类,本质就是将路由处理代码放入单独的静态类和静态方法。

静态类和静态方法:

namespace PandaTest.Route
{public static class PandaRoute{/// /// 路由端点处理/// /// /// public static async Task Endpoint(HttpContext httpContext){//模拟具体的任务await httpContext.Response.WriteAsync("Panda666\n");}}
}

设置静态映射:

app.UseEndpoints(endpoints =>
{//设置映射endpoints.MapGet("/Panda", PandaRoute.Endpoint);
});

只映射指定的HTTP方法

使用Map+HTTP方法即可。

实例:

app.UseEndpoints(endpoints =>
{//映射GETendpoints.MapGet("order", context => context.Response.WriteAsync("Panda Get"));//映射Postendpoints.MapPost("order", context => context.Response.WriteAsync("Panda Post"));//映射Putendpoints.MapPut("/abc/def", context => context.Response.WriteAsync("Panda Put"));//映射Deleteendpoints.MapDelete("/abc/def", context => context.Response.WriteAsync("Panda Delete"));
});

回退路由

当没有其他路由与请求匹配时,可以使用回退路由定义将请求定向到指定端点。这让任何路由都有相应。也可以避免请求沿着请求管道继续传递。

使用以下2个方法定义回退路由。

MapFallback(endpoint) 		//This method creates a fallback that routes requests to an endpoint.
MapFallbackToFile(path) 	//This method creates a fallback that routes requests to a file.

实例:自定义回退路由

app.UseEndpoints(endpoints =>
{//设置映射endpoints.MapFallback(async context => {await context.Response.WriteAsync("FallBack");});//设置映射到文件//endpoints.MapFallbackToFile("filePath");
});

实例:自定义回退文件

app.UseEndpoints(endpoints =>
{//设置映射到文件endpoints.MapFallbackToFile("filePath");
});

获得路由端点

并不是所有的中间件都有相应,有时候我们需要去获得路由端点(endpoint),比如:处理会话中间件、状态代码中间件。直接使用上下文对象的 GetEndpoint() 方法获得端点。

app.Use(async (context, next) => {//获得路由端点Endpoint? endpoint = context.GetEndpoint();if (endpoint != null){//使用端点的代码//......await context.Response.WriteAsync("get endpoint");}else{await context.Response.WriteAsync("No endpoint");}//调用下一个中间件await next();
});

代合并-添加路由服务 和 其底层原理

配置路由服务中间件

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{var trackPackageRouteHandler = new RouteHandler(context =>{var routeValues = context.GetRouteData().Values;return context.Response.WriteAsync($"Hello! Route values: {string.Join(", ",routeValues)}");});var routeBuilder = new RouteBuilder(app,trackPackageRouteHandler);routeBuilder.MapRoute("Track Package Route","package/{operation}/{id:int}");routeBuilder.MapGet("hello/{name}", context =>{var name = context.GetRouteValue("name");return context.Response.WriteAsync($"Hi, {name}!");});var routes = routeBuilder.Build();app.UseRouter(routes);
}

在服务中添加路由服务

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

首先创建了一个RouteHandler,即路由处理器
它会从请求的URL中获取路由信息,并将其输出;接着创建了一个RouteBuilder
并使用它的MapRoute方法来添加路由信息
这些信息包括路由名称以及要匹配URL模板
在上面的示例中,URL模板的值为package/{operation}/{id:int}
除了调用MapRoute外,后面还可以使用MapGet方法添加仅匹配GET方法的请求
最后调用IApplicationBuilder的UseRouter扩展方法来添加路由中间件
以上是在ASP.NET Core中底层路由的创建方式
通常情况下并不需要这么做,这种方式比较复杂,更主要的原因则是当使用MVC后
就只需将对应的U RL路由到Controller与Action,这简化了路由规则
并且MVC中间件也封装了相关的逻辑,使基于MVC的路由更易于配置

待合并-自定义路由

对于ASP.NET Core MVC,定义路由的方法有以下两种
基于约定的路由:
基于约定的路由会根据一些约定来创建路由
它要在应用程序的Startup类中来定义
要使用基本约定的路由,首先定义一个或若干个路由约定
同时只要保证所有定义的路由约定能够尽可能地满足不同形式的映射即可
特性路由:
使用C#特性对Controller和Action指定其路由信息

public void Configure(IApplicationBuilder app,IHostingEnvironment env)
{app.UseMvc(routes =>{routes.MapRoute(template: "{controller}/{action}");});
}

注意:一个URL模板中只能有一个可选参数,并且只能放在最后

在指定参数的同时,也可以为参数添加一些约束或限制
例如,参数id的值为一个整型的数字,则应该这样定义路由
routes.MapRoute(template: "{controller}/{action}/{id:int}");

另一种实现路由的方法是使用特性路由
即RouteAttribute。它能够为每个Controller
甚至每个Action显式地设置路由信息
只要在Controller类或Action方法上添加[Route]特性即可,因此它要更为灵活

public class HomeController: Controller
{[Route("")][Route("Home/Index")][Route("AnotherOne")]public IActionResult Index(){return Ok("Hello from Index method of Home Controller");}
}

实例:

[Route("Home")]
public class HomeController : Controller
{[Route("")][Route("Index")]public IActionResult Index(){}[Route("Welcome")]public IActionResult Welcome(){}
}

实例:使用[controller]与[action]来分别代替固定的值

[Route("[controller]")]
public class HomeController : Controller
{[Route("")][Route("[action]")]public IActionResult Index(){}[Route("[action]")]public IActionResult Welcome(){}
}

实例:传递参数

[Route("[action]/{name?}")]
public IActionResult Welcome(string name)
{
}

实例:多个路由映射到Controller

[Controller]
[Route("api/[controller]")]
public class Blogs
{
}

实例:Controller禁止映射

[NonController]
public class BooksController
{
}

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...