European ASP.NET 4.5 Hosting BLOG

BLOG about ASP.NET 4, ASP.NET 4.5 Hosting and Its Technology - Dedicated to European Windows Hosting Customer

European ASP.NET Core Hosting :: How to Create a Multi Tenant .NET Core Application

clock January 21, 2020 07:30 by author Scott

Introduction

In the final installment we will extend our multi-tenant solution to allow each tenant to have different ASP.NET Identity providers configured. This will allow each tenant to have different external identiy providers or different clients for the same identity provider.

This is important to allow consent screens on third party services reflect the branding of the particular tenant that the user is signing in to.

Tenant specific authentication features

In this post we will enable three features

Allow different tenants to configure different authentication options

This is useful if different tenants want to allow different ways of signing in, e.g. one tenant might want to allow Facebook and Instagram while another wants local logins only.

Make sure each tenant has their own authentication cookies

This is useful if tenants are sharing a domain, you don’t want the cookies of one tenant signing you in to all tenants.

Note: If you follow this post your tenants will be sharing decryption keys so one tenant’s cookie is a valid ticket on another tenant. Someone could send one tenant’s cookie to a second tenant, you will need to either also include a tenant Id claim or extend this further to have seperate keys to verify the cookie supplied is intended for the tenant.

Allow different tenants to use different configured clients on an external identity provider

With platforms such as Facebook if it’s the first time the user is signing in they will often be asked to grant access to the application for their account, it’s importannt that this grant access screen mentions the tenant that is requesting access. Otherwise the user might get confused and deny access if it’s from a shared app with a generic name.

Implementation

There are 3 main steps to our solution

1. Register the services depending on your tenant: Configure the authentication services depending on which tenant is in scope

2. Support dynamic registration of schemes: Move the scheme provider to be fetched at request time to reflect the current tenant context

3. Add an application builder extension: Make it nice for developers to enable the feature

This implementation is only compatible with ASP.NET Core 2.X. In 1.0 the Authentication was defined in the pipeline so you could use branching to configure services on a per-tenant basis, however this is no longer possible. The approach we’ve taken is to use our tenant specific container to register the different schemes/ options for each tenant.

This post on GitHub outlines all of the changes between 1.0 and 2.0.

1. Register the services depending on your tenant

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    ...

    return services.UseMultiTenantServiceProvider<Tenant>((t, c) =>
    {
        //Create a new service collection and register all services
        ServiceCollection tenantServices = new ServiceCollection();       

        var builder = tenantServices.AddAuthentication(o =>
            {
                //Support tenant specific schemes
                o.DefaultScheme = $"{t.Id}-{IdentityConstants.ApplicationScheme}";
            }).AddCookie($"{t.Id}-{IdentityConstants.ApplicationScheme}", o =>
            {
                ...
            });

        //Optionally add different handlers based on tenant
        if (t.FacebookEnabled)
                builder.AddFacebook(o => {
                    o.ClientId = t.FacebookClientId;
                    o.ClientSecret = t.FacebookSecret; });

        //Add services to the container
        c.Populate(tenantServices);

        ...
    });
}

Seems too easy right? It is. If you run it now your handlers aren’t registered using the default “.UseAuthentication” middleware. The schemes are registered in the middleware constructor before you have a valid tenant context. Since it doesn’t support registering schemes dynamically OOTB we will need to slightly modify it.

2. Update the authentication middleware to support dynamic registration of schemes

Disclaimer ahead! The ASP.NET framework was written by very smart people so I get super nervous about making any changes here - I’ve tried to limit the change but there could be unintended consequences! Proceed with caution

We’re going to take the existing middleware and just move the IAuthenticationSchemeProvider injection point from the constructor to the Invoke method. Since the invoke method is called after we’ve registered our tenant services it will have all the tenant specific authentication services available to it now.

/// <summary>
/// AuthenticationMiddleware.cs from framework with injection point moved
/// </summary>
public class TenantAuthMiddleware
{
    private readonly RequestDelegate _next;

    public TenantAuthMiddleware(RequestDelegate next)
    {
        _next = next ?? throw new ArgumentNullException(nameof(next));
    }  

    public async Task Invoke(HttpContext context, IAuthenticationSchemeProvider Schemes)
    {
        context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
        {
            OriginalPath = context.Request.Path,
            OriginalPathBase = context.Request.PathBase
        });

        // Give any IAuthenticationRequestHandler schemes a chance to handle the request
        var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
        foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
        {
            var handler = await handlers.GetHandlerAsync(context, scheme.Name)
                as IAuthenticationRequestHandler;
            if (handler != null && await handler.HandleRequestAsync())
            {
                return;
            }
        }

        var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
        if (defaultAuthenticate != null)
        {
            var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
            if (result?.Principal != null)
            {
                context.User = result.Principal;
            }
        }       

        await _next(context);
    }
}

3. Add an application builder extension to register our slightly modified authentication middleware

This provides a nice way for the developer to quickly register the tenant aware authentication middleware

/// <summary>
/// Use the Teanant Auth to process the authentication handlers
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IApplicationBuilder UseMultiTenantAuthentication(this IApplicationBuilder builder)
    => builder.UseMiddleware<TenantAuthMiddleware>();

Now we can register the tenant aware authenticaion middleware like this

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    ...

    app.UseMultiTenancy()
        .UseMultiTenantContainer()
        .UseMultiTenantAuthentication();
}

Wrapping up

In this post we looked at how we can upgrade ASP.NET Core to support tenant specifc authentication, this means each tenant can have different external identify providers registered and connect to different clients for each of those providers.



European ASP.NET Core Hosting :: How to Make Simple Chat Using Asp.net Core SignalR

clock December 12, 2019 07:54 by author Scott

This is tutorial about how to make simple chat with Asp.net Core SignalR. It only takes around 5-10 mins for someone who is familiar with Asp.net Core. Here we go

Creating the projects

We will create a new empty ASP.NET Core Web project. You can either do it with Visual Studio or execute dotnet new web in the command line.

I have Angular CLI installed on my machine. If you don’t either install it or create a new empty Angular application. I am using Angular CLI 1.5 and creating a new project with it – Angular 5 application.

I will just execute ng new CodingBlastChat in the command line, inside of solution folder. And now I have basic working Angular application. To start it, I just type in ng serve and I my application is running on localhost port 4200.

Installing dependencies

We need to install both server-side and client-side libraries for ASP.NET Core SignalR.

To install the server-side library we will use NuGet. You can either use Visual Studio or do it via command line. The package name is Microsoft.AspNetCore.SignalR

dotnet add package Microsoft.AspNetCore.SignalR

We will use npm to add client-side library:

npm install @aspnet/signalr-client

If you are using npm 4 or older you will need to add the –save argument, if you want it to be saved inside of your package.json as well. And that’s it for library dependencies. We are all set and we can now use SignalR.

Setting up server-side

We can now add the simple ChatHub class:

public class ChatHub : Hub
{
    public void SendToAll(string name, string message)
    {
        Clients.All.InvokeAsync("sendToAll", name, message);
    }
}

This will call the sendToAll client method for ALL clients.

