Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs
Contact   •   Articles   •   Products   •   Support   •   Search
Ad-free experience sponsored by:
ASPOSE - the market leader of .NET and Java APIs for file formats – natively work with DOCX, XLSX, PPT, PDF, images and more

Interactive ASP.NET Resource Linking and Editing with Westwind.Globalization


One of the most effective ways to improve localization workflow in an application is to have easy access to resources from within a running application. It’s useful to see your resources, and then be able to click on the resource content and jump directly to editing that resource. Further it’s pretty nice to be able to switch an application into a specific language easily to see the results of localizations immediately.

I’ve been putting the final touches on the next version of my Westwind.Globalization library and one of the last pieces that were rebuilt have been the interactive resource linking features. Westwind.Globalization is a localization library for .NET that allows storing resources in various kinds of databases (MS SQL, MySql, SqLit, SqlCe) which makes it much more dynamic to create, edit and generally manage resources. Because the resources are in a database they can be easily edited and the resource linking feeds into this by making it possible to directly link your Web content to the localization resource administration form and the in-context resource.

This allows not only for localization, but also for basic CMS like features that allow for admin editable user content in the page. Together with the Markdown support for resources as a baked in feature of the library, it’s actually quite easy to build runtime customizable interfaces. It isn’t going to replace a full featured CMS system, but if you have a few pieces of content that need to be user editable in an otherwise custom coded site this can certainly provide that flexibility.

In this post I’ll describe how the resource linking features in Westwind.Globalization work as well as describe some of the logic of how it’s implemented. You might find this useful if you have a need in your own applications to link content quickly to other resources either on the web or in your own applications.

What is Resource Linking?

Let’s start by demonstrating what the resource linking features in Westwind.Globalization look like. Here’s a short animated GIF that demonstrates the typical workflow for resource editing:

The flag icons are links that bring up the Web based resource editor with the selected resource preselected. At this point the resource is in context with the cursor jumping directly to the editable resource text, ready for editing. In this redesign of the Westwind.Globalization the resource editor in particular has gone through a lot of tweaking to make it efficient to use via keyboard, so you can quickly navigate, add and edit resources without taking your hands off the keyboard. For example, pressing Ctrl-Enter saves the current entry and jumps to the next resource entry and tabs/shift tab cycle through the resource entries. If a resource doesn’t exist you’re jumped straight into the Add Resource dialog to add a new resource so no extra clicks to add.

To facilitate turning resource editing on and off in your own pages the library also provides a small JavaScript based button/icon to enable resource editing on the page. You can see the opaque icon on the bottom right of the page, which when clicked turns on the resource links on the page.

Hooking up the Resource Linking

To be clear, the resource linking in the example above does not happen automatically. It relies on at least one extra attribute on an element to designate an HTML element as ‘resource linkable’.

