详解 .Net6 Minimal API 的使用方式

随着 .Net6 的发布,微软也改进了对之前 ASP.NET Core 构建方式,使用了新的 Minimal API 模式。以前默认的方式是需要在 Startup 中注册 IOC 和中间件相关,但是在 Minimal API 模式下你只需要简单的写几行代码就可以构建一个 ASP.NET Core的Web 应用,可谓非常的简单,加之配合 c# 的 global using 和 Program 的顶级声明方式,使得 Minimal API 变得更为简洁,不得不说 .NET 团队在 .NET 上近几年下了不少功夫,接下来我们就来大致介绍下这种极简的使用模式。

1. 使用方式

使用 Visual Studio 2022 新建的 ASP.NET Core 6 的项目,默认的方式就是 Minimal API 模式,整个 Web 程序的结构看起来更加简单,再i加上微软对 Lambda 的改进,使其可以对 Lambda 参数进行 Attribute 标记,有的场景甚至可以放弃去定义 Controller 类了。

2. 几行代码构建Web程序

使用 Minimal API 最简单的方式就是能通过三行代码就可以构建一个 WebApi 的程序,代码如下:

var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World");
app.Run();

是的你没有看错,仅仅这样运行起来就可以,默认监听的 http://localhost:5000 和 https://localhost:5001,所以直接在浏览器输入http://localhost:5000地址就可以看到浏览器输出Hello World字样。

3. 更改监听地址

如果你想更改它监听的服务端口,可以使用如下的方式:

var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World");
app.Run("http://localhost:6666");

如果想同时监听多个端口的话,可以使用如下的方式:

var app = WebApplication.Create(args);
app.Urls.Add("http://localhost:6666");
app.Urls.Add("http://localhost:8888");
app.MapGet("/", () => "Hello World");
app.Run();

或者是直接通过环境变量的方式设置监听信息,设置环境变量ASPNETCORE_URLS的值为完整的监听URL地址,这样的话就可以直接省略了在程序中配置相关信息了。

ASPNETCORE_URLS=http://localhost:6666

如果设置多个监听的URL地址的话可以在多个地址之间使用分号;隔开多个值:

ASPNETCORE_URLS=http://localhost:6666;https://localhost:8888

如果想监听本机所有Ip地址,可以使用如下方式:

var app = WebApplication.Create(args);
app.Urls.Add("http://*:6666");
app.Urls.Add("http://+:8888");
app.Urls.Add("http://0.0.0.0:9999");
app.MapGet("/", () => "Hello World");
app.Run();

同样的也可以使用添加环境变量的方式添加监听地址:

ASPNETCORE_URLS=http://*:6666;https://+:8888;http://0.0.0.0:9999

4. 日志操作

日志操作也是比较常用的操作,在Minimal API中微软干脆把它提出来,直接简化了操作,如下所示:

var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddJsonConsole();
var app = builder.Build();
app.Logger.LogInformation("读取到的配置信息:{content}", builder.Configuration.GetSection("consul").Get<ConsulOption>());
app.Run();

5. 基础环境配置

无论我们在之前的.Net Core开发或者现在的.Net6开发都有基础环境的配置,它包括 ApplicationName、ContentRootPath、 EnvironmentName相关,不过在Minimal API中,可以通过统一的方式去配置:

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{ApplicationName = typeof(Program).Assembly.FullName,ContentRootPath = Directory.GetCurrentDirectory(),EnvironmentName = Environments.Staging
});Console.WriteLine($"应用程序名称: {builder.Environment.ApplicationName}");
Console.WriteLine($"环境变量: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot目录: {builder.Environment.ContentRootPath}");var app = builder.Build();

或者是通过环境变量的方式去配置,最终实现的效果都是一样的。

  • ASPNETCORE_ENVIRONMENT
  • ASPNETCORE_CONTENTROOT
  • ASPNETCORE_APPLICATIONNAME

6. 主机相关设置

我们在之前的.Net Core开发模式中,程序的启动基本都是通过构建主机的方式,比如之前的Web主机或者后来的泛型主机,在Minimal API中同样可以进行这些操作,比如我们模拟一下之前泛型主机配置Web程序的方式:

var builder = WebApplication.CreateBuilder(args);
builder.Host.ConfigureDefaults(args).ConfigureWebHostDefaults(webBuilder =>
{webBuilder.UseStartup<Startup>();
});var app = builder.Build();

