ASP.NET Core updates in .NET 7 Preview 2

.NET 7 Preview 2 is now available and includes many great new improvements to ASP.NET Core.

Here’s a summary of what’s new in this preview release:

Infer API controller action parameters that come from services
Dependency injection for SignalR hub methods
Provide endpoint descriptions and summaries for minimal APIs
Binding arrays and StringValues from headers and query strings in minimal APIs
Customize the cookie consent value

For more details on the ASP.NET Core work planned for .NET 7 see the full ASP.NET Core roadmap for .NET 7 on GitHub.

Get started

To get started with ASP.NET Core in .NET 7 Preview 2, install the .NET 7 SDK.

If you’re on Windows using Visual Studio, we recommend installing the latest Visual Studio 2022 preview. Visual Studio for Mac support for .NET 7 previews isn’t available yet but is coming soon.

To install the latest .NET WebAssembly build tools, run the following command from an elevated command prompt:

dotnet workload install wasm-tools

Upgrade an existing project

To upgrade an existing ASP.NET Core app from .NET 7 Preview 1 to .NET 7 Preview 2:

Update all Microsoft.AspNetCore.* package references to 7.0.0-preview.2.*.
Update all Microsoft.Extensions.* package references to 7.0.0-preview.2.*.

See also the full list of breaking changes in ASP.NET Core for .NET 7.

Infer API controller action parameters that come from services

Parameter binding for API controller actions now binds parameters through dependency injection when the type is configured as a service. This means it’s no longer required to explicitly apply the [FromServices] attribute to a parameter.

Services.AddScoped<SomeCustomType>();

[Route(“[controller]”)]
[ApiController]
public class MyController : ControllerBase
{
// Both actions will bound the SomeCustomType from the DI container
public ActionResult GetWithAttribute([FromServices]SomeCustomType service) => Ok();
public ActionResult Get(SomeCustomType service) => Ok();
}

You can disable the feature by setting DisableImplicitFromServicesParameters:

Services.Configure<ApiBehaviorOptions>(options =>
{
options.DisableImplicitFromServicesParameters = true;
})

Dependency injection for SignalR hub methods

SignalR hub methods now support injecting services through dependency injection (DI).

Services.AddScoped<SomeCustomType>();

public class MyHub : Hub
{
// SomeCustomType comes from DI by default now
public Task Method(string text, SomeCustomType type) => Task.CompletedTask;
}

You can disable the feature by setting DisableImplicitFromServicesParameters:

services.AddSignalR(options =>
{
options.DisableImplicitFromServicesParameters = true;
});

To explicitly mark a parameter to be bound from configured services, use the [FromServices] attribute:

public class MyHub : Hub
{
public Task Method(string arguments, [FromServices] SomeCustomType type);
}

Provide endpoint descriptions and summaries for minimal APIs

Minimal APIs now support annotating operations with descriptions and summaries used for OpenAPI spec generation. You can set these descriptions and summaries for route handlers in your minimal API apps using an extension methods:

app.MapGet(“/hello”, () => …)
.WithDescription(“Sends a request to the backend HelloService to process a greeting request.”);

Or set the description or summary via attributes on the route handler delegate:

app.MapGet(“/hello”, [EndpointSummary(“Sends a Hello request to the backend”)]() => …)

Binding arrays and StringValues from headers and query strings in minimal APIs

With this release, you can now bind values from HTTPS headers and query strings to arrays of primitive types, string arrays, or StringValues:

// Bind query string values to a primitive type array
// GET /tags?q=1&q=2&q=3
app.MapGet(“/tags”, (int[] q) => $”tag1: {q[0]} , tag2: {q[1]}, tag3: {q[2]}”)

// Bind to a string array
// GET /tags?names=john&names=jack&names=jane
app.MapGet(“/tags”, (string[] names) => $”tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}”)

// Bind to StringValues
// GET /tags?names=john&names=jack&names=jane
app.MapGet(“/tags”, (StringValues names) => $”tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}”)

You can also bind query strings or header values to an array of a complex type as long as the type has TryParse implementation as demonstrated in the example below.

// Bind to aan array of a complex type
// GET /tags?tags=trendy&tags=hot&tags=spicy
app.MapGet(“/tags”, (Tag[] tags) =>
{
return Results.Ok(tags);
});

class Tag
{
public string? TagName { get; init; }

public static bool TryParse(string? tagName, out Tag tag)
{
if (tagName is null)
{
tag = default;
return false;
}

tag = new Tag { TagName = tagName };
return true;
}
}

Customize the cookie consent value

You can now specify the value used to track if the user consented to the cookie use policy using the new CookiePolicyOptions.ConsentCookieValue property.

Thank you @daviddesmet for contributing this improvement!

Request for feedback on shadow copying for IIS

In .NET 6 we added experimental support for shadow copying app assemblies to the ASP.NET Core Module (ANCM) for IIS. When an ASP.NET Core app is running on Windows, the binaries are locked so that they cannot be modified or replaced. You can stop the app by deploying an app offline file, but sometimes doing so is inconvenient or impossible. Shadow copying enables the app assemblies to be updated while the app is running by making a copy of the assemblies.

You can enable shadow copying by customizing the ANCM handler settings in web.config:

<?xml version=”1.0″ encoding=”utf-8″?>
<configuration>
<system.webServer>
<handlers>
<remove name=”aspNetCore”/>
<add name=”aspNetCore” path=”*” verb=”*” modules=”AspNetCoreModuleV2″ resourceType=”Unspecified”/>
</handlers>
<aspNetCore processPath=”%LAUNCHER_PATH%” arguments=”%LAUNCHER_ARGS%” stdoutLogEnabled=”false” stdoutLogFile=”.logsstdout”>
<handlerSettings>
<handlerSetting name=”experimentalEnableShadowCopy” value=”true” />
<handlerSetting name=”shadowCopyDirectory” value=”../ShadowCopyDirectory/” />
</handlerSettings>
</aspNetCore>
</system.webServer>
</configuration>

We’re investigating making shadow copying in IIS a feature of ASP.NET Core in .NET 7, and we’re seeking additional feedback on whether the feature satisfies user requirements. If you deploy ASP.NET Core to IIS, please give shadow copying a try and share with us your feedback on GitHub.

Give feedback

We hope you enjoy this preview release of ASP.NET Core in .NET 7. Let us know what you think about these new improvements by filing issues on GitHub.

Thanks for trying out ASP.NET Core!

The post ASP.NET Core updates in .NET 7 Preview 2 appeared first on .NET Blog.

Flatlogic Admin Templates banner

Transforming identity claims in ASP.NET Core and Cache

The article shows how to add extra identity claims to an ASP.NET Core application which authenticates using the Microsoft.Identity.Web client library and Azure AD B2C or Azure AD as the identity provider (IDP). This could easily be switched to OpenID Connect and use any IDP which supports OpenID Connect. The extra claims are added after an Azure Microsoft Graph HTTP request and it is important that this is only called once for a user session.

Code https://github.com/damienbod/azureb2c-fed-azuread

Normally I use the IClaimsTransformation interface to add extra claims to an ASP.NET Core session. This interface gets called multiple times and has no caching solution. If using this interface to add extra claims to you application, you must implement a cache solution for the extra claims and prevent extra API calls or database requests with every request. Instead of implementing a cache and using the IClaimsTransformation interface, alternatively you could just use the OnTokenValidated event with the OpenIdConnectDefaults.AuthenticationScheme scheme. This gets called after a successfully authentication against your identity provider. If Microsoft.Identity.Web is used as the OIDC client which is specific for Azure AD and Azure B2C, you must add the configuration to the MicrosoftIdentityOptions otherwise downstream APIs will not work. If using OpenID Connect directly and a different IDP, then use the OpenIdConnectOptions configuration. This can be added to the services of the ASP.NET Core application.

