Categories
code development programming

Translations with React, Redux and Asp.Net

In a recent project, which was the first time I’ve used React and Redux in anger, we had a requirement to support 2 different languages, in the .Net backend for emails and PDFs, and in the React frontend.

As the translators we used were used to resx files, we wanted to use those as the master source. In other projects we’ve done the javascript translations via a pre-compilation step into static javascript files, but since redux has it’s own store, I decided to see if we could use that to store and process the translations.

This approach has the advantage that we can use the redux state to translate the site automatically when a new language is selected, without having to reload any pages. For an application that depends on up-to-date data, and had to operate on por data connections, avoiding reloads is essential.

The redux examples shown here use TypeScript, which certainly helped in our development process, but there’s a lot of gotchas getting the dotnet new react-redux template and many 3rd party react and redux libraries working nicely with TypeScript. You can make it work, but it’s definitely not an out-of-the-box solution in most cases.

The solution consists of 3 parts:

  1. The Asp.Net Core 2.0 controller to translate the resx data into JSON (in this example, the resource file is called UserFacingStrings.resx and the default is en-US,
  2. The Redux configuration to retrieve the data and populate it into the store; and
  3. A React function that reads the translation from the store and presents it to the user.

The solution below attempts to get the localisation from the user headers, so you’ll need to enable localisation in your Startup.cs, but depending on your use case, you may want save user language settings in the user’s account data, or in a cookie or other storage in their browser.

ASP.Net Core API controller

[Produces("application/json")]
[Route("api/Localisation")]
public class LocalisationController : Controller
{
    private static Dictionary _cultureCache = new Dictionary();

    /// Get all known translations
    /// Choose a culture (e.g. `en-US` )
    /// will default to browser culture if not specified
    [HttpGet("[action]/{culture?}")]
    public IActionResult Translations(string culture)
    {
        try
        {
            var cultureInfo = CultureInfo.CurrentUICulture;
            try
            {
                string preferredLanguage = HttpContext.GetPreferredLanguage();
                if (!string.IsNullOrWhiteSpace(culture))
                {
                    cultureInfo = CultureInfo.GetCultureInfo(culture);
                }
                else if (!string.IsNullOrWhiteSpace(preferredLanguage))
                {
                    cultureInfo = CultureInfo.GetCultureInfo(preferredLanguage);
                }
            }
            catch (CultureNotFoundException)
            {
                // Fallback to en-US
            }

            if (_cultureCache.Keys.Contains(cultureInfo.ToString()))
            {
                return new OkObjectResult(new { t = _cultureCache[cultureInfo.ToString()] });
            }

            var translations = new Dictionary();

            // Insert all resources, to then be overwritten
            if (cultureInfo.TwoLetterISOLanguageName != "en")
            {
                ExtractResources(translations, UserFacingStrings.ResourceManager.GetResourceSet(CultureInfo.GetCultureInfo("en-US"), true, true));
            }

            ExtractResources(translations, UserFacingStrings.ResourceManager.GetResourceSet(cultureInfo, true, true));

            _cultureCache[cultureInfo.ToString()] = translations;

            return new OkObjectResult(new { t = translations });
    }
    catch (Exception)
    {
        return NotFound();
    }
}

private static void ExtractResources(Dictionary translations, ResourceSet resourceSet)
{
    foreach (DictionaryEntry res in resourceSet)
    {
        translations[res.Key.ToString()] = res.Value.ToString();
    }
}

Browser Extension

public static class BrowserDetailsExtensions
{
    public static string GetPreferredLanguage(this HttpContext http)
    {
        return http?.Request
            ?.Headers["Accept-Language"].ToString()
            ?.Split(',').FirstOrDefault()
            ?.Split(';').FirstOrDefault();
    }
}

Redux plumbing

CultureActions.ts

import { AppThunkAction } from '../store/index';
import { fetch } from 'domain-task';
import { CALL_API } from 'redux-api-middleware';
import { actionCreatorFactory } from 'typescript-fsa';
import { Translations } from '../reducers/CultureState';
import { Dictionary } from 'lodash';

const API_ADDRESS: string = "/api/Localisation/";
const API_GET_TRANSLATIONS: string = "Translations/";
const API_LANGUAGE_CODE: string = ""; // "pt-BR", "en-GB"
const API_REQUEST_TYPE_GET: string = "GET";

export interface ApiError {
    name: 'ApiError',
    status: number,
    statusText: string,
    response: string
}

export interface GetTranslationsAction {
    type: 'RECEIVE_TRANSLATIONS';
    t: Dictionary;
};

export interface TranslationsApiPosted {
    type: 'Translations_REQUEST_POSTED';
    payload: never;
};

export interface TranslationsApiFailed {
    type: 'Translations_FAILURE';
    payload: ApiError;
    error: true;
};

export interface TranslationsApiSuccess {
    type: 'Translations_SUCCESS';
    t: Dictionary;
}

// Declare a 'discriminated union' type. This guarantees that all references to 'type' properties contain one of the
// declared type strings (and not any other arbitrary string).
export type CultureAction = GetTranslationsAction;

const actionCreator = actionCreatorFactory();
export const translationsAwaitingResponse = actionCreator('Translations_REQUEST_POSTED');
export const translationsSuccess = actionCreator('Translations_SUCCESS');
export const translationsFailure = actionCreator('Translations_FAILURE');

// ACTION CREATORS don't directly mutate state, but they can have external side-effects (such as loading data).
export const cultureActions = {
    getTranslations: (): AppThunkAction => (dispatch, getState) => {
        console.log("REQUEST TRANSLATIONS ACTION")
        const rsaaRequestAuctions = {
            [CALL_API]: {
                credentials: 'same-origin',
                endpoint: API_ADDRESS + API_GET_TRANSLATIONS + API_LANGUAGE_CODE,
                method: API_REQUEST_TYPE_GET,
                types: ['Translations_REQUEST_POSTED', 'Translations_SUCCESS', 'Translations_FAILURE']
            }
        }
        dispatch(rsaaRequestAuctions);
    },
};

CultureStore.ts and the tr() method

import { Dictionary } from "lodash";
import * as He from 'he';
import { Decimal } from "decimal.js";

export type CultureState = {
    strings: Translations
};

export interface Translations {
    t: Dictionary
};

export const emptyCulture: CultureState = {
    strings: {
        t: {}
    }
};

export function tr(culture: CultureState, key: string): string {
    try {
        return He.decode(culture.strings.t[key]) || key;
    } catch (error) {
        console.log("translation for '" + key + "' not found");
        if (key == undefined || key == null) {
            return "- -";
        }
        return "-".repeat(key.length);
    }
}

// Returns the key value itself if no match is found in the resx
export function trFallback(culture: CultureState, key: string): string {
    try {
        return He.decode(culture.strings.t[key]) || key;
    } catch (error) {
        return key;
    }
}

export function trFormat(culture: CultureState, key: string, args: string[]): string {
    return formatString(tr(culture, key), args);
}

export function formatDecimalAmount(subject: Decimal): string {
    return formatAmount(subject.toNumber());
};

export function formatAmount(subject: number): string {
    return subject.toLocaleString(navigator.language, { maximumFractionDigits: 2 });
};

function formatString(subject: string, args: string[]): string {
    if (subject === undefined) {
        return "";
    }
    return subject.replace(/{(\d+)}/g, function (match, number) {
        return typeof args[number] != 'undefined'
            ? args[number]
            : match
        ;
     });
};

CultureReducer.ts

import { Action, Reducer } from 'redux';
import { isType } from 'typescript-fsa';
import { Translations, emptyCulture, CultureState } from './CultureState';
import { ApiError, CultureAction, translationsAwaitingResponse, translationsFailure, translationsSuccess } from '../actions/CultureActions';
import { Dictionary } from 'lodash';

export interface CultureStateR extends CultureState { }

const unloadedState: CultureStateR = emptyCulture;

export const reducer: Reducer = (state: CultureStateR, incomingAction: Action) => {
    const action = incomingAction as CultureAction;

    if (typeof state === undefined) {
        return unloadedState;
    }

    if (isType(incomingAction, translationsAwaitingResponse)) {
        return {
            strings: state.strings
        };
    }
    if (isType(incomingAction, translationsFailure)) {
        return {
            strings: state.strings
        };
    }
    if (isType(incomingAction, translationsSuccess)) {
        const rawTranslations: Dictionary = incomingAction.payload.t;
        return {
            strings: { t: rawTranslations }
        };
    }

    switch (action.type) {
        case 'RECEIVE_TRANSLATIONS':
            return state;

        default:
            break;
    }

    return state || unloadedState;
}

Use in account controller

import { CultureState, tr } from '../reducers/CultureState';
/// Some imports removed for clarity

/// Add CultureState into props for this component
type AccountProps = AccountState & CultureState & typeof accountActions & RouteComponentProps;

export class Account extends React.Component {
}

Use tr() in view tsx file

</pre>
<h2>{tr(this.props, "PersonalDetails")}</h2>
<table>
<tbody>
<tr>
<td>{tr(this.props, "Name")}:</td>
<td>{this.props.user.userName}</td>
<td><a href="/account/profile">{tr(this.props, "Edit")}</a></td>
</tr>
<tr>
<td>{tr(this.props, "Company")}:</td>
<td>{this.props.user.companyName}</td>
</tr>
<tr>
<td>{tr(this.props, "Email")}:</td>
<td>{this.props.user.emailAddress}</td>
</tr>
</tbody>
</table>
<pre>
Categories
development

Ddd.scot, diversity, and your career

I had a great day at DDD Scotland, thanks to everyone who came along for the discussions. Apart from the panel sessions I chaired and participated in, Joe Wright ran a great mob programming session, Gary Fleming led a lean coffee session, and we had a couple of great lightning talks about recruitment at Skyscanner and Becoming a Technical Lead From Tugberk Ugurlu of Redgrave.

There were a few recurring themes that I want to highlight.

Diversity

This was a strong recurrent theme throughout the sessions in the community room. Whilst the focus was on gender due mostly to the makeup of the attendees, a few people pointed out the need to respect diversity for LGBT, age (graduates don’t have 5 years experience), family circumstances (single parents and others don’t have time in the evenings to do coding interviews), dyslexia and autism. To which I’d also add physical disabilities, skin colour, religion, any of which can and have been used intentionally or otherwise to limit the pool of candidates brought to interview, or created a hostile environment once in the job.

If you want to hire on merit, don’t just give the job to the white guy because he’s “a culture fit” and recognise that your recruitment may be biased. When I put an ad on Stackoverflow, all the replies were men, but working with a couple of recruiters we found a better mix of candidates, including the woman who we ended up hiring.

Job hunting and moving on in your career

There was a scary graph that suggests that computer scientists are less employable that other graduates, and yet of all the STEM subjects, there are more vacancies in software (where the stem jobs are ).

The job market is broken. There’s a lot of smart people out there, and for my last 2 jobs I had no experience in one of the key technologies they were advertising for, so the job adverts in many ways are meaningless. I want to work with people who have the skills to evaluate the next JavaScript framework, not 10 years experience in Vue. Nothing I work on today existed when I graduated. No ASP.NET MVC, no REST, JavaScript was for image rollovers, no Swift, no Xamarin. But job adverts don’t care about ability to learn. They’re a checklist.

I know recruitment agents get a bad reputation, and for some it’s well deserved, but a good one will help you get past the keyword gate, because they can sell you on your potential. If a company isn’t interested in your potential, choose another one. If you don’t want to deal with an agent, you need to be bold, demonstrate what you can for the requirements, and find examples to help them see that you can learn the rest quickly.

But have examples. You don’t want to be the clueless braggard who can’t even FizzBuzz.

Culture of learning, and mentorship

If you want to continue to be successful, you need to learn. Some of it you can do on your own, some you’ll need help with. If you’re working for the right company, they’ll provide you with a mentor, but even if they do, it’s worth finding others to help, whether it’s a formal process, or just someone to discuss if all companies make you deal with that stressful thing that’s getting you down.

Write a blog, volunteer for projects outside your comfort zone that help you improve those skills you’re lacking. Seek feedback. Accept that you won’t know everything and the learning experience is littered with failures. Learn by doing. Pair, mob, spike ideas.

When you’re tired of learning, find a new job.

Categories
development programming

Cloud thinking : storage as data structures

We’ve all experienced the performance implications of saving files. We use buffers, and we use them asynchronously.

Azure storage has Blobs. They look like files, and they fail like files if you write a line at a time rather than buffering. But they’re not files, they’re data structures, and you need to understand them as you need to understand O(N) when looking at Arrays and Linked Lists. Are you optimising for inserts, appends or reads, or cost?

I know it’s tempting to ignore the performance and just blame “the network” but you own your dependencies and you can do better. Understand your tools, and embrace data structures as persistent storage. After all, in the new serverless world, that’s all you’ve got.


Understanding Block Blobs, Append Blobs, and Page Blobs | Microsoft Docs

 

Categories
development programming

Working exactly to spec

Is there a problem?

  • The work was completed to spec.
  • Any additional work was likely to break the time and cost estimates.
  • The work meets the current standards. To bring the remaining paintwork up to standard would require removing and re-implementing the solution, further risking budgets and blocking higher priority work.
  • The only available colours do not provide the legacy appearance required to match the existing lines.
  • The blue lines were not part of the original specification and therefore no knowledge is available on their purpose and whether they should be extended or removed.
  • The disjointed yellow line on the right-hand side would require straightening, which would cause confusion to existing users. There are multiple consistent configurations and the workers have no means to evaluate which of these minimises confusion.
  • The user who raised the bug report is unaware of the timetable detailing the repainting plan and the agreed extent of the lines.
  • The user who raised the bug report is unaware of future proposed fixes that will require additional upheaval. Any attempt to fix other line issues now will result in unnecessary rework.
  • The existing pattern does not cover the required scope (see the far side), and any additional work would lead to scope creep to correct this error.
Categories
code development programming

Abstractions are scaffolding

All software is an abstraction. It’s human-like language with a logical structure to abstract over ill defined business processes, and gates and transistors, and assembly language, and often 7 network layers, and memory management and databases and a myriad of other things that abstractions paper over to help you deliver business value.

Abstractions are the scaffolding you need to get your project running, but they’re another dependency you need to own. You need to understanding what’s under the abstraction. Or you start thinking network traffic is free. The network is not your friend, it’s slow and unreliable.

Abstractions provide some structure to make what they support easier, but they in turn rely on structures underneath. If you’re going to own an abstraction like any other dependency, you need to understand the structure it’s built on and what it’s designed to support. I understand what ORMs do because I’ve written some of that code in a less performant, less reliable way, before I realised someone had done it a lot better. Indeed, that was the realisation that drove me to alt.Net and NHibernate, but it meant that I understood there was SQL underneath, and the SELECT N+1 problem was a mismatch in the queries that could be resolved, and not just an unexplainable performance hit caused by a network spike.

Abstractions make more sense if you understand what they’re abstracting over. They’re not there for you to forget about what’s underneath, just to save you having to write that code for every class in every project you work on, and to benefit from a wider pool of developers working on battle-tested code. If you don’t know TCP from HTTP, don’t write web applications. If you don’t understand databases or SQL, don’t use an ORM.

All abstractions are imperfect. Learn what’s under the cracks.

Categories
code development

Demotivated by refactoring

Technical debt causes drag. It slows down new features and lengthens the half life of a project (Erik Bernhardsson talks about projects with a code half-life measured in years, but Sandi Metz talks about projects with a half life of 6 weeks on her Chainline mailing list – go sign up now). We all know the benefit of doing it and development teams fight to get time to do it.

But it’s not the most exciting work. Dealing with debt can easily demotivate a team fired up by new challenges and new features, even when they see the benefit. Yak shaving doesn’t get innovators out of bed.

Sometimes the motivation comes from seeing the next step, 2 days refactoring so that this new feature can be delivered faster. Sometimes it’s a negative motivation, fix this and you’ll never have to worry about it again.

But for teams driven by delivering business value, taking time away from that to naval gaze at the code stacks up the guilt, and can tire the team out faster than a crunch, because there’s no adrenaline to go around. That’s because often the goal of refactoring isn’t enabling functionality, it’s unlocking efficiency, and that’s a less tangible goal. Why go through the pain when other tasks show more short term benefit for less work?

For one, the team has to be motivated by medium and long term goals rather than just short term. Not just what can be done this sprint, but what code do we want to work with next year? For another, refactoring in general should be small enough that the next business value task is on the horizon, and directly linked to the success of the current task. Or maybe there’s a personal reward. The team appreciates the work and so is happy to cover your support shift. They all agree that you get off pager duty for the length of the task plus a week.

When yak shaving is a drag, how do you keep motivated?

Categories
.net development programming

My .net journey

With the release of Visual Studio 2017 and .net core, I’ve seen a few folk talking about their story with the platform. This is mine.

I’ve never been the biggest Microsoft fan, ever since I grabbed a copy of Mandrake Linux and figured out how much more tinkering was available and how much more logical certain operations were than on Windows 95. But it was definitely still a tinkerers platform.

But I got an internship at Edinburgh University whilst I was a student there, funded by Microsoft. I got a laptop for the summer and a iPaq (remember that?) to keep. I also got a trip to Amsterdam to meet the other interns and some folk from Microsoft, back before they had much more than sales people in the UK. And they told me, no matter how much anyone hates Microsoft, they always hate Oracle more.

It meant that I was among the first to get the .net 1.0 CD, so I could legitimately claim later that yes, I did have 2 years of .net experience.

But from there, I stayed in Linux, learning the joys of Java Threading on Solaris (top tip : Sun really should have known what they were doing, that they didn’t means I can see some of why they failed – it was far easier working with threads on Red Hat or Windows).

And then I did my PhD, digging into device drivers, DirectX and MFC. I hated Microsoft’s Win32 GUI stuff, but the rest, in C++, was quite nice. I enjoyed being closer to the metal, and shedding the Java ceremony. I trained on templates and started to understand their power. And Java just wasn’t good enough.

I wrote research projects in C++ and data analysis engines in Python. It was good.

But Java came back, and I wrote some media playback for MacOS, and fought iTunes to listen to music. And I vowed never to buy from Apple because both were a right pain.

And I needed a new job. And I’d written bots in IronPython against C#, so I got a .Net job. And I missed the Java and Python communities, the open source chatter. And I wanted to write code in C# that was as beautiful and testable as C++. And I wanted to feel that Bulmer’s Developers! chant was a rallying call, not a lunch order from a corporate monster.

So I found alt.net and it was in Scotland, and I wrote a lot of code, and I learned that open source did exist in c#, and that there was a conference named after that chant and I met more like minded developers. I fought my nervousness and my stumbling voice and I found some confidence to present. And blog. And help write a package manager. And then everyone else learned Ruby.

And then the Scotts joined Microsoft and alt.net became .net. And then LINQ came and I remembered how clean functional programming is, and I started feeling like I was writing Python if I squinted hard, and ignored types. And then core came, and Microsoft had some growing pains. But it’s a sign that the company has completely shifted in the right direction, learning from the guys who left for Ruby. And Node.

I’m proud of what I’ve built in C#, and it’s a much better language than Java, now. It’s definitely the right choice for what I’ve built. The documentation is definitely better than Apple or Sun/Oracle produce, although MSDN and docs.microsoft.com are having some migration pains of its own.

And alt.net is making a comeback.

And I still use Python on hobby projects.

Categories
.net development programming

Windows resource limit in services

Here’s a little something that stumped us for a few days and might be worth posting to save others time.

Following a move to IIS8.5, we started seeing “Out of resource” errors on a server that did not appear to be bottlenecked by disk, CPU or RAM.

It turns out that since a previous version of IIS, the Application Pool service doesn’t grab GDI handles as it runs as a non-interactive service, so anything relying on that, such as a DLL with GDI dependencies, like an image resizing library, only gets the non-interactive desktop heap for graphical services. As soon as you get enough calls into that DLL, the heap fills and the program crashes with the “Out of resources” error.

So you recreate the issue in the debugger, attached to IIS Express, running in user space, with the full interactive desktop heap, and you can’t recreate the issue.

To fix the problem, you need to carefully adjust the heap limit in one of the ugliest registry values in Windows. Have a read here to find out what the Desktop Heap is and the registry key that controls it, then up the 3rd SharedSection value (the non-interactive heap) in small increments (lest you put in a value too high, break the interactive heap and lose the ability to log on).

And then find a way to rewrite the DLL.

Categories
development programming

Good developers learn

When I interview people for a job, I look for their skills, but most of all, I need people on my team who love to learn. I was thinking about this when I listened to Rick Strahl talking on .Net Rocks

When I started developing, there was a lot of talk about certifications and becoming a language expert, or even an expert in one aspect of a language. C# for web applications, for example.

Now, it’s no longer a matter of learning a technology. Good developers learn to learn. Understanding enough to detect smells in languages and frameworks and knowing how to trial and evaluate them. In an agile world, there’s no architecture team to dictate to you. You need to be brave enough and supported enough to make a good decision. Not necessarily the best, but at least not a wrong decision.

More than ever, with the architect hat on, we need to make quick decisions based on incomplete information and be willing and able to change if and when new information invalidates those decisions.

I have no doubt that .Net core is the future for the platform, but having made the decision to try it on a project, we had to change back to .Net framework because core isn’t ready yet. We needed experience to tell us that.

If you’re going to do something new this year, learn to learn. Invest in knowledge, experiment with new ideas and technologies, and document your discoveries, in a journal, a blog, a video, a presentation or a dictaphone, to give you the chance to reflect on what you’ve learned.

Categories
development leadership programming

! Not the lazy programmer

There’s been a popular stereotype about good programmers being productively lazy. Automating tasks to avoid work. It’s an easy thing to share but I don’t think it’s quite true. It’s about reducing inefficiency.

It’s not that developers don’t want to work, we want to do interesting work. Not repetitive work, not work that gets binned, not work to make life miserable for ourselves or others, and not work that can be done easier, cheaper and faster in a different way.

At it’s heart, software is a process that turns data, sometimes with human input, into other data, and sometimes into information and insight. Great developers understand processes, sub-processes and the connections between them, and inefficiencies smell. They distract, like a stone in the shoe, or a dam in the river. Sometimes we can quickly throw out the stone and run faster. Sometimes it takes years of rubbing away, fighting the blockages, until the path is clear.

Sometimes we shave yaks (and btw, check out that gif), Not because we’re too lazy to climb the mountain, but because we know we’ll have a better chance of getting there with a warm coat and a good plan.

Don’t be lazy. Be efficient. Be effective. And route around or remove any blockages in the way.