another technical blog...technically

Thursday, August 6, 2015

SharePoint 2013 'Page not found (404)' with catalog item page and standard page

If you read these blog post:
SharePoint 2010 ‘Page not found (404)’ page the way it should be and this Inconvenient Catalog Item Page and ‘Page not found’ (404) experience from the master Waldek Mastykarz , and you're here, you're one step closer to the solution.
This blog post extends the Mastykartz strategy with just few lines of code.
 Let's assume you developed those components:
  1. PageNotFoundHttpModule from SharePoint 2010 ‘Page not found (404)’ page the way it should be
  2. PageNotFoundWebPart from SharePoint 2010 ‘Page not found (404)’ page the way it should be 
  3. Something like MaventionCatalogItemReuseWebPart from Inconvenient Catalog Item Page and ‘Page not found’ (404) experience
PageNotFoundHttpModule and PageNotFoundWebPart works also for SharePoint 2013.
MaventionCatalogItemReuseWebPart simply redirects to site collection page not found url, but, since this is a server side redirection, the HTTP status code is 302 and the redirect location it's also well known, so i just modified PageNotFoundHttpModule in this way.
public class PageNotFoundHttpModule : IHttpModule
    {
        #region Const
        const string URL_RELATIVE_PAGENOTFOUND = "/Pages/PageNotFoundError.aspx";
        #endregion

        #region Fields
        private HttpApplication webApplication;
        #endregion

        #region Public methods        
        public void Init(HttpApplication context)
        {
            webApplication = context;
            webApplication.PreSendRequestContent += new EventHandler(webApplication_PreSendRequestContent);
        }

        public void Dispose()
        {
        }
        #endregion

        #region Event handlers
        void webApplication_PreSendRequestContent(object sender, EventArgs e)
        {
            HttpResponse response = webApplication.Response;
            HttpRequest request = webApplication.Request;

            if ((response.StatusCode == 404 && string.Compare(request.Url.AbsolutePath, URL_RELATIVE_PAGENOTFOUND, StringComparison.InvariantCultureIgnoreCase) != 0) ||
               (response.StatusCode == 302 && string.Compare(response.RedirectLocation, URL_RELATIVE_PAGENOTFOUND, StringComparison.InvariantCultureIgnoreCase) == 0))
            {
                webApplication.Server.TransferRequest(URL_RELATIVE_PAGENOTFOUND);
            }
        }
        #endregion
So what i added is essentially a new or condition.
Share:

Wednesday, July 29, 2015

Publishing by search: what about video in HTML field?

I think the title it's someway self-explaing: we know that when you add a video to a HTML field, SharePoint doesn't use HTML5 video tag, but it installs a web part which show the video itself using a player.
A video, in SharePoint, it's not simply a file, but a folder which contains that video file, maybe its renditions and other stuff i'm not interested in.
Video and assets generally are in a Asset Site which could be accessed from catalog, public site and many others.
If we want to simply use HTML5 video tag, we first need to find the correct video file, then we have to create a reusable content with these features:
  • User can select the video using the OOB AssetPicker
  • Reusable content automatically finds the video file
  • Reusable content automatically generates HTML5 video tag
So, let's first declare a reusable content for video with this HTML code


Why in the world i'm using an img tag?
Because i want the user to have a placeholder like this


Then include a JS (in master page or page layout: up to you) which:
  1. Binds a click event to every asset_video class object 
  2. On click opens OOB Asset Picker and let user choose an asset
  3. On Asset Picker close event, finds the video URL using REST
  4. Writes the HTML5 tag using the URL
 Below some code you can use
jQuery(document).ready(function () {
    initAddVideoTemplate();
    SP.SOD.executeOrDelayUntilScriptLoaded(function () {
        SP.SOD.executeOrDelayUntilScriptLoaded(function () {
            var ctx = SP.ClientContext.get_current();
            var site = ctx.get_site();
            ctx.load(site);
        }, "cui.js");
    }, "sp.js");
});

// Adds a listener on every HTML element with class "asset_video"
// You can add your own classes in this obj array, lister for all elements will be created
// The listener will make asset picker to be opened when you click on a asset_video
function initAddVideoTemplate() {
    var count = 0;
    var obj = { 'asset_video': '' }
    jQuery.each(obj, function (index, value) {
        jQuery('body').on('click', 'img[class="' + index + '"]', function () {
            var className = this.className;
            var idOldVideo = "reusableContentInsertVideo-" + rc_generateUUID();
            this.setAttribute('id', idOldVideo);
            if (!count) {
                jQuery.getScript('/_layouts/15/AssetPickers.js', function () { insertVideo(idOldVideo, className, value); });
                count += 1;
            } else insertVideo(idOldVideo, className, value);
        });
    });
};

// Asset picker configuration
function insertVideo(idOldVideo, className, value) {
    var assetPicker = new AssetPickerConfig("");
    assetPicker.ClientID = "";
    assetPicker.DefaultAssetLocation = "";
    assetPicker.DefaultAssetImageLocation = "";
    assetPicker.CurrentWebBaseUrl = "";
    assetPicker.AllowExternalUrls = "";
    assetPicker.ManageHyperlink = false;
    assetPicker.AssetUrlClientID = idOldVideo;
    assetPicker.AssetType = "Media";
    assetPicker.ReturnItemFields = 'Title,Name,Label,SourceUrl,FileRef';
    assetPicker.ReturnCallback = function () {
  
  // Self explaining variable names
        var video_AbsoluteUrl = arguments[0];
        var video_FolderName = arguments[1];

        var pathArray = video_AbsoluteUrl.split('/');

        var assetSite_Url;
        var assetList_DocLib;

        if (pathArray.length >= 5) {
            assetSite_Url = pathArray[0] + "//" + pathArray[2];
            assetList_DocLib = pathArray[3];

   // Helps you to rebuild the folder relative URL
            for (i = 4; i < pathArray.length - 1; i++) {
                assetList_DocLib = assetList_DocLib + "/" + pathArray[i];
            }

            // Finds the HTML img tag to substitute
            var idCalled = jQuery(this).attr('AssetUrlClientID');

            if (undefined !== arguments &&
    arguments.length > 3 &&
    assetSite_Url !== undefined &&
    assetList_DocLib !== undefined) {

    // REST url generation
    // Using URL and folder, you can request the first file of the video folder, which is the default video rendition
                restUrl = assetSite_Url + "/_api/Web/GetFolderByServerRelativeUrl('/" + assetList_DocLib + "/" + video_FolderName + "')/Files?$select=ServerRelativeUrl,Name&top=1";

                jQuery.ajax({
                    url: restUrl,
                    type: "GET",
                    crossDomain: true,
                    dataType: "json",
                    headers: { "Accept": "application/json; odata=verbose" },
                    success: function (data) {
                        // If success, generate che HTML
                        var response = data.d.results;
                        if (response.length > 0) {
                            var videoUrl = assetSite_Url + response[0].ServerRelativeUrl;
                            var fileName = response[0].Name;
                            var extension = fileName.split('.').pop();

                            if (jQuery("#" + idCalled).length > 0)
                                jQuery("#" + idCalled).first().replaceWith("");
                        }
                    },
                    error: function (data) {
                        alert('Error');
                    }
                });
            }
        }
        else {
            alert("Error");
        }
    };

    var imageAsset = new LinkAsset("");
    imageAsset.LaunchModalAssetPicker(assetPicker);
};

// Generate a ID 
function rc_generateUUID() {
    var d = new Date().getTime();
    var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = (d + Math.random() * 16) % 16 | 0;
        d = Math.floor(d / 16);
        return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16);
    });
    return uuid;
};

