Mastering UseManagedNtlm on .NET 10 & .NET 8

I recently needed to make call to a API which use NTLM auth. It worked like a charm on Windows but I struggle on Linux Containers. You could find some tips on the Internet to install the gss-ntlmssp native lib in order to help but be careful, there’s also a strange behavior for the actual version (1.2.0-1build3) through apt-get for aspnet10 noble.

Reminder

The EWS WebService answer add these headers at first request :

WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM

It means .NET will try 2 different authentication, first Negotiate, then NTLM

  1. Negotiate => will use Interop (gss-ntlmssp for Linux)
  2. NTLM => will use Managed (if UseManagedNtlm=true)

On Linux, if the lib gss-ntlmssp is not installed,

.NET 10 (via Linux Noble)

UseManagedNtlmgss-ntlmsspResultRemarks
FalseNot installedFail1. Cannot use Interop for Negotiate
2. Cannot use Interop for NTLM
False1.2.1-build3 (apt-get)Fail1. Use Interop for Negotiate
2. Cannot use Interop for NTLM
False1.3.1 (compiled from sources)Fail1. Use Interop for Negotiate
2. Cannot use Interop for NTLM
True1.2.1-build3 (apt-get)Fail1. Use Interop for Negotiate
2. Use Managed for NTLM

the gss-ntlmssp version seems broken
True1.3.1 (compiled from sources)Ok1. Use Interop for Negotiate
2. Use Managed for NTLM
TrueNot installedOk1. Use Managed for NTLM

Will only work for NTLM, no Kerberos

Solution

Based on my dig, in order to make Negotiate and NTLM and Kerberos you need to :

  • 1. Compile and install gss-ntlmssp latest version 1.3.1
  • 2. Enable UseManagedNtlm by AppSwitch in your code
if (OperatingSystem.IsLinux())
{
    // We need to do that to be able to authenticate on Linux with EWS that uses NTLM under the hood
    AppContext.SetSwitch("System.Net.Security.UseManagedNtlm", true);
}
  • 3. install these extra libs
    • libgssapi-krb5-2
    • krb5-user
    • libkrb5-3

Publié dans Non classé | Laisser un commentaire

How to master Resilience with ASP.net and Aspire

Standard Resilience

Resilience is not an easy topic, and the official documentation could be difficult to understand.

On your Aspire project when you call

builder.AddServiceDefaults()

it really calls

