How to Create Your Own Membership Provider Instead of Using SqlMembershipProvider

So you want to use the ASP.NET SQL Membership authentification system but you’re constrained by data access rules. Well you can create your own provider instead of using the default SqlMembershipProvider, no matter which way you access selected data.

For example, you are forced to use a webservice, or the database of users already exists. The following solution will allow you to handle such cases instead of appealing to SqlMembershipProvider.

Step 1: Identify data to be managed

Let’s consider the following data model, which we will assume is the data contract provided by a webservice:

   
public class User
{
	public long UserId { get; set; }
	public string FirstName { get; set; }
	public string LastName { get; set; }
	public string Email { get; set; }
	public string Password { get; set; }
	public string Address { get; set; }
	public string City { get; set; }
	public string ZipCode { get; set; }
	public string Country { get; set; }
	public bool IsActive { get; set; }
	public DateTime CreationDate { get; set; }
	public DateTime LastLoginDate { get; set; }
}

Let’s also consider the data access service, which contains this signature:

    
public class AuthenticationService : IDisposable
{
	public bool UserExists(string email, string password)
	{
		//implementation here
	}

	public User GetUser(string username)
	{
		//implementation here
	}

	public User GetUser(long userId)
	{
		//implementation here
	}

	public bool UpdateUser(User user)
	{
		//implementation here
	}

	public void Dispose()
	{
		//implementation here
	}
}

Step 2: Create a custom MembershipUser derived from the original MembershipUser​

As can be seen, this class has the contract “User” as a property.

    
public class CustomMemberShipUser : MembershipUser
{
	private readonly User _userData;

	public User UserData
	{
		get { return _userData; }
	}

	
	/// 
	/// Constructeur de la classe derivée du MemberShip
	/// 
	public CustomMemberShipUser(string providername, User userData) :
	base(providername,
		 userData.Email,
		 userData.UserId,
		 userData.Email,
		 string.Empty,
		 string.Empty,
		 true,
		 !userData.IsActive,
		 userData.CreationDate,
		 userData.LastLoginDate,
		 DateTime.Now,
		 DateTime.Now,
		 DateTime.Now)
	{
		this._userData = userData;
	}
}

Note that you will need to import “System.Web.Security” assembly.

I have used some data in the “User” contract to fill in required fields in the base class.

I have also decided that the username is the email “!userData.IsActive”, filled into the “isLockedOut” property in the base class.

Step 3: Create a custom MembershipProvider derived from the original MembershipProvider​

Note that you must implement all methods. Otherwise, you’ll need to create an override method and add in “throw new NotImplementedException();”.

    
public class CustomMemberShipProvider : MembershipProvider
{
	public override bool ValidateUser(string username, string password)
	{
		using (var service = new AuthenticationService())
		{
			return service.UserExists(username, password);
		}
	}

	public override MembershipUser GetUser(string username, bool userIsOnline)
	{
		using (var service = new AuthenticationService())
		{
			var user = service.GetUser(username);

			if (null != user)
				return new CustomMemberShipUser(Membership.Provider.ApplicationName, user);

			return null;
		}
	}

	public override void UpdateUser(MembershipUser userToUpdate)
	{
		var user = (CustomMemberShipUser) userToUpdate;
		using (var service = new AuthenticationService())
		{
			var result = service.UpdateUser(user.UserData);
			if (!result)
				throw new Exception("User has not been updated");
		}

	}

	public override string ApplicationName
	{
		get { return "MyAppMemberShip"; }
		set { throw new NotImplementedException(); }
	}

	public override bool ChangePassword(string username, string oldPassword, string newPassword)
	{
		throw new NotImplementedException();
	}

	///
	/// 
	/// all overrrided methods
	/// 
	/// 

}

Step 4: Define this custom MembershipProvider as the default Membership provider in your web.config as follows:​

<system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
    <authentication mode="Forms">
      <forms loginUrl="~/Home/DoLogin" timeout="2880"/>
    </authentication>
    <membership defaultProvider="MyAppMemberShip">
      <providers>
        <clear/>
        <add name="MyAppMemberShip" type="MemberShip.CustomMemberShipProvider, MemberShip"/>
      </providers>
    </membership>
</system.web>

Note that “MemberShip.CustomMemberShipProvider, MemberShip” is my assembly where i have written my provider

Step 5: As the classical SqlMembershipProvider, add the authentication mode (with the login page url) to your web.config: ​

