《ASP.NET Core 6框架揭秘》实例演示[02]:基于路由、MVC和gRPC的应用开发

ASP.NET Core可以视为一种底层框架,它为我们构建出了基于管道的请求处理模型,这个管道由一个服务器和多个中间件构成,而与路由相关的EndpointRoutingMiddleware和EndpointMiddleware是两个最为重要的中间件。MVC和gRPC开发框架就建立在路由基础上。本篇提供了四个实例用来演示如何利用路由、MVC和gRPC来开发API/APP。[本文节选《ASP.NET Core 6框架揭秘》第1章]

[113]路由的应用(源代码)
[114]开发MVC API(源代码)
[115]开发MVC APP(源代码)
[116]开发gRPC API(源代码)

[113]路由的应用

ASP.NET Core的路由是由EndpointRoutingMiddleware和EndpointMiddleware这两个中间件实现的,在所有预定义的中间件类中,这应该算是最重要的两个中间件了,因为不仅仅是MVC和gRPC框架建立在路由系统之上,后面介绍的Dapr.NET针对发布订阅和Actor编程模式也是如此。如下面的代码片段所示,我们在利用WebApplicationBuilder将代表承载应用的WebApplication对象构建出来之后,并没有注册任何的中间件,而是调用它的MapGet扩展方法注册了一个指向路径“/greet”的路由终结点(Endpoint)。该终结点的处理器是一个指向Greet方法的委托,意味着请求路径为“/greet”的GET请求会路由到这个终结点,并最终调用这个方法进行处理。

using App;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IGreeter, Greeter>().Configure<GreetingOptions>(builder.Configuration.GetSection("greeting"));
var app = builder.Build();
app.MapGet("/greet", Greet);
app.Run();static string Greet(IGreeter greeter) => greeter.Greet(DateTimeOffset.Now);

ASP.NET Core的路由系统的强大之处在于,我们可以使用任何类型的委托作为注册终结点的处理器,路由系统在调用处理器方法之前会“智能地”提取相应的数据初始化每一个参数。当方法执行之后,它还会针对我们具体返回的对象来对请求实施响应。对于我们提供的Greet方法来说,路由系统在调用它之前会利用依赖注入容器提供作为参数的IGreeter对象。由于返回的是一个字符串,文本经过编码后会直接作为响应的主体内容, 响应的内容类型(Content-Type)最终会被设置为“text/plain”。程序启动之后,如果我们利用浏览器请求“/greet”这个路径,针对当前时间解析出来的问候语会以图1的形式呈现出来。

e9fbbb2b27ee5e3a714c91f0c2b712b0.jpeg
图1 采用路由返回的问候

[114]开发MVC API

我们直接将上面演示的程序改写成MVC应用。MVC应用以Controller为核心,所有的请求总是指向定义在某个Controller类型中的某个Action方法。当应用接收到请求之后,会激活对应的Controller对象,并通过执行对应的Action方法来处理该请求。按照约定,合法的Controller类型必须是以“Controller”作为后缀命名的公共实例类型。我们一般会让定义的Controller类型派生自Controller基类以“借用”一些有用的API,但这不是必须的,比如下面定义的GreetingController就没有指定基类。

public class GreetingController
{[HttpGet("/greet")]    public string Greet([FromServices] IGreeter greeter) => greeter.Greet(DateTimeOffset.Now);
}

由于MVC框架是建立在路由系统之上的,定义在Controller类型中的Action方法最终会转换成一个或者多个注册到指定路径模板的终结点。对于定义在GreetingController类型中的Action方法Greet来说,我们通过标注的HttpGetAttrbute特性不仅为对应的路由终结点定义了针对HTTP方法的约束(该终结点仅限于处理GET请求),还同时指定了绑定的请求路径(“/greet”)。

依赖的服务可以直接注入到Controller类型中。具体来说,它支持两种注入形式,一种是注入到构造函数中,另一种则是直接注入到Action方法中。对于方法注入,对应参数上必须标注一个FromServiceAttribute特性。我们IGreeter对象就是采用这种方式注入注入到Greet方法中的。和路由系统针对返回对象的处理方式一样,MVC框架针对Action方法的返回值也会根据其类型进行针对性的处理。Greet方法直接返回的字符串会直接作为响应的主体内容,响应的内容类型(Content-Type)会被设置为“text/plain”。

