ControllerModel类型的Actions属性包含一组描述有效Action方法的ActionModel对象。对于定义在Controller类型中的所有方法,究竟哪些方法才能成为有效的Action方法呢?所以在正式介绍ActionModel类型之前,我们先来聊聊Action方法的选择规则。

一、Action方法的选择

二、ActionModel

三、PropertyModel与ParameterModel

四、SelectorModel

五、实例演示:选择器模型的构建

一、 Action方法的选择

当DefaultApplicationModelProvider对象根据某个具体的Controller类型创建对应ControllerModel对象的时候,它会提取出定义在该类型中的方法,并按照预定义的规则选择出有效的Action方法。有效的Action方法必须满足如下条件:

  • 公共方法:私有(Private)、内部(Internal)和受保护(Protected)方法均为无效Action方法。
  • 非抽象方法:抽象方法为无效的Action方法(这个限制可以忽略,因为Controller不会是抽象类型)。
  • 非泛型方法:Action方法不能定义成泛型方法。
  • 非静态方法:静态方法为无效Action方法。
  • 不是从Object类型上继承的方法:Action方法支持继承,但是从Object类型上继承的方法不能成为Action方法。
  • 不是对IDisposable接口的实现:如果Controller类型实现了IDisposable接口,实现的Dispose方法不是有效的Action方法。

二、ActionModel

如下面的代码片段所示,ActionModel类型实现了ICommonModel、IFilterModel和IApiExplorerModel三个接口。默认注册的DefaultApplicationModelProvider会对ActionModel对象做如下的设置:MemberInfo和ActionMethod属性都将设置为描述当前Action方法的MethodInfo对象。通过标注的特性注册到Action方法上的过滤器会被提取出来,对应的元数据会添加到Filters属性中。ApiExplorer属性返回的ApiExplorerModel对象由标注在Action方法上实现了IApiDescriptionGroupNameProvider和IApiDescriptionVisibilityProvider接口的特性构建而成。

public class ActionModel : ICommonModel, IFilterModel, IApiExplorerModel
{
public ControllerModel Controller { get; set; } public IList<IFilterMetadata> Filters { get; }
public ApiExplorerModel ApiExplorer { get; set; }
public IDictionary<object, object> Properties { get; } public IList<ParameterModel> Parameters { get; }
public IList<SelectorModel> Selectors { get; } public MethodInfo ActionMethod { get; }
public string DisplayName { get; }
public string ActionName { get; set; }
public IReadOnlyList<object> Attributes { get; } public IOutboundParameterTransformer RouteParameterTransformer { get; set; }
public IDictionary<string, string> RouteValues { get; } MemberInfo ICommonModel.MemberInfo { get; }
string ICommonModel.Name { get; }
}

DefaultApplicationModelProvider会为Action方法的每个参数创建一个ParameterModel对象,并添加到Parameters属性中。应用在Action方法上的用于封装路由信息(特性路由、约束和终结点元数据)的SelectorModel对象会按照上述的方式构建出来,并添加到Selectors属性中。标注在Action方法上的特性会被提取出来并添加到Attributes属性返回的列表中。表示Action名称的ActionName与Name属性具有相同的值,DefaultApplicationModelProvider会默认将它们设置为方法的名称,但是我们可以在方法上通过标注如下这个ActionNameAttribute特性对该属性进行设置。

[AttributeUsage(AttributeTargets.Method, AllowMultiple=false, Inherited=true)]
public sealed class ActionNameAttribute : Attribute
{
public string Name { get; }
public ActionNameAttribute(string name);
}

我们照例通过一个简单的示例来演示一下DefaultApplicationModelProvider对象针对ActionModel的构建规则。我们依然沿用前面创建的演示实例,这次我们将FoobarController定义成如下的形式。如代码片段所示,我们为Action方法定义了三个参数,并在上面标注了四个特性,其中FoobarAttribute特性是我们应用在该Action方法上的过滤器,ApiExplorerSettingsAttribute特性针对ApiExplorer作了相应设置,HttpGetAttribute特性完成了针对路由和HTTP方法约束的定义,ActionNameAttribute特性则将Action名称设置为“Baz”。