<system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
    <authentication mode="Forms">
      <forms loginUrl="~/Home/DoLogin"/>
    </authentication>
    <membership defaultProvider="MyAppMemberShip">
      <providers>
        <clear/>
        <add name="MyAppMemberShip" type="MemberShip.CustomMemberShipProvider, MemberShip"/>
      </providers>
    </membership>
</system.web>

Step 6: Test your implementation!

Don’t forget to use the “FormsAuthentication” class in order to create the “.ASPXAUTH” cookie that is used by MembershipProvider to identify the user and manage the sign-out method. 🙂

    
public class HomeController : Controller
{
	public ActionResult DoLogin()
	{
		if (Membership.ValidateUser("myemail@gmail.com", "xxxxx"))
		{
			FormsAuthentication.SetAuthCookie("myemail@gmail.com", true);
			return Content("login success");
		}
		return Content("login error");
	}

	public ActionResult Index()
	{
		if (User.Identity.IsAuthenticated)
		{
			var user = (CustomMemberShipUser)Membership.GetUser();
			return Content("User connected!");
		}

		return RedirectToAction("DoLogin");
	}

	public void SignOut()
	{
		FormsAuthentication.SignOut();
	}

}

Here are the results after my demo execution:

capture_1_0.png

capture_2.png

Easy, isn’t it? 😉

How to Load an HTML View Other Than With an MVC View File?

Have you ever asked yourself if it was possible to display some HTML content in an ASP.NET MVC application, using another way than through a traditional MVC view? This article will cover that exactly.

The principle is to short circuit the views resolution mechanism.

Step 1 : Override the “VirtualPathProvider” class

We’ll override “FileExists” and “GetFile” methods and then create filters that will detect the views to short circuit.

In this example, I have created a private member “filters” that defines the list of the views we want to “catch” and load them using another way.

Then, we override “FileExists” and “GetFile” methods to catch our filters.

It’s necessary to simulate a “real” path for our views we want to catch, that’s what “PathFormatter”will do.

Finally, “GetViewBytesData” will load the short circuited content and send it in to another overrided class : “CustomVirtualFile”.

You can implement this method as you like, and load different views from anywhere (flat file, webservice, database…)

public class CustomVirtualPathProvider : VirtualPathProvider 
    {
        private static List filters = new List()
        {
            PathFormatter("PhantomView")
        };
               
        public override bool FileExists(string virtualPath)
        {
            if (!filters.Contains(virtualPath))
                return base.FileExists(virtualPath);
            return true;
        }
      
         public override VirtualFile GetFile(string virtualPath)
         {
            if(!filters.Contains(virtualPath))
                return base.GetFile(virtualPath);
            return new CustomVirtualFile(virtualPath, GetViewBytesData());
        }
       
        private byte[] GetViewBytesData()
        {
            string htmlBody = @"@{ Layout = null;}
                        <hgroup>  
                            <h1>@ViewBag.Title</h1>
                            <h2>@ViewBag.Message</h2>
 
                            <div>Message:      @Model.Message         </div>
                        </hgroup>";
            return Encoding.ASCII.GetBytes(htmlBody);
        }

        public static string PathFormatter(string viewName)
        {
            return string.Format(@"/Views/Shared/{0}.cshtml",viewName);
        }
    }

Step 2 : Implement a custom VirtualFile class derived from the abstract class “VirtualFile”

By implementing “Open” methods, this class allows you to inject content from anywhere instead of the classical MVC view approach:

public class CustomVirtualFile : VirtualFile
    {
        private readonly byte[] _data;

        public CustomVirtualFile(string virtualPath, byte[] data)
            : base(virtualPath)
        {
            _data = data;
        }

        public override Stream Open()
        {
            return new MemoryStream(_data);
        }
    }

Step 3 : Register our own VirtualPathProvider into the Global.asax file

protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);

            HostingEnvironment.RegisterVirtualPathProvider(new CustomVirtualPathProvider());
        }

Step 4 : Create the folder “Shared” if it doesn’t exist (by default, it should becreated automatically when you create a new MVC project)

Step 5 : Create a controller and an action that render a “fake” view

public class PhantomController : Controller
    {
        public ActionResult Index()
        {
            ViewData["Title"] = "The phantom view";
            ViewBag.Message = "Welcome into the phantom view!!!!";

            var model = new PhantomModel
            {
                Message = "Ouuuuh I don't exist!"
            };

            return View(CustomVirtualPathProvider.PathFormatter("PhantomView"), model);
        }
    }

