Category: .NET Core

TILT–Analyzing code–part 13

I want a static analysis tool that can be used to check the quality of the code. SonarCloud.io is a great tool for this.

You can find the instructions for the tool here: https://sonarcloud.io/dashboard/index/organization/sonarcloud/project/sonarcloud-project-id

It was easy to set up – and it is free. You can see the results here: https://sonarcloud.io/summary/overall?id=ignatandrei_TILT

This is the result

Two were a false positive

var c = this.User?.Claims.ToArray();
if ((c?.Length ?? 0) == 0)
    return -1;
return c.Length;

and one was a problem ( comparing bool with null).

There was a security hotspot – based on Configuring in EF . No problem here.

Solving most of code smells were ok.

I have added also a badge Maintainability Rating

Tools used

https://sonarcloud.io

GitHub Actions

TILT- Tests- part 12

First, it is a decision between NUnit and XUnit. I took this time NUnit. Also, I take LightBDD to show data.

Let’s say I want to verify the rule that the user cannot make more than 1 TILT per day.

In order to do 1 TILT per day

  1. User must be authenticated and have have an URL registered into the database

  2. Code must verify that is no TILT for today ( i.e UTC Date)
  3. Code must insert the TILT into the database

I can work with a real database, but let’s see how we can Mock it – and run without database

This is the setup

namespace NetTilt.Tests;

[FeatureDescription(@"Test add a new TILT")]
[Label("FakesMocks")]
public partial class TestAdd
{
    ServiceProvider? serviceProvider;
    
    [SetUp]
    public void Setup()
    {

    }
    [Scenario]
    public async Task NewTilt() //scenario name
    {
       await Runner
            .AddSteps(_ => Given_No_TILT_For_URL())
            .AddAsyncSteps(_ => Then_Can_Add_A_New_TILT())
            .RunAsync();
    }
    [Scenario]
    public async Task Existing_TILT_Start_Of_The_Day() 
    {
        var minutes = DateTime.UtcNow.Subtract(DateTime.UtcNow.Date).TotalMinutes;
        minutes--;
        await Runner
              .AddSteps(_ => Given_Today_Is(DateTime.UtcNow))
             .AddSteps(_ => Given_Exists_One_TILT_ForDate(DateTime.UtcNow.AddMinutes(-minutes)))
             .AddAsyncSteps(_ => Then_Can_NOT_Add_A_New_TILT())
             .RunAsync();
    }
    [Scenario]
    [TestCase(2)]
    [TestCase(20)]
    [TestCase(1)]
    public async Task ExistingTILT_DaysAgo(int days) //scenario name
    {
        await Runner
              .AddSteps(_ => Given_Today_Is(DateTime.UtcNow))
             .AddSteps(_ => Given_Exists_One_TILT_ForDate(DateTime.UtcNow.AddDays(-days)))
             .AddAsyncSteps(_ => Then_Can_Add_A_New_TILT())
             .RunAsync();
    }
    [Scenario]
    [TestCase(1,false)]
    [TestCase(2, false)]
    [TestCase(20, false)]
    [TestCase(100, false)]
    [TestCase(200, false)]
    [TestCase(4*60, false)]
    [TestCase(5 * 60, false)]
    [TestCase(6 * 60, false)]
    [TestCase(7 * 60, false)]
    [TestCase(8 * 60, false)]
    [TestCase(9 * 60, false)]
    [TestCase(10 * 60, false)]
    [TestCase(11* 60, false)]
    [TestCase(12 * 60, false)]
    [TestCase(23 * 60, false)]
    [TestCase(23 * 60+58, false)]
    [TestCase(24 * 60 + 1, true)]
    public async Task ExistingTILT_MinutesAgo(int minutes, bool canAdd) //scenario name
    {
        var run =  Runner
              .AddSteps(_ => Given_Today_Is(DateTime.UtcNow))
              .AddSteps(_ => Given_Exists_One_TILT_ForDate(DateTime.UtcNow.Date.AddDays(1).AddMinutes(-minutes)));

        if (canAdd)
            run = run
             .AddAsyncSteps(_ => Then_Can_Add_A_New_TILT());
        else
            run = run
             .AddAsyncSteps(_ => Then_Can_NOT_Add_A_New_TILT());

        await run.RunAsync();
    }
    [Test]
    public async Task TestAddOneTilt()
    {
        var myTilt= serviceProvider.GetRequiredService<IMyTilts>();
        var data= await myTilt.AddTILT(new TILT_Note_Table(), null);
        Assert.IsNotNull(data);
    }
}

