X
dnn.blog.
Back

Rapid Module Development Part 2 - The multilanguage thing…

Since I get to know DNN back in 2008, one subject grinds my gears all the time and this is the multilanguage feature of modules. Back In 2008/2009 there were a lot of rumors and discussions about ML. The corp started with building their multilanguage solution and I remember some intense discussion with Sebastian and others about this topic. At the end the corp solution does not help me building my ML modules and so I had to find my own way to handle this.

In this blog I’ll show you how you can implement ML features without any hassle. My method is very straight forward and you could use this as a pattern for all your modules. There is no more increased effort needed to make your module ML compliant if you follow these guidelines.

The sample module

We want to build a product module in this tutorial. To keep it simple, we bind the product to the module: Every module shows only his module-specific product. In the action menu, we’ll have the option to edit the product. All text properties of the product should be ML capable.

Database Design

It is good practise to prefix your table names with something. My table names  start with the module name as prefix followed by an underline and the real table name. In this sample we have a product table with some fields that are language independent and a compagnion table with the same name and the Suffix **Lang **that contain all the language dependent fields. As the third part in this game we define an SQL View containing all the fields joined from both tables for display purposes.

Database Design

After the design phase  I let my favourite tool XCase  generate the needed SQL code to create everything in SQL server:

Generated SQL

IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'{databaseOwner}[{objectQualifier}BBLanguagePattern_Product]') and OBJECTPROPERTY(id, N'IsTable') = 1)
   BEGIN
      CREATE TABLE {databaseOwner}[{objectQualifier}BBLanguagePattern_Product] ( 
         ProductId INT NOT NULL IDENTITY (1,1),
         ModuleId INT NULL,
         Image NVARCHAR(120) NULL,
         Price DECIMAL(12,4) NULL,
         Tax CHAR(10) NULL
      )
      ALTER TABLE {databaseOwner}[{objectQualifier}BBLanguagePattern_Product] ADD CONSTRAINT PK_BBLanguagePattern_Product PRIMARY KEY NONCLUSTERED  (ProductId ASC) WITH ( IGNORE_DUP_KEY = OFF)
   END
GO
IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'{databaseOwner}[{objectQualifier}BBLanguagePattern_ProductLang]') and OBJECTPROPERTY(id, N'IsTable') = 1)
   BEGIN
      CREATE TABLE {databaseOwner}[{objectQualifier}BBLanguagePattern_ProductLang] ( 
         ProductId INT NULL,
         Language CHAR(5) NULL,
         Name NVARCHAR(40) NULL,
         Shortdescription NVARCHAR(400) NULL,
         Longdescription NVARCHAR(MAX) NULL
      )
   END
GO
IF NOT EXISTS (SELECT 1 FROM sys.objects where name='FK_ProductLang' and type='F')
   ALTER TABLE {databaseOwner}[{objectQualifier}BBLanguagePattern_ProductLang] WITH NOCHECK ADD CONSTRAINT FK_ProductLang FOREIGN KEY ( ProductId ) REFERENCES {databaseOwner}[{objectQualifier}BBLanguagePattern_Product] ( ProductId ) ON DELETE CASCADE
GO
CREATE VIEW BBLanguagePattern_ProductLoc AS
SELECT  ALL    BBLanguagePattern_Product.ProductId , BBLanguagePattern_Product.ModuleId ,  
      BBLanguagePattern_Product.Image , BBLanguagePattern_Product.Tax ,  
      BBLanguagePattern_Product.Price , ProductLang.Language , ProductLang.Name ,  
      ProductLang.Shortdescription , ProductLang.Longdescription 
   FROM BBLanguagePattern_Product INNER JOIN BBLanguagePattern_ProductLang ProductLang ON 
     BBLanguagePattern_Product.ProductId = ProductLang.ProductId
GO

I paste this code into the host/SQL window of DNN and let it create the tables and the view.

Setting up the project

Within Visual studio we create a new project depending on my Bitboxx DNN 7 project template. Project name is equal to the prefix of my database tables – BBLanguagePattern in this case. If the prefixes differ from the project name, you have to edit the file Models/Generated/Database.tt and alter the value in prefix.