如果只是配置Web主机的话Minimal API还提供了另一种更直接的方式,如下所示:

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseStartup<Startup>();
builder.WebHost.UseWebRoot("webroot");var app = builder.Build();

7. 默认容器替换

很多时候我们在使用IOC的时候会使用其他三方的IOC框架,比如大家耳熟能详的Autofac,我们之前也介绍过其本质方式就是使用UseServiceProviderFactory中替换容器的注册和服务的提供,在Minimal API中可以使用如下的方式去操作:

var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
//之前在Startup中配置ConfigureContainer可以使用如下方式
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));var app = builder.Build();

8. 中间件相关

相信大家都已经仔细看过了WebApplication.CreateBuilder(args).Build()通过这种方式构建出来的是一个WebApplication类的实例,而WebApplication正是实现了 IApplicationBuilder接口。所以其本质还是和我们之前使用Startup中的Configure方法的方式是一致的,比如我们配置一个Swagger程序为例:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();var app = builder.Build();
//判断环境变量
if (app.Environment.IsDevelopment())
{//异常处理中间件app.UseDeveloperExceptionPage();app.UseSwagger();app.UseSwaggerUI();
}
//启用静态文件
app.UseStaticFiles();app.UseAuthorization();
app.MapControllers();app.Run();

常用的中间件配置还是和之前是一样的,因为本质都是IApplicationBuilder的扩展方法,我们这里简单列举一下:

中间件名称描述API
Authentication认证中间件app.UseAuthentication()
Authorization授权中间件.app.UseAuthorization()
CORS跨域中间件.app.UseCors()
Exception Handler全局异常处理中间件.app.UseExceptionHandler()
Forwarded Headers代理头信息转发中间件.app.UseForwardedHeaders()
HTTPS RedirectionHttps重定向中间件.app.UseHttpsRedirection()
HTTP Strict Transport Security (HSTS)特殊响应头的安全增强中间件.app.UseHsts()
Request LoggingHTTP请求和响应日志中间件.app.UseHttpLogging()
Response Caching输出缓存中间件.app.UseResponseCaching()
Response Compression响应压缩中间件.app.UseResponseCompression()
SessionSession中间件app.UseSession()
Static Files静态文件中间件.app.UseStaticFiles(), app.UseFileServer()
WebSocketsWebSocket支持中间件.app.UseWebSockets()

9. 请求处理

我们可以使用WebApplication中的Map{HTTPMethod}相关的扩展方法来处理不同方式的Http请求,比如以下示例中处理Get、Post、Put、Delete相关的请求:

app.MapGet("/", () => "Hello GET");
app.MapPost("/", () => "Hello POST");
app.MapPut("/", () => "Hello PUT");
app.MapDelete("/", () => "Hello DELETE");

如果想让一个路由地址可以处理多种Http方法的请求,可以使用MapMethods方法,如下所示:

app.MapMethods("/multiple", new[] { "GET", "POST","PUT","DELETE" }, (HttpRequest req) => $"Current Http Method Is {req.Method}" );

通过上面的示例我们不仅看到了处理不同Http请求的方式,还可以看到Minimal Api可以根据委托的类型自行推断如何处理请求,比如上面的示例,我们没有写Response Write相关的代码,但是输出的却是委托里的内容,因为我们上面示例中的委托都满足Func的形式,所以Minimal Api自动处理并输出返回的信息,其实只要满足委托类型的它都可以处理,接下来咱们来简单一下,首先是本地函数的形式:

static string LocalFunction() => "This is local function";
app.MapGet("/local-fun", LocalFunction);

还可以是类的实例方法:

HelloHandler helloHandler = new HelloHandler();
app.MapGet("/instance-method", helloHandler.Hello);class HelloHandler
{public string Hello(){return "Hello World";}
}

亦或者是类的静态方法:

app.MapGet("/static-method", HelloHandler.SayHello);class HelloHandler
{public static string SayHello(string name){return $"Hello {name}";}
}

其实本质都是一样的,那就是将他们转换为可执行的委托,无论什么样的形式,能满足委托的条件即可。

10. 路由约束

Minimal Api还支持在对路由规则的约束,这个和我们之前使用UseEndpoints的方式类似,比如我约束路由参数只能为整型,如果不满足的话会返回404。