public static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder
    ) where TBuilder : IHostApplicationBuilder
{
    .....

    builder.Services.ConfigureHttpClientDefaults(http =>
    {
        // Turn on resilience by default
        http.AddStandardResilienceHandler();        

        // Turn on service discovery by default
        http.AddServiceDiscovery();
    });

It will add the default standard resilience, which is basically by default :

  • 10 sec timeout for each HTTP request
  • 3 retry in case of failure/timeout above
  • 30 sec timeout total
  • open the circuit for 5 sec if 10% of the http requests fail (sliding window of 30 sec after 100 requests have been called)
  • set HttpClient.Timeout to Infinite (because it’s legacy)
.AddRateLimiter(options.RateLimiter) // 1000 + OldestFirst
.AddTimeout(options.TotalRequestTimeout) // 30sec for all processing
.AddRetry(options.Retry) // 3 retry 
.AddCircuitBreaker(options.CircuitBreaker) //0.1 - 100 - 30sec sampl - 5sec duration break
.AddTimeout(options.AttemptTimeout) // 10 sec per try;

I really don’t like the fact everything is magic, and my goal is to move the whole settings in the appsettings.json like that :

public static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder,
     IConfigurationSection resilienceConfigurationSection    
    ) where TBuilder : IHostApplicationBuilder
{
    .....

    builder.Services.ConfigureHttpClientDefaults(http =>
    {
        // Turn on resilience by default reading the configuration
        http.AddStandardResilienceHandler(resilienceConfigurationSection);        

        // Turn on service discovery by default
        http.AddServiceDiscovery();
    });

and then call it like

builder.AddServiceDefaults(builder.Configuration.GetSection("Resilience:Standard"));

Add these parameters in the appsettings.json

{
  ....
  "Resilience": {
    "Standard": {
      "RateLimiter": {
        "Name": "Standard-RateLimiter",
        "DefaultRateLimiterOptions": {
          "PermitLimit": 1000,
          "QueueLimit": 0,
          "QueueProcessingOrder": "OldestFirst"
        }
      },
      "AttemptTimeout": {
        "Name": "Standard-AttemptTimeout",
        "Timeout": "00:00:10"
      }
      "TotalRequestTimeout": {
        "Name": "Standard-TotalRequestTimeout",
        "Timeout": "00:00:30"
      },
      "Retry": {
        "Name": "Standard-Retry",
        "ShouldRetryAfterHeader": true,
        "MaxRetryAttempts": 3,
        "BackoffType": "Exponential",
        "UseJitter": true,
        "Delay": "00:00:02",
        "MaxDelay": null
      },
      "CircuitBreaker": {
        "Name": "Standard-CircuitBreaker",
        "FailureRatio": 0.1,
        "MinimumThroughput": 100,
        "SamplingDuration": "00:00:30",
        "BreakDuration": "00:00:05",
        "ManualControl": null,
        "StateProvider": null
      }
    }
  }
}

here we are, you have the default standard strategy available through the appSettings !

How to add a customer strategy for a named HttpClient ?

You need to remove the previous one with RemoveAllResilienceHandlers, set the legacy HttpTimeout to infinite (because Resilience will handle it) :

// 1. define the resilience builder
var section = builder.Configuration.GetSection("Resilience:Custom");
builder.Services.Configure<HttpStandardResilienceOptions>("CustomResilience", section);

var resiliencePipelineBuilder = 
    (ResiliencePipelineBuilder<HttpResponseMessage> builder, ResilienceHandlerContext context) =>
{
    // Optional : enable reloads dynamically when changing the appsettings
    context.EnableReloads<HttpStandardResilienceOptions>("CustomResilience");
    
    // Retrieve the named options
    var retryOptions =
        context.GetOptions<HttpStandardResilienceOptions>("CustomResilience");

    // Rebuild all properties, order is important
    builder.AddRateLimiter(retryOptions.RateLimiter);
    builder.AddTimeout(retryOptions.TotalRequestTimeout);
    builder.AddRetry(retryOptions.Retry);
    builder.AddCircuitBreaker(retryOptions.CircuitBreaker);
    builder.AddTimeout(retryOptions.AttemptTimeout);
};

// ...
#pragma warning disable EXTEXP0001 //to make work RemoveAllResilienceHandlers

builder.Services.AddHttpClient<IMyService, MyService>((client) =>
{
    client.BaseAddress = new Uri($"https+http://my-service-api/Servers/");
})
  .RemoveAllResilienceHandlers()
  .ConfigureHttpClient(client => client.Timeout = Timeout.InfiniteTimeSpan)
  .AddResilienceHandler("custom", resiliencePipelineBuilder);
  
#pragma warning restore EXTEXP0001

and copy past the Resilience:Standard to a new one in the appsettings.json :

 "Resilience": {
   "Standard": {
    }
    ....
   "Custom": {
     "RateLimiter": {
       "Name": "Custom-RateLimiter",
       ...
      }
   }
}
     

How to make work only one retry

Imagine you only want one try of maximum 5sec, and don’t want to have retry, here are the settings to change :

 "Custom": {
   ...
   "AttemptTimeout": {
     "Name": "Custom-AttemptTimeout",
     "Timeout": "00:00:05"
   },
   "TotalRequestTimeout": {
    "Name": "Custom-TotalRequestTimeout",
      "Timeout": "00:00:05"
   },
   "Retry": {
     "Name": "Custom-Retry",
     "ShouldRetryAfterHeader": true,
     "MaxRetryAttempts": 1,
     "BackoffType": "Exponential",
     "UseJitter": true,
     "Delay": "00:00:02",
     "MaxDelay": null
   },
   ...
 }

Publié dans Non classé | Marqué avec , | Laisser un commentaire

How to access LocalStorage in DelegatingHandler with Blazor SignalR – HttpClient

If you want to access LocalStorage within a DelegatingHandler (to get your access token by example), you will face this error :

« JavaScript interop calls cannot be issued at this time. This is because the component is being statically rendered. When prerendering is enabled, JavaScript interop calls can only be performed during the OnAfterRenderAsync lifecycle method »

The problem is DelegatingHandler are created in an other scope than the circuit scope (and also are cached), and you can’t reach thr circuit scope (which contains the correctly instanciated IJSRuntime).

The trick is to get the IServiceProvider from a scoped CircuitHandler, then we add it to the HttpContext Items (from IHttpContextAccessor, don’t forget to add builder.Services.AddHttpContextAccessor() in Startup)


    public class CustomCircuitHandler : CircuitHandler
    {
        private readonly IServiceProvider _serviceProvider;
        private readonly IHttpContextAccessor _contextAccessor;

        public CustomCircuitHandler(IServiceProvider serviceProvider, IHttpContextAccessor contextAccessor)
        {
            _serviceProvider = serviceProvider;
            _contextAccessor = contextAccessor;
        }

        public override Task OnConnectionUpAsync(Circuit circuit, CancellationToken cancellationToken)
        {
            // Add the service provider in the Items of the HttpContext (SignalR circuit)
            _contextAccessor.HttpContext.Items["CircuitServiceProvider"] = _serviceProvider;
            return Task.CompletedTask;
        }
    }

in Program.cs, register it (Scoped is important here to get the right IServiceProvider, don’t register as Singleton)

    
// Register it as Scoped
builder.Services.AddScoped<CircuitHandler, CustomCircuitHandler>();

Then in your HttpClient DelegatingHandler:

    public class CustomMessageHandler : DelegatingHandler
    {
        protected readonly IHttpContextAccessor _httpContextAccessor;

        public CustomMessageHandler(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }

        protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var serviceProvider = _httpContextAccessor.HttpContext.Items["CircuitServiceProvider"] as IServiceProvider;
            var protectedLocalStorage = serviceProvider.GetService<ProtectedLocalStorage>();
            await protectedLocalStorage.SetAsync("hello", "hello local storage");

            var response = await base.SendAsync(request, cancellationToken);

            return response;
        }
    }

