Thursday, September 28, 2017

BCS (BDC) metadata caching bug - external list always shows loading

According BCS architecture 
there is a model metadata caching.  Sometimes, after updating model (by Central Administration, or provisioning by farm scope feature there is a bug occurring when external list is being viewed – loading icon always showing.

This happens when the parameters of connection to the database changed in the model because of some problem in the metadata model caching.
To fix this bug - it is necessary with the Sharepoint Designer to change connection settings (for example Authentication Mode), save and then return all back. This manipulation will reset the cache. Don’t forget about one minute:
BDC caches the objects when they first load. A timer running on each server looks for any changes to the metadata objects one time each minute. If it sees a change to a metadata object, it clears the cache and then reloads it. As a result, after you change metadata, you must wait up to a minute for changes to propagate to all the servers in the farm. The changes are immediate on the server where you make the change.

Friday, March 17, 2017

Configuration an environment for apps for SharePoint 2013

There are a lot of articles in internet about it (like this on msdn:  https://technet.microsoft.com/en-us/en-en/library/fp161236.aspx), but in my one I will share my own experience of full cycle which begins with configuration of servers and ends with deploying first app.

1. Configuration of DNS server.

We have two virtual servers in our development environment:  
  •          Domain controller (DC), Domain name - dev.com
  •          SharePoint 2013 server (SP2013)
On DC server in DNS Manager I added

a)       New Host(A or AAAA) on dev.com with the same IP address:


b)       New Alias (CNAME) on dev.com like this:


2. I configured SharePoint 2013 farm by Power Shell script below:

Add-PSSnapin "Microsoft.SharePoint.PowerShell"
$appDomain = "apps.sp2013.dev.com" #your domain
$appPrefix = "app"
$accountName = "dev\spfarm" #login for pools
$accountPassword = "*********" #password

net start spadminv4
net start sptimerv4
Write-Host "Start" -foregroundcolor "green";
$existedAppDomain = Get-SPAppDomain
$existedAppPrefix = Get-SPAppSiteSubscriptionName


if (!$existedAppDomain -or !$existedAppPrefix)
{
        if (!$existedAppDomain)
        {
           Set-SPAppDomain $appDomain -ea:Stop
        }

        Get-SPServiceInstance | where{$_.GetType().Name -eq "AppManagementServiceInstance" -or $_.GetType().Name -eq "SPSubscriptionSettingsServiceInstance"} | Start-SPServiceInstance
        Get-SPServiceInstance | where{$_.GetType().Name -eq "AppManagementServiceInstance" -or $_.GetType().Name -eq "SPSubscriptionSettingsServiceInstance"}
        $User = $accountName
        $account = Get-SPManagedAccount  -Identity $User -ea:Silently
        if(!$account)
        {
           $PWord = ConvertTo-SecureString –String $accountPassword –AsPlainText -Force
           $Credential = New-Object –TypeName System.Management.Automation.PSCredential –ArgumentList $User, $PWord
           $account = New-SPManagedAccount -Credential $Credential -ea:Stop
        }
       
        $appPoolSubSvc = Get-SPServiceApplicationPool -Identity SettingsServiceAppPool
        if (!$appPoolSubSvc)
        {
           $appPoolSubSvc = New-SPServiceApplicationPool -Name SettingsServiceAppPool -Account $account
        }
        $appPoolAppSvc = Get-SPServiceApplicationPool -Identity AppServiceAppPool
        if (!$appPoolAppSvc)
        {
           $appPoolAppSvc = New-SPServiceApplicationPool -Name AppServiceAppPool -Account $account
        }

        $appSubSvc = New-SPSubscriptionSettingsServiceApplication –ApplicationPool $appPoolSubSvc –Name SettingsServiceApp –DatabaseName SettingsServiceDB
        $proxySubSvc = New-SPSubscriptionSettingsServiceApplicationProxy –ServiceApplication $appSubSvc
        $appAppSvc = New-SPAppManagementServiceApplication -ApplicationPool $appPoolAppSvc -Name AppServiceApp -DatabaseName AppServiceDB
        $proxyAppSvc = New-SPAppManagementServiceApplicationProxy -ServiceApplication $appAppSvc

        Set-SPAppSiteSubscriptionName -Name $appPrefix -Confirm:$false -ea:Stop
             Write-Host "End" -foregroundcolor "green";
}Write-Host "Registration Completed" -foregroundcolor "green";

3. Next, I created a simple SharePoint 2013 add-n app «Hellow word» and deployed it on my dev site collection.
But then I opened my deployed app I was prompted login and password multiple times with a blank page at the end. I modified my registry the way of this article described.   
I tried open my app again, entered login and passport and 'voila' the app was opened! But it was very strange:

4.  To fix it I created an empty root site collection on dev Web Application, that was all! 

5.  I also added the portal to intranet zone in IE:

Wednesday, March 15, 2017

Fixing search index after taxonomy field values changed in SharePoint