For SignalR to work we have to add it to DI Container inside of ConfigureServices method in Startup class:

services.AddSignalR();

Also, we have to tell the middleware pipeline that we will be using SignalR. When the request comes to the /chat endpoint we want our ChatHub to take over and handle it.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseSignalR(routes =>
    {
        routes.MapHub<ChatHub>("chat");
    });
}

Enabling CORS

Since we will be serving the Angular application on a separate port, for it to be able to access the SignalR server we will need to enable CORS on the Server.

Add the following inside of ConfigureServices, just before the code that adds SignalR to DI container.

services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
    {
        builder
        .AllowAnyMethod()
        .AllowAnyHeader()
        .WithOrigins("http://localhost:4200");
    }));

We also have to tell the middleware to use this CORS policy. Add the following inside of Configure method, BEFORE SignalR:

app.UseCors("CorsPolicy");

Now your Configure method should look like this:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseCors("CorsPolicy");

    app.UseSignalR(routes =>
    {
        routes.MapHub<ChatHub>("chat");
    });
}

Also, make sure to check your Properties/launchSettings.json file so you can know which port is your app running. You can also configure it to use any port you want. I will set it to 5000.

Client-side

You would ideally want to have a separate service for communicating with ChatHub on the server. Also, you would want to store your endpoints in some kind of Angular service for constants. But for the simplicity sake, we will skip that for now and add it after we make the chat functional.

I will use existing AppComponent that Angular CLI created, and extend it.

I will add properties for nick, message and list of messages. Also, I will add a property for HubConnection.

import { Component } from '@angular/core';
import { HubConnection } from '@aspnet/signalr-client';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  private hubConnection: HubConnection;
  nick = '';
  message = '';
  messages: string[] = [];
}

HubConnection is part of the signalr-client library built by ASP.NET team. And we will use it to establish the connection with the server and also to send messages and listen for messages from the server.

We will establish the connection before any other code runs in our component. Hence, we will use the OnInit event.

import { Component, OnInit } from '@angular/core';
import { HubConnection } from '@aspnet/signalr-client';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  private hubConnection: HubConnection;
  nick = '';
  message = '';
  messages: string[] = [];

  ngOnInit() {
    this.nick = window.prompt('Your name:', 'John');

    this.hubConnection = new HubConnection('http://localhost:5000/chat');

    this.hubConnection
      .start()
      .then(() => console.log('Connection started!'))
      .catch(err => console.log('Error while establishing connection :('));

    }
}

Notice the ngOnInit method. We are asking the user to enter his name and we store that inside of nick property that we created previously.

After that, we create the HubConnection object and try to establish the connection with the server.

Inside of that method, we will also add the listener for sendToAll event from the server:

this.hubConnection.on('sendToAll', (nick: string, receivedMessage: string) => {
  const text = `${nick}: ${receivedMessage}`;
  this.messages.push(text);
});

After the event is received, we get two parameters: nick and the message itself. Now we form the new string from these two parameters and we add it to our messages array on AppComponent.

Inside of AppComponent we also need a method for sending messages from client TO server. We will use it from our view and here is the code:

  public sendMessage(): void {
    this.hubConnection
      .invoke('sendToAll', this.nick, this.message)
      .catch(err => console.error(err));
  }

View

Now we need to set up the view. Since we plan to use the form element, we will import FormsModule in our AppModule. We will change the app.module.ts file.

We can now add the view to app.component.html:

<div id="main-container" style="text-align:center">
  <h1>
    <a href="https://dotnet4europeanhosting.hostforlife.eu/make-chat-using-as-net-core-signalr/" target="_new">
      Make Chat Using ASP.NET Core SignalR
    </a>
  </h1>

  <div class="container">
    <h2>Hello {{nick}}!</h2>
    <form (ngSubmit)="sendMessage()" #chatForm="ngForm">
      <div>
        <label for="message">Message</label>
        <input type="text" id="message" name="message" [(ngModel)]="message" required>
      </div>
      <button type="submit" id="sendmessage" [disabled]="!chatForm.valid">
        Send
      </button>
    </form>
  </div>

  <div class="container" *ngIf="messages.length > 0">
    <div *ngFor="let message of messages">
      <span>{{message}}</span>
    </div>
  </div>

</div>

The view has two main parts.

 

First is a container for sending messages with a form that consists of input and button for sending the message.

The second part is for listing the messages that we store inside of messages property on AppComponent. We push a new message to this array every time we get an event (message) from the ASP.NET Core SignalR server.

That’s all there is to it!



European ASP.NET Core Hosting :: How to Use AutoMapper in Asp.Net Core Application

clock December 10, 2019 11:46 by author Scott

This is only brief tutorial about how to use AutoMapper in Asp Net Core 3.0 application. Automapper is a very popular Object-to-Object mapping library that can be used to map objects.

How to use automapper in Asp.Net Core 3.0 application

Let’s see how to use automapper in Asp Net Core 3.0 application using a very simple example.

1. First step you need to do is please make sure you install Asp.net Core 3.0 application à Create a new project button à then choose “ASP.NET Core Web Application” template à click “Next” button and then enter your Project name. Please see the image below for further information

 

2. Install AutoMapper in Asp.Net Core 3.0 Application 

Now, in this step, we will install AutoMapper in our project. So, go to visual studio and then go to “Tools” from the menu and then select “NuGet Package Manager” and then choose “Manager NuGet Packages for Solution”. Now, go to “Browse” tab and then install this below package as you do see below in the screenshot.

Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection

3. Configure AutoMapper in Asp.net Core 3.0 Application

Go to project folder structure, and then go to Startup class and then add some changes as you do see below in the code file’s Line # 11 and Line # 26.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using AutoMapper;
namespace AspNetCore3AutoMapper
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            services.AddAutoMapper(typeof(Startup));
        }
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });

        }
    }
}

4. Create a Model Class and Data Transfer Object Class

Now, in this step , we will create two classes. One is Model class and the other one is Dto(Data Transfer Object) class. So, go to Models’ folder and then right click on the folder name and then add a new class with the name of “Employee” and then write some properties as you do see below in the code.

public class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Department { get; set; }
    }

Now, again add a new class with the name of “EmployeeDto” as you do see below in the file.

public class EmployeeDto
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Department { get; set; }
    }

5. Add relation of Employee class with EmployeeDto class

Now, in this step, we will see how to add relation between a domain class and a Dto class. So, again add a new class (E.g. AutoMapping.cs) and then write some code as you do see below in the code.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;

namespace AspNetCoreAutoMapper.Models
{
    public class AutoMapping : Profile
    {
        public AutoMapping()
        {
            CreateMap<Employee, EmployeeDto>();
        }
    }
}

Let’s understand the above code.

Line # 9: In this line, we are inheriting AutoMapping class from Profile.

Line # 13: In this line, we are mapping our Employee and EmployeeDto Classes.

6. Map Employee class with EmployeeDto Controller

We will see how to map Employee class with EmployeeDto class within the Home Controller. So, go to HomeController and then go to Index method and then write some code as you do see below in the file.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using AspNetCore3AutoMapper.Models;
using AutoMapper;