Enjoy the access of all the javascript part in your DelegatingHandler

Publié dans Non classé | Marqué avec , , | Laisser un commentaire

How to Start or Stop resources in Aspire by code

You may wanted to Start or Stop some resources during the tests with Aspire – by example when using DistributedApplicationTestingBuilder. Unfortunately, there’s no easy API for now, but you can do it (without reflection) but you have to use at least Aspire 9.1.

1. Obtain the IResource

// DistributedApplication app;
var applicationModel = app.Services.GetRequiredService<DistributedApplicationModel>();
var resources = applicationModel.Resources;
var resource = resources.SingleOrDefault(r => string.Equals(r.Name, resourceName, StringComparison.OrdinalIgnoreCase));

2. Get the ResourceId

// Asking for any state give us back the resourceId like "resource-x1cd1sd"
var eventResource = await App.ResourceNotifications.WaitForResourceAsync(resourceName, _ => true);
var resourceId = eventResource.ResourceId;

3. Get the ResourceCommandAnnotation « resource-start » or « resource-stop » (internal KnownResourceCommands)

if (!resource.TryGetAnnotationsOfType<ResourceCommandAnnotation>(out var commands))
{
    throw new Exception("Cannot get ResourceCommandAnnotations, use at least Aspire 9.1");
}

