Category: .NET 7

Db2Code–part 2- architecture

What we will build you can see here :

Each class will have it is own CodeTemplates\EFCore  templates from which will generate the code.

Let’s start with ExampleModels : Here will be the class definitions . From the table defintion , the DB2Code will generate

1. A definition of a interface with table columns as properties

public interface I_Department_Table 
{
 long IDDepartment { get; set; }
 string Name { get; set; }
}

2. A class with relationships

public partial class Department
{
    [Key]
    public long IDDepartment { get; set; }

    [StringLength(50)]
    [Unicode(false)]
    public string Name { get; set; } = null!;

    [InverseProperty("IDDepartmentNavigation")]
    public virtual ICollection<Employee> Employee { get; } = new List<Employee>();
}

3. class without relationship

public class Department_Table : I_Department_Table
{
 public long IDDepartment { get; set; }
 public string Name { get; set; }
 }

4. Explicit operator to convert 2 to 3

public static explicit operator Department_Table?(Department obj) { 
if(obj == null)
return null;
//System.Diagnostics.Debugger.Break();
var ret= new Department_Table();
ret.CopyFrom(obj as I_Department_Table );
return ret;
}
public static explicit operator Department?(Department_Table obj) { 
if(obj == null)
return null;
//System.Diagnostics.Debugger.Break();
var ret= new Department();
ret.CopyFrom(obj as I_Department_Table) ;
return ret;
}

5. Public method CopyFrom( interface)


public void CopyFrom(I_Department_Table other)  {
 this.IDDepartment = other.IDDepartment;
 this.Name = other.Name;
}

5. Enum with name of the columns

public enum eDepartmentColumns {
None = 0
,IDDepartment 
,Name 
}

6. Metadata with name of the tables and the columns

public static MetaTable metaData = new("Department");
static Department_Table (){
 MetaColumn mc=null;
 mc=new ("IDDepartment","long",false);
 metaData.AddColumn(mc);
 mc=new ("Name","string",false);
 metaData.AddColumn(mc);
}

 

Now it comes ExampleContext . Here will the database context and various search definitions that you need (e.g. for a column of type int, search will generate  = , > , < , between , in array  ) . More , it will generate metadata for the tables that are part of the context.

public  IAsyncEnumerable&lt;Department&gt; DepartmentSimpleSearch(GeneratorFromDB.SearchCriteria sc, eDepartmentColumns colToSearch, string value){}
public  IAsyncEnumerable&lt;Department&gt; DepartmentGetAll(){}
public async Task&lt;Department[]&gt; DepartmentFind_Array( SearchDepartment? search){}
public Task&lt;long&gt; DepartmentCount( SearchDepartment search)

 

Now it comes ExampleControllers  . This will generate controllers for REST API for the table and also Search Controllers for any kind of search that you may want.

//this is the REST controller
[ApiController]
[Route("[controller]")]    
public partial class RESTDepartmentController : Controller
{
    private ApplicationDBContext _context;
    public RESTDepartmentController(ApplicationDBContext context)
	{
        _context=context;
	}
    [HttpGet]
    public async Task&lt;Department_Table[]&gt; Get(){
        var data= await _context.Department.ToArrayAsync();
        var ret = data.Select(it =&gt; (Department_Table)it!).ToArray();
        return ret;

        
    }
    
        [HttpGet("{id}")]
    public async Task&lt;ActionResult&lt;Department_Table&gt;&gt; GetDepartment(long id)
    {
        if (_context.Department == null)
        {
            return NotFound();
        }
        var item = await _context.Department.FirstOrDefaultAsync(e =&gt; e.IDDepartment==id);

        if (item == null)
        {
            return NotFound();
        }

        return (Department_Table)item!;
    }


    [HttpPatch("{id}")]
        public async Task&lt;IActionResult&gt; PutDepartment(long id, Department value)
        {
            if (id != value.IDDepartment)
            {
                return BadRequest();
            }

            _context.Entry(value).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!DepartmentExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return NoContent();
        }

        [HttpPost]
        public async Task&lt;ActionResult&lt;Department&gt;&gt; PostDepartment(Department_Table value)
        {
          
            var val = new Department();
            val.CopyFrom(value);
            _context.Department.Add(val);
            await _context.SaveChangesAsync();

            return CreatedAtAction("GetDepartment", new { id = val.IDDepartment }, val);
        }
        [HttpDelete("{id}")]
        public async Task&lt;IActionResult&gt; DeleteDepartment(long id)
        {
            if (_context.Department == null)
            {
                return NotFound();
            }
            var item = await _context.Department.FirstOrDefaultAsync(e =&gt; e.IDDepartment==id);
            if (item == null)
            {
                return NotFound();
            }

            _context.Department .Remove(item);
            await _context.SaveChangesAsync();

            return NoContent();
        }

