Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enum.ToString is slower than explicit implementation #57748

Open
deeprobin opened this issue Aug 19, 2021 · 28 comments
Open

Enum.ToString is slower than explicit implementation #57748

deeprobin opened this issue Aug 19, 2021 · 28 comments
Labels
area-System.Runtime needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration tenet-performance Performance related issue
Milestone

Comments

@deeprobin
Copy link
Contributor

deeprobin commented Aug 19, 2021

Description

Since Enum.ToString usually accesses Enum.GetEnumName, which accesses it in the runtime.

public override string ToString()
{
// Returns the value in a human readable format. For PASCAL style enums who's value maps directly the name of the field is returned.
// For PASCAL style enums who's values do not map directly the decimal value of the field is returned.
// For BitFlags (indicated by the Flags custom attribute): If for each bit that is set in the value there is a corresponding constant
// (a pure power of 2), then the OR string (ie "Red, Yellow") is returned. Otherwise, if the value is zero or if you can't create a string that consists of
// pure powers of 2 OR-ed together, you return a hex value
// Try to see if its one of the enum values, then we return a String back else the value
return InternalFormat((RuntimeType)GetType(), ToUInt64()) ?? ValueToString();
}

private static string? InternalFormat(RuntimeType enumType, ulong value)
{
EnumInfo enumInfo = GetEnumInfo(enumType);
if (!enumInfo.HasFlagsAttribute)
{
return GetEnumName(enumInfo, value);
}
else // These are flags OR'ed together (We treat everything as unsigned types)
{
return InternalFlagsFormat(enumInfo, value);
}
}

private static string? GetEnumName(EnumInfo enumInfo, ulong ulValue)
{
int index = Array.BinarySearch(enumInfo.Values, ulValue);
if (index >= 0)
{
return enumInfo.Names[index];
}
return null; // return null so the caller knows to .ToString() the input
}

However, since the name is already given at compile time I think this should be treated as a constant and assembled at compile time.

Configuration

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1165 (21H1/May2021Update)
AMD Ryzen 5 3600, 1 CPU, 12 logical and 6 physical cores
.NET SDK=5.0.102
  [Host]     : .NET 5.0.2 (5.0.220.61120), X64 RyuJIT
  DefaultJob : .NET 5.0.2 (5.0.220.61120), X64 RyuJIT

Data

Benchmarks

Method Mean Error StdDev Code Size Gen 0 Allocated
RegularToString 35.004 ns 0.5859 ns 0.5481 ns 104 B 0.0029 24 B
EnumGetName 72.600 ns 0.8403 ns 0.7860 ns 68 B 0.0029 24 B
ExplicitToStringImplementation 1.764 ns 0.0613 ns 0.0573 ns 209 B - -
References

Enum.GetEnumName IL

.method assembly hidebysig static string
    GetEnumName(
      class System.RuntimeType enumType,
      unsigned int64 ulValue
    ) cil managed
  {
    .maxstack 8

    // [108 13 - 108 64]
    IL_0000: ldarg.0      // enumType
    IL_0001: ldc.i4.1
    IL_0002: call         class System.Enum/EnumInfo System.Enum::GetEnumInfo(class System.RuntimeType, bool)
    IL_0007: ldarg.1      // ulValue
    IL_0008: call         string System.Enum::GetEnumName(class System.Enum/EnumInfo, unsigned int64)
    IL_000d: ret

  }