As you can see, I’m using a classical model, ViewBag and and the ViewData.

Remember, the content of our “fake” view is defined in the “GetViewBytesData” on step 1

Step 6 : Test it!

phantom.png

Funny isn’t it? 🙂

How to Easily Manage Different Languages ​​on a Website: a Complete Tutorial With ASP.NET MVC

As information technology becomes an ever-larger part of our lives, it is becoming ever easier to access Internet content from all over the world, and developers are increasingly creating multilingual sites to reach a wide audience.

The following is a tutorial to easily and efficiently manage ASP.NET MVC web sites in multiple languages.

I use XML files as source files in order to avoid being beholden to a database. Then, I create a service that implements an interface to be able to easily change the implementation at need.

With this service, I am able to:

– Retrieve resources for building dynamic HTML controls (code behind side).
– Create HtmlHelpers providing access to these resources.
– Create attributes on models to translate label form fields.

Finally, I create an action filter to access the desired resource.

Step 1: Define XML source file(s)

I recommend creating a specific directory in “App_GlobalResources”, for example “XmlResources”.

For the purposes of translating the homepage of our website, let’s create the Home.xml file to differentiate the resources on this page from others. Let’s say it contains two resources:

<?xml version="1.0" encoding="utf-8" ?>
 <Resources>
  <Resource key="HelloWordKey">
   <Language key="EN">Hello World!</Language>
   <Language key="FR">Bonjour le monde!</Language>
  </Resource>
  <Resource key="MyNameKey">
   <Language key="EN">My name is Anthony</Language>
   <Language key="FR">Mon prénom est Anthony</Language>
  </Resource>
  <Resource key="EnterYourNationalityKey">
   <Language key="EN">What's your nationality</Language>
   <Language key="FR">De quelle nationalité êtes-vous?</Language>
  </Resource>
</Resources>

Step 2 : Create a Specific Reader for This(ese) File(s)

using System.Collections.Generic; 
using System.IO; 
using System.Linq; 
using System.Xml.Linq; 
using MyApp.Tools; 

namespace MyApp.Services 
{ 
   public static class ResourceXmlReader 
   { 
       //static property, public readable only 
       public static readonly Dictionary>> Resources = new Dictionary>>(); 

       //static constructor 
       static ResourceXmlReader() 
       { 
           try { 
           string path = System.Web.Hosting.HostingEnvironment.MapPath("~/App_GlobalResources/XmlResources/"); 
           FolderContentBrowser content = new FolderContentBrowser(path); 
           LoopOnResources(content.FileNameList); 
           } 
           catch { } 
       } 

       //Browse each xml resource file on the current directory 
       private static void LoopOnResources(List fileList) 
       { 
          fileList.Where(o => o.EndsWith(".xml")).ToList().ForEach(o => OpenAndStoreResource(o)); 
       } 

       //Open, read and store into the static property xml file 
       private static void OpenAndStoreResource(string resourcePath) 
       { 
          try { 
          string fileName = Path.GetFileName(resourcePath).Split('.')[0]; 
          XDocument doc = XDocument.Load(resourcePath); 
          if (null != doc) { 
             Dictionary> currentResource = new Dictionary>(); 
             var resources = doc.Descendants("Resource").ToList(); 
             resources.ForEach(o => currentResource.Add(o.Attribute("key").Value, getEachLanguage(o.Elements("Language")))); 

             //attachement des resources à une ressource nommée 
             Resources.Add(fileName, currentResource); 
          } 
          } 
          catch { } 
        } 

        //Loop on each language into the file 
        private static Dictionary getEachLanguage(IEnumerable elements) 
        { 
           Dictionary langList = new Dictionary(); 
           elements.ToList().ForEach(o => langList.Add(o.Attribute("key").Value, o.Value)); 
           return langList; 
        } 
     } 
}

I use a static constructor because it will be executed just one time. I read xml files just onceand store them in my static property. This is the secret of performance management system languages. I do not read the xml files for each page load.

Note that the reader developed a string of dictionaries. The data is arranged as follows: Name of dictionary xml file (one for each page), containing a dictionary, in turn containing language, itself a resource dictionary (key resources, textual value of the resource).

Step 3: Create a Service That Implements an Interface For Managing Access to Resources