services.Configure<MicrosoftIdentityOptions>(
OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.Events.OnTokenValidated = async context =>
{
if (ApplicationServices != null && context.Principal != null)
{
using var scope = ApplicationServices.CreateScope();
context.Principal = await scope.ServiceProvider
.GetRequiredService<MsGraphClaimsTransformation>()
.TransformAsync(context.Principal);
}
};
});

Note

If using default OpenID Connect and not the Microsoft.Identity.Web client to authenticate, use the OpenIdConnectOptions and not the MicrosoftIdentityOptions.

Here’s an example of an OIDC setup.

builder.Services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.Events.OnTokenValidated = async context =>
{
if(ApplicationServices != null && context.Principal != null)
{
using var scope = ApplicationServices.CreateScope();
context.Principal = await scope.ServiceProvider
.GetRequiredService<MyClaimsTransformation>()
.TransformAsync(context.Principal);
}
};
});

The IServiceProvider ApplicationServices are used to add the scoped MsGraphClaimsTransformation service which is used to add the extra calls using Microsoft Graph. This needs to be added to the configuration in the startup or the program file.

protected IServiceProvider ApplicationServices { get; set; } = null;

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
ApplicationServices = app.ApplicationServices;

The Microsoft Graph services are added to the IoC.

services.AddScoped<MsGraphService>();
services.AddScoped<MsGraphClaimsTransformation>();

The MsGraphClaimsTransformation uses the Microsoft Graph client to get groups of a user, create a new ClaimsIdentity, add the extra claims to this group and add the ClaimsIdentity to the ClaimsPrincipal.

using AzureB2CUI.Services;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;

namespace AzureB2CUI;

public class MsGraphClaimsTransformation
{
private readonly MsGraphService _msGraphService;

public MsGraphClaimsTransformation(MsGraphService msGraphService)
{
_msGraphService = msGraphService;
}

public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
ClaimsIdentity claimsIdentity = new();
var groupClaimType = “group”;
if (!principal.HasClaim(claim => claim.Type == groupClaimType))
{
var objectidentifierClaimType = “http://schemas.microsoft.com/identity/claims/objectidentifier”;
var objectIdentifier = principal.Claims.FirstOrDefault(t => t.Type == objectidentifierClaimType);

var groupIds = await _msGraphService.GetGraphApiUserMemberGroups(objectIdentifier.Value);

foreach (var groupId in groupIds.ToList())
{
claimsIdentity.AddClaim(new Claim(groupClaimType, groupId));
}
}

principal.AddIdentity(claimsIdentity);
return principal;
}
}

The MsGraphService service implements the different HTTP requests to Microsoft Graph. Azure AD B2C is used in this example and so an application client is used to access the Azure AD with the ClientSecretCredential. The implementation is setup to use secrets from Azure Key Vault directly in any deployments, or from user secrets for development.

using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Graph;
using System.Threading.Tasks;

namespace AzureB2CUI.Services;

public class MsGraphService
{
private readonly GraphServiceClient _graphServiceClient;

public MsGraphService(IConfiguration configuration)
{
string[] scopes = configuration.GetValue<string>(“GraphApi:Scopes”)?.Split(‘ ‘);
var tenantId = configuration.GetValue<string>(“GraphApi:TenantId”);

// Values from app registration
var clientId = configuration.GetValue<string>(“GraphApi:ClientId”);
var clientSecret = configuration.GetValue<string>(“GraphApi:ClientSecret”);

var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};

// https://docs.microsoft.com/dotnet/api/azure.identity.clientsecretcredential
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret, options);

_graphServiceClient = new GraphServiceClient(clientSecretCredential, scopes);
}

public async Task<User> GetGraphApiUser(string userId)
{
return await _graphServiceClient.Users[userId]
.Request()
.GetAsync();
}

public async Task<IUserAppRoleAssignmentsCollectionPage> GetGraphApiUserAppRoles(string userId)
{
return await _graphServiceClient.Users[userId]
.AppRoleAssignments
.Request()
.GetAsync();
}

public async Task<IDirectoryObjectGetMemberGroupsCollectionPage> GetGraphApiUserMemberGroups(string userId)
{
var securityEnabledOnly = true;

return await _graphServiceClient.Users[userId]
.GetMemberGroups(securityEnabledOnly)
.Request().PostAsync();
}
}

When the application is run, the two ClaimsIdentity instances exist with every request and are available for using in the ASP.NET Core application.

Notes

This works really well but you should not add too many claims to the identity in this way. If you have many identity descriptions or a lot of user data, then you should use the IClaimsTransformation interface with a good cache solution.

Links

https://docs.microsoft.com/en-us/aspnet/core/security/authentication/claims

https://andrewlock.net/exploring-dotnet-6-part-10-new-dependency-injection-features-in-dotnet-6/

Flatlogic Admin Templates banner

Create Azure B2C users with Microsoft Graph and ASP.NET Core

This article shows how to create different types of Azure B2C users using Microsoft Graph and ASP.NET Core. The users are created using application permissions in an Azure App registration.

Code https://github.com/damienbod/azureb2c-fed-azuread

The Microsoft.Identity.Web Nuget package is used to authenticate the administrator user that can create new Azure B2C users. An ASP.NET Core Razor page application is used to implement the Azure B2C user management and also to hold the sensitive data.

public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<MsGraphService>();
services.AddTransient<IClaimsTransformation, MsGraphClaimsTransformation>();
services.AddHttpClient();

services.AddOptions();

services.AddMicrosoftIdentityWebAppAuthentication(Configuration, “AzureAdB2C”)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();

The AzureAdB2C app settings configures the B2C client. An Azure B2C user flow is implemented for authentication. In this example, a signin or signup flow is implemented, although if creating your own user, maybe only a signin is required. The GraphApi configuration is used for the Microsoft Graph application client with uses the client credentials flow. A user secret was created to access the Azure App registration. This secret is stored in the user secrets for development and stored in Azure Key Vault for any deployments. You could use certificates as well but this offers no extra security unless using directly from a client host.

“AzureAdB2C”: {
“Instance”: “https://b2cdamienbod.b2clogin.com”,
“ClientId”: “8cbb1bd3-c190-42d7-b44e-42b20499a8a1”,
“Domain”: “b2cdamienbod.onmicrosoft.com”,
“SignUpSignInPolicyId”: “B2C_1_signup_signin”,
“TenantId”: “f611d805-cf72-446f-9a7f-68f2746e4724”,
“CallbackPath”: “/signin-oidc”,
“SignedOutCallbackPath “: “/signout-callback-oidc”
},
“GraphApi”: {
“TenantId”: “f611d805-cf72-446f-9a7f-68f2746e4724”,
“ClientId”: “1d171c13-236d-4c2b-ac10-0325be2cbc74”,
“Scopes”: “.default”
//”ClientSecret”: “–in-user-settings–”
},
“AadIssuerDomain”: “damienbodhotmail.onmicrosoft.com”,

The application User.ReadWrite.All permission is used to create the users. See the permissions in the Microsoft Graph docs.

The MsGraphService service implements the Microsoft Graph client to create Azure tenant users. Application permissions are used because we use Azure B2C. If authenticating using Azure AD, you could use delegated permissions. The ClientSecretCredential is used to get the Graph access token and client with the required permissions.

public MsGraphService(IConfiguration configuration)
{
string[] scopes = configuration.GetValue<string>(“GraphApi:Scopes”)?.Split(‘ ‘);
var tenantId = configuration.GetValue<string>(“GraphApi:TenantId”);

// Values from app registration
var clientId = configuration.GetValue<string>(“GraphApi:ClientId”);
var clientSecret = configuration.GetValue<string>(“GraphApi:ClientSecret”);

_aadIssuerDomain = configuration.GetValue<string>(“AadIssuerDomain”);
_aadB2CIssuerDomain = configuration.GetValue<string>(“AzureAdB2C:Domain”);

var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};

