Web, HTTP and Services
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Visual Basic 17.13 on .NET 10 (SDK 10.0.402) and was checked on 2026-10-19. Examples marked "built and
run" are complete programs that start with Option Strict On, were built with warnings treated as errors and were run in the
.NET 10 SDK container (Linux, arm64); the output shown is real output. No example needs the internet: every web server in this page
listens on the loopback address (port 0, so the operating system chooses a free port) or runs in memory, and the client code talks
to it inside the same process. The scratch projects had the root namespace App. Code that needs .NET Framework or Windows is
labelled "built, not run" and was compiled against reference assemblies on Linux; nothing more is claimed for it.
"The web" means several different things to a Visual Basic programmer, and the answer to "can I do it in VB?" changes with each.
The server side has a hard edge: ASP.NET Web Forms and ASP.NET MVC 5 are fully supported with Visual Basic, but they run only on
.NET Framework and IIS, and ASP.NET Core can be written in Visual Basic by hand, but Microsoft ships no Visual Basic template for
it and the Razor view engine has no Visual Basic syntax. The client side has no edge at all: HttpClient, System.Net.Http.Json,
the SignalR client and ClientWebSocket are ordinary .NET libraries that Visual Basic calls like any other. This page covers both
sides, using the shelf library catalogue (an ISBN lookup service and its callers) as the running example. The existing sections on
ASP.NET Web Forms, ASP.NET MVC and
ASP.NET Core document those frameworks in depth, in C# for ASP.NET Core and in markup plus C# or VB
code-behind for the Framework stacks; this page does not repeat them, it says what is different when the language is Visual Basic.
What the web means for Visual Basic
| Workload | Runs on | Visual Basic status (checked 2026-10-19) | What to do |
|---|---|---|---|
ASP.NET Web Forms ( |
.NET Framework 4.8.1, IIS |
Fully supported: Visual Studio 2026 still offers "ASP.NET Web Application (.NET Framework)" with Visual Basic. No .NET 10 version exists. |
Maintain it; plan the move described in Migrating to Modern ASP.NET. |
ASP.NET MVC 5 and Web API 2 |
.NET Framework 4.8.1, IIS |
Fully supported, views are |
Maintain it; see Migrating to ASP.NET Core. |
ASP.NET Core minimal APIs and Web API controllers |
.NET 10, Kestrel |
Not a documented or templated VB workload, but the libraries are ordinary .NET and the code compiles and runs (this page proves it). Handwritten. |
Possible for small services; keep to the rules in the section on pitfalls below. |
ASP.NET Core Razor Pages, MVC views, Blazor |
.NET 10 |
Not available: |
Write the UI in C#, keep the domain in a VB class library. |
A VB class library used by a C# ASP.NET Core host |
.NET 10 |
Supported and ordinary: a project reference or a package. |
The recommended design when the host is ASP.NET Core. |
HTTP, JSON, SignalR and WebSocket clients |
.NET 10 and .NET Framework |
Fully supported. |
|
SOAP and WCF clients |
.NET 10 |
The WCF client libraries work; the |
Generate the proxy into a small C# library, or write the contract by hand. |
ASMX web services, WCF servers, |
.NET Framework |
Framework only; nothing to port directly. |
CoreWCF (a server-side open-source port) or a REST API. |
|
.NET Framework only |
Does not compile on .NET 10 (BC30002, shown below). |
Use |
The matrix is the page in one table. The sections that follow take the rows in order: the Framework stacks, ASP.NET Core written in Visual Basic, the class-library pattern, and then the client side.
System.Net.Http.Json (any .NET)"] B -->|"Client: real-time"| D["SignalR .NET client
or ClientWebSocket"] B -->|"Client: SOAP"| E["WCF client libraries,
proxy generated as C#"] B -->|"Server"| F{"Existing application?"} F -->|"Yes: Web Forms or MVC 5"| G["Stay on .NET Framework,
VB code-behind is supported"] F -->|"No: new service"| H{"Needs views or Blazor?"} H -->|"Yes"| I["C# host for the UI,
VB class library for the domain"] H -->|"No: JSON API"| J{"Comfortable without templates?"} J -->|"Yes"| K["ASP.NET Core minimal API or controllers
written by hand in VB"] J -->|"No"| I
Web Forms and MVC 5 with Visual Basic
Web Forms and MVC 5 are the stacks where Visual Basic is a first-class language on the server. A Web Forms page is markup in an
.aspx file with a Visual Basic code-behind file (Default.aspx.vb); an MVC 5 application has controllers in .vb files and
views in .vbhtml. Both run on .NET Framework 4.8.1 under IIS, which is why they have no net10.0 form and why the rest of this
page cannot show them running on Linux. The listing below is the code-behind of a Web Forms search page and an MVC 5 controller for
the shelf, compiled against the .NET Framework 4.8 reference assemblies (Microsoft.NETFramework.ReferenceAssemblies, which the SDK
adds on its own for a net48 target) with the Microsoft.AspNet.Mvc 5.3.0 package. It is built, not run: compiling proves the
language and API shapes, not that a page renders.
Option Strict On
Imports System.Web
Imports System.Web.Mvc
Imports System.Web.UI
Imports System.Web.UI.WebControls
' Web Forms code-behind: the .aspx markup declares the controls, this class handles the events.
Public Partial Class BookSearch
Inherits Page
Protected SearchBox As TextBox
Protected ResultLabel As Label
Protected Sub SearchButton_Click(sender As Object, e As EventArgs)
ResultLabel.Text = "Searching for " & HttpUtility.HtmlEncode(SearchBox.Text)
Session("LastSearch") = SearchBox.Text
End Sub
End Class
' ASP.NET MVC 5 controller.
Public Class BooksController
Inherits Controller
Function Index() As ActionResult
ViewData("Title") = "Shelf"
Return View()
End Function
<HttpPost>
<ValidateAntiForgeryToken>
Function Lend(isbn As String) As ActionResult
Return RedirectToAction("Index")
End Function
End Class
The project file is an SDK-style .vbproj targeting net48, with <Reference Include="System.Web" /> and the two NuGet packages.
Visual Studio’s "ASP.NET Web Application (.NET Framework)" template builds the same code.
ShelfWeb -> bin/Debug/net48/ShelfWeb.dll
Build succeeded.
Source: ASP.NET Web Forms, ASP.NET MVC, ASP.NET overview
Three things are specific to Visual Basic on these stacks. First, Handles clauses wire events in code-behind (Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load), exactly as in Windows Forms; the markup can also name the handler (OnClick="SearchButton_Click") as above, in
which case no Handles is written. Second, Public Partial Class is how the designer-generated half of a page (the .aspx.designer.vb
file declaring the control fields) joins the half you edit; in a hand-written class the fields are plain Protected members. Third,
Razor views are .vbhtml files with Visual Basic expressions (@If model IsNot Nothing Then ... End If); that dialect exists only
in MVC 5 and Web Pages, and was never carried to ASP.NET Core.
The web My objects belong to the same world. MyType=Web makes the compiler generate My.User, My.Request, My.Response,
My.Computer (as ServerComputer) and My.Log (as AspLog), all backed by System.Web. On .NET Framework that works; on .NET 10 it
does not. The same project property that builds on net48:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net48</TargetFramework>
<MyType>Web</MyType>
</PropertyGroup>
<ItemGroup>
<Reference Include="System.Web" />
</ItemGroup>
</Project>
builds there (a class using My.User.Name compiled, not run), while <MyType>Web</MyType> in a Microsoft.NET.Sdk.Web project that
targets net10.0 fails with five BC30002 errors:
vbc : error BC30002: Type 'Global.Microsoft.VisualBasic.ApplicationServices.WebUser' is not defined.
vbc : error BC30002: Type 'Global.Microsoft.VisualBasic.Devices.ServerComputer' is not defined.
vbc : error BC30002: Type 'Global.Microsoft.VisualBasic.Logging.AspLog' is not defined.
vbc : error BC30002: Type 'Global.System.Web.HttpRequest' is not defined.
vbc : error BC30002: Type 'Global.System.Web.HttpResponse' is not defined.
The generated My code names System.Web.HttpRequest and friends, which do not exist on .NET 10. The other MyType values
(WindowsForms, WindowsFormsWithCustomSubMain, Console, Empty) are described in
The My Namespace.
Session and application state are the two ideas that Classic ASP, Web Forms and ASP.NET Core share, with different mechanics:
Session("visits") and Application("hits") are dictionaries of Object in Classic ASP and Web Forms. ASP.NET Core splits them:
per-visitor state is the opt-in session middleware (typed accessors such as SetInt32), application-wide state is an ordinary
singleton or a field. The ASP.NET Core version, written in Visual Basic, is shown further down in the section on sessions.
ASP.NET Core written in Visual Basic
ASP.NET Core is a set of ordinary .NET libraries (the Microsoft.AspNetCore.App shared framework), so nothing stops Visual Basic from
calling them. What is missing is everything around the libraries: dotnet new webapi -lang VB does not exist, and the template
engine says so:
No templates found matching: 'webapi', -lang='VB'.
Allowed values for '-lang' option are: 'C#', 'F#'.
Microsoft’s ASP.NET Core documentation shows C# only, and the Razor compiler produces C# and skipped the Visual Basic project in the test below. The language strategy (see History, Versions and Language Strategy) says that new workloads are not planned for Visual Basic, so treat what follows as supported-by-the-compiler, not supported-by-the-product.
The project file is the C# one with a .vbproj extension: the Web SDK works for Visual Basic. The difference that matters is that there
is no implicit using list. C# web projects get ImplicitUsings, a generated file of global usings for Microsoft.AspNetCore.Builder,
Microsoft.AspNetCore.Http, Microsoft.Extensions.DependencyInjection and others; a Visual Basic project gets none of them, so every
file imports what it needs, and a missing import shows up as BC30456 on an extension method:
error BC30456: 'UseUrls' is not a member of 'ConfigureWebHostBuilder'.
error BC30456: 'ClearProviders' is not a member of 'ILoggingBuilder'.
Both are extension methods whose namespaces (Microsoft.AspNetCore.Hosting and Microsoft.Extensions.Logging) were not imported.
The demonstration project used in this page has one .vbproj and several modules; StartupObject selects the program, and each
listing is complete except for the shared types, shown first:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>App</RootNamespace>
<OptionStrict>On</OptionStrict>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<StartupObject>App.MinimalApiDemo</StartupObject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.13" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.13" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="10.0.13" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="10.11.0" />
</ItemGroup>
</Project>
The three Microsoft.AspNetCore.* packages and the resilience package are needed only by the later examples; the minimal API itself
needs none. (When switching StartupObject on the command line with -p:StartupObject=App.X, build with --no-incremental: MSBuild
does not see a changed property as a reason to recompile, and the previous program runs again.)
Option Strict On
Imports Microsoft.AspNetCore.Builder
Imports Microsoft.AspNetCore.Hosting.Server
Imports Microsoft.AspNetCore.Hosting.Server.Features
Imports Microsoft.Extensions.DependencyInjection
Public Class BookDto
Public Property Isbn As String = ""
Public Property Title As String = ""
Public Property Year As Integer
End Class
Public Class BookStore
Private ReadOnly _books As New Dictionary(Of String, BookDto) From {
{"9780306406157", New BookDto With {.Isbn = "9780306406157", .Title = "Shelf Basics", .Year = 2020}}
}
Public Function Find(isbn As String) As BookDto
Dim book As BookDto = Nothing
Return If(_books.TryGetValue(isbn, book), book, Nothing)
End Function
Public Sub Add(book As BookDto)
_books(book.Isbn) = book
End Sub
Public Function All() As IReadOnlyList(Of BookDto)
Return _books.Values.ToList()
End Function
End Class
Friend Module Hosting
' The loopback address Kestrel picked when asked for port 0.
Friend Function ServerAddress(app As WebApplication) As String
Return app.Services.GetRequiredService(Of IServer)().Features.Get(Of IServerAddressesFeature)().Addresses.First()
End Function
End Module
BookDto is a plain class with public get/set properties and a parameterless constructor, which is what the JSON binder needs;
Visual Basic has no record, and a Structure or a class with only a parameterised constructor would not bind the way a C# record
does. Hosting.ServerAddress reads the address Kestrel chose for port 0 from IServerAddressesFeature.
A minimal API
A minimal API maps a URL pattern and a delegate to the request pipeline. Visual Basic lambdas are the delegates. Two things differ
from the C# examples in the ASP.NET Core documentation: a multi-statement lambda is written as a Function block ending in End Function, and the As IResult is worth keeping, because when the branches return different result types (here
Results.NotFound() and Results.Ok(book)) an explicit return type avoids relying on type inference.
Option Strict On
Imports System.Net.Http
Imports System.Net.Http.Json
Imports Microsoft.AspNetCore.Builder
Imports Microsoft.AspNetCore.Hosting
Imports Microsoft.AspNetCore.Http
Imports Microsoft.Extensions.DependencyInjection
Imports Microsoft.Extensions.Hosting
Module MinimalApiDemo
Sub Main(args As String())
RunAsync(args).GetAwaiter().GetResult()
End Sub
Async Function RunAsync(args As String()) As Task
Dim builder = WebApplication.CreateBuilder(args)
builder.WebHost.UseUrls("http://127.0.0.1:0")
builder.Configuration("Logging:LogLevel:Default") = "None"
builder.Services.AddSingleton(Of BookStore)()
Dim app = builder.Build()
app.MapGet("/books", Function(store As BookStore) store.All())
app.MapGet("/books/{isbn}",
Function(isbn As String, store As BookStore) As IResult
Dim book As BookDto = store.Find(isbn)
Return If(book Is Nothing, Results.NotFound(), Results.Ok(book))
End Function)
app.MapPost("/books",
Function(book As BookDto, store As BookStore) As IResult
store.Add(book)
Return Results.Created("/books/" & book.Isbn, book)
End Function)
Await app.StartAsync()
Dim address As String = Hosting.ServerAddress(app)
Console.WriteLine("listening on loopback: " & address.StartsWith("http://127.0.0.1:").ToString())
Using client As New HttpClient With {.BaseAddress = New Uri(address)}
Dim all = Await client.GetFromJsonAsync(Of List(Of BookDto))("/books")
Console.WriteLine("GET /books -> " & all.Count & " book(s), first = " & all(0).Title)
Dim created = Await client.PostAsJsonAsync("/books", New BookDto With {.Isbn = "9780131103627", .Title = "Types and Tables", .Year = 1988})
Console.WriteLine("POST /books -> " & CInt(created.StatusCode) & " " & created.StatusCode.ToString() & ", Location = " & created.Headers.Location.ToString())
Dim found = Await client.GetAsync("/books/9780131103627")
Console.WriteLine("GET known -> " & CInt(found.StatusCode) & " " & Await found.Content.ReadAsStringAsync())
Dim missing = Await client.GetAsync("/books/0000000000000")
Console.WriteLine("GET unknown -> " & CInt(missing.StatusCode) & " " & missing.StatusCode.ToString())
End Using
Await app.StopAsync()
End Function
End Module
listening on loopback: True
GET /books -> 1 book(s), first = Shelf Basics
POST /books -> 201 Created, Location = /books/9780131103627
GET known -> 200 {"isbn":"9780131103627","title":"Types and Tables","year":1988}
GET unknown -> 404 NotFound
Source: Minimal APIs overview
Notes on the listing. WebApplication.CreateBuilder(args) and builder.Build() are the same calls as in C#. app.StartAsync()
(rather than app.Run()) starts Kestrel without blocking so that the program can drive it; a real service ends Main with
app.Run(). The entry point is a synchronous Sub Main that waits on an Async Function, because the language has no Async Main
(declaring one fails with BC30737, see the pitfalls below). Configuration("Logging:LogLevel:Default") = "None" silences the
request log so that the output stays readable; ClearProviders() would need Imports Microsoft.Extensions.Logging, and that import
makes the name Console bind to the namespace Microsoft.Extensions.Logging.Console (BC30456 on every Console.WriteLine), which is
why the listing uses configuration instead. If you do need the logging import, put it in a file that does not print.
Controllers
Web API controllers are classes decorated with attributes, which Visual Basic writes with angle brackets and line continuation
inside the brackets as usual. The action methods are Function`s; `ActionResult(Of BookDto) is ActionResult<BookDto>, and returning
a BookDto where an ActionResult(Of BookDto) is expected works through the implicit conversion that the type defines (Visual Basic
accepts the widening conversion without a cast). Validation attributes on the model class give the automatic 400
application/problem+json response of [ApiController].
Option Strict On
Imports System.ComponentModel.DataAnnotations
Imports System.Net.Http
Imports System.Net.Http.Json
Imports System.Text.Json
Imports Microsoft.AspNetCore.Builder
Imports Microsoft.AspNetCore.Hosting
Imports Microsoft.AspNetCore.Http
Imports Microsoft.AspNetCore.Mvc
Imports Microsoft.Extensions.Configuration
Imports Microsoft.Extensions.DependencyInjection
Imports Microsoft.Extensions.Hosting
Public Class NewBook
<Required, StringLength(13, MinimumLength:=10)>
Public Property Isbn As String = ""
<Required>
Public Property Title As String = ""
<Range(1450, 2100)>
Public Property Year As Integer
End Class
<ApiController>
<Route("api/[controller]")>
Public Class BooksController
Inherits ControllerBase
Private ReadOnly _store As BookStore
Public Sub New(store As BookStore)
_store = store
End Sub
<HttpGet>
Public Function GetAll() As IEnumerable(Of BookDto)
Return _store.All()
End Function
<HttpGet("{isbn}")>
Public Function GetOne(isbn As String) As ActionResult(Of BookDto)
Dim book As BookDto = _store.Find(isbn)
If book Is Nothing Then
Return NotFound()
End If
Return book
End Function
<HttpPost>
Public Function Create(newBook As NewBook) As ActionResult(Of BookDto)
Dim book As New BookDto With {.Isbn = newBook.Isbn, .Title = newBook.Title, .Year = newBook.Year}
_store.Add(book)
Return CreatedAtAction(NameOf(GetOne), New With {.isbn = book.Isbn}, book)
End Function
End Class
Module ControllersDemo
Sub Main(args As String())
RunAsync(args).GetAwaiter().GetResult()
End Sub
Async Function RunAsync(args As String()) As Task
Dim builder = WebApplication.CreateBuilder(args)
builder.WebHost.UseUrls("http://127.0.0.1:0")
builder.Configuration("Logging:LogLevel:Default") = "None"
builder.Services.AddSingleton(Of BookStore)()
builder.Services.AddControllers()
Dim app = builder.Build()
app.MapControllers()
Await app.StartAsync()
Using client As New HttpClient With {.BaseAddress = New Uri(Hosting.ServerAddress(app))}
Dim all = Await client.GetFromJsonAsync(Of List(Of BookDto))("/api/books")
Console.WriteLine("GET /api/books -> " & all.Count & " book(s)")
Dim ok = Await client.PostAsJsonAsync("/api/books", New NewBook With {.Isbn = "9780131103627", .Title = "Types and Tables", .Year = 1988})
Console.WriteLine("POST valid -> " & CInt(ok.StatusCode) & ", Location = " & ok.Headers.Location.AbsolutePath)
Dim bad = Await client.PostAsJsonAsync("/api/books", New NewBook With {.Isbn = "123", .Title = "", .Year = 3000})
Console.WriteLine("POST invalid -> " & CInt(bad.StatusCode) & " " & bad.Content.Headers.ContentType.MediaType)
Using problem As JsonDocument = JsonDocument.Parse(Await bad.Content.ReadAsStringAsync())
Console.WriteLine(problem.RootElement.GetProperty("title").GetString())
For Each field In problem.RootElement.GetProperty("errors").EnumerateObject().OrderBy(Function(p) p.Name)
Console.WriteLine(" " & field.Name & ": " & field.Value(0).GetString())
Next
End Using
Dim missing = Await client.GetAsync("/api/books/0000000000000")
Console.WriteLine("GET unknown -> " & CInt(missing.StatusCode))
End Using
Await app.StopAsync()
End Function
End Module
GET /api/books -> 1 book(s)
POST valid -> 201, Location = /api/Books/9780131103627
POST invalid -> 400 application/problem+json
One or more validation errors occurred.
Isbn: The field Isbn must be a string with a minimum length of 10 and a maximum length of 13.
Title: The Title field is required.
Year: The field Year must be between 1450 and 2100.
GET unknown -> 404
Visual Basic specifics visible here. The attribute argument "api/[controller]" is just a string; NameOf(GetOne) works as in
C#; the anonymous type New With {.isbn = book.Isbn} supplies route values to CreatedAtAction; and the controller is discovered
because it is Public and declared at namespace level in the entry assembly (a Public Class nested inside a default Friend Module
is not visible outside the assembly, because its container is Friend; the same rule makes XmlSerializer fail on such a class). The
Location header reads /api/Books/... with a capital B: the [controller] token takes the class name without the Controller
suffix as written, so the casing of the class name appears in the URL, and URLs are case-insensitive in ASP.NET Core routing.
Handlers, attributes and the places where Visual Basic differs
The delegate-based API is where the language shows. These are the four things a C# example in the documentation does that a Visual Basic program cannot, with the real compiler messages and the working replacement for each.
1. A method group does not convert to Delegate. MapGet takes a Delegate (and a RequestDelegate). C# converts a method
group to a delegate type it infers; Visual Basic’s AddressOf needs a concrete delegate type to convert to:
app.MapGet("/hello/{name}", AddressOf Hello)
Program.vb(14,13): error BC30518: Overload resolution failed because no accessible 'MapGet' can be called with these arguments:
Wrap the method in the delegate type that matches it: New Func(Of String, IResult)(AddressOf Hello).
2. No attributes on lambda parameters. C# writes ([FromQuery(Name = "q")] string term) => term. Visual Basic forbids attributes
on lambda parameters:
app.MapGet("/search", Function(<FromQuery(Name:="q")> term As String) term)
Program.vb(10,40): error BC36634: Attributes cannot be applied to parameters of lambda expressions.
Put the attributes on a named method’s parameters (allowed) and pass the method in a Func, as the listing below does, or bind a
whole parameter object with <AsParameters>.
3. No Optional lambda parameters. C# lambdas can have default values ((int page = 1) => ...); Visual Basic cannot:
app.MapGet("/page", Function(Optional page As Integer = 1) page.ToString())
Program.vb(9,38): error BC33010: 'Lambda' parameters cannot be declared 'Optional'.
Use a named method with Optional, a nullable parameter (page As Integer?) or a property with a default in an <AsParameters> class.
4. An untyped Async lambda handed to MapGet returns an empty body. This one has no compiler message. A lambda converted
straight to Delegate becomes an instance of a compiler-generated type named VB$AnonymousDelegate_n, not of Func(Of ...):
Option Strict On
Module DelegateProbe
Sub Main()
Dim a As [Delegate] = Async Function() As Task(Of String)
Await Task.Delay(1)
Return "x"
End Function
Dim s As [Delegate] = Function() "x"
Dim t As Func(Of Task(Of String)) = Async Function() As Task(Of String)
Await Task.Delay(1)
Return "x"
End Function
For Each d As [Delegate] In {a, s, t}
Console.WriteLine(d.GetType().Name & " | Method.ReturnType = " & d.Method.ReturnType.Name & " | Invoke = " & d.GetType().GetMethod("Invoke").ReturnType.Name & " | IsPublic = " & d.GetType().IsPublic.ToString())
Next
End Sub
End Module
VB$AnonymousDelegate_2`1 | Method.ReturnType = Task`1 | Invoke = Task`1 | IsPublic = False
VB$AnonymousDelegate_2`1 | Method.ReturnType = String | Invoke = String | IsPublic = False
Func`1 | Method.ReturnType = Task`1 | Invoke = Task`1 | IsPublic = True
(The number in the type name depends on how many anonymous delegates the assembly already contains.) With a synchronous lambda this
is harmless; with an Async Function returning Task(Of String) the endpoint answered 200 with an empty body in the listing
below, while the same lambda stored in a Func(Of Task(Of String)) variable first produced its text. I did not trace the cause
inside ASP.NET Core; the safe rule is to give an asynchronous handler a real delegate type (Func(Of Task(Of String))) before
passing it to MapGet, and to test async endpoints for their body, not only their status code.
The next program shows all four working: named methods with binding attributes wrapped in Func, an <AsParameters> class,
TypedResults with a Results(Of ..., ...) union, a route group, the async pitfall, and an endpoint filter. Filters are classes:
IEndpointFilter.InvokeAsync returns ValueTask(Of Object), and a Visual Basic Async Function cannot return ValueTask (BC36945),
so the interface method is a plain Function that wraps the Task of a private Async helper in a New ValueTask(Of Object)(...).
Option Strict On
Imports System.Net.Http
Imports System.Net.Http.Json
Imports Microsoft.AspNetCore.Builder
Imports Microsoft.AspNetCore.Hosting
Imports Microsoft.AspNetCore.Http
Imports Microsoft.AspNetCore.Http.HttpResults
Imports Microsoft.AspNetCore.Mvc
Imports Microsoft.Extensions.Configuration
Imports Microsoft.Extensions.DependencyInjection
Imports Microsoft.Extensions.Hosting
Public Class SearchOptions
<FromQuery(Name:="q")>
Public Property Term As String = ""
<FromQuery>
Public Property Page As Integer = 1
End Class
Public NotInheritable Class Handlers
Private Sub New()
End Sub
' A named method can carry binding attributes on its parameters.
Public Shared Function Search(<FromQuery(Name:="q")> term As String,
<FromQuery> Optional limit As Integer = 10) As String
Return "term=" & term & " limit=" & limit
End Function
Public Shared Function Paged(<AsParameters> options As SearchOptions) As String
Return "term=" & options.Term & " page=" & options.Page
End Function
Public Shared Function Lookup(isbn As String, store As BookStore) As Results(Of Ok(Of BookDto), NotFound)
Dim book As BookDto = store.Find(isbn)
If book Is Nothing Then
Return TypedResults.NotFound()
End If
Return TypedResults.Ok(book)
End Function
End Class
Public NotInheritable Class LoggingFilter
Implements IEndpointFilter
Public Function InvokeAsync(context As EndpointFilterInvocationContext,
[next] As EndpointFilterDelegate) As ValueTask(Of Object) Implements IEndpointFilter.InvokeAsync
Return New ValueTask(Of Object)(RunAsync(context, [next]))
End Function
Private Shared Async Function RunAsync(context As EndpointFilterInvocationContext,
[next] As EndpointFilterDelegate) As Task(Of Object)
Console.WriteLine(" filter: before")
Dim result As Object = Await [next](context)
Console.WriteLine(" filter: after, result = " & result.ToString())
Return result
End Function
End Class
Module HandlersDemo
Sub Main(args As String())
RunAsync(args).GetAwaiter().GetResult()
End Sub
Async Function RunAsync(args As String()) As Task
Dim builder = WebApplication.CreateBuilder(args)
builder.WebHost.UseUrls("http://127.0.0.1:0")
builder.Configuration("Logging:LogLevel:Default") = "None"
builder.Services.AddSingleton(Of BookStore)()
Dim app = builder.Build()
' AddressOf alone does not convert to the Delegate parameter of MapGet: wrap it.
app.MapGet("/search", New Func(Of String, Integer, String)(AddressOf Handlers.Search))
app.MapGet("/paged", New Func(Of SearchOptions, String)(AddressOf Handlers.Paged))
app.MapGet("/typed/{isbn}", New Func(Of String, BookStore, Results(Of Ok(Of BookDto), NotFound))(AddressOf Handlers.Lookup))
' A route group shares a prefix.
Dim api = app.MapGroup("/api/v1")
api.MapGet("/ping", Function() "pong")
' The same asynchronous handler twice: handed over as a bare lambda, and held in a typed Func first.
api.MapGet("/lambda",
Async Function() As Task(Of String)
Await Task.Delay(1)
Return "async lambda"
End Function)
Dim typed As Func(Of Task(Of String)) =
Async Function() As Task(Of String)
Await Task.Delay(1)
Return "typed Func"
End Function
api.MapGet("/typed-func", typed)
' An endpoint filter is a class: IEndpointFilter returns ValueTask(Of Object), which an Async method cannot return.
api.MapGet("/filtered", Function() "body").AddEndpointFilter(New LoggingFilter())
Await app.StartAsync()
Using client As New HttpClient With {.BaseAddress = New Uri(Hosting.ServerAddress(app))}
For Each path In {"/search?q=vb", "/search?q=vb&limit=3", "/paged?q=dotnet&page=2", "/typed/9780306406157", "/typed/1", "/api/v1/ping", "/api/v1/lambda", "/api/v1/typed-func", "/api/v1/filtered"}
Dim response = Await client.GetAsync(path)
Console.WriteLine(path & " -> " & CInt(response.StatusCode) & " " & Await response.Content.ReadAsStringAsync())
Next
End Using
Await app.StopAsync()
End Function
End Module
/search?q=vb -> 200 term=vb limit=10
/search?q=vb&limit=3 -> 200 term=vb limit=3
/paged?q=dotnet&page=2 -> 200 term=dotnet page=2
/typed/9780306406157 -> 200 {"isbn":"9780306406157","title":"Shelf Basics","year":2020}
/typed/1 -> 404
/api/v1/ping -> 200 pong
/api/v1/lambda -> 200
/api/v1/typed-func -> 200 typed Func
filter: before
filter: after, result = body
/api/v1/filtered -> 200 body
Source: Filters in Minimal API apps
Reading the output: /search shows <FromQuery(Name:="q")> binding q to term and the Optional limit taking its default;
/paged shows <AsParameters> filling Term (from q) and Page from one class; /typed/... returns 404 with an empty body from
TypedResults.NotFound(); /api/v1/lambda is the pitfall (200 and nothing after it) next to /api/v1/typed-func; and the filter
printed before and after the endpoint ran.
Middleware, dependency injection and options
The pipeline is configured with app.Use.... An inline middleware is a lambda taking HttpContext and the next step. Spelling out
the parameter types, as below, selects the Func(Of Task) form unambiguously and reads better than untyped parameters. A
convention-based middleware class (a constructor taking RequestDelegate and an InvokeAsync method whose other parameters come from
dependency injection) needs [next] in brackets because Next is a Visual Basic keyword. Options bind configuration sections to a
class with Configure(Of T); note that the property initialisers give the defaults.
Option Strict On
Imports System.Net.Http
Imports Microsoft.AspNetCore.Builder
Imports Microsoft.AspNetCore.Hosting
Imports Microsoft.AspNetCore.Http
Imports Microsoft.Extensions.Configuration
Imports Microsoft.Extensions.DependencyInjection
Imports Microsoft.Extensions.Hosting
Imports Microsoft.Extensions.Options
Public Class ShelfOptions
Public Const SectionName As String = "Shelf"
Public Property LibraryName As String = "Unnamed"
Public Property MaxLoans As Integer = 3
End Class
Public Interface IClock
Function UtcNow() As DateTimeOffset
End Interface
Public NotInheritable Class FixedClock
Implements IClock
Public Function UtcNow() As DateTimeOffset Implements IClock.UtcNow
Return New DateTimeOffset(2026, 10, 19, 12, 0, 0, TimeSpan.Zero)
End Function
End Class
' Convention-based middleware: a constructor taking RequestDelegate and an InvokeAsync method.
Public NotInheritable Class StampMiddleware
Private ReadOnly _next As RequestDelegate
Public Sub New([next] As RequestDelegate)
_next = [next]
End Sub
Public Async Function InvokeAsync(context As HttpContext, clock As IClock) As Task
context.Response.Headers("X-Stamp") = clock.UtcNow().ToString("yyyy-MM-dd")
Await _next(context)
End Function
End Class
Module PipelineDemo
Sub Main(args As String())
RunAsync(args).GetAwaiter().GetResult()
End Sub
Async Function RunAsync(args As String()) As Task
Dim builder = WebApplication.CreateBuilder(args)
builder.WebHost.UseUrls("http://127.0.0.1:0")
builder.Configuration("Logging:LogLevel:Default") = "None"
builder.Configuration("Shelf:LibraryName") = "Central Library"
builder.Services.Configure(Of ShelfOptions)(builder.Configuration.GetSection(ShelfOptions.SectionName))
builder.Services.AddSingleton(Of IClock, FixedClock)()
Dim app = builder.Build()
' Inline middleware: spell out the parameter types so the Func(Of Task) overload is chosen.
app.Use(Async Function(context As HttpContext, [next] As Func(Of Task)) As Task
Console.WriteLine(" inline middleware: before " & context.Request.Path.Value)
Await [next]()
Console.WriteLine(" inline middleware: after, status " & context.Response.StatusCode)
End Function)
app.UseMiddleware(Of StampMiddleware)()
app.MapGet("/info",
Function(options As IOptions(Of ShelfOptions), clock As IClock) As String
Return options.Value.LibraryName & " (max " & options.Value.MaxLoans & " loans) at " & clock.UtcNow().ToString("u")
End Function)
Await app.StartAsync()
Using client As New HttpClient With {.BaseAddress = New Uri(Hosting.ServerAddress(app))}
Dim response = Await client.GetAsync("/info")
Console.WriteLine("body: " & Await response.Content.ReadAsStringAsync())
Console.WriteLine("X-Stamp: " & response.Headers.GetValues("X-Stamp").First())
End Using
Await app.StopAsync()
End Function
End Module
inline middleware: before /info
inline middleware: after, status 200
body: Central Library (max 3 loans) at 2026-10-19 12:00:00Z
X-Stamp: 2026-10-19
Source: ASP.NET Core Middleware
Public Const SectionName As String = "Shelf" is the idiom for the section key. Interface IClock with FixedClock is the usual
dependency-injection example; builder.Services.AddSingleton(Of IClock, FixedClock)() is the generic registration, with Of where C#
has angle brackets. Visual Basic has no primary constructors, so each injected service is a field set in an explicit New.
Sessions and application state
The Classic ASP and Web Forms idioms Session("visits") and Application("hits") map to the session middleware and to a singleton (or,
as below, a captured variable). The session needs AddDistributedMemoryCache() and AddSession() registered and UseSession() in the
pipeline; the accessors GetInt32 and SetInt32 are extension methods in Microsoft.AspNetCore.Http. If(a, b) is the Visual Basic
replacement for C# ?? (the language has no ??): GetInt32 returns Integer?, and If(x, 0) supplies the default.
Option Strict On
Imports System.Net.Http
Imports Microsoft.AspNetCore.Builder
Imports Microsoft.AspNetCore.Hosting
Imports Microsoft.AspNetCore.Http
Imports Microsoft.Extensions.Configuration
Imports Microsoft.Extensions.DependencyInjection
Imports Microsoft.Extensions.Hosting
Module SessionDemo
Sub Main(args As String())
RunAsync(args).GetAwaiter().GetResult()
End Sub
Async Function RunAsync(args As String()) As Task
Dim builder = WebApplication.CreateBuilder(args)
builder.WebHost.UseUrls("http://127.0.0.1:0")
builder.Configuration("Logging:LogLevel:Default") = "None"
builder.Services.AddDistributedMemoryCache()
builder.Services.AddSession()
Dim app = builder.Build()
app.UseSession()
Dim hits As Integer = 0 ' application-wide state (Application("hits") in classic ASP)
app.MapGet("/visit",
Function(context As HttpContext) As String
Dim own As Integer = If(context.Session.GetInt32("visits"), 0) + 1 ' per-visitor state (Session("visits"))
context.Session.SetInt32("visits", own)
hits += 1
Return "you: " & own & ", everyone: " & hits
End Function)
Await app.StartAsync()
Dim address As New Uri(Hosting.ServerAddress(app))
Using ada As New HttpClient With {.BaseAddress = address}, grace As New HttpClient With {.BaseAddress = address}
Console.WriteLine("ada: " & Await ada.GetStringAsync("/visit"))
Console.WriteLine("ada: " & Await ada.GetStringAsync("/visit"))
Console.WriteLine("grace: " & Await grace.GetStringAsync("/visit"))
Console.WriteLine("ada: " & Await ada.GetStringAsync("/visit"))
End Using
Await app.StopAsync()
End Function
End Module
ada: you: 1, everyone: 1
ada: you: 2, everyone: 2
grace: you: 1, everyone: 3
ada: you: 3, everyone: 4
Two clients (two HttpClient instances, so two cookie jars) get separate session counters (ada counts 1, 2, 3; grace counts 1)
while the application-wide counter increases for everybody. The in-memory session store is for demonstrations and single servers; a
real deployment uses a distributed cache. The lambda captures the local hits, which is shared state mutated from several requests:
use Interlocked or a lock in real code.
OpenAPI
AddOpenApi() and MapOpenApi() (from the Microsoft.AspNetCore.OpenApi package) generate an OpenAPI 3.1 document at runtime
from the endpoint metadata. They work from Visual Basic. The output lists the two routes and the BookDto schema with its camelCase
property names. Two limits apply: lambda parameters carry no attributes in Visual Basic (see above), so richer metadata
comes from .WithName(...) and the Produces methods on the endpoint, not from attributes; and the package’s integration with XML
documentation comments is implemented as a source generator, which was not tested from Visual Basic.
Option Strict On
Imports System.Net.Http
Imports System.Text.Json
Imports Microsoft.AspNetCore.Builder
Imports Microsoft.AspNetCore.Hosting
Imports Microsoft.AspNetCore.Http
Imports Microsoft.Extensions.Configuration
Imports Microsoft.Extensions.DependencyInjection
Imports Microsoft.Extensions.Hosting
Module OpenApiDemo
Sub Main(args As String())
RunAsync(args).GetAwaiter().GetResult()
End Sub
Async Function RunAsync(args As String()) As Task
Dim builder = WebApplication.CreateBuilder(args)
builder.WebHost.UseUrls("http://127.0.0.1:0")
builder.Configuration("Logging:LogLevel:Default") = "None"
builder.Services.AddOpenApi()
builder.Services.AddSingleton(Of BookStore)()
Dim app = builder.Build()
app.MapOpenApi()
app.MapGet("/books/{isbn}",
Function(isbn As String, store As BookStore) As IResult
Dim book As BookDto = store.Find(isbn)
Return If(book Is Nothing, Results.NotFound(), Results.Ok(book))
End Function).WithName("GetBook")
app.MapPost("/books", Function(book As BookDto, store As BookStore) As IResult
store.Add(book)
Return Results.Created("/books/" & book.Isbn, book)
End Function)
Await app.StartAsync()
Using client As New HttpClient With {.BaseAddress = New Uri(Hosting.ServerAddress(app))}
Dim json As String = Await client.GetStringAsync("/openapi/v1.json")
Using doc As JsonDocument = JsonDocument.Parse(json)
Console.WriteLine("openapi = " & doc.RootElement.GetProperty("openapi").GetString())
For Each path In doc.RootElement.GetProperty("paths").EnumerateObject()
For Each op In path.Value.EnumerateObject()
Console.WriteLine(op.Name.ToUpperInvariant() & " " & path.Name)
Next
Next
For Each entry In doc.RootElement.GetProperty("components").GetProperty("schemas").EnumerateObject()
Console.WriteLine("schema " & entry.Name & ": " & String.Join(", ", entry.Value.GetProperty("properties").EnumerateObject().Select(Function(p) p.Name)))
Next
End Using
End Using
Await app.StopAsync()
End Function
End Module
openapi = 3.1.1
GET /books/{isbn}
POST /books
schema BookDto: isbn, title, year
Source: OpenAPI support in ASP.NET Core
The loop variable is called entry, not schema: with Imports System.Text.Json in scope, the child namespace
System.Text.Json.Schema is visible as Schema, and because names are case-insensitive a local named schema is read as that
namespace (BC30112 "is a namespace and cannot be used as an expression"). The same trap applies to any local named after a child
namespace of an imported one.
What does not work: Razor and Blazor
A Razor Pages application, MVC views and Blazor components are authored in .cshtml and .razor files and compiled by a Razor source
generator that emits C#. A Visual Basic project accepts the files, builds without a warning, and ignores them. This program calls
AddRazorPages() and MapRazorPages() with a Pages/Index.cshtml file next to it:
Option Strict On
Imports System.Net.Http
Imports Microsoft.AspNetCore.Builder
Imports Microsoft.AspNetCore.Hosting
Imports Microsoft.Extensions.Configuration
Imports Microsoft.Extensions.DependencyInjection
Imports Microsoft.Extensions.Hosting
Module Program
Sub Main(args As String())
Dim builder = WebApplication.CreateBuilder(args)
builder.WebHost.UseUrls("http://127.0.0.1:0")
builder.Configuration("Logging:LogLevel:Default") = "None"
builder.Services.AddRazorPages()
Dim app = builder.Build()
app.MapRazorPages()
app.Start()
Dim address = app.Urls.First()
Using client As New HttpClient With {.BaseAddress = New Uri(address)}
Dim r = client.GetAsync("/").GetAwaiter().GetResult()
Console.WriteLine(CInt(r.StatusCode))
End Using
app.StopAsync().GetAwaiter().GetResult()
End Sub
End Module
with a file Pages/Index.cshtml next to it that contains @page and a heading. The output is the HTTP status of GET /:
404
The status is 404: no page was compiled. This is the reason the matrix says "not available". The supported
design for UI work is a C# host (next section). Source generators written for C# (the ASP.NET Core
request-delegate generator and similar) were not tested from Visual Basic, and publishing an ASP.NET Core host as Native AOT is not
something this page tested; the examples here rely on reflection.
SignalR and WebSockets on the server
A SignalR hub is a class that inherits Hub; clients call its public methods by name and the server pushes to clients with
Clients.All.SendAsync("Name", arg1, arg2), an extension method with overloads for up to ten arguments that Visual Basic calls directly. The same project hosts a
raw WebSocket endpoint. The program in the section on the client side below contains the hub, the endpoint and both clients.
In-process testing
Microsoft.AspNetCore.TestHost runs the whole pipeline in memory: UseTestServer() replaces Kestrel and GetTestClient() returns an
HttpClient that dispatches straight to the app. No socket is opened, which makes it the right default for tests of a Visual Basic
service (see Testing for the test frameworks).
Option Strict On
Imports System.Net.Http
Imports System.Net.Http.Json
Imports Microsoft.AspNetCore.Builder
Imports Microsoft.AspNetCore.Http
Imports Microsoft.AspNetCore.TestHost
Imports Microsoft.Extensions.Configuration
Imports Microsoft.Extensions.DependencyInjection
Imports Microsoft.Extensions.Hosting
Module TestServerDemo
Sub Main(args As String())
RunAsync(args).GetAwaiter().GetResult()
End Sub
Async Function RunAsync(args As String()) As Task
Dim builder = WebApplication.CreateBuilder(args)
builder.WebHost.UseTestServer() ' no socket is opened
builder.Configuration("Logging:LogLevel:Default") = "None"
builder.Services.AddSingleton(Of BookStore)()
Dim app = builder.Build()
app.MapGet("/books/{isbn}",
Function(isbn As String, store As BookStore) As IResult
Dim book As BookDto = store.Find(isbn)
Return If(book Is Nothing, Results.NotFound(), Results.Ok(book))
End Function)
Await app.StartAsync()
Using client As HttpClient = app.GetTestClient()
Dim book = Await client.GetFromJsonAsync(Of BookDto)("/books/9780306406157")
Console.WriteLine("in-process GET -> " & book.Title)
Dim response = Await client.GetAsync("/books/0000000000000")
Console.WriteLine("in-process GET unknown -> " & CInt(response.StatusCode))
Console.WriteLine("BaseAddress = " & client.BaseAddress.ToString())
End Using
Await app.StopAsync()
End Function
End Module
in-process GET -> Shelf Basics
in-process GET unknown -> 404
BaseAddress = http://localhost/
The helper WebApplicationFactory<TEntryPoint> from Microsoft.AspNetCore.Mvc.Testing was not tried with a Visual Basic entry point;
building the WebApplication yourself as above needs no factory.
A Visual Basic library behind a C# ASP.NET Core host
This is the pattern to prefer when the host needs Razor, Blazor, OpenAPI source generation or Native AOT. The domain (the catalogue
rules, validation, calculations) lives in a Visual Basic class library, and a C# project references it and exposes it. Nothing about
the library knows it is behind a web host. The C# host below was run with dotnet run and calls its own endpoints:
Option Strict On
Public Enum LoanOutcome
Lent
AlreadyOnLoan
UnknownBook
End Enum
Public Class Book
Public Property Isbn As String = ""
Public Property Title As String = ""
Public Property OnLoan As Boolean
End Class
Public Class CatalogService
Private ReadOnly _books As New Dictionary(Of String, Book)
Public Sub New()
_books("9780306406157") = New Book With {.Isbn = "9780306406157", .Title = "Shelf Basics"}
_books("9780131103627") = New Book With {.Isbn = "9780131103627", .Title = "Types and Tables", .OnLoan = True}
End Sub
Public Function Find(isbn As String) As Book
Dim book As Book = Nothing
Return If(_books.TryGetValue(isbn, book), book, Nothing)
End Function
Public Function Lend(isbn As String) As LoanOutcome
Dim book As Book = Find(isbn)
If book Is Nothing Then Return LoanOutcome.UnknownBook
If book.OnLoan Then Return LoanOutcome.AlreadyOnLoan
book.OnLoan = True
Return LoanOutcome.Lent
End Function
End Class
using ShelfLib;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseUrls("http://127.0.0.1:0");
builder.Configuration["Logging:LogLevel:Default"] = "None";
builder.Services.AddSingleton<CatalogService>();
var app = builder.Build();
app.MapGet("/books/{isbn}", (string isbn, CatalogService catalog) =>
catalog.Find(isbn) is { } book ? Results.Ok(book) : Results.NotFound());
app.MapPost("/loans/{isbn}", (string isbn, CatalogService catalog) => catalog.Lend(isbn) switch
{
LoanOutcome.Lent => Results.Ok(new { outcome = "lent" }),
LoanOutcome.AlreadyOnLoan => Results.Conflict(new { outcome = "already on loan" }),
_ => Results.NotFound(),
});
await app.StartAsync();
var address = app.Urls.First();
using var client = new HttpClient { BaseAddress = new Uri(address) };
Console.WriteLine("GET known : " + await client.GetStringAsync("/books/9780306406157"));
foreach (var isbn in new[] { "9780306406157", "9780306406157", "9780131103627", "0000000000000" })
{
var r = await client.PostAsync("/loans/" + isbn, null);
Console.WriteLine($"POST /loans/{isbn} -> {(int)r.StatusCode} {await r.Content.ReadAsStringAsync()}");
}
await app.StopAsync();
GET known : {"isbn":"9780306406157","title":"Shelf Basics","onLoan":false}
POST /loans/9780306406157 -> 200 {"outcome":"lent"}
POST /loans/9780306406157 -> 409 {"outcome":"already on loan"}
POST /loans/9780131103627 -> 409 {"outcome":"already on loan"}
POST /loans/0000000000000 -> 404
Source: ASP.NET Core documentation
The ShelfHost.csproj contains a <ProjectReference Include="..\ShelfLib\ShelfLib.vbproj" />. A Visual Basic Enum becomes a C#
enum that switch expressions match, a Class with auto-properties serialises to JSON with the same camelCase rule as any other,
and a Dictionary(Of ...) field stays an implementation detail. The details of exposing a Visual Basic library to C# (case-insensitivity, Module`s, `ByRef
parameters) are in
Libraries, Packaging and Deployment and
Interop with C# and Other .NET Languages.
HTTP clients
An HTTP client is the part of the web story with no Visual Basic caveats. HttpClient is the only class to use; WebClient,
HttpWebRequest and WebRequest are obsolete (the diagnostic is SYSLIB0014, shown below). The shelf’s ISBN lookup is the running
example: IsbnClient wraps one HttpClient, converts a 404 into Nothing, turns other failures into exceptions, and passes a
CancellationToken through.
Option Strict On
Imports System.Net
Imports System.Net.Http
Imports System.Net.Http.Json
Imports System.Text.Json
Imports System.Threading
Imports Microsoft.AspNetCore.Builder
Imports Microsoft.AspNetCore.Hosting
Imports Microsoft.AspNetCore.Http
Imports Microsoft.Extensions.Configuration
Imports Microsoft.Extensions.DependencyInjection
Imports Microsoft.Extensions.Hosting
' The shelf's ISBN lookup client: one HttpClient, JSON in and out, errors turned into results.
Public NotInheritable Class IsbnClient
Private ReadOnly _http As HttpClient
Public Sub New(http As HttpClient)
_http = http
End Sub
Public Async Function FindAsync(isbn As String, cancellationToken As CancellationToken) As Task(Of BookDto)
Using response As HttpResponseMessage = Await _http.GetAsync("isbn/" & isbn, cancellationToken)
If response.StatusCode = HttpStatusCode.NotFound Then
Return Nothing
End If
response.EnsureSuccessStatusCode()
Return Await response.Content.ReadFromJsonAsync(Of BookDto)(cancellationToken)
End Using
End Function
Public Async Function AddAsync(book As BookDto, cancellationToken As CancellationToken) As Task(Of String)
Using response As HttpResponseMessage = Await _http.PostAsJsonAsync("isbn", book, cancellationToken)
response.EnsureSuccessStatusCode()
Return response.Headers.Location.ToString()
End Using
End Function
End Class
Module ClientDemo
Sub Main(args As String())
RunAsync(args).GetAwaiter().GetResult()
End Sub
Async Function RunAsync(args As String()) As Task
Dim builder = WebApplication.CreateBuilder(args)
builder.WebHost.UseUrls("http://127.0.0.1:0")
builder.Configuration("Logging:LogLevel:Default") = "None"
Dim app = builder.Build()
app.MapGet("/isbn/{isbn}",
Function(isbn As String) As IResult
Return If(isbn = "9780306406157",
Results.Ok(New BookDto With {.Isbn = isbn, .Title = "Shelf Basics", .Year = 2020}),
Results.NotFound())
End Function)
app.MapPost("/isbn", Function(book As BookDto) Results.Created("/isbn/" & book.Isbn, book))
app.MapGet("/boom", Function() Results.StatusCode(503))
app.MapGet("/slow",
New Func(Of HttpContext, Task)(
Async Function(context As HttpContext) As Task
Try
Await Task.Delay(TimeSpan.FromSeconds(5), context.RequestAborted)
Catch ex As OperationCanceledException
' The client gave up; nothing to send.
End Try
End Function))
Await app.StartAsync()
' One HttpClient for the life of the program; BaseAddress needs the trailing slash for relative paths.
Using http As New HttpClient With {.BaseAddress = New Uri(Hosting.ServerAddress(app) & "/")}
Dim client As New IsbnClient(http)
Dim found As BookDto = Await client.FindAsync("9780306406157", CancellationToken.None)
Console.WriteLine("known ISBN: " & found.Title & " (" & found.Year & ")")
Dim missing As BookDto = Await client.FindAsync("0000000000000", CancellationToken.None)
Console.WriteLine("unknown ISBN: " & If(missing Is Nothing, "Nothing", missing.Title))
Console.WriteLine("added: " & Await client.AddAsync(New BookDto With {.Isbn = "9780131103627", .Title = "Types and Tables", .Year = 1988}, CancellationToken.None))
Try
Dim response = Await http.GetAsync("boom")
response.EnsureSuccessStatusCode()
Catch ex As HttpRequestException
Console.WriteLine("EnsureSuccessStatusCode: " & ex.StatusCode.ToString() & " / " & ex.Message)
End Try
Using cts As New CancellationTokenSource(TimeSpan.FromMilliseconds(200))
Try
Await http.GetAsync("slow", cts.Token)
Catch ex As OperationCanceledException
Console.WriteLine("cancelled after 200 ms: " & ex.GetType().Name)
End Try
End Using
End Using
Using shortLived As New HttpClient With {.BaseAddress = New Uri(Hosting.ServerAddress(app) & "/"), .Timeout = TimeSpan.FromMilliseconds(200)}
Try
Await shortLived.GetAsync("slow")
Catch ex As TaskCanceledException
Console.WriteLine("HttpClient.Timeout: " & ex.GetType().Name & " / inner " & ex.InnerException.GetType().Name)
End Try
End Using
Await app.StopAsync()
End Function
End Module
known ISBN: Shelf Basics (2020)
unknown ISBN: Nothing
added: /isbn/9780131103627
EnsureSuccessStatusCode: ServiceUnavailable / Response status code does not indicate success: 503 (Service Unavailable).
cancelled after 200 ms: TaskCanceledException
HttpClient.Timeout: TaskCanceledException / inner TimeoutException
Rules shown or implied by the listing:
-
One
HttpClientfor the life of the program (or one per named/typed client from the factory), not one per request: each instance holds a connection pool, and creating many exhausts sockets. Dispose it withUsingonly when you really own the lifetime. -
BaseAddressneeds a trailing slash when the relative paths have no leading slash ("isbn/..."):http://host/apiplus"isbn/1"resolves tohttp://host/isbn/1, droppingapi. The listing appends"/". -
GetFromJsonAsync(Of T),PostAsJsonAsyncandReadFromJsonAsync(Of T)(namespaceSystem.Net.Http.Json) replace the manualReadAsStringAsyncplusJsonSerializer.Deserializepair; they use the web defaults (camelCase, case-insensitive on read). They are reflection-based, so a trimmed or AOT application needs a source-generated context (a C# helper project; see the Serialization, JSON and XML page). -
EnsureSuccessStatusCode()throwsHttpRequestExceptionwhoseStatusCodeproperty carries the code (ServiceUnavailableabove). Decide per call whether a404is an error (here it is a normal "not found" result). -
Cancellation and timeouts both surface as
TaskCanceledException(a subclass ofOperationCanceledException). TheHttpClient.Timeoutexpiry carries aTimeoutExceptionas its inner exception, as the last line of the output shows; that is how to tell it from a cancellation by your own token.Timeoutis set once, before the first request: changing it afterwards throwsInvalidOperationException. -
Async is the rule: see Async and Await; do not call
.Resultor.GetAwaiter().GetResult()on a UI thread.
The obsolete classes still compile on .NET 10, but every use is reported as warning SYSLIB0014, which becomes an error under
TreatWarningsAsErrors. Creating a WebClient with New, or calling WebRequest.Create or HttpWebRequest.Create, each
produces a diagnostic of this form:
Program.vb(7,23): warning SYSLIB0014: 'Public Overloads Sub New()' is obsolete: 'WebRequest, HttpWebRequest, ServicePoint, and WebClient are obsolete. Use HttpClient instead.'. (https://aka.ms/dotnet-warnings/SYSLIB0014)
This applies to every book example that downloads with WebClient.DownloadString or builds a request with
HttpWebRequest/WebRequest.Create: the replacement is HttpClient.GetStringAsync and friends. The My.Computer.Network members that download and upload files are
listed in the table in The My Namespace.
IHttpClientFactory, typed clients and resilience
In a Generic Host or ASP.NET Core application, IHttpClientFactory manages the handler lifetimes and offers typed clients: a class
that receives an HttpClient in its constructor, registered with AddHttpClient(Of TClient). Message handlers (the HttpClient
pipeline’s middleware) are added in registration order. Microsoft.Extensions.Http.Resilience adds AddStandardResilienceHandler
(retry, circuit breaker, timeouts) in one call. The program below plays a service that fails twice with 503 and then answers; the
client retries.
Option Strict On
Imports System.Net
Imports System.Net.Http
Imports System.Net.Sockets
Imports System.Threading
Imports Microsoft.AspNetCore.Builder
Imports Microsoft.AspNetCore.Hosting
Imports Microsoft.AspNetCore.Http
Imports Microsoft.Extensions.Configuration
Imports Microsoft.Extensions.DependencyInjection
Imports Microsoft.Extensions.Hosting
' A DelegatingHandler is the HttpClient pipeline's middleware.
Public NotInheritable Class CountingHandler
Inherits DelegatingHandler
Public Shared Attempts As Integer
Protected Overrides Async Function SendAsync(request As HttpRequestMessage,
cancellationToken As CancellationToken) As Task(Of HttpResponseMessage)
Dim n As Integer = Interlocked.Increment(Attempts)
Dim response As HttpResponseMessage = Await MyBase.SendAsync(request, cancellationToken)
Console.WriteLine(" attempt " & n & " -> " & CInt(response.StatusCode))
Return response
End Function
End Class
Public NotInheritable Class ShelfApiClient
Private ReadOnly _http As HttpClient
Public Sub New(http As HttpClient)
_http = http
End Sub
Public Function GetTitleAsync() As Task(Of String)
Return _http.GetStringAsync("title")
End Function
End Class
Module FactoryDemo
Private Function FreePort() As Integer
Dim listener As New TcpListener(IPAddress.Loopback, 0)
listener.Start()
Dim port As Integer = CType(listener.LocalEndpoint, IPEndPoint).Port
listener.Stop()
Return port
End Function
Sub Main(args As String())
RunAsync(args).GetAwaiter().GetResult()
End Sub
Async Function RunAsync(args As String()) As Task
Dim port As Integer = FreePort()
' The flaky service: the first two calls answer 503, the third succeeds.
Dim serverBuilder = WebApplication.CreateBuilder(args)
serverBuilder.WebHost.UseUrls("http://127.0.0.1:" & port)
serverBuilder.Configuration("Logging:LogLevel:Default") = "None"
Dim server = serverBuilder.Build()
Dim calls As Integer = 0
server.MapGet("/title",
Function() As IResult
calls += 1
Return If(calls < 3, Results.StatusCode(503), Results.Text("Shelf Basics"))
End Function)
Await server.StartAsync()
' The client side: Generic Host, typed client, resilience handler.
Dim builder = Host.CreateApplicationBuilder(args)
builder.Configuration("Logging:LogLevel:Default") = "None"
builder.Services.AddTransient(Of CountingHandler)()
Dim clientBuilder = builder.Services.AddHttpClient(Of ShelfApiClient)(
Sub(client) client.BaseAddress = New Uri("http://127.0.0.1:" & port & "/"))
' Handlers run in registration order: the resilience handler first, so the counting handler sees every attempt.
clientBuilder.AddStandardResilienceHandler(
Sub(options)
options.Retry.MaxRetryAttempts = 3
options.Retry.Delay = TimeSpan.FromMilliseconds(20)
options.Retry.UseJitter = False
End Sub)
clientBuilder.AddHttpMessageHandler(Of CountingHandler)()
Using host As IHost = builder.Build()
Dim api = host.Services.GetRequiredService(Of ShelfApiClient)()
Console.WriteLine("title: " & Await api.GetTitleAsync())
End Using
Await server.StopAsync()
End Function
End Module
attempt 1 -> 503
attempt 2 -> 503
attempt 3 -> 200
title: Shelf Basics
Visual Basic points. A handler subclass overrides SendAsync with an Async Function declared Protected Overrides and returning Task(Of HttpResponseMessage); the base call is MyBase.SendAsync. The builder calls are made as separate statements on the
IHttpClientBuilder because the order of registration is the order of execution, and the resilience handler is registered before
the counting handler so that the counter sits inside the retry loop and sees all three attempts (registered the other way round it would
count once). AddHttpClient(Of ShelfApiClient)(Sub(client) ...) takes a Sub lambda; options.Retry.Delay = ... inside a Sub lambda
is an assignment statement and needs no Return. The defaults of the standard handler are tuned for production; the listing shortens the retry delay and turns jitter off
so that the run is quick and repeatable. Host.CreateApplicationBuilder(args) is the
non-web host builder (no Kestrel); it needs no Imports beyond Microsoft.Extensions.Hosting.
SignalR and WebSocket clients
The SignalR .NET client (Microsoft.AspNetCore.SignalR.Client) is a library. The pieces to get right in Visual Basic: handlers are
registered with connection.On(Of T1, T2)("Method", Sub(a, b) ...) before StartAsync, a Sub lambda is the
Action<T1, T2> overload (an Async Function lambda would bind the Func<T1, T2, Task> overload), InvokeAsync calls a hub
method by name, and a connection is released with Await connection.DisposeAsync() because Visual Basic has no Await Using. The
program hosts the hub and a raw WebSocket endpoint and calls both from clients.
Option Strict On
Imports System.Net
Imports System.Net.Sockets
Imports System.Net.WebSockets
Imports System.Text
Imports System.Threading
Imports Microsoft.AspNetCore.Builder
Imports Microsoft.AspNetCore.Hosting
Imports Microsoft.AspNetCore.Http
Imports Microsoft.AspNetCore.SignalR
Imports Microsoft.AspNetCore.SignalR.Client
Imports Microsoft.Extensions.Configuration
Imports Microsoft.Extensions.DependencyInjection
Imports Microsoft.Extensions.Hosting
' Server side: a hub is an ordinary class; clients call its public methods by name.
Public Class LoanHub
Inherits Hub
Public Async Function ReturnBook(isbn As String, member As String) As Task
Await Clients.All.SendAsync("BookReturned", isbn, member)
End Function
End Class
Module RealtimeDemo
Sub Main(args As String())
RunAsync(args).GetAwaiter().GetResult()
End Sub
Async Function RunAsync(args As String()) As Task
Dim builder = WebApplication.CreateBuilder(args)
builder.WebHost.UseUrls("http://127.0.0.1:0")
builder.Configuration("Logging:LogLevel:Default") = "None"
builder.Services.AddSignalR()
Dim app = builder.Build()
app.UseWebSockets()
app.MapHub(Of LoanHub)("/hubs/loans")
app.Map("/ws/echo",
New Func(Of HttpContext, Task)(
Async Function(context As HttpContext) As Task
If Not context.WebSockets.IsWebSocketRequest Then
context.Response.StatusCode = 400
Return
End If
Using socket As WebSocket = Await context.WebSockets.AcceptWebSocketAsync()
Dim buffer(1023) As Byte
Dim received = Await socket.ReceiveAsync(New ArraySegment(Of Byte)(buffer), CancellationToken.None)
Await socket.SendAsync(New ArraySegment(Of Byte)(buffer, 0, received.Count),
WebSocketMessageType.Text, True, CancellationToken.None)
Await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None)
End Using
End Function))
Await app.StartAsync()
Dim baseAddress As String = Hosting.ServerAddress(app)
' SignalR .NET client: build, register handlers with On, start, invoke.
Dim connection As HubConnection = New HubConnectionBuilder().
WithUrl(baseAddress & "/hubs/loans").
Build()
Dim gotMessage As New TaskCompletionSource(Of String)(TaskCreationOptions.RunContinuationsAsynchronously)
connection.On(Of String, String)("BookReturned",
Sub(isbn, member) gotMessage.TrySetResult(member & " returned " & isbn))
Await connection.StartAsync()
Console.WriteLine("connection state: " & connection.State.ToString())
Await connection.InvokeAsync("ReturnBook", "9780306406157", "Ada")
Console.WriteLine("hub pushed: " & Await gotMessage.Task)
Await connection.StopAsync()
Await connection.DisposeAsync()
' Plain WebSocket client.
Using client As New ClientWebSocket()
Dim wsUri As New Uri(baseAddress.Replace("http://", "ws://") & "/ws/echo")
Await client.ConnectAsync(wsUri, CancellationToken.None)
Await client.SendAsync(New ArraySegment(Of Byte)(Encoding.UTF8.GetBytes("ping from VB")),
WebSocketMessageType.Text, True, CancellationToken.None)
Dim buffer(1023) As Byte
Dim result = Await client.ReceiveAsync(New ArraySegment(Of Byte)(buffer), CancellationToken.None)
Console.WriteLine("websocket echo: " & Encoding.UTF8.GetString(buffer, 0, result.Count))
End Using
Await app.StopAsync()
End Function
End Module
connection state: Connected
hub pushed: Ada returned 9780306406157
websocket echo: ping from VB
Notes. TaskCompletionSource(Of String) with RunContinuationsAsynchronously turns the push into something the program can
Await. Dim buffer(1023) As Byte declares 1,024 bytes (Visual Basic array bounds are upper bounds). The WebSocket endpoint is
mapped with an explicit Func(Of HttpContext, Task) for the same reason as in the minimal-API section: a delegate type the
framework recognises. SignalR clients for Web Forms and MVC 5 are the older ASP.NET SignalR 2 libraries, documented in
SignalR 2 in MVC 5; the ASP.NET Core client above does not talk to a SignalR 2 server.
SOAP and WCF clients
The books show "Add Service Reference" wizards and ASMX proxies. On .NET 10 the equivalents are the WCF client libraries
(System.ServiceModel.Http and System.ServiceModel.Primitives) plus dotnet-svcutil, a command-line tool that reads a WSDL and
writes the client code. The tool runs for Visual Basic projects but wrote C# in the test: it produced ServiceReference1/Reference.cs
next to a .vbproj and did not touch the project, because a Visual Basic project cannot compile a .cs file. The practical pattern
is to compile the generated file in a small C# library and reference it from Visual Basic.
To test this without an external service the example hosts a SOAP service with CoreWCF (an open-source server-side port of WCF) in a Visual Basic ASP.NET Core application, then generates a proxy from its WSDL and calls it. The server, built and run on port 5080:
Option Strict On
Imports CoreWCF
Imports CoreWCF.Configuration
Imports CoreWCF.Description
Imports Microsoft.AspNetCore.Builder
Imports Microsoft.AspNetCore.Hosting
Imports Microsoft.Extensions.Configuration
Imports Microsoft.Extensions.DependencyInjection
<ServiceContract(Namespace:="urn:shelf")>
Public Interface IIsbnService
<OperationContract>
Function TitleOf(isbn As String) As String
End Interface
Public Class IsbnService
Implements IIsbnService
Public Function TitleOf(isbn As String) As String Implements IIsbnService.TitleOf
Return If(isbn = "9780306406157", "Shelf Basics", "(unknown)")
End Function
End Class
Module Program
Sub Main(args As String())
Dim builder = WebApplication.CreateBuilder(args)
builder.WebHost.UseUrls("http://127.0.0.1:5080")
builder.Configuration("Logging:LogLevel:Default") = "None"
builder.Services.AddServiceModelServices().AddServiceModelMetadata()
builder.Services.AddSingleton(Of IServiceBehavior, UseRequestHeadersForMetadataAddressBehavior)()
Dim app = builder.Build()
' WebApplication is both an IApplicationBuilder and an IHost, and UseServiceModel exists for both: pick one.
Dim pipeline As IApplicationBuilder = app
pipeline.UseServiceModel(Sub(serviceBuilder As IServiceBuilder)
serviceBuilder.AddService(Of IsbnService)()
serviceBuilder.AddServiceEndpoint(Of IsbnService, IIsbnService)(New BasicHttpBinding(), "/IsbnService.svc")
End Sub)
app.Services.GetRequiredService(Of ServiceMetadataBehavior)().HttpGetEnabled = True
app.Run()
End Sub
End Module
UseServiceModel exists as an extension of IApplicationBuilder and of IHost, and WebApplication is both, so a direct
app.UseServiceModel(...) fails with BC30521 (ambiguous overload); assign app to an IApplicationBuilder variable first. The
lambda needs Sub(serviceBuilder As IServiceBuilder) for the same overload reasons.
With the server running, the generator is run against its WSDL (the installed tool, version 8.0.0, targets an older runtime than the
container’s .NET 10 and ran with the environment variable DOTNET_ROLL_FORWARD=Major):
dotnet tool install --global dotnet-svcutil
dotnet-svcutil http://127.0.0.1:5080/IsbnService.svc?wsdl -n "*,IsbnClient.Proxy" --noLogo
Attempting to download metadata from 'http://127.0.0.1:5080/IsbnService.svc?wsdl' using WS-Metadata Exchange and HttpGet.
Generating files...
ServiceReference1/Reference.cs
The generated Reference.cs declares IIsbnService, IsbnServiceClient (derived from ClientBase), and a task-based
TitleOfAsync. It was compiled in this ProxyLib.csproj and referenced from the Visual Basic client:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.ServiceModel.Http" Version="8.*" />
<PackageReference Include="System.ServiceModel.Primitives" Version="8.*" />
</ItemGroup>
</Project>
Option Strict On
Imports System.ServiceModel
Imports IsbnClient.Proxy
Module Program
Sub Main()
RunAsync().GetAwaiter().GetResult()
End Sub
Async Function RunAsync() As Task
Dim client As New IsbnServiceClient(New BasicHttpBinding(), New EndpointAddress("http://127.0.0.1:5080/IsbnService.svc"))
Try
Console.WriteLine("SOAP call: " & Await client.TitleOfAsync("9780306406157"))
Console.WriteLine("SOAP call: " & Await client.TitleOfAsync("0000000000000"))
Await client.CloseAsync()
Catch ex As Exception
client.Abort()
Throw
End Try
End Function
End Module
SOAP call: Shelf Basics
SOAP call: (unknown)
Source: WCF Web Service Reference Provider and dotnet-svcutil, What is Windows Communication Foundation
Try ... Catch around the client follows the WCF rule that a faulted channel must be Abort`ed, not `Close`d; the `Await
client.CloseAsync() on success is the polite shutdown. If the project that consumes the service is a library whose only job is the
proxy, keep it C#, and keep the Visual Basic logic on the other side of an interface. Server-side WCF (ServiceHost, .svc files) and
ASMX stay on .NET Framework; if a service must be kept alive on .NET 10 the open-source CoreWCF project hosts WCF contracts on
ASP.NET Core, as in the test server above; for new work, prefer REST with the minimal API shown earlier.
gRPC and other protocols
gRPC clients and servers are generated from .proto files by the Grpc.Tools MSBuild package, which emits C# only. A Visual Basic
program can use a gRPC client by compiling the generated code in a C# project and referencing it, the same pattern as the WCF proxy.
.NET also has an IoT stack (System.Device.Gpio and the Iot.Device.Bindings package) usable from Visual Basic like any
.NET library; it is outside this page.
Where the books are outdated
The two Visual Basic books this reference was checked against (see the section bibliography on the index page) present several web topics that need updating for .NET 10:
| What the books show | What is current |
|---|---|
ASP.NET Core, SignalR with the |
There is no Visual Basic template and no Razor syntax. The |
Visual Studio 2019 is needed for Visual Basic Web Forms because newer versions lack VB ASP.NET. |
Visual Studio 2026 still has "ASP.NET Web Application (.NET Framework)" for Visual Basic. Only ASP.NET Core lacks VB support. |
ASP.NET hosted on Apache or nginx. |
Web Forms and MVC 5 need IIS and .NET Framework. ASP.NET Core can sit behind nginx or Apache as a reverse proxy, but that does not make Web Forms portable. |
|
Obsolete since .NET 6 (SYSLIB0014). Use |
"Add Service Reference" and ASMX proxies. |
The WCF client libraries plus |
|
ASP.NET Core session middleware with typed accessors, a distributed cache for real deployments, and singletons for application-wide state. |
Classic ASP pages ( |
Not a .NET technology and not covered here; see The VB Family: VB6, VBA and VBScript for recognition and migration notes only. |
Differences from C#
| Topic | C# | Visual Basic |
|---|---|---|
Web templates |
|
None for ASP.NET Core; Visual Studio keeps Web Forms and MVC 5 templates for .NET Framework. |
Implicit usings |
|
Not applied; import every namespace (extension methods fail with BC30456). |
Method group as a handler |
|
|
Attributes on handler parameters |
|
Not on lambdas (BC36634); use a named method or |
Default values in lambdas |
|
|
Async handlers |
|
Type the lambda as |
Async filters |
|
An |
Entry point |
Top-level statements, |
|
Models |
|
Classes with auto-properties and a parameterless constructor. |
|
|
|
Views |
Razor Pages, MVC views, Blazor. |
Not available; |
Source generators (request delegates, JSON, OpenAPI comments) |
Run and are what enable Native AOT for ASP.NET Core. |
The |
Pitfalls
-
An Async Main does not exist.
Async Function Main(args As String()) As Taskfails withBC30737: No accessible 'Main' method with an appropriate signature was found in 'App'. UseSub Mainand wait:RunAsync(args).GetAwaiter().GetResult(). -
Changing
StartupObjecton the command line without--no-incrementalre-runs the previous program. -
A local named after a child namespace (
schemawithImports System.Text.Json,textwithImports System) hides or is hidden by the namespace, because Visual Basic names are case-insensitive. -
Imports Microsoft.Extensions.LoggingbreaksConsole.WriteLinein the same file (BC30456): qualify asSystem.Consoleor keep that import in another file. -
Publicis required for controllers, hubs and DTOs; a type declared in a defaultFriend ModuleisFriendand invisible to discovery and to the JSON serialisers. -
A lambda passed where a
Delegateis expected becomes a compiler-generatedVB$AnonymousDelegatetype: give the handler a real delegate type when its return type matters (async handlers). -
Do not trust a build for a view layer:
.cshtml/.razorfiles in a VB project compile to nothing. -
Security: the shelf examples are demonstrations. Real services need authentication, authorization and hardened headers; see Authentication and Identity and Security Hardening. SQL access from a handler is in Data Access: ADO.NET and EF Core; secrets and cryptography are in Security and Cryptography.
Compiler messages seen on this page
| Code | Message (first line, as printed by the build) | Cause |
|---|---|---|
BC30456 |
|
Extension method namespace not imported (no implicit usings in VB). |
BC30518 |
|
|
BC36634 |
|
|
BC33010 |
|
Default value on a lambda parameter. |
BC30737 |
|
|
BC36945 |
|
|
BC30521 |
|
|
BC30002 |
|
|
SYSLIB0014 |
|
Obsolete networking classes (a warning). |
BC codes on this page have no per-code pages in this site, so they are plain text; the Microsoft documentation lists each Visual Basic error under its code in the Visual Basic compiler messages reference.
Version notes
-
Visual Basic 11 (Visual Studio 2012) introduced
Async/Await; everything on the client side of this page needs that or later. -
The
If(a, b)null-coalescing operator arrived in Visual Basic 9 (Visual Studio 2008). -
ASP.NET Core minimal APIs (
WebApplication.CreateBuilder) arrived with .NET 6.AddOpenApi()(the in-box OpenAPI document generator) arrived with .NET 9; the examples here target .NET 10. -
SYSLIB0014 was added in .NET 6.
References
-
ASP.NET Core documentation, Minimal APIs overview, Create web APIs with ASP.NET Core and Middleware
-
OpenAPI support, Session and state management and Integration tests
-
HttpClient, HttpClient guidelines, IHttpClientFactory and HTTP resilience
-
ASP.NET Web Forms, ASP.NET MVC and ASP.NET overview for the .NET Framework stacks