Strategy: catalog connection with managed metadata multivalue field

Everybody knows about SharePoint catalog connection... maybe

What if you have a pre-existing catalog connection and your customer asks you to switch from a single value field (generally ItemCategory) to a multivalue field?

In a ideal world you should quote this and say "don't do it, don't even think about it"

In a business world you'll say "Yes, why not?".
I tried to convert the single value field in a multivalue field... trust me... don't try this if you don't like strange side effects.

AS IS
This is the typical product catalog configuration:
  • A product catalog site which is used as backend website
  • Contents are crawled by search service application
  • Public sites connect to indexed catalog


Solution
To understand next steps, mind every link under categories navigation term is allowed.
Let's assume you have a term set like that
  • Category 1
    • Subcategory 11
    • Subcategory 12
  • Category 2 
    • Subcategory 21
The catalog connection automatically creates these category friendly urls:

http://site/category1
http://site/category1/subcategory11
http://site/category1/subcategory11
http://site/category2
http://site/category2/subcategory21

So let's assume i have a product named Test, which is tagged on ItemCategory with term "Subcategory 11", i'll go to URL http://site/category1/subcategory11/test .

But what if i tell you that links like these below will not repond with a 404?
http://site/category1/test
http://site/category1/subcategory12/test
http://site/category2/test
http://site/category2/subcategory21/test

This behaviour will be extremely useful for us.