app.MapGet("/users/{userId:int}", (int userId) => $"user id is {userId}");
app.MapGet("/user/{name:length(20)}", (string name) => $"user name is {name}");

经常使用的路由约束还有其他几个,也不是很多大概有如下几种,简单的列一下表格:

限制示例匹配示例说明
int{id:int}123456789, -123456789匹配任何整数
bool{active:bool}true, false匹配 truefalse. 忽略大小写
datetime{dob:datetime}2016-12-31, 2016-12-31 7:32pm匹配满足DateTime类型的值
decimal{price:decimal}49.99, -1,000.01匹配满足 decimal类型的值
double{height:double}1.234, -1,001.01e8匹配满足 double 类型的值
float{height:float}1.234, -1,001.01e8匹配满足 float 类型的值
guid{id:guid}CD2C1638-1638-72D5-1638-DEADBEEF1638匹配满足Guid类型的值
long{ticks:long}123456789, -123456789匹配满足 long 类型的值
minlength(value){username:minlength(4)}KOBE字符串长度必须是4个字符
maxlength(value){filename:maxlength(8)}CURRY字符串长度不能超过8个字符
length(length){filename:length(12)}somefile.txt字符串的字符长度必须是12个字符
length(min,max){filename:length(8,16)}somefile.txt字符串的字符长度必须介于8和l6之间
min(value){age:min(18)}20整数值必须大于18
max(value){age:max(120)}119整数值必须小于120
range(min,max){age:range(18,120)}100整数值必须介于18和120之间
alpha{name:alpha}Rick字符串必须由一个或多个a-z的字母字符组成,且不区分大小写。
regex(expression){ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}123-45-6789字符串必须与指定的正则表达式匹配。
required{name:required}JAMES请求信息必须包含该参数

11. 模型绑定

在我们之前使用ASP.NET Core Controller方式开发的话,模型绑定是肯定会用到的,它的作用就是简化我们解析Http请求信息也是MVC框架的核心功能,它可以将请求信息直接映射成c#的简单类型或者POCO上面。在Minimal Api的Map{HTTPMethod}相关方法中同样可以进行丰富的模型绑定操作,目前可以支持的绑定源有如下几种:

  • Route(路由参数)
  • QueryString
  • Header
  • Body(比如JSON)
  • Services(即通过IServiceCollection注册的类型)
  • 自定义绑定

1) 绑定示例
接下来我们首先看一下绑定路由参数:

app.MapGet("/sayhello/{name}", (string name) => $"Hello {name}");

还可以使用路由和querystring的混用方式:

app.MapGet("/sayhello/{name}", (string name,int? age) => $"my name is {name},age {age}");

这里需要注意的是,我的age参数加了可以为空的标识,如果不加的话则必须要在url的请求参数中传递age参数,否则将报错,这个和我们之前的操作还是有区别的。

具体的类也可以进行模型绑定,比如咱们这里定义了名为Goods的POCO进行演示:

app.MapPost("/goods",(Goods goods)=>$"商品{goods.GName}添加成功");class Goods
{public int GId { get; set; }public string GName { get; set; }public decimal Price { get; set; }
}

需要注意的是HTTP方法GET、HEAD、OPTIONS、DELETE将不会从body进行模型绑定,如果需要在Get请求中获取Body信息,可以直接从HttpRequest中读取它。

如果我们需要使用通过IServiceCollection注册的具体实例,可以以通过模型绑定的方式进行操作(很多人喜欢叫它方法注入,但是严格来说却是是通过定义模型绑定的相关操作实现的),而且还简化了具体操作,我们就不需要在具体的参数上进行FromServicesAttribute标记了。

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<Person>(provider => new() { Id = 1, Name = "yi念之间", Sex = "Man" });
var app = builder.Build();app.MapGet("/", (Person person) => $"Hello {person.Name}!");
app.Run();

如果是混合使用的话,也可以不用指定具体的BindSource进行标记了,前提是这些值的名称在不同的绑定来源中是唯一的,这种感觉让我想到了刚开始学习MVC4.0的时候模型绑定的随意性,比如下面的例子:

app.MapGet("/sayhello/{name}", (string name,int? age,Person person) => $"my name is {name},age {age}, sex {person.Sex}");

上面示例的模型绑定参数来源可以是:

参数绑定来源
name路由参数
agequerystring
person依赖注入