Extension method IL

  .method public hidebysig static string
    ExplicitToString(
      valuetype ToStringBenchmarks.ExampleEnum 'enum'
    ) cil managed
  {
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor()
      = (01 00 00 00 )
    .maxstack 3
    .locals init (
      [0] string V_0
    )

    // [9 13 - 16 15]
    IL_0000: ldarg.0      // 'enum'
    IL_0001: switch       (IL_001c, IL_0024, IL_002c, IL_0034, IL_003c)
    IL_001a: br.s         IL_0044

    // [10 41 - 10 69]
    IL_001c: ldstr        "VariantA"
    IL_0021: stloc.0      // V_0
    IL_0022: br.s         IL_0056

    // [11 41 - 11 69]
    IL_0024: ldstr        "VariantB"
    IL_0029: stloc.0      // V_0
    IL_002a: br.s         IL_0056

    // [12 41 - 12 69]
    IL_002c: ldstr        "VariantC"
    IL_0031: stloc.0      // V_0
    IL_0032: br.s         IL_0056

    // [13 41 - 13 69]
    IL_0034: ldstr        "VariantD"
    IL_0039: stloc.0      // V_0
    IL_003a: br.s         IL_0056

    // [14 41 - 14 69]
    IL_003c: ldstr        "VariantE"
    IL_0041: stloc.0      // V_0
    IL_0042: br.s         IL_0056

    // [15 22 - 15 87]
    IL_0044: ldstr        "enum"
    IL_0049: ldarg.0      // 'enum'
    IL_004a: box          ToStringBenchmarks.ExampleEnum
    IL_004f: ldnull
    IL_0050: newobj       instance void [System.Runtime]System.ArgumentOutOfRangeException::.ctor(string, object, string)
    IL_0055: throw

    IL_0056: ldloc.0      // V_0
    IL_0057: ret

  }

We see the IL is bigger, but the code is faster.

Analysis

It is because in runtime the value is resolved although it is already known at compile time.

Perhaps it would also make sense to add a valuenameof keyword similar to nameof with the difference that valuenameof is taken the name of the corresponding enum variant.

External references

@deeprobin deeprobin added the tenet-performance Performance related issue label Aug 19, 2021
@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged New issue has not been triaged by the area owner label Aug 19, 2021
@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@EgorBo
Copy link
Member

EgorBo commented Aug 19, 2021

MyEnum.MyValue.ToString() can be easily folded into a constant indeed (requires some verbose changes in JIT-EE interface). However, in that case JIT will have to allocate a string object on heap for that value at the compilation time. So there must be a good reason for this opt to exist (same about 42.ToString() -> "42").

@deeprobin
Copy link
Contributor Author

@EgorBo A good reason would be that for example logging frameworks often use ToString to output components of the message.

@Tornhoof
Copy link
Contributor

This could simply be a source generator.

@deeprobin
Copy link
Contributor Author

@Tornhoof Yes thats true. Does dotnet/runtime has already built-in source code generators?

@Tornhoof
Copy link
Contributor

Yeah, in 6.0 source generators were added for JSON serializers and for logging.

@GrabYourPitchforks
Copy link
Member

Can't this be a Roslyn code analyzer and fixer under the "performance" category? If the analyzer sees SomeEnum.SomeValue.ToString(), it recommends a replacement of nameof(SomeEnum.SomeValue)? Ignore flags enums for now since they complicate matters a bit.

Note: This could in theory be a behavioral breaking change if new values are introduced to the enum between compile time and runtime. If the enum is defined in the same assembly as the call site, this obviously isn't an issue. But I don't think this would happen in practice often enough to be worth worrying about.

@ghost
Copy link

ghost commented Aug 20, 2021

Tagging subscribers to this area: @dotnet/area-system-runtime
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

Since Enum.ToString usually accesses Enum.GetEnumName, which accesses it in the runtime.

public override string ToString()
{
// Returns the value in a human readable format. For PASCAL style enums who's value maps directly the name of the field is returned.
// For PASCAL style enums who's values do not map directly the decimal value of the field is returned.
// For BitFlags (indicated by the Flags custom attribute): If for each bit that is set in the value there is a corresponding constant
// (a pure power of 2), then the OR string (ie "Red, Yellow") is returned. Otherwise, if the value is zero or if you can't create a string that consists of
// pure powers of 2 OR-ed together, you return a hex value
// Try to see if its one of the enum values, then we return a String back else the value
return InternalFormat((RuntimeType)GetType(), ToUInt64()) ?? ValueToString();
}

private static string? InternalFormat(RuntimeType enumType, ulong value)
{
EnumInfo enumInfo = GetEnumInfo(enumType);
if (!enumInfo.HasFlagsAttribute)
{
return GetEnumName(enumInfo, value);
}
else // These are flags OR'ed together (We treat everything as unsigned types)
{
return InternalFlagsFormat(enumInfo, value);
}
}