Whether you’re using client side or server side resources you still have to add the actual resource links (ie. Razor tags (@), WebForms Script (<%: %>), or client side binding expressions like Angular ({{ expr }} or ng-bind values) in order for resources to render. If you want resources to be linkable/editable  an additional attribute on either the actual element or a container where you want the edit icon to appear is also required.

The process works by requiring a data-resource-id and data-resource-set attribute to be present on elements. The data-resource-set attribute can exist either on the actual element or on any parent element in the DOM hierachy up the chain. If a data-resource-id  element is found, the a helper function searches out the data-resource-set and then proceeds to inject an element into the page that represents the resource link icon.

For typical pages this means that you can declare the data-resource-set at the body tag or other view level DOM element:

<body data-resource-set="LocalizationForm">

Then for each for the controls you bind you can just wrap the controls with data-resource-id attributes.

Here’s an example using server side Razor syntax in ASP.NET MVC or WebPages:

<body data-resource-set="LocalizationForm">
    <div class="page-title"
         data-resource-id="PageTitle">
        @DbRes.T("PageTitle", "LocalizationForm")
    </div>

    <span data-resource-id="HelloWorld">@LocalizationForm.HelloWorld</span>
</body>

The example demonstrates both the string based DbRes resource binding (which can use any configured Resource store including Resx interchangably in any application) and strongly typed resources.

Using WebForms you can use the same approach of marking up either wrapping HTML markup or the actual WebForms controls:

<label data-resource-id="MetaTag"> Meta Tag (meta:resourcekey= lblHelloWorldLabel.Text):</label>
<asp:Label ID="lblHelloLabel" runat="server" meta:resourcekey="lblHelloWorldLabel"></asp:Label>
            
            
<label data-resource-id="StronglyTypedDbResource">Strongly typed Resource Generated from Db (uses ASP.NET ResourceProvider)</label>
<span data-resource-id="HelloWorld">
    <%= Resources.HelloWorld %>      
</span>

This works fine is essentially identical to raw markup. If you are using meta:resourcekey tags, you can also use a custom WebForms control I’ll describe later on, that can automatically generate the data-resource-id link icon for any WebForms control that includes localizable properties.

Using a pure client side interface with AngularJs you can use the following:

<div>
    <p data-resource-id="CreateClassInfo">
        {{::view.resources.CreateClassInfo}}
    </p>
    <p data-resource-id="CreateClassInfo2">
        {{::view.resources.CreateClassInfo2}}
    </p>
</div>

You can apply the same mechanism to any other kind of client side template framework. This approach works with HandleBars templates or Ember scripting – heck it works with any kind of HTML. The resource linking can be attached to any HTML elements and works on both client and server side.

Adding resource edit links with data-resource-id is optional. It may not be necessary to expose every resource id this way, but using this declarative client side mechanism you have a choice of whether and where to add the resource editing feature if at all.

Getting the Resource Linking to Work

The markup above on its own doesn’t provide the linking features – a bit of script code and CSS markup is required to provide the logic and display features.

To get resource linking to work you need to do the following:

  • Add the required JavaScript references to your page
  • Add CSS to provide the Resource Link display
  • Enable Resource Editing by calling showResourceIcons()/removeResourceIcons() 
    or use the built in Icon/Button calling showEditButton()
  • Mark up elements to edit with data-resource-id and data-resource-set

Enabling Resource Editing with JavaScript

The JavaScript required to launch the resource edit linking is minimal. There are two approaches.

The first is to just add the resource edit button to the page and let it handle enabling and disabling edit mode (example uses an MVC Razor page):

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js" type="text/javascript"></script>
@if (allowResourceEditing)
{
    <script src="~/localizationAdmin/scripts/ww.resourceEditor.js"></script>
    <script>
        // enable resource editing - button on bottom right
        ww.resourceEditor.showEditButton(
            { adminUrl: "./localizationAdmin/" }
        );
    </script>
}

This adds the Resource Edit button shown in the animated gif above which is a nice drop in way to get resource editing to work with a one liner.

If you want more control and hook enabling and disabling of resource  linking to your own application logic you explicitly enable and disable the resource linking:

@if (allowResourceEditing)
{
    <script src="~/localizationAdmin/scripts/ww.resourceEditor.js"></script>
    <script>
        var toggleEditMode = false;
        $("#btnEditResources").click(function() {
            toggleEditMode = !toggleEditMode;
            if(toggleEditMode)
                ww.resourceEditor.showResourceIcons({ adminUrl: "./localizationAdmin/" });
            else
                ww.resourceEditor.removeResourceIcons();
        });
    </script>
}

Here you are hooking the click event of some button that can act as a toggle and call showResourceIcons() and  removeResourceIcons() to toggle the edit state.

Protect Access to Resource Editing

Note the server side allowResourceEditing variable in both implementations. You don’t want the public to edit your site :-) Since resource editing is rather an administrative task you’ll want to isolate the resource editing code so that only admin or otherwise authorized users have access to this functionality.

Adding the CSS

You’ll also need a bit of CSS for the resource link icons, and the edit toggle button/icon. The resource icons are rendered as absolutely positioned and layered transparently on top and slightly above the element that is wrapped which is managed through a bit of CSS.