// https://docs.microsoft.com/dotnet/api/azure.identity.clientsecretcredential
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret, options);

_graphServiceClient = new GraphServiceClient(clientSecretCredential, scopes);
}

The CreateAzureB2CSameDomainUserAsync method creates a same domain Azure B2C user and also creates an initial password which needs to be updated after a first signin. The users UserPrincipalName email must match the Azure B2C domain and the users can only signin with the the password. MFA should be setup. This works really good but it is not a good idea to handle passwords from your users, if this can be avoided. You need to share this with the user in a secure way.

public async Task<(string Upn, string Password, string Id)>
CreateAzureB2CSameDomainUserAsync(UserModelB2CTenant userModel)
{
if(!userModel.UserPrincipalName.ToLower().EndsWith(_aadB2CIssuerDomain.ToLower()))
{
throw new ArgumentException(“incorrect Email domain”);
}

var password = GetEncodedRandomString();
var user = new User
{
AccountEnabled = true,
UserPrincipalName = userModel.UserPrincipalName,
DisplayName = userModel.DisplayName,
Surname = userModel.Surname,
GivenName = userModel.GivenName,
PreferredLanguage = userModel.PreferredLanguage,
MailNickname = userModel.DisplayName,
PasswordProfile = new PasswordProfile
{
ForceChangePasswordNextSignIn = true,
Password = password
}
};

await _graphServiceClient.Users
.Request()
.AddAsync(user);

return (user.UserPrincipalName, user.PasswordProfile.Password, user.Id);
}

The CreateFederatedUserWithPasswordAsync method creates an Azure B2C with any email address. This uses the SignInType federated, but uses a password and the user signs in directly to the Azure B2C. This password is not updated after a first signin. Again this is a bad idea because you need share the password with the user somehow and you as an admin should not know the user password. I would avoid creating users in this way and use a custom invitation flow, if you need this type of Azure B2C user.

public async Task<(string Upn, string Password, string Id)>
CreateFederatedUserWithPasswordAsync(UserModelB2CIdentity userModel)
{
// new user create, email does not matter unless you require to send mails
var password = GetEncodedRandomString();
var user = new User
{
DisplayName = userModel.DisplayName,
PreferredLanguage = userModel.PreferredLanguage,
Surname = userModel.Surname,
GivenName = userModel.GivenName,
OtherMails = new List<string> { userModel.Email },
Identities = new List<ObjectIdentity>()
{
new ObjectIdentity
{
SignInType = “federated”,
Issuer = _aadB2CIssuerDomain,
IssuerAssignedId = userModel.Email
},
},
PasswordProfile = new PasswordProfile
{
Password = password,
ForceChangePasswordNextSignIn = false
},
PasswordPolicies = “DisablePasswordExpiration”
};

var createdUser = await _graphServiceClient.Users
.Request()
.AddAsync(user);

return (createdUser.UserPrincipalName, user.PasswordProfile.Password, createdUser.Id);
}

The CreateFederatedNoPasswordAsync method creates an Azure B2C federated user from a specific Azure AD domain which already exists and no password. The user can only signin using a federated signin to this tenant. No passwords are shared. This is really good way to onboard existing AAD users to an Azure B2C tenant. One disadvantage with this is that the email is not verified unlike implementing this using an invitation flow directly in the Azure AD tenant.

public async Task<string>
CreateFederatedNoPasswordAsync(UserModelB2CIdentity userModel)
{
// User must already exist in AAD
var user = new User
{
DisplayName = userModel.DisplayName,
PreferredLanguage = userModel.PreferredLanguage,
Surname = userModel.Surname,
GivenName = userModel.GivenName,
OtherMails = new List<string> { userModel.Email },
Identities = new List<ObjectIdentity>()
{
new ObjectIdentity
{
SignInType = “federated”,
Issuer = _aadIssuerDomain,
IssuerAssignedId = userModel.Email
},
}
};

var createdUser = await _graphServiceClient.Users
.Request()
.AddAsync(user);

return createdUser.UserPrincipalName;
}

When the application is started, you can signin as an IT admin and create new users as required. The Birthday can only be added if you have an SPO license. If the user exists in the AAD tenant, the user can signin using the federated identity provider. This could be improved by adding a search of the users in the target tenant and only allowing existing users.

Notes:

It is really easy to create users using Microsoft Graph but this is not always the best way, or a secure way of onboarding new users in an Azure B2C tenant. If local data is required, this can be really useful. Sharing passwords between an IT admin and a new user should be avoided if possible. The Microsoft Graph invite APIs do not work for Azure AD B2C, only Azure AD.

Links

https://docs.microsoft.com/en-us/aspnet/core/introduction-to-aspnet-core

Flatlogic Admin Templates banner

Adding feature flags to an ASP.NET Core app

This post is about Adding feature flags to an ASP.NET Core app.Feature flags (also known as feature toggles or feature switches) are a software development technique that turns certain functionality on and off during runtime, without deploying new code. In this post we will discuss about flags using appsettings.json file. I am using an ASP.NET Core MVC project, you can do it for any .NET Core project like Razor Web Apps, or Web APIs.

First we need to add reference of Microsoft.FeatureManagement.AspNetCore nuget package – This package created by Microsoft, it will support creation of simple on/off feature flags as well as complex conditional flags. Once this package added, we need to add the following code to inject the Feature Manager instance to the http pipeline.

using Microsoft.FeatureManagement;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();

builder.Services.AddFeatureManagement();

var app = builder.Build();

Next we need to create a FeatureManagement section in the appsettings.json with feature with a boolean value like this.

“FeatureManagement”: {
“WelcomeMessage”: false
}

Now we are ready with feature toggle, let us write code to manage it from the controller. In the controller, ASP.NET Core runtime will inject an instance of the IFeatureManager. And in this interface we can check whether a feature is enabled or not using the IsEnabledAsync method. So for our feature we can do it like this.

public async Task<IActionResult> IndexAsync()
{
if(await _featureManager.IsEnabledAsync(“WelcomeMessage”))
{
ViewData[“WelcomeMessage”] = “Welcome to the Feature Demo app.”;
}
return View();
}

And in the View we can write the following code.

@if (ViewData[“WelcomeMessage”] != null)
{
<div class=“alert alert-primary” role=“alert”>
@ViewData[“WelcomeMessage”]
</div>
}

Run the application, the alert will not be displayed. You can change the WelcomeMessage to true and refresh the page – it will display the bootstrap alert.

This way you can start introducing Feature Flags or Feature Toggles in ASP.NET Core MVC app. As you may already noticed the Feature Management library built on top of the configuration system of .NET Core. So it will support any configuration sources as Feature flags source. Microsoft Azure provides Azure App Configuration service which helps to implement feature flags for cloud native apps.

Happy Programming 🙂

Flatlogic Admin Templates banner

Building a gRPC Server in .NET

Introduction

In this article, we will look at how to build a simple web service with gRPC in .NET. We will keep our changes to minimal and leverage the same Protocol Buffer IDL we used in my previous post. We will also go through some common problems that you might face when building a gRPC server in .NET.

Motivation

For this article also we will be using the Online Bookshop example and leveraging the same Protobufs as we saw before. For those who aren’t familiar with or missed this series, you can find them from here.

Introduction to gRPC
Building a gRPC server with Go

Building a gRPC server with .NET (You are here)
Building a gRPC client with Go
Building a gRPC client with .NET

We will be covering steps 1 and 2 in the following diagram.


Plan

So this is what we are trying to achieve.

Generate the .proto IDL stubs.
Write the business logic for our service methods.
Spin up a gRPC server on a given port.

In a nutshell, we will be covering the following items on our initial diagram.