And this is the output of the tests in markdown

Results of tests (Passed:65)

TestAdd

Existing TILT Start Of The
Day

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “05/01/2022
00:01:00”]
Passed
3 THEN Can NOT Add A New TILT Passed

ExistingTILT DaysAgo [days:
“2”]

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “04/29/2022
06:52:44”]
Passed
3 THEN Can Add A New TILT Passed

ExistingTILT DaysAgo [days:
“20”]

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “04/11/2022
06:52:44”]
Passed
3 THEN Can Add A New TILT Passed

ExistingTILT DaysAgo [days:
“1”]

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “04/30/2022
06:52:44”]
Passed
3 THEN Can Add A New TILT Passed

ExistingTILT MinutesAgo [minutes:
“1”] [canAdd: “False”]

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “05/01/2022
23:59:00”]
Passed
3 THEN Can NOT Add A New TILT Passed

ExistingTILT MinutesAgo [minutes:
“2”] [canAdd: “False”]

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “05/01/2022
23:58:00”]
Passed
3 THEN Can NOT Add A New TILT Passed

ExistingTILT MinutesAgo [minutes:
“20”] [canAdd: “False”]

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “05/01/2022
23:40:00”]
Passed
3 THEN Can NOT Add A New TILT Passed

ExistingTILT MinutesAgo [minutes:
“100”] [canAdd: “False”]

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “05/01/2022
22:20:00”]
Passed
3 THEN Can NOT Add A New TILT Passed

ExistingTILT MinutesAgo [minutes:
“200”] [canAdd: “False”]

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “05/01/2022
20:40:00”]
Passed
3 THEN Can NOT Add A New TILT Passed

ExistingTILT MinutesAgo [minutes:
“240”] [canAdd: “False”]

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “05/01/2022
20:00:00”]
Passed
3 THEN Can NOT Add A New TILT Passed

ExistingTILT MinutesAgo [minutes:
“300”] [canAdd: “False”]

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “05/01/2022
19:00:00”]
Passed
3 THEN Can NOT Add A New TILT Passed

ExistingTILT MinutesAgo [minutes:
“360”] [canAdd: “False”]

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “05/01/2022
18:00:00”]
Passed
3 THEN Can NOT Add A New TILT Passed

ExistingTILT MinutesAgo [minutes:
“420”] [canAdd: “False”]

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “05/01/2022
17:00:00”]
Passed
3 THEN Can NOT Add A New TILT Passed

ExistingTILT MinutesAgo [minutes:
“480”] [canAdd: “False”]

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “05/01/2022
16:00:00”]
Passed
3 THEN Can NOT Add A New TILT Passed

ExistingTILT MinutesAgo [minutes:
“540”] [canAdd: “False”]

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “05/01/2022
15:00:00”]
Passed
3 THEN Can NOT Add A New TILT Passed

ExistingTILT MinutesAgo [minutes:
“600”] [canAdd: “False”]

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “05/01/2022
14:00:00”]
Passed
3 THEN Can NOT Add A New TILT Passed

ExistingTILT MinutesAgo [minutes:
“660”] [canAdd: “False”]

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “05/01/2022
13:00:00”]
Passed
3 THEN Can NOT Add A New TILT Passed

ExistingTILT MinutesAgo [minutes:
“720”] [canAdd: “False”]

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “05/01/2022
12:00:00”]
Passed
3 THEN Can NOT Add A New TILT Passed

ExistingTILT MinutesAgo [minutes:
“1380”] [canAdd: “False”]

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “05/01/2022
01:00:00”]
Passed
3 THEN Can NOT Add A New TILT Passed

ExistingTILT MinutesAgo [minutes:
“1438”] [canAdd: “False”]

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “05/01/2022
00:02:00”]
Passed
3 THEN Can NOT Add A New TILT Passed

ExistingTILT MinutesAgo [minutes:
“1441”] [canAdd: “True”]

Number Name Status Comments
1 GIVEN Today Is [date: “05/01/2022
06:52:44”]
Passed
2 AND Exists One TILT ForDate [date: “04/30/2022
23:59:00”]
Passed
3 THEN Can Add A New TILT Passed

NewTilt