Editing product catalog
Because you don't want to destroy catalog connection, you just have to add a TaxonomyFieldTypeMulti field to the product catalog item content type

  
    
   
     TextField
     {024FECDC-E8A7-4DAC-BEB1-E9C373708BE5}
   
    
  

Link this new field to the term set you use to tag catalog items.
After that, you can create a event receiver in order to write the first  value entered in MultivalueItemCategory field in ItemCategory field (or the field you use for the catalog connection).
Create Content Search Web Part replacements
This is the most annoying part, we have to create a Web Part that we will use in category pages.
This web part does essentially three things:
  1. Recover the current navigation term
  2. Make a query searching che term related to the navigation term, in the MultivalueItemCategory we defined above
  3. Substitute Path values (catalog item friendly url) in order to make links relative to the current navigation term
  4. Show results
The core methods are About the single product page you can create a web part or a page layout, which has to contain methods similar to the previous web part.
This web part, must get the product using the navigation term and the friendly url segment and show the result.

Editing publishing site 
In pages document library, you have to create a category page (which contains the category CSWP replacement), and a product page (which contains the product CSWP replacement) and then edit target page settings for terms like this


And that's all folks.

Deleting a host named site collection... for real

A simple problem: you have to delete a host named site collection in order to recreate it.
When you try to recreate it, you get an error: SharePoint says the site collection already exists... if you restart the machine you'll be able to recreate it.
What if you cannot restart the machine whenever you want (read production environment).
$variable = Get-SPSite "http://already.deleted.site"
$variable
You'll get an output like this


Now recreate the site collection without problems ;)

Saturday, June 20, 2015

Onion Pi: Set up as a Wifi-to-Wifi Tor middlebox

Some weeks ago i bought a Raspberry Pi 2 in order to replace my old Raspberry Pi as media center...
So i had some free time and unused spare wifi connectors and i decided to create a tor access point using this guides.
https://learn.adafruit.com/onion-pi/overview  
https://learn.adafruit.com/setting-up-a-raspberry-pi-as-a-wifi-access-point/overview
This access point is completely useless in my home, but i was curious about it, outside it was rainy and you know, cuorisity is the cure fore boredom.
Those guides are quite self-explaining, but what if you want to set up a Wi-Fi to Wi-Fi middlebox?
The guide does not provides so muchi infos about it.
Let's assume:
eth0: adapter you connect to the main router wlan0: adapter you want to connect to the main router wlan1: adapter you want to use as access point
what you have to do using iptables is:
Create a network translation between the adapter wlan1 and the adapter wlan0 (in my case also hostap uses as interface wlan1)
sudo iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE
sudo iptables -A FORWARD -i wlan0 -o wlan1 -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A FORWARD -i wlan1 -o wlan0 -j ACCEPT
Set up new iptables rules, redirecting also wlan0 traffic to torrc TransPort 9040 (adding also the well knows exceptions)
sudo iptables -t nat -D PREROUTING -i wlan0 -p tcp --dport 22 -j REDIRECT --to-ports 22
sudo iptables -t nat -D PREROUTING -i wlan0 -p udp --dport 53 -j REDIRECT --to-ports 53
sudo iptables -t nat -D PREROUTING -i wlan0 -p tcp --syn -j REDIRECT --to-ports 9040
That's all folks.
Share:

SharePoint 2013 Azure farm accessible from outside part 2: what about friendly URLs

Have you read this? SharePoint 2013 Azure farm accessible from outside
I discovered it doesn't work for friendly urls, it shows hyperlinks targeting site collection, so it's not possible to reach other pages. Moreover you'll get this error if you only create a outbuond rule for URL rewriting because of the gzip compression.
Outbound rewrite rules cannot be applied when the content of the HTTP response is encoded ("gzip").
What you have to do is something like this:
  • Introduce a outbound rule which rewritest some links
  • Introduce rewrite rules for gzip compression
The example below assumes:
  • Local site collection is: http://sitecollection.example.cloud
  • Public azure url is: http://publicsite.example.azure

    
        
            
                
                    
                    
                        
                        
                    
                    
                
            

            
                
                    
                    
                
                
                    
                    
                

                
                    
                        
                    
                    
                        
                    
                
                    
        
    

And that's all... for now!
Share:

Monday, June 8, 2015

Targeting contents using XRANK in SP2013 problem

