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 ……..

Friday, March 28, 2014

Problems with the SharePoint Provider hosted Apps

There are many manuals out there that will help with the basic installation of a provider hosted SharePoint environment. However, I still encountered exceptions on my machine and on machines of my fellow developers. For me this is the reason to write them down.

First up the Token Helper call: “string actorTokenString = new JsonWebSecurityTokenHandler().WriteTokenAsString(actorToken);” breaks with a System.Security.Cryptography.CryptographicException “Invalid provider type specified.”

This very useful exception is trying to tell you that the Cryptography can’t handle the Certificate “the key, pfx file” you are using. When you go to your web.config you’ve probably got something like this:

    <add key="ClientSigningCertificatePath" value="C:\Certificates\Wingtip.pfx"/>
<
add key="ClientSigningCertificatePassword" value="Password1"/>
<
add key="IssuerId" value="11111111-1111-1111-1111-111111111111"/>


The WingtipServer.pfx is of a wrong type or the .net framework is of a version that is not capable of understanding the type of certificate you are using. So this problem can be solved by : 


Replacing the pfx file with a new pfx file. Which is easy to do on a developer machine. Just go to IIS Click on your server, double click on the “Server Certificates” icon Click “Create Self Signed Certificate” This certificate will work.



The follow-up on the previous problem is: Getting a 401 access denied when connecting to the AppWeb this happens on the ExecuteQuery of a CSOM call. For example you are doing something like this:

            string SPAPPWebUrl = Request.QueryString["SPAppWebUrl"];
Uri appWebUrl = new Uri(SPAPPWebUrl);

