ASP.NET Core: Building enum provider to convert C# enums to JavaScript
My previous post about ASP.NET Core and getting C# enums to JavaScript was primitive and used simple attribute on enums to detect ones we need in JavaScript. This blog post extends the idea and makes some generalizations to support also those enums that are located in the libraries we don’t control or on what we don’t want to apply attribute.
- ASP.NET Core: Converting C# enums to JavaScript
- ASP.NET Core: Building enum provider to convert C# enums to JavaScript
- Converting multiple C# enums to JavaScript
JavaScriptEnum attribute
We start again by defining JavaScriptEnum attribute and some sample enums that are decorated with this attribute. JavaScriptEnum attribute is marker attribute and it doesn’t carry any functionality.
public class JavaScriptEnumAttribute : Attribute
{
}
[JavaScriptEnum]
public enum PaymentTypeEnum
{
CreditCard,
Check,
Cash
}
[JavaScriptEnum]
public enum CustomerStatusEnum
{
Regular,
Gold,
Platinum
}
Now let’s move away from web application specifics and suppose we have to work with application that contains multiple libraries. We cannot force JavaScriptEnum attribute in library projects as we don’t want web specifics to pollute so called lower layers. Also we want to support system enums and enums from third-party libraries.
We need something more general than just marker attribute. We need provider and we start with defining interface for this. This way we are able to support multiple implementations of provider.
public interface IEnumProvider
{
IEnumerable<Type> GetEnumTypes();
}
Enum provider will be class that gathers all enum types that are needed in JavaScript and returns them as an IEnumerable<Type>. Notice that code below gathers enums that are decorated with JavaScriptEnum attribute and adds also two system enums to return value.
public class EnumProvider : IEnumProvider
{
public IEnumerable<Type> GetEnumTypes()
{
var enums = new List<Type>();
enums.AddRange(GetJavaScriptEnums());
enums.Add(typeof(CalendarWeekRule));
enums.Add(typeof(ProcessorArchitecture));
return enums;
}
private static IEnumerable<Type> GetJavaScriptEnums()
{
return from a in GetReferencingAssemblies()
from t in a.GetTypes()
from r in t.GetTypeInfo().GetCustomAttributes<JavaScriptEnumAttribute>()
where t.GetTypeInfo().BaseType == typeof(Enum)
select t;
}
private static IEnumerable<Assembly> GetReferencingAssemblies()
{
var assemblies = new List<Assembly>();
var dependencies = DependencyContext.Default.RuntimeLibraries;
foreach (var library in dependencies)
{
try
{
var assembly = Assembly.Load(new AssemblyName(library.Name));
assemblies.Add(assembly);
}
catch (FileNotFoundException)
{ }
}
return assemblies;
}
}
NB! It’s possible to write more flexible and general enum provider. By example, enum provider can read information about enums to return from application configuration. It is also possible to make it return all enums from given name spaces and so on. There are no limits for providers.
Before using enum provider in web application views we have to register it with built-in dependency injection so it will be available for views and view components.
services.AddSingleton<IEnumProvider, EnumProvider>();
No let’s write view component that takes enum provider and turns returned enums to JavaScript string. Here again StringBuilder is used as buffer.
public class EnumsToJavaScriptViewComponent : ViewComponent
{
private readonly IEnumProvider _enumProvider;
public EnumsToJavaScriptViewComponent(IEnumProvider enumProvider)
{
_enumProvider = enumProvider;
}
public Task<HtmlString> InvokeAsync()
{
var buffer = new StringBuilder(10000);
foreach (var jsEnum in _enumProvider.GetEnumTypes())
{
buffer.Append("var ");
buffer.Append(jsEnum.Name);
buffer.Append(" = ");
buffer.Append(EnumToString(jsEnum));
buffer.Append("; \r\n");
}
return Task.FromResult(new HtmlString(buffer.ToString()));
}
private static string EnumToString(Type enumType)
{
var values = Enum.GetValues(enumType).Cast<int>();
var enumDictionary = values.ToDictionary(value => Enum.GetName(enumType, value));
return JsonConvert.SerializeObject(enumDictionary);
}
}
Notice that the view component doesn’t need any views. It returns just HtmlString with JavaScript and that’s it. Building view for this would be pointless overkill.
The following code snippet shows how to use the view component on layout page.
<script>
@await Component.InvokeAsync("EnumsToJavaScript")
</script>
And here is the result as seen in browser.
<script>
var PaymentTypeEnum = { "CreditCard": 0, "Check": 1, "Cash": 2 };
var CustomerStatusEnum = { "Regular": 0, "Gold": 1, "Platinum": 2 };
var CalendarWeekRule = { "FirstDay": 0, "FirstFullWeek": 1, "FirstFourDayWeek": 2 };
var ProcessorArchitecture = { "None": 0, "MSIL": 1, "X86": 2, "IA64": 3, "Amd64": 4, "Arm": 5 };
</script>
If some other casing is needed then enum provider can be changed. It also possible to create JavaScript variable formatter and ibject this to enum provider.
Other ways to convert enums to JavaScript
It’s also possible to use other ways to get enums to JavaScript.
- HtmlHelper extension method – it’s possible but it’s not very convenient to make it work with dependency injection. I don’t have any good idea right now how to do it the way the code looks nice and there are no architectural pollution,
- Extension method for Enum – we can replace this solution by simple extension method but then we have to write one line of code per Enum in view where we want to define enums; I don’t like this solution much,
- EnumProvider that returns JavaScript as string – this solution has one good point: we don’t need view component but the drawback is we need base class where method for creating JavaScript string is held (we can use the provider easily in controller actions or when using view injection we can use it also directly in views).
From these methods I would go with last one but there is one thing I don’t like – it binds enum provider strictly to web application and it has additional ballast when used in some other context. Of course, this is the matter of taste and like always – it depends!
Wrapping up
I started this enum to JavaScript saga with converting one enum to JavaScript on classic ASP.NET. Step by step I fleshed out the solution described in this post as ASP.NET Core has good features like view injection and view components that make the framework more flexible and easier to use when architecture is more complex. Although the solution here may seem like too big or too much like “corporate application architecture” it still keeps changes to enums logic in one place – if something changes then is is the matter of provider and there is no need to change layout pages. Also this way it is easier to test enum providers.
Pingback:The Morning Brew - Chris Alcock » The Morning Brew #2407
Great Article!
What would happen if you wrap the “@await Component.InvokeAsync(“EnumsToJavaScript”)” in a cache tag?
https://www.davepaquette.com/archive/2015/06/03/mvc-6-cache-tag-helper.aspx
probably get a small perf gain? Otherwise, just output it as a JS file from a T4 template because it is compile time generated.
Thanks, Thomas for feedback. Optimizations are possible, of course. I think T4 is best way to go as then it is possible to add generated JS file to JS bundle if bundling is enabled for given application.