在完成了针对GreetingController类型的定义之后,我们需要对入口程序进行如下的修改。如代码片段所示,在完成了针对IGreeter服务的注册和针对GreetingOptions配置选项的设置之后,我们调用同一个IServiceCollection对象的AddControllers扩展方法注册了与Controller相关服务的注册。在WebApplication对象被构建出来后,我们调用了它的MapControllers扩展方法将定义在所有Controller类型中的Action方法映射为对应的终结点。程序启动之后,如果我们利用浏览器请求“/greet”这个路径,我们依然会得到如图1的所示的输出结果。

using App;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IGreeter, Greeter>().Configure<GreetingOptions>(builder.Configuration.GetSection("greeting")).AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();

[115]开发MVC APP

上面改造的MVC程序并没有涉及到视图,请求的响应内容是由Action方法直接提供的,现在我们利用视图来呈现最终响应的内容。由于上个例子调用IServiceCollection接口的AddControllers扩展方法只会注册Controller相关的服务,现在我们得将其换成AddControllersWithViews方法。顾名思义,新的扩展方法会将视图相关的服务添加进来。

using App;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IGreeter, Greeter>().Configure<GreetingOptions>(builder.Configuration.GetSection("greeting")).AddControllersWithViews();
var app = builder.Build();
app.MapControllers();
app.Run();

我们对GreetinigController进行了改造。如下面的代码片段所示,我们让它继承Controller这个基类。Action方法Greet的返回类型改为IActionResult接口,具体返回的是通过View方法创建的代表默认视图(针对当前Action方法)的ViewResult对象。在Action方法返回之前,它还利用对ViewBag的设置将当前时间传递到呈现的视图中。

public class GreetingController : Controller
{[HttpGet("/greet")]    public IActionResult Greet(){ViewBag.Time = DateTimeOffset.Now;        return View();}
}

ASP.NET Core MVC采用Razior视图引擎,视图被定义成一个后缀名为.cshtml的文件,这是一个按照Razor语法编写的静态HTML和动态C#代码动态交织的文本文件。由于上面为了呈现试图调用的View方法没有指定任何参数,所以视图引擎会根据当前Controller的名称(“Greeting”)和Action的名称(“Greet”)去定位定义目标视图的.cshtml文件。为了迎合默认的视图定位规则,我们需要采用Action的名称来命名创建的视图文件(Greet.cshtml),并将其添加到“Views/Greeting”目录下。

@using App
@inject IGreeter Greeter;
<html><head><title>Greeting</title></head><body><p>@Greeter.Greet((DateTimeOffset)ViewBag.Time)</p></body>
</html>