?  As always, all the code samples documentation can be found at: https://github.com/sahansera/dotnet-grpc

Prerequisites

.NET 6 SDK
Visual Studio Code or IDE of your choice
gRPC compiler

Please note that I’m using some of the commands that are macOS specific. Please follow this link to set it up if you are on a different OS.

To install Protobuf compiler:

brew install protobuf

Project Structure

We can use the the .NET’s tooling to generate a sample gRPC project. Run the following command in at the root of your workspace.

dotnet new grpc -o BookshopServer

Once you run the above command, you will see the following structure.


We also need to configure the SSL trust:

dotnet dev-certs https –trust

As you might have guessed, this is like a default template and it already has a lot of things wired up for us like the Protos folder.

Generating the server stubs

Usually, we would have to invoke the protocol buffer compiler to generate the code for the target language (as we saw in my previous article). However, for .NET they have streamlined the code generation process. They use the Grpc.Tools NuGet package with MSBuild to provide automatic code generation, which is pretty neat! ?

If you open up the Bookshop.csproj file you will find the following lines:


<ItemGroup>
<Protobuf Include=Protosgreet.proto GrpcServices=Server />
</ItemGroup>

We are going to replace greet.proto with our Bookshop.proto file.


We will also update our csproj file like so:

<ItemGroup>
<Protobuf Include=../proto/bookshop.proto GrpcServices=Server />
</ItemGroup>

Implementing the Server

The implementation part is easy! Let’s clean up the GreeterService that comes default and add a new file called InventoryService.cs

rm BookshopServer/Services/GreeterService.cs
code BookshopServer/Services/InventoryService.cs

This is what our service is going to look like.

InventoryService.cs


Let’s go through the code step by step.

Inventory.InventoryBase is an abstract class that got auto-generated (in your obj/debug folder) from our protobuf file.

GetBookList method’s stub is already generated for us in the InventoryBase class and that’s why we are overriding it. Again, this is the RPC call we defined in our protobuf definition. This method takes in a GetBookListRequest which defines what the request looks like and a ServerCallContext param which contains the headers, auth context etc.
Rest of the code is pretty easy – we prepare the response and return it back to the caller/client. It’s worth noting that we never defined the GetBookListRequest GetBookListResponse types ourselves, manually. The gRPC tooling for .NET has already created these for us under the Bookshop namespace.

Make sure to update the Program.cs to reflect the new service as well.

// …
app.MapGrpcService<InventoryService>();
// …

And then we can run the server with the following command.

dotnet run –project BookshopServer/BookshopServer.csproj


We are almost there! Remember we can’t access the service yet through the browser since browsers don’t understand binary protocols. In the next step, we will to test our service ?

Common Errors

A common error you’d find on macOS systems with .NET is HTTP/2 and TLS issue shown below.


gRPC template uses TLS by default and Kestrel doesn’t support HTTP/2 with TLS on macOS systems. We need to turn off TLS (ouch!) in order for our demo to work.

? Please don’t do this in production! This is intended for local development purposes only.

On local development

// Turn off TLS
builder.WebHost.ConfigureKestrel(options =>
{
// Setup a HTTP/2 endpoint without TLS.
options.ListenLocalhost(5000, o => o.Protocols =
HttpProtocols.Http2);
});

Testing the service

Usually, when interacting with the HTTP/1.1-like server, we can use cURL to make requests and inspect the responses. However, with gRPC, we can’t do that. (you can make requests to HTTP/2 services, but those won’t be readable). We will be using gRPCurl for that.

Once you have it up and running, you can now interact with the server we just built.

grpcurl -plaintext localhost:8080 Inventory/GetBookList

? Note: gRPC defaults to TLS for transport. However, to keep things simple, I will be using the `-plaintext` flag with `grpcurl` so that we can see a human-readable response.

How do we figure out the endpoints of the service? There are two ways to do this. One is by providing a path to the proto files, while the other option enables reflection through the code.

Using proto files

If you don’t want to enable reflection, we can use the Protobuf files to let gRPCurl know which methods are available. Normally, when a team makes a gRPC service they will make the protobuf files available if you are integrating with them. So, without having to ask them or doing trial-and-error you can use these proto files to introspect what kind of endpoints are available for consumption.

grpcurl -import-path Proto -proto bookshop.proto -plaintext localhost:5000 Inventory/GetBookList


Now, let’s say we didn’t have reflection enabled and try to call a method on the server.

grpcurl -plaintext localhost:5000 Inventory/GetBookList

We can expect that it will error out. Cool!


Enabling reflection

While in the BookshopServer folder run the following command to install the reflection package.

dotnet add package Grpc.AspNetCore.Server.Reflection

Add the following to the Program.cs file. Note that we are using the new Minimal API approach to configure these services

// Register services that enable reflection
builder.Services.AddGrpcReflection();

// Enable reflection in Debug mode.
if (app.Environment.IsDevelopment())
{
app.MapGrpcReflectionService();
}


Conclusion

As we have seen, similar to the Go implementation, we can use the same Protocol buffer files to generate the server implementation in .NET. In my opinion .NET’s new tooling makes it easier to generate the server stubs when a change happens in your Protobufs. However, setting up the local developer environment could be a bit challenging especially for macOS.

Feel free to let me know if you have any questions or feedback. Until next time! ?

References

https://docs.microsoft.com/en-us/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-6.0&tabs=visual-studio-code
https://grpc.io/docs/languages/csharp/quickstart/
https://docs.microsoft.com/en-us/aspnet/core/grpc/troubleshoot?view=aspnetcore-6.0#unable-to-start-aspnet-core-grpc-app-on-macos
https://docs.microsoft.com/en-us/aspnet/core/migration/50-to-60-samples?view=aspnetcore-6.0

Flatlogic Admin Templates banner

Sharing code between ASP.NET and ASP.NET Core

With the release of .NET 6 there are even more benefits to using ASP.NET Core. But migrating existing code to ASP.NET Core often sounds like a big investment. Today we’ll share how you can accelerate the migration to ASP.NET Core. There are minor changes you can make today that can make it easier to migrate to ASP.NET Core tomorrow.

Before we begin let’s talk about a real scenario. Let’s talk about how to modify code in a 10-year-old application so that it can be shared with ASP.NET Core. In the next sections we’ll give samples that migrate the ShoppingCartController.cs from the MVC Music Store app that was used to demo ASP.NET MVC3.

The first step to migrating this web app is to create a new ASP.NET Core Web App (Model-View-Controller) project. This template will add support for Controllers and map the default route for Controllers in the Program.cs file. Once we have the new project setup, we’ll remove the default HomeController and the view files for Home/Index and Home/Privacy so we can share content from the MVC3 Music Store web app without conflict.

You can share Controllers

The first thing you can share between the two projects is Controllers. Many teams want the new website to work the same as the current one. And when we say “the same” we mean “the same”. If you fix a bug in one project, then you need that same fix to show up in both sites. One of the easiest ways to assure this behavior is to share the same file in both projects. Luckily ASP.NET Core uses the new SDK style project files. That means it’s easy to open the csproj file and add some changes because the files are very readable.

To start sharing a Controller class you’ll need to create an <ItemGroup> and add a reference to the existing class. Here’s a sample that shows how to share the ShoppingCartController.cs by updating the csproj file of the ASP.NET Core project.

<ItemGroup>
<Compile Include=”..MvcMusicStoreControllersShoppingCartController.cs” LinkBase=”Controllers” />
</ItemGroup>

Okay, now the file is included in the project but you may have guessed that the ASP.NET Core project doesn’t compile anymore. In ASP.NET Core the Controller class doesn’t use System.Web.Mvc it uses Microsoft.AspNetCore.Mvc.

Here’s a sample that shows how the ShoppingCartController.cs can use both namespaces to fix that compiler error.