Lately i had to create a content priority/targeting system in SharePoint 2013, using search capabilities in a public context (so no target audience available).
What you'll find below it's a targeting system based on managed metadata content tagging and dinamically generated queries using XRANK directives.
Let's think about a enterprise model with a hierarchy like this
  • Channel 1 (00000000-0000-0000-0000-000000000100)
    • Network 1 (00000000-0000-0000-0000-000000000110)
      • Agent 1 (00000000-0000-0000-0000-000000000111)
      • Agent 2 (00000000-0000-0000-0000-000000000112)
    • Network 2 (00000000-0000-0000-0000-000000000120)
    • ...
    • Network n (...)
  • Channel 2 (00000000-0000-0000-0000-000000000200)
  • ...
  • Channel n (...)
This could be represented as a hierarchical term set in Managed Metadata Service Application.
Now, let's assume we have these contents:
  • Page A, tagged with "Channel 1"
  • Page B, tagged with "Network 1"
  • Page C, tagged With "Agent 1"
Following this article http://techmikael.blogspot.it/2014/03/s15e01-kql-basics.html we can target contents using Search.
For example, if i am "Agent 1" and i want to obtain contents in this order
  1. Page C
  2. Page B
  3. Page A
i can use a query like this
(((owstaxIdTargeting:"GP0|#00000000-0000-0000-0000-000000000100" XRANK(cb=1))
owstaxIdTargeting:"GP0|#00000000-0000-0000-0000-000000000110" XRANK(cb=10))
owstaxIdTargeting:"GP0|#00000000-0000-0000-0000-000000000111" XRANK(cb=100))


Basically, i'm boosting contents created for "Agent 1", then contents for "Network 1", then "Channel 1".
Great? No. This method apparently works.
In this query i used XRANK, which boost the rank score... boost means SharePoint assign a score using ranking models you can boost manually using XRANK query directive.
This also means that rank scores could be scrambled by a lot of rank model rules, take a look to this articles:
This leads me to think i can create a "Fake ranking model" for those queries, useful only for this content targeting technique.
This model basically assigns a 0 score to all contents and it simply does NOTHING, so only XRANK values will be considered.

 
  
   
    0
   
   
    0
   
  
  
  
 

You can install this rank model on SharePoint farm and use it in your search based query (programmatically and/or in content search query web part).
Share:

SharePoint 2013 Azure farm accessible from outside

I gave you the possibility to create a SharePoint 2013 farm from scratch with ASPM... don't you know what it is? You can download it here
So, let's assume you have a site collection (http://sitecollection.example.cloud) on this SharePoint 2013 farm e you want to make it accessible to your customer, manager and so on...
Open IIS Manager and head to Default Web Site, then URL Rewrite (if you don't have this option on your IIS please downlad URL Rewrite module from Web Platform Installer).

Then, create a new blank rule

like this

And that's all.
I noticed you will have some problems with users adding, so it's possible you can experience some problems with other features.
Maybe further settings could be necessary.

Thursday, June 4, 2015

Updating term set in SP2013

First of all, this is not something i discovered on my own, i just read it somewhere in the web and I never found the original post again, maybe because it was written in german (credits to the original author), so I decided to write this useful trick in english in order to help more people to accomplish the same task: updating a termset from code behind.
Let’s assume you need to do this from an application page, you need to use the SPSecurity and RunWithElevatedPrivileges as usual.
SPSecurity.RunWithElevatedPrivileges(() =>
{
 using (SPSite elevatedSite = new SPSite(site.ID, site.Zone))
 {
  TaxonomySession taxonomySession = new TaxonomySession(elevatedSite);
  //Do here the update work
 }
});
Ok! This code simply doesn’t work: this is due to the fact you have to switch the context to the service account one like this.
SPSecurity.RunWithElevatedPrivileges(() =>
{
 using (SPSite elevatedSite = new SPSite(site.ID, site.Zone))
 {
  HttpContext oldContext = HttpContext.Current;

  try
  {
   HttpContext.Current = null;
   TaxonomySession taxonomySession = new TaxonomySession(elevatedSite);

   //Do here the update work
  }
  finally
  {
   HttpContext.Current = oldContext;
  }
 }
});
le jeux sont fait
Share:

Friday, May 29, 2015

Azure SPFarm Manager 1.2 released

Since they changed something, i had to change something.
Here's the new verion, what's new? Nothing, simply MS changed something about image names and i have to readapt the Build script.
Now it works like a charm.
The old config.xml will not work anymore (only if you wanna BUILD another farm).
ImageFamily attribute was deprecated and now you have to use ImageLabel, please have a look to the new package ;) .
Download it here
Share:

Me, myself and I

My Photo
I'm just another IT guy sharing his knowledge with all of you out there.
Wanna know more?