// Settings
ConnectionStringName = "SiteSqlServer";            // Uses last connection string in config if not specified
Namespace = "Bitboxx.DNNModules.BBLanguagePattern";
RepoName = ".";
GenerateOperations = false;
GeneratePocos = true;
GenerateCommon = false;
ClassPrefix = "";
ClassSuffix = "";
TrackModifiedColumns = false;
IncludeViews = true;
// Read schema
Tables tables = LoadTables();
string prefix = "BBLanguagePattern";

If everything is OK, saving the Database.tt file now generates Database.cs containing all the needed POCO’s for our tables and for the view:

using System;
using System.Web.Caching;
using Bitboxx.DNNModules.Controls;
using DotNetNuke.ComponentModel.DataAnnotations;
namespace Bitboxx.DNNModules.BBLanguagePattern
{
    [TableName("BBLanguagePattern_Product")]
    [PrimaryKey("ProductId")]
	[Cacheable("BBLanguagePattern_Product", CacheItemPriority.Normal, 20)]
    public partial class ProductInfo     
	{
        public int ProductId { get; set; }
        public string Image { get; set; }
        public decimal? Price { get; set; }
        public string Tax { get; set; }
        public int? ModuleId { get; set; }
    }
	[Serializable]
    [TableName("BBLanguagePattern_ProductLang")]
	[Cacheable("BBLanguagePattern_ProductLang", CacheItemPriority.Normal, 20)]
    public partial class ProductLangInfo : ILanguageEditorInfo     
	{
        public int? ProductId { get; set; }
        public string Language { get; set; }
		[LanguageEditor("TextBox" , MaxLength = 40)]
        public string Name { get; set; }
		[LanguageEditorAttribute("TextBox" , MaxLength = 400)]
        public string Shortdescription { get; set; }
		[LanguageEditorAttribute("TextEditor", Height = "600px")]
        public string Longdescription { get; set; }
    }
    [TableName("BBLanguagePattern_ProductLoc")]
	[Cacheable("BBLanguagePattern_ProductLoc", CacheItemPriority.Normal, 20)]
    public partial class ProductLocInfo     
	{
        public int ProductId { get; set; }
        public int? ModuleId { get; set; }
        public string Image { get; set; }
        public string Tax { get; set; }
        public decimal? Price { get; set; }
        public string Language { get; set; }
        public string Name { get; set; }
        public string Shortdescription { get; set; }
        public string Longdescription { get; set; }
    }
}

Please take a look at the POCO of the ~Lang table. This one is inherited from the ILanguageEditorInfo, an interface specifically for the LanguageEditor to work later. I tweaked the PetaPoco tt files to generate this automatically if we have a table and its Lang counterpart. Also the string properties in the ~Lang POCO now have attributes defining which type of editor control is used to edit (TextBox,TextEditor) and some other properties (MaxLength,Width, Height,Rows,Label).

The LanguageEditor Control

Visual Studio does not know the attributes and the interface at the moment, so now we have to add the Language Editor Control to the project. The easiest way to do this is to download it from here and extract all files into a new project folder named Controls. Then open this folder in Windows Explorer, mark all files insides and do a drag&drop action into the project:

Language Editor Control

The View.ascx

In this part of the Rapid Module Development Blog we will only have a fixed display of our product. The templating will be part of another blog post introducing the template control.

Thats how it should look alike:

View.ascx

The ascx-code is simple:

<div id="bblanguagepattern-view">
    <asp:Image runat="server" ID="imgProduct"/>
    <h3><asp:Label runat="server" ID="lblName"/></h3>
    <p><strong><asp:Label runat="server" ID="lblShortDescription"/></strong></p>
    <p><asp:Label runat="server" ID="lblLongDescription"/></p>
    <div style="float:right;text-align:right;background-color:;padding:40px 20px;;">
        <span class="price"><asp:Label runat="server" ID="lblPrice"/></span><br/> 
        <asp:Label runat="server" ID="lblTax"/>
    </div>
