Preparing for Internationalization of Asp.net MVC website - asp.net

I'm going to start a website which I know is going to be presented in multiple languages. However, for the first version we're only going to need the English version. Once the features are all working, we'll add the other languages.
Unfortunately since there are not enough enough features baked into Asp.Net Core, we have to use the Asp.Net MVC 5 for the website. My question has 2 parts:
Right now, which practice is considered the best approach for this? Using resource files and loading them in razor pages? Using a framework? Can we use the new localization and globalization features of Asp.Net MVC 6 somehow? Or is there a better alternative? I personally hate using the resource files. It adds too much clutter to the code.
Would you suggest just using plane text for now and then adding the Internationalization features to the website or start now and only add the translations?

I would use resource files, seems to be the easiest solution. You can also use a Database resource provider, so you have less clutter.
If you start with plain text, it will get more complicated and cumbersome to add the translations later. So I would not do that.

We use Smart internationalization for ASP.NET.
Features
Localize everything: HTML, Razor, C#, VB, JavaScript, .NET attributes
and data annotations, ...;
SEO-friendly: language selection varies the URL, and Content-Language is set appropriately;
Automatic: no URL/routing changes required in the app;
High performance, minimal overhead and minimal heap allocations; Unit testing support;
Smart: knows when to hold them, fold them, walk away, or run, based on i18n best practices.
How I use i18n in the project step by step:
Add the I18N nuget package to your MVC project.
in Web.config:
Add a folder named "locale" to the root of your site. Create a subfolder for each culture you wish to support. For example, /locale/fr/.
copy i18n.PostBuild.exe into locale folder
Right click on tne project name --> Properties --> Build Events:
in Post-build event command line:
"$(TargetDir)i18n.PostBuild.exe" "$(ProjectDir)\web.config"
In views use [[[some text]]] to translate it later
Build the project
Refresh Solution Explorer and push Show All Files
Include all files in "locale" folder into the project
Provide translation of the words in locale\fr\messages.po
In Global.aspx add :
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
//other app start code
UrlLocalizer.UrlLocalizationScheme = UrlLocalizationScheme.Void;
}
}
Create DefaultController :
public class DefaultController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
if (Session["currentLanguage"] == null)
{
Session["currentLanguage"] = "en";
}
}
}
In HomeController add inheritance of DefaultController and SwitchLanguage(string lang):
public class HomeController : DefaultController
{
public HomeController() : base()
{
[AllowAnonymous]
public async Task<ActionResult> SwitchLanguage(string lang)
{
LocalizedApplication.Current.DefaultLanguage = lang;
Session["currentLanguage"] = lang;
return Redirect(Request.UrlReferrer.PathAndQuery);
}
}
}
In navigation bar View (_LoginPartial.cshtml in my case) add links to switch between languages:
#if (Session["currentLanguage"].ToString() == "fr")
{
<li class="navItem">#Html.ActionLink("EN", "SwitchLanguage", "Home", new { lang = "en", area = "" }, null)</li>
}
else
{
<li class="navItem">#Html.ActionLink("FR", "SwitchLanguage", "Home", new { lang = "fr", area = "" }, null)</li>
}
Build project, Start in Browser and enjoy!!!
see some help in:
https://www.codeday.top/2017/09/19/42409.html

Related

Custom Routing in ASP.NET Core 2.2 Razor Pages

I'm having trouble setting up a routing convention for Razor Pages in an ASP.NET Core 2.2 application (I'm migrating from traditional MVC to Razor Pages).
I am using the standard Pages folder structure as recommended in the documentation, but I want to customise the generated routes slightly.
For example, on a Details.cshtml page in the Products folder I have the following directive:
#page "{id:int}
The URL for this page is:
/Products/Details/42
however I want the URL to be:
/Products/42
I want this to be globally applicable, not just on the Product Details page. The documentation is not particularly clear on this.
So this seems to be the way to do it:
https://www.mikesdotnetting.com/article/327/customising-routing-conventions-in-razor-pages
Create a class that implements IPageRouteModelConvention:
public class CustomPageRouteModelConvention : IPageRouteModelConvention
{
public void Apply(PageRouteModel model)
{
foreach (var selector in model.Selectors.ToList())
{
var template = selector.AttributeRouteModel.Template;
if (template.EndsWith("Details/{id:int}", StringComparison.OrdinalIgnoreCase))
selector.AttributeRouteModel.Template = template.Replace("Details/{id:int}", "{id:int}", StringComparison.OrdinalIgnoreCase);
}
}
}
Register the class in Startup.cs:
services
.AddMvc()
.AddRazorPagesOptions(o =>
{
o.Conventions.Add(new CustomPageRouteModelConvention());
});
Compared to the old MVC way of doing it this seems like a massive effort, but I can see that it allows for a finer level of control.
You need to override the entire route, so this is what you needed -
#page "/Products/{id:int}"