namespace AspNetCore3AutoMapper.Controllers
{
    public class HomeController : Controller
    {
        private readonly IMapper _mapper;
        public HomeController(IMapper mapper)
        {
            _mapper = mapper;
        }
        public IActionResult Index()
        {
            var empObj = new Employee();
            empObj.Id = 1;
            empObj.Name = "Scott";
            empObj.Department = "MM";
            var employeeDto = _mapper.Map<EmployeeDto>(empObj);
            return View(employeeDto);
        }
    }
}

Let’s understand the above code.

Line # 15 to 19: In this block of code, we are injecting IMapper interface. This will help us to use our configured mappings.

Line # 22 to 25: In this block of code, we are initializing Employee object.

Line # 26: In this line, we are using IMapper interface that we have injected into our constructor to call the Map method. And we are giving the type that we want to map and the object that we want to map from.

Line # 27: In this line, we are returning the EmployeeDto object to the view.

Now, go to Index view and access the returning values from the Index method as you do see below in the file.

@model EmployeeDto
@{
    ViewData["Title"] = "Home Page";
}
<div>
    <h1>ID: @Model.Id</h1>
    <h1>Name: @Model.Name</h1>
    <h1>Department: @Model.Department</h1>
</div>

Now, run your project by pressing f5 and then you will see the output

Or you can install this package by using this below command in Package Manager Console as you do see below in the screenshot.



European ASP.NET Core Hosting :: Error When Published ASP.NET Core? See Below Tips!

clock November 5, 2019 05:38 by author Scott

At the past few years, we have discussed about common error that you can find when published .NET Core, the most common error is 502.5 – process failure error.

Startup errors with ASP.NET Core don’t provide much information either, at least not in a production environment. Here are 7 tips for understanding and fixing those errors.

1. There are two types of startup errors.

There are unhandled exceptions originating outside of the Startup class, and exceptions from inside of Startup. These two error types can produce different behavior and may require different troubleshooting techniques.

2. ASP.NET Core will handle exceptions from inside the Startup class.

If code in the ConfigureServices or Configure methods throw an exception, the framework will catch the exception and continue execution.

Although the process continues to run after the exception, every incoming request will generate a 500 response with the message “An error occurred while starting the application”.

Two additional pieces of information about this behavior:

- If you want the process to fail in this scenario, call CaptureStartupErrors on the web host builder and pass the value false.

- In a production environment, the “error occurred” message is all the information you’ll see in a web browser. The framework follows the practice of not giving away error details in a response because error details might give an attacker too much information. You can change the environment setting using the environment variable ASPNETCORE_ENVIRONMENT, but see the next two tips first. You don’t have to change the entire environment to see more error details.

3. Set detailedErrors in code to see a stack trace.

The following bit of code allows for detailed error message, even in production, so use with caution.

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
           .CaptureStartupErrors(true) // the default
           .UseSetting("detailedErrors", "true")
           .UseStartup<Startup>();

4. Alternatively, set the ASPNETCORE_DETAILEDERRORS environment variable.

Set the value to true and you’ll also see a stack trace, even in production, so use with caution.

5. Unhandled exceptions outside of the Startup class will kill the process.

Perhaps you have code inside of Program.cs to run schema migrations or perform other initialization tasks which fail, or perhaps the application cannot bind to the desired ports. If you are running behind IIS, this is the scenario where you’ll see a generic 502.5 Process Failure error message.

These types of errors can be a bit more difficult to track down, but the following two tips should help.

6. For IIS, turn on standard output logging in web.config.

If you are carefully logging using other tools, you might be able to capture output there, too, but if all else fails, ASP.NET will write exception information to stdout by default. By turning the log flag to true, and creating the output directory, you’ll have a file with exception information and a stack trace inside to help track down the problem.

The following shows the web.config file created by dotnet publish and is typically the config file in use when hosting .NET Core in IIS. The attribute to change is the stdoutLogEnabled flag.

<system.webServer>
  <handlers>
    <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />

  </handlers>
  <aspNetCore processPath="dotnet" arguments=".\codes.dll"
              stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" />
</system.webServer>

Important: Make sure to create the logging output directory.

Important: Make sure to turn logging off after troubleshooting is complete.

7. Use the dotnet CLI to run the application on your server.

If you have access to the server, it is sometimes easier to go directly to the server and use dotnet to witness the exception in real time. There’s no need to turn on logging or set and unset environment variables.

Summary

Debugging startup errors in ASP.NET Core is a simple case of finding the exception. In many cases, #7 is the simplest approach that doesn’t require code or environment changes. FYI, we also have support latest ASP.NET Core on our hosting environment. Feel free to visit our site at https://www.hostforlife.eu.



European ASP.NET Core 3 Hosting :: Simple Steps to Migrate Your ASP.NET Core 2 to ASP.NET Core 3

clock October 25, 2019 07:18 by author Scott

.NET Core always interesting. In the past few months, we have just launched ASP.NET Core 2.2 on our hosting environment, and now Microsoft has support latest ASP.NET Core 3. This post will be taking the Contacts project used in the ASP.NET Basics series and migrating it from .NET Core 2.2 to .NET Core 3.0.

Installation

If you are a Visual Studio user you can get .NET Core 3.0 by installing at least Visual Studio 16.3. For those not using Visual Studio, you can download and install .NET Core 3.0 SDK from here. As with previous versions, the SDK is available for Windows, Linux, and Mac.

After installation is complete you can runt the following command from a command prompt to see all the versions of the .NET Core SDK you have installed.

dotnet --list-sdks

You should see 3.0.100 listed. If you are like me you might also see a few preview versions of the SDK that can be uninstalled at this point.

Project File Changes

Right-click on the project and select Edit projectName.csproj.

Change the TargetFramework to netcoreapp3.0.

Before:
<TargetFramework>netcoreapp2.2</TargetFramework>

After:
<TargetFramework>netcoreapp3.0</TargetFramework>

The packages section has a lot of changes. Microsoft.AspNetCore.App is now gone and part of .NET Core without needing a specific reference. The other thing to note is that Entity Framework Core is no longer “in the box” so you will see a lot of references add to make Entity Framework Core usable.

Before:
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.2.0" PrivateAssets="All" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="4.0.1" />

After:
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.0.0" PrivateAssets="All" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0-rc4" />

The last thing to note is that Swashbuckle doesn’t have a final version ready for .NET Core 3 so you will have to make sure you are using version 5 rc2 at a minimum.

The following is my full project file for reference.

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
    <UserSecretsId>aspnet-Contacts-cd2c7b27-e79c-43c7-b3ef-1ecb04374b70</UserSecretsId>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.0.0" />
    <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="3.0.0" />
    <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.0.0" />
    <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.0.0" />
    <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.0.0" PrivateAssets="All" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0-rc4" />
  </ItemGroup>
</Project>

Program Changes

In Program.cs some changes to the way the host is constructed. The over version may or may not have worked, but I created a new app and pulled this out of it just to make sure I’m using the current set up.

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
namespace Contacts
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }
        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                                          {
                                              webBuilder.UseStartup<Startup>();
                                          });
    }
}

Startup Changes