using System;
using System.Collections.Generic;
namespace MyApp.Globalization
{
   public interface IResourceService
   {
      string GetResource(string resourceName, string resourceKey);
      Dictionary> GetRessourcesByName(string resourceName);
   }
}
using MyApp.Services;
using System.Collections.Generic;
using System.Globalization;

namespace MyApp.Globalization
{
   public class ResourceService : IResourceService
   {
      public string GetResource(string resourceName, string resourceKey)
      {
         try {
            string language = CultureInfo.CurrentCulture.TwoLetterISOLanguageName.ToUpper();
            if (ResourceXmlReader.Resources.ContainsKey(resourceName)) {
               if (ResourceXmlReader.Resources[resourceName].ContainsKey(resourceKey)) {
                  if (ResourceXmlReader.Resources[resourceName][resourceKey].ContainsKey(language))
                     return ResourceXmlReader.Resources[resourceName][resourceKey][language];
                  else
                     return ResourceXmlReader.Resources[resourceName][resourceKey]["EN"];
               }
               else
                  return string.Empty;
            }
            else return string.Empty;
            }
        catch { return string.Empty; }
      } 

      public Dictionary> GetRessourcesByName(string resourceName)
      {
         try {
            return ResourceXmlReader.Resources[resourceName];
         }
         catch { return null; }
      }
   }
}

We access to the right resource by using the “TwoLetterISOLanguageName” Property, but we need to define it! so here step 4 !

Step 4: Create an action filter attribute which defines the language in the current context

using System.Globalization; 
using System.Linq; 
using System.Threading; 
using System.Web; 
using System.Web.Mvc; 

namespace MVC.Globalization 
{ 
   public class GlobalizeFilterAttribute : ActionFilterAttribute 
   { 
      // Define language in current context 
      public override void OnActionExecuting(ActionExecutingContext filterContext) 
      { 
         //Get current Http HttpContextBase context = filterContext.HttpContext; 
         //if sent by Url 
         string cultureName = context.Request.QueryString["lang"]; 
         //Cookie test 
         if (string.IsNullOrEmpty(cultureName)) 
         { 
            cultureName = (null != context.Request.Cookies["lang"]) ? context.Request.Cookies["lang"].Value : string.Empty; 
            if (string.IsNullOrEmpty(cultureName)) 
            { 
               try { 
                  //sinon langue du navigateur 
                  cultureName = context.Request.UserLanguages.FirstOrDefault(); 
                  if (string.IsNullOrEmpty(cultureName)) cultureName = "EN"; 
               } 
               catch { cultureName = "EN"; } 
            } 
         } 
         else 
         { 
            var langCookie = new HttpCookie("lang"); 
            langCookie.Value = cultureName; 
            context.Response.Cookies.Add(langCookie); 
         } 

         // Change culture on current thread 
         CultureInfo culture = CultureInfo.CreateSpecificCulture(cultureName); 
         Thread.CurrentThread.CurrentCulture = culture; 
         Thread.CurrentThread.CurrentUICulture = culture; 

         //action continuation 
         base.OnActionExecuting(filterContext); 
      } 
   } 
}

This attribute allows you to intercept a language set from a form (by Url in this example) and memorize it into a cookie.

If no language is set by a form or by a cookie, the first language set in your browser will be used. If no language is set in your browser, English will be the default language.

This attribute works both if you set the language yourself and if you use the browser language by default.

To use this attribute for each page of your website, define it as a global filter in your FilterConfig class, as follows:

using MVC.Globalization; 
using System.Web; 
using System.Web.Mvc; 

namespace MVC 
{ 
   public class FilterConfig 
   { 
      public static void RegisterGlobalFilters(GlobalFilterCollection filters) 
      { 
         filters.Add(new GlobalizeFilterAttribute()); 
         filters.Add(new HandleErrorAttribute()); 
      } 
   } 
}

Now it’s time to implement each use case translation functionality from service resources (IResourceService).

Step 5 : Implement usage, translation functionality cases

  • HtmlHelper :
using MyApp.Globalization; 
using System.Collections.Generic; 
using System.Globalization; 
using System.Linq; 
using System.Text; 
using System.Web.Mvc;
 
namespace MVC.Helpers 
{ 
   public static class ResourceHelper 
   { 
      private static IResourceService _resources; 
      public static string GetResource(this HtmlHelper helper, string resourceName, string resourceKey) 
      { 
         CheckProvider();
         return _resources.GetResource(resourceName, resourceKey); 
      } 

