Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Share enums between C# and Javascript in MVC Razor

I have seen this useful answer to a question for adding constants into a javascript file so it can be used with razor views: Share constants between C# and Javascript in MVC Razor

I'd like to be able to do the same except define enums, but I'm not sure how to convert the C# Enum into a constant in javascript.

Off GetType(), there doesn't seem to be a way of actually getting at the constant value.

like image 901
jaffa Avatar asked Dec 01 '11 17:12

jaffa


2 Answers

I took a mixture from several people's answers and wrote this HtmlHelper extension method:

public static HtmlString GetEnums<T>(this HtmlHelper helper) where T : struct
{
    System.Text.StringBuilder sb = new System.Text.StringBuilder();
    sb.AppendLine("<script type=\"text/javascript\">");
    sb.AppendLine("if(!window.Enum) Enum = {};");
    var enumeration = Activator.CreateInstance(typeof(T));
    var enums = typeof(T).GetFields().ToDictionary(x => x.Name, x => x.GetValue(enumeration));
    sb.AppendLine("Enum." + typeof(T).Name + " = " + System.Web.Helpers.Json.Encode(enums) + " ;");
    sb.AppendLine("</script>");
    return new HtmlString(sb.ToString());
}

You can then call the method using Razor syntax like this: @(Html.GetEnums<Common.Enums.DecisionStatusEnum>())

It will then spit out javascript like this:

<script type="text/javascript">
    if(!window.Enum) Enum = {};
    Enum.WorkflowStatus = {"value__":0,"DataEntry":1,"SystemDecisionMade":2,"FinalDecisionMade":3,"ContractCreated":4,"Complete":5} ;
</script>

You can then use this in javascript like such:
if(value == Enum.DecisionStatusEnum.Complete)

Because of the check for property at the top (if(!window.Enum)), this allows you to call it for multiple enums and it won't overwrite the global Enum variable, just appends to it.

like image 124
valdetero Avatar answered Sep 18 '22 14:09

valdetero


Another method I've used before is to use t4 templates to do this kind of work. Similar to the way that t4mvc works, which can be a very powerful tool if you know how to wield it.

The idea is the in the t4template you crawl through your project and look for an enumerations, when it finds one, you'll want to template to transform it and spit out some javascript based on the C# code. The first time I worked with T4Templates through, it exploded my mind, and there aren't a lot of great resources for them, but they can be exceptional (see t4mvc)

The advantage of using templates over controller action used in the other question you linked to, is that the file generated by the t4 templating engine is the output is a regular js file and can be served/minified like any other JavaScript file, rather than requiring the overhead of the MVC stack to fulfill the request.

I could probably dig up an example if you are interested. I probably have no laying around in a repository somwhere.

Edit

So I dug around and found an example, I edited it a bit from its original form and didn't test it but you should get the idea. Its a little long, so I put it up as a github gist. But I'll highlight some important chunks here.

First, T4 templates are a templating engine built into Visual Studio, the control blocks are written in C# (or VB if you want). I am by no stretch of the imagination an expert, and am not claiming to be one, but I'll share what I can. So these files, once in a visual studio project appear similar to other "code-behind" types of items, where you can expand the .tt item and see the generated template file behind it.

So lets dig in:

<#@ template language="C#v3.5" debug="true" hostspecific="true" #>

The first line sets the language for the control blocks. As you can see I'm going to be using C#.

<#@ output extension=".js" #>

Next, i'm setting the extension of the generated file. In this case I'm saying that I want to generate a .js file. So when I place this template into a solution, lets as enum.tt, when I run the template it will create a file called enum.js. You do have control over the file (or files) that are generated. For example, t4mvc has the option to be able to generate a bunch of different files (one for each controller) or generate a single t4mvc.cs file.

Next you'll find a bunch of assemblies that I need to use. Some of the more interesting ones are the following:

<#@ assembly name="EnvDTE" #>
<#@ assembly name="EnvDTE80" #> 

Again, I'm not an expert, but you can find the documentation for these on the msdn site. These provide some core functionality to be able to access/manipulate the visual studio solution.

Then there are some fairly uninteresting imports. You'll notice that the control blocks are delimited by <# .. #> (to be honest, I dont really remember the significance of the next character, its been a while.) Anything thats not wrapped in a control block will be written directly to the output stream.

Which brings us to the start of actual file that will be written:

window.Enum = function() {
    this.__descriptions = [];
    this.__ids = []
    this.__last_value = 0;
}

window.Enum.prototype.add = function(name, val) {
    if(val == undefined) val = ++this.__last_value;
    this[name] = val;
    this[val] = name;
    this.__ids[val] = name;
    this.__descriptions[val] = name.replace(/ShowWithEllipses$/,"...").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^\s+/,"");
    return this;
}

window.Enum.prototype.describe = function(val) { return this.__descriptions[val] };

Here i'm just making a trivial javascript Enum implementation. Not claiming it to be the best. But it is what it is. :)

<#
Prepare(this);
foreach(ProjectItem pi in FindProjectItemsIn(CurrentProject.ProjectItems.Item("Models"))) {
    DumpEnumerationsFrom(pi);
}
#>

Then we get to the meat of the template. Basically it looks in a folder called Models. And digs around and tries to find any enums it can find. When it does, it calls the following method:

void DumpEnumerationsFrom(ProjectItem file) {
    var enumerations = new List<CodeEnum>();
    FindEnum(file.FileCodeModel.CodeElements, enumerations);

    if(enumerations.Count > 0) TT.WriteLine("// {0}",file.Name);

    foreach(CodeEnum enumeration in enumerations) {
        TT.Write("window.Enum.{0}=(new Enum())", enumeration.Name);
        foreach(CodeElement ce in enumeration.Children) {
            var cv = ce as CodeVariable;
            if(cv == null) continue;
            TT.Write("\r\n\t.add(\"{0}\", {1})", cv.Name, cv.InitExpression ?? "undefined");
        }
        TT.WriteLine(";\r\n");
    }
}

where it will generate something that looks like:

window.Enum.TheNameOfTheEnum = (new Enum()).add("Value1",1).add("Value2",2);

So the resulting JS file is based directly on the enumerations in your c# project.

There are some limitations though. One the enumeration has to be in a file that is in your project (not in a referenced library), at least using this implementation there might be a more clever way to do it. Every time you change your enums, you have to re-run the template (right click on it and select "Run Custom Tool").

But there are some advantages, like I mentioned before, the resulting file is just a plain js file, so can be combined and run through minification. Because its just a file, it can be hosted on a CDN, and like I mentioned before doesn't require a hit to the MVC stack to serve the request.

Anyway, I'm not saying its the best idea for all purposes, but it an under used approach in my opinion. Hopefully this may have helped shed some light and give you a direction of investigation.

like image 41
J. Holmes Avatar answered Sep 18 '22 14:09

J. Holmes