public class FoobarController
{
[Foobar]
[ApiExplorerSettings(GroupName ="test")]
[HttpGet("/foobar/baz")]
[ActionName("Baz")]
public void Index(string foo, string bar, string baz) => throw new NotImplementedException();
}

我们需要修改定义在HomeController中的Action方法Index。如下面的代码片段所示,在利用通过参数注入的ApplicationModelProducer对象根据提供的FoobarController类型创建出对应ApplicationModel对象之后,我们提取出描述Action方法Index的ActionModel对象,并将其作为Model呈现在默认的View中。

public class HomeController : Controller
{
[HttpGet("/")]
public IActionResult Index([FromServices]ApplicationModelProducer producer)
{
var applicationModel = producer.Create(typeof(FoobarController));
return View(applicationModel.Controllers.Single().Actions.Single());
}
}

我们将按照如下的形式重新定义了Action方法Index对应的View。如下面的代码片段所示,这是一个Model类型为ActionModel的强类型View,,它将ActionModel承载的元数据呈现在一个表格中。

@model Microsoft.AspNetCore.Mvc.ApplicationModels.ActionModel
@{
var filters = Model.Filters;
var routeValues = Model.RouteValues.ToArray();
var parameters = Model.Parameters;
var attributes = Model.Attributes;
}
<html>
<head>
<title>Action</title>
</head>
<body>
<table border="1" cellpadding="0" cellspacing="0">
<tr><td>Method</td><td>@Model.ActionMethod.Name</td></tr>
<tr><td>ActionName</td><td>@Model.ActionName</td></tr>
<tr><td>DisplayName</td><td>@Model.DisplayName</td></tr>
<tr>
<td rowspan="@parameters.Count">Parameters</td>
<td>@parameters[0].Name</td>
</tr>
@for (int index = 1; index < parameters.Count; index++)
{
<tr><td>@parameters[index].Name</td></tr>
}
<tr>
<td rowspan="@filters.Count">Filters</td>
<td>@filters[0].GetType().Name</td>
</tr>
@for (int index = 1; index < filters.Count; index++)
{
<tr><td>@filters[index].GetType().Name</td></tr>
}
<tr>
<td rowspan="@attributes.Count">Attributes</td>
<td>@attributes[0].GetType().Name</td>
</tr>
@for (int index = 1; index < attributes.Count; index++)
{
<tr><td>@attributes[index].GetType().Name</td></tr>
}
@if (routeValues.Length == 0)
{
<tr><td>RouteValues</td><td>N/A</td></tr>
}
else
{
<tr>
<td rowspan="@routeValues.Length">RouteValues</td>
<td>@routeValues[0].Key = @routeValues[0].Value</td>
</tr>
}
@for (int index = 1; index < routeValues.Length; index++)
{
<tr><td>@routeValues[index].Key = @routeValues[index].Value</td></tr>
}
<tr>
<td rowspan="2">ApiExplorer</td>
<td>IsVisible = @Model.ApiExplorer.IsVisible </td>
</tr>
<tr>
<td>GroupName = @Model.ApiExplorer.GroupName </td>
</tr>
</table>
</body>
</html>

改动后的演示程序启动后,我们利用浏览器访问应用的主页,可以得到如图1所示的输出结果。我们从图中可以看出,Action名称来源于标注在方法上的ActionNameAttribute特性。DefaultApplicationModelProvider会为方法的每个参数创建一个ParameterModel对象并添加到ActionModel对象的Properties属性中。通过特性标注注册到Action方法上的FoobarAttribute过滤器被添加到ActionModel对象的Filters属性中。Action方法标注的四个特性全部被添加到ActionModel对象的Attributes属性中。ActionModel对象的ApiExplorer属性返回的ApiExplorerModel对象是由标注在方法上的ApiExplorerSettingsAttribute特性构建的。值得一提的是,Controller和Action的名称此时并没有作为路由参数添加到RouteValues属性中。