.resource-editor-icon, .resource-editor-icon:hover,  .resource-editor-icon:visited {
    position: absolute;
    display: inline;
    height: 13px;
    width: 13px;     
    text-decoration: none;
    z-index: 999999;
    opacity: 0.35;
    margin: -14px 0 0 -2px;
    cursor: pointer;
}
.resource-editor-icon:hover {
    opacity: 1;         
}
.resource-editor-icon:before {
    font-family: fontawesome;
    content: "\f024"; /* flag */
    font-size: 9pt;
    color: red;        
}
.resource-editor-button { z-index: 999999; color: white; background-color: DarkGreen; opacity: 0.35; position: fixed; bottom: 10px; right: 20px; padding: 7px 9px 5px 10px; border-radius: 50%; } .resource-editor-button.off { background-color: #b64545; } .resource-editor-button:before{ font-family: fontawesome; content: "\f024"; /* flag */ font-size: 14pt; } .resource-editor-button:hover { font-family: fontawesome; opacity: 0.65; }

This CSS relies on FontAwesome for the flag icon. However, if you don’t use FontAwesome just remove the FontAwesome font reference and pick any other character:

font-family: arial; content: "#";

The edit resource button is rendered as a fixed position semi-transparent object that stays in the bottom right corner of the screen and is clickable at anytime to toggle the resource edit mode. When clicked resource icons appear and disappear depending on the status and the button itself changes color from green (active) and red (inactive).

How does it work?

Behind the scenes the ww.resourceEditor class is used to retrieve all data-resource-id elements in the page. When it finds one it picks out the data-resource-set and if both of those are available generates a new element into the page which is injected just before the element that holds the data-resource-id attribute. The element is absolutely positioned with the CSS so it sits right on top of the source element in the top left corner.

The generated HTML (minus the comments) looks something like this:

<!-- injected element -->
<res-edit class="resource-editor-icon"
          title="Edit resource: Add"></res-edit>

<!-- original element -->
<button class="btn btn-sm btn-default ng-binding"
        title="Add new ResourceSet"
        ng-click="view.onAddResourceClick()"
        data-resource-id="Add">
    <i class="fa fa-plus"></i> Add
</button>

I’m using a custom res-edit HTML element for the injected element rather than a link or other official tag to avoid styling interference. This minimizes layout CSS conflicts as much as possible, but it doesn’t eliminate them completely. In testing on a variety of forms and applications I find that in most cases the resource editing works fairly well, shifting things on the page slightly but not drastically. Because the resource editing feature is an optional attribute if there is a problem with a particular control you can always remove the attribute for editing, or create another element that more appropriately represents the proper location in the document if the element is crucial for editing.

The ww.resourceEditor.js that drives this code is installed alongside the LocalizationAdmin interface and referenced from there. You can check out the code on GitHub if you’re curious, but here is the relevant code that creates the resource links and embeds them into the page.

showResourceIcons: function(options) {
    self.removeResourceIcons();

    var opt = self.options;
    $.extend(opt, options);
    self.options = opt;

    var set = $("[data-resource-set]");
    if (set.length < 1) {
        console.log("resourceEditor: No 'data-resource-set' attribute defined");
        return;
    }

    var $els = $("[data-resource-id]");
    if ($els.length < 1) {
        console.log("resourceEditor: No 'data-resource-id' attributes found");
        return;
    }

    $els.each(function() {
        var $el = $(this);
        var resId = $el.data("resource-id");
        var pos = $el.position();

        var $new = $("<res-edit>")
            .addClass("resource-editor-icon")
            .css(pos)
            .data("resource-element", this) // store actual base element
            .attr("target", "resourceEditor")
            .attr("title", "Edit resource: " + resId)
            .click(self.showEditorForm);

        $new.insertBefore($el);
    });

    $(window).bind("resize.resize_ww_resourceeditor",
        function() {
            ww.resourceEditor.removeResourceIcons();
            ww.resourceEditor.showResourceIcons(options);
        });
},
removeResourceIcons: function () {
    $(window).unbind("resize.resize_ww_resourceeditor");
    $(".resource-editor-icon").remove();
},

The key feature is the element creation where the $new variable is created. The original element item is attached in the data element to provide a reference back to the original control which that the resource id references when the icon is clicked.

When clicked the target routine picks out the data-resource-id and data-resource-set and builds up a link which is navigated with a window.open() call:

showEditorForm: function(e) {
    e.preventDefault();

    var $el = $($(this).data("resource-element"));
    var resId = $el.data("resource-id");
    var resSet = $el.data("resource-set");
    var content = $el.text() || $el.val() || "";
    content = $.trim(content);

    if (content && content.length > 600)
        content = "";

    if (!resSet) {
        var $resSets = $el.parents("[data-resource-set]");
        if ($resSets.length > 0)
            resSet = $resSets.eq(0).data("resource-set");
    }

    window.open(self.options.adminUrl + "?ResourceSet=" + encodeURIComponent(resSet) +
        "&ResourceId=" + encodeURIComponent(resId) +
        "&Content=" + encodeURIComponent(content),
        self.options.editorWindowName, self.options.editorWindowOpenOptions);
},

Web Forms Support

The original versions of Westwind.Globalization were built in the age of Web Forms, and this new release distances itself a bit from the purely WebForms based approach. Nowhere is this more prominent than in this resource linking functionality. In the past I hooked into the meta-resource key architecture of Web forms which allowed the server side to automatically generate resource links for resources based on the any [Localizable] properties on any given control.

This process worked well at the time, but it’s extremely limited to WebForms for one, and also adds a ton of overhead on the server when resource icons are rendered as the processing has to walk the entire control hierarchy and scan for localizable attributes. Further it’s not always obvious where the resource icons should be displayed since in some cases the Localizable attributes might point at non-visible or related elements. In short, while it was nice for some simple cases there were also a host of problems.

In the new version of Westwind.Globalization you now have a choice between using the same mechanism I described above, of manually marking up HTML or Control markup using the data-resource-id and data-resource-set attributes. Using this mechanism you get full control over where the icons pop up and what they link to. You can decide which resource ids you actually want to link and you can ignore a bunch of the superfluous stuff that is localizable that nobody ever actually localized…

DbResourceControl

If you liked the old behavior using the DbResourceControl which enabled resource editing no a page by walking the control hierarchy, that control is still available, and has been updated to use the new client side attribute syntax for controls. So rather than generating the flag control HTML into the page at render time, the new version only renders the attributes on the relevant controls.

To use this you can add the localization control to the bottom of your page:

<loc:DbResourceControl ID="DbResourceControl1" runat="server" EnableResourceLinking="true" />

You still need to add the JavaScript scripts and edit activation code as before:

<script src="LocalizationAdmin/bower_components/jquery/dist/jquery.min.js"></script>
<script src="LocalizationAdmin/scripts/ww.resourceEditor.js"></script>
<script>
    ww.resourceEditor.showEditButton(
        {
            adminUrl: "./localizationAdmin"
        }
    );
</script>

Then any ASP.NET controls on a page  are automatically marked up with data-resource-id and data-resource-set attributes:

<div class="form-group">
    <label class="control-label" for="form-group-input">
        <asp:Label runat="server" ID="lblName" Text="Name" meta:resourcekey="lblName" /></label>
    <asp:TextBox runat="server" ID="txtName" Text="" class="form-control" />
</div>

<div class="form-group">
    <label class="control-label" for="form-group-input">
        <asp:Label runat="server" ID="lblCompany" Text="Company" meta:resourcekey="lblCompany" /></label>
    <asp:TextBox runat="server" ID="txtCompany" Text="" class="form-control" />
</div>

<div class="well-sm well">
    <asp:Button runat="server" ID="btnSumbit" Text="Save"  meta:resourcekey="btnSubmit" CssClass="btn btn-primary" />
</div>

The resource control will automatically pick up any ASP.NET control and add links to it, whether meta-resourcekey values are assigned to it or not. The generated icons will default to a .Text property if it exists, and otherwise use the first localizable property available.

Here’s what the above form looks like including the resource linking toggle and resource editing icons activated:

WebFormsResourceEdit

Note that using the DbResourceControl is totally optional. If you want you can use the same explicit resource markup syntax described earlier and as shown for MVC applications, by explicitly marking data-resource-id attributes on your controls or containers. This is more effort, but it gives you more control.

If you are heavily use meta-resourcekey attributes for your localization bindings, then using the DbResourceControl can be useful, but if you are using other mechanisms (like strongly typed resources) in your WebForms apps, then using direct data-resource-id is a better choice.

Summary

Efficient resource linking and editing can make for a much easier workflow when localizing applications. The ability to see resources in real time as you are editing them and what effect they have on the user interface can be an invaluable tool to make you more productive in your localization process.

When I redesigned this library my goal was to make the process of editing resources easy and efficient so that localization becomes a bit less of a pain both during development when getting the localizable content set up, as well as later on actually localizing that content. The goal has been to optimize the workflow and I hope that this has been accomplished in this iteration of the tool…

Resources


I'll be at DevIntersection in Vegas this fall giving sessions on ASP.NET Core with Angular and Localization. Thinking of coming? Use discount code STRAHL and save a few bucks. If you do be sure to stop by and say hello!

ASP.NET DevIntersection 2017. Rick Strahl Coupon Code


The Voices of Reason


 

Thomas
January 27, 2016

# re: Interactive ASP.NET Resource Linking and Editing with Westwind.Globalization

Hello,

First of all, thanks for a great blog and a great globalization library :-)

I'm playing around with your globalization library, and a couple of things strike me.
When using the library on a public website, how is it possible to properly secure ONLY admin access to the /LocalizationAdmin folder and files within ? I hide all the localization GUI stuff from the frontend. In my code-behind (yes, I'm using asp.net 4.0) I simply check for a specific user and role combination. This works fine, and everything seems to be good. But when I (or google or a hacker) does some manual directory traversing on the website, and hits the /LocalizationAdmin folder, the interface appears. No authentication needed. Am I missing something? Is that behavior expected? If not, can you give me a hint on how to secure that folder?

Do you in the future intend to use stored procedures when communicating with the SQL-Server database?

I hope you'll take the time to answer my questions.

/Regards
Thomas
 

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2017