var commandName = "resource-start"; // resource-stop, resource-restart
var resourceCommandAnnotation = commands.First(a => a.Name == commandName);

4. Execute the command

 var result = await resourceCommandAnnotation.ExecuteCommand(new ExecuteCommandContext
 {
     ServiceProvider = app.Services,
     ResourceName = resourceId,
     CancellationToken = CancellationToken.None
 });

 if (!result.Success)
 {
     throw new Exception(result.ErrorMessage);
 }

5. Check the Status

// waiting for Start
await app.ResourceNotifications.WaitForResourceAsync(resourceName, KnownResourceStates.Running);

// waiting for Stop
await app.ResourceNotifications.WaitForResourceAsync(resourceName, KnownResourceStates.TerminalStates);

Et voilà !

Publié dans Non classé | Laisser un commentaire

How to resolve dynamic TargetPort in Aspire

Aspire is a really great technology for interconnecting all kind of services. It’s so good that you will probably use it in all your old projects.
Ports randomization+proxy is a really good feature to spawn all your services, and maybe launch n instances through tests.

We recently encountered an issue, we wanted to resolve a dynamic TargetPort for an EndPointResource for a TCP service.

In our App « MyAppService » we had a port listening configured like that in appsettings.json :

{
    "ListenerConfig":    
    {
        "Port" : 1200
    }
}

And Somewhere in the code

var tcpListener = new TcpListener(IPAddress.Loopback, listenerConfig.Port);
tcpListener.Start();

As our service will be launched multiple times with tests thanks to DistributedApplicationTestingBuilder, we can’t bind multiple times to the same port, and we also don’t want to convert our project to a Docker container (which would solve the problem easily)

In Aspire we add this

builder.AddProject<Projects.MyAppServiceService>("myappservice")
       .WithEndpoint("mytcpendpoint", a => a.Port = 1200);

And now we want to inject the TargetPort in the environement variable ListenerConfig__Port in order to override the port.

var resourceMyApp = builder.AddProject<Projects.MyAppServiceService>("myappservice")
       .WithEndpoint("mytcpendpoint", a => a.Port = 1200);

