Closed mdmoura closed 12 years ago
Hi Miguel,
For #1 - CONVENTIONS: I'd recommend creating your own RouteConvention, or just not using conventions at all if possible. If you look at the code for the RestfulRouteConvention, you can see how easy it would be to add your own flavor.
Also, you don't HAVE to use PUT or DELETE. But you can if you want to. If you look at the code in /Extensions/MvcExtensions.GetHttpMethod(), you'll see how the verb is detected.
For #2 - SLUGS: You could either create a permalink that does not change, or inside your actions issue a 301to the canonical url. If Google is crawling, and hits /post/123/old-slug and gets a 301 to /post/123/new-slug, then maybe things will work out. Not sure as SEO is not my forte.
For #3 - LOCALIZATION: I think ultimately you'll need to have the route table know when to accept and generate the correct, culture-specific routes. This would have to be a new feature of AR, and I'm not sure the best way to implement it. One idea would be to expose a LocalizationProvider which would let you store your localized routes wherever (db, file, in-memory) and simply write your own provider implementation to supply AR with the correct route information (like url, defaults, constraints). To tell the truth I have not thought of localization much because I haven't had a need for it. But I do see that if there was support for this that AR would be truly awesome.
Regarding #3, I will start thinking of ways forward.
Thanks for your feedback and please feel free to fork the code and hack in your own solution. If you do so, let me know what happens! We could bring it into the project and you could be the first contributor.
t
On Nov 9, 2011, at 4:04 PM, shapper wrote:
Hello Tim,
I am using Attribute Routing on a CMS I develop and I have a few questions.
1 - CONVENTIONS
I am planning to use the RestfulRouteConvention but I have a few questions: A) Do you consider Post / Index as Post / List? Or it can be just the "entry" page? B) Instead of: Edit — GET ControllerName/{id}/Edit (Post/34/Edit) Wouldn't be better to have: Edit — GET ControllerName Pluralized/{id}/Edit (Posts/34/Edit) Reference: http://edgeguides.rubyonrails.org/routing.html You pluralize the controller or add an attribute to the controller? I suppose both can be used, correct? C) I see that all forms must have the HttpMethodOverride. I need to a "Delete" link in each row of a table. How would you do it? Maybe using Ajax? But can I do the HttpVerbs.Delete? $('table.Data a.Delete').click(function (e) { e.preventDefault(); if (confirm("Delete this item?")) { $.ajax({ type: 'POST', url: $(this).href }); }; });
2 - SLUG IN URLs
I would like to have friendly slugs on the URLs. A few options: A) posts/124/learn-about-attribute-routing (SLUG = TITLE) In this case when the TITLE / SLUG changes I will not have broken links. This is because the post is retrieved using the ID. The only problem is if the title is changed I get 2 URLs indexed to the same page: OLD: posts/124/learn-about-attribute-routing NEW: posts/124/learn-about-mvc I think this is penalized by Search Engines. In fact, aren't you penalized when using: [GET("", Order = 1)] [GET("Posts", Order = 2)] [GET("Posts/Index", Order = 3)] public void Index() { return View(); } You have multiple routes to the same page. B) posts/124-learn-about-attribute-routing (SLUG = ID + TITLE) In this case when the TITLE / SLUG changes I will have broken links. That is a problem worse then the previous one. On both cases the Slug is saved on the Database when creating the post ... ... probably it is better then creating it on the fly. What do you think? Which approach would you use? And is this compatible with Restfull convention?
3 - LOCALIZATION
We already talked about localization before. In fact created a branch when we talked. After reading and researching a lot I think there is a better approach. (A) Use different Country Top Level domains for each version. ENGLISH: www.domain.com; PORTUGAL: www.domain.pt; FRANCE: www.domain.fr "At the SMX Confernece in Sydney Australia, Priyank Garg and Greg Grothaus, Yahoo’s and Google’s search engineers, shared some issue: If you use multiple country domains (ccTLD), even if content is in the same language (identical content) on all of your localized sites (for instance you have it all in English, as it commonly happens with USA, UK and Australia), you will not experience any duplicated content issues / penalties. This is true for both Google and Yahoo, however, you might get some penalty if you abuse this feature (spammy sites) and if you don’t localize your site properly." (B) If Top Level domains are not available then use sub domains. ENGLISH: en.domain.com; PORTUGAL: pt.domain.com; FRANCE: fr.domain.com HOW IT WORKS When a page is requested the MVC application detects the domain of the request and sets the culture. ADVANTAGES The portuguese version is indexed by Google.pt, the french version by Google.fr, etc. I did a search for MSN in Google.com, Google.pt and Google.fr and in fact I got: www.msn.com, pt.msn.com and fr.msn.com. ISSUES A route translation would be necessary to do the following: - Go from "www.domain.pt/contacto" to "www.domain.com/contacto" - Go from "www.domain.pt/post/ola-mundo" to "www.domain.com/post/hello-world" The problem is that for the last route the title can change ... And in fact a post can have no translation so it would be redirected to home/index or other defined route. I am not sure if the best option would be to have a table to hold routes. What do you think?
Cheers, Miguel
Reply to this email directly or view it on GitHub: https://github.com/mccalltd/AttributeRouting/issues/20
Hello,
For #1: I don't know why (maybe because of Wiki) but I though you were advising the use of conventions.
My idea is to use on the CMS something similar to what you have on the Wiki:
https://github.com/mccalltd/AttributeRouting/wiki/1.-Getting-Started
The only differences are:
A) I am not sure if I will use PUT and DELETE Verbs or if I will replace them by POST.
B) I will have another action named:
[GET("Posts/{id}/flag/Get")]
public ActionResult Get(int id, string flag) { }
I have all files (binary data) saved on a single table (using filestream).
Each file is identified with an ID and a Flag.
Sometimes I request the file directly, through the FileController, as follows:
[GET("Files/{id}/flag/Get")]
public ActionResult Get(int id, string flag) { }
But in posts controller I can set some authorization levels and other options associated to that post.
Is there a more common way to name this actions?
For #2: Yes, the 301 is a good idea. I will allow the user do change the Slug but advice him/her not to do it.
For #3: In my opinion saving the URLs in a database might be a problem. It would become really strict and a problem for changes in the routing.
I think it should be more flexible. Better to use and less headaches for AttributeRouting.
1 - Detect the origin of the request and set culture.
A) Use different domains:
Contains "domain.com" > en-US
Contains "domain.pt" > pt-PT
B) Use different sub domains:
"en.domain.com" > en-US
"pt.domain.com" > pt-PT
C) Finally, if people don't like this approach then use:
"domain.com/en" > en-US
"domain.com/pt" > pt-PT
Option (C), as I explained, does not seem to be recommended.
And option (A) and (B) raise a problem: how to test the site during development.
Maybe setting option (C) in DEBUG and (A) or (B) in RELEASE?
2 - Routes
(A) Direct translation
- Area: "Store" in EN > "Loja" in PT
- Controller: "Products" in EN > "Produtos" in PT
- Action: "New" in EN > "Novo" in PT
(B) Parameters
"Store/Produts/2/Learn-Mvc-Book"
The part "Store/Produts/" is taken by (A) and becomes "Loja/Produtos/".
And the rest of the URL is taken care by the application itself.
@Html.ActionLink(model.ItemName, MVC.Store.Index(model.Item.Id, model.Item.NameSlug))
Model alreay have the values translated by the application.
(C) Switching
A "complex" culture switching from EN to PT:
"www.domain.com/Store/Produts/2/Learn-Mvc-Book/Edit"
And the rules to translate would be become:
- "%domain.com%" becomes "%domain.pt%"
- "Store" > "Loja", "Products" > "Produtos", "Edit" > "Editar"
- Use ProductTranslation(id, slug, culture) **********
In ProductTranslation:
> Access service layer: Find Product by ID:
> If product is found use ID and Localized SLUG.
If it is not found then redirect to route "www.domain.pt/message/5/product-unavailable-in-portugal"
3 - Implementation
IMHO I think the best way would be a flexible solution just like "Plug & Play". :-)
For example Plug one or more Translation Providers (maybe with Fluent ...)? Example:
public class TranslationProvider : ITranslationProvider {
public Initialize(String culture) {
Culture.Default("en-US").Allow("pt-PT").Allow("fr-FR")
Maybe some Regex would be interesting to use on the following.
This is because of domain.com:80, etc
URL.Domain("domain.com").For("en-US").For(en-GB).Release();
// OR
URL.Domain("domain.com").For("en").Release();
// OR
URL.Domain("en.domain.com").For("en").Release();
URL.Domain("domain.com/{culture}").Width(culture).Debug()
Area("Store").For("en").Is("Loja").For("pt").Is("Boutique").For("fr")
Controller("Products").For("en").Is("Produtos").For("pt") ...
Action("...
Route("Store/Products/{id}/{slug}/Action").Use(x => ProductRouteTranslation(x.Id, x.Slug))
// The ProductRouteTranslation would have the code in **********.
}
}
routes.MapAttributeRoutes();
routes.Translation.AddProvide(new TranslationProvider())
This is an example. Maybe can be better adapted for AttributeRouting.
The code could be divided in more than one translation provider:
AreaTranslationProvider, ControllerTranslationProvider, etc.
And RouteTranslation : IRouteTranslation could be used to create very specific translations.
Well, this is an idea. I tried to make it flexible.
And remove most of the translation decision from AttributeRouting.
What do you think?
Cheers, Miguel
Just leaving a useful post for localized sites:
http://guides.rubyonrails.org/i18n.html
Check items 2.3, 2.4 and 2.5. Basically this is what I am talking about.
Thanks. I'll check this out!
On Nov 21, 2011, at 4:56 AM, shapper reply@reply.github.com wrote:
Just leaving a useful post for localized sites:
http://guides.rubyonrails.org/i18n.html
Check items 2.3, 2.4 and 2.5. Basically this is what I am talking about.
Reply to this email directly or view it on GitHub: https://github.com/mccalltd/AttributeRouting/issues/20#issuecomment-2814321
Hello,
After using AttributeRouting for the past 2 months I think I can give more feedback on localization.
I am not sure if this is possible but this is what seems logic to me:
routes.MapAttributeRoutes(x => {
x.SetTranslationProvider(new TranslationProvider());
x.SetCulturePrefix(CulturePrefix.Culture);
x.SetCultures("en-US", new { "pt-PT", "fr-FR" });
});
Notes: 1) SetTranslationProvider would define the class responsible for translation. When creating routes AttributeRouting would call this class.
2) SetCulturePrefix would define the format of the culture prefix:Culture or Language. So when adding a CulturePrefix attribute on a controller the routes would become:
"/en-US/area/controller/action" or "/en/area/controller/action"
3) SetCultures would define the default culture (used in routes) and the other ones.
The TranslationProvider could be something like:
public class TranslationProvider {
public void Resolve() {
.For(Route.Area, x => {
x.Add("Admin", { "AdminPT", "AdminFR" })
});
} }
When AttributeRouting is defining the routes it would:
1) Check if CulturePrefix is defined ... If yes then: Add current culture on start of route if CulturePrefix is used.
2) Call translation provider. For example, and area named "Admin" would become "AdminPT" if current culture is "pt-PT".
Basically, before routes are build the culture should be checked and use this configuration to define the route.
Does this make sense?
Thank You, Miguel
Miguel, I like your fluent translation provider API as the default, and allowing this to be swapped with something else. Something like:
FluentTranslationProvider translations;
translations
.ByKey("HomePage_RouteUrl")
.Add("en-ZZ", "nook")
.Add("en-AA", "cave")
.ByKey(...)
.Add(...)
.Add(...);
// or
translations
.ByCulture("en-ZZ")
.Add("HomePage_RouteUrl", "nook")
.Add("AboutPage_RouteUrl", "sleeping")
....;
// which you then pass into
routes.MapAttributeRoutes(config => {
config.TranslationProvider = translations
})
Anyhow - yes, all that you wrote makes sense. Below are some notes I wrote after first reading your comments.
What would be nice to have, and what would AR do?
/{areaUrl}/{prefix}/{routeUrl}/{param}
having a translated route of
/easay/apescay/oatgay/{param}
having a default generated route of
/sea/scape/goat/milk
and ending up with
/easay/apescay/oatgay/milk
with milk left alone cause it's a route param, but could have been generated from translation for route default, and would apply translated route constraints as well.)
What do you think?
Miguel -- I've added a bunch of stuff to the branch 20-localization. I think localization should be functional now. Maybe not complete, but workable. Please do the following:
$ git clone git@github.com:mccalltd/AttributeRouting.git
$ git checkout origin/20-localization
Open the web app in the solution and look in global.asax for configuration of translations. If you change the default language of your browser to es or fr, you will have a generated link on the Localization page pointing to the translated route. Currently:
var provider = new FluentTranslationProvider();
provider.AddTranslations().ForController<HomeController>()
.AreaUrl(new Dictionary<string, string> {...})
.RoutePrefix(new Dictionary<string, string> {...})
.RouteUrl(c => c.Action(), new Dictionary<string, string> {...})
....;
var provider = new FluentTranslationProvider();
provider.AddTranslations()
.ForKey("Key", new Dictionary<string, string> {
{ "es", "espanol" }
})....;
Please give me feedback ASAP. If you think this is good enough to use now, and doesn't leave you missing anything important, I want to merge it into the master branch and push it out to nuget.
Thanks for your feedback and suggestions!
Just added wiki 6. - Localization. Going to push in the next few days.
Now available via nuget v1.5
@mccalltd I just have to say a big THANK YOU for this powerful routing lib. I'm trying it in my bilingual project (pt-BR and en-US) and it's working as expected. I think I'll write a blog post to showcase the new Localization feature that's AWESOME! :D
I tried this just to make sure it worked as I wanted:
[GET("Realty/Details/{id}", TranslationKey = "Realty/Details/{id}")]
public ViewResult Details(int id)
{
...
}
Then in AttributteRouting AppStart:
var translations = new FluentTranslationProvider();
translations.AddTranslations()
.ForKey("Realty/Details/{id}", new Dictionary<string, string>
{
{ "pt", "Imovel/Detalhes/{id}" }
});
routes.MapAttributeRoutes(config =>
{
config.AddRoutesFromController<RealtyController>();
config.AddTranslationProvider(translations);
config.UseLowercaseRoutes = true;
config.AutoGenerateRouteNames = true;
});
It's working flawlessly.
Is this the best way of configuring the translations?
Hey thanks! I'd lean toward using the ForController method rather than the ForKey method, just cause then I have a bit of freedom for refactoring. I also have some changes in master that will affect localization, FYI. Added support for constraining inbound route handling by culture. Will push those changes to nuget soon. Just gotten sidetracked with other things.
@mccalltd Is it possible to translate QueryString parameters with AttributeRouting? For example: http://leniel-pc:8083/imoveis?page=2 would become http://leniel-pc:8083/imoveis?pagina=2 in pt-BR...
In the case above I already translate the route from http://leniel-pc:8083/realties?page=2 to http://leniel-pc:8083/imoveis?page=2.
At the moment I don't think it is possible ... And I am not sure how that would be done ...
For now I am using p for page, q for query, etc as an alternative
Tim, when do you expect to push the "constraining inbound route handling by culture" changes?
Cheers, Miguel
Hello Tim,
I am using Attribute Routing on a CMS I develop and I have a few questions.
1 - CONVENTIONS
2 - SLUG IN URLs
3 - LOCALIZATION
Cheers, Miguel