What is the difference between declaring a property inside #functions {} and the one declared in the PageModel in Razor pages

I am building razor pages app using .Net Core 2.0
What is the difference between declaring a property inside the .cshtml such as follows
#functions
{
public StandardListenerViewModel listener { get; set; }
}
and the one declared in the page model
public class SingleDeviceModel : PageModel
{
[BindProperty]
public StandardListenerViewModel listener { get; set; }
public void OnGet(StandardListenerViewModel lstner)
{
this.listener = lstner;
}
}
There's no real technical difference as far as the property is concerned whether it is declared in a functions block or a PageModel class. It still becomes a property of the generated class when the app is compiled. The difference is really one of code organisation.
Most people prefer to work with a PageModel class because it provides a clean separation between UI (markup) and request processing logic. And it is a lot easier to unit test logic. You just need to instantiate an instance of the PageModel class in your test.
Generally, functions blocks are more likely to be used in simple demos to make the code easier to follow for proof of concepts. They probably also provide an easier ramp for those moving to Razor Pages from PHP, classic ASP or ASP.NET Web Pages where having processing logic and UI markup in the same file is a common pattern.

How to add JS and CSS to all content parts in an Orchard module

I am writing a module for Orchard CMS 1.8.1
I would like to add custom styles to all content parts that I have written for the module. I need these to work regardless of the theme chosen by the website admins. I could add links to the CSS and JS files in every view file for every content part - but that seems messy and prone to future bugs - what's the best way to have a single file that loads up the styles needed for all my content parts?
Should I provide a different Content.cshtml that includes the links? This also seems like it could be problematic if the admins need their own control over the main Content.cshtml
Many thanks
Handler should do the trick, I wrote this from the top of my head so not sure if it really works.
First create ResourceManifest.cs and define your stylesheets and scripts
public class ResourceManifest : IResourceManifestProvider
{
public void BuildManifests(ResourceManifestBuilder builder)
{
var manifest = builder.Add();
manifest.DefineStyle("MyStylesheet").SetUrl("mystylesheet.min.css", "mystylesheet.css").SetVersion("1.0.0");
manifest.DefineScript("MyScript").SetUrl("myscript.min.js", "myscript.js").SetVersion("1.0.0");
}
}
Then it should be enough to create content handler and override the BuildDisplayShape
public class MyResourceHandler : ContentHandler
{
private readonly Work<IResourceManager> _resourceManager;
public MyResourceHandler(Work<IResourceManager> resourceManager)
{
_resourceManager = resourceManager;
}
protected override void BuildDisplayShape(BuildDisplayContext context)
{
if (context.DisplayType == "Detail" && context.ContentItem.Has(typeof(MyPart)))
{
this._resourceManager.Value.Require("stylesheet", "MyStylesheet");
this._resourceManager.Value.Require("script", "MyScript");
}
base.BuildDisplayShape(context);
}
}
Adjust the IF as necessary. And let me know if it works ;)
Beauty of using ResourceManifest with versioning is that anyone can replace your stylesheets/javascript with their own just by defining style in their own ResourceManifest (module/theme) with same name and higher version number and don't have to touch any original files.

How can I render Razor page from Assembly

UPDATE1
I've added RazorGenerator and etc...
After set custom tools, I've seen generated code for my razor pages.
Added this code in assembly
public class MyAreaRegistration : AreaRegistration
{
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute("Dictionary_default", "MyDictionary/{Action}/", new { controller = "DictionaryControllerBase", action = "Index" });
}
public override string AreaName
{
get { return "MyDictionary"; }
}
#endregion
}
But when I open page by url /MyDictionary, i see "Unable to find the resource."
NOTE I use in my project MVC3 and Spring.Net
I use one controller (base controller) in another Assembly with razor pages.
In my project I make controller inherited from base controller, just it make some settings. But razor pages I wish to use from assembly.
How can I do it?
You could the RazorGenerator extension. I have detailed how this can be achieved in the following post. The idea is that the RazorGenerator extension would create a corresponding .cs file for each Razor view and it will update it every-time you make a change to the corresponding view. This way the Razor views will be precompiled in the class library along with their respective controllers and view models. The RazorGenerator.Mvc NuGet will then register a custom virtual path provider which will take care of resolving those views.

ASP.NET MVC URL auto-resolution in CSS files