We have custom managed metadata column Tags in some lists in our SharePoint 2013 portal and this column is mapped to Managed Property Tags. After first full crawl everything was fine: customer edited items (filled tags column) and after incremental crawl completed search index updated correctly.

But if Taxonomy term store was modified  (Default label was changed, terms were merged etc.) there was a problem  - search index had old terms.

After researching the issue I found a solution.

There is standard Timer Job Taxonomy Update Scheduler  which starts every hour by default:


After Timer Job completed (you can run it manually) you need start a Full Crawl for search content source. Then the search index will be fixed and you would find items in search result by modified taxonomy field values.

P.S. Here is an article about Taxonomy Update Scheduler in 2010. In SharePoint 2013 you don’t need to wait for completing of this Timer Job to see term changes on list views or forms. It’s actual only for search. 

Wednesday, February 15, 2017

Sharepoint 2013 CSR ajax paging issue

There is a common SharePoint 2013 CSR pagination issue :
if you override Header/Footer/Item template it will break ajax pagination –
and after paginator button is clicked the whole page will load.

After spending a lot of time finding standard way of fixing this I found a solution without of using Client Object model and jquery pagination and based on this article.

1. If you need override footer template you should use ctx.ListData.NextHref and ctx.ListData.PrevHref to render html like this:

SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
    Templates: {
      Footer: function (ctx) {
        var footerHtml = '<table class="ms-bottompaging"><tr>';
        var next = ctx.ListData.NextHref;
        var prev = ctx.ListData.PrevHref;

        footerHtml += "<td><a href='javascript:void(0);' onclick=\"RefreshPageTo(event, &quot;" + prev + "&quot;);return false;\"><span><img class='ms-promlink-button-left' src='/_layouts/15/images/spcommon.png?rev=23'/></span></a></td>";

        footerHtml += "<td><ahref='javascript:void(0);' onclick=\"RefreshPageTo(event, &quot;" + next + "&quot;);return false;\"><span><img class='ms-promlink-button-right' src='/_layouts/15/images/spcommon.png?rev=23'/></span></a></td>";
        

        footerHtml += "</tr></table>";
        return footerHtml;
      }
    }});

But in most cases I do not advise override footer - OOTB footer template is  good.

2. If you override the header template, you should do the next trick. It turns out that the 'ms-listviewtable' table is generated by OOTB header template. If you override the header template, it is not generated and thus paging goes non-Ajax yet. You need generate it and insert into rendering html code. There is standard core function RenderTableHeader (clienttemplates.js) , but you need only last part of generated by RenderTableHeader code:

overrideContext.Templates.Header = function (ctx) {
        var headerHtml = RenderTableHeader(ctx) + "</table>";
        headerHtml = headerHtml.substring(headerHtml.indexOf("<table onmousedown") - 9);
        headerHtml += "<div class='news-main'>News: </div><div class='rh-rcol-button'>" +
        "<a href='" + _spPageContextInfo.webServerRelativeUrl + _constants.GlobalNews.Url + _constants.GlobalNews.Views.Filtered + "'>All news <img src='~site/_layouts/15/Comp/EAE.LUK.GR/img/icon_go.png'/></a></div><br>";
        return headerHtml;
    };

3. If you override the item template, there are two cases:
а) you are rendering new rows of html table;
In this case if you table is standard – you don’t need close you table in header template  after calling RenderTableHeader(ctx). But if your need to apply custom css class to table you HAVE TO CLOSE 'ms-listviewtable' OOTB TABLE AND CREATE NEW TABLE with custom css class in header template, because if you just apply custom class to  OOTB TABLE  paging goes non-Ajax yet again.

b) you are rendering new rows by different html elements(div, etc.)
In this case there is another problem - Ajax paging will working but after page number is changed item area will be empty... The trick is to append them by hands:

overrideContext.Templates.Item = function (ctx) {
        var html =
        "<div class='listview-date-field'>" ….."</div>";
        // check whether the Ajax request is (found empirically)
        if (ctx.operationType > 0) {
            var startElement = $("#scriptBody" + ctx.wpq);
            if (startElement.nextAll('div').length > 0) {
                startElement = startElement.nextAll('div:last');
            }
            startElement.after(html);
        }
        return html;
    };

The code $("#scriptBody" + ctx.wpq);  is for the case of multiple web-parts on the page.


Sometimes,  this code will not working and  page listing breakes. The cause is different order of loading javascript files and clvp object of 'ms-listviewtable' OOTB TABLE is undefined.

To fix it you need to call  InitAllClvps() in  OnPostRender  event handler:
overrideContext.OnPostRender =  function (ctx) {
        fixContext();     
    };

function fixContext() {
        var ctx = window.ctx;
        if (ctx) {
            ctxLocal = window["ctx" + ctx.ctxId];
            if (!ctxLocal || !ctxLocal.clvp) {
                EnsureScript("inplview", typeof InitAllClvps, function () {
                    if (ctxLocal.clvp == null)
                        InitAllClvps();
                });
            }
        }

    }