图1 Action模型默认的构建规则

三、PropertyModel与ParameterModel

默认注册的DefaultApplicationModelProvider会将定义在Controller类型的公共属性(包括从基类继承的属性)提取创建,然后创建相应的PropertyModel对象并添加到ControllerModel对象的ControllerProperties属性中。描述属性的PropertyModel对象和描述参数的ParameterModel对象都是为了提供模型绑定的元数据,所以它们具有相同的基类ParameterModelBase。

public abstract class ParameterModelBase : IBindingModel
{
public string Name { get; protected set; }
public Type ParameterType { get; }
public IReadOnlyList<object> Attributes { get; }
public BindingInfo BindingInfo { get; set; }
public IDictionary<object, object> Properties { get; }
}

如上面的代码片段所示,抽象类ParameterModelBase实现了IBindingModel,所以它需要利用实现的BindingInfo书信提供模型绑定信息。ParameterModelBase并没有实现IPropertyModel接口,但是其自身提供了一个Properties属性。ParameterModelBase的Name和ParameterType属性分别表示对应参数/属性的名称和类型。标注到属性上的特性会添加到Attributes属性中。如下所示的是描述Controller属性的PropertyModel类型,和描述Action方法参数的ParameterModel类型的定义。

public class PropertyModel : ParameterModelBase, ICommonModel, IBindingModel
{
public ControllerModel Controller { get; set; }
public PropertyInfo PropertyInfo { get; }
public string PropertyName { get; set; }
public IReadOnlyList<object> Attributes { get; }
public IDictionary<object, object> Properties { get; }
MemberInfo ICommonModel.MemberInfo { get; }
} public class ParameterModel : ParameterModelBase, ICommonModel
{
public ActionModel Action { get; set; }
public ParameterInfo ParameterInfo { get; }
public string ParameterName { get; set; }
public string DisplayName { get; }
public IReadOnlyList<object> Attributes { get; }
public IDictionary<object, object> Properties { get; }
MemberInfo ICommonModel.MemberInfo { get; }
}

四、SelectorModel

SelectorModel类型是对Action选择器(Selector)的描述,这里的选择器旨在解决如何为请求选择匹配Action的问题,所以它承载的其实针对路由的原始定义。如下面的代码片段所示,SelectorModel类型通过AttributeRouteModel、ActionConstraints和EndpointMetadata分别存储了特性路由信息、约束和终结点元数据。

public class SelectorModel
{
public AttributeRouteModel AttributeRouteModel { get; set; }
public IList<IActionConstraintMetadata> ActionConstraints { get; }
public IList<object> EndpointMetadata { get; }
} public class AttributeRouteModel
{
public IRouteTemplateProvider Attribute { get; }
public string Template { get; set; }
public int? Order { get; set; }
public string Name { get; set; }
public bool SuppressLinkGeneration { get; set; }
public bool SuppressPathMatching { get; set; }
public bool IsAbsoluteTemplate { get; }
}

由于路由可以通过标注到Controller类型或者Action方法上相应的特性来定义,所以描述Controller类型和Action方法的ControllerModel和ActionModel类型都具有一个Selectors属性来保存各自的选择器,DefaultApplicationModelProvider针对它们的解析方式也是一致的。这里用来定义路由的特性实现了如下所示的IRouteTemplateProvider特性。

public interface IRouteTemplateProvider
{
string Template { get; }
int? Order { get; }
string Name { get; }
}

顾名思义,实现IRouteTemplateProvider接口的特性旨在定义一个针对指定模板的路由。除此之外,针对SelectorModel的构建还涉及另一个名为IActionHttpMethodProvider的接口,实现该接口的特性为目标Action定义针对HTTP方法的约束。

public interface IActionHttpMethodProvider
{
IEnumerable<string> HttpMethods { get; }
}