private static string? GetEnumName(EnumInfo enumInfo, ulong ulValue)
{
int index = Array.BinarySearch(enumInfo.Values, ulValue);
if (index >= 0)
{
return enumInfo.Names[index];
}
return null; // return null so the caller knows to .ToString() the input
}

However, since the name is already given at compile time I think this should be treated as a constant and assembled at compile time.

Configuration

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1165 (21H1/May2021Update)
AMD Ryzen 5 3600, 1 CPU, 12 logical and 6 physical cores
.NET SDK=5.0.102
  [Host]     : .NET 5.0.2 (5.0.220.61120), X64 RyuJIT
  DefaultJob : .NET 5.0.2 (5.0.220.61120), X64 RyuJIT

Data

Benchmarks

Method Mean Error StdDev Code Size Gen 0 Allocated
RegularToString 35.004 ns 0.5859 ns 0.5481 ns 104 B 0.0029 24 B
EnumGetName 72.600 ns 0.8403 ns 0.7860 ns 68 B 0.0029 24 B
ExplicitToStringImplementation 1.764 ns 0.0613 ns 0.0573 ns 209 B - -
References

Enum.GetEnumName IL

.method assembly hidebysig static string
    GetEnumName(
      class System.RuntimeType enumType,
      unsigned int64 ulValue
    ) cil managed
  {
    .maxstack 8

    // [108 13 - 108 64]
    IL_0000: ldarg.0      // enumType
    IL_0001: ldc.i4.1
    IL_0002: call         class System.Enum/EnumInfo System.Enum::GetEnumInfo(class System.RuntimeType, bool)
    IL_0007: ldarg.1      // ulValue
    IL_0008: call         string System.Enum::GetEnumName(class System.Enum/EnumInfo, unsigned int64)
    IL_000d: ret

  }

Extension method IL

  .method public hidebysig static string
    ExplicitToString(
      valuetype ToStringBenchmarks.ExampleEnum 'enum'
    ) cil managed
  {
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor()
      = (01 00 00 00 )
    .maxstack 3
    .locals init (
      [0] string V_0
    )

    // [9 13 - 16 15]
    IL_0000: ldarg.0      // 'enum'
    IL_0001: switch       (IL_001c, IL_0024, IL_002c, IL_0034, IL_003c)
    IL_001a: br.s         IL_0044

    // [10 41 - 10 69]
    IL_001c: ldstr        "VariantA"
    IL_0021: stloc.0      // V_0
    IL_0022: br.s         IL_0056

    // [11 41 - 11 69]
    IL_0024: ldstr        "VariantB"
    IL_0029: stloc.0      // V_0
    IL_002a: br.s         IL_0056

    // [12 41 - 12 69]
    IL_002c: ldstr        "VariantC"
    IL_0031: stloc.0      // V_0
    IL_0032: br.s         IL_0056

    // [13 41 - 13 69]
    IL_0034: ldstr        "VariantD"
    IL_0039: stloc.0      // V_0
    IL_003a: br.s         IL_0056

    // [14 41 - 14 69]
    IL_003c: ldstr        "VariantE"
    IL_0041: stloc.0      // V_0
    IL_0042: br.s         IL_0056

    // [15 22 - 15 87]
    IL_0044: ldstr        "enum"
    IL_0049: ldarg.0      // 'enum'
    IL_004a: box          ToStringBenchmarks.ExampleEnum
    IL_004f: ldnull
    IL_0050: newobj       instance void [System.Runtime]System.ArgumentOutOfRangeException::.ctor(string, object, string)
    IL_0055: throw

    IL_0056: ldloc.0      // V_0
    IL_0057: ret

  }

We see the IL is bigger, but the code is faster.

Analysis

It is because in runtime the value is resolved although it is already known at compile time.

Perhaps it would also make sense to add a valuenameof keyword similar to nameof with the difference that valuenameof is taken the name of the corresponding enum variant.

External references

Author: deeprobin
Assignees: -
Labels:

area-System.Runtime, tenet-performance, untriaged

Milestone: -

@deeprobin
Copy link
Contributor Author