#if NET
using Microsoft.AspNetCore.Mvc;
#else
using System.Web.Mvc;
#endif

There are other places in the ShoppingCartController that would need to be updated but the approach is the same. Using C# preprocessor directives we can make the class flexible enough to compile for both projects.

For scenarios with large sections of code that work differently for ASP.NET Core you may want to create implementation specific files. A good approach is to create a partial class and extract those code blocks to new method(s) that are different between the two web app targets and use the csproj to control which files are included when building the project.

You can share Models

Now that we can share Controllers we’ll want to share the Models they return. In many scenarios this will just start working when we include them by adding another <ItemGroup> to the csproj file. But if your models also reference System.Web then we can use the same approach we just used for Controllers. Start by updating the namespaces so that the same class file can exist in both projects. Keep using the C# precompiler directives to add ASP.NET Core support.

Here’s a sample that shows how to modify the [Bind] attribute.

#if !NET
[Bind(Exclude=”OrderId”)]
#endif
public partial class Order
{
[ScaffoldColumn(false)]
#if NET
[BindNever]
#endif
publicintOrderId{ get; set; }

You can share Views

We can even share views. Using the same approach again we can edit the csproj file to share files like the _Layout.cshtml. And, inside the view you can keep using C# precompiler directives to make the file flexible enough to be used by both projects.

Here’s what it looks like to have a master page with mixed support for Child Actions from ASP.NET and View Components from ASP.NET Core so that we can render the part of the page that knows how many items are in the shopping cart.

@{
#if NET
<text>@awaitComponent.InvokeAsync(“CartSummary”)</text>
#else
@Html.RenderAction(“CartSummary”, “ShoppingCart”);
#endif
}

Wrapping up

The ability to share code also includes static content like CSS, JavaScript and images. Step-by-step you can build flexibility into your web app today to make your migration to ASP.NET Core easier.

If you would like more detailed guidance to migrate the entire ShoppingCartController.cs you can follow a full walkthrough with samples at MvcMusicStoreMigration. The walkthrough will also demonstrate how you can run both ASP.NET and ASP.NET Core from the same IIS Application Pool to incrementally migrate your web app one controller at a time.

For those planning to start work on their ASP.NET Core migration we’ll share a few more tips.

Upgrade your NuGet packages so you can use netstandard.
Change your class libraries to netstandard so you can share code between ASP.NET and ASP.NET Core.
Find references to System.Web in your class libraries build interfaces replace them. Use dependency injection so you can easily switch between ASP.NET and ASP.NET Core features.

You can also find more guidance from our docs at Migrate from ASP.NET to ASP.NET Core.

The post Sharing code between ASP.NET and ASP.NET Core appeared first on .NET Blog.

Flatlogic Admin Templates banner

.NET ? GitHub Actions

Hi friends, I put together two posts where I’m going to teach you the basics of the GitHub Actions platform. In this first post, you’ll learn how GitHub Actions can improve your .NET development experience and team productivity. I’ll show you how to use them to automate common .NET app dev scenarios with workflow composition. In the next post, I’ll show you how to create a custom GitHub Action written in .NET.

An introduction to GitHub Actions

Developers that use GitHub for managing their git repositories have a powerful continuous integration (CI) and continuous delivery (CD) feature with the help of GitHub Actions. A common developer scenario is when developers propose changes to the default branch (typically main) of a GitHub repository. These changes, while often scrutinized by reviewers, can have automated checks to ensure that the code compiles and tests pass.

GitHub Actions allow you to build, test, and deploy your code right from your source code repository on https://github.com. GitHub Actions are consumed by GitHub workflows. A GitHub workflow is a YAML (either *.yml or *.yaml) file within your GitHub repository. These workflow files reside in the .github/workflows/ directory from the root of the repository. A workflow references one or more GitHub Action(s) together as a series of instructions, where each instruction executes a specific task.

The GitHub Action terminology

To avoid mistakenly using some of these terms inaccurately, let’s define them:

GitHub Actions: GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform that allows you to automate your build, test, and deployment pipeline.

workflow: A workflow is a configurable automated process that will run one or more jobs.

event: An event is a specific activity in a repository that triggers a workflow run.

job: A job is a set of steps in a workflow that execute on the same runner.

action: An action is a custom application for the GitHub Actions platform that performs a complex but frequently repeated task.

runner: A runner is a server that runs your workflows when they’re triggered.

For more information, see GitHub Docs: Understanding GitHub Actions

Inside the GitHub workflow file

A workflow file defines a sequence of jobs and their corresponding steps to follow. Each workflow has a name and a set of triggers, or events to act on. You have to specify at least one trigger for your workflow to run unless it’s a reusable workflow. A common .NET GitHub workflow would be to build and test your C# code when changes are either pushed or when there’s a pull request targeting the default branch. Consider the following workflow file:

name: build and test
on:
push:
pull_request:
branches: [ main ]
paths-ignore:
– ‘README.md’
env:
DOTNET_VERSION: ‘6.0.x’
jobs:
build-and-test:
name: build-and-test-${{matrix.os}}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
– uses: actions/[email protected]
– name: Setup .NET
uses: actions/[email protected]
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
– name: Install dependencies
run: dotnet restore
– name: Build
run: dotnet build –configuration Release –no-restore
– name: Test
run: dotnet test –no-restore –verbosity normal

I’m not going to assume that you have a deep understanding of this workflow, and while it’s less than thirty lines — there is still a lot to unpack. I put together a sequence diagram (powered by Mermaid), that shows how a developer might visualize this workflow.

Here’s the same workflow file, but this time it is expanded with inline comments to add context (if you’re already familiar with the workflow syntax, feel free to skip past this):

# The name of the workflow.
# This is the name that’s displayed for status
# badges (commonly embedded in README.md files).
name: build and test

# Trigger this workflow on a push, or pull request to
# the main branch, when either C# or project files changed
on:
push:
pull_request:
branches: [ main ]
paths-ignore:
– ‘README.md’

# Create an environment variable named DOTNET_VERSION
# and set it as “6.0.x”
env:
DOTNET_VERSION: ‘6.0.x’ # The .NET SDK version to use

# Defines a single job named “build-and-test”
jobs:
build-and-test:

# When the workflow runs, this is the name that is logged
# This job will run three times, once for each “os” defined
name: build-and-test-${{matrix.os}}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]

# Each job run contains these five steps
steps:

# 1) Check out the source code so that the workflow can access it.
– uses: actions/[email protected]

# 2) Set up the .NET CLI environment for the workflow to use.
# The .NET version is specified by the environment variable.
– name: Setup .NET
uses: actions/[email protected]
with:
dotnet-version: ${{ env.DOTNET_VERSION }}

# 3) Restore the dependencies and tools of a project or solution.
– name: Install dependencies
run: dotnet restore

# 4) Build a project or solution and all of its dependencies.
– name: Build
run: dotnet build –configuration Release –no-restore

# 5) Test a project or solution.
– name: Test
run: dotnet test –no-restore –verbosity normal

The preceding workflow file contains many comments to help detail each area of the workflow. You might have noticed that the steps define various usages of GitHub Actions or simple run commands. The relationship between a GitHub Action and a consuming GitHub workflow is that workflows consume actions. A GitHub Action is only as powerful as the consuming workflow. Workflows can define anything from simple tasks to elaborate compositions and everything in between. For more information on creating GitHub workflows for .NET apps, see the following .NET docs resources:

Create a build validation workflow
Create a test validation workflow
Create a deploy workflow
Create a CodeQL security vulnerability scanning CRON job workflow

I hope that you’re asking yourself, “why is this important?” Sure, we can create GitHub Actions, and we can compose workflows that consume them — but why is that important?! That answer is GitHub status checks .

GitHub status checks