In normal WebForms scenario, any root-relative URLs (e.g. ~/folder/file.txt) inside CSS files such as:
.form { background-image: url(~/Content/Images/form_bg.gif); }
will automatically get resolved during runtime if I specify
<head runat="server">
In the referencing page.
However, that is no longer happening on an ASP.NET MVC Beta1 website.
Is there a way I could enable this functionality without resorting to hacks or CSS-loader file? Like maybe HttpModules or something?
Or am I not desigining my website correctly? What is supposed to be a good design?
Since original ASP.NET WebForms already has this feature, I'd prefer to utilize any existing functionality if possible. But I don't have much clue.
This web application will be deployed in several environments where the ~ root folder might not be obvious.
EDIT: I mean the url in the file's CONTENT not the file's url itself.
I would not bother with the auto-root-finding ~ character. I understand that you want the same solution to work where the root directory differs between deployments, but within the CSS document you shouldn't have any problems using relative paths. The paths in the CSS document (to the image URL in your example) will always be relative to the location of the CSS file regardless of the path of any page that loads that CSS file. So if your images are in ~/Content/Images and your stylesheets are in ~/Content/Stylesheets, you'll always be able to use background-image: url(../Images/form_bg.gif); and it will work regardless of the location of the page that loads the stylesheet.
Is there a reason this wouldn't work?
One trick I have used in the past, was to actually make my CSS file have a .ASPX extension, and set the ContentType property in the page signature:
<%# Page Language="C#" ContentType="text/css" %>
body {
margin: 0;
padding: 0;
background: #C32605 url(<%= ResolveUrl("~/Content/themes/base/images/BodyBackground.png") %>) repeat-x;
font-family: Verdana, Arial, sans-serif;
font-size: small;
color: #d7f9ff;
}
This will ensure that the CSS file goes through the ASP.NET framework, and replaces the server side code with your relative path.
Here are some resources on implementing IHttpModule to intercept web requests to your app...
Write/adapt one to check for filetype (e.g. pseudocode: if (request ends with ".css") ...)
then use a regular expression to replace all instances of "~/" with System.Web.VirtualPathUtility.ToAbsolute("~/")
I don't know what this will do to performance, running every request through this kind of a filter, but you can probably fiddle with your web.config file and/or your MVC URL routes to funnel all .css requests through this kind of a filter while skipping past it for other files.
Come to think of it, you can probably achieve the same effect inside an ASP.NET MVC app by pointing all your CSS refrences at a special controller.action that performs this kind of preprocessing for you. i doubt that would be as performant as an IHttpModule though.
If you're trying to parse the ~/ out of any file, including text files, javascript, etc, you can write a handler that assigns a filter to it and you can use that to search for those paths... for example...
public class StringParsingFilter : MemoryStream {
public Stream OriginalStream {
get { return this.m_OriginalStream; }
set { this.m_OriginalStream = value; }
}
private System.IO.Stream m_OriginalStream;
public StringParsingFilter() : base() {
this.m_OriginalStream = null;
}
public override void Flush() {
this.m_OriginalStream.Flush();
}
public override void Write(byte[] buffer, int offset, int count) {
//otherwise, parse for the correct content
string value = System.Text.Encoding.Default.GetString(buffer);
string contentType = HttpContext.Current.Response.ContentType;
//Do any parsing here
...
//write the new bytes to the stream
byte[] bytes = System.Text.Encoding.Default.GetBytes(value);
this.m_OriginalStream.Write(bytes, offset, count + (bytes.Length - buffer.Length));
}
}
And you'll write a custom handler to know when to assign this filter... like the following...
public class FilterControlModule : IHttpModule {
public void Init(HttpApplication context) {
HttpApplication oAppContext = context;
oAppContext.BeginRequest += new EventHandler(_HandleSettingFilter);
}
private void _HandleSettingFilter(object sender, EventArgs e) {
//You might check the file at this part to make sure
//it is a file type you want to parse
//if (!CurrentFile.isStyleSheet()) { return; }
...
//assign the new filter
StringParsingFilter filter = new StringParsingFilter();
filter.OriginalStream = HttpContext.Current.Response.Filter;
HttpContext.Current.Response.Filter = (Stream)filter;
}
}
It may have actually been easier just to say "look up IHttpModules" but this is some code that I've used to parse files for paths other than ASP.net files.
You'll also have to change some things in your IIS settings to allow the files to be parsed by setting the ASP.net ISAPI to be a wildcard for all of the files that get handled. You can see more at this website, if you're using IIS6 that is...
You can also use this to modify any file types so you could assign some filters for images, some for javascript or stylesheets or ... really anything...
You could use an URL Rewriter to fix the URL as the request comes in, though I am not so sure it is so much elegant as a hack in this case.
I created a PathHelper util class that gives me all the paths I need.
For example
<link href="<%=PathHelper.CssUrl("FormulaIndex.css")%>" rel="Stylesheet" type="text/css"/>
Gives me the correct full url with the help of System.Web.VirtualPathUtility.ToAbsolute() and my own convention (content/css/yourFile.css).
I did the same for js, xml, t9n, pics...
Its central, reusable and now I only had to change one line to catch the move of the scripts folder from content/js to Scripts in all my websites and pages.
A moronic move if you ask me, but it's reality in the current beta :(

Resources