对于目前提供的实现了IActionHttpMethodProvider接口的特性来说,它们无一例外都同时实现了IRouteTemplateProvider接口。比如下面这个AcceptVerbsAttribute特性可以标注到Action方法上指定一组支持的HTTP方法,同时也可以利用三个属性(Route、Name和Order)对路由作相应的定义(显式实现的Template和Order属性与自身Route和Order属性具有相同的值)。顺便提一下,我们在AcceptVerbsAttribute特性构造函数以字符串指定的HTTP方法名称会一律转换成大写形式。

[AttributeUsage( AttributeTargets.Method, AllowMultiple=true, Inherited=true)]
public sealed class AcceptVerbsAttribute : Attribute, IActionHttpMethodProvider, IRouteTemplateProvider
{
public IEnumerable<string> HttpMethods { get; }
public string Route { get; set; }
public int Order { get; set; }
public string Name { get; set; } string IRouteTemplateProvider.Template { get; }
int? IRouteTemplateProvider.Order { get; } public AcceptVerbsAttribute(string method);
public AcceptVerbsAttribute(params string[] methods);
}

虽然AcceptVerbsAttribute特性可以为我们指定多个支持的HTTP方法,但是我们似乎更倾向于使用针对具有某种HTTP方法的特性,比如HttpGetAttribute、HttpPostAttribute、HttpPutAttribute、HttpDeleteAttribute、HttpHeadAttribute、HttpPatchAttribute和HttpOptionsAttribute特性,它们都派生于如下这个抽象的HttpMethodAttribute特性类型。

[AttributeUsage( AttributeTargets.Method, AllowMultiple=true, Inherited=true)]
public abstract class HttpMethodAttribute : Attribute, IActionHttpMethodProvider, IRouteTemplateProvider
{
public IEnumerable<string> HttpMethods { get; }
public string Template { get; }
public int Order { get; set; }
public string Name { get; set; } int? IRouteTemplateProvider.Order { get; } public HttpMethodAttribute(IEnumerable<string> httpMethods);
public HttpMethodAttribute(IEnumerable<string> httpMethods, string template);
}

IRouteTemplateProvider接口除了上述这些实现类型之外,如下这个专门用来定义路由的RouteAttribute特性单独实现了该接口。从提供的代码片段可以看出,RouteAttribute特性可以标注在Controller类型或者具体的Action方法上。

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple=true, Inherited=true)]
public class RouteAttribute : Attribute, IRouteTemplateProvider
{
public string Template { get; }
public int Order { get; set; }
public string Name { get; set; } public RouteAttribute(string template);
int? IRouteTemplateProvider.Order { get; }
}

DefaultApplicationModelProvider针对SelectorModel对象的构建逻辑分为两种情况。如果标注在当前Controller类型或者Action方法上的特性没有提供任何路由信息,这种情况由分为两种场景:其一,根本没有标注任何实现了IRouteTemplateProvider接口的特性;其二,所有的特性并没有对定义在该接口的三个属性(Template、Name和Order)做任何设置,比如我们经常在Action方法上标注一个没有提供任何参数的HttpGetAttribute特性,其目的只是限制支持的HTTP方法,而不是定义路由。

在这种情况下,DefaultApplicationModelProvider会创建一个唯一的SelectorModel对象。由于没有任何路由被定义,所以该对象的AttributeRouteModel属性会返回Null。标注在Controller类型或者Action方法上实现了IActionConstraintMetadata接口的特性会作为约束添加到ActionConstraints属性中。如果标注了实现IActionHttpMethodProvider接口的特性对HTTP方法做了限制,一个对应的HttpMethodActionConstraint对象会额外创建出来并添加到ActionConstraints属性中。与此同时,一个针对HTTP方法列表的HttpMethodMetada对象会被创建出来并作为终结点元数据被添加到EndpointMetadata属性中。除此之外,所有特性都会作为终结点元数据添加到EndpointMetadata属性中。