In Startup.cs we have quite a few changes to make. As long as you haven’t do any customization in the constructor you can replace it with the following.

public Startup(IConfiguration configuration)
{
        Configuration = configuration;
}

Next, they type on the configuration property changed from IConfigurationRoot to IConfiguration.

Before:
public IConfigurationRoot Configuration { get; }

After:
public IConfiguration Configuration { get; }

Moving on to the ConfigureServices function has a couple of changes to make. The first is a result of updating to the newer version of the Swagger package where the Info class has been replaced with OpenApiInfo.

Before:
services.AddSwaggerGen(c =>
{
        c.SwaggerDoc("v1", new Info { Title = "Contacts API", Version = "v1"});
});

After:
services.AddSwaggerGen(c =>
{
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "Contacts API", Version = "v1" })
});

Next, we are going to move from using UserMvc to the new AddControllersWithViews which is one of the new more targeted ways to add just the bits of the framework you need.

Before:
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

After:
services.AddControllersWithViews();

Now in the Configure function, the function signature needs to be updated and the logging factory bits removed. If you do need to configure logging that should be handled as part of the HostBuilder.

Before:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
      loggerFactory.AddDebug();

After:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{

For the next set of changes, I’m just going to show the result and not the before. The UseCors may or may not apply but the addition of UserRouting and the replacement of UseMvc with UserEndpoint will if you want to use the new endpoint routing features.

app.UseStaticFiles();
app.UseRouting();
app.UseCors(builder =>
            {
                builder.AllowAnyHeader();
                builder.AllowAnyMethod();
                builder.AllowAnyOrigin();
            }
           );
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
                 {
                     endpoints.MapControllerRoute(
                                                  name: "default",
                                                  pattern: "{controller=Home}/{action=Index}/{id?}");
                     endpoints.MapRazorPages();
                 });

Other Miscellaneous Changes

The only other change I had was the removal of @using Microsoft.AspNetCore.Http.Authentication in a few cshtml files related to login.

 



European ASP.NET Core 3 Hosting :: How to Enable GRPC Compression in ASP.NET Core 3

clock October 22, 2019 09:41 by author Scott

How Do I Enable Response Compression with GRPC?

There are two main approaches that I’ve found so far to enable the compression of gRPC responses. You can either configure this at the server level so that all gRPC services apply compression to responses, or at a per-service level.

Server Level Options

services.AddGrpc(o =>
{
    o.ResponseCompressionLevel = CompressionLevel.Optimal;
    o.ResponseCompressionAlgorithm = "gzip";
});

When registering the gRPC services into the dependency injection container with the AddGrpc method inside ConfigureServices, it’s possible to set properties on the GrpcServiceOptions. At this level, the options affect all gRPC services which the server implements.

Using the overload of the AddGrpc extension method, we can supply an Action<GrpcServiceOptions>. In the above snippet we’ve set the compression algorithm to “gzip”. We can also optionally control the CompressionLevel which trades the time needed to compress the data against the final size that is achieved by compression. If not specified the current implementation defaults to using CompressionLevel.Fastest. In the preceding snippet, we’ve chosen to allow more time for the compression to reduce the bytes to the smallest possible size.

Service Level Options

services.AddGrpc()
    .AddServiceOptions<WeatherService>(o =>
        {
            o.ResponseCompressionLevel = CompressionLevel.Optimal;
            o.ResponseCompressionAlgorithm = "gzip";
        });

After calling AddGrpc, an IGrpcServerBuilder is returned. We can call an extension method on that builder called AddServiceOptions to provide per service options. This method is generic and accepts the type of the gRPC service that the options should apply.

In the preceding example, we have decided to provide options specifically for calls that are handled by the WeatherService implementation. The same options are available at this level as we discussed for the server level configuration. In this scenario, if we mapped other gRPC services within this server, they would not receive the compression options.

Making Requests from A GRPC Client

Now that response compression is enabled, we need to ensure our requests state that our client accepts compressed content. In fact, this is enabled by default when using a GrpcChannel created using the ForAddress method so we have nothing to do in our client code.

var channel = GrpcChannel.ForAddress("https://localhost:5005");

Channels created in this way already send a “grpc-accept-encoding” header which includes the gzip compression type. The server reads this header and determines that the client allows gzipped responses to be returned.

One way to visualise the effect of compression is to enable trace level logging for our application while in development. We can achieve this by modifying the appsettings.Development.json file as follows:

{
  "Logging": {
    "LogLevel": {
        "Default": "Debug",
        "System": "Information",
        "Grpc": "Trace",
        "Microsoft": "Trace"
    }
  }
}

When running our server, we now get much more verbose console logging.

info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
      Executing endpoint 'gRPC - /WeatherForecast.WeatherForecasts/GetWeather'
dbug: Grpc.AspNetCore.Server.ServerCallHandler[1]
      Reading message.
dbug: Microsoft.AspNetCore.Server.Kestrel[25]
      Connection id "0HLQB6EMBPUIA", Request id "0HLQB6EMBPUIA:00000001": started reading request body.
dbug: Microsoft.AspNetCore.Server.Kestrel[26]
      Connection id "0HLQB6EMBPUIA", Request id "0HLQB6EMBPUIA:00000001": done reading request body.
trce: Grpc.AspNetCore.Server.ServerCallHandler[3]
      Deserializing 0 byte message to 'Google.Protobuf.WellKnownTypes.Empty'.
trce: Grpc.AspNetCore.Server.ServerCallHandler[4]
      Received message.
dbug: Grpc.AspNetCore.Server.ServerCallHandler[6]
      Sending message.
trce: Grpc.AspNetCore.Server.ServerCallHandler[9]
      Serialized 'WeatherForecast.WeatherReply' to 2851 byte message.
trce: Microsoft.AspNetCore.Server.Kestrel[37]
      Connection id "0HLQB6EMBPUIA" sending HEADERS frame for stream ID 1 with length 104 and flags END_HEADERS
trce: Grpc.AspNetCore.Server.ServerCallHandler[10]
      Compressing message with 'gzip' encoding.
trce: Grpc.AspNetCore.Server.ServerCallHandler[7]
      Message sent.
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
      Executed endpoint 'gRPC - /WeatherForecast.WeatherForecasts/GetWeather'
trce: Microsoft.AspNetCore.Server.Kestrel[37]
      Connection id "0HLQB6EMBPUIA" sending DATA frame for stream ID 1 with length 978 and flags NONE
trce: Microsoft.AspNetCore.Server.Kestrel[37]
      Connection id "0HLQB6EMBPUIA" sending HEADERS frame for stream ID 1 with length 15 and flags END_STREAM, END_HEADERS
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 2158.9035ms 200 application/grpc

On line 16 of this log, we can see that the WeatherReply, essentially an array of 100 WeatherData items in this sample, has been serialised to protocol buffers and has a size of 2851 bytes.

Later, in line 20, we can see that the message has been compressed with gzip encoding and on line 26, we can see the size of the data frame for this call which is 978 bytes. The data, in this case, has compressed quite well (66% reduction) because the repeated WeatherData items contain text and many of the values repeat within the message.

In this example, gzip compression has a good effect on the over the wire size of the data.