Number Name Status Comments
1 GIVEN No TILT For URL Passed
2 THEN Can Add A New TILT Passed

Tools used

Nunit

LightBdd

Microsoft.Extensions.DependencyInjection

Microsoft.NET.Test.Sdk Moq

NUnit

Visual Studio

TILT–Telemetry–part 11

I want to see what is happening in my application – i.e. having the Stack Trace for any kind of errors.

I have add with

    [AutoMethods(CustomTemplateFileName = "../AutoMethod.txt", MethodPrefix = "auto", template = TemplateMethod.CustomTemplateFile)]
    [AutoGenerateInterface]
    public partial class AuthUrl : IAuthUrl
    {

        [AOPMarkerMethod]
        private async Task<string?> privateLogin(string url, string secret)
        {
//code

And inside the method

var act=Activity.Current;
using var span = MyActivitySource.StartActivity("{{mi.NewName}}", ActivityKind.Client,act?.Context??default(ActivityContext));
{
try{
                //call original method
            }
            catch(Exception ex){
                span?.RecordException(ex);
                span?.SetTag("exceptionMessage",ex.Message);
                span?.SetStatus(Status.Error);
                throw;
            }
            finally{
                span?.Stop();
                
            }

The final result is ( showing an error into the database)


Tools used

AppInsights

Powershell ( AOPMethods.ps1)

OpenTelemetry.Api

OpenTelemetry.Contrib.Instrumentation.EntityFrameworkCore

OpenTelemetry.Extensions.Hosting

Azure.Monitor.OpenTelemetry.Exporter

OpenTelemetry.Instrumentation.AspNetCore

OpenTelemetry.Instrumentation.Http

OpenTelemetry.Instrumentation.SqlClient

Visual Studio

TILT- BlocklyScenario–part 10

Scenario for adding and retrieving the own TILTs

I needed a way to show to the front-end programmer how he should call the API.

One is to retrieve all the TILTS urls and the other is to add a new TILT, once authenticated

I have used NetCore2Blockly nuget package and configured with demoBlocks ( wwwroot\BlocklyAutomation\assets\showUsage\demoBlocks )

For authentication, this is the link https://tiltwebapp.azurewebsites.net/BlocklyAutomation/automation/loadexample/authenticate

For adding a new TILT , this is how you do it https://tiltwebapp.azurewebsites.net/BlocklyAutomation/automation/loadexample/newPost :

For see all public TILTS, this is how you do it ( https://tiltwebapp.azurewebsites.net/BlocklyAutomation/automation/loadexample/publicTilts )

Tools Used

NetCore2Blockly Visual Studio

TILT–Authentication and Authorization–part 9

This is the difficult part. I decide that , for the moment, I do not need in the application any fancy authorization – just a simple url + secret ( a.k.a password).

I decided also to use JWT – it is a simple way to add authentication + authorization to the application.

Let’s see the login code

            var data = await search.TILT_URLSimpleSearch_URLPart(SearchCriteria.Equal, url).ToArrayAsync(); ;
            if (data == null)
                return null;

            if (data.Length != 1)
                return null;

            var item = data[0];
            if (item.Secret != secret)
                return null;

            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(SecretKey);
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new Claim[]
                    {
                    new Claim(TokenId ,item.ID.ToString() ),
                    new Claim(ClaimTypes.Role, "Editor")
                    }),

                Expires = DateTime.UtcNow.AddMinutes(5), 
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            };

            var token = tokenHandler.CreateToken(tokenDescriptor);
            var ret = tokenHandler.WriteToken(token);
            return ret;

Nothing fancy – just SecretKey must be set in the appsettings.json file – and be enough large – otherwise an error occurs

For adding the TILTs to the own url , a custom authentication should be made. The code is not so simple, so I show here:

builder.Services.AddAuthorization(options=>

    options.AddPolicy("CustomBearer", policy =>
    {
        policy.AuthenticationSchemes.Add("CustomBearer");
        policy.RequireAuthenticatedUser();
    }));
builder.Services.AddAuthentication()
           .AddJwtBearer("CustomBearer",options =>
           {
              
               options.RequireHttpsMetadata = false;
               options.SaveToken = true;
               options.TokenValidationParameters = new TokenValidationParameters
               {
                   ValidateIssuerSigningKey = true,
                   IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(key)),
                   ValidateIssuer = false,
                   ValidateAudience = false
               };
               options.Events = new JwtBearerEvents()
               {
                   OnMessageReceived = ctx =>
                   {
                       if (!(ctx?.Request?.Headers?.ContainsKey("Authorization") ?? true))
                       {
                           ctx.NoResult();
                           return Task.CompletedTask;
                       };
                       var auth = ctx.Request.Headers["Authorization"].ToString();
                       if (string.IsNullOrEmpty(auth))
                       {
                           ctx.NoResult();
                           return Task.CompletedTask;
                       }
                       if (!auth.StartsWith("CustomBearer ", StringComparison.OrdinalIgnoreCase))
                       {
                           ctx.NoResult();
                           return Task.CompletedTask;
                       }

                       ctx.Token = auth.Substring("CustomBearer ".Length).Trim();
                       return Task.CompletedTask;

                   }
               };
           });

and the controller will be

    [Authorize(Policy = "CustomBearer", Roles = "Editor")]
    [HttpPost]
    public async Task<ActionResult<TILT_Note_Table>> AddTILT([FromServices] I_InsertDataApplicationDBContext insert, TILT_Note_Table note)
    {
        //TB: 2022-04-30 to be moved into a class - skinny controllers
        var c = this.User?.Claims.ToArray();
        var idUrl = auth.MainUrlId(c);
        if (idUrl == null)
        {
            return new UnauthorizedResult();
        }
        note.IDURL = idUrl.Value;
        note.ID = 0;
        note.ForDate = DateTime.UtcNow;
        var noteOrig = new TILT_Note();
        noteOrig.CopyFrom(note);
        await insert.InsertTILT_Note(noteOrig);
        note.CopyFrom(noteOrig);
        return note;

    }

The

        //TB: 2022-04-30 to be moved into a class - skinny controllers

is a time bomb comment- it will generate an error when compiling on the date.

Also, I have added CORS

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "AllowAll",
                      policy =>
                      {
                          policy.AllowAnyHeader().AllowAnyMethod().AllowCredentials().SetIsOriginAllowed(it => true);
                      });
});
//code
var app = builder.Build();
//code
app.UseCors("AllowAll");