One of the primary benefits of using workflows is to define conditional status checks that can deterministically fail a build. A workflow can be configured as a status check for a pull request (PR), and if the workflow fails, for example the source code in the pull request doesn’t compile — the PR can be blocked from being merged. Consider the following screen capture, which shows that two checks have failed, thus blocking the PR from being merged.

As the developer who is responsible for reviewing a PR, you’d immediately see that the pull request has failing status checks. You’d work with the developer who proposed the PR to get all of the status checks to pass. The following is a screen capture showing a “green build”, a build that has all of its status checks as passing.

For more information, see GitHub Docs: GitHub status checks.

GitHub Actions that .NET developers should know

As a .NET developer, you’re likely familiar with the .NET CLI. The .NET CLI is included with the .NET SDK. If you don’t already have the .NET SDK, you can download the .NET 6 SDK.

Using the previous workflow file as a point of reference, there are five steps — each step includes either the run or uses syntax:

Action or command
Description

uses: actions/[email protected]
This action checks-out your repository under $GITHUB_WORKSPACE, so your workflow can access it. For more information, see actions/checkout

uses: actions/[email protected]
This action sets up a .NET CLI environment for use in actions. For more information, see actions/setup-dotnet

run: dotnet restore
Restores the dependencies and tools of a project or solution. For more information, see dotnet restore

run: dotnet build
Builds the project or solution. For more information, see dotnet build

run: dotnet test
Runs the tests for the project or solution. For more information, see dotnet test

Some steps rely on GitHub Actions and reference them with the uses syntax, while others run commands. For more information on the differences, see Workflow syntax for GitHub Actions: uses and run.

.NET applications rely on NuGet packages. You can optimize your workflows by caching various dependencies that change infrequently, such as NuGet packages. As an example, you can use the actions/cache to cache NuGet packages:

steps:
– uses: actions/[email protected]
– name: Setup dotnet
uses: actions/[email protected]
with:
dotnet-version: ‘6.0.x’
– uses: actions/[email protected]
with:
path: ~/.nuget/packages
# Look to see if there is a cache hit for the corresponding requirements file
key: ${{ runner.os }}-nuget-${{ hashFiles(‘**/packages.lock.json’) }}
restore-keys: |
${{ runner.os }}-nuget
– name: Install dependencies
run: dotnet add package Newtonsoft.Json –version 12.0.1

For more information, see GitHub Docs: Building and testing .NET – Caching dependencies.

In addition to using the standard GitHub Actions or invoking .NET CLI commands using the run syntax, you might be interested in learning about some additional GitHub Actions.

Additional GitHub Actions

Several .NET GitHub Actions are hosted on the dotnet GitHub organization:

.NET GitHub Action
Description

dotnet/versionsweeper
This action sweeps .NET repos for out-of-support target versions of .NET. The .NET docs team uses the .NET version sweeper GitHub Action to automate issue creation. The action runs as a cron job (or on a schedule). When it detects that .NET projects target out-of-support versions, it creates issues to report its findings. The output is configurable and helpful for tracking .NET version support concerns.

dotnet/code-analysis
This action runs the code analysis rules that are included in the .NET SDK as part of continuous integration (CI). The action runs both code-quality (CAXXXX) rules and code-style (IDEXXXX) rules.

.NET developer community spotlight

The .NET developer community is building GitHub Actions that might be useful in your organizations. As an example, check out the zyborg/dotnet-tests-report which is a GitHub Action to run .NET tests and generate reports and badges. If you use this GitHub Action, be sure to give their repo a star .

There are many .NET GitHub Actions that can be consumed from workflows, see the GitHub Marketplace: .NET.

A word on .NET workloads

.NET runs anywhere, and you can use it to build anything. There are optional workloads that may need to be installed when building from a GitHub workflow. There are many workloads available, see the output of the dotnet workload search command as an example:

dotnet workload search

Workload ID Description
—————————————————————————————–
android .NET SDK Workload for building Android applications.
android-aot .NET SDK Workload for building Android applications with AOT support.
ios .NET SDK Workload for building iOS applications.
maccatalyst .NET SDK Workload for building macOS applications with MacCatalyst.
macos .NET SDK Workload for building macOS applications.
maui .NET MAUI SDK for all platforms
maui-android .NET MAUI SDK for Android
maui-desktop .NET MAUI SDK for Desktop
maui-ios .NET MAUI SDK for iOS
maui-maccatalyst .NET MAUI SDK for Mac Catalyst
maui-mobile .NET MAUI SDK for Mobile
maui-windows .NET MAUI SDK for Windows
tvos .NET SDK Workload for building tvOS applications.
wasm-tools .NET WebAssembly build tools

If you’re writing a workflow for Blazor WebAssembly app, or .NET MAUI as an example — you’ll likely run the dotnet workload install command as one of your steps. For example, an individual run step to install the WebAssembly build tools would look like:

run: dotnet workload install wasm-tools

Summary

In this post, I explained the key differences between GitHub Actions and GitHub workflows. I explained and scrutinized each line in an example workflow file. I then showed you how a developer might visualize the execution of a GitHub workflow as a sequence diagram. I shared a few additional resources you may not have known about. For more information, see .NET Docs: GitHub Actions and .NET.

In the next post, I’ll show how to create GitHub Actions using .NET. I’ll walk you through upgrading an existing .NET GitHub Action that is used to automatically maintain a _CODEMETRICS.md file within the root of the repository. The code metrics analyze the C# source code of the target repository to determine things such as cyclomatic complexity and the maintainability index. In addition to these metrics, we’ll add the ability to generate Mermaid class diagrams, which is now natively supported by GitHub flavored markdown.

The post .NET ? GitHub Actions appeared first on .NET Blog.

Flatlogic Admin Templates banner

Implementing an API Gateway in ASP.NET Core with Ocelot

This post is about what is an API Gateway and how to build an API Gateway in ASP.NET Core with Ocelot. An API gateway is service that sits between an endpoint and backend APIs, transmitting client requests to an appropriate service of an application. It’s an architectural pattern, which was initially created to support microservices. In this post I am building API Gateway using Ocelot. Ocelot is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system.

Let’s start the implementation.

First we will create two web api applications – both these services returns some hard coded string values. Here is the first web api – CustomersController – which returns list of customers.

using Microsoft.AspNetCore.Mvc;

namespace ServiceA.Controllers;

[ApiController]
[Route(“[controller]”)]
public class CustomersController : ControllerBase
{
private readonly ILogger<CustomersController> _logger;

public CustomersController(ILogger<CustomersController> logger)
{
_logger = logger;
}

[HttpGet(Name = “GetCustomers”)]
public IActionResult Get()
{
return Ok(new[] { “Customer1”, “Customer2”,“Customer3” });
}
}

And here is the second web api – ProductsController.

using Microsoft.AspNetCore.Mvc;

namespace ServiceB.Controllers;

[ApiController]
[Route(“[controller]”)]
public class ProductsController : ControllerBase
{
private readonly ILogger<ProductsController> _logger;

public ProductsController(ILogger<ProductsController> logger)
{
_logger = logger;
}

[HttpGet(Name = “GetProducts”)]
public IActionResult Get()
{
return Ok(new[] { “Product1”, “Product2”,
“Product3”, “Product4”, “Product5” });
}
}

Next we will create the API Gateway. To do this create an ASP.NET Core empty web application using the command – dotnet new web -o ApiGateway. Once we create the gateway application, we need to add the reference of Ocelot nuget package – we can do this using dotnet add package Ocelot. Now we can modify the Program.cs file like this.

using Ocelot.DependencyInjection;
using Ocelot.Middleware;

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(“configuration.json”, false, true).AddEnvironmentVariables();

builder.Services.AddOcelot(builder.Configuration);
var app = builder.Build();

app.UseOcelot();
app.Run();