Can't this be a Roslyn code analyzer and fixer under the "performance" category? If the analyzer sees SomeEnum.SomeValue.ToString(), it recommends a replacement of nameof(SomeEnum.SomeValue)? Ignore flags enums for now since they complicate matters a bit.

Well in many cases it is unknown which Enum Variant you get by .ToString i.e. you would always have to write such a huge switch construct.

@GrabYourPitchforks
Copy link
Member

Well in many cases it is unknown which Enum Variant you get by .ToString i.e. you would always have to write such a huge switch construct.

I don't follow. Why would an analyzer need a switch statement? The suggestion above was basically pattern recognition, with a few extra checks to make sure the substitution is legal.

@deeprobin
Copy link
Contributor Author

Well in many cases it is unknown which Enum Variant you get by .ToString i.e. you would always have to write such a huge switch construct.

I don't follow. Why would an analyzer need a switch statement? The suggestion above was basically pattern recognition, with a few extra checks to make sure the substitution is legal.

Oh so that's what you mean, would have assumed you meant creating an analyzer in general, for .ToString calls to enums and not their variants.
That is certainly useful but not the initial problem. The problem is that a value of type MyEnum, for example, calls the .ToString call in the runtime and that makes a drastic difference performance-wise as opposed to constantly compiling that call.

@GrabYourPitchforks
Copy link
Member

Ok, thanks for the clarification. Let me repeat what I believe your scenario and proposal are so that we're all clear here.

In your scenario, you have an enum of type MyEnum whose value is not a compile-time or JIT-time constant, perhaps because the value is read from Console.ReadLine or similar. You're asking for the JIT to codegen an optimal implementation (such as emitting a jump table) of ToString for each enum type so that the ToString implementation can call into this new logic.

Is this correct?

@giladfrid009
Copy link

I believe @deeprobin refers to the following use case:

void PrintEnum(SomeEnum value) // value not known during compile time
{
    Console.WriteLine(value.ToString());
}

@deeprobin
Copy link
Contributor Author

I believe @deeprobin refers to the following use case:

void PrintEnum(SomeEnum value) // value not known during compile time
{
    Console.WriteLine(value.ToString());
}

@giladfrid009 Exactly, you got that right :)

@koszeggy
Copy link

@deeprobin: Calling it a "performance trap" from which your code "suffers" is a huge exaggeration that can be okay for a YouTube video title. But what it suggests could be applied even for int constants: using switch for a not too large group of possible values will be always faster than int.ToString.

As a real-life scenario, I suggest to add some cases to your benchmark where enum values are part of log messages, for example. Resolving the placeholders in a String.Format (or in a Serilog template string, which is mentioned even by Nick in the comments as a real use-case) makes this cost negligible, not mentioning the cost of saving that log in a database or file (or just printing it on the console).

In fact, Enum.ToString performance has been radically improved in .NET Core. The old .NET Framework's enum performance was so painfully slow that a lot of developers created their "faster enum libraries" (including me). It's still somewhat faster than the System.Enum methods but just because when the new .NET Core solutions outperformed my library I tweaked it a lot to be better again... but to be frank, I hardly can say it means a huge benefit today.

@Elfocrash
Copy link

Hey everyone, just jumping in to give my two cents since I made the video in the first place and I don't wanna be taken out of context.

Bit of a background first. I didn't discover this, I actually attended @davidfowl's talk at IO Associates and he mentions this very use-case, the switch being a solution and addresses the O(n) and O(log(n)) thing. All I did is take that, wrap it in a good story and a clickable title and publish it.

Now as I mentioned in the video, people don't have to do this until the need to, which basically eliminates the vest majority of C# devs. Do I have usecases for it personally? Sure but that's just another thing in my micro optimization "checklist". So is not using LINQ, eliminating heap allocations when possible, preventing boxing and a bunch of other stuff.

Realistically the only thing I wanted out of that video was for someone to take the time and create a source generator that can do this automatically at compile time so I don't have to create switches by hand, and I got that. That's all there is to it.

@tannergooding tannergooding added needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration and removed untriaged New issue has not been triaged by the area owner labels Sep 7, 2021
@tannergooding tannergooding added this to the Future milestone Sep 7, 2021
@deeprobin
Copy link
Contributor Author