如果当前Controller类型或者Action方法上标注了实现IRouteTemplateProvider接口的特性,并且作了具体的路由设置,DefaultApplicationModelProvider会为每个IRouteTemplateProvider对象创建一个SelectorModel对象。对于每一个针对IRouteTemplateProvider对象创建的SelectorModel对象来说,设置的路由信息会被提取出来用于创建对应的AttributeRouteModel对象。如果当前特性是一个IActionHttpMethodProvider对象,一个对应的HttpMethodActionConstraint对象会额外创建出来并添加到ActionConstraints属性中。与此同时一个针对HTTP方法列表的HttpMethodMetada对象会被创建出来,当前特性和这个对象都将作为终结点元数据被添加到EndpointMetadata属性中。

如果当前IRouteTemplateProvider对象类型为RouteAttribute,那些没有提供路由信息的实现了IActionHttpMethodProvider接口的特性(即只定义了HTTP方法约束的特性)会被提取出来,一个根据它们提供的HTTP方法列表创建的HttpMethodActionConstraint对象并添加到ActionConstraints属性中。与此同时,一个针对HTTP方法列表的HttpMethodMetada对象会被创建出来并作为终结点元数据被添加到EndpointMetadata属性中。EndpointMetadata属性最终包含的终结点元数据还包括当前RouteAttribute特性和这些单纯定义约束的特性。

五、实例演示:选择器模型的构建

对于DefaultApplicationModelProvider为Controller类型或者Action方法构建SelectorModel的逻辑,我想针对具体的场景会更好理解一点,为此我们来演示几个简单的实例。我们依然沿用前面的演示程序,并将FoobarController类型改写成如下的形式。FoobarController类型中定义了三个Action方法,接下来我们看看DefaultApplicationModelProvider对象会为它们创建出怎样的选择器。

public class FoobarController
{
[HttpGet]
[HttpPost]
public void Foo()=>throw new NotImplementedException(); [HttpGet("bar")]
[HttpPost("bar")]
public void Bar() => throw new NotImplementedException(); [HttpGet()]
[HttpPost("bar")]
[Route("bar")]
public void Baz() => throw new NotImplementedException();
}

我们对定义在HomeController中的Action方法Index作如下的修改。如代码片段所示,在该方法中,我们利用通过参数注入的ApplicationModelProducer对象根据FoobarController类型创建出对应的ApplicationModel对象,然后利用查询字符串绑定的actionName参数提取出描述对应Action的ActionModel对象。我们将ActionModel对象的Selectors属性提取的选择器列表作为Model呈现在View中。

public class HomeController : Controller
{
[HttpGet("/")]
public IActionResult Index([FromServices]ApplicationModelProducer producer, [FromQuery]string actionName)
{
var applicationModel = producer.Create(typeof(FoobarController));
var actionModel = applicationModel.Controllers.Single().Actions.Single(it=>string.Compare(it.ActionName, actionName, true) == 0);
return View(actionModel.Selectors);
}
}

如下所示的是修改后的View。如代码片段所示,这个是一个Model类型为IList<SelectorModel>的强类型View,我们将提供的用于描述选择器的每个SelectorModel对象的元数据信息呈现在表格中。

@using Microsoft.AspNetCore.Mvc.ApplicationModels;
@model IList<SelectorModel>
<html>
<head>
<title>Selectors</title>
</head>
<body>
<table border="1" cellpadding="0" cellspacing="0">
@for (int i = 0; i < Model.Count; i++)
{
var selector = Model[i];
var constraints = selector.ActionConstraints;
var metadata = selector.EndpointMetadata;
<tr><td colspan="2">Selector @(i+1)</td></tr>
<tr>
<td>AttributeRouteModel</td>
<td>@selector.AttributeRouteModel?.Template</td>
</tr>
<tr>
<td rowspan="@constraints.Count">ActionConstraints</td>
<td>@constraints[0].GetType().Name</td>
</tr>
@for (int j = 1; j < constraints.Count; j++)
{
<tr><td>@constraints[j].GetType().Name</td></tr>
}
<tr>
<td rowspan="@metadata.Count">EndpointMetadata</td>
<td>@metadata[0].GetType().Name</td>
</tr>
@for (int j = 1; j < metadata.Count; j++)
{
<tr><td>@metadata[j].GetType().Name</td></tr>
}
}
</table>
</body>
</html>

