.NET Core local tools

( Video at https://youtu.be/iHLRBxi4S7c )

.NET Core has the concept of “local tools”  – that means, tools for a solution / project. That is different from “global tools”  by the fact that you can have it registered for the solution in a \.config\dotnet-tools.json file. You can read about those at https://docs.microsoft.com/en-us/dotnet/core/tools/local-tools-how-to-use

But I want to show you my local tools. First , the json:

{

“version”: 1,

“isRoot”: true,

“tools”: {

“dotnet-property”: {

“version”: “1.0.0.11”,

“commands”: [

“dotnet-property”

]

},

“powershell”: {

“version”: “7.0.0”,

“commands”: [

“pwsh”

]

},

“xunit-cli”: {

“version”: “0.1.3”,

“commands”: [

“xunit”

]

},

“coverlet.console”: {

“version”: “1.7.0”,

“commands”: [

“coverlet”

]

},

“dotnet-reportgenerator-globaltool”: {

“version”: “4.5.0”,

“commands”: [

“reportgenerator”

]

},

“dotnet-aop”: {

“version”: “2020.2.17.1904”,

“commands”: [

“dotnet-aop”

]

},

“loxsmoke.mddox”: {

“version”: “0.5.1”,

“commands”: [

“mddox”

]

},

“dotnet-project-licenses”: {

“version”: “1.1.1”,

“commands”: [

“dotnet-project-licenses”

]

},

“dotnetthx”: {

“version”: “0.2.0”,

“commands”: [

“dotnet-thx”

]

},

“dotnet-depends”: {

“version”: “0.4.0”,

“commands”: [

“dotnet-depends”

]

},

“dotnet-outdated”: {

“version”: “2.10.0”,

“commands”: [

“dotnet-outdated”

]

}

}

}

Now, how I use for code coverage:

dotnet tool restore –add-source https://myget.org/F/natemcmaster/api/v3/index.json

pwsh ./setVersion.ps1

dotnet coverlet bin\$(buildConfiguration)\netcoreapp3.1\CLITests.dll –target “dotnet” –targetargs “test –no-build –configuration $(buildConfiguration)” –exclude ‘[*Test*]*’ –format opencover  –output $(Build.ArtifactStagingDirectory)\testResults\coverlet.xml

dotnet reportgenerator “-reports:$(Build.ArtifactStagingDirectory)\testResults\coverlet.xml” “-targetdir:$(Build.ArtifactStagingDirectory)\testResults” “-reporttypes:Cobertura;HtmlSummary;Badges;HtmlInline_AzurePipelines”

In this way I can have set version and running tests and see code coverage in Azure Devops: https://dev.azure.com/ignatandrei0674/WebAPI2CLI/_build?definitionId=7&_a=summary

( also, for code coverage, see video at  https://youtu.be/JvahoA0WWms  )

You can find also a list of tools here: https://github.com/natemcmaster/dotnet-tools

Embed files for a Nuget ASP.NET Core package

For NetCoreBlockly I have the need to embed some files in the NUGET package (https://www.nuget.org/packages/NetCore2Blockly ) to display a GUI.

After unsuccessful trying to deploy, I  have read https://weblog.west-wind.com/posts/2018/Jan/29/Distributing-Content-and-Showing-a-ReadMe-file-in-a-NET-Core-Nuget-Package .

So I must learn from the experts: e.g. , swagger : https://github.com/domaindrivendev/Swashbuckle.AspNetCore it is displaying the HTML UI.  How it does ? By embedding into the .csproj the index.html : https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/src/Swashbuckle.AspNetCore.SwaggerUI/Swashbuckle.AspNetCore.SwaggerUI.csproj

But NetCoreBlockly have more than 1 file – more js, more css … and I want to be done automatically.

So I solved this in 3 steps:

 

  1. I Embed all files from a folder in the nuget csproj

<ItemGroup>
<EmbeddedResource Include=”blocklyFiles\**\*”>
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>

2. In the NuGet I obtain the files as embedded

var manifestEmbeddedProvider =
new ManifestEmbeddedFileProvider(Assembly.GetExecutingAssembly());

3. I generate ASP.NET Core endpoints for each file from the Embed, according to the tree :

private static void mapFile(string dirName, IFileProvider provider, IApplicationBuilder appBuilder)
{
var folder = provider.GetDirectoryContents(dirName);
foreach (var item in folder)
{
    if (item.IsDirectory)
    {
        mapFile(dirName + "/" + item.Name, provider, appBuilder);
        continue;
    }
    string map = (dirName + "/" + item.Name).Substring("blocklyFiles".Length);
    appBuilder.Map(map, app =>
    {
        var f = item;

        app.Run(async cnt =>
        {
            //TODO: find from extension
            //cnt.Response.ContentType = "text/html";
            using var stream = new MemoryStream();
            using var cs = f.CreateReadStream();
            byte[] buffer = new byte[2048]; // read in chunks of 2KB
            int bytesRead;
            while ((bytesRead = cs.Read(buffer, 0, buffer.Length)) > 0)
            {
                stream.Write(buffer, 0, bytesRead);
            }
            byte[] result = stream.ToArray();
            var m = new Memory<byte>(result);
            await cnt.Response.BodyWriter.WriteAsync(m);
        });
    });
}

}        

Optional:

4. Because I need to test , copy the files in the CI from a real ASP.NET Core website to the .csproj with the NUGET package

5. You can replace some tags with your values – or just use a .js file that generates options ( see BlocklyUIOptions  below)

 

If you want to see in Action, browse to https://netcoreblockly.herokuapp.com/blockly.html

If you want to see the code, goto https://github.com/ignatandrei/NETCoreBlockly/blob/master/src/NetCore2Blockly/NetCore2Blockly/ExtensionMethods/CLIExtension.cs and see public static void UseBlocklyUI(this IApplicationBuilder appBuilder, BlocklyUIOptions options =null)

Interceptor in Angular–Error service

Similar video at https://www.youtube.com/watch?v=ajosuyhcKV4

I need to inform the user when the application is loading data with error ( i.e. the backend is giving some error back). And do it seamlessly , every time.

I found the documentation about interceptors at https://angular.io/api/common/http/HttpInterceptor”– and you can find more searching on google /  bing .

Also, I have the need to display this no matter in what page  – so a service will help


import { Injectable } from '@angular/core';
import { Subject, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class ErrorService {

  private err: Subject<string>;

  constructor() {
    this.err = new Subject<string>();
  }

  public NextError(): Observable<string> {

    return this.err.asObservable();

  }

  public setNextError(str: string) {

    this.err.next(str);

  }

}


Now the interceptor

import { Injectable } from '@angular/core';
import {
  HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse
} from '@angular/common/http';

import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { ErrorService } from './error.service';

/** Pass untouched request through to the next request handler. */
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {


  constructor(private err: ErrorService ) {


  }
  intercept(req: HttpRequest<any>, next: HttpHandler):
    Observable<HttpEvent<any>> {

    return next.handle(req).pipe(
        catchError( err => this.handleError(req, err))
    );
  }

  private handleError(req: HttpRequest<any>, error: HttpErrorResponse) {
    if (error.error instanceof ErrorEvent) {
      // A client-side or network error occurred. Handle it accordingly.
      const str = 'An error occurred:' +   error.error.message;
      console.log(str);
      this.err.setNextError(str);

    } else {
      // The backend returned an unsuccessful response code.
      // The response body may contain clues as to what went wrong,
      const str =
        `Backend returned code ${error.status}, ` +
        `body was: ${JSON.stringify(error.error)}`;
      console.log(str);
      this.err.setNextError(str);
    }
    // return an observable with a user-facing error message
    return throwError(
      'Something bad happened; please try again later.');
  }
}


The class itself

@Component({
  selector: 'app-covid-navig',
  templateUrl: './covid-navig.component.html',
  styleUrls: ['./covid-navig.component.css']
})
export class CovidNavigComponent  {
  public isLoading = false;
  constructor(private breakpointObserver: BreakpointObserver
    ,         private err: ErrorService
    ,         private snackBar: MatSnackBar
    ,         private ls: LoaderService) {
    this.err.NextError().pipe(
      tap(it => {
        this.snackBar.open(it, 'ERROR', {
          duration: 5000,
        });
      }),
      shareReplay()
    ).subscribe();
    this.ls.loading$().subscribe(it => this.isLoading = it);

  }

And this is all !( besides registering the interceptors in a barrelInterceptors.ts

import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { ErrorInterceptor } from './ErrorInterceptor';
import { LoaderInterceptor } from './LoaderInterceptor';





/** Http interceptor providers in outside-in order */
export const httpInterceptorProviders = [
  { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
  { provide: HTTP_INTERCEPTORS, useClass: LoaderInterceptor, multi: true },
];

and register to the appmodule.ts


providers: [
    httpInterceptorProviders,
  ] 

Interceptor in Angular–loader and service

Video at https://www.youtube.com/watch?v=ajosuyhcKV4

I need to inform the user when the application is loading data – and when it is not loading anymore. Usually , the data comes from a backend via an HTTP call– and will be some timeout before it becomes available. So – simple – just intercept this and then show some spinning to the user, right ?

I found the documentation about interceptors at https://angular.io/api/common/http/HttpInterceptor – and you can find more searching on google /  bing .

Also, my inspiration was started from here:

https://grensesnittet.computas.com/loading-status-in-angular-done-right/

But , if we intercept, how to distribute ? The services singleton in Angular will provide help

import { Injectable } from '@angular/core';
import { Subject, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class LoaderService {
  constructor() {
    this.s = new Subject<boolean>();

  }

  public s: Subject<boolean>;
  public loading$(): Observable<boolean> {
    return this.s.asObservable();
  } 

  public isLoading(b: boolean) {
    this.s.next(b);
  }

}

So I have created the HttpInterceptor to call the service , like here:

import { Injectable } from '@angular/core';
import {
  HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse
} from '@angular/common/http';

import { Observable, throwError } from 'rxjs';
import { catchError, tap, finalize } from 'rxjs/operators';

import { LoaderService } from './loader.service';

/** Pass untouched request through to the next request handler. */
@Injectable()
export class LoaderInterceptor implements HttpInterceptor {

  private requests: HttpRequest<any>[] = [];
  constructor(private ls: LoaderService ) {


  }
  removeRequest(req: HttpRequest<any>) {
    const i = this.requests.indexOf(req);
    if (i >= 0) {
      this.requests.splice(i, 1);
    }
    this.ls.isLoading(this.requests.length > 0);
  }
  // https://grensesnittet.computas.com/loading-status-in-angular-done-right/
  intercept(req: HttpRequest<any>, next: HttpHandler):
    Observable<HttpEvent<any>> {
    this.requests.push(req);
    this.ls.isLoading(true);
    return next.handle(req).pipe(
        finalize(() => this.removeRequest(req))
    );
  }

}

And now it is simple to display in any page: – in the HTML

<mat-spinner *ngIf="isLoading"></mat-spinner>

and in the .ts pass in the constructor via DI

@Component({
  selector: 'app-covid-navig',
  templateUrl: './covid-navig.component.html',
  styleUrls: ['./covid-navig.component.css']
})
export class CovidNavigComponent  {
  public isLoading = false;
  constructor(private breakpointObserver: BreakpointObserver
    ,         private err: ErrorService
    ,         private snackBar: MatSnackBar
    ,         private ls: LoaderService) {
    this.err.NextError().pipe(
      tap(it => {
        this.snackBar.open(it, 'ERROR', {
          duration: 5000,
        });
      }),
      shareReplay()
    ).subscribe();
    this.ls.loading$().subscribe(it => this.isLoading = it);

  }

Simple , right ?

Andrei Ignat weekly software news(mostly .NET)

* indicates required

Please select all the ways you would like to hear from me:

You can unsubscribe at any time by clicking the link in the footer of our emails. For information about our privacy practices, please visit our website.

We use Mailchimp as our marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp's privacy practices here.