I would look into adding a source code generator to the System.Private.CoreLib for the enum type soon.
@tannergooding If you want, you can assign me 😄

Is there anything against implementing a source code generator for this?

@stephentoub
Copy link
Member

Is there anything against implementing a source code generator for this?

That does what, specifically?

@deeprobin
Copy link
Contributor Author

Is there anything against implementing a source code generator for this?

That does what, specifically?

I haven't dealt with the implementation exactly yet, but was thinking of implementing a simple switch construct.

Something like this: Sharplab

IL Version: Sharplab

@KalleOlaviNiemitalo
Copy link

KalleOlaviNiemitalo commented Nov 17, 2021

AFAIK, Roslyn source generators currently can only add source code, not remove or modify any. So if the original source code contains an enum type definition, then it will be compiled. C# enum type definitions cannot be partial, and according to ECMA-335 6th edition sections I.8.5.2 (Assemblies and scoping) and II.22.37 (TypeDef : 0x02), enum types cannot define methods of their own. So the source generator cannot inject an override ToString() method in the enum type. It could generate a type with an extension method, but if the extension method were named ToString(), then C# would not use it because the enum type inherits Enum.ToString() already. So the best that the source generator could do in the current SDK would be to generate a class with a differently-named extension method, e.g. ToStringFast(), and recommend its use with an analyzer and a code fixer. It might be possible to automatically use this for interpolated strings, though.

@stephentoub
Copy link
Member

stephentoub commented Nov 17, 2021

I haven't dealt with the implementation exactly yet, but was thinking of implementing a simple switch construct.

You can't override ToString on an enum in C#, which is what a C# source generator would output. And we can't / wouldn't want to modify the base Enum.ToString to know about every enum in the universe, nor would we want it to use some kind of reflection to find and hook up to some well-known-named method that provided this functionality. Having a heavily-optimized ToString implementation that comes at the expense of a non-trivial amount of additional IL (especially for larger enums) is not something that 99.9% of enums require. As @KalleOlaviNiemitalo outlined, a source generator could output an extension method for a specific enum, based for example on attributing that enum with some attribute the source generator knows about, and any code paths that need the super-fast access could call that extension method. I do not currently see a strong need for such a source generator to be in the .NET SDK; if a source generator here is something you're interested in pursuing, I suggest you do it in some other repo, release it as a nuget for others to consume, etc. Only a small percentage of valuable source generators need actually be in the .NET SDK.

@deeprobin
Copy link
Contributor Author

deeprobin commented Dec 13, 2021

True. Accordingly, we could have an intrinsic method that prints the name of an enum variant. Or is there something against it?

@stephentoub
Copy link
Member

True. Accordingly, we could have an intrinsic method that prints the name of an enum variant. Or is there something against it?

I don't understand the suggestion. Can you elaborate? And also share an example scenario where it's important to have the optimization you're suggesting because it's on a really hot path?

@deeprobin
Copy link
Contributor Author

True. Accordingly, we could have an intrinsic method that prints the name of an enum variant. Or is there something against it?

I don't understand the suggestion. Can you elaborate? And also share an example scenario where it's important to have the optimization you're suggesting because it's on a really hot path?

In any case, the implementation does no harm. It is nothing performance critical, but we are all generally interested in improving the performance of .NET.

@koszeggy
Copy link

In any case, the implementation does no harm. It is nothing performance critical, but we are all generally interested in improving the performance of .NET.

To place things in context I created a small test. Of course, when comparing the switched optimization directly to Enum.ToString ('from which your code is suffering'), the 6x difference seems significant. But please note that once you put it into some real-life scenario (eg. logging), the cost of the conversion becomes negligible:

==[Performance Test Results]================================================
Iterations: 1,000,000
Warming up: Yes
Test cases: 4
Repeats: 3
Calling GC.Collect: Yes
Forced CPU Affinity: No
Cases are sorted by time (quickest first)
--------------------------------------------------
1. 'switched' ToString: average time: 7.24 ms
  #1           7.26 ms
  #2           7.31 ms     <---- Worst
  #3           7.15 ms     <---- Best
  Worst-Best difference: 0.16 ms (2.21 %)