由于第一个Action方法Foo上的两个IActionHttpMethodProvider特性并没有提供任何的路由信息,所以它只具有一个AttributeRouteModel属性为Null的SelectorModel对象。这两个特性提供的针对HTTP方法(GET和POST)的约束会转换成一个HttpMethodActionConstraint对象并添加到SelectorModel对象的ActionConstraints属性中。除此之外,这两个特性会直接作为终结点元数据被添加到SelectorModel对象的EndpointMetadata属性中,该属性还会包含一个针对HTTP方法约束的HttpMethodMetada对象。图2所示的就是演示应用返回的针对Action方法Foo的选择器信息。

图2 Action方法Foo的选择器

第二个Action方法Bar上的两个特性均指定了路由模板,所以DefaultApplicationModelProvider会为它创建两个针对性的SelectorModel对象。DefaultApplicationModelProvider会根据特性(HttpGetAttribute和HttpPostAttribute)提供的路由信息来创建对应的AttributeRouteModel对象。SelectorModel对象ActionConstraints属性会包含根据各自提供的HTTP方法约束创建的HttpMethodActionConstraint对象。EndpointMetadata属性将会包含两个终结点元数据对象,分别是当前的特性和根据HTTP方法约束创建的HttpMethodMetada对象。图3所示的就是演示应用返回的针对Action方法Bar的选择器列表。

图3  Action方法Bar的选择器

第三个Action方法方法上标注了三个特性,但是其中只有两个特性提供了路由信息,所以DefaultApplicationModelProvider最终会根据标注的HttpPostAttribute和RouteAttribute特性创建出两个对应的SelectorModel对象。根据标注的HttpPostAttribute特性针对SelectorModel对象的创建与上面一致,所以我们现在只关注针对RouteAttribute特性创建的SelectorModel对象。该对象提供的AttributeRouteModel对象自然由RouteAttribute特性提供的路由信息来创建。

该方法上没有提供路由信息的HttpGetAttribute特性将被用来提供当前路由的约束,所以这个SelectorModel对象的ActionConstraints属性中会包含一个根据这个特性创建的HttpMethodActionConstraint对象。这个SelectorModel对象的EndpointMetadata属性中最终会包含三个终结点元数据,分别是标注的RouteAttribute和HttpGetAttribute特性,以及根据HTTP方法约束创建的HttpMethodMetada对象。图4所示的就是演示应用返回的针对Action方法Baz的选择器列表。

图4  Action方法Bar的选择器

