Search code examples
c#.netwindows-10template-enginewindows-10-universal

How to implement templating on UWP/UAP


I need to generate HTML based off some models in an UWP/UAP project - written in C# entirely.

The problem is that no preexisting templating engines (I really wouldn't want to write one from scratch) are compatible with the platform. I looked up a few most popular alternatives, and none would work out of the box.

What would be your suggested solution for this substantive issue I am facing? I thought about porting one of available open-source libraries, but I'm not sure whether it would be worth the effort.

I'm not interested in opinionated answers, or recommendations of templating engines (that don't really work on UAP), only in the easiest route to solve the problem of templating in a Windows 10 universal app.


Solution

  • If you are developing, using HTML/JavaScript, you can use built-in WinJS library for templating in HTML. Also Microsoft created a number of adapters for Angular, React or Knockout to use them in UWP apps.

    Usually most of JavaScript code should just work, the only thing you should take into consideration is security limitations while embedding iframes or external script. To bypass such limitations you should update logic of used library or manually enforce by using the execUnsafeLocalFunction method.

    MSApp.execUnsafeLocalFunction(function() {
        var body = document.getElementsByTagName('body')[0];
        body.innerHTML = '<div style="color:' + textColor + '">example</div>';
    });
    

    == Update:

    For C# there are two options:

    1. continue looking for library or impellent it yourself that will do such convertion, or
    2. build a wrapper around webview control, that will do it for you with already existing libraries.

    In the second case it should be quite easy: 1) You should implement a converter from JSON to HTML (with template) using any library of your choose in JavaScript, it should be available for calling from external context (global function or object). Such function should receive data as a JSON-string and return a string (e.g. from innerHTML). 2) Use standard technics with InvokeScriptAsync/ScriptNotify event for the WebView control to transfer data between C# and JS environments. You don't need actually to display WebView inside your page. Just use it as environment for JS execution with access to DOM, required by corresponding libraries.

    Here is code sample with WinJS: HTML/JS. template.html file with WinJS library. Global js-function:

    function convertFromJSON2HTML(jsonString, templateHTML) {
        var templateEl = document.createElement("div");
        templateEl.innerHTML = templateHTML;
    
        var jsonData = JSON.parse(jsonString);
    
        if (WinJS != undefined) {
            var template = new WinJS.Binding.Template(templateEl);
    
            //promise
            template.render(jsonData).done(function(output) {
                window.external.notify(output.innerHTML);
            });
        } else {
               // catch error
        }
    }
    

    C# Code

    public async Task<string> GenerateTemplate(string jsonData, string template)
    {
        var webView = new WebView();
        webView.Source = new Uri("ms-appx-web:///HTML/template.html");
    
        webView.DOMContentLoaded += async (o, e) =>
        {
            await webView.InvokeScriptAsync("convertFromJSON2HTML", new string[] { jsonData, template });
        };            
    
        string output = "";
    
        // Simple trick to convert event result to async result
        TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
        webView.ScriptNotify += (o, e) =>
        {
            output = e.Value;
            tcs.SetResult(true);
        };
        await tcs.Task;
        return output;
    }
    

    Usage. I'm using WinJS-style templates here:

    var result = await GenerateTemplate("{\"x\":\"test\"}","<p data-win-bind='textContent:x'></p>");
    resultBox.Text = result;
    

    Also to simplify communications I would recommend to use the AddAllowedWebObject method to pass WinRT-objects between two environments instead of just text.