2. Enum.ToString: average time: 46.77 ms (+39.53 ms / 645.80 %)
  #1          49.52 ms
  #2          49.76 ms     <---- Worst
  #3          41.02 ms     <---- Best
  Worst-Best difference: 8.74 ms (21.31 %)
3. Interpolation with 'switched' ToString: average time: 147.77 ms (+140.52 ms / 2,040.44 %)
  #1         157.81 ms     <---- Worst
  #2         143.99 ms
  #3         141.50 ms     <---- Best
  Worst-Best difference: 16.31 ms (11.53 %)
4. Interpolation with Enum.ToString: average time: 201.91 ms (+194.67 ms / 2,788.09 %)
  #1         228.40 ms     <---- Worst
  #2         188.37 ms     <---- Best
  #3         188.96 ms
  Worst-Best difference: 40.03 ms (21.25 %)

Please also note that:

  • You should divide those milliseconds by 1 million to get the time of one iteration
  • The performance of the switched 'optimization' degrades for larger enums (eg. KnownColor instead of the used ConsoleColor) because of the sequential execution of the cases
  • The 'real-life' context was just a simple interpolation without doing anything with the 'log entry'. Adding it to some collection, saving in some database, or just printing it out to the console makes the difference almost unnoticeable
  • The interpolated results do not even reflect the time stamp evaluation (only a pre-stored value is formatted). And a real log entry may contain much more embedded values
  • Pre-.NET 6 string interpolation without the latest optimizations or some templating framework (eg. Serilog) would provide even worse results, making the differences even more negligible

Disclaimer: The linked test uses a very short warm-up period to spare the resources of the .NET Fiddle site. To get more reliable results (with low worst-best differences) execute it on your computer with default configuration. The test needs this package (and as I mentioned in my last comment it happens to have some solution for faster enum handling but on recent .NET platforms I would not say it means a huge benefit).

@andrewlock
Copy link
Contributor

So I understand this issue doesn't have much love, but just wanted to add something I was experimenting with.

@stephentoub mentioned:

a source generator could output an extension method for a specific enum, based for example on attributing that enum with some attribute the source generator knows about, and any code paths that need the super-fast access could call that extension method.

That's exactly what I did in this NuGet Package and it works great 🙂

However, I was interested playing with the interceptors support in .NET 8. So I put together a simple example program:

var value = MyEnum.Second;
Console.WriteLine(value.ToString());

public enum MyEnum
{
    First,
    Second,
}

public static class Interceptors
{
    [InterceptsLocation(@"Program.cs", line: 5, column: 25)] // these are correct in my test
    public static string OtherToString(this System.Enum value)
        => "Wrong Value" + value;
}

There's a couple of interesting things here:

  • The compiler forces using System.Enum as the interceptor argument, not MyEnum
  • When you run the program you get an InvalidProgramException
InvalidProgramException: Common Language Runtime detected an invalid program

I can understand that there are deep CLR reasons why this won't work, but a runtime InvalidProgramException doesn't feel like the right behaviour here 😅

@jkotas
Copy link
Member

jkotas commented Nov 15, 2023

I can understand that there are deep CLR reasons why this won't work, but a runtime InvalidProgramException doesn't feel like the right behaviour here

This is a bug in Roslyn compiler. I have opened an issue for it: dotnet/roslyn#70841

@KalleOlaviNiemitalo
Copy link

Microsoft.Extensions.EnumStrings looks related… but it has no 8.0.0 release because of dotnet/extensions#4639.