Next you need to configure your API routes using configuration.json. Here is the basic configuration which help to send requests from one endpoint to the web api endpoints.

{
Routes: [
{
DownstreamPathTemplate: /customers,
DownstreamScheme: https,
DownstreamHostAndPorts: [
{
Host: localhost,
Port: 7155
}
],
UpstreamPathTemplate: /api/customers,
UpstreamHttpMethod: [ Get ]
},
{
DownstreamPathTemplate: /products,
DownstreamScheme: https,
DownstreamHostAndPorts: [
{
Host: localhost,
Port: 7295
}
],
UpstreamPathTemplate: /api/products,
UpstreamHttpMethod: [ Get ]
}
],
GlobalConfiguration: {
BaseUrl: https://localhost:7043
}
}

Now run all the three applications and browse the endpoint – https://localhost:7043/api/products – which invokes the ProductsController class GET action method. And if we browse the endpoint – https://localhost:7043/api/customers – which invokes the CustomersController GET action method. In the configuration the UpstreamPathTemplate will be the API Gateway endpoint and API Gateway will transfers the request to the DownstreamPathTemplate endpoint.

Due to some strange reason it was not working properly for me. Today I configured it again and it started working. It is an introductory post. I will blog about some common use cases where API Gateway help and how to deploy it in Azure and all in the future.

Happy Programming 🙂

Flatlogic Admin Templates banner

ASP.NET Core updates in .NET 7 Preview 1

.NET 7 Preview 1 is now available!. This is the first preview of the next major version of .NET, which will include the next wave of innovations for web development with ASP.NET Core.

In .NET 7 we plan to make broad investments across ASP.NET Core. Below are some of the areas we plan to focus on:

Performance: .NET 6 contained many performance improvements for ASP.NET Core, and we’ll do work to make ASP.NET Core even faster and more efficient in .NET 7.

HTTP/3: HTTP/3 support shipped as a preview feature in .NET 6. For .NET 7, we want to finish it and make it a supported feature that’s enabled by default. In future previews, you can expect to see advanced TLS features and more performance improvements in our HTTP/3 support.

Minimal APIs: Add support for endpoint filters and route grouping as core primitives for minimal APIs. Also simplify authentication and authorization configurations for APIs in general.

gRPC: We’re investing in gRPC JSON transcoding. This feature allows gRPC services to be called like RESTful HTTP APIs with JSON requests and responses.

SignalR: Add support for strongly-typed clients and returning results from client invocations.

Razor: We’ll make various improvements to the Razor compiler to improve performance, resiliency, and to facilitate improved tooling.

Blazor: After finishing Blazor Hybrid support for .NET MAUI, WPF, and Windows Forms, we’ll make broad improvements to Blazor including:

New .NET WebAssembly capabilities: mixed-mode AOT, multithreading, web crypto.
Enhanced Hot Reload support.
Data binding improvements.
More flexible prerendering.
More control over the lifecycle of Blazor Server circuits.
Improved support for micro frontends.

MVC: Improvements to endpoint routing, link generation, and parameter binding.

Orleans: The ASP.NET Core and Orleans teams are investigating ways to further align and integrate the Orleans distributed programming model with ASP.NET Core. Orleans 4 will ship alongside .NET 7 and focuses on simplicity, maintainability, and performance, including human readable stream identities and a new optimized, version-tolerant serializer.

For more details on the specific ASP.NET Core work planned for .NET 7 see the full ASP.NET Core roadmap for .NET 7 on GitHub.

.NET 7 Preview 1 is the first of many .NET 7 preview releases in preparation for the .NET 7 release in November 2022.

Here’s a summary of what’s new in this preview release:

Minimal API improvements:

IFormFile and IFormFileCollection Support
Bind the request body as a Stream or PipeReader

JSON options configuration

SignalR client source generator
Support for nullable models in MVC views and Razor Pages
Use JSON property names in validation errors
Improved console output for dotnet watch

Configure dotnet watch to always restart for rude edits
Use dependency injection in a ValidationAttribute

Faster header parsing and writing
gRPC JSON transcoding

Get started

To get started with ASP.NET Core in .NET 7 Preview 1, install the .NET 7 SDK.

If you’re on Windows using Visual Studio, we recommend installing the latest Visual Studio 2022 preview. Visual Studio for Mac support for .NET 7 previews isn’t available yet but is coming soon.

To install the latest .NET WebAssembly build tools, run the following command from an elevated command prompt:

dotnet workload install wasm-tools

Upgrade an existing project

To upgrade an existing ASP.NET Core app from .NET 6 to .NET 7 Preview 1:

Update the target framework for your app to net7.0.
Update all Microsoft.AspNetCore.* package references to 7.0.0-preview.1.*.
Update all Microsoft.Extensions.* package references to 7.0.0-preview.1.*.

See also the full list of breaking changes in ASP.NET Core for .NET 7.

Minimal API improvements

IFormFile and IFormFileCollection Support

You can now handle file uploads in minimal APIs using IFormFile and IFormFileCollection:

app.MapPost(“/upload”, async(IFormFile file) =>
{
using var stream = System.IO.File.OpenWrite(“upload.txt”);
await file.CopyToAsync(stream);
});
app.MapPost(“/upload”, async (IFormFileCollection myFiles) => { … });

Using this feature with authentication requires anti-forgery support, which isn’t yet implemented. Anti-forgery support for minimal APIs is on our roadmap for .NET 7. Binding to IFormFile or IFormFileCollection when the request contains an Authorization header with a bearer token is currently disabled. This limitation will be addressed as soon as we complete the work on anti-forgery support.

Thanks to @martincostello for contributing this feature.

Bind the request body as a Stream or PipeReader

You can now bind the request body as a Stream or PipeReader to efficiently support scenarios where the user has to ingest data and either store it to a blob storage or enqueue the data to a queue provider (Azure Queue, etc.) for later processing by a worker or cloud function. The following example shows how to use the new binding:

app.MapPost(“v1/feeds”, async (QueueClient queueClient, Stream body, CancellationToken cancellationToken) =>
{
await queueClient.CreateIfNotExistsAsync(cancellationToken: cancellationToken);
await queueClient.SendMessageAsync(await BinaryData.FromStreamAsync(body), cancellationToken: cancellationToken);
});

When using the Stream or PipeReader there are a few things to take into consideration:

When ingesting data, the Stream will be the same object as HttpRequest.Body.
The request body isn’t buffered by default. After the body is read, it’s not rewindable (you can’t read the stream multiple times).
The Stream/PipeReader are not usable outside of the minimal action handler as the underlying buffers will be disposed and/or reused.

JSON options configuration

We’re introducing a new and cleaner API, ConfigureRouteHandlerJsonOptions, to configure JSON options for minimal API endpoints. This new API avoids confusion with Microsoft.AspNetCore.Mvc.JsonOptions.

var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureRouteHandlerJsonOptions(options =>
{
//Ignore Cycles
options.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});

SignalR client source generator

We’ve added a new client source generator for SignalR thanks to a contribution by @mehmetakbulut.

The SignalR client source generator generates strongly-typed sending and receiving code based on interfaces that you define. You can reuse the same interfaces from strongly-typed SignalR hubs on the client in place of the loosely-typed .On(“methodName”, …) methods. Similarly, your hub can implement an interface for its methods and the client can use that same interface to call the hub methods.

To use the SignalR client source generator:

Add a reference to the Microsoft.AspNetCore.SignalR.Client.SourceGenerator package.
Add a HubServerProxyAttribute and HubClientProxyAttribute class to your project (this part of the design will likely change in future previews):

[AttributeUsage(AttributeTargets.Method)]
internal class HubServerProxyAttribute : Attribute
{
}

[AttributeUsage(AttributeTargets.Method)]
internal class HubClientProxyAttribute : Attribute
{
}

