Thursday, February 26, 2015

SharePoint Search using Keyword Queries and Result Sources

Working on a SharePoint 2013 Farm solution I found that there are many blogs out there that state that we should use the "query.SourceId" to indicate which source should be used by the Keyword query.

Now the SourceId is a Guid and can be found by asking the search service proxy. To get the search service proxy the developer would need to create some mechanism to reverence the proxy by its name. This is all server side and not the way to do it.


 

There is a much better way of pointing towards a Result source.

The Keyword query supports two properties for this the "SourceName" and the "SourceLevel" properties.

Here is an example of how these can be used in a keyword query to use a Result Source named "NewsItems"


 

SPSite site = SPContext.Current.Site;

KeywordQuery query = new
KeywordQuery();

query.SelectProperties.Clear();

string[] propertiesToReturn = { "ListItemID", "Title", "Created", "LastModifiedTime", "ExpirationTime", "Contents", "WebId", "SPSiteURL" };

query.SelectProperties.AddRange(propertiesToReturn);

query.Properties["SourceName"] = "NewsItems";

query.Properties["SourceLevel"] = "SPSiteSubscription";

query.RowLimit = int.Parse(ParentWebPart.PageSize);


 

SearchExecutor searchExecutor = new
SearchExecutor();

ResultTableCollection resultTableCollection = searchExecutor.ExecuteQuery(query);

var resultTables = resultTableCollection.Filter("TableType", KnownTableTypes.RelevantResults);

var resultTable = resultTables.FirstOrDefault();

DataTable results = resultTable.Table;


 

foreach (DataRow row in results.Rows)

{

System.Diagnostics.Debug.Writeline( row["Title"].ToString());

}    

Friday, February 13, 2015

ASP.NET Classic mode and the magic control ID’s

Having worked in a MVC environment returning to a project that uses ASP Server Controls is rather painful for a multitude of reasons. The one that hurts the most is the fact that asp.net controls generate their own client side ids. In doing that they really destroy the fluent communication between server side and client side code.

Pinpointing the asp.net control and manipulating it using standard libraries like JQuery isn't as easy as it should be.

But now with SharePoint 2013 we have the .NET 4.0 Framework and with that comes the option to tell ASP.NET to keep his hands off of the control id's.

This is the magic word: ClientIDMode='Static'

With the clientidmode set to static we are able to set the server id and asp.net will leave it the way we set is. The developer has the responsibility to make sure that every id is unique

Friday, January 30, 2015

Manually created SharePoint List definitions using XML a bad practice.

While developing SharePoint solutions using Visual Studio we can create lists and views using XML. When we choose to use this we can use a visual editor within Visual Studio. It is nice looks like this.


This editor will create the list definition and it will do so correctly. However the editor is limited in it's functionality.
For instance in the example above the fields displayed are from an external list, the editor can't cope with that. Forcing the developer to manually edit the XML.

This is where thing go wrong, manually edited xml definitions are not validated they are just consumed by SharePoint. Here is a real life example of a View I found.

<ViewFields>
  <!--<FieldRef Name="LinTitle"></FieldRef>-->
<FieldRef Name="TestKolom" /><FieldRef Name="ConcernNaam" /></ViewFields>

 
 

The "TestKolom" field did not exist within the project, but the definition got imported and applied by SharePoint. The result was that everything looked nice at first glance until a site administrator tried to edit the view on the list. He couldn't, at that point SharePoint raised an exception. The project had formal testers and a iterative process that would guaranty quality, they failed to find this problem.

Thinking about a solution making sure that problems like these are prevented. I can only come up with one do not edit the xml definitions manually.

  • Use the Visual Studio editor and learn to live with the fact that you cannot define everything.
    This will result in an increase in manual creation of lists and views using the SharePoint user interface and thus in the increase of installation manuals.
  • Use code to create lists and their views.
    When using code it is much harder to introduce errors in list definitions. Taking the example above I would not be able to add a non existing field to a view using code. The down side of creating list definitions in code is that it generally takes more time to create the list.

Edit 13-2-2015: Having spoken to Frank Marasco on the Office 365 Summit in Amsterdam, it is really only option 2 that is really viable. Just don't do XML definitions anymore, because these don't upgrade. Just learn how to do it the proper way and use Code

Wednesday, January 21, 2015

Runtime Exception building a solution from Visual Studio

Today I encountered the situation in which one of my assemblies gave the following exception

MissingFieldException was unhandled
An unhandled exception of type 'System.MissingFieldException' occurred in ...
Additional information: Field not found:

Visual studio gave the following hint: if a field in a class library has been removed, recompile any assemblies that reference that library.

After adding a new method to the failing assembly and calling it as expected I got the following exception: An unhandled exception of type 'System.MissingMethodException' occurred in

Somehow Visual Studio did not update the assembly, it may be that Visual Studio did not detect that the assembly changed or the assembly got locked and visual studio couldn’t update the assembly.

The solution was: Removing the assembly from the GAC C:\Windows\Microsoft.NET\assembly\GAC_MSIL

Monday, July 14, 2014

MVC 5 SharePoint 2013 and Data Annotations

 

Within MVC they have this great way of validation, they specify the restrains the data has to abide to on the model class. Unfortunately most data annotations that are available out of the box do not support SharePoint as backend data system. So You will soon find yourself in need to develop your own data annotations. To make live a little easier on you here is an example on how to make one. The example data annotation will test the list field combination to make sure that the value entered is unique.

using Microsoft.SharePoint.Client;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;