Disable Response Compression within A Service Method Implementation

It’s possible to control the response compression on a per-method basis. At this time, I’ve only found a way to do this on an opt-out approach. When compression is enabled for a service or server, we can opt-out of compression within the service method implementation.

Let’s look at the server log output when calling a service method which streams WeatherData messages from the server.

info: WeatherForecast.Grpc.Server.Services.WeatherService[0]
      Sending WeatherData response
dbug: Grpc.AspNetCore.Server.ServerCallHandler[6]
      Sending message.
trce: Grpc.AspNetCore.Server.ServerCallHandler[9]
      Serialized 'WeatherForecast.WeatherData' to 30 byte message.
trce: Grpc.AspNetCore.Server.ServerCallHandler[10]
      Compressing message with 'gzip' encoding.
trce: Microsoft.AspNetCore.Server.Kestrel[37]
      Connection id "0HLQBMRRH10JQ" sending DATA frame for stream ID 1 with length 50 and flags NONE
trce: Grpc.AspNetCore.Server.ServerCallHandler[7]
      Message sent.

On line 6, we can see that an individual WeatherData message is 30 bytes in size. On line 8, this gets compressed, and on line 10, we can see that the data length is now 50 bytes, larger than the original message. In this case, there is no gain from gzip compression, and we see an increase in the overall message size sent over the wire.

We can avoid compression for a particular message by setting the WriteOptions for the call within the service method.

public override async Task GetWeatherStream(Empty _, IServerStreamWriter<WeatherData> responseStream, ServerCallContext context)
{
    context.WriteOptions = new WriteOptions(WriteFlags.NoCompress);

    // implementation of the method which writes to the stream
}

At the top of our service method, we can set the WriteOptions on the ServerCallContext. We pass in a new WriteOptions instance which the WriteFlags value set to NoCompress. These write options are used for the next write.

With streaming responses, it’s also possible to set this value on the IServerStreamWriter.

public override async Task GetWeatherStream(Empty _, IServerStreamWriter<WeatherData> responseStream, ServerCallContext context)
{   
    responseStream.WriteOptions = new WriteOptions(WriteFlags.NoCompress);

    // implementation of the method which writes to the stream
}

When this option is applied, the logs now show that compression for calls to this service method is not applied.

info: WeatherForecast.Grpc.Server.Services.WeatherService[0]
      Sending WeatherData response
dbug: Grpc.AspNetCore.Server.ServerCallHandler[6]
      Sending message.
trce: Grpc.AspNetCore.Server.ServerCallHandler[9]
      Serialized 'WeatherForecast.WeatherData' to 30 byte message.
trce: Microsoft.AspNetCore.Server.Kestrel[37]
      Connection id "0HLQBMTL1HLM8" sending DATA frame for stream ID 1 with length 35 and flags NONE
trce: Grpc.AspNetCore.Server.ServerCallHandler[7]
      Message sent.

Now the 30 byte message has a length of 35 bytes in the DATA frame. There is a small overhead which accounts for the extra 5 bytes which we don’t need to concern ourselves with here.

Disable Response Compression from A GRPC Client

By default, the gRPC channel includes options that control which encodings it accepts. It is possible to configure these when creating the channel if you wish to disable compression of responses from your client. Generally, I would avoid this and let the server decide what to do, since it knows best what can and cannot be compressed. That said, you may sometimes need to control this from the client.

The only way I’ve found to do this in my exploration of the API to date is to configure the channel by passing in a GrpcChannelOptions instance. One of the properties on this options class is for the CompressionProviders, an IList<ICompressionProvider>. By default, when this is null, the client implementation adds the Gzip compression provider for you automatically. This means that the server can choose to gzip the response message(s) as we have already seen.

private static async Task Main()
{
    using var channel = GrpcChannel.ForAddress("https://localhost:5005", new GrpcChannelOptions { CompressionProviders = new List<ICompressionProvider>() });

    var client = new WeatherForecastsClient(channel);

    var reply = await client.GetWeatherAsync(new Empty());

    foreach (var forecast in reply.WeatherData)
    {
        Console.WriteLine($"{forecast.DateTimeStamp.ToDateTime():s} | {forecast.Summary} | {forecast.TemperatureC} C");
    }

    Console.WriteLine("Press a key to exit");
    Console.ReadKey();
}

In this sample client code, we establish the GrpcChannel and pass in a new instance of GrpcChannelOptions. We set the CompressionProviders property to an empty list. Since we now specify no providers in our channel, when the calls are created and sent via this channel, they won’t include any compression encodings in the “grpc-accept-encoding” header. The server acknowledges this and not apply gzip compression to the response.

Summary

In this post, we’ve explored the possibility of compressing response messages from a gRPC server. We’ve identified that in some cases, but crucially not all, this may result in smaller payloads. We’ve seen that by default clients calls include the gzip “grpc-accept-encoding” value in the headers. If the server is configured to apply compression, it only does so if a supported encoding type is matched from the request header.

We can configure the GrpcChannelOptions when creating a channel for the client, to disable the inclusion of the gzip compression encoding. On the server, we can configure the whole server, or a specific service to enable compression for responses. We can override and disable that on a per service-method level as well.



European ASP.NET Core 3 Hosting :: Custom JSONConverter ASP.NET Core 3

clock October 17, 2019 07:17 by author Scott

With the introduction of ASP.NET Core 3.0 the default JSON serializer has been changed from Newtonsoft.Json to System.Text.Json. For projects and libraries switching to the new JSON serializer this change means more performance and the opportunity to rewrite our JsonConverters.

Serialization of concrete classes

Let's start with a simple one that can (de)serialize a concrete class Category. In our example we (de)serialize the property Name only.

public class Category
{
   public string Name { get; }

   public Category(string name)
   {
      Name = name;
   }
}

To implement a custom JSON converter we have to derive from the generic class JsonConverter<T> and to implement 2 methods: Read and Write.

public class CategoryJsonConverter : JsonConverter<Category>
{
   public override Category Read(ref Utf8JsonReader reader,
                                 Type typeToConvert,
                                 JsonSerializerOptions options)
   {
      var name = reader.GetString();

      return new Category(name);
   }

   public override void Write(Utf8JsonWriter writer,
                              Category value,
                              JsonSerializerOptions options)
   {
      writer.WriteStringValue(value.Name);
   }
}

The method Read is using the Utf8JsonReader to fetch a string, i.e. the name, and the method Write is writing a string using an instance of Utf8JsonWriter.

In both cases (i.e. during serialization and deserialization) the converter is not being called if the value is null so I skipped the null checks. The .NET team doesn't do null checks either, see JsonKeyValuePairConverter<TKey, TValue>.

Let's test the new JSON converter. For that we create an instance of JsonSerializerOptions and add our CategoryJsonConverter to the Converters collection. Next, we use the static class JsonSerializer to serialize and to deserialize an instance of Category.

Category category = new Category("my category");

var serializerOptions = new JsonSerializerOptions
{
    Converters = { new CategoryJsonConverter() }
};

// json = "my category"
var json = JsonSerializer.Serialize(category, serializerOptions);