Add a static partial class to your project and write static partial methods with the [HubClientProxy] and [HubServerProxy] attributes

internal static partial class MyCustomExtensions
{
[HubClientProxy]
public static partial IDisposable ClientRegistration<T>(this HubConnection connection, T provider);

[HubServerProxy]
public static partial T ServerProxy<T>(this HubConnection connection);
}

Use the partial methods from your code!

public interface IServerHub
{
Task SendMessage(string message);
Task<int> Echo(int i);
}

public interface IClient
{
Task ReceiveMessage(string message);
}

public class Client : IClient
{
// Equivalent to HubConnection.On(“ReceiveMessage”, (message) => {});
Task ReceiveMessage(string message)
{
return Task.CompletedTask;
}
}

HubConnection connection = new HubConnectionBuilder().WithUrl(“…”).Build();
var stronglyTypedConnection = connection.ServerProxy<IServerHub>();
var registrations = connection.ClientRegistration<IClient>(new Client());

await stronglyTypedConnection.SendMessage(“Hello world”);
var echo = await stronglyTypedConnection.Echo(10);

Support for nullable models in MVC views and Razor Pages

We enabled defining a nullable page or view model to improve the experience when using null state checking with ASP.NET Core apps:

@model Product?

Use JSON property names in validation errors

When model validation produces a ModelErrorDictionary it will by default use the property name as the error key (“MyClass.PropertyName”). Model property names are generally an implementation detail, which can make them difficult to handle from single-page apps. You can now configure validation to use the corresponding JSON property names instead with the new SystemTextJsonValidationMetadataProvider (or NewtonsoftJsonValidationMetadataProvider when using Json.NET).

services.AddControllers(options =>
{
options.ModelMetadataDetailsProviders.Add(new SystemTextJsonValidationMetadataProvider())
});

Improved console output for dotnet watch

We cleaned up the console output from dotnet watch to better align with the log out of ASP.NET Core and to stand out with emojis.

Here’s an example of what the new output looks like:

C:BlazorApp> dotnet watch
dotnet watch ? Hot reload enabled. For a list of supported edits, see https://aka.ms/dotnet/hot-reload.
? Press “Ctrl + R” to restart.
dotnet watch ? Building…
Determining projects to restore…
All projects are up-to-date for restore.
You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
BlazorApp -> C:UsersdarothDesktopBlazorAppbinDebugnet7.0BlazorApp.dll
dotnet watch ? Started
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:7148
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5041
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:UsersdarothDesktopBlazorApp
dotnet watch ⌚ File changed: .PagesIndex.razor.
dotnet watch ? Hot reload of changes succeeded.
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down…
dotnet watch ? Shutdown requested. Press Ctrl+C again to force exit.

Configure dotnet watch to always restart for rude edits

Configure dotnet watch to always restart without a prompt for rude edits (edits that can’t be hot reloaded) by setting the DOTNET_WATCH_RESTART_ON_RUDE_EDIT environment variable to true.

Inject services into custom validation attributes in Blazor

You can now inject services into custom validation attributes in Blazor. Blazor will setup the ValidationContext so it can be used as a service provider.

public class SaladChefValidatorAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var saladChef = validationContext.GetRequiredService<SaladChef>();
if (saladChef.ThingsYouCanPutInASalad.Contains(value.ToString()))
{
return ValidationResult.Success;
}
return new ValidationResult(“You should not put that in a salad!”);
}
}

// Simple class configured as a service for dependency injection
public class SaladChef
{
public string[] ThingsYouCanPutInASalad = { “Strawberries”, “Pineapple”, “Honeydew”, “Watermelon”, “Grapes” };
}

Thank you @MariovanZeist for this contribution!

Faster header parsing and writing

We made several improvements to the performance of header parsing and writing for HTTP/2 and HTTP/3. See the following pull requests for details:

HTTP/2: Improve incoming header performance
HTTP/3: Optimize validating and setting incoming headers
HTTP headers enumerator move directly to next

gRPC JSON transcoding

gRPC JSON transcoding allows gRPC services to be used like a RESTful HTTP APIs. Once configured, gRPC JSON transcoding allows you to call gRPC methods with familiar HTTP concepts:

HTTP verbs
URL parameter binding
JSON requests/responses

Of course gRPC can continue to be used as well. RESTful APIs for your gRPC services. No duplication!

ASP.NET Core has experimental support for this feature using a library called gRPC HTTP API. For .NET 7 we plan to make this functionality a supported part of ASP.NET Core. This functionality isn’t included with .NET 7 yet, but you can try out the existing experimental packages. For more information, see the gRPC HTTP API getting started documentation.

Give feedback

We hope you enjoy this preview release of ASP.NET Core in .NET 7 and that you’re as excited about about our roadmap for .NET 7 as we are! We’re eager to hear about your experiences with this release and your thoughts on the roadmap. Let us know what you think by filing issues on GitHub and commenting on the roadmap issue.

Thanks for trying out ASP.NET Core!

The post ASP.NET Core updates in .NET 7 Preview 1 appeared first on .NET Blog.

Flatlogic Admin Templates banner

Implementing Basic Authentication in ASP.NET Core Minimal API

This post is about how implement basic authentication in ASP.NET Core Minimal API. Few days back I got a question / comment in the blog post about Minimal APIs – about implementing Basic authentication in Minimal APIs. Since the Action Filters support is not available in Minimal API I had to find some alternative approach for the implementation. I already wrote two blog posts Basic authentication middleware for ASP.NET 5 and Basic HTTP authentication in ASP.Net Web API on implementing Basic authentication. In this post I am implementing an AuthenticationHandler and using this for implementing basic authentication. As I already explained enough about the concepts, I am not discussing them again in this post.

Here is the implementation of the BasicAuthenticationHandler which implements the abstract class AuthenticationHandler.

public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public BasicAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock
) : base(options, logger, encoder, clock)
{
}

protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var authHeader = Request.Headers[“Authorization”].ToString();
if (authHeader != null && authHeader.StartsWith(“basic”, StringComparison.OrdinalIgnoreCase))
{
var token = authHeader.Substring(“Basic “.Length).Trim();
System.Console.WriteLine(token);
var credentialstring = Encoding.UTF8.GetString(Convert.FromBase64String(token));
var credentials = credentialstring.Split(‘:’);
if (credentials[0] == “admin” && credentials[1] == “admin”)
{
var claims = new[] { new Claim(“name”, credentials[0]), new Claim(ClaimTypes.Role, “Admin”) };
var identity = new ClaimsIdentity(claims, “Basic”);
var claimsPrincipal = new ClaimsPrincipal(identity);
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(claimsPrincipal, Scheme.Name)));
}

Response.StatusCode = 401;
Response.Headers.Add(“WWW-Authenticate”, “Basic realm=”dotnetthoughts.net””);
return Task.FromResult(AuthenticateResult.Fail(“Invalid Authorization Header”));
}
else
{
Response.StatusCode = 401;
Response.Headers.Add(“WWW-Authenticate”, “Basic realm=”dotnetthoughts.net””);
return Task.FromResult(AuthenticateResult.Fail(“Invalid Authorization Header”));
}
}
}

Next modify the Program.cs like this.

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddAuthentication(“BasicAuthentication”)
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>
(“BasicAuthentication”, null);
builder.Services.AddAuthorization();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthentication();
app.UseAuthorization();

app.UseHttpsRedirection();

Now it is done. You can enable block the anonymous access by adding the authorize attribute to the method like this.

app.MapGet(“/weatherforecast”, [Authorize]() =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateTime.Now.AddDays(index),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
}).WithName(“GetWeatherForecast”);

Now if you browse the Weather forecast endpoint – https://localhost:5001/weatherforecast, it will prompt for user name and password. Here is the screenshot of the app running on my machine.

Happy Programming 🙂

Flatlogic Admin Templates banner