namespace System.ComponentModel.DataAnnotations
{
public sealed class UniqueValueAttribute : ValidationAttribute
{
private const string _defaultErrorMessage = "'{0}' moet uniek zijn";
private string _listName;
private string _fieldName;
private string _errorMessage;

public UniqueValueAttribute(string ListName, string FieldName) : base(_defaultErrorMessage)
{
_listName = ListName;
_fieldName = FieldName;
_errorMessage = _defaultErrorMessage;
}

public UniqueValueAttribute(string ListName, string FieldName, string ErrorMessage):base(ErrorMessage)
{
_listName = ListName;
_fieldName = FieldName;
_errorMessage = ErrorMessage;
}

public override string FormatErrorMessage(string name)
{
return string.Format(_errorMessage, name);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
return null;

string result = null;
SharePointContext spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext.Current);
if (spContext == null)
return new ValidationResult("SharePoint context was not found. Did you leave this screen open for a long time?");

using (ClientContext context = spContext.CreateAppOnlyClientContextForSPHost())
{
if (context != null)
{
var lst = context.Web.Lists.GetByTitle(_listName);
if (lst == null)
{
result =
String.Format("List name {0} could not be found in the SharePoint Web", _listName);
throw new ValidationException(result);
}
else
{
CamlQuery query = new CamlQuery();
string whereXml = @"<Where><Eq><FieldRef Name='{0}'></FieldRef><Value Type='Text'>{1}</Value></Eq></Where>";
whereXml =
String.Format(whereXml, _fieldName, value.ToString());
string viewXml = "<View><Query>{0}</Query></View>";
query.ViewXml =
String.Format(viewXml, whereXml);
ListItemCollection queryResult = lst.GetItems(query);
context.Load(queryResult);
context.ExecuteQuery();
if (queryResult != null && queryResult.Count > 0)
{
result =
String.Format(_errorMessage, _fieldName);
return new ValidationResult(result);
}
return null;
}
}
else
{
return new ValidationResult("No sharePoint context");
}
}
}
}
}

The use of the DataAnnotation is the same as it allways was in MVC just place the attribute on top of the property that needs the validation.


[UniqueValueAttribute("Foxy Documents", "Title", "De kamervraag met deze naam bestaat al")]
[Display(Name= "Naam")]
[Required(ErrorMessage="De naam van de Kamervraag is verplicht")]
public string Titel
{
get;
set;
}

To make sure that datetime fields where not defined in the past I also created a data annotation to validate datetime fields. I Noticed that there weren't many working answers on the forums so here you go

 

    public class DateTimeLargerThenNowMinus : ValidationAttribute
{
private const string _defaultErrorMessage = "Date is not allowed";
private string _errorMessage;
private DateTime _validationDate;

public DateTimeLargerThenNowMinus(int days=0, int months=0, int years=0, string ErrorMessage=_defaultErrorMessage)
:
base(ErrorMessage)
{
_validationDate =
DateTime.Now.AddDays(days * -1);
_validationDate = _validationDate.AddMonths(months * -1);
_validationDate = _validationDate.AddYears(years * -1);
_errorMessage = ErrorMessage;
}

public override string FormatErrorMessage(string name)
{
return string.Format(_errorMessage, name);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
return null;

var date = (DateTime)value;
if (date > _validationDate)
return null;
else
{
return new ValidationResult(_errorMessage);
}
}
}

Thursday, July 10, 2014

SharePoint 2013 X-FrameOptions in MVC 5

 

Modern browsers do not allow pages to be displayed within I-Frames by default. The idea of this security measure is to prevent ‘click-jacking’. Click Jacking is best explained by an example: Lets say you are working for a bank and create an internet page. Now a hacker can try to display your banking page in an I-Frame on his own site and adds a little extra functionality a key logger and a click jacker. With doing this the hacker will try to capture all the users actions and will potentially be able to get the users password.

This is why modern browsers prevent pages to be displayed in I-Frames by default.

However, within the secured confines of a SharePoint communicating with a on premise provider hosted app this security measure can be a hindrance.  Because SharePoint won’t be allowed to show the provider hosted pages in I-Frames. The good news, the developer of the pages can turn the security measure off for his application by stating that displaying the pages in I-Frames is allowed. This can be done with the ‘SuppressXFrameOptionsHeader’  in the Global.asax.

    public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);

AntiForgeryConfig.SuppressXFrameOptionsHeader = true;
}
}
This option was a default setting in MVC 4 however in the MVC 5 template for SharePoint this option needs to be added manually.

Thursday, April 10, 2014

Registering Provider hosted apps as trusted

 

Working with provider hosted apps you’re going to work with trusts. The combination of IssuerId and a Certificate will be used as identifier and apps that are signed with the combination of those two can be submitted to SharePoint as trusted.

The software supplier will deliver a certificate and an IssuerId. These can be registered as trusted using the following script: (replace the blue stuff and mind the IISReset)

# load in SharePoint snap-in
$snapin = Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue -PassThru
if ($snapin -eq $null) {
Write-Error "Unable to load the Microsoft.SharePoint.PowerShell Snapin! Have you installed SharePoint?"
return
}

$publicCertPath = "C:\Certificates\TestCert.cer"
$certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($publicCertPath)
New-SPTrustedRootAuthority -Name "TestCert" -Certificate $certificate
$realm = Get-SPAuthenticationRealm
$specificIssuerId = "11111111-1111-1111-1111-111111111111"
$fullIssuerIdentifier = $specificIssuerId + '@' + $realm
New-SPTrustedSecurityTokenIssuer -Name "TestCert" -Certificate $certificate -RegisteredIssuerName $fullIssuerIdentifier -IsTrustBroker
$fullIssuerIdentifier
iisreset


Removing the trust can be done by first retrieving the SecurityToken using


Get-SPTrustedSecurityToken


Getting the Id from the result and running


Remove-SPTrustedSecurityTokenIssuer –Identity …..The Id found in the previous step ……..