      public static MvcHtmlString GetJSONResources(this HtmlHelper helper, string[] resourcesName) 
      { 
         CheckProvider();
         string lang = CultureInfo.CurrentCulture.TwoLetterISOLanguageName.ToUpper(); 
         TagBuilder builder = new TagBuilder("script"); 
         builder.MergeAttribute("type", "text/javascript"); 
         StringBuilder strBuilder = new StringBuilder(); 
         strBuilder.AppendLine(); 
         strBuilder.AppendLine("var MyApp = MyApp || {};"); 
         strBuilder.AppendLine("MyApp.Resources = MyApp.Resources || {};"); 
         strBuilder.AppendLine("MyApp.Resources ="); 
         strBuilder.AppendLine("{"); 
         resourcesName.ToList().ForEach(resourceName => { 
            var ressourceCollection = _resources.GetRessourcesByName(resourceName); 
            if (null != ressourceCollection && ressourceCollection.Count > 0) 
            { 
               int nbElements = ressourceCollection.Count; 
               int i = 1; 
               foreach (KeyValuePair> item in ressourceCollection) { 
                  string value = string.Empty; 
                  try { 
                     value = item.Value[lang]; 
                  } 
                  catch { 
                     try { 
                        value = item.Value["EN"]; 
                     } 
                     catch { } 
                  } 
                  strBuilder.AppendFormat(@"""{0}"" : ""{1}""", item.Key, value); 
                  strBuilder.Append(","); 
                  strBuilder.AppendLine(); 
                  i++; 
               } 
            } 
         }); 
         strBuilder.Remove(strBuilder.Length - 3, 1); 
         strBuilder.AppendLine("}"); 
         builder.InnerHtml = strBuilder.ToString(); 
         return new MvcHtmlString(builder.ToString()); 
      } 

      public static void RegisterProvider(IResourceService provider) 
      { 
         _resources = provider; 
      }
      
      private void CheckProvider()
      {
         if (null == _resources)
            throw new Exception("Resource provider is not set");
      }
   } 
}

I have created two ways to do this. The first, “GetResource”, allows you to call the resource you want to display in html. The second, “GetJSONResources”,  allows you to serialize the complete resource into a Json object in order to use resources with Javascript. It takes an array of string parameters because you can serialize several resources (defined as “Dictionary” in the resource container described at the beginning of this article).

As this helper requires an IResourceService instance, you must register an instance when the application starts, as follows:

using MVC.Helpers; 
using MyApp.Globalization; 
using System.Web.Mvc; 
using System.Web.Routing; 

namespace MVC 
{ 
   public class MvcApplication : System.Web.HttpApplication 
   { 
      protected void Application_Start() 
      { 
         AreaRegistration.RegisterAllAreas(); 
         RouteConfig.RegisterRoutes(RouteTable.Routes); 
         FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 
         
         IResourceService r = new ResourceService(); 
         ResourceHelper.RegisterProvider(r); 
         CustomDisplayNameAttribute.RegisterProvider(r); 
      } 
   } 
}
  •  Attribute on Models (for managing Model labels into a html form) :
using MyApp.Globalization; 
using System.ComponentModel; 
namespace MVC 
{ 
   public class CustomDisplayNameAttribute : DisplayNameAttribute 
   { 
      private static IResourceService _resourceService; 
      private string _resourceName; 
      private string _resourceKey;

      public CustomDisplayNameAttribute(string resourceName, string resourceKey) 
      { 
         _resourceName = resourceName; 
         _resourceKey = resourceKey; 
      } 

      public override string DisplayName 
      { 
         get 
         { 
            CheckProvider();
            return _resourceService.GetResource(_resourceName, _resourceKey); 
         } 
      } 

      public static void RegisterProvider(IResourceService provider)
      {
         _resources = provider;
      }
 
      private void CheckProvider()
      {
         if (null == _resourceService)
            throw new Exception("Resource provider is not set");
      }
}

namespace MVC.Models 
{ 
   public class TestModel 
   { 
      [CustomDisplayName("Home", "EnterYourNationalityKey")] 
      public string Name { get; set; } 
   } 
}

Like the previous HtmlHelper you need to register also an IResourceService instance

  • Using directly the IResourceService into a MVC Controller :
using MyApp.Globalization; 
using System.Web.Mvc; 

namespace MVC.Controllers 
{ 
   public class HomeController : Controller 
   { 
      private IResourceService _resourceService;