不仅仅如此,它还支持更复杂的方式,这使得模型绑定更为灵活,比如以下示例:

app.MapPost("/goods",(Goods goods, Person person) =>$"{person.Name}添加商品{goods.GName}成功");

它的模型绑定的值来源可以是:

参数绑定来源
goodsbody里的json
person依赖注入

当然如果你想让模型绑定的来源更清晰,或者就想指定具体参数的绑定来源那也是可以的,反正就是各种灵活,比如上面的示例改造一下,这样就可以显示声明:

app.MapPost("/goods",([FromBody]Goods goods, [FromServices]Person person) =>$"{person.Name}添加商品{goods.GName}成功");

很多时候我们可能通过定义类和方法的方式来声明Map相关方法的执行委托,这个时候呢依然可以进行灵活的模型绑定,而且可能你也发现了,直接通过lambda表达式的方式虽然支持可空类型,但是它不支持缺省参数,也就是咱们说的方法默认参数的形式,比如:

app.MapPost("/goods", GoodsHandler.AddGoods);class GoodsHandler
{public static string AddGoods(Goods goods, Person person, int age = 20) => $"{person.Name}添加商品{goods.GName}成功";
}

当然你也可以对AddGoods方法的参数进行显示的模型绑定处理,十分的灵活。

public static string AddGoods([FromBody] Goods goods, [FromServices] Person person, [FromQuery]int age = 20) => $"{person.Name}添加商品{goods.GName}成功";

在使用Map相关方法的时候,由于是在Program入口程序或者其他POCO中直接编写相关逻辑的,因此需要用到HttpContext、HttpRequest、HttpResponse相关实例的时候没办法进行直接操作,这个时候也需要通过模型绑定的方式获取对应实例:

app.MapGet("/getcontext",(HttpContext context,HttpRequest request,HttpResponse response) => response.WriteAsync($"IP:{context.Connection.RemoteIpAddress},Request Method:{request.Method}"));

2) 自定义绑定
Minimal Api采用了一种新的方式来自定义模型绑定,这种方式是一种基于约定的方式,无需提前注册,也无需集成什么类或者实现什么接口,只需要在自定义的类中存在TryParse和BindAsync方法即可,这两个方法的区别如下:

TryParse方法是对路由参数、url参数、header相关的信息进行转换绑定
BindAsync可以对任何请求的信息进行转换绑定,功能比TryParse要强大
接下来我们分别演示一下这两种方式的使用方法,首先是TryParse方法:

app.MapGet("/address/getarray",(Address address) => address.Addresses);public class Address
{public List<string>? Addresses { get; set; }public static bool TryParse(string? addressStr, IFormatProvider? provider, out Address? address){var addresses = addressStr?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);if (addresses != null && addresses.Any()){address = new Address { Addresses = addresses.ToList() };return true;}address = new Address();return false;}
}

这样就可以完成简单的转换绑定操作,从写法上我们可以看到,TryParse方法确实存在一定的限制,不过操作起来比较简单,这个时候我们模拟请求:

http://localhost:5036/address/getarray?address=山东,山西,河南,河北

请求完成会得到如下结果:

["山东","山西","河南", "河北"]

然后我们改造一下上面的例子使用BindAsync的方式进行结果转换,看一下它们操作的不同:

app.MapGet("/address/getarray",(Address address) => address.Addresses);public class Address
{public List<string>? Addresses { get; set; }public static ValueTask<Address?> BindAsync(HttpContext context, ParameterInfo parameter){string addressStr = context.Request.Query["address"];var addresses = addressStr?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);Address address = new();if (addresses != null && addresses.Any()){address.Addresses = addresses.ToList();return ValueTask.FromResult<Address?>(address);}return ValueTask.FromResult<Address?>(address);}
}

同样请求http://localhost:5036/address/getarray?address=山东,山西,河南,河北 地址会得到和上面相同的结果,到底如何选择同学们可以按需使用,得到的效果都是一样的。如果类中同时存在TryParse和BindAsync方法,那么只会执行BindAsync方法。

输出结果:

相信通过上面的其他示例演示,我们大概看到了一些在Minimal Api中的结果输出,总结起来其实可以分为三种情况:

IResult 结果输出,可以包含任何值得输出,包含异步任务Task和ValueTask
string 文本类型输出,包含异步任务Task和ValueTask
T 对象类型输出,比如自定义的实体、匿名对象等,包含异步任务 Task和ValueTask
接下来简单演示几个例子来简单看一下具体是如何操作的,首先最简单的就是输出文本类型:

app.MapGet("/hello", () => "Hello World");

然后输出一个对象类型,对象类型可以包含对象或集合甚至匿名对象,或者是咱们上面演示过的HttpResponse对象,这里的对象可以理解为面向对象的那个对象,满足Response输出要求即可。

app.MapGet("/simple", () => new { Message = "Hello World" });//或者是app.MapGet("/array",()=>new string[] { "Hello", "World" });//亦或者是EF的返回结果app.Map("/student",(SchoolContext dbContext,int classId)=>dbContext.Student.Where(i=>i.ClassId==classId));

还有一种是微软帮我们封装好的一种形式,即返回的是IResult类型的结果,微软也是很贴心的为我们统一封装了一个静态的Results类,方便我们使用,简单演示一下这种操作:

//成功结果app.MapGet("/success",()=> Results.Ok("Success"));//失败结果app.MapGet("/fail", () => Results.BadRequest("fail"));//404结果app.MapGet("/404", () => Results.NotFound());//根据逻辑判断返回app.Map("/student", (SchoolContext dbContext, int classId) => {var classStudents = dbContext.Student.Where(i => i.ClassId == classId);return classStudents.Any() ? Results.Ok(classStudents) : Results.NotFound();});

上面我们也提到了Results类其实是微软帮我们多封装了一层,它里面的所有静态方法都是返回IResult的接口实例,这个接口有许多实现的类,满足不同的输出结果,比如Results.File(“foo.text”)方法,本质就是返回一个FileContentResult类型的实例。

public static IResult File(byte[] fileContents,string? contentType = null,string? fileDownloadName = null,bool enableRangeProcessing = false,DateTimeOffset? lastModified = null,EntityTagHeaderValue? entityTag = null)=> new FileContentResult(fileContents, contentType){FileDownloadName = fileDownloadName,EnableRangeProcessing = enableRangeProcessing,LastModified = lastModified,EntityTag = entityTag,};

亦或者Results.Json(new { Message=“Hello World” }),本质就是返回一个JsonResult类型的实例。

public static IResult Json(object? data, JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null)=> new JsonResult{Value = data,JsonSerializerOptions = options,ContentType = contentType,StatusCode = statusCode,};

当然我们也可以自定义IResult的实例,比如我们要输出一段html代码。

微软很贴心的为我们提供了专门扩展Results的扩展类IResultExtensions基于这个类我们才能完成IResult的扩展:

static class ResultsExtensions{//基于IResultExtensions写扩展方法public static IResult Html(this IResultExtensions resultExtensions, string html){ArgumentNullException.ThrowIfNull(resultExtensions, nameof(resultExtensions));//自定义的HtmlResult是IResult的实现类return new HtmlResult(html);}}class HtmlResult:IResult{//用于接收html字符串private readonly string _html;public HtmlResult(string html){_html = html;}/// <summary>/// 在该方法写自己的输出逻辑即可/// </summary>/// <returns></returns>public Task ExecuteAsync(HttpContext httpContext){httpContext.Response.ContentType = MediaTypeNames.Text.Html;httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);return httpContext.Response.WriteAsync(_html);}}

定义完成这些我们就可以直接在Results类中使用我们定义的扩展方法了,使用方式如下:

app.MapGet("/hello/{name}", (string name) => Results.Extensions.Html(@$"<html><head><title>Index</title></head><body><h1>Hello {name}</h1></body></html>"));

这里需要注意的是,我们自定义的扩展方法一定是基于IResultExtensions扩展的,然后再使用的时候注意是使用的Results.Extensions这个属性,因为这个属性是IResultExtensions类型的,然后就是我们自己扩展的Results.Extensions.Html方法。

12. 总结

本文我们主要是介绍了ASP.NET Core 6 Minimal API的常用的使用方式,相信大家对此也有了一定的了解,在.NET6中也是默认的项目方式,整体来说却是非常的简单、简洁、强大、灵活,不得不说Minimal API却是在很多场景都非常适用的。当然我也在其它地方看到过关于它的评价,褒贬不一吧,笔者认为,没有任何一种技术是银弹,存在即合理。如果你的项目够规范够合理,那么使用Minimal API绝对够用,如果不想用或者用不了也没关系,能最终实现需要的结果就好。

参考资料