in order for the front-end ,no matter where it is , to work.

I have put SetIsOriginAllowed(it => true) in the policy – works with credentials.

Tools Used

RSCG_TimeBombComment

QueryGenerator Visual Studio

BoilerplateFree – to generate interface from an existing class

System.IdentityModel.Tokens.Jwt – to generate the token and decrypt the token Visual Studio

TILT–HealthCheck–part 8

Read about https://docs.microsoft.com/en-us/azure/architecture/patterns/health-endpoint-monitoring

In ASP.NET Core it is easy to add health checks to your web application.

I have added for Sqlite in the release and for SqlServer in the Azure.

This is the code

bool IsBuildFromCI = new XAboutMySoftware_78102118871091131225395110108769286().IsInCI;
//more code
builder.Services
     .AddHealthChecksUI(setup =>
     {

         var health = "/healthz";
         if (IsBuildFromCI)
         {
             health = builder.Configuration["MySettings:url"] + health;
         }
         setup.AddHealthCheckEndpoint("me",health );
         setup.SetEvaluationTimeInSeconds (60*60);
         //setup.SetHeaderText
         setup.MaximumHistoryEntriesPerEndpoint(10);
     }
        
    )
     .AddInMemoryStorage()
     ;

//more code
app.MapHealthChecks("healthz", new HealthCheckOptions
{
    Predicate = _ => true,
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
app.MapHealthChecksUI(setup =>
{

});

You can see the result at

https://tiltwebapp.azurewebsites.net/healthz https://tiltwebapp.azurewebsites.net/healthchecks-api https://tiltwebapp.azurewebsites.net/healthchecks-ui

Also, you can monitor at Monitoring – health check. Also, it would help to see the logs in real time, at Monitoring – logstream

Tools used

AspNetCore.HealthChecks.Sqlite

AspNetCore.HealthChecks.SqlServer

AspNetCore.HealthChecks.UI

AspNetCore.HealthChecks.UI.Client

AspNetCore.HealthChecks.UI.InMemory.Storage

Visual Studio

TILT- ConnectionSecrets in Azure–part 7

There is relatively easy to use – just go to your web app in Azure, Configuration, ConnectionStrings and add your data.

This tutorial is based on the following article:

https://docs.microsoft.com/en-us/azure/app-service/tutorial-dotnetcore-sqldb-app

The .NET Core code is simple:

services.AddDbContext<MyDatabaseContext>(options =>
         options.UseSqlServer(Configuration.GetConnectionString(“AZURE_SQL_CONNECTIONSTRING”)));

Tools used:

https://portal.azure.com/

TILT–CRUD API–part 5

It is true that usually you should not create CRUD API. In this case I was thinking that is faster just to have those already created and making the application later. More, the CRUD was created automatically – sgtarting from database.

Install the generator https://marketplace.visualstudio.com/items?itemName=ignatandrei.databasetocode in VS2022 , create a new solution

and then edit the connectionDetails.txt with the correct sql server name.

Tools used:

https://marketplace.visualstudio.com/items?itemName=ignatandrei.databasetocode

Visual Studio

TILT–specifications –part 2

This is an application to store what I have learned today / each day .

It will be one note string per day , note will be no more than 140 characters.

It has tags – programming, life, and so on. Can add one link to the note.

Can be saved on local ( desktop, mobile )or on cloud ( site). If wanted will be sync by having url part and password

Will have a calendar to see every day what have I learned – missing days will have an red X

Reports:

Can be exported by date end -date start into Excel Word PDF

Will show what you have learned one year before

Default are all public – if you want to be private , should pay or copy the application.

Tools used:

notepad

NetCoreUsefullEndpoints-4 Nuget package

or NugetPackages there is a simple .NET Pack CLI command  and many configurations

However, it is not so simple . See https://docs.microsoft.com/en-us/nuget/create-packages/package-authoring-best-practices .

In practice, I have a readme.md file ( for showing on nuget.org ) and a readme.txt file( for the programmer to show help after installing the package) THis is what I have in the csproj:

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

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

	<ItemGroup>
		<FrameworkReference Include="Microsoft.AspNetCore.App" />
		 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />   
		<PackageReference Include="RSCG_Static" Version="2021.12.18.2037" PrivateAssets="all" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
		<None Include="../../../README.md" Pack="true" PackagePath="\" />
		<None Include="../../../docs/nuget.png" Pack="true" PackagePath="\" />    
		<None Include="../readme.txt" Pack="true" PackagePath="\" />
	</ItemGroup>
	<PropertyGroup>
		<Version>6.2022.722.712</Version>
		<Authors>Andrei Ignat</Authors>
		
		<Description>Some usefull extensions: error, environment, user</Description>
		<Title>NetCoreUsefullEndpoints</Title>
		<PackageId>NetCoreUsefullEndpoints</PackageId>
		<PackageTags>C#;.NET;ASP.NET Core;</PackageTags>
		<PackageReadmeFile>README.md</PackageReadmeFile>
		<RepositoryUrl>https://github.com/ignatandrei/NetCoreUsefullEndpoints</RepositoryUrl>
		<PackageProjectUrl>https://github.com/ignatandrei/NetCoreUsefullEndpoints</PackageProjectUrl>
		<RepositoryType>GIT</RepositoryType>
		<Copyright>MIT</Copyright>
		<PackageLicenseExpression>MIT</PackageLicenseExpression>
		<IncludeSymbols>true</IncludeSymbols>
		<PublishRepositoryUrl>true</PublishRepositoryUrl>
		<EmbedUntrackedSources>true</EmbedUntrackedSources>
		<Deterministic>true</Deterministic>
		<DebugType>embedded</DebugType>

	</PropertyGroup>
	<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
		<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
	</PropertyGroup>
	<PropertyGroup>
	  <TreatWarningsAsErrors>False</TreatWarningsAsErrors>
	  <PackageIcon>nuget.png</PackageIcon>
	</PropertyGroup>

</Project>

Also, the versioning will be a mixture of patron major version + calendar version FOr example there are 2 versions of https://www.nuget.org/packages/NetCoreUsefullEndpoints : 6.2022.722.712 – means for ASP.NET Core 6, on year 2022, month 7, day 22, hour 7, min 12 6.2022.721.1154 – means for ASP.NET Core 6, on year 2022, month 7, day 21, hour 11, min 54

You can see

  1. as Swagger at https://netcoreusefullendpoints.azurewebsites.net/swagger

  2. As BlocklyAutomation at https://netcoreusefullendpoints.azurewebsites.net/BlocklyAutomation

  3. As package at https://www.nuget.org/packages/NetCoreUsefullEndpoints

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.