OmidID

.Net technology weblog

Mono mkbundle - How to export stand alone UI application on Linux base on GTK#

Lot of people would like to have a multiple platform application using share code One of the best cross platform language is C# and .Net framework. With C# you able to create application on too many platforms same as Windows, Linux, Mac, iOS, Android, Tizen, Web application and so on. But the big problem around C# is huage framework and installtion by customer, this is good to know how you able to have standalone application on diffent platforms.

Must know something about License

First you must know Mono changed the license to MIT at 2016 and it's free to use whatever you want.

What is the mkbundle

mkbundle is part of the Mono project that generates an executable program that will contain static copies of the assemblies listed on the command line. You able to create executions binary for Windows, Mac and Linux.

What is the GTK+ and GTK#

GTK+, or the GIMP Toolkit, is a multi-platform toolkit for creating graphical user interfaces. Offering a complete set of widgets, GTK+ is suitable for projects ranging from small one-off tools to complete application suites.

Gtk# is a .NET language binding for the GTK+ toolkit and assorted GNOME libraries.

What is the Mono.Xwt

Xwt is a new .NET framework for creating desktop applications that run on multiple platforms from the same codebase. Xwt works by exposing one unified API across all environments that is mapped to a set of native controls on each platform.

This means that Xwt tends to focus on providing controls that will work across all platforms. However, that doesn't mean that the functionality available is a common denominator of all platforms. If a specific feature or widget is not available in the native framework of a platform, it will be emulated or implemented as a set of native widgets.

Xwt can be used as a standalone framework to power the entire application or it can be embedded into an existing host. This allows developers to develop their "shell" using native components (for example a Ribbon on Windows, toolbars on Linux) and use Xwt for specific bits of the application, like dialog boxes or cross platform surfaces.