  1. C#教程
  2. 编程宝库


---------------------
作者:编程宝库
来源:CSDN
原文:https://blog.csdn.net/wanghao72214/article/details/121682606
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/284244.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.NET 20周年专访 - 张善友:.NET 技术是如何赋能并改变世界的

点击蓝字关注我们今年是 .NET 发布20周年&#xff0c;值此20周年之际&#xff0c;微软 Reactor 特别策划了 .NET 20周年系列主题专访。我们邀请了数位中国 .NET 领域的技术专家以及社区名人&#xff0c;来聊聊他们与 .NET 的情缘、认识 .NET 的契机、选择 .NET 的理由&#xff…

【ArcGIS错误异常100问】之005:ArcGIS字段计算器python中文编码问题解决

问题描述&#xff1a; 现因工作的需要&#xff0c;对照2017最新版&#xff1a;《土地利用现状分类》&#xff08;GBT 21010-2017&#xff09;&#xff0c;需根据DLMC对DLBM进行批量修改&#xff0c;如旱地是0103&#xff0c;其他林地是0307等&#xff0c;共计19种用地类型。 问…

【ArcGIS微课1000例】0055:根据图层创建自定义图例符号案例教程

在利用ArcGIS作图时,有时候需要根据线状或面状图层自己的矢量形状去创建图例项目符号,本文讲解根据图层创建自定义图例符号。 本实验使用的数据为配套案例数据包中的0055.rar中的水库数据。 文章目录 1. 添加“新建图例图面形状”工具2. 根据图层形状创建符号3. 绘制形状符号…

jQuery 3.3.1已经发布,开发团队正在准备4.0版本

\看新闻很累&#xff1f;看技术新闻更累&#xff1f;试试下载InfoQ手机客户端&#xff0c;每天上下班路上听新闻&#xff0c;有趣还有料&#xff01;\\\jQuery 3.3.1已经发布&#xff0c;其中包含了许多新特性也提出要移除几个之前的特性&#xff0c;移除一些特性是为了jQuery …

C#.NET版本、Visual Studio版本对应关系

C#版本.NET版本Visual Studio版本发布日期特性C# 1.0.NET Framework 1.0Visual Studio .NET 20022002-02-13委托、事件C# 1.1.NET Framework 1.1Visual Studio .NET 20032003-04-24APM&#xff08;异步编程模型&#xff09;C# 2.0.NET Framework 2.0Visual Studio 20052005-11-…

真魔法!图形化管理 Kafka 超轻量的自动化工具

Kafka Magic[1] 是一个用于处理 Apache Kafka 集群的 GUI 工具。它可以查找和显示消息、在 Topic 之间转换和移动消息、查看和更新模式、管理 Topic 以及自动化复杂任务。Kafka Magic 通过方便的用户界面促进 Topic 管理、QA 和集成测试。Kafka Magic Community Edition 可免费…

前端工程构建工具——Yeoman

一、Yeoman 简介 通常在开发新项目时我们都需要配置工程环境&#xff0c;开发目录&#xff0c;需要下载一些库、框架文件&#xff08;如 jQuery、Backbone 等&#xff09;&#xff0c;配置编译环境&#xff08;Less、Sass、Coffeescript等&#xff09;&#xff0c;甚至还要配置…

【FME实战教程】001:FME2020中文安装图文教程(附安装包下载)

文章目录1. 安装license2. 安装FME Desktop3. 安装中文语言4. FME软件下载地址1. 安装license 打开软件安装包中的fme-flexnet-win-x64.msi&#xff0c;如下图所示&#xff1a; 点击Next。 点击Next。 单击install。 点击finish&#xff0c;完成。 &#xff08;1&#xff09;修…

算法导论 第三部分——基本数据结构——第14章:数据结构的扩张

本章通过扩张红黑树构造出两种数据结构&#xff1a;动态顺序统计和区间树。 1、动态顺序统计&#xff1a;查找倒数第i小的数据 复杂度为 lg(n) 为什么是扩张红黑树而不是搜索二叉树或者二叉树&#xff1f; 相对于搜索二叉树&#xff0c;红黑树的平衡性更好&#xff0c;高度在l…

/hgfs下无共享文件夹?/mnt下没有hgfs文件夹?vmhgfs-fuse:找不到命令?

前言&#xff1a;最近在使用linux的过程中&#xff0c;需要在宿主操作系统与客户操作系统间建立共享文件夹&#xff0c;遇到了些许问题&#xff0c;在网上参考了许多文章与各种尝试后&#xff0c;现得以解决&#xff0c;分享如下。1、系统环境&#xff1a;宿主操作系统&#xf…

GraphQL入门有

本文将从GraphQL是什么&#xff0c;为什么要使用GraphQL&#xff0c;使用GraphQL创建简单例子&#xff0c;以及GraphQL实战&#xff0c;四个方面对GraphQL进行阐述。说得不对的地方&#xff0c;希望大家指出斧正。 github项目地址&#xff1a;https://github.com/Charming2015/…

对话庄表伟:开源第一课

庄表伟目前就职于华为的开源管理中心。自2014年开源社成立之初&#xff0c;他便友情参与了开源社的筹办工作。2017年&#xff0c;开源社转型为完全由个人成员组成的组织&#xff0c;庄表伟就以个人身份加入了开源社。作为开源社理事&#xff0c;当被问到“为什么要参选”时&…

【FME实战教程】002:FME完美实现CAD数据转shp案例教程(以三调土地利用现状数据为例)

FME完美实现CAD数据转shp案例教程&#xff08;以三调土地利用数据为例&#xff09; 文章目录1. cad数据预览2. 转换过程3. shp数据预览1. cad数据预览 2. 转换过程 &#xff08;1&#xff09;打开FME Desktop2020中文软件&#xff0c;点击【新建】。 &#xff08;2&#xff09…

Linux学习之01_基础命令介绍

初学Linux&#xff0c;还在摸索中&#xff0c;在这个过程中希望能记录下学习到的东西&#xff0c;参考的的书籍为《鸟哥的Linux私房菜》 在这里学到的主要命令有这几个&#xff1a; data cal bc man shutdown sync 1、基础命令操作 data----显示日期与实践的命令 cal----显示日…

穷举算法实例

public static void main(String[] args) {Scanner scnew Scanner(System.in);System.out.println("输入头的个数:");int headsc.nextInt();System.out.println("输入腿的个数:");int footsc.nextInt();for(int i0;i<head;i){//假设兔子的数量为iint jh…

VMware Workstation All Key

官方下载&#xff1a;https://www.vmware.com/products/workstation-pro/workstation-pro-evaluation.html 懒人打包&#xff1a;链接:https://pan.baidu.com/s/1kWJRfjL 密码:wzce 注&#xff1a;如果是WinXP或32位系统请用 10.0 版本 VMware 所有版本永久许可证激活密钥&…

【GlobalMapper精品教程】017:KML generator快速将坐标转为KML文件

本文介绍KML generator软件,并快速将坐标转为KML文件的使用方法,并用globalmapper中打开kml文件加以验证。本专栏配套完整的案例数据包,请打开data017.rar获取软件及数据。 文章目录 1. KML文件介绍2. kml generator软件介绍2.1 单点KML制作2.2 Excel数据KML制作2.3 文本文件…

从cpp向qml文件传中文字符串的方法

Qt 使用Unicode编码来存储操作字符串&#xff0c;但很多情况下&#xff0c;我们不得不处理采用其他编码格式的数据&#xff0c;举例来说&#xff0c;中文多采用GBK和Big5编码&#xff0c;而日本则多采用Shift-JIS or ISO2022编码。将其他编码格式的字符串转化成采用Unicode编码…

Codeforces 746 G. New Roads

题目链接&#xff1a;http://codeforces.com/contest/746/problem/G mamaya&#xff0c;不知道YY了一个什么做法就这样过去了啊 2333 首先我显然可以随便构造出一棵树满足他所给出的深度要求。 但是他还对于叶子节点的数目有要求。 考虑首先构造一棵树最大化在满足给出的深度条…

模型验证组件 FluentValidation

FluentValidation 是 .NET 下的模型验证组件&#xff0c;和 ASP.NET MVC 基于Attribute 声明式验证的不同处&#xff0c;其利用表达式语法链式编程&#xff0c;使得验证组件与实体分开。正如 FluentValidation 的 介绍&#xff1a; A small validation library for .NET that u…