// deserializedCategory.Name = "my category"
var deserializedCategory = JsonSerializer.Deserialize<Category>(json, serializerOptions);

Serialization of generic classes

The next example is slightly more complex. The property we are serializing is a generic type argument, i.e. we can't use methods like reader.GetString() or writer.WriteStringValue(name) because we don't know the type at compile time.

In this example I've changed the class Category to a generic type and renamed the property Name to Key:

public class Category<T>
{
   public T Key { get; }

   public Category(T key)
   {
      Key = key;
   }
}

For serialization of the generic property Key we need to fetch a JsonSerializer<T> using the instance of JsonSerializerOptions.

public class CategoryJsonConverter<T> : JsonConverter<Category<T>>
{
   public override Category<T> Read(ref Utf8JsonReader reader,
                                    Type typeToConvert,
                                    JsonSerializerOptions options)
   {
      var converter = GetKeyConverter(options);
      var key = converter.Read(ref reader, typeToConvert, options);

      return new Category<T>(key);
   }

   public override void Write(Utf8JsonWriter writer,
                              Category<T> value,
                              JsonSerializerOptions options)
   {
      var converter = GetKeyConverter(options);
      converter.Write(writer, value.Key, options);
   }

   private static JsonConverter<T> GetKeyConverter(JsonSerializerOptions options)
   {
      var converter = options.GetConverter(typeof(T)) as JsonConverter<T>;

      if (converter is null)
         throw new JsonException("...");

      return converter;
   }
}

The behavior of the generic JSON converter is the same as before especially if the Key is of type string.

Deciding the concrete JSON converter at runtime

Having several categories with different key types, say, string and int, we need to register them all with the JsonSerializerOptions.

var serializerOptions = new JsonSerializerOptions
                        {
                           Converters =
                           {
                              new CategoryJsonConverter<string>(),
                              new CategoryJsonConverter<int>()
                           }
                        };

If the number of required CategoryJsonConverters grows to big or the concrete types of the Key are not known at compile time then this approach is not an option. To make this decision at runtime we need to implement a JsonConverterFactory. The factory has 2 method: CanConvert(type) that returns true if the factory is responsible for the serialization of the provided type; and CreateConverter(type, options) that should return an instance of type JsonConverter.

public class CategoryJsonConverterFactory : JsonConverterFactory
{
   public override bool CanConvert(Type typeToConvert)
   {
      if (!typeToConvert.IsGenericType)
         return false;

      var type = typeToConvert;

      if (!type.IsGenericTypeDefinition)
         type = type.GetGenericTypeDefinition();

      return type == typeof(Category<>);
   }

   public override JsonConverter CreateConverter(Type typeToConvert,
                                                 JsonSerializerOptions options)
   {
      var keyType = typeToConvert.GenericTypeArguments[0];
      var converterType = typeof(CategoryJsonConverter<>).MakeGenericType(keyType);

      return (JsonConverter)Activator.CreateInstance(converterType);
   }
}

Now, we can remove all registrations of the CategoryJsonConverter<T> from the options and add the newly implemented factory.

Category<int> category = new Category<int>(42);

var serializerOptions = new JsonSerializerOptions
{
    Converters = { new CategoryJsonConverterFactory() }
};

// json = 42
var json = JsonSerializer.Serialize(category, serializerOptions);

// deserialized.Key = 42
var deserialized = JsonSerializer.Deserialize<Category<int>>(json, serializerOptions);

In the end the implementation of a custom converter for System.Text.Json is very similar to the one for Newtonsoft.Json. The biggest difference here is the non-existence of a non-generic JsonConverter but for that we've got the JsonConverterFactory.

Actually, there is a non-generic JsonConverter which is the base class of the JsonConverter<T> and the JsonConverterFactory but we cannot (and should not) use this class directly because its constructor is internal.



European ASP.NET Core Hosting :: Knowing about WinForms and .NET Core 3.0

clock October 11, 2019 09:29 by author Scott

Desktop support for .NET Core 3.0 has been in preview for some time, it is slated to be released later this year. As per indicators by Microsoft, .NET Core will receive more updates compared to full .NET Framework, keeping this in mind, it makes sense to start thinking about migrating existing applications and creating new ones on .NET Core if the application still has some years to go.

Desktop support in .NET Core has not been completely migrated yet; however, it is possible to create and migrate WinForms applications using the preview bits. In this article, we'll consider the creation and migration of a WinForms application in .NET Core 3.0 that uses ComponentOne controls.

Creating a New .NET Core 3.0 Application

To create a new .NET Core 3.0 application, it's recommended to download VS 2019 and the nightly build of the .NET Core 3.0 SDK. Make sure that you’ve installed the .NET Framework 4.7.2 and .NET Core 2.1 development tools in the Visual Studio Installer. This software will be necessary whether you plan on creating a new project or migrating an existing one to .NET Core 3.0.

The simplest way to work with .NET Core 3.0 (for now) is to use dotnet.exe command line utility. It can be used to create a new project, add/restore dependencies, build, etc.

.NET Core 3 - Using x64 and x86 Versions

Visual Studio uses the appropriate version depending on your .NET Core 3 project’s target platform – x86 or x64. It uses last installed version for both platforms. So, if you intend to build your project for both platforms, you must download and install both platform SDK versions with the same version.

These are the typical paths for .NET Core 3 command line utility:

  • c:\Program Files\dotnet\dotnet.exe - for x64 version of .Net Core 3
  • c:\Program Files (x86)\dotnet\dotnet.exe- for x86 version of .Net Core 3

Let’s use x64 version of dotnet.exe to simplify it for further description.

Create a New Visual Studio Project

First, you’ll need to open the Visual Studio Developer Command Prompt to create a project through the command line. Currently, the tooling for Visual Studio is very limited in its support of .NET Core 3.0, so it’s easiest to create a project through the command line interface.

First, we’ll create the project:

“c:\Program Files\dotnet\dotnet.exe” **new** winforms -o TestAppWinFormsCore

Once this is finished, we’ll navigate into the project directory:

cd TestAppWinFormsCore

Finally, we can run the project to make sure that it works:

“c:\Program Files\dotnet\dotnet.exe” run

Now that the project has been created, we can use Visual Studio 2019 to open it and modify its contents. 

In the Visual Studio 2019, open the TestAppWinFormsCore.csproj file we just created.

You should see something like this in Visual Studio:

.NET Core 3.0 uses a new project type and has no design-time support currently, so the process of adding controls will be a little different than what you may be accustomed to. We need to add C1 libraries manually to the project to make use of them, also, we should add the controls manually to Form1.cs because of missing design-time support.

Because of this, let’s use a trick. We'll add the classic .NET Framework WinForms project in order to use its Form designer:

1. Right-click on Solution node and Add – New Project… - Windows Forms Desktop project (with .Net Framework, say version 4.0). The name of new project is by default WindowsFormsApp1.

2. Remove the existing Form1 from WindowsFormsApp1 project

3. Add Form1.cs from TestAppWinFoirmsCore project as link:

After that, the following view of Solution Explorer appears:

Now, we can edit Form1 form in the usual way:

Now, remove unneeded “Hello .NET Core!” label and add some C1 control, for example C1DockingTab:

Launch WindowsFormsApp1 application and observe C1DockingTab on Form1. But this will be a .Net Framework 4 application.

Adding C1DockingTab caused adding C1.Win.C1Command.4 reference to WindowsFormsApp1 project. Therefore, the same reference should be added to TestAppWinFormsCore project also. For this let’s take a look in the properties of the C1.Win.C1Command.4 reference (right-click – Properties…) and find the path to the assembly. It looks like C:\Program Files (x86)\ComponentOne\WinForms Edition\bin\v4.0\C1.Win.C1Command.4.dll. Add this reference to the TestAppWinFormsCore project (right-click on Dependencies – Add reference… - Browse… - choose C:\Program Files (x86)\ComponentOne\WinForms Edition\bin\v4.0\C1.Win.C1Command.4.dll).

You should be able to run the code at this point, though you’ll notice a ComponentOne nag screen since the TestAppWinFormsCore project doesn’t contain any licensing information.

We can correct this by adding a licenses.licx file to the project. To do so, right-click on TestAppWinFormsCore project and select Add -> Existing Items. And browse the licenses.licx file from WindowsFormsApp1/Properties folder.

Build and rerun the app and you should no longer see a nag screen.

And now this is a genuine .NET Core 3.0 WinForms app. You can see this in the Modules window. All system references are from C:\Program Files\dotnet... folder.

Migrating an Existing Project to .NET Core 3.0

It is possible to migrate an existing project to .NET Core 3.0, though the process may require a few more steps than you would expect. Also, before trying to migrate a project, it’s worth running Microsoft’s portability analyzer tool on the project you want to convert.

The tool can give you an idea (ahead of time) how compatible your project will be with .NET Core 3.0, and it points out potential problems you may run into (included unsupported APIs).

If you’ve made up your mind to try to migrate a project, it’s easiest to begin the process by creating a project through dotnet.exe as described above.Let’s look at the WeatherChart sample migration. This sample demonstrates the C1FlexChart control possibilities.

First, we’ll create a new DotNetCore3FlexChart project from the command line:

“c:\Program Files\dotnet\dotnet.exe” **new** winforms -o DotNetCore3FlexChart

Once this is finished, we’ll navigate into the project directory and open DotNetCore3FlexChart.csproj from Visual Studio 2019.

Next, add the WeatherChart project to the DotNetCore3FlexChart solution. WeatherChart project is placed in Documents\ComponentOne Samples\WinForms\C1FlexChart\CS\WeatherChart\WeatherChart\WeatherChart.csproj by default.

Here, we'll use the WeatherChart project in our .NET Core 3.0 project. Therefore, the WeatherChart project reference should be added to DotNetCore3FlexChart dependencies (right-click on Dependencies – Add Reference… - Projects - WeatherChart)

After that, open Project.cs from DotNetCore3FlexChart project and change the following line:

Application.Run(new Form1()); 

by 

Application.Run(new WeatherChart.Form1()); 

It means that the .NET Core 3.0 app (DotNetCore3FlexChart) will run WeatherChart.Form1 from WeatherChart assembly instead of own Form1\.

That’s all!

If you run the WeatherChart project it will be launched as .NET Framework 4.0 app.

But if you run the DotNetCore3FlexChart project then the WeatherChart assembly will be launched in .NET Core 3.0 environment.

.NET Core 3.0 Preview Caveats

Using the .NET Core 3.0 Preview for any kind of day-to-day work isn’t recommended at this point, as it’s obviously still work in progress. As the steps above illustrate, much of the project creation and migration processes require a lot of manual configuration from the user, and it’s possible to find some bugs and unfinished implementations presently.The lack of designer support also makes working with the preview somewhat difficult, though Microsoft has committed to adding this feature in the coming months.

Overall, .NET Core 3.0 will be an important change for developers, though this preview is merely the first step.



European ASP.NET Core 3 Hosting :: ASP.NET Core 3 Certificate Authentication

clock October 8, 2019 12:04 by author Scott

This article shows how Certificate Authentication can be implemented in ASP.NET Core 3.0. In this example, a shared self signed certificate is used to authenticate one application calling an API on a second ASP.NET Core application.

Setting up the Server

Add the Certificate Authentication using the Microsoft.AspNetCore.Authentication.Certificate NuGet package to the server ASP.NET Core application.

 

This can also be added directly in the csproj file.

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Authentication.Certificate"
      Version="3.0.0-preview6.19307.2" />
  </ItemGroup>

  <ItemGroup>
    <None Update="sts_dev_cert.pfx">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>

</Project>

The authentication can be added in the ConfigureServices method in the Startup class. This example was built using the ASP.NET Core documentation. The AddAuthentication extension method is used to define the default scheme as “Certificate” using the CertificateAuthenticationDefaults.AuthenticationScheme string. The AddCertificate method then adds the configuration for the certificate authentication. At present, all certificates are excepted which is not good and the MyCertificateValidationService class is used to do extra validation of the client certificate. If the validation fails, the request is failed and the request for the resource will be rejected.

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<MyCertificateValidationService>(); 

    services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
        .AddCertificate(options => // code from ASP.NET Core sample
        {
            options.AllowedCertificateTypes = CertificateTypes.All;
            options.Events = new CertificateAuthenticationEvents
            {
                OnCertificateValidated = context =>
                {
                    var validationService =
                        context.HttpContext.RequestServices.GetService<MyCertificateValidationService>(); 
                    if (validationService.ValidateCertificate(context.ClientCertificate))
                    {
                        var claims = new[]
                        {
                            new Claim(ClaimTypes.NameIdentifier, context.ClientCertificate.Subject, ClaimValueTypes.String, context.Options.ClaimsIssuer),
                            new Claim(ClaimTypes.Name, context.ClientCertificate.Subject, ClaimValueTypes.String, context.Options.ClaimsIssuer)
                        }; 

                        context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name));
                        context.Success();
                    }
                    else
                    {
                        context.Fail("invalid cert");
                    } 

                    return Task.CompletedTask;
                }
            };
        }); 

    services.AddAuthorization(); 

    services.AddControllers();
}

The AddCertificateForwarding method is used so that the client header can be specified and how the certificate is to be loaded using the HeaderConverter option. When sending the certificate with the HttpClient using the default settings, the ClientCertificate was always be null. The X-ARR-ClientCert header is used to pass the client certificate, and the cert is passed as a string to work around this.

services.AddCertificateForwarding(options =>
{
    options.CertificateHeader = "X-ARR-ClientCert";
    options.HeaderConverter = (headerValue) =>
    {
        X509Certificate2 clientCertificate = null;
        if(!string.IsNullOrWhiteSpace(headerValue))
        {
            byte[] bytes = StringToByteArray(headerValue);
            clientCertificate = new X509Certificate2(bytes);
        }

        return clientCertificate;
    };
});

The Configure method then adds the middleware. UseCertificateForwarding is added before the UseAuthentication and the UseAuthorization.

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

    app.UseCertificateForwarding();
    app.UseAuthentication();
    app.UseAuthorization(); 

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