//We are going to the appweb using nothing but the app only permissions
using (var contextAppWeb = TokenHelper.GetS2SClientContextWithWindowsIdentity(appWebUrl, null))
{
var web = contextAppWeb.Web;
contextAppWeb.Load(web);
contextAppWeb.ExecuteQuery();


When the ExecuteQuery method breaks with a 401 you probably forgot to register the certificate with the SharePoint farm. The following script was used in my case to register my newly created certificate with the SharePoint farm.

# 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\Wingtip.cer"
$certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($publicCertPath)
New-SPTrustedRootAuthority -Name "WingtipCert" -Certificate $certificate
$realm = Get-SPAuthenticationRealm
$specificIssuerId = "11111111-1111-1111-1111-111111111111"
$fullIssuerIdentifier = $specificIssuerId + '@' + $realm
New-SPTrustedSecurityTokenIssuer -Name "WingtipCert" -Certificate $certificate -RegisteredIssuerName $fullIssuerIdentifier -IsTrustBroker
iisreset






Handeling AppEvents are failing on a developer machine. Using IIS Express Visual Studio will register a certificate of it’s own with the IIS Server on which SharePoint is running. But it won’t register that certificate with the SharePoint farm. That’s what needs to be done. It’s the same script as registering for the 401 just using a different certificate. You’ll need to go to IIS click on the server name –> Server Certificates and click Export



image


 



Server Error in ‘/’ Application. The specified network password is not correct. System.Security.Cryptography.CryptographicException. The ClientSigningCertificatePassword defined in the web.config is not correct.


System.Security.Cryptography.CryptographicException Access Denied.The account used for your application pool doesn’t have enough rights.The account needs to be able to access the local machine certificate store.


Server Error in ‘/’ Application The system cannot find the file specified. System.Security.Cryptography.CryptographicException. This exception may be caused by a wrong ClientSigningCerticicatePath in the web.config file.

TokenHelper.CreateAppEventClientContext returns a TypeInitializationException, Inner Exception shows a System.Security.Cryptography.CryptographicException: "The system cannot find the file specified" possible cause is that the certificate defined in the web.config is not pointing to the right folder or there is a typo in the config.

Good luck SharePointing!

Thursday, January 9, 2014

SharePoint 2013 CSV Bulk Taxonomy TermSet Importer/Exporter

 

I noticed that the tool that worked perfectly for SharePoint 2010 for exporting and importing term sets was not available for SharePoint 2013.|
All credits still go to the original developer, all I did was change some references and recompile the project.

It can be downloaded here : SharePoint 2013 CSV Bulk Taxonomy TermSet Importer/Exporter

Wednesday, November 6, 2013

SharePoint October 2013 patch 503 error

 

Just installed the Oktober patch for SharePoint server 2013. http://support.microsoft.com/kb/2825647 
I knew that after installing a patch you should always run the SharePoint Products 2013 Configuration Wizard so I did. However the patch still left me with a “HTTP Error 503. The service is unavailable.” error.

Solving this is rather easy, SharePoint patches have the tendency of shutting down the IIS application pools. So all you have to do is start up IIS manager navigate to the pools and hit the ‘play’ button.

 

Here you can see that the installation of the patch stops all pools but doesn’t start them back up again after finishing the installation.

image

Tuesday, September 17, 2013

SharePoint 2013 getting the cross site collection data

 

Microsoft has a supported limit of 200 GB per content database in a SharePoint 2013 Farm for general usage scenario's (1).  In effect Microsoft recommends dividing content over multiple site collections when ever the site collection would risk passing the 200GB. This because of performance and flexibility benefits in distributing the data over content databases. Creating a single site collection results in a single content database. When the site collection grows, so will the content database. In an organization that utilizes a lot of data the content database can grow into unmanageable proportions. An example of an unmanageable situation would be a Service Level Agreement (SLA) that states that the SharePoint environment should be up and running within 4 hours after a disaster. While the restore procedure of the database takes longer then 4 hours.

A SharePoint environment that uses multiple site collections has a more disperse data set up than one that uses a single site collection set up. That causes two major implications:

  1. Site Collection queries are often used within web parts and also within custom code. However, Cross site collection queries are not possible, so getting the data from the content database and displaying it to the end user may be a little harder.
  2. Content types are inherently defined at Site Collection level. Microsoft did provide a solution so content types can be synchronized but this also takes additional effort.

In this posting I want to present the case where we have to deal with multiple site collections sharing a single content type. The data found in multiple lists distributed across these multiple site collections need to be returned and displayed in a single list this list needs to be security trimmed. The result of the example will be a “start page” on with the user will see all the site collections which are available to him. The site collection administrators are capable of granting access or retracting it for their own site collection. They are also capable of adding multiple access points towards sub sites that are defined within their site collection. 

Create a content type and set up a content type hub to publish the content type towards the other subscribing site collections

  1. Create a new Site Collection based on “Team Site” and give it a short URL like CTH (Content Type Hub)
    image
  2. Open the site collection and site collection features and  enable the Content Type Syndication Hub feature on the site collection.
    image
  3. Connect the content type syndication hub to the Managed Metadata Service.
    1. Within Central Administration go to Application management, click manage service applications. Find your Managed Metadata Service highlight the bar (do not click the URL). Now click the properties button in the bar. Scroll all the way down and enter the URL to the site collection created in step 1.image
    2. Select the managed metadata service bar and click properties.
      image
    3. Turn on “Consumes content types from the Content Type Gallery at http:….”
      image
  4. Create a content type that need to get published towards some other “subscriber” site collection.
    1. Open the Content Type Hub site Collection, go to the site settings and add the following site columns (you could use others of course):
      • SiteInfo_Name_Full
      • SiteInfo_Name_Abbreviation
      • SiteInfo_Description
      • SiteInfo_Contact_Information
      • SiteInfo_Keywords
      • SiteInfo_URL
    2. go to the site settings create a new content type and add the site columns to it:
      • SiteInformation
  5. Publish the SiteInformation contenttype towards the subscribing site collections.
    Within the site content type open the SiteInformation content type (if it’s not open already) and click the link “Manage publishing for this content type”. Now Publish the content type.
    image
  6. The default set up of SharePoint will use a timer job this time job will synchronize the content type within the hour. However, this is not convenient in a testing or a developer environment. To speed things up the timer job can be instructed to start immediately.
    In Central Administration under Monitoring  there is a link called “Review job definitions”. When clicking on that link a screen will show all running and scheduled jobs. The Content Type Hub and the Content Type Subscriber jobs are listed.
    image
    Clicking the links will enable setting up a different synchronization schedule it will also enable to run the job immediately.

After the synchronization has taken place the new content type should be available in all other subscribing site collections.

Use the Published Content Type in a SharePoint list.

  1. Create or use one of the site collections that consumed the published content type.
    From within Central Administration click the link Create Site Collections and create a site collection. Click the link to open the root site within the site collection.
  2. Create a list within one of the sites within the consuming site collection.
    Click Gear Icon, Add an App. Click Custom List, give the list a name for instance “SiteInformation”.
  3. Correct the content type to the list.
    Navigate towards the list and open the “List” Tab, click on list settings.
    Click on Advanced Settings
    image
    Set “Allow management of content types?” to Yes and press OK.
    Back on the list settings page there are new links added for adding site content types and setting the default content type.
    image
    Add the content type by clicking “Add from existing content types” and selecting the content type that was created on the Hub.
    Click the “Change new button order and default content type” , Remove the check before the “Item” content type and click OK
    image
  4. Enter some data based using the content type.
    image
Set up Search to produce a list filtered on the content type.

To get started with this part I’d like to reference my previous post Customizing Search in SharePoint 2013

Additional Reading

(1) Software boundaries and limits for SharePoint 2013