        private bool DepartmentExists(long id)
        {
            return (_context.Department.Any(e =&gt; e.IDDepartment  == id));
        }

    }    

And also a SEARCH controller

[ApiController]
[Route("[controller]/[action]")]    
public partial class AdvancedSearchDepartmentController : Controller
{
    private ISearchDataDepartment _search;
    public AdvancedSearchDepartmentController(ISearchDataDepartment search)
	{
        _search=search;
	}

    [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; GetAll()
    {
        await foreach(var item in _search.DepartmentFind_AsyncEnumerable(null))
        {
            yield return (Department_Table)item!;
        }
        
    }
    [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; GetWithSearch(SearchDepartment s)
    {
        await foreach(var item in _search.DepartmentFind_AsyncEnumerable(s))
        {
            yield return (Department_Table)item!;
        }
        
    }

//has one key
    [HttpGet]
    public async Task&lt;Department_Table?&gt; GetSingle(long id){
        var data=await _search.DepartmentGetSingle(id);
       if(data == null)
        return null;
       return (Department_Table)data;
    }

                  [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; EqualValues_IDDepartment( long[]  values)
    {
        string? value=null;
        if(values.Length&gt;0)
            value=string.Join( ",",values);
        var sc=SearchDepartment.FromSearch(GeneratorFromDB.SearchCriteria.InArray,eDepartmentColumns.IDDepartment,value);
        await foreach (var item in _search.DepartmentFind_AsyncEnumerable(sc))
        {
        
            yield return (Department_Table)item!;
        }
    }
     [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; DifferentValues_IDDepartment( long[]  values)
    {
        string? value=null;
        if(values.Length&gt;0)
            value=string.Join( ",",values);
        var sc=SearchDepartment.FromSearch(GeneratorFromDB.SearchCriteria.NotInArray,eDepartmentColumns.IDDepartment,value);
        await foreach (var item in _search.DepartmentFind_AsyncEnumerable(sc))
        {
        
            yield return (Department_Table)item!;
        }
    }
         [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; EqualValue_IDDepartment( long  value)
    {
        var sc = GeneratorFromDB.SearchCriteria.Equal;
        await foreach (var item in _search.DepartmentSimpleSearch_IDDepartment(sc, value))
        {
            yield return (Department_Table)item!;
        }
    }
    [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; DifferentValue_IDDepartment( long  value)
    {
        var sc = GeneratorFromDB.SearchCriteria.Different;
        await foreach (var item in _search.DepartmentSimpleSearch_IDDepartment(sc, value))
        {
            yield return (Department_Table)item!;
        }
    }
    [HttpGet]
    public  async IAsyncEnumerable&lt;Department_Table&gt; SimpleSearch_IDDepartment(GeneratorFromDB.SearchCriteria sc,  long value){
        await foreach(var item in _search.DepartmentSimpleSearch_IDDepartment(sc,value))
        {
            yield return (Department_Table)item!;
        }
    }
    [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; FindNull_IDDepartment(){
        var sc = GeneratorFromDB.SearchCriteria.Equal;
        await foreach(var item in _search.DepartmentSimpleSearchNull_IDDepartment(sc))
        {
            yield return (Department_Table)item!;
        }
    }
    [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; FindNotNull_IDDepartment(){
        var sc = GeneratorFromDB.SearchCriteria.Different;
        await foreach(var item in _search.DepartmentSimpleSearchNull_IDDepartment(sc))
        {
            yield return (Department_Table)item!;
        }
    }
              [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; EqualValues_Name( string[]  values)
    {
        string? value=null;
        if(values.Length&gt;0)
            value=string.Join( ",",values);
        var sc=SearchDepartment.FromSearch(GeneratorFromDB.SearchCriteria.InArray,eDepartmentColumns.Name,value);
        await foreach (var item in _search.DepartmentFind_AsyncEnumerable(sc))
        {
        
            yield return (Department_Table)item!;
        }
    }
     [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; DifferentValues_Name( string[]  values)
    {
        string? value=null;
        if(values.Length&gt;0)
            value=string.Join( ",",values);
        var sc=SearchDepartment.FromSearch(GeneratorFromDB.SearchCriteria.NotInArray,eDepartmentColumns.Name,value);
        await foreach (var item in _search.DepartmentFind_AsyncEnumerable(sc))
        {
        
            yield return (Department_Table)item!;
        }
    }
         [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; EqualValue_Name( string  value)
    {
        var sc = GeneratorFromDB.SearchCriteria.Equal;
        await foreach (var item in _search.DepartmentSimpleSearch_Name(sc, value))
        {
            yield return (Department_Table)item!;
        }
    }
    [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; DifferentValue_Name( string  value)
    {
        var sc = GeneratorFromDB.SearchCriteria.Different;
        await foreach (var item in _search.DepartmentSimpleSearch_Name(sc, value))
        {
            yield return (Department_Table)item!;
        }
    }
    [HttpGet]
    public  async IAsyncEnumerable&lt;Department_Table&gt; SimpleSearch_Name(GeneratorFromDB.SearchCriteria sc,  string value){
        await foreach(var item in _search.DepartmentSimpleSearch_Name(sc,value))
        {
            yield return (Department_Table)item!;
        }
    }
    [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; FindNull_Name(){
        var sc = GeneratorFromDB.SearchCriteria.Equal;
        await foreach(var item in _search.DepartmentSimpleSearchNull_Name(sc))
        {
            yield return (Department_Table)item!;
        }
    }
    [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; FindNotNull_Name(){
        var sc = GeneratorFromDB.SearchCriteria.Different;
        await foreach(var item in _search.DepartmentSimpleSearchNull_Name(sc))
        {
            yield return (Department_Table)item!;
        }
    }
        


    


}//end class

 

Finally , ExampleWebAPI . This will add the controllers from the ExampleControllers   to show the use .

var assControllers = typeof(UtilsControllers).Assembly;
builder.Services.AddControllers()
              .PartManager.ApplicationParts.Add(new AssemblyPart(assControllers)); ;
        

 

Of course , the name Example will be replaced , from the template, with the name of your project

.NET 7 Latest features

.NET 7 is a version of the .NET platform that was released on November 10, 2021. It is a major release that includes many new features and improvements. Some of the notable features of .NET 7 include:

Improved performance: .NET 7 includes many improvements that result in faster startup time and better overall performance.

Enhanced support for web development: .NET 7 includes several new features that make it easier to build web applications, including support for WebAssembly and a new HTTP client library.

Improved support for ARM64: .NET 7 includes better support for ARM64, allowing you to build applications that can run on devices with ARM processors.

New native interop API: .NET 7 includes a new native interop API that makes it easier to call native code from .NET applications.

Improved support for .NET MAUI: .NET MAUI is a cross-platform UI framework for building mobile, desktop, and cloud-based applications. .NET 7 includes several improvements to .NET MAUI, including a new Hot Reload feature and improved support for .NET 5.

For more information about .NET 7, you can visit the following links:

.NET 7 documentation: https://docs.microsoft.com/en-us/dotnet/standard/dotnet-7
.NET 7 release notes: https://github.com/dotnet/announcements/blob/master/releasedotnet/7/7.0.md

The previous lines were generated with ChatGPT , https://chat.openai.com/chat.  My presentation is at  https://ignatandrei.github.io/Presentations/WhatsNewNet7prez.html#1 with

  1. What’s new VS2022 ?

  2. What’s new C# 11
  3. What’s new Blazor
  4. What’s new ASP.NET Core 7
  5. What’s new EFCore 7
  6. Links

Rate Limiter–CORS limited

The problem is that , for the site ( Angular , React, plain HTML ) deployed into wwwroot of .NET Core, I want to have unlimited requests. Also, if I made requests to localhost ( when I try from local), I want also unlimited requests. However, if other site make requests to my AP( CORS enabled ) I, should have limited requests .

There is the Stefan Prodan package – https://github.com/stefanprodan/AspNetCoreRateLimit  . And in .NET Core 7 has appeared https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit  with 4 limiters  -please be sure that you read the article https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit .

Back to the problem:

So we have 2 limits; NoLimit if same site and a simple limiter( 3 request per minute )_ if coming via other site.

var noLimit = RateLimitPartition.GetNoLimiter(“”);

Func<string, RateLimitPartition<string>> simpleLimiter =
     (string address) =>
RateLimitPartition.GetFixedWindowLimiter(address, _ =>
{
     return new FixedWindowRateLimiterOptions()
     {
         PermitLimit = 3,
         Window = TimeSpan.FromMinutes(1),
         QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
         QueueLimit = 1
     };

});

Now, let’s see how we apply :

  1. verify if the host is unknown or  localhost – then no limit.
  2. verify if header has Origin key
    1. if has not – could be same site or desktop – then no limit ( if the xhr request to same site, Edge sends “Origin” header – Chrome does not! – same as desktop )
    2. if has – verify if it is the same with host
      1. if it is, then no limit
      2. if it is not, then simple limit
  3. return default simple limit

And this is the code

builder.Services.AddRateLimiter(opt =>
{
     opt.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
     opt.OnRejected = (ctx, ct) =>
     {
         ctx.HttpContext.Response.Headers.Add(“tiltLimit”, “please try later”);
         return ValueTask.CompletedTask;
     };
     opt.AddPolicy(“UnlimitMeAndLocalHost”, context =>
     {
        
         var host = context.Request.Host;
         var hostName = host.HasValue ? host.Host : “”;
         if (string.IsNullOrWhiteSpace(hostName))
         {
             Console.WriteLine(“no host???”);
             return simpleLimiter(“”);
         }
         if (string.Equals(hostName,”localhost”,StringComparison.InvariantCultureIgnoreCase))
         {
             Console.WriteLine(“localhost have no limit”);
             return noLimit;
         }
         //chrome does not send origin, if same site
         var hasOrigin = context.Request.Headers.TryGetValue(“Origin”, out var origin);
         //maybe also verify referer?
         if (!hasOrigin)
         {
             Console.WriteLine(“no origin -same site?”);
             return noLimit;

        }
         //edge sends origin
         var originHost = origin.ToString();
         //removing scheme
         if (originHost.StartsWith(“http://”))
         {
             originHost = originHost.Substring(7);
         }
         if (originHost.StartsWith(“https://”))
         {
             originHost = originHost.Substring(8);
         }
         var fullHost = context.Request.Host.Host;
         Console.WriteLine($”has origin {originHost} , full host {fullHost}”);
         if (string.Equals(fullHost,originHost, StringComparison.CurrentCultureIgnoreCase))
         {
             Console.WriteLine($”same site – no cors”);
             return noLimit;
         }
         //return noLimit;
         return simpleLimiter(origin.ToString());
     });

Pretty complicated – read again the algorithm above.

As a proof of concept , browse to https://ignatandrei.github.io/TILT/tilt/public  and to http://tiltwebapp.azurewebsites.net/AngTilt/ with developer tools open –  you will see 429 errors for the first, but not for the second.

And for seeing in action , execute 4 times fast this powershell

cls
$x=””
$hostName = “https://tiltwebapp.azurewebsites.net”
#$hostName = “http://localhost:9900″

$fullUrl = $hostName + “/api/PublicTILTs/LatestTILTs/ignatandrei/1”

$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
$session.UserAgent = “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.62”
$x = Invoke-WebRequest -UseBasicParsing -Uri $fullUrl `
-WebSession $session `
-Headers @{
  “Origin” = “http://www.foo.com/”
“Accept-Encoding”=”gzip, deflate, br”
   “Accept-Language”=”en-US,en;q=0.9”
   “Referer”=”https://netcoreusefullendpoints.azurewebsites.net/swagger/index.html”
   “accept”=”application/json”
   “sec-ch-ua”=”`”Microsoft Edge`”;v=`”107`”, `”Chromium`”;v=`”107`”, `”Not=A?Brand`”;v=`”24`””
   “sec-ch-ua-mobile”=”?0″
   “sec-ch-ua-platform”=”`”Windows`””
}

$x.StatusCode
Write-Host ” ————————–“

NetCoreUsefullEndpoints–part 6–passing to .NET 7

So  .NET 7 has appeared and I decided to pass NetCoreUsefullEndpoints to .NET 7 .

Also, for RateLimiter , I have considered that is good to know if the request is local or remote … so I decided to add connection ( remote IP, local IP, and more details) to the nuget package.

So I have created for .NET 6 this :

So , first things first : modify the version from  6.2022.1203.1551 to  7.2022.1203.1551 ( my versioning scheme is .NETCore version compliant of the package.year.MMdd.HHmm  – pretty stable and easy to decide what package you should add)

Then I want to profit to show in swagger the return type with TypedResults – so , as an example , I have modified from

route.MapGet(“api/usefull/httpContext/Connection”, (HttpContext httpContext) =>
{
var con = httpContext.Connection;
if (con == null)
{
     return Results.NoContent();
}
var conSerialize = new
{
     LocalIpAddress = con.LocalIpAddress?.ToString(),
     RemoteIpAddress = con.RemoteIpAddress?.ToString(),
     con.RemotePort,
     con.LocalPort,
     con.ClientCertificate,
     con.Id
};
return Results.Ok(conSerialize);

})

to

route.MapGet(“api/usefull/httpContext/Connection”,

Results<NoContent, Ok<object>>
(HttpContext httpContext) =>
{
var con = httpContext.Connection;
if (con == null)
{
     return TypedResults.NoContent();
}
var conSerialize = new
{
     LocalIpAddress = con.LocalIpAddress?.ToString(),
     RemoteIpAddress = con.RemoteIpAddress?.ToString(),
     con.RemotePort,
     con.LocalPort,
     con.ClientCertificate,
     con.Id
};
return TypedResults.Ok((object)conSerialize);
})

As you see , a pretty easy modification – indicating the INesteHttpResult Results<NoContent, Ok<object>>  ( so the swagger understand the 2 different return types )  and returning TypedResults instead of Results

Also the github ci must add the .NET Core and Azure App Service should be going to .NET 7 STS

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.