      public HomeController() : this(new ResourceService()) { } 

      public HomeController(IResourceService resourceService) 
      { 
         _resourceService = resourceService; 
      } 

      // GET: /Index/ 
      public ActionResult Index() 
      { 
         ViewData["HelloWorld"] = _resourceService.GetResource("Home", "HelloWordKey"); 
         return View(); 
      } 
   } 
}

I recommend you use Injection dependency. I will not describe it in this article, but I have “prepared” this controller in order to use this pattern with this constructor “public HomeController(IResourceService resourceService)”

Step 6: Test Tools in an HTML Page

@using MVC.Helpers 
@model MVC.Models.TestModel 

@{ ViewBag.Title = "Index"; } 
<h2>@Html.Raw(ViewData["HelloWorld"])</h2> 
<h3>@Html.GetResource("Home", "MyNameKey")</h3> 
<br /> @Html.LabelFor(m=> m.Name) 
<br /> @Html.TextBoxFor(m=> m.Name) 
@Html.GetJSONResources(new string[] { "Home" }) 

<script type="text/javascript"> alert(MyApp.Resources.HelloWordKey + "n" + MyApp.Resources.MyNameKey); </script>

 

As you can see, there is a sample of each implemented tool:

  • @Html.GetResource(“Home”, “MyNameKey”) as simple HtmlHelper in order to access to a specific asked resource
  • @Html.Raw(ViewData[“HelloWorld”]) as ViewData setted into the MVC Controller by accessing directly to IResourceService (_resourceService.GetResource(“Home”, “HelloWordKey”);)
  • @Html.GetJSONResources(new string[] { “Home” }) as HtmlHelper wich serialize a resource into a JSON object
  • @Html.LabelFor(m=> m.Name) as a translated Model label

And now for the result:

Sample 1: French language as default language on browser

chix-langue.png

french-default.png

Source code :

<!DOCTYPE html> 
   <html> 
      <head> 
         <meta charset="utf-8" /> 
         <meta name="viewport" content="width=device-width, initial-scale=1.0"> 
         <title>MyApp</title> 
      </head> 
      <body> 
         <h2>Bonjour le monde!</h2> 
         <h3>Mon pr&amp;#233;nom est Anthony</h3> 
         <br /> 
         <label for="Name">De quelle nationalit&amp;#233; &amp;#234;tes-vous?</label> 
         <br /> 
         <input id="Name" name="Name" type="text" value="" /> 
         <script type="text/javascript"> 
            var MyApp = MyApp || {}; 
            MyApp.Resources = MyApp.Resources || {}; 
            MyApp.Resources = { "HelloWordKey" : "Bonjour le monde!", 
                                "MyNameKey" : "Mon prénom est Anthony", 
                                "EnterYourNationalityKey" : "De quelle nationalité êtes-vous?" 
                              } 
         </script> 
         <script type="text/javascript"> 
            alert(MyApp.Resources.HelloWordKey + "n" + MyApp.Resources.MyNameKey); 
         </script> 
          </body> 
	    </html>

 

Sample 2: German language as default language on browser (as German is not managed, it will be managed in English by default)

choix-langue 1

english-default.png

Source code :

<!DOCTYPE html> 
<html> 
      <head> 
         <meta charset="utf-8" /> 
         <meta name="viewport" content="width=device-width, initial-scale=1.0"> 
         <title>MyApp</title> 
      </head> 
      <body> 
         <h2>Hello World!</h2> 
         <h3>My name is Anthony</h3> 
         <br /> 
         <label for="Name">What&amp;#39;s your nationality</label> 
         <br /> 
         <input id="Name" name="Name" type="text" value="" /> 
         <script type="text/javascript"> 
            var MyApp = MyApp || {}; 
            MyApp.Resources = MyApp.Resources || {}; 
            MyApp.Resources = { "HelloWordKey" : "Hello World!", 
                                "MyNameKey" : "My name is Anthony", 
                                "EnterYourNationalityKey" : "What's your nationality" 
                              } 
         </script> 
         <script type="text/javascript"> 
            alert(MyApp.Resources.HelloWordKey + "n" + MyApp.Resources.MyNameKey); 
         </script> 
      </body> 
</html> 

Sample 3 :  French language as default browser language and select into a form english language (stored in cookie after selection)

select-en

Reselect french by form action :

french-default1.png

 

I hope this article has helped you to easily translate your ASP.NET application 😉