上面这个代码片段就是添加的视图文件(Views/Greeting/Greet.cshtml)的内容。总体来说,这是一个HTML文档,除了在主体部分呈现的问候语文本(前置的@字符定义动态执行的C#表达式)是根据指定时间动态解析出来的,其他内容则均为静态的HTML。我们借助@inject指令将依赖的IGreeter对象以属性的形式注入进来,并且将属性名称设置为Greeter,所以我们可以在视图中直接调用它的Greet方法得到呈现的问候语。调用Greet方法指定的时间是GreetingController利用ViewBag传递过来的,所以我们可以直接利用它将其提取出来。程序启动之后,如果我们利用浏览器请求“/greet”这个路径,虽然浏览器也会呈现出相同的文本(如图2所示),但是响应的内容是完全不同的。之前响应的仅仅是内容类型为“text/plain”的单纯文本,现在响应则是一份完整的HTML文档,内容类型为“text/html”。

693769f8e2feb88491ee9a8b94697968.jpeg
图2 以试图形式返回的问候

[116]开发gRPC API

虽然Vistual Studio提供了创建gRPC的项目模板,该模板提供的脚手架会自动为我们创建一系列的初始文件,同时也会对项目做一些初始设置,但这反而是笔者不想要的,至少是不希望在这里使用这个模板。和前面一样,我们希望演示的实例只包含最本质和必要的元素,所以我们选择在一个空的解决方案上构建gRPC应用。

e731a0916709d3796bc881608aa5fb82.png
图3 gRPC解决方案

如图3所示,我们在一个空的解决方案上添加了三个项目。Proto是一个空的类库项目,我们将会使用它来存放标准的Proto Buffers消息和gRPC服务的定义;Server是一个空的ASP.NET Core应用,gRPC服务的实现类型就放在这里,它同时也是承载gRPC服务的应用。Client是一个控制台程序,我们用它来模拟调用gRPC服务的客户端。gRPC是语言中立的远程调用框架,gRPC服务契约使用到的数据类型都采用标准的定义方式。具体来说,gRPC传输的数据采用Proto Buffers协议进行序列化,Proto Buffers采用高效紧凑的二进制编码。我们将用于定义数据类型和服务的Proto Buffers文件定义在Proto项目中,在这之前我们需要为这个空的类库项目添加针对“Grpc.AspNetCore”这个NuGet包的引用。

不再使用简单的“Hello World”,现在我们为演示的gPRC服务指定另一种稍微“复杂”一点的应用场景——用它来完成简单的加、减、乘、除运算。我们在Proto项目中添加一个名为Calculator.proto的文本文件,并在其中以如下的形式将Calculator这个rGPC服务定义出来。如代码片段所示,这个服务包含四个操作,它们的输入和输出都被定义成Proto Buffers消息。作为输入的InputMessage消息包含两个整型的数据成员(表示运算的两个操作数)。返回的OutpuMessage消息除了通过result表示计算结果外,还具有status和error两个成员,前者表示计算状态(成功还是失败),后者提供计算失败时的错误消息。

syntax = "proto3";
option csharp_namespace = "App";service Calculator {rpc Add (InputMessage) returns (OutpuMessage);rpc Substract (InputMessage) returns (OutpuMessage);rpc Multiply (InputMessage) returns (OutpuMessage);rpc Divide (InputMessage) returns (OutpuMessage);}message InputMessage {int32 x = 1;int32 y = 2;}message OutpuMessage {int32 status = 1;int32 result = 2;string error = 3;}

创建的Calculator.proto文件无法直接使用,我们需要利用内置的代码生成器将它转换成.cs代码。具体的作为很简单,我们只需要在Visual Studio的解决方案窗口中右键选择这个文件,打开如图4所示的属性对话框。我们在Build Action下拉列表中选择“Protobuf compiler”选项,同时在gRPC Stub Classes下拉列表中选择“Client and Server”。

3720dbc6c9cca5e8f45f4502b411dc8a.png
图4 Calculator.proto文件属性对话框

做了这样的设置之后,在任何时对Calculator.proto文件所作的改变都将触发代码的自动生成,具体生成的.cs文件会自动保存在obj目录下。由于在gRPC Stub Classes下拉列表中选择了“Client and Server”选项,所以它不仅会生成服务端用来定义服务实现类型的Stub类,还会生成客户端用来调用服务的Stub类。上面以可视化形式所作的设置最终会体现在项目文件(Proto.csproj)上,所以我们直接修改此文件也可以达到相同的目的,如下所示的就是这个文件的完整内容。

<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>net6.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable></PropertyGroup><ItemGroup><None Remove="Calculator.proto" /></ItemGroup><ItemGroup><PackageReference Include="Grpc.AspNetCore" Version="2.40.0" /></ItemGroup><ItemGroup><Protobuf Include="Calculator.proto" /></ItemGroup>
</Project>

Proto项目中的Calculator.proto文件仅仅是按照标准的形式定义的“服务契约”,我们需要在Server项目中定义具体的实现类型。在添加了针对Proto项目的引用之后,我们定义了如下这个名为CalculatorService的gRPC服务实现类型。如代码片段所示,我们让CalculatorService类型继承自一个内嵌于Calculator中的CalculatorBase类型,这个Calculator类型就是根据Calculator.proto生成的一个类型。

public class CalculatorService : Calculator.CalculatorBase
{private readonly ILogger _logger;public CalculatorService(ILogger<CalculatorService> logger) => _logger = logger;public override Task<OutpuMessage> Add(InputMessage request,  ServerCallContext context) => InvokeAsync((op1, op2) => op1 + op2, request);public override Task<OutpuMessage> Substract(InputMessage request,    ServerCallContext context) => InvokeAsync((op1, op2) => op1 - op2, request);public override Task<OutpuMessage> Multiply(InputMessage request,     ServerCallContext context) => InvokeAsync((op1, op2) => op1 * op2, request);public override Task<OutpuMessage> Divide(InputMessage request,     ServerCallContext context) => InvokeAsync((op1, op2) => op1 / op2, request);private Task<OutpuMessage> InvokeAsync(Func<int, int, int> calculate,     InputMessage input){OutpuMessage output;try{output = new OutpuMessage { Status = 0, Result = calculate(input.X, input.Y) };}catch (Exception ex){_logger.LogError(ex, "Calculation error.");output = new OutpuMessage { Status = 1, Error = ex.ToString() };}return Task.FromResult(output);}
}

Calculator.proto文件为Calcultor服务定义的四个操作会转换成CalculatorBase类型中对应的虚方法,我们按照上面的方式重写了它们。在完成了针对gRPC服务实现类型的定义之后,我们需要对承载它的入口程序定义编写如下的代码。由于gRPC采用HTTP2传输协议,所以在利用WebApplicationBuilder的WebHost属性得到对应的IWebHostBuilder对象,我们调用其ConfigureKestrel扩展方法让默认注册的Kestrel服务器监听的终结点默认采用HTTP2协议。gRPC相关的服务通过调用IServiceCollection接口的AddGrpc扩展方法进行注册。由于gRPC也是建立在路由系统之上的,定义在服务中的每个操作最终也会转换成相应的路由终结点,这些终结点的生成和注册是通过调用WebApplication对象的MapGrpcService<TService>扩展方法完成的。

using App;
using Microsoft.AspNetCore.Server.Kestrel.Core;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(kestrel => kestrel.ConfigureEndpointDefaults( endpoint => endpoint.Protocols =  HttpProtocols.Http2));
builder.Services.AddGrpc();
var app = builder.Build();
app.MapGrpcService<CalculatorService>();
app.Run();

Calculator.proto文件生成的代码包含用来调用对应gRPC服务的Stub类,所以模拟客户端的Client项目也需要添加对Proto项目的引用。在此之后,我们可以编写如下的程序调用gRPC服务完成四种基本的数学运算。

using App;using Grpc.Core;using Grpc.Net.Client;using var channel = GrpcChannel.ForAddress("http://localhost:5000");var client = new Calculator.CalculatorClient(channel);var inputMessage = new InputMessage { X = 1, Y = 0 };await InvokeAsync(input => client.AddAsync(input), inputMessage, "+");await InvokeAsync(input => client.SubstractAsync(input), inputMessage, "-");await InvokeAsync(input => client.MultiplyAsync(input), inputMessage, "*");await InvokeAsync(input => client.DivideAsync(input), inputMessage, "/");static async Task InvokeAsync(Func<InputMessage, AsyncUnaryCall<OutpuMessage>> invoker,  InputMessage input, string @operator){var output = await invoker(input);if (output.Status == 0){Console.WriteLine($"{input.X}{@operator}{input.Y}={output.Result}");}else{Console.WriteLine(output.Error);}}

如上面的代码片段所示,我们通过调用GrpcChannel类型的静态方法ForAddress针对gRPC服务的地址“http://localhost:5000”创建了一个GrpcChannel对象,该对象表示与服务进行通信的“信道(Channel)”。我们利用它创建了一个CalculatorClient对象作为调用gRPC服务的客户端或者代理,CalculatorClient类型同样是内嵌在生成的Calculator类型中。最终我们利用这个代理完成了针对四种基本运算的服务调用,具体的gRPC调用实现在InvokeAsync这个本地方法中。接下来我们以命令行的方式先后启动Server和Client应用,客户端和服务端控制台上会呈现出如图5所示的输出结果。由于我们传入的参数分别为1和0,所以除了除法运算,其它三此调用都会返回成功的结果,针对除法的调用则会将错误信息呈现出来。由于CalculatorService进行了异常处理,并且将异常信息以日志的形式记录了下来,所以错误信息也输出到了服务端的控制台上。

94c762fd4cd7b85b36d360b5ffd15f3f.png

图5 gRPC应用的承载与调用

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

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

相关文章

什么是 JWT -- JSON WEB TOKEN

什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准&#xff08;(RFC 7519).该token被设计为紧凑且安全的&#xff0c;特别适用于分布式站点的单点登录&#xff08;SSO&#xff09;场景。JWT的声明一般被用来在身份提供者和服务提…

【ArcGIS微课1000例】0024:ArcGIS如何连接文件夹、设认工作目录、默认地理数据库、相对路径与绝对路径?

ArcGIS软件在初次安装完成或者为了工作的方便,通常需要连接到指定的文件夹、设置默认工作路径,默认地理数据库、相对路径与绝对路径等。 文章目录 1. 文件夹连接2. 默认工作目录3. 默认地理数据库4. 相对路径与绝对路径1. 文件夹连接 在初次安装完ArcGIS时,默认没有文件夹连…

【Spring Cloud】Redis缓存接入监控、运维平台CacheCloud

CacheCloud CacheCloud提供一个Redis云管理平台&#xff1a;实现多种类型(Redis Standalone、Redis Sentinel、Redis Cluster)自动部署、解决Redis实例碎片化现象、提供完善统计、监控、运维功能、减少运维成本和误操作&#xff0c;提高机器的利用率&#xff0c;提供灵活的伸缩…

[Win10应用开发] 使用 Windows 推送服务 (WNS)

前言 Windows 推送服务&#xff08;WNS&#xff09;也是 Win10 通知机制中的一种&#xff0c;今天与大家一起学习一下有关WNS的相关知识。使用 Windows 推送服务的前提是你需要有一个微软开发者账号&#xff0c;这样才能得到一些合法的密钥信息用于与WNS服务器完成通讯操作。 …

Windows 11 新版 25158 推送!全新搜索框和图标、小组件动态内容和通知标记

面向 Dev 频道的 Windows 预览体验成员&#xff0c;微软现已推送 Windows 11 预览版 Build 25158。主要变化1.微软宣布为 Windows 11 搜索引入全新视觉体验&#xff0c;由搜索框或重新设计的搜索图标呈现。目前该功能仅向部分 Windows 预览体验成员推出&#xff0c;将在未来向所…

【BIM入门实战】Revit创建地形的几种方法及优缺点

Revit在体量和场地选项卡的【地形表面】工具可以创建三维地形,有三种方法:放置点、指定点文件和导入实例文件、倾斜摄影点云技术和InfraWorks地形生成。 文章目录 1. 放置点2. 指定点文件3. 导入实例文件4. 倾斜摄影点云技术5. InfraWorks地形生成1. 放置点 放置点功能位于体…

2024年起重机司机(限门式起重机)证考试题库及起重机司机(限门式起重机)试题解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年起重机司机(限门式起重机)证考试题库及起重机司机(限门式起重机)试题解析是安全生产模拟考试一点通结合&#xff08;安监局&#xff09;特种作业人员操作证考试大纲和&#xff08;质检局&#xff09;特种设备作…

微服务架构下的统一身份认证和授权

一、预备知识 本文讨论基于微服务架构下的身份认证和用户授权的技术方案&#xff0c;在阅读之前&#xff0c;最好先熟悉并理解以下几个知识点&#xff1a; 微服务架构相关概念&#xff1a;服务注册、服务发现、API 网关身份认证和用户授权&#xff1a;SSO、CAS、OAuth2.0、JW…

使用vh来制作高度自适应页面和元素垂直居中

为什么80%的码农都做不了架构师&#xff1f;>>> vh单位 vh是CSS3中的一个长度单位&#xff0c;其值为&#xff1a;100vh 视窗高度。即如果窗口高度为500px&#xff0c;那么 1vh 5px。具体的值会随着浏览器视窗高度的改变而实时改变&#xff0c;因此可以利用这个单…

传智168期JavaEE struts2杜宏 day32~day33(2017年2月15日23:27:09)

struts2学习完毕&#xff0c;寒假学习效率还不错。 笔记链接 链接&#xff1a;http://pan.baidu.com/s/1boBJLVp 密码&#xff1a;wwl4转载于:https://www.cnblogs.com/huangtao1996/p/6403886.html

GitLab的安装、配置、使用

前言上周去参与“中国数字经济创新发展大会”了&#xff0c;然后又忙新项目的事情&#xff0c;博客又有一段时间没有更新&#xff0c;今天周一事情比较少&#xff0c;立刻开始写文&#xff0c;最近有挺多值得记录的东西~进入正文&#xff0c;最近我们搭了个Gitlab&#xff0c;并…

【BIM入门实战】Navisworks2018简体中文安装教程(附安装包下载)

Navisworks是Autodesk公司开发的一款三维模型可视化软件,它以轻量化的运行方式进行BIM成果的后期处理及整合,是一款非常实用的软件。基于Navisworks,项目的参建方可以在施工前进行模拟施工,以达到减少返工、缩短工期、提供经济效益的目的。 Navisworks同时支持4D和5D模拟,…

微软 Windows11 Build 22000.71 更新(KB5004252)发布

微软推出了一个全新的娱乐小工具。这一部件允许用户查看 Microsoft Store 中可用的新电影和精选电影。选择一部电影会引导用户到 Microsoft Store 查看有关该影片的更多信息。 7 月 16 日消息 今日凌晨&#xff0c;微软宣布向预览体验计划用户发布 Windows 11 Build22000.71 更…

【BIM入门实战】Revit模型导入到第三方软件方法汇总

本文以案例的方式,汇总展示了Revit模型导入到ArcGIS Pro、3ds max、Navisworks、Lumion、InfraWorks等的方法。 文章目录 1. Revit导入ArcGIS Pro2. Revit导入3ds Max3. Revit导入Navisworks4. Revit导入Lumion5. Revit导入InfraWorks1. Revit导入ArcGIS Pro ArcGIS Pro2.8可…

Blazor University (37)JavaScript 互操作 —— JavaScript 启动过程

原文链接&#xff1a;https://blazor-university.com/javascript-interop/javascript-boot-process/JavaScript 启动过程在 Blazor 启动过程中&#xff0c;浏览器将在 Blazor 初始化之前创建 HTML 文档&#xff0c;这意味着从引导 HTML 引用的任何 JavaScript 都将立即加载&…

时代聚焦AI安全——可解释性

今年的NIPS多集中在人工智能安全上&#xff0c;此外精彩的部分还有凯特克劳福德关于人工智能公平性问题上被忽视的主题演讲、ML安全研讨会、以及关于“我们是否需要可解释性&#xff1f;”可解释ML讨论会辩论。 值校准文件 逆向奖励设计是为了解决RL代理根据人类设计的代理奖励…

【BIM入门实战】渲染器Vray for 3d max 2018图文安装教程

VRay是由chaosgroup和asgvis公司出品的一款高质量渲染软件。VRay是业界最受欢迎的渲染引擎。基于V-Ray 内核开发的有VRay for 3ds max、Maya、Sketchup、Rhino等诸多版本,为不同领域的优秀3D建模软件提供了高质量的图片和动画渲染。方便使用者渲染各种图片。 Vray for 3d max …

Android--Activity四种启动模式

launchMode在多个Activity跳转的过程中扮演着重要的角色&#xff0c;它可以决定是否生成新的Activity实例&#xff0c;是否重用已存在的Activity实例&#xff0c;是否和其他Activity实例公用一个task里。这里简单介绍一下task的概念&#xff0c;task是一个具有栈结构的对象&…

Hibernate初探

Hibernate对数据库结构提供了较为完整的封装&#xff0c;Hibernate的O/R Mapping实现了POJO 和数据库表之间的映射&#xff0c;以及SQL 的自动生成和执行。程序员往往只需定义好了POJO 到数据库表的映射关系&#xff0c;即可通过Hibernate 提供的方法完成持久层操作。程序员甚至…

【BIM入门实战】InfraWorks2018图文安装教程

Autodesk InfraWorks是易于使用的草图绘制工具,适用于土地规划师和基础设施设计师。AutodeskInfraWorks可以生成可视化的高级模型,而无需借助辅助,可以独立进行建模的BIM可视化软件。 InfraWorks2018软件预览: 双击安装包进行安装。 自解压中,等待即可。 点击【安装】。 点…