</div>

Lets have a look at the code behind:

public partial class View : PortalModuleBase, IActionable
{
    #region Event Handlers
    /// <summary>
    /// Runs when the control is loaded
    /// </summary>
    private void Page_Load(object sender, EventArgs e)
    {
        try
        {
            if (!Page.IsPostBack)
            {
                BBLanguagePatternController controller = new BBLanguagePatternController();
                ProductLocInfo product = controller.GetProductLoc(ModuleId, Thread.CurrentThread.CurrentCulture.Name);
                lblName.Text = product.Name;
                lblShortDescription.Text = product.Shortdescription;
                lblLongDescription.Text = product.Longdescription;
                lblPrice.Text = String.Format("{0:c}",product.Price);
                lblTax.Text = String.Format(LocalizeString("lblTax.Text"), product.Tax);
                imgProduct.ImageUrl = PortalSettings.HomeDirectory + product.Image;
            }
        }
        catch (Exception exc) 
        {
            Exceptions.ProcessModuleLoadException(this, exc);
        }
    }
    #endregion
    #region Interfaces
    public ModuleActionCollection ModuleActions
    {
        get
        {
            ModuleActionCollection Actions = new ModuleActionCollection();
            Actions.Add(GetNextActionID(), Localization.GetString("EditProduct.Action", LocalResourceFile), ModuleActionType.EditContent, "", "edit.gif", EditUrl(), false, SecurityAccessLevel.Edit, true, false);
            return Actions;
        }
    }
    #endregion
}

All we have to do here is instantiating the Controller (BBLanguagePatternController class was built by the project template), retrieve the corresponding ProductLocInfo object (this is the view data for the current UI language and the current moduleId, created with the help of the CRUD-Generator (see first blog part), or coded manually)

public ProductLocInfo GetProductLoc(int moduleId, string language)
{
    using (IDataContext context = DataContext.Instance())
    {
        var repository = context.GetRepository<ProductLocInfo>();
        return repository.Find("WHERE ModuleId = @0 AND Language = @1", moduleId, language).FirstOrDefault();
     }
}

Binding the labels to the ProductLocInfo properties is standard work and we are ready to go!

The Edit.ascx

This is the exciting part. First lets have a look at the UI:

Edit.ascx

So how is this done ? The ascx-code is fairly simple too. We need to register the Language editor control (and the dnn filepicker control for the image) and use them later in the code. To combine the Control with the data to edit, we reference our ProductLangInfo class (containing the LanguageEditor attributes) in the LanguageEditor tag (InternalType=”…”) :

<%@ Control language="C#" Inherits="Bitboxx.DNNModules.BBLanguagePattern.Edit" AutoEventWireup="true" Codebehind="Edit.ascx.cs" %>
<%@ Register TagPrefix="dnn" TagName="Label" Src="~/controls/LabelControl.ascx" %>
<%@ Register TagPrefix="bb" TagName="LanguageEditor" Src="Controls/LanguageEditorControl.ascx" %>
<%@ Register TagPrefix="dnn" TagName="FilePickerUploader" Src="~/controls/filepickeruploader.ascx" %>
<div class="dnnForm bblanguagepattern-productedit dnnClear" id="bblanguagepattern-edit">
    <fieldset>
        <asp:HiddenField ID="hidProductId" runat="server" />
        <div class="dnnFormItem">
            <dnn:Label ID="lblImage" runat="server" ControlName="ctlImage"  Suffix=":"/>
            <dnn:FilePickerUploader ID="ctlImage" runat="server" Required="True" />
        </div>
        <div class="dnnFormItem">
            <dnn:Label ID="lblPrice" runat="server" ControlName="txtPrice"  Suffix=":"/>
            <asp:TextBox ID="txtPrice" runat="server" MaxLength="12" />
        </div>
        <div class="dnnFormItem">
            <dnn:Label ID="lblTax" runat="server" ControlName="txtTax"  Suffix=":"/>
            <asp:TextBox ID="txtTax" runat="server" MaxLength="10" />
        </div>
        <bb:LanguageEditor ID="lngProduct" runat="server" InternalType="Bitboxx.DNNModules.BBLanguagePattern.ProductLangInfo" />
    </fieldset>
    <ul class="dnnActions dnnClear">
        <li><asp:LinkButton CssClass="dnnPrimaryAction" ID="cmdUpdate" runat="server" resourcekey="cmdUpdate" OnClick="cmdUpdate_Click" /></li>
        <li><asp:LinkButton CssClass="dnnSecondaryAction" ID="cmdCancel" runat="server" resourcekey="cmdCancel" OnClick="cmdCancel_Click" CausesValidation="false"/></li>
    </ul>