resourceMyApp = resourceMyApp.WithEnvironment((EnvironmentCallbackContext cb) =>
{
     var endpointTcp = resourceProxySmtp.GetEndpoint("mytcpendpoint");
     
     // THIS WILL NOT WORK, targetPort  will be null    
     var targetPort = endpointTcp.TargetPort;
     cb.EnvironmentVariables.Add("ListenerConfig__Port", targetPort);
}

It would have work but unfortunately, TargetPort will be null here, despite the documentation telling « A callback that allows for deferred execution for computing many environment variables. This runs after resources have been allocated by the orchestrator and allows access to other resources to resolve computed data, e.g. connection strings, ports. »

While digging on the sourcecode of Aspire, I found this code and specially these 2 lines in the ProjectResourceBuilderExtensions.SetKestrelUrlOverrideEnvVariables method:

var url = ReferenceExpression.Create($"{e.EndpointAnnotation.UriScheme}://{host}:{e.Property(EndpointProperty.TargetPort)}");

// We use special config system environment variables to perform the override.

context.EnvironmentVariables[$"Kestrel__Endpoints__{e.EndpointAnnotation.Name}__Url"] = url;


So, Aspire use a special syntax called ReferenceExpression, which will be called after all WithEnvironment to Resolve endpoints target ports.

Here is the final solution :

var resourceMyApp = builder.AddProject<Projects.MyAppServiceService>("myappservice");
                           .WithEndpoint("mytcpendpoint", a => a.Port = 1200);

resourceMyApp = resourceMyApp.WithEnvironment((EnvironmentCallbackContext cb) =>
{
     var endpointTcp = resourceMyApp.GetEndpoint("mytcpendpoint");
     var targetPortExpression = endpointTcp.Property(EndpointProperty.TargetPort);
     var targetPortRefExpression = ReferenceExpression.Create($"{targetPortExpression}");

     cb.EnvironmentVariables.Add("ListenerConfig__Port", targetPortRefExpression);
}

The solution was hard to find, but I’m happy to found it – in the source code rather than in the documentation, huh – Enjoy !

Publié dans Non classé | Marqué avec , | Laisser un commentaire

Improve Windows Docker Containers Performances in isolation Process

If you use Windows Server, default isolation of containers are « process« . The good news is you will have best CPU performance with this mode(no hyperV). You can also run container in isolation process on your dev Windows machine like this :

docker run --isolation=process -it YOUR_CONTAINER_NAME

The bad news is everything you’ll do with files will be real time monitored by Windows Defender, And it will destroy the performances, specially when you read/write a lot of files (in CI/CD by exemple, getting packages like nuget or npm, building solution etc).
You will see 25 to 50% CPU Usage of MsMpEng.exe (aka Windows Defender) in Task manager. And you will look at your watch how it is slow to build/compile a project on a Windows Container.

If you try to see what files are involved with ProcMon.exe (after a call to dotnet restore), you will see that Path are not usual and point to VHD volume.

So, how to Disable Windows Defender Real Time Protection for my Windows Container in Isolation Process ?

First -wrong- Idea

I initially excluded process like « dotnet.exe » from Windows Defender. It seemed legit, but by doing this, it exposes the whole server on these process (and who knows maybe a malware cans upload a dotnet.exe on the server and execute it without any real time protection, huh !?).

Solution

Let’s exclude the VhdHardDisk* from Windows Defender, these volumes are supposed to not interact with the OS directly. By the way, you actually can’t do it with the Parameter UI (which requires a real folder), so let’s do it in PowerShell Admin

# Open Administrator Powershell
Add-MpPreference -ExclusionPath "\Device\VhdHardDisk{*}\"

Enjoy the performances now, as fast as the host !

Publié dans Non classé | Laisser un commentaire

Deserialize nested Array property with Stream in JSON.net

Sometimes, you have to deal with importing big data files and the data is not really what you’ve expected. Here is a solution for handling Deserializing data with a nested property (array). You may need to add a Depth parameter if propertyName is not enough to identify the field to deserialize.
Of course, the method use yield in order to not allocate any memory, very handy for big files !

Enjoy 🙂

/// <summary>
/// Deserialize nested array property as IEnumerable
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="stream">Stream</param>
/// <param name="propertyName">nested property to deserialize</param>
/// <param name="settings">JSON.net setting</param>
/// <returns>Enumerable of T</returns>
public static IEnumerable<T> DeserializeEnumerableNestedProperty<T>(Stream stream, string propertyName, JsonSerializerSettings settings = default)
{
	using StreamReader sr = new StreamReader(stream, leaveOpen: true);
	using JsonTextReader reader = new JsonTextReader(sr);

	// looking for property
	while (reader.Read())
	{
		if (reader.TokenType != JsonToken.PropertyName)
		{
			continue;
		}

		var currentPropertyName = reader.Value as string;

		if (currentPropertyName?.Equals(propertyName, StringComparison.OrdinalIgnoreCase) != true)
		{
			continue;
		}

		break;
	}

	// deserialize Array as enumerable
	while (reader.Read())
	{
		if (reader.TokenType != JsonToken.StartArray)
		{
			continue;
		}

	        var serializer = Newtonsoft.Json.JsonSerializer.CreateDefault(settings);

		while (reader.Read() && reader.TokenType == JsonToken.StartObject)
		{
			T obj = serializer.Deserialize<T>(reader)!;
			yield return obj;
		}

		break;
	}
}
Publié dans Non classé | Laisser un commentaire

Optimize IN clause with LINQ Entity Framework with Ranges

Problem

Suppose you have this simple query :

var ids = new long[]{1,3,4,5,8,9,10,78,34,23...};
Context.MyTable.Where(a=>ids.Contains(a)).ToList()

if ids contains one million number, Linq provider will write a giant query like :

SELECT id, field1, field2 WHERE
id IN (1,3,4,5,8,9,10,78,34,23 ...) // until one million numbers

You will have an exception because it’s too much for the SQL provider to execute this query.
You’ll probably think about these 2 solutions :

  • Inserting all theses datas in a temporary table (with a third party library), then joining
  • Splitting your queries in multiple chunks

Both are bad solutions because you have to Upload millions of number to the SQL Server.

Solution

To resolve this problem, you have to group the numbers by ranges.

Range 0 : 1,2,3,4,5
Range 1 : 19,20
Range 2 : 45,46,47,48,49,50
…..
Range 10 : 125558,125559,125560,125561,125562

IList<Range> GroupArrayToRanges(IEnumerable<int> values)
{
    var res = values.Order().Select((num, index) =>
    new
    {
        Id = num,
        Rank = index,
        Group = num - index
    })
    .GroupBy(a => a.Group)
    .Select(a => new Range(a.Min(a => a.Id), a.Max(a => a.Id)))
    .ToList();
    return res;
}

Now you have the groups, you need to rewrite the correct LINQ query.

var ids = new int[]{1,3,4,5,8,9,10,78,34,23...};
var ranges = GroupArrayToRanges(ids);
Context.MyTable.Where(a=>
   (a>=ranges[0].Start && a<=ranges[0].End) 
|| (a>=ranges[1].Start && a<=ranges[1].End)
|| (a>=ranges[2].Start && a<=ranges[2].End)
...
|| (a>=ranges[10].Start && a<=ranges[10].End)  
).ToList()

Building this query dynamically would need the use of Expression Trees because of logical OR.
If we want to build this query without Expression, we need to convert to an AND query, by including the whole range first and excluding ranges like this :

var ids = new int[]{1,3,4,5,8,9,10,78,34,23...};
var ranges = GroupArrayToRanges(ids);
Context.MyTable.Where(a=>
   (a>=ranges[0].Start && a<=ranges[10].End) // include whole range
&& !(a>ranges[0].End && a<ranges[1].Start) // exclude
&& !(a>ranges[1].End && a<ranges[2].Start) // exclude
&& !(a>ranges[2].End && a<ranges[3].Start) // exclude
...
&& !(a>ranges[9].End && a<ranges[10].Start)  
).ToList()

Now you can enjoy an elegant solution without uploading tons of data.
The only drawback of the solution is it will not work if your data cannot be grouped by interval like 1,3,6,8,12,14,16,29,31 => it will create 9 groups…
But if you have 1,2,3,4…..1001045 data, that will perfectly fit !

Go further with SQL and Linq

This is the equivalent way to do in SQL

SELECT 
     MIN([t].[Id]) AS [Min], 
     MAX([t].[Id]) AS [Max]
FROM (
    SELECT [s].[Id], [s].[Id] - (
        SELECT COUNT(*)
        FROM [SuperTable] AS [s0]
        WHERE [s0].[Id] < [s].[Id]) AS [Key]
    FROM [SuperTable] AS [s]
) AS [t]
GROUP BY [t].[Key]

-- if you really need performance, you would better go with ROW_NUMBER

SELECT 
     MIN([t].[Id]) AS [Min], 
     MAX([t].[Id]) AS [Max]
FROM (
    SELECT 
	[s].[Id], 
	s.Id - (ROW_NUMBER() OVER(ORDER BY id ASC)) AS [Key]
    FROM [SuperTable] AS [s]

) AS [t]
GROUP BY [t].[Key]

-- if you have an old MYSQL server, you can emulate ROW_NUMBER with a session variable
SET @row_number := 0;
SELECT 
     MIN(t.id) AS Min, 
     MAX(t.id) AS Max
FROM (
    SELECT 
	s.id, 
	s.id - s.RowNumber as `Key`
    FROM (    
	     SELECT id,  ((@row_number := @row_number + 1)) as RowNumber
		FROM
		T_USERS
        ORDER BY id
    ) AS s   
) AS t
GROUP BY t.Key;

And the Linq (SQL transcriptable) Syntax (without ROW_NUMBER function as it’s not supported by current LINQ providers):

var q = from s in context.Table
        orderby s.Id
        select new
        {
            Id = s.Id,           
            Rank = (from o in context.SuperTables where o.Id < s.Id select o).Count(),
            Group = s.Id - (from o in context.SuperTables where o.Id < s.Id select o).Count(),
        } into sub2
        group sub2 by sub2.Group into sub3
        select new
        {
            Min = sub3.Min(a => a.Id),
            Max = sub3.Max(a => a.Id)
        };

In a non SQL context, you would probably rewrite Select by using Select((s,index)=> syntax to perform ranking like ROW_NUMBER, it’s more efficient.

int[] data = new int[] { 1, 2, 3, 4, 5, 7, 8, 10, 11, 12 };

var res = data.Order().Select((num, index) =>
    new
    {
        Id = num,
        Rank = index,
        Group = num - index
    })
    .GroupBy(a => a.Group)
    .Select(a =>
    new
    {
        Min = a.Min(a => a.Id),
        Max = a.Max(a => a.Id)
    })
    .ToList();

Update : Use ROW_NUMBER within Entity Framework Core

I developed my own extension to support Row_Number within EntityFramework Core for SqlServer,Sqlite,Mysql,PostreSql here :

https://www.nuget.org/packages?q=Webrox.EntityFrameworkCore
Github repository : https://github.com/Poppyto/Webrox.EntityFrameworkCore

Then use it like that in order to get the id intervals :

 var res = context.Table
.Select((a, index) =>
 new
 {
	 Id = a.Id,
	 Group = a.Id - EF.Functions.RowNumber(EF.Functions.OrderBy(a.Id))
 })
 .GroupBy(a => a.Group)
 .Select(a =>
 new
 {
	 Min = a.Min(a => a.Id),
	 Max = a.Max(a => a.Id)
 })
 .ToList();
Publié dans Non classé | Marqué avec , , , , , | Laisser un commentaire

TextBox Autocomplete – Crash AccessViolationException – Workaround

It seems a nogo, but there’s a bug with AutoCompletion on Winforms since years. (.net7 here)
The only way to make it work is to set the source only once.
If you try to have a dynamic Suggestion, you could set :

Text_OnChanged(object e)
{
AutoCompleteStringCollection.AddRange("hello");
// CRASH AccessViolationException
}

It will crash with AccessViolationException.

Based on my Winforms TextBox.cs reading, I found an elegant solution.

1. Set private field _fromHandleCreate to true after HandleCreated

public class MyTextBox : TextBox
{
    FieldInfo? fiFromHandleCreate = typeof(TextBox).GetField("_fromHandleCreate",
                                                        BindingFlags.Instance |
                                                        BindingFlags.NonPublic |
                                                        BindingFlags.SetField |
                                                        BindingFlags.GetField);

    protected bool IsFromHandleCreate
    {
        get => (bool)fiFromHandleCreate.GetValue(this);
        set => fiFromHandleCreate.SetValue(this, value);
    }

    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        IsFromHandleCreate = true; //important to not destroy AutoComplete handle, avoid flickering 
    }
}

2. Redefine AutoCompleteStringCollection

The main trick is there’s always at least one element : String.Empty, because when the collections is cleared, Autocompletion is disconnected from the Textbox (and recreation bring bug)
The second trick is to set RemovedItem to String.empty instead of removing them
The third trick is to occupate empty Slot when Adding to reduce memory consumption

    /// <summary>
    /// Make AutoCompleteStringCollection dynamic
    /// </summary>
    public class MyAutoCompleteStringCollection : AutoCompleteStringCollection
    {
        static readonly string EmptyEntry = String.Empty;//space magic string
        public MyAutoCompleteStringCollection()
        {
            this.Add(EmptyEntry);//never delete this entry
        }

        FieldInfo? fidata = typeof(AutoCompleteStringCollection).GetField("data",
                                                        BindingFlags.Instance |
                                                        BindingFlags.NonPublic |
                                                        BindingFlags.SetField |
                                                        BindingFlags.GetField);
        /// <summary>
        /// base.data private field
        /// </summary>
        protected ArrayList Data
        {
            get => (ArrayList)fidata.GetValue(this);
            set => fidata.SetValue(this, value);
        }

        /// <summary>
        /// base.Clear is Evil => it disconnect the Autocompletion....:(
        /// </summary>
        public new void Clear()
        {
            var vData = this.Data;
            vData.RemoveRange(1, this.Count-1);

            //notify
            if (this.Count>1)
                base.OnCollectionChanged(new System.ComponentModel.CollectionChangeEventArgs(System.ComponentModel.CollectionChangeAction.Refresh, null));
        }

        /// <summary>
        /// Remove but without removing the String.Empty rentry
        /// </summary>
        /// <param name="value"></param>
        public new void Remove(string value)
        {
            int index = IndexOf(value);
            if (index > 0) //keep String.Empty
                base[index] = string.Empty; // clean slot + notify
        }
        /// <summary>
        /// RemoveAt but without removing the String.Empty rentry
        /// </summary>
        public new void RemoveAt(int index)
        {
            if (index > 0)//keep String.Empty
                base[index] = string.Empty; // clean slot + notify
        }

        /// <summary>
        /// Add to an unoccuped slot or a new one
        /// </summary>
        public new void Add(string text)
        {
            int index = -1;
            for (int i = 1; i < Count; i++)
            {
                if (this[i] == string.Empty)
                {
                    index = i;
                    break;
                }
            }
            if (index > 0)
                base[index] = text; // occupate the slot + notify            
            else
                base.Add(text); // add+notify
        }

        /// <summary>
        /// AddRange with efficient slot occupating
        /// </summary>
        /// <param name="values"></param>
        public new void AddRange(string[] values)
        {
            var vData = this.Data;

            int cpt = 0;
            for (int x = 0; x < values.Length; x++)
            {
                int index = -1;
                for (int i = 1; i < Count; i++)
                {
                    if (vData[i] == string.Empty)
                    {
                        index = i;
                        break;
                    }
                }
                if (index > 0)
                {
                    vData[index] = values[x]; // occupate the slot                
                    cpt++;
                }
                else
                    break;
            }

            //Send last part via AddRange
            if (cpt < values.Length)
                vData.AddRange(values.Skip(cpt).ToArray());

            if(values?.Length>0) //notify
                base.OnCollectionChanged(new System.ComponentModel.CollectionChangeEventArgs(System.ComponentModel.CollectionChangeAction.Refresh, null));

        }

    }

Et voilà !
Stephane

Publié dans Non classé | Marqué avec , | Laisser un commentaire

The solution for validating an email address (RFC compliant) in .net C#

It’s a common question : “How can I validate one email address” ?

There are 0b10 (2) types of developers : lazy and ignorant.

  1. Lazy people always wins, because they do not reinventing the wheel, they prefer to search already done API.
  2. Ignorant people will probably use Regexp (I love regexp but not in this case) or write a new algorithm, they could spend few hours or days to implement the full RFC, good luck !

So the code is obvisouly server side, and in .net :

using System.Net.Mail;
try
{
    new MailAddress("mail@foo.com"); // You can also use MailAddress.TryCreate from .net Core
   //here the mail is valid
}
catch(FormatException ex)
{
// here the email is invalid
}
Publié dans Non classé | Laisser un commentaire