We are thinking about migration from Kentico CMS to Kontent.
So far we are found answers to most of our navigation related questions. But one question remains not clear. How to implement page aliases in MVC.NET+Kontent application.
Not rare business case when some page, let's say sub-productAA is being moved from productA to ProductB.
For instance: from /products/ProductA/sub-productAA it becomes /products/ProductB/sub-productsAA .
Let's assume, that sub-productAA was advertised in a 3rd party website by it's original URL: /products/ProductA/sub-productAA .
Now original URL is dead.
How to handle this situation?
Another example that product has been renamed and slug has been renamed as well to better address product mean. And original URL has been published elsewhere.
How handle it and properly redirect to a new URL ?
Thank you,
Kontent doesn't provide you with any built-in means to handle page aliases. As a headless CMS, it is up to you to decide how you map content items to URLs.
I don't think there is any single correct solution for this. If you don't have a huge number of aliases to manage you could handle them within Kontent by creating a content type that has a slug and a linked item so you can use this to store aliases and redirect to the correct content item. For your example:
product content type item has a slug of "ProductB/sub-productsAA"
alias content type item has a slug of "ProductA/sub-productAA"
you then have an action that retrieves content by slug from products:
var response = await _deliveryClient.GetItemsAsync<Product>(new EqualsFilter($"elements.{Product.SlugCodename}", productUrlSlug));
and if none is returned do another search against aliases:
var response = await _deliveryClient.GetItemsAsync<ProductAlias>(new EqualsFilter($"elements.{ProductAlias.SlugCodename}", productUrlSlug));
That way you only make the second call if the first fails. You can then retrieve the slug for the linked item and redirect the client to the correct URL.
The downside approach is that it requires content editors to manage aliases. Not a bad thing, as they should be thinking about the consequences of changing URLs.
An alternative approach would be to track aliases on your server. You could use webhooks to receive a notification about content changes. If a product slug has been modified you could store the old value and use custom middleware to redirect to the new version. More work, but no editor effort required.
Related
We are using Sitecore 7.2 with multi-site implementation. The actual data is shared between multisite, hence it's been stored in common Global Item folder.
We are facing a problem generating aunique ID on URL. I had a good search but could not find any solution except to use Sitecore Item ID.
This is what we want:
domain/players/player_id
e.g. domain/players/1234
where 1234 is uniquely generated ID.
Could someone please suggest if this is possible?
Every page that is managed in Sitecore is a Sitecore Item. As such, you should be able to just navigate to the name of the player item. If you were trying to say in your post that player items are stored in globals and not as pages, then you are left with the following options:
Query String: domain/players/player?playerId={ID}
If this is the route that you choose to take then I would suggest using the player item's Sitecore ID for the value of the query string parameter.
If you have other IDs then put the specified ID there, however it would be easiest with Sitecore IDs
What you would then do is get the item with the ID specified in the query string parameter (or get the item with the player ID specified in the query string parameter, depending on which route you take) and display the data for that player on the page
Sitecore.Context.Database.GetItem(Request.QueryString["playerId"])
playerItems.FirstOrDefault(playerItem => playerItem["Player ID"] == Request.QueryString["playerId"])
Note that this assumes that the Player ID is a field, not the Sitecore ID
If it is the Sitecore ID then change the lambda to use playerItem.ID == new ID(Request.QueryString["playerId"]
Regardless of which one you use, I suggest adding null checks to the QueryString getter
Sublayout Parameters
If you use this method, the query string will not change and people will not be able to get to the page from the direct URL
The process is the same as for query strings, except that you are using Sublayout parameters instead
Note that you must set these in a parent sublayout or in Sitecore (which means that you have a separate page for each player - i.e. this would be a bad solution)
Virtual Items
This is really what I think you are looking for
This can be a lot of work if you do it from scratch, or you can use the Wildcard Module
If you do it from scratch, you will need a custom pipeline and/or processor for handling the requests
Good suggestions from Zachary. I will add a couple more:
1) IIS Rewrite Module. If what you are really after is having external URLs look like /domain/players/1234, you could easily accomplish this by forwarding these requests to something like Zachary's option #1. The client sees /domain/players/1234, but it's really handled by a single Sitecore item at /domain/player/player.aspx?playerid=1234. Client doesn't have to know that.
2) Custom ItemResolver pipeline handler. Custom Pipelines may be a bit intimidating at first, but they are actually pretty easy to implement and highly useful. Would be pretty straightforward to add a new one which checked for "players/1234" and set the ContextItem to your player handling page and drop the ID into a session variable or some context variable.
I can work with Ember.js(rc0) and Rails, and have a simple app working as I'd expect, but want to focus on a specific story:
As a user, I want to type in "filter" text in a form, then have my ArrayController only show me those items that match the filter. For example, think of a Contacts app that shows people with the name like "Ya%"...
Caveat: Say the database holds thousands of Contact records. I don't want to filter those contacts on the client, it makes more sense to do that on the server.
Question:
How do I do this in ember.js/ember-data? On the server, I can easily allow for a search parameter in my index URL to filter data so it's a manageable list, or even limit the response to say, 20 items.
I can also use a view to have access to my filter text in my controller, but where do I go next? How can I pass that filter onto the server?
Update:
I was able to use "find" on the model object, and ember (ember data) went to the server to grab new data - as the client side only had a subset of all the Contact records to begin with. Instead of filtering on what on the client, it automatically deferred to the the server... which is nice.
App.ContactIndexController = Ember.ArrayController.extend
search_term: null
submit: (view) ->
this.set('content', App.Contact.find({search: "#{view.search_term}"}))
This is a good use case for findQuery. For example:
store.findQuery(App.Contact, {q: queryString})
This will in turn call findQuery on the appropriate adapter, and if successful, load the returned records into the store and return a DS.AdapterPopulatedRecordArray.
Note that you can completely customize the query object to include params that match your server's endpoints.
Update: As Michael pointed out in the comments, the above is equivalent to:
App.Contact.find({q: queryString})
... which is certainly a cleaner solution, especially without direct access to the store.
I want to make sure that my visitors (not authenticated users), are unable to visit a particular view without coming directly from a "previous view". I've kind of had to manually create a form preview and confirmation state. It's the step between submission and preview, and preview and confirm I'd like to "secure".
form submission-view -> preview-view -> confirm-view.
Is there some way that I can create a unique hash, POST it, and check if it's correct, or somewhat generate a cookie, session — or anything else that feels clever?
I'm a Django beginner (programming beginner in general) and any snippets' or pointing me in a right direction would be very much appreciated!
Thanks.
There are at least two ways you can accomplish this that I can think of:
One would be to include a hidden field in your form or querystring value that contains your hash/unique that you want to pick up in the next view. If it's not there, or incorrect, redirect.
Another would be to check the referring url from the request.META to see if they've come from the view you want them to come in on first, and save a session value from the form submission to carry through the rest of the views. If it's not there, redirect. If the referring URL isn't what you expect, redirect.
Whether you use a cookie, session, querystring parameter or hidden form post, it's all doing the same thing - validating a value exists. Whatever method works best, is what makes the most sense for you as the developer and most likely maintainer of said app.
To POST data to the server for an object, one must provide the object's ID for lookup. Where to include the ID and send it to the server is something that has been bugging me. There are three options I have in mind:
Include the ID as part of the URL
Include the ID as part of the POST data
Put the ID in query string (I'm not sure if query string is allowed when doing a POST)
I use python/django for my server end.
Someone please comment on which one I should go with and why. Thanks.
I'd do it via the URL in most cases, particularly if you are creating a view to display an object. Furthermore, I'd use the slug (if there is one) instead of an ID as it looks better in Google SERP, it makes more sense semantically, and it is more readable for users.
Remember that you can easily reverse object's urls using get_absolute_url(), the {% url ... %} tag and the reverse() function. You won't be able to avail of these if you use query strings or Posts to display your objects.
Regarding query strings/parameters; I usually go by the rule of constructing the queryset with my URL, but filtering it with parameters. i.e. If I want to see all posts tagged with something, I get the main queryset via the URL /posts/tagged/some_tag and then drill them down where necessary with parameters; /posts/tagged/some_tag?rating=2
Regarding POST data; this should only really apply when you are presenting a form to edit an object. I would still use the URL to find and display the form (i.e. /posts/my-post-slug/edit/) and I would use parameters to control any options or features (i.e. /posts/my-post/slug/edit/?highlight_required_fields=true) but all the fields of the form would be submitted via POST
Im actually working in a django project and I'm not sure about the best format of the URL to access into one particular object page.
I was thinking about these alternatives:
1) Using the autoincremental ID => .com/object/15
This is the simplest and well known way of do that. "id_object" is the autoincremental ID generated by the database engine while saving the object. The problem I find in this way is that the URLs are simple iterable. So we can make an simple script and visit all the pages by incrementing the ID in the URL. Maybe a security problem.
2) Using a <hash_id> => .com/object/c30204225d8311e185c3002219f52617
The "hash_id" should be some alphanumeric string value, generated for example with uuid functions. Its a good idea because it is not iterable. But generate "random" uniques IDs may cause some problems.
3) Using a Slug => .com/object/some-slug-generated-with-the-object
Django comes with a "slug" field for models, and it can be used to identify an object in the URL. The problem I find in this case is that the slug may change in the time, generating broken URLs. If some search engine like Google had indexed this broken URL, users may be guided to "not found" pages and our page rank can decrease. Freezing the Slug can be a solution. I mean, save the slug only on "Add" action, and not in the "Update" one. But the slug can now represent something old or incorrect.
All the options have advantages and disadvantages. May be using some combination of them can some the problems.
What do you think about that?
I think the best option is this:
.com/object/AUTOINCREMENT_ID/SLUG_FIELD
Why?
First reason: the AUTOINCREMENT_ID is simple for the users to identify an object. For example, in an ecommerce site, If the user want to visit several times the page (becouse he's not sure of buying the product) he will recognize the URL.
Second reason: The slug field will prevent the problem of someone iterating over the webpage and will make the URL more clear to people.
This .com/object/10/ford-munstang-2010 is clearer than .com/object/c30204225d8311e185c3002219f52617
IDs are not strictly "iterable". Things get deleted, added back, etc. Over time, there's very rarely a straight linear progression of IDs from 1-1000. From a security perspective, it doesn't really matter. If views need to be protected for some reason, you use logins and only show what each user is allowed to see to each user.
There's upsides and downsides with every approach, but I find slugs to be the best option overall. They're descriptive, they help users know where there at and at a glance enable them to tell where they're going when they click a URL. And, the downsides (404s if slugs change) can be mitigated by 1) don't change slugs, ever 2) set up proper redirects when a slug does need to change for some reason. Django even has a redirects framework baked-in to make that even easier.
The idea of combine an id and a slug is just crazy from where I'm sitting. You still rely on either the id or the slug part of the URL, so it's inherently no different that using one or the other exclusively. Or, you rely on both and compound your problems and introduce additional points of failure. Using both simply provides no meaningful benefit and seems like nothing more than a great way to introduce headaches.
Nobody talked about the UUID field (django model field reference page) which can be a good implementation of the "hash id". I think you can have an url like:
.com/object/UUID/slug
It prevents from showing an order in the URL if this order is not relevant.
Other alternatives could be:
.com/object/yyyy-mm-dd/ID/slug
.com/object/kind/ID/slug
depending of the relevant information you want to have in the url