gumbarros added a commit to JJConsulting/JJMasterData that referenced this issue May 24, 2024
gumbarros added a commit to JJConsulting/JJMasterData that referenced this issue May 27, 2024
* Show confirmation dialog (#221)

* Added `showConfirmation` to replace native JS alert using `MessageBox.ts`

* Validations and bug fixes

* Don't use showConfirmation at deleteFile (modal inside modal)

* Missing jjmasterdata.js

* Load Additional ConnectionStrings from IOptions (#220)

AdditionalConnectionStrings

* Encryption fixes

* UI and rename `showConfirmation` to `showConfirmationMessage`

* Create _ConnectionStringModel.cshtml

* Created `jj-list-group` CSS class

* Fixed `ExpressionTagHelper` at FloatingLabel

* `IconPickerTagHelper` bug fix

* _Menu `ms-auto`

* Better NCalc extension method with pre-configured options (#223)

* Message toast (#224)

A simple toast message

* Added `GetDataSet` methods to `DataAccess` (#226)

* Add `GetDataSet` to `IEntityRepository`

* Invert `MessageBox.ts` `showConfirmationMessage` buttons

* Fix CodeMirror not looking good at Dark Mode

* Added `GetDataSetAsync`

* Update docs

* Update to NCalc v13 (#230)

* Update to NCalc v13

* Added --md-sticky-top-height variable

* Change `GetDataSetAsync` CommandBehavior

* Delete unused class

* Remove unused `DeleteSelectedRowsAction`

* `ModelStateWrapper` now allow empty `ModelStateDictionary`

* Adjust the log layout

* Breadcrumb Component

* Added `TypeIdentifier` to `FormElement`

* Use Breadcrumb component

* Added `Content` init accessor to `BreadcrumbItem`

* Breadcrumb development

* Support FormElement Identifier

* Add default padding to `_MasterDataLayout`

* Adjust Horizontal Layout

* `ViewBag.IsForm` conditionals

* Added `ExpressionDataAccessCommandFactory`

* Added ModelState support to `ValidationSummary` taghelper

* Added `JJOffcanvas`

* Remove dependencies from About

* Allow `JJDataPanel` at `PageState.Filter` (#232)

* Fix `DbLogger` eventId and added `LogGridViewDataSourceException`

* Move Floating Labels to `FormElementOptions`

* Added `GridRenderingTemplate`

* l10n and missing `JsonProperty`

* Fix `PdfWriter.cs`

* `loadFieldDetails` bug fix

* Fix `list-group-item` at dark theme.

* Use `ExpressionHelper` for `GridRenderingTemplate`

* Fix `DataItem.Command.Sql` editor

* Add `ViewBag.IsForm` to `_FieldLayout.cshtml`

* Update Directory.Build.props

* fix Settings breadcrumb

* bugfix: Add `IFloatingLabelControl` to handle more floating label components

* bugfix: Add `small` CSS class to selected items at `GridPagination`

* Remove unnecesary async keyword at OffcanvasHelper.ts

* Remove unnecessary `SetsRequiredMembersAttribute`

* Added `MessageToastTagHelper` and `MessageToast.ts`

* Update NCalc to v4 - Now without Antlr!

* Update Core.csproj

* Added actions to `JJTitle` and allow editing element from /Render

* Improve `JJDataPanel` debug by using `Task<List<HtmlBuilder>>` instead of `IAsyncEnumerable<HtmlBuilder>`

* bugfix: `DataPanel` not respecting `AutoReloadFormFields`

* bugfix: `Modal.ts` not using `getMasterDataForm()`

* Improve debug by removing `System.Linq.Async` from `JJMasterData.Commons`

* Improve HtmlBuilder performance (dotnet/runtime/issues/57748)

* Fix `InsertAtGridView` DataPanel `AutoReloadFormFields` behavior.

* Hide `BackAction` when `IsChildFormView`

* Remove redundant value

* Remove More from `_Menu`

* Remove preview from package version and update plugins

---------

Co-authored-by: Gustavo Mauricio de Barros <gustavomauriciodebarros@gmail.com>
gumbarros added a commit to JJConsulting/JJMasterData that referenced this issue Jun 5, 2024
* Show confirmation dialog (#221)

* Added `showConfirmation` to replace native JS alert using `MessageBox.ts`

* Validations and bug fixes

* Don't use showConfirmation at deleteFile (modal inside modal)

* Missing jjmasterdata.js

* Load Additional ConnectionStrings from IOptions (#220)

AdditionalConnectionStrings

* Encryption fixes

* UI and rename `showConfirmation` to `showConfirmationMessage`

* Create _ConnectionStringModel.cshtml

* Created `jj-list-group` CSS class

* Fixed `ExpressionTagHelper` at FloatingLabel

* `IconPickerTagHelper` bug fix

* _Menu `ms-auto`

* Better NCalc extension method with pre-configured options (#223)

* Message toast (#224)

A simple toast message

* Added `GetDataSet` methods to `DataAccess` (#226)

* Add `GetDataSet` to `IEntityRepository`

* Invert `MessageBox.ts` `showConfirmationMessage` buttons

* Fix CodeMirror not looking good at Dark Mode

* Added `GetDataSetAsync`

* Update docs

* Update to NCalc v13 (#230)

* Update to NCalc v13

* Added --md-sticky-top-height variable

* Change `GetDataSetAsync` CommandBehavior

* Delete unused class

* Remove unused `DeleteSelectedRowsAction`

* `ModelStateWrapper` now allow empty `ModelStateDictionary`

* Adjust the log layout

* Breadcrumb Component

* Added `TypeIdentifier` to `FormElement`

* Use Breadcrumb component

* Added `Content` init accessor to `BreadcrumbItem`

* Breadcrumb development

* Support FormElement Identifier

* Add default padding to `_MasterDataLayout`

* Adjust Horizontal Layout

* `ViewBag.IsForm` conditionals

* Added `ExpressionDataAccessCommandFactory`

* Added ModelState support to `ValidationSummary` taghelper

* Added `JJOffcanvas`

* Remove dependencies from About

* Allow `JJDataPanel` at `PageState.Filter` (#232)

* Fix `DbLogger` eventId and added `LogGridViewDataSourceException`

* Move Floating Labels to `FormElementOptions`

* Added `GridRenderingTemplate`

* l10n and missing `JsonProperty`

* Fix `PdfWriter.cs`

* `loadFieldDetails` bug fix

* Fix `list-group-item` at dark theme.

* Use `ExpressionHelper` for `GridRenderingTemplate`

* Fix `DataItem.Command.Sql` editor

* Add `ViewBag.IsForm` to `_FieldLayout.cshtml`

* Update Directory.Build.props

* fix Settings breadcrumb

* bugfix: Add `IFloatingLabelControl` to handle more floating label components

* bugfix: Add `small` CSS class to selected items at `GridPagination`

* Remove unnecesary async keyword at OffcanvasHelper.ts

* Remove unnecessary `SetsRequiredMembersAttribute`

* Added `MessageToastTagHelper` and `MessageToast.ts`

* Update NCalc to v4 - Now without Antlr!

* Update Core.csproj

* Added actions to `JJTitle` and allow editing element from /Render

* Improve `JJDataPanel` debug by using `Task<List<HtmlBuilder>>` instead of `IAsyncEnumerable<HtmlBuilder>`

* bugfix: `DataPanel` not respecting `AutoReloadFormFields`

* bugfix: `Modal.ts` not using `getMasterDataForm()`

* Improve debug by removing `System.Linq.Async` from `JJMasterData.Commons`

* Improve HtmlBuilder performance (dotnet/runtime/issues/57748)

* Fix `InsertAtGridView` DataPanel `AutoReloadFormFields` behavior.

* Hide `BackAction` when `IsChildFormView`

* Remove redundant value

* Remove More from `_Menu`

* Change `ConnectionString` nullability

* Use last form at the page to prevent issues with modal

* Allow Multiselect at non 'F' `FormElement`s

* bugfix: `ExpressionDataAccessCommandFactory` not converting UserId

* bugfix: OriginalName form field was creating the fields at `FieldController`

* Ignore case at `JJDataPanel` dictionaries

* Update jQuery to latest version

* Add OffcanvasHelper.hide

* R# analyzer as error: Make method static to improve performance

* `JJAlert`: Better layout for multiple messages and title

* bugfix: Log is with wrong title

* bugfix: Custom `DataPanel` with `PageState.Filter` was showing (All) at filter.

* bugfix: Encode was happening two times at GridView

* bugfix: Sticky was not working

* Update version

---------

Co-authored-by: Lucio Pelinson <lucio@jjconsulting.com.br>
Co-authored-by: Lucio Pelinson <lucio.pelinson@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.Runtime needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration tenet-performance Performance related issue
Projects
None yet
Development

No branches or pull requests