Xwt works by creating an engine at runtime that will map to the underlying platform. These are the engines that are supported on each platform:

  • Windows: WPF engine, Gtk engine (using Gtk#)
  • MacOS X: Cocoa engine (using Xamarin.Mac) and Gtk engine (using Gtk#)
  • Linux: Gtk engine (using Gtk#)

This means for example that you can write code for Xwt on Windows that can be hosted on an existing WPF application (like Visual Studio) or an existing Gtk# application (like MonoDevelop). Or on Mac, you can host Xwt on an existing Cocoa/Xamarin.Mac application or you can host it in our own MonoDevelop IDE.

Linux requirements

  1. Install the gtk-2 development headers first. On Debian, this can be done using: apt-get install libgtk2.0-dev libpango1.0-dev libglade2-dev

  2. Install zlib-devel version from the package manager if you want to compress your binary

NOTE: Better to do it because already you have lot of dependency and better to minimal your code

Download GTK# 2 source code and compile using the code at below:

git clone https://github.com/mono/gtk-sharp.git
cd gtk-sharp
git checkout gtk-sharp-2-12-branch
./bootstrap-2.12

After you success compile progress take a copy of all the .so files

find . -name "*.so"

You must have 5 so files. those file should be copy into the final distribute package.

Create config file for your application

Here we need to map all dll usage in our application to Linux version files for example all the native .dll was used in GTK# should be convert to Linux .so files for example if you have GtkTest.exe you need to create config file GtkTest.exe.config and edit it using vim or text editor

<configuration>
	<dllmap dll="i:cygwin1.dll" target="libc.so.6" os="!windows" />
	<dllmap dll="libc" target="libc.so.6" os="!windows"/>
	<dllmap dll="intl" target="libc.so.6" os="!windows"/>
	<dllmap dll="intl" name="bind_textdomain_codeset" target="libc.so.6" os="solaris"/>
	<dllmap dll="libintl" name="bind_textdomain_codeset" target="libc.so.6" os="solaris"/>
	<dllmap dll="libintl" target="libc.so.6" os="!windows"/>
	<dllmap dll="i:libxslt.dll" target="libxslt.so" os="!windows"/>
	<dllmap dll="i:odbc32.dll" target="libodbc.so" os="!windows"/>
	<dllmap dll="i:odbc32.dll" target="libiodbc.dylib" os="osx"/>
	<dllmap dll="oci" target="libclntsh.so" os="!windows"/>
	<dllmap dll="db2cli" target="libdb2_36.so" os="!windows"/>
	<dllmap dll="MonoPosixHelper" target="libMonoPosixHelper.so" os="!windows" />
	<dllmap dll="i:msvcrt" target="libc.so.6" os="!windows"/>
	<dllmap dll="i:msvcrt.dll" target="libc.so.6" os="!windows"/>
	<dllmap dll="sqlite" target="libsqlite.so.0" os="!windows"/>
	<dllmap dll="sqlite3" target="libsqlite3.so.0" os="!windows"/>
	<dllmap dll="libX11" target="libX11.so.6" os="!windows" />
	<dllmap dll="libgdk-x11-2.0" target="libgdk-x11-2.0.so.0" os="!windows"/>
	<dllmap dll="libgtk-x11-2.0" target="libgtk-x11-2.0.so.0" os="!windows"/>
	<dllmap dll="libXinerama" target="libXinerama.so.1" os="!windows" />
	<dllmap dll="libcairo-2.dll" target="libcairo.so.2" os="!windows"/>
	<dllmap dll="libcairo-2.dll" target="libcairo.2.dylib" os="osx"/>
	<dllmap dll="libcups" target="libcups.so.2" os="!windows"/>
	<dllmap dll="libcups" target="libcups.dylib" os="osx"/>
	<dllmap dll="i:kernel32.dll">
		<dllentry dll="__Internal" name="CopyMemory" target="mono_win32_compat_CopyMemory"/>
		<dllentry dll="__Internal" name="FillMemory" target="mono_win32_compat_FillMemory"/>
		<dllentry dll="__Internal" name="MoveMemory" target="mono_win32_compat_MoveMemory"/>
		<dllentry dll="__Internal" name="ZeroMemory" target="mono_win32_compat_ZeroMemory"/>
	</dllmap>
	<dllmap dll="gdiplus" target="libgdiplus.so" os="!windows"/>
	<dllmap dll="gdiplus.dll" target="libgdiplus.so"  os="!windows"/>
	<dllmap dll="gdi32" target="libgdiplus.so" os="!windows"/>
	<dllmap dll="gdi32.dll" target="libgdiplus.so" os="!windows"/>

	<dllmap os="linux" dll="libglib-2.0-0.dll" target="libglib-2.0.so.0"/>
	<dllmap os="linux" dll="libgobject-2.0-0.dll" target="libgobject-2.0.so.0"/>
	<dllmap os="linux" dll="libgthread-2.0-0.dll" target="libgthread-2.0.so.0"/>
	<dllmap os="linux" dll="glibsharpglue-2" target="libglibsharpglue-2.so"/>
</configuration>

Take a copy of GTK# files to the Release folder

They must be in gac directory /usr/lib/mono/gac/* for example you can find /usr/lib/mono/gac/gtk-sharp/[VERSION]/gtk-sharp.dll From here you need to take a copy for both .dll and .config files. IMPORTANT: you must take a copy of .config files too because they contains dllmap config inside for Linux

Compile and make bundle file

After you compile and test your project successfully you must make a bundle file to be able to run standalone using mkbundle

mkbundle --static GtkTest.exe Xwt.Gtk.dll Xwt.dll atk-sharp.dll gdk-sharp.dll glib-sharp.dll gtk-dotnet.dll gtk-sharp.dll pango-sharp.dll -o GtkTest --deps -z -L /usr/lib/mono/4.5 --config GtkTest.exe.config

Than take a copy of all the config files + .so files together + output files to a same directory and run in another Linux for test.

Done, Now you have a package to run it most linux distro. I had created a binary using Centos and I was able to run on Ubuntu.

How about other OS (Windows, Mac)

For Mac you can simply use Xamarin.Mac and you able to take make a bundle by VisualStudio for Mac And for Windows there is 2 cases.

  1. WPF
  2. GTK+

If you want to use WPF you must install .Net framework on Windows if you use specific .Net framework. But you have automatic system for install Or use GTK+ and Mono with mkbundle, but you end up to some problem(s)

  1. GTK+ for windows only supporting 32bit application
  2. You need to instal GTK+ separately
  3. .Net have better performance on Windows.

So it's much better to use normal .Net framework and WPF for Windows

Prepared files

Already I had compiled GTK2 and GTK# also I had uploaded Compiled version of Xwt Test application for linux.

Prepared files for donwload

For test please run GtkTest directly on your Linux. You can have this test on any minimal Linux distro without Mono runtime

How to Globalization and localization Asp.Net Core application

Creating a multilingual website with ASP.NET Core will allow your site to reach a wider audience. ASP.NET Core provides services and middleware for localizing into different languages and cultures.

In the first step you should install Microsoft.AspNetCore.Localization package from Nuget package manager then add supporting localize to middleware.

/*
   Add supporting localization to Mvc and set Directory of Resources files
   For example all resourcess will be in ~/Resources/Controllers/AboutController.en-GB.resx
*/
services.AddMvc()
    .AddViewLocalization(
        LanguageViewLocationExpanderFormat.Suffix,
        opts => { opts.ResourcesPath = "Resources"; })
    .AddDataAnnotationsLocalization();
services.Configure<RequestLocalizationOptions>(
    opts => {
        var supportedCultures = new List<CultureInfo>
        {
            new CultureInfo("en-GB"),
            new CultureInfo("zh-CN")
        };

        opts.DefaultRequestCulture = new RequestCulture("en-GB");
        // Formatting numbers, dates, etc.
        opts.SupportedCultures = supportedCultures;
        // UI strings that we have localized.
        opts.SupportedUICultures = supportedCultures;

    });

Now Dependency Injection help us to load strings using IStringLocalizer and IStringLocalizer<T>. those class working over ResourceManager and ResourceReader were architected to improve productivity when developing localized apps in ASP.NET Core

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;

namespace Localization.StarterWeb.Controllers
{
    public class AboutController : Controller
    {
        private readonly IStringLocalizer<AboutController> _localizer;

        public AboutController(IStringLocalizer<AboutController> localizer)
        {
            _localizer = localizer;
        }

        [HttpGet]
        public string Index()
        {
            ViewBag.Title = _localizer["About Title"];
            return View();
        }
    }
}
@using Microsoft.AspNetCore.Mvc.Localization

@inject IViewLocalizer Localizer

@{
    ViewData["Title"] = Localizer["About"];
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<p>@Localizer["Use this area to provide additional information."]</p>

In the example at above you need to add 4 files in Resources directory ~/Resources/Controllers/AboutController.en-GB.resx ~/Resources/Controllers/AboutController.zh-CN.resx ~/Resources/Views/About/Index.en-GB.resx ~/Resources/Views/About/Index.zh-CN.resx

NOTE: Don't forget when you add language code suffix to your .resx files, Visual Studio will not generate resource manager for you

Now we able to detect language by providers already exists. To use them just add the providers to RequestCultureProviders

  1. QueryStringRequestCultureProvider
  2. CookieRequestCultureProvider
  3. AcceptLanguageHeaderRequestCultureProvider
  4. CustomRequestCultureProvider

QueryStringRequestCultureProvider

Some apps will use a query string to set the culture and UI culture. For apps that use the cookie or Accept-Language header approach, adding a query string to the URL is useful for debugging and testing code. By default, the QueryStringRequestCultureProvider is registered as the first localization provider in the RequestCultureProvider list. You pass the query string parameters culture and ui-culture. The following example sets the specific culture (language and region) to Spanish/Mexico: http://localhost:5000/?culture=es-MX&ui-culture=es-MX

For change the query string key you need to set QueryStringKey when you want to init QueryStringRequestCultureProvider object

/*
   At example below your URL will be looks as this:
   http://localhost:5000/?lang=es-MX&ui-lang=es-MX
*/
opts.RequestCultureProviders.Add(new QueryStringRequestCultureProvider() { QueryStringKey = "lang", UIQueryStringKey = "ui-lang" });

CookieRequestCultureProvider

Production apps will often provide a mechanism to set the culture with the ASP.NET Core culture cookie. Use the MakeCookieValue method to create a cookie.

The CookieRequestCultureProvider DefaultCookieName returns the default cookie name used to track the user’s preferred culture information. The default cookie name is ".AspNetCore.Culture".

The cookie format is c=%LANGCODE%|uic=%LANGCODE%, where c is Culture and uic is UICulture, for example:

c='en-UK'|uic='en-US'
How to set the culture programmatically

First create a Partial View for language selector

@using Microsoft.AspNetCore.Builder
@using Microsoft.AspNetCore.Http.Features
@using Microsoft.AspNetCore.Localization
@using Microsoft.AspNetCore.Mvc.Localization
@using Microsoft.Extensions.Options

@inject IViewLocalizer Localizer
@inject IOptions<RequestLocalizationOptions> LocOptions

@{
    var requestCulture = Context.Features.Get<IRequestCultureFeature>();
    var cultureItems = LocOptions.Value.SupportedUICultures
        .Select(c => new SelectListItem { Value = c.Name, Text = c.DisplayName })
        .ToList();
}

<div title="@Localizer["Request culture provider:"] @requestCulture?.Provider?.GetType().Name">
    <form id="selectLanguage" asp-controller="Home" 
          asp-action="SetLanguage" asp-route-returnUrl="@Context.Request.Path" 
          method="post" class="form-horizontal" role="form">
        @Localizer["Language:"] <select name="culture"
          onchange="this.form.submit();"
          asp-for="@requestCulture.RequestCulture.UICulture.Name" asp-items="cultureItems">
        </select>
    </form>
</div>

After that use it everywhere you like following example at below:

@await Html.PartialAsync("_SelectLanguagePartial")

Then write the Action in your controller for change the language using Cookie provider

[HttpPost]
public IActionResult SetLanguage(string culture, string returnUrl)
{
    Response.Cookies.Append(
        CookieRequestCultureProvider.DefaultCookieName,
        CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
        new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }
    );

    return LocalRedirect(returnUrl);
}

AcceptLanguageHeaderRequestCultureProvider

The Accept-Language header is settable in most browsers and was originally intended to specify the user's language. This setting indicates what the browser has been set to send or has inherited from the underlying operating system. The Accept-Language HTTP header from a browser request is not an infallible way to detect the user's preferred language (see Accept-Language header). A production app should include a way for a user to customize their choice of culture.

For automatic select language using browser accept list you can add this provider to detect language by each request from user.

CustomRequestCultureProvider

Suppose you want to let your customers store their language and culture in your databases. You could write a provider to look up these values for the user. The following code shows how to add a custom provider

For select culture as custom way for example what we did in DotNetBlog you can take a looks to the source code or see the example below. In this way we can select the language in Admin page and force the user to see the web site by our selected language. It's very useful for Weblog, Forum, CMS,... and application to manage the content.

opts.RequestCultureProviders.Insert(0, new CustomRequestCultureProvider(context => {
    var settingService = context.RequestServices.GetService<Core.Service.SettingService>();
    // My custom request culture logic
    return Task.Run(() => new ProviderCultureResult(settingService.Get().Language));
}));

**Refrences: **

Microsoft Globalization and localization

RFC 4646

Table of Language Culture Names, Codes, and ISO

Dot net core Weblog Engine

I working to make localize a Weblog engine and this is my first release.

https://github.com/scheshan/DotNetBlog

This weblog have 2 parts: Web, Admin Admin part was developed base on ReactJS and I used ReactJS to localize this project.

Also we start to make a plan for keep updating this application. So if you have any idea about contributing and add new feature you can send us anything you have in your mind to us ISSUE LIST

So if you looking for a fast and tiny weblog you can download and install it from the link below

https://github.com/scheshan/DotNetBlog

Special thanks to @scheshan for develop this application 🙏

  • After the article
  • 1
  • Before the article