The MyCertificateValidationService is used to implement validation logic. Because we are using self signed certificates, we need to ensure that only our certificate can be used. We validate that the thumbprints of the client certificate and also the server one match, otherwise any certificate can be used and will be be enough to authenticate.

using System.IO;
using System.Security.Cryptography.X509Certificates; 

namespace AspNetCoreCertificateAuthApi
{
    public class MyCertificateValidationService
    {
        public bool ValidateCertificate(X509Certificate2 clientCertificate)
        {
            var cert = new X509Certificate2(Path.Combine("sts_dev_cert.pfx"), "1234");
            if (clientCertificate.Thumbprint == cert.Thumbprint)
            {
                return true;
            } 

            return false;
        }
    }
}

The API ValuesController is then secured using the Authorize attribute.

[Route("api/[controller]")]
[ApiController]
[Authorize]
public class ValuesController : ControllerBase


...

The ASP.NET Core server project is deployed in this example as an out of process application using kestrel. To use the service, a certificate is required. This is defined using the ClientCertificateMode.RequireCertificate option.

public static IWebHost BuildWebHost(string[] args)
  => WebHost.CreateDefaultBuilder(args)
  .UseStartup<Startup>()
  .ConfigureKestrel(options =>
  {
    var cert = new X509Certificate2(Path.Combine("sts_dev_cert.pfx"), "1234");
    options.ConfigureHttpsDefaults(o =>
    {
        o.ServerCertificate = cert;
        o.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
    });
  })
  .Build();

Implementing the HttpClient

The client of the API uses a HttpClient which was create using an instance of the IHttpClientFactory. This does not provide a way to define a handler for the HttpClient and so we use a HttpRequestMessage to add the Certificate to the “X-ARR-ClientCert” request header. The cert is added as a string using the GetRawCertDataString method.

private async Task<JArray> GetApiDataAsync()
{
    try
    {
        var cert = new X509Certificate2(Path.Combine(_environment.ContentRootPath, "sts_dev_cert.pfx"), "1234"); 

        var client = _clientFactory.CreateClient(); 

        var request = new HttpRequestMessage()
        {
            RequestUri = new Uri("
https://localhost:44379/api/values"),
            Method = HttpMethod.Get,
        }; 

        request.Headers.Add("X-ARR-ClientCert", cert.GetRawCertDataString());
        var response = await client.SendAsync(request); 

        if (response.IsSuccessStatusCode)
        {
            var responseContent = await response.Content.ReadAsStringAsync();
            var data = JArray.Parse(responseContent); 

            return data;
        } 

        throw new ApplicationException($"Status code: {response.StatusCode}, Error: {response.ReasonPhrase}");
    }
    catch (Exception e)
    {
        throw new ApplicationException($"Exception {e}");
    }
}

If the correct certificate is sent to the server, the data will be returned. If no certificate is sent, or the wrong certificate, then a 403 will be returned. It would be nice if the IHttpClientFactory would have a way of defining a handler for the HttpClient. I also believe a non valid certificates should fail per default and not require extra validation for this. The AddCertificateForwarding should also not be required to use for a default HTTPClient client calling the service.

Certificate Authentication is great, and helps add another security layer which can be used together with other solutions. See the code and ASP.NET Core src code for further documentation and examples. Links underneath. 



European ASP.NET Core Hosting :: File Upload in Angular 7

clock September 20, 2019 10:52 by author Scott

In this article, we will see how to upload a file into the database using the Angular 7 app in an ASP.NET project. Let us create a new project in Visual Studio 2017. We are using ASP.NET Core 2 and Angular 7 for this project.

Prerequisites:

  • Node JS must be installed
  • Angular CLI must be installed
  • Basic knowledge of Angular

Let’s get started.

Create a new project and name it as FileUploadInAngular7

Select a new project with Angular in .NET Core and uncheck the configure HTTPS

Your project will be created. Left click on ClientApp and open it in file explorer

Type cmd as shown in the picture.

Type npm install in the cmd

Now it will take time and all the node_modules will be installed in the project.

Create a Web API controller as UploadController.

Add the following code in it.

using System.IO;
using System.Net.Http.Headers;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
namespace FileUploadInAngular7.Controllers
{
    [Produces("application/json")]
    [Route("api/[controller]")]
    public class UploadController : Controller
    {
        private IHostingEnvironment _hostingEnvironment;
        public UploadController(IHostingEnvironment hostingEnvironment)
        {
            _hostingEnvironment = hostingEnvironment;
        }
        [HttpPost, DisableRequestSizeLimit]
        public ActionResult UploadFile()
        {
            try
            {
                var file = Request.Form.Files[0];
                string folderName = "Upload";
                string webRootPath = _hostingEnvironment.WebRootPath;
                string newPath = Path.Combine(webRootPath, folderName);
                if (!Directory.Exists(newPath))
                {
                    Directory.CreateDirectory(newPath);
                }
                if (file.Length > 0)
                {
                    string fileName = ContentDispositionHeaderValue.Parse(file.ContentDisposition).FileName.Trim('"');
                    string fullPath = Path.Combine(newPath, fileName);
                    using (var stream = new FileStream(fullPath, FileMode.Create))
                    {
                        file.CopyTo(stream);
                    }
                }
                return Json("Upload Successful.");
            }
            catch (System.Exception ex)
            {
                return Json("Upload Failed: " + ex.Message);
            }
        }
    }
}

Navigate to the following path and open both the files.

In home.component.html write the following code.

<h1>File Upload Using Angular 7 and ASP.NET Core Web API</h1>
<input #file type="file" multiple (change)="upload(file.files)" />
<br />
<span style="font-weight:bold;color:green;" *ngIf="progress > 0 && progress < 100">
  {{progress}}%
</span>
<span style="font-weight:bold;color:green;" *ngIf="message">
  {{message}}
</span>

In home.component.ts file write the following code in it.

import { Component, OnInit } from '@angular/core';
import { HttpClient, HttpRequest, HttpEventType, HttpResponse } from '@angular/common/http';
@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
})
export class HomeComponent {
  public progress: number;
  public message: string;
  constructor(private http: HttpClient) { }
  upload(files) {
    if (files.length === 0)
      return;
    const formData = new FormData();
    for (let file of files)
      formData.append(file.name, file);
    const uploadReq = new HttpRequest('POST', `api/upload`, formData, {
      reportProgress: true,
    });
    this.http.request(uploadReq).subscribe(event => {
      if (event.type === HttpEventType.UploadProgress)
        this.progress = Math.round(100 * event.loaded / event.total);
      else if (event.type === HttpEventType.Response)
        this.message = event.body.toString();
    });
  }
}

Run the project and you will see the following output.



About HostForLIFE.eu

HostForLIFE.eu is European Windows Hosting Provider which focuses on Windows Platform only. We deliver on-demand hosting solutions including Shared hosting, Reseller Hosting, Cloud Hosting, Dedicated Servers, and IT as a Service for companies of all sizes.

We have offered the latest Windows 2016 Hosting, ASP.NET Core 2.2.1 Hosting, ASP.NET MVC 6 Hosting and SQL 2017 Hosting.


Tag cloud

Sign in