ASP.NET Core MVC应用模型的构建[4]: Action的选择的更多相关文章

  1. ASP.NET Core MVC/WebAPi 模型绑定探索

    前言 相信一直关注我的园友都知道,我写的博文都没有特别枯燥理论性的东西,主要是当每开启一门新的技术之旅时,刚开始就直接去看底层实现原理,第一会感觉索然无味,第二也不明白到底为何要这样做,所以只有当你用 ...

  2. ASP.NET Core MVC 之模型(Model)

    1.模型绑定 ASP.NET Core MVC 中的模型绑定将数据从HTTP请求映射到操作方法参数.参数既可以是简单类型,也可以是复杂类型.MVC 通过抽象绑定解决了这个问题. 2.使用模型绑定 当 ...

  3. ASP.NET Core MVC/WebAPi 模型绑定探索 转载https://www.cnblogs.com/CreateMyself/p/6246977.html

    前言 相信一直关注我的园友都知道,我写的博文都没有特别枯燥理论性的东西,主要是当每开启一门新的技术之旅时,刚开始就直接去看底层实现原理,第一会感觉索然无味,第二也不明白到底为何要这样做,所以只有当你用 ...

  4. 【转】ASP.NET Core MVC/WebAPi 模型绑定探索

    前言 相信一直关注我的园友都知道,我写的博文都没有特别枯燥理论性的东西,主要是当每开启一门新的技术之旅时,刚开始就直接去看底层实现原理,第一会感觉索然无味,第二也不明白到底为何要这样做,所以只有当你用 ...

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

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

  6. ASP.NET Core MVC/WebAPi 模型绑定

    public class Person { public string Name { get; set; } public string Address { get; set; } public in ...

  7. ASP.NET Core MVC 模型绑定用法及原理

    前言 查询了一下关于 MVC 中的模型绑定,大部分都是关于如何使用的,以及模型绑定过程中的一些用法和概念,很少有关于模型绑定的内部机制实现的文章,本文就来讲解一下在 ASP.NET Core MVC ...

  8. Pro ASP.NET Core MVC 第6版 第一章

    目录 第一章 ASP.NET Core MVC 的前世今生 ASP.NET Core MVC 是一个微软公司开发的Web应用程序开发框架,它结合了MVC架构的高效性和简洁性,敏捷开发的思想和技术和.N ...

  9. ASP.NET Core MVC学习笔记

    最近由于疫情紧张,遂在家办公,在领导的带领下,学习了一下.Net Core MVC. 一,构建web应用 1.选择c#-所有平台-web  找到ASP.NET Core web应用程序 2.项目命名之 ...

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

    Http GET方法 首先我们来看看GET方法的Http请求,URL参数和ASP.NET Core MVC中Controller的Action方法参数匹配情况. 我定义一个UserController ...

随机推荐

  1. [转帖]CPU Utilization is Wrong

    Brendan Gregg's Blog home CPU Utilization is Wrong 09 May 2017 The metric we all use for CPU utiliza ...

  2. 文心一言 VS 讯飞星火 VS chatgpt (187)-- 算法导论14.1 4题

    四.用go语言,写出一个递归过程 OS-KEY-RANK(T,k),以一棵顺序统计树T和一个关键字k作为输入,要求返回 k 在由 T 表示的动态集合中的秩.假设 T 的所有关键字都不相同. 文心一言, ...

  3. STM32CubeMX教程25 PWR 电源管理 - 睡眠、停止和待机模式

    1.准备材料 开发板(正点原子stm32f407探索者开发板V2.4) STM32CubeMX软件(Version 6.10.0) 野火DAP仿真器 keil µVision5 IDE(MDK-Arm ...

  4. vite配置开发环境和生产环境

    为什么需要境变量的配置 在很多的时候,我们会遇见这样的问题. 开发环境的接口是:http://test.com/api 但是我们的生产环境地址是:http://yun.com/api 此时,我们打包的 ...

  5. 9.2 Windows驱动开发:内核解析PE结构导出表

    在笔者的上一篇文章<内核特征码扫描PE代码段>中LyShark带大家通过封装好的LySharkToolsUtilKernelBase函数实现了动态获取内核模块基址,并通过ntimage.h ...

  6. C/C++ 反汇编:针对加减乘除的还原

    算术运算通常是指,加减乘除四则运算,而计算机中的四则运算与数学中的有所不同,同样是实现算术运算,高级语言与汇编语言的实现思路完全不同,往往一个简单的减法运算,都要几条指令的配合才能得出计算结果,而为了 ...

  7. Socket.D v2.3.9 发布(增加 node.js server 实现)

    Socket.D 是基于"事件"和"语义消息""流"的网络应用层传输协议.有用户说,"Socket.D 之于 Socket,尤如 ...

  8. 答疑解惑:解释在Mybatis-Spring下@Mapper与@MapperScan为何不能同时生效以及实现动态条件注册Mapper接口

    若项目中使用了@MapperScan注解后,则@Mapper注解不再生效, 原因是:@MapperScan注解 会执行@Import(MapperScannerRegistrar.class),而Ma ...

  9. TActionManager Delphi 超级方便的快捷键 草草

    delphi 中用快捷键 草 实在是 太简单了 . 自己摸索出来的 方法 --------------------------------------------------------------- ...

  10. 《ASP.NET Core 与 RESTful API 开发实战》-- (第8章)-- 读书笔记(上)

    第 8 章 认证和安全 8.1 认证 认证(Authentication)是指验证用户身份的过程,授权(Authorization)是验证一个已经通过认证的用户是否有权限做某些事的过程 常见的 HTT ...