</div>

At Load event we retrieve the corresponding product and set the values for the textboxes, the filepicker and our LanguageEditor. (The controller method to read the ProductLangs and all other DAL2 methods could also be generated with CRUD-Generator or coded manually)

_product = Controller.GetProductLoc(ModuleId, CurrentLanguage);
if (_product != null)
{
    hidProductId.Value = _product.ProductId.ToString();
    if (!IsPostBack)
    {
        ctlImage.FilePath = _product.Image;
        txtPrice.Text = _product.Price.ToString();
        txtTax.Text = _product.Tax;
        var dbLangs = new List<ILanguageEditorInfo>();
        lngProduct.Langs.Clear();
        foreach (ProductLangInfo productLang in Controller.GetProductLangs(_product.ProductId))
        {
            dbLangs.Add(productLang);
        }
        lngProduct.Langs = dbLangs;
    }
}

In cmdUpdate_Click we read out all values and save them back to the database:

int productId = Convert.ToInt32(hidProductId.Value);
ProductInfo product = new ProductInfo();
product.ProductId = productId;
product.Image = ctlImage.FilePath.Replace("//", "/");
product.Price = Convert.ToDecimal(txtPrice.Text.TrimEnd());
product.Tax = txtTax.Text.TrimEnd();
product.ModuleId = ModuleId;
if (productId > -1)
    Controller.UpdateProduct(product);
else
    productId = Controller.InsertProduct(product);
// Now lets update Language information
lngProduct.UpdateLangs();
Controller.DeleteProductLangs(productId);
foreach (ProductLangInfo lang in lngProduct.Langs)
{
    lang.ProductId = productId;
    Controller.InsertProductLang(lang);
}
Response.Redirect(Globals.NavigateURL(TabId),true);

Summary

With the LanguageEditor control, the help of the CRUD-Generator and the tuned PetaPoco tt files there are no more excuses to write only single language modules. Feel free to download the sample module, test it and ask questions if something is not working.

Download Sample project

Download CRUD Generator

Back
Total: 15 Comment(s)
mariakim
Interview on TV isn't an easy thing to do because someone doing cross questioning with you and you'll do that answer immediately without https://www.essaysolution.co.uk any hesitation. If someone going out from this process so they got a good confidence later.
Saturday, December 16, 2017 · reply ·
susanrichard
Thanks very much for the excellent tutorial. Your efforts are very appreciated. I hope you get something out of it besides adoration from grateful coders! How about a quick posting on https://www.assignmenthelperuk.co.uk
Monday, December 18, 2017 · reply ·
Dubai Offshore Company Formation
Amazing article thanks or sharing..
Thursday, February 1, 2018 · reply ·
Dubai Offshore Company Formation
Dissertation Guidance Provides quality Online Dissertation Help for students. https://www.uaefreezone.xyz/">Dubai Offshore Company Formation
Thursday, February 1, 2018 · reply ·
nancystark
Influencing the more tips on this blog for the one month with mo, to keep it up. All are getting splendid spent testing the performed well with the better attempting truly influencing the dropping his to hold up under. I may need to thank you for the undertakings you have made in making this article. Influencing the more tips on this blog for the one month with mo, to keep it up. All are getting splendid spent testing the performed well with the better attempting truly influencing the dropping his to hold up under. I may need to thank you for the undertakings you have made in making this article. https://www.assignmentsolution.co.uk/write-my-assignment
Thursday, February 8, 2018 · reply ·
asd
Tuesday, February 27, 2018 · reply ·
gracia
Klinik Utama Gracia merupakan salah satu klinik terbesar di jakarta saat ini. Dengan peralatan medis yang lengkap dan modern, Klinik Utama Gracia siap bersaing dengan klnik dan rumah sakit lainnya di dalam maupun di luar jakarta. http://klinikutama.com/pengertian-penyakit-gonore-10224.html
Wednesday, April 4, 2018 · reply ·
VB Assignment Help
I’m really impressed with your article, such great & usefull knowledge you mentioned here http://vbhelponline.com/
Wednesday, April 11, 2018 · reply ·
John Martin
only professional writers can make this kind of material, cheers Case-Study-Solutions.com https://www.case-study-solutions.com/
Wednesday, April 11, 2018 · reply ·
Solidworks Assignment Help
This a good way to appreciate the teacher as they put their efforts to train students. UK dissertation Writers appreciates the teachers. Solidworks Assignment Help https://solidworksaid.com/
Wednesday, April 11, 2018 · reply ·
AutoCad Help
Great Information,it has lot for stuff which is informative.I will share the post with my friends. AutoCad Help http://www.autocadhelp.net/
Wednesday, April 11, 2018 · reply ·
MBA Assignment Help
They are not able to finish the writing assignments on time. For some students, writing any writing assignments is able to waste their time MBA Assignment Help http://assignmentsmba.com/
Wednesday, April 11, 2018 · reply ·
Programming Assignment Help
This is really a great stuff for sharing. Thanks for sharing. Programming Assignment Help https://www.assignmentsprogramming.xyz/
Wednesday, April 11, 2018 · reply ·
CaseGurus.com
I personally like your post; you have shared good insights and experiences. Keep it CaseGurus.com up. http://casegurus.com/
Wednesday, April 11, 2018 · reply ·
Caseism.com
Hi buddy, your blog' s design is simple and clean and i like it. Your blog posts about Online writing Help are superb. Please keep them coming. Caseism.com Greets! https://caseism.com
Wednesday, April 11, 2018 · reply ·

about.me.

Torsten WeggenMy name is Torsten Weggen and I am CEO of indisoftware GmbH in Hanover, Germany. I'm into DNN since 2008. Before this, I did a lot of desktop stuff mainly coded with Visual Foxpro (see http://www.auktionsbuddy.de). 

I'm programmer, husband, father + born in 1965.

Please feel free to contact me if you have questions.

Latest Posts

DNN module development with AngularJS (Part 6)
12/16/2016 7:00 AM | Torsten Weggen
DNN module development with AngularJS (Part 5)
12/16/2016 6:00 AM | Torsten Weggen
DNN module development with AngularJS (Part 4)
12/16/2016 5:00 AM | Torsten Weggen
DNN module development with AngularJS (Part 3)
12/16/2016 4:00 AM | Torsten Weggen
DNN module development with AngularJS (Part 2)
12/16/2016 3:00 AM | Torsten Weggen
DNN module development with AngularJS (Part 1)
12/15/2016 7:19 AM | Torsten Weggen
Blogging in DNN with Markdown Monster by Rick Strahl
11/27/2016 1:14 PM | Torsten Weggen
Creating a global token engine
11/18/2016 10:25 AM | Torsten Weggen
DnnImagehandler - Hot or not ?
2/21/2015 11:52 PM | Torsten Weggen
Rapid Module Development Part 2 - The multilanguage thing…
4/7/2014 7:32 PM | Torsten Weggen

My Twitter

Torsten Weggen 3/1/2018

Yeah! Booked my journey to #DNNConnect2018 ! Will be great to meet old friends again and have some Guiness together! #DNNCMS rulez !

Torsten Weggen 2/17/2018

Yess! My first commercial hybrid mobile App build with #ionic + #Angular and connected with #DNNCMS is online now https://t.co/61nDXYXu14

Torsten Weggen 1/13/2018

@TChailland Hi Thomas